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