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