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