e4194f37151c6aad7d4eb24c3b447c9917c6bf68
[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 /* 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
39 millisleep(int msec)
40 {}
41 uschar *
42 readconf_printtime(int t)
43 { return NULL; }
44 void *
45 store_get_3(int size, BOOL tainted, const char *filename, int linenumber)
46 { return NULL; }
47 void **
48 store_reset_3(void **ptr, const char *filename, int linenumber)
49 { return NULL; }
50 void
51 store_release_above_3(void *ptr, const char *func, int linenumber)
52 { }
53 gstring *
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 *
58 string_sprintf_trc(const char * a, const uschar * b, unsigned c, ...)
59 { return NULL; }
60 BOOL
61 string_format_trc(uschar * buf, int len, const uschar * func, unsigned line,
62   const char * fmt, ...)
63 { return FALSE; }
64 void
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
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 *
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)
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 */