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