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