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