02da079f696c77bb038da97509a84f28fce371b7
[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)
216   f = stdin;
217 else if (!(f = fopen(argv[arg], "rb")))
218   {
219   printf("exim_dbmbuild: unable to open %s: %s\n", argv[arg], strerror(errno));
220   exit(1);
221   }
222
223 /* By default Berkeley db does not put extensions on... which
224 can be painful! */
225
226 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
227 if (Ustrcmp(argv[arg], argv[arg+1]) == 0)
228   {
229   printf("exim_dbmbuild: input and output filenames are the same\n");
230   exit(1);
231   }
232 #endif
233
234 /* Check length of filename; allow for adding .dbmbuild_temp and .db or
235 .dir/.pag later. */
236
237 if (strlen(argv[arg+1]) > sizeof(temp_dbmname) - 20)
238   {
239   printf("exim_dbmbuild: output filename is ridiculously long\n");
240   exit(1);
241   }
242
243 Ustrcpy(temp_dbmname, US argv[arg+1]);
244 Ustrcat(temp_dbmname, US".dbmbuild_temp");
245
246 Ustrcpy(dirname, temp_dbmname);
247 if ((bptr = Ustrrchr(dirname, '/')))
248   *bptr = '\0';
249 else
250   Ustrcpy(dirname, US".");
251
252 /* It is apparently necessary to open with O_RDWR for this to work
253 with gdbm-1.7.3, though no reading is actually going to be done. */
254
255 EXIM_DBOPEN(temp_dbmname, dirname, O_RDWR|O_CREAT|O_EXCL, 0644, &d);
256
257 if (d == NULL)
258   {
259   printf("exim_dbmbuild: unable to create %s: %s\n", temp_dbmname,
260     strerror(errno));
261   (void)fclose(f);
262   exit(1);
263   }
264
265 /* Unless using native db calls, see if we have created <name>.db; if not,
266 assume .dir & .pag */
267
268 #if !defined(USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
269 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
270 is_db = Ustat(real_dbmname, &statbuf) == 0;
271 #endif
272
273 /* Now do the business */
274
275 bptr = buffer;
276 started = 0;
277
278 while (Ufgets(line, max_insize, f) != NULL)
279   {
280   uschar *p;
281   int len = Ustrlen(line);
282
283   p = line + len;
284
285   if (len >= max_insize - 1 && p[-1] != '\n')
286     {
287     printf("Overlong line read: max permitted length is %d\n", max_insize - 1);
288     yield = 2;
289     goto TIDYUP;
290     }
291
292   if (line[0] == '#') continue;
293   while (p > line && isspace(p[-1])) p--;
294   *p = 0;
295   if (line[0] == 0) continue;
296
297   /* A continuation line is valid only if there was a previous first
298   line. */
299
300   if (isspace(line[0]))
301     {
302     uschar *s = line;
303     if (!started)
304       {
305       printf("Unexpected continuation line ignored\n%s\n\n", line);
306       continue;
307       }
308     while (isspace(*s)) s++;
309     *(--s) = ' ';
310
311     if (bptr - buffer + p - s >= max_outsize - 1)
312       {
313       printf("Continued set of lines is too long: max permitted length is %d\n",
314         max_outsize -1);
315       yield = 2;
316       goto TIDYUP;
317       }
318
319     Ustrcpy(bptr, s);
320     bptr += p - s;
321     }
322
323   /* A first line must have a name followed by a colon or whitespace or
324   end of line, but first finish with a previous line. The key is lower
325   cased by default - this is what the newaliases program for sendmail does.
326   However, there's an option not to do this. */
327
328   else
329     {
330     int i, rc;
331     uschar *s = line;
332     uschar *keystart;
333
334     if (started)
335       {
336       EXIM_DATUM_INIT(content);
337       EXIM_DATUM_DATA(content) = CS buffer;
338       EXIM_DATUM_SIZE(content) = bptr - buffer + add_zero;
339
340       switch(rc = EXIM_DBPUTB(d, key, content))
341         {
342         case EXIM_DBPUTB_OK:
343           count++;
344           break;
345
346         case EXIM_DBPUTB_DUP:
347           if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer);
348           dupcount++;
349           if(duperr) yield = 1;
350           if (lastdup) EXIM_DBPUT(d, key, content);
351           break;
352
353         default:
354           fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
355             keybuffer, errno);
356           yield = 2;
357           goto TIDYUP;
358         }
359
360       bptr = buffer;
361       }
362
363     EXIM_DATUM_INIT(key);
364     EXIM_DATUM_DATA(key) = CS keybuffer;
365
366     /* Deal with quoted keys. Escape sequences always make one character
367     out of several, so we can re-build in place. */
368
369     if (*s == '\"')
370       {
371       uschar *t = s++;
372       keystart = t;
373       while (*s != 0 && *s != '\"')
374         {
375         *t++ = *s == '\\'
376         ? string_interpret_escape((const uschar **)&s)
377         : *s;
378         s++;
379         }
380       if (*s != 0) s++;               /* Past terminating " */
381       EXIM_DATUM_SIZE(key) = t - keystart + add_zero;
382       }
383     else
384       {
385       keystart = s;
386       while (*s != 0 && *s != ':' && !isspace(*s)) s++;
387       EXIM_DATUM_SIZE(key) = s - keystart + add_zero;
388       }
389
390     if (EXIM_DATUM_SIZE(key) > 256)
391       {
392       printf("Keys longer than 255 characters cannot be handled\n");
393       started = 0;
394       yield = 2;
395       goto TIDYUP;
396       }
397
398     if (lowercase)
399       for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
400         keybuffer[i] = tolower(keystart[i]);
401     else
402       for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
403         keybuffer[i] = keystart[i];
404
405     keybuffer[i] = 0;
406     started = 1;
407
408     while (isspace(*s))s++;
409     if (*s == ':')
410       {
411       s++;
412       while (isspace(*s))s++;
413       }
414     if (*s != 0)
415       {
416       Ustrcpy(bptr, s);
417       bptr += p - s;
418       }
419     else buffer[0] = 0;
420     }
421   }
422
423 if (started)
424   {
425   int rc;
426   EXIM_DATUM_INIT(content);
427   EXIM_DATUM_DATA(content) = CS buffer;
428   EXIM_DATUM_SIZE(content) = bptr - buffer + add_zero;
429
430   switch(rc = EXIM_DBPUTB(d, key, content))
431     {
432     case EXIM_DBPUTB_OK:
433     count++;
434     break;
435
436     case EXIM_DBPUTB_DUP:
437     if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer);
438     dupcount++;
439     if (duperr) yield = 1;
440     if (lastdup) EXIM_DBPUT(d, key, content);
441     break;
442
443     default:
444     fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
445       keybuffer, errno);
446     yield = 2;
447     break;
448     }
449   }
450
451 /* Close files, rename or abandon the temporary files, and exit */
452
453 TIDYUP:
454
455 EXIM_DBCLOSE(d);
456 (void)fclose(f);
457
458 /* If successful, output the number of entries and rename the temporary
459 files. */
460
461 if (yield == 0 || yield == 1)
462   {
463   printf("%d entr%s written\n", count, (count == 1)? "y" : "ies");
464   if (dupcount > 0)
465     {
466     printf("%d duplicate key%s \n", dupcount, (dupcount > 1)? "s" : "");
467     }
468
469   #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
470   Ustrcpy(real_dbmname, temp_dbmname);
471   Ustrcpy(buffer, US argv[arg+1]);
472   if (Urename(real_dbmname, buffer) != 0)
473     {
474     printf("Unable to rename %s as %s\n", real_dbmname, buffer);
475     return 1;
476     }
477   #else
478
479   /* Rename a single .db file */
480
481   if (is_db)
482     {
483     sprintf(CS real_dbmname, "%s.db", temp_dbmname);
484     sprintf(CS buffer, "%s.db", argv[arg+1]);
485     if (Urename(real_dbmname, buffer) != 0)
486       {
487       printf("Unable to rename %s as %s\n", real_dbmname, buffer);
488       return 1;
489       }
490     }
491
492   /* Rename .dir and .pag files */
493
494   else
495     {
496     sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
497     sprintf(CS buffer, "%s.dir", argv[arg+1]);
498     if (Urename(real_dbmname, buffer) != 0)
499       {
500       printf("Unable to rename %s as %s\n", real_dbmname, buffer);
501       return 1;
502       }
503
504     sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
505     sprintf(CS buffer, "%s.pag", argv[arg+1]);
506     if (Urename(real_dbmname, buffer) != 0)
507       {
508       printf("Unable to rename %s as %s\n", real_dbmname, buffer);
509       return 1;
510       }
511     }
512
513   #endif /* USE_DB || USE_TDB || USE_GDBM */
514   }
515
516 /* Otherwise unlink the temporary files. */
517
518 else
519   {
520   printf("dbmbuild abandoned\n");
521 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
522   /* We created it, so safe to delete despite the name coming from outside */
523   /* coverity[tainted_string] */
524   Uunlink(temp_dbmname);
525 #else
526   if (is_db)
527     {
528     sprintf(CS real_dbmname, "%s.db", temp_dbmname);
529     Uunlink(real_dbmname);
530     }
531   else
532     {
533     sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
534     Uunlink(real_dbmname);
535     sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
536     Uunlink(real_dbmname);
537     }
538 #endif /* USE_DB || USE_TDB */
539   }
540
541 return yield;
542 }
543
544 /* End of exim_dbmbuild.c */