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