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