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