e07a47ac131918614e62d964ef641a08976920ef
[users/jgh/exim.git] / src / src / exim_dbmbuild.c
1 /* $Cambridge: exim/src/src/exim_dbmbuild.c,v 1.8 2007/01/08 10:50:18 ph10 Exp $ */
2
3 /*************************************************
4 *     Exim - an Internet mail transport agent    *
5 *************************************************/
6
7 /* Copyright (c) University of Cambridge 1995 - 2007 */
8 /* See the file NOTICE for conditions of use and distribution. */
9
10
11 /* A small freestanding program to build dbm databases from serial input. For
12 alias files, this program fulfils the function of the newaliases program used
13 by other mailers, but it can be used for other dbm data files too. It operates
14 by writing a new file or files, and then renaming; otherwise old entries can
15 never get flushed out.
16
17 This program is clever enough to cope with ndbm, which creates two files called
18 <name>.dir and <name>.pag, or with db, which creates a single file called
19 <name>.db. If native db is in use (USE_DB defined) or tdb is in use (USE_TDB
20 defined) there is no extension to the output filename. This is also handled. If
21 there are any other variants, the program won't cope.
22
23 The first argument to the program is the name of the serial file; the second
24 is the base name for the DBM file(s). When native db is in use, these must be
25 different.
26
27 Input lines beginning with # are ignored, as are blank lines. Entries begin
28 with a key terminated by a colon or end of line or whitespace and continue with
29 indented lines. Keys may be quoted if they contain colons or whitespace or #
30 characters. */
31
32
33 #include "exim.h"
34
35
36 #define max_insize   20000
37 #define max_outsize 100000
38
39 /* This is global because it's defined in the headers and compilers grumble
40 if it is made static. */
41
42 uschar *hex_digits = US"0123456789abcdef";
43
44
45 #ifdef STRERROR_FROM_ERRLIST
46 /* Some old-fashioned systems still around (e.g. SunOS4) don't have strerror()
47 in their libraries, but can provide the same facility by this simple
48 alternative function. */
49
50 char *
51 strerror(int n)
52 {
53 if (n < 0 || n >= sys_nerr) return "unknown error number";
54 return sys_errlist[n];
55 }
56 #endif /* STRERROR_FROM_ERRLIST */
57
58
59 /* For Berkeley DB >= 2, we can define a function to be called in case of DB
60 errors. This should help with debugging strange DB problems, e.g. getting "File
61 exists" when you try to open a db file. The API changed at release 4.3. */
62
63 #if defined(USE_DB) && defined(DB_VERSION_STRING)
64 void
65 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
66 dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
67 {
68 dbenv = dbenv;
69 #else
70 dbfn_bdb_error_callback(const char *pfx, char *msg)
71 {
72 #endif
73 pfx = pfx;
74 printf("Berkeley DB error: %s\n", msg);
75 }
76 #endif
77
78
79
80 /*************************************************
81 *          Interpret escape sequence             *
82 *************************************************/
83
84 /* This function is copied from the main Exim code.
85
86 Arguments:
87   pp       points a pointer to the initiating "\" in the string;
88            the pointer gets updated to point to the final character
89 Returns:   the value of the character escape
90 */
91
92 int
93 string_interpret_escape(uschar **pp)
94 {
95 int ch;
96 uschar *p = *pp;
97 ch = *(++p);
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 *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 /* It is apparently necessary to open with O_RDWR for this to work
211 with gdbm-1.7.3, though no reading is actually going to be done. */
212
213 EXIM_DBOPEN(temp_dbmname, O_RDWR|O_CREAT|O_EXCL, 0644, &d);
214
215 if (d == NULL)
216   {
217   printf("exim_dbmbuild: unable to create %s: %s\n", temp_dbmname,
218     strerror(errno));
219   (void)fclose(f);
220   exit(1);
221   }
222
223 /* Unless using native db calls, see if we have created <name>.db; if not,
224 assume .dir & .pag */
225
226 #if !defined(USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
227 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
228 is_db = Ustat(real_dbmname, &statbuf) == 0;
229 #endif
230
231 /* Now do the business */
232
233 bptr = buffer;
234 started = 0;
235
236 while (Ufgets(line, max_insize, f) != NULL)
237   {
238   uschar *p;
239   int len = Ustrlen(line);
240
241   p = line + len;
242
243   if (len >= max_insize - 1 && p[-1] != '\n')
244     {
245     printf("Overlong line read: max permitted length is %d\n", max_insize - 1);
246     yield = 2;
247     goto TIDYUP;
248     }
249
250   if (line[0] == '#') continue;
251   while (p > line && isspace(p[-1])) p--;
252   *p = 0;
253   if (line[0] == 0) continue;
254
255   /* A continuation line is valid only if there was a previous first
256   line. */
257
258   if (isspace(line[0]))
259     {
260     uschar *s = line;
261     if (!started)
262       {
263       printf("Unexpected continuation line ignored\n%s\n\n", line);
264       continue;
265       }
266     while (isspace(*s)) s++;
267     *(--s) = ' ';
268
269     if (bptr - buffer + p - s >= max_outsize - 1)
270       {
271       printf("Continued set of lines is too long: max permitted length is %d\n",
272         max_outsize -1);
273       yield = 2;
274       goto TIDYUP;
275       }
276
277     Ustrcpy(bptr, s);
278     bptr += p - s;
279     }
280
281   /* A first line must have a name followed by a colon or whitespace or
282   end of line, but first finish with a previous line. The key is lower
283   cased by default - this is what the newaliases program for sendmail does.
284   However, there's an option not to do this. */
285
286   else
287     {
288     int i, rc;
289     uschar *s = line;
290     uschar *keystart;
291
292     if (started)
293       {
294       EXIM_DATUM_INIT(content);
295       EXIM_DATUM_DATA(content) = CS buffer;
296       EXIM_DATUM_SIZE(content) = bptr - buffer + add_zero;
297
298       switch(rc = EXIM_DBPUTB(d, key, content))
299         {
300         case EXIM_DBPUTB_OK:
301         count++;
302         break;
303
304         case EXIM_DBPUTB_DUP:
305         if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n",
306           keybuffer);
307         dupcount++;
308         if(duperr) yield = 1;
309         if (lastdup) EXIM_DBPUT(d, key, content);
310         break;
311
312         default:
313         fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
314           keybuffer, errno);
315         yield = 2;
316         goto TIDYUP;
317         }
318
319       bptr = buffer;
320       }
321
322     EXIM_DATUM_INIT(key);
323     EXIM_DATUM_DATA(key) = CS keybuffer;
324
325     /* Deal with quoted keys. Escape sequences always make one character
326     out of several, so we can re-build in place. */
327
328     if (*s == '\"')
329       {
330       uschar *t = s++;
331       keystart = t;
332       while (*s != 0 && *s != '\"')
333         {
334         if (*s == '\\') *t++ = string_interpret_escape(&s);
335           else *t++ = *s;
336         s++;
337         }
338       if (*s != 0) s++;               /* Past terminating " */
339       EXIM_DATUM_SIZE(key) = t - keystart + add_zero;
340       }
341     else
342       {
343       keystart = s;
344       while (*s != 0 && *s != ':' && !isspace(*s)) s++;
345       EXIM_DATUM_SIZE(key) = s - keystart + add_zero;
346       }
347
348     if (EXIM_DATUM_SIZE(key) > 256)
349       {
350       printf("Keys longer than 255 characters cannot be handled\n");
351       started = 0;
352       yield = 2;
353       goto TIDYUP;
354       }
355
356     if (lowercase)
357       {
358       for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
359         keybuffer[i] = tolower(keystart[i]);
360       }
361     else
362       {
363       for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
364         keybuffer[i] = keystart[i];
365       }
366
367     keybuffer[i] = 0;
368     started = 1;
369
370     while (isspace(*s))s++;
371     if (*s == ':')
372       {
373       s++;
374       while (isspace(*s))s++;
375       }
376     if (*s != 0)
377       {
378       Ustrcpy(bptr, s);
379       bptr += p - s;
380       }
381     else buffer[0] = 0;
382     }
383   }
384
385 if (started)
386   {
387   int rc;
388   EXIM_DATUM_INIT(content);
389   EXIM_DATUM_DATA(content) = CS buffer;
390   EXIM_DATUM_SIZE(content) = bptr - buffer + add_zero;
391
392   switch(rc = EXIM_DBPUTB(d, key, content))
393     {
394     case EXIM_DBPUTB_OK:
395     count++;
396     break;
397
398     case EXIM_DBPUTB_DUP:
399     if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer);
400     dupcount++;
401     if (duperr) yield = 1;
402     if (lastdup) EXIM_DBPUT(d, key, content);
403     break;
404
405     default:
406     fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
407       keybuffer, errno);
408     yield = 2;
409     break;
410     }
411   }
412
413 /* Close files, rename or abandon the temporary files, and exit */
414
415 TIDYUP:
416
417 EXIM_DBCLOSE(d);
418 (void)fclose(f);
419
420 /* If successful, output the number of entries and rename the temporary
421 files. */
422
423 if (yield == 0 || yield == 1)
424   {
425   printf("%d entr%s written\n", count, (count == 1)? "y" : "ies");
426   if (dupcount > 0)
427     {
428     printf("%d duplicate key%s \n", dupcount, (dupcount > 1)? "s" : "");
429     }
430
431   #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
432   Ustrcpy(real_dbmname, temp_dbmname);
433   Ustrcpy(buffer, argv[arg+1]);
434   if (Urename(real_dbmname, buffer) != 0)
435     {
436     printf("Unable to rename %s as %s\n", real_dbmname, buffer);
437     return 1;
438     }
439   #else
440
441   /* Rename a single .db file */
442
443   if (is_db)
444     {
445     sprintf(CS real_dbmname, "%s.db", temp_dbmname);
446     sprintf(CS buffer, "%s.db", argv[arg+1]);
447     if (Urename(real_dbmname, buffer) != 0)
448       {
449       printf("Unable to rename %s as %s\n", real_dbmname, buffer);
450       return 1;
451       }
452     }
453
454   /* Rename .dir and .pag files */
455
456   else
457     {
458     sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
459     sprintf(CS buffer, "%s.dir", argv[arg+1]);
460     if (Urename(real_dbmname, buffer) != 0)
461       {
462       printf("Unable to rename %s as %s\n", real_dbmname, buffer);
463       return 1;
464       }
465
466     sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
467     sprintf(CS buffer, "%s.pag", argv[arg+1]);
468     if (Urename(real_dbmname, buffer) != 0)
469       {
470       printf("Unable to rename %s as %s\n", real_dbmname, buffer);
471       return 1;
472       }
473     }
474
475   #endif /* USE_DB || USE_TDB || USE_GDBM */
476   }
477
478 /* Otherwise unlink the temporary files. */
479
480 else
481   {
482   printf("dbmbuild abandoned\n");
483   #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
484   Uunlink(temp_dbmname);
485   #else
486   if (is_db)
487     {
488     sprintf(CS real_dbmname, "%s.db", temp_dbmname);
489     Uunlink(real_dbmname);
490     }
491   else
492     {
493     sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
494     Uunlink(real_dbmname);
495     sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
496     Uunlink(real_dbmname);
497     }
498   #endif /* USE_DB || USE_TDB */
499   }
500
501 return yield;
502 }
503
504 /* End of exim_dbmbuild.c */