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