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