tidying
[exim.git] / src / src / exim_dbmbuild.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) The Exim Maintainers 2020 - 2023 */
6 /* Copyright (c) University of Cambridge 1995 - 2018 */
7 /* See the file NOTICE for conditions of use and distribution. */
8 /* SPDX-License-Identifier: GPL-2.0-or-later */
9
10
11 /* A small freestanding program to build dbm databases from serial input. For
12 alias files, this program fulfils the function of the newaliases program used
13 by other mailers, but it can be used for other dbm data files too. It operates
14 by writing a new file or files, and then renaming; otherwise old entries can
15 never get flushed out.
16
17 This program is clever enough to cope with ndbm, which creates two files called
18 <name>.dir and <name>.pag, or with db, which creates a single file called
19 <name>.db. If native db is in use (USE_DB defined) or tdb is in use (USE_TDB
20 defined) there is no extension to the output filename. This is also handled. If
21 there are any other variants, the program won't cope.
22
23 The first argument to the program is the name of the serial file; the second
24 is the base name for the DBM file(s). When native db is in use, these must be
25 different.
26
27 Input lines beginning with # are ignored, as are blank lines. Entries begin
28 with a key terminated by a colon or end of line or whitespace and continue with
29 indented lines. Keys may be quoted if they contain colons or whitespace or #
30 characters. */
31
32
33 #include "exim.h"
34
35 uschar * spool_directory = NULL;        /* dummy for hintsdb.h */
36
37 /******************************************************************************/
38                                         /* dummies needed by Solaris build */
39 void
40 millisleep(int msec)
41 {}
42 uschar *
43 readconf_printtime(int t)
44 { return NULL; }
45 void *
46 store_get_3(int size, const void * proto_mem, const char *filename, int linenumber)
47 { return NULL; }
48 void **
49 store_reset_3(void **ptr, const char *filename, int linenumber)
50 { return NULL; }
51 void
52 store_release_above_3(void *ptr, const char *func, int linenumber)
53 { }
54 gstring *
55 string_catn(gstring * g, const uschar * s, int count)
56 { return NULL; }
57 gstring *
58 string_vformat_trc(gstring * g, const uschar * func, unsigned line,
59   unsigned size_limit, unsigned flags, const char *format, va_list ap)
60 { return NULL; }
61 uschar *
62 string_sprintf_trc(const char * a, const uschar * b, unsigned c, ...)
63 { return NULL; }
64 BOOL
65 string_format_trc(uschar * buf, int len, const uschar * func, unsigned line,
66   const char * fmt, ...)
67 { return FALSE; }
68 void
69 log_write(unsigned int selector, int flags, const char *format, ...)
70 { }
71
72
73 struct global_flags     f;
74 unsigned int            log_selector[1];
75 uschar *                queue_name;
76 BOOL                    split_spool_directory;
77
78
79 /* These introduced by the taintwarn handling */
80 rmark
81 store_mark_3(const char *func, int linenumber)
82 { return NULL; }
83 #ifdef ALLOW_INSECURE_TAINTED_DATA
84 BOOL    allow_insecure_tainted_data;
85 #endif
86
87 /******************************************************************************/
88
89
90 #define max_insize   20000
91 #define max_outsize 100000
92
93 /* This is global because it's defined in the headers and compilers grumble
94 if it is made static. */
95
96 const uschar *hex_digits = CUS"0123456789abcdef";
97
98
99 #ifdef STRERROR_FROM_ERRLIST
100 /* Some old-fashioned systems still around (e.g. SunOS4) don't have strerror()
101 in their libraries, but can provide the same facility by this simple
102 alternative function. */
103
104 char *
105 strerror(int n)
106 {
107 if (n < 0 || n >= sys_nerr) return "unknown error number";
108 return sys_errlist[n];
109 }
110 #endif /* STRERROR_FROM_ERRLIST */
111
112
113
114 /*************************************************
115 *          Interpret escape sequence             *
116 *************************************************/
117
118 /* This function is copied from the main Exim code.
119
120 Arguments:
121   pp       points a pointer to the initiating "\" in the string;
122            the pointer gets updated to point to the final character
123 Returns:   the value of the character escape
124 */
125
126 int
127 string_interpret_escape(const uschar **pp)
128 {
129 int ch;
130 const uschar *p = *pp;
131 ch = *(++p);
132 if (ch == '\0') return **pp;
133 if (isdigit(ch) && ch != '8' && ch != '9')
134   {
135   ch -= '0';
136   if (isdigit(p[1]) && p[1] != '8' && p[1] != '9')
137     {
138     ch = ch * 8 + *(++p) - '0';
139     if (isdigit(p[1]) && p[1] != '8' && p[1] != '9')
140       ch = ch * 8 + *(++p) - '0';
141     }
142   }
143 else switch(ch)
144   {
145   case 'n':  ch = '\n'; break;
146   case 'r':  ch = '\r'; break;
147   case 't':  ch = '\t'; break;
148   case 'x':
149   ch = 0;
150   if (isxdigit(p[1]))
151     {
152     ch = ch * 16 +
153       Ustrchr(hex_digits, tolower(*(++p))) - hex_digits;
154     if (isxdigit(p[1])) ch = ch * 16 +
155       Ustrchr(hex_digits, tolower(*(++p))) - hex_digits;
156     }
157   break;
158   }
159 *pp = p;
160 return ch;
161 }
162
163
164 /*************************************************
165 *               Main Program                     *
166 *************************************************/
167
168 int main(int argc, char **argv)
169 {
170 int started;
171 int count = 0;
172 int dupcount = 0;
173 int yield = 0;
174 int arg = 1;
175 int add_zero = 1;
176 BOOL lowercase = TRUE;
177 BOOL warn = TRUE;
178 BOOL duperr = TRUE;
179 BOOL lastdup = FALSE;
180 #if !defined (USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
181 int is_db = 0;
182 struct stat statbuf;
183 #endif
184 FILE *f;
185 EXIM_DB *d;
186 EXIM_DATUM key, content;
187 uschar *bptr;
188 uschar  keybuffer[256];
189 uschar  temp_dbmname[512];
190 uschar  real_dbmname[512];
191 uschar  dirname[512];
192 uschar *buffer = malloc(max_outsize);
193 uschar *line = malloc(max_insize);
194
195 while (argc > 1)
196   {
197   if      (Ustrcmp(argv[arg], "-nolc") == 0)     lowercase = FALSE;
198   else if (Ustrcmp(argv[arg], "-nowarn") == 0)   warn = FALSE;
199   else if (Ustrcmp(argv[arg], "-lastdup") == 0)  lastdup = TRUE;
200   else if (Ustrcmp(argv[arg], "-noduperr") == 0) duperr = FALSE;
201   else if (Ustrcmp(argv[arg], "-nozero") == 0)   add_zero = 0;
202   else break;
203   arg++;
204   argc--;
205   }
206
207 if (argc != 3)
208   {
209   printf("usage: exim_dbmbuild [-nolc] <source file> <dbm base name>\n");
210   exit(1);
211   }
212
213 if (Ustrcmp(argv[arg], "-") == 0)
214   f = stdin;
215 else if (!(f = fopen(argv[arg], "rb")))
216   {
217   printf("exim_dbmbuild: unable to open %s: %s\n", argv[arg], strerror(errno));
218   exit(1);
219   }
220
221 /* By default Berkeley db does not put extensions on... which
222 can be painful! */
223
224 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
225 if (Ustrcmp(argv[arg], argv[arg+1]) == 0)
226   {
227   printf("exim_dbmbuild: input and output filenames are the same\n");
228   exit(1);
229   }
230 #endif
231
232 /* Check length of filename; allow for adding .dbmbuild_temp and .db or
233 .dir/.pag later. */
234
235 if (strlen(argv[arg+1]) > sizeof(temp_dbmname) - 20)
236   {
237   printf("exim_dbmbuild: output filename is ridiculously long\n");
238   exit(1);
239   }
240
241 Ustrcpy(temp_dbmname, US argv[arg+1]);
242 Ustrcat(temp_dbmname, US".dbmbuild_temp");
243
244 Ustrcpy(dirname, temp_dbmname);
245 if ((bptr = Ustrrchr(dirname, '/')))
246   *bptr = '\0';
247 else
248   Ustrcpy(dirname, US".");
249
250 /* It is apparently necessary to open with O_RDWR for this to work
251 with gdbm-1.7.3, though no reading is actually going to be done. */
252
253 if (!(d = exim_dbopen(temp_dbmname, dirname, O_RDWR|O_CREAT|O_EXCL, 0644)))
254   {
255   printf("exim_dbmbuild: unable to create %s: %s\n", temp_dbmname,
256     strerror(errno));
257   (void)fclose(f);
258   exit(1);
259   }
260
261 /* Unless using native db calls, see if we have created <name>.db; if not,
262 assume .dir & .pag */
263
264 #if !defined(USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
265 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
266 is_db = Ustat(real_dbmname, &statbuf) == 0;
267 #endif
268
269 /* Now do the business */
270
271 bptr = buffer;
272 started = 0;
273
274 while (Ufgets(line, max_insize, f) != NULL)
275   {
276   uschar *p;
277   int len = Ustrlen(line);
278
279   p = line + len;
280
281   if (len >= max_insize - 1 && p[-1] != '\n')
282     {
283     printf("Overlong line read: max permitted length is %d\n", max_insize - 1);
284     yield = 2;
285     goto TIDYUP;
286     }
287
288   if (line[0] == '#') continue;
289   while (p > line && isspace(p[-1])) p--;
290   *p = 0;
291   if (line[0] == 0) continue;
292
293   /* A continuation line is valid only if there was a previous first
294   line. */
295
296   if (isspace(line[0]))
297     {
298     uschar *s = line;
299     if (!started)
300       {
301       printf("Unexpected continuation line ignored\n%s\n\n", line);
302       continue;
303       }
304     while (isspace(*s)) s++;
305     *(--s) = ' ';
306
307     if (bptr - buffer + p - s >= max_outsize - 1)
308       {
309       printf("Continued set of lines is too long: max permitted length is %d\n",
310         max_outsize -1);
311       yield = 2;
312       goto TIDYUP;
313       }
314
315     Ustrcpy(bptr, s);
316     bptr += p - s;
317     }
318
319   /* A first line must have a name followed by a colon or whitespace or
320   end of line, but first finish with a previous line. The key is lower
321   cased by default - this is what the newaliases program for sendmail does.
322   However, there's an option not to do this. */
323
324   else
325     {
326     int i, rc;
327     uschar *s = line;
328     uschar *keystart;
329
330     if (started)
331       {
332       exim_datum_init(&content);
333       exim_datum_data_set(&content, buffer);
334       exim_datum_size_set(&content, bptr - buffer + add_zero);
335
336       switch(rc = exim_dbputb(d, &key, &content))
337         {
338         case EXIM_DBPUTB_OK:
339           count++;
340           break;
341
342         case EXIM_DBPUTB_DUP:
343           if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer);
344           dupcount++;
345           if(duperr) yield = 1;
346           if (lastdup) exim_dbput(d, &key, &content);
347           break;
348
349         default:
350           fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
351             keybuffer, errno);
352           yield = 2;
353           goto TIDYUP;
354         }
355
356       bptr = buffer;
357       }
358
359     exim_datum_init(&key);
360     exim_datum_data_set(&key, keybuffer);
361
362     /* Deal with quoted keys. Escape sequences always make one character
363     out of several, so we can re-build in place. */
364
365     if (*s == '\"')
366       {
367       uschar *t = s++;
368       keystart = t;
369       while (*s != 0 && *s != '\"')
370         {
371         *t++ = *s == '\\'
372         ? string_interpret_escape((const uschar **)&s)
373         : *s;
374         s++;
375         }
376       if (*s != 0) s++;               /* Past terminating " */
377       exim_datum_size_set(&key, t - keystart + add_zero);
378       }
379     else
380       {
381       keystart = s;
382       while (*s && *s != ':' && !isspace(*s)) s++;
383       exim_datum_size_set(&key, s - keystart + add_zero);
384       }
385
386     if (exim_datum_size_get(&key) > 256)
387       {
388       printf("Keys longer than 255 characters cannot be handled\n");
389       started = 0;
390       yield = 2;
391       goto TIDYUP;
392       }
393
394     if (lowercase)
395       for (i = 0; i < exim_datum_size_get(&key) - add_zero; i++)
396         keybuffer[i] = tolower(keystart[i]);
397     else
398       for (i = 0; i < exim_datum_size_get(&key) - add_zero; i++)
399         keybuffer[i] = keystart[i];
400
401     keybuffer[i] = 0;
402     started = 1;
403
404     while (isspace(*s)) s++;
405     if (*s == ':')
406       {
407       s++;
408       while (isspace(*s)) s++;
409       }
410     if (*s != 0)
411       {
412       Ustrcpy(bptr, s);
413       bptr += p - s;
414       }
415     else buffer[0] = 0;
416     }
417   }
418
419 if (started)
420   {
421   int rc;
422   exim_datum_init(&content);
423   exim_datum_data_set(&content, buffer);
424   exim_datum_size_set(&content, bptr - buffer + add_zero);
425
426   switch(rc = exim_dbputb(d, &key, &content))
427     {
428     case EXIM_DBPUTB_OK:
429     count++;
430     break;
431
432     case EXIM_DBPUTB_DUP:
433     if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer);
434     dupcount++;
435     if (duperr) yield = 1;
436     if (lastdup) exim_dbput(d, &key, &content);
437     break;
438
439     default:
440     fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
441       keybuffer, errno);
442     yield = 2;
443     break;
444     }
445   }
446
447 /* Close files, rename or abandon the temporary files, and exit */
448
449 TIDYUP:
450
451 exim_dbclose(d);
452 (void)fclose(f);
453
454 /* If successful, output the number of entries and rename the temporary
455 files. */
456
457 if (yield == 0 || yield == 1)
458   {
459   printf("%d entr%s written\n", count, (count == 1)? "y" : "ies");
460   if (dupcount > 0)
461     {
462     printf("%d duplicate key%s \n", dupcount, (dupcount > 1)? "s" : "");
463     }
464
465   #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
466   Ustrcpy(real_dbmname, temp_dbmname);
467   Ustrcpy(buffer, US argv[arg+1]);
468   if (Urename(real_dbmname, buffer) != 0)
469     {
470     printf("Unable to rename %s as %s\n", real_dbmname, buffer);
471     return 1;
472     }
473   #else
474
475   /* Rename a single .db file */
476
477   if (is_db)
478     {
479     sprintf(CS real_dbmname, "%s.db", temp_dbmname);
480     sprintf(CS buffer, "%s.db", argv[arg+1]);
481     if (Urename(real_dbmname, buffer) != 0)
482       {
483       printf("Unable to rename %s as %s\n", real_dbmname, buffer);
484       return 1;
485       }
486     }
487
488   /* Rename .dir and .pag files */
489
490   else
491     {
492     sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
493     sprintf(CS buffer, "%s.dir", argv[arg+1]);
494     if (Urename(real_dbmname, buffer) != 0)
495       {
496       printf("Unable to rename %s as %s\n", real_dbmname, buffer);
497       return 1;
498       }
499
500     sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
501     sprintf(CS buffer, "%s.pag", argv[arg+1]);
502     if (Urename(real_dbmname, buffer) != 0)
503       {
504       printf("Unable to rename %s as %s\n", real_dbmname, buffer);
505       return 1;
506       }
507     }
508
509   #endif /* USE_DB || USE_TDB || USE_GDBM */
510   }
511
512 /* Otherwise unlink the temporary files. */
513
514 else
515   {
516   printf("dbmbuild abandoned\n");
517 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
518   /* We created it, so safe to delete despite the name coming from outside */
519   /* coverity[tainted_string] */
520   Uunlink(temp_dbmname);
521 #else
522   if (is_db)
523     {
524     sprintf(CS real_dbmname, "%s.db", temp_dbmname);
525     Uunlink(real_dbmname);
526     }
527   else
528     {
529     sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
530     Uunlink(real_dbmname);
531     sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
532     Uunlink(real_dbmname);
533     }
534 #endif /* USE_DB || USE_TDB */
535   }
536
537 return yield;
538 }
539
540 /* End of exim_dbmbuild.c */