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