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