CVS-ing the new test suite.
[exim.git] / src / src / exim_dbmbuild.c
CommitLineData
aa2b5c79 1/* $Cambridge: exim/src/src/exim_dbmbuild.c,v 1.5 2005/08/30 09:19:33 ph10 Exp $ */
059ec3d9
PH
2
3/*************************************************
4* Exim - an Internet mail transport agent *
5*************************************************/
6
c988f1f4 7/* Copyright (c) University of Cambridge 1995 - 2005 */
059ec3d9
PH
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
12alias files, this program fulfils the function of the newaliases program used
13by other mailers, but it can be used for other dbm data files too. It operates
14by writing a new file or files, and then renaming; otherwise old entries can
15never get flushed out.
16
17This 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
20defined) there is no extension to the output filename. This is also handled. If
21there are any other variants, the program won't cope.
22
23The first argument to the program is the name of the serial file; the second
24is the base name for the DBM file(s). When native db is in use, these must be
25different.
26
27Input lines beginning with # are ignored, as are blank lines. Entries begin
28with a key terminated by a colon or end of line or whitespace and continue with
29indented lines. Keys may be quoted if they contain colons or whitespace or #
30characters. */
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
40if it is made static. */
41
42uschar *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()
47in their libraries, but can provide the same facility by this simple
48alternative function. */
49
50char *
51strerror(int n)
52{
53if (n < 0 || n >= sys_nerr) return "unknown error number";
54return 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
60errors. This should help with debugging strange DB problems, e.g. getting "File
1f922db1 61exists" when you try to open a db file. The API changed at release 4.3. */
059ec3d9
PH
62
63#if defined(USE_DB) && defined(DB_VERSION_STRING)
64void
1f922db1
PH
65#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
66dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
67{
68dbenv = dbenv;
69#else
059ec3d9
PH
70dbfn_bdb_error_callback(const char *pfx, char *msg)
71{
1f922db1 72#endif
059ec3d9
PH
73pfx = pfx;
74printf("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
86Arguments:
87 pp points a pointer to the initiating "\" in the string;
88 the pointer gets updated to point to the final character
89Returns: the value of the character escape
90*/
91
92int
93string_interpret_escape(uschar **pp)
94{
95int ch;
96uschar *p = *pp;
97ch = *(++p);
98if (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 }
108else 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;
125return ch;
126}
127
128
129/*************************************************
130* Main Program *
131*************************************************/
132
133int main(int argc, char **argv)
134{
135int started;
136int count = 0;
137int dupcount = 0;
138int yield = 0;
139int arg = 1;
140int add_zero = 1;
141BOOL lowercase = TRUE;
142BOOL warn = TRUE;
143BOOL duperr = TRUE;
144BOOL lastdup = FALSE;
145#if !defined (USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
146int is_db = 0;
147struct stat statbuf;
148#endif
149FILE *f;
150EXIM_DB *d;
151EXIM_DATUM key, content;
152uschar *bptr;
153uschar keybuffer[256];
154uschar temp_dbmname[256];
155uschar real_dbmname[256];
156uschar *buffer = malloc(max_outsize);
157uschar *line = malloc(max_insize);
158
159while (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
171if (argc != 3)
172 {
173 printf("usage: exim_dbmbuild [-nolc] <source file> <dbm base name>\n");
174 exit(1);
175 }
176
177if (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
188can be painful! */
189
190#if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
191if (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
198Ustrcpy(temp_dbmname, argv[arg+1]);
199Ustrcat(temp_dbmname, ".dbmbuild_temp");
200
201/* It is apparently necessary to open with O_RDWR for this to work
202with gdbm-1.7.3, though no reading is actually going to be done. */
203
204EXIM_DBOPEN(temp_dbmname, O_RDWR|O_CREAT|O_EXCL, 0644, &d);
205
206if (d == NULL)
207 {
208 printf("exim_dbmbuild: unable to create %s: %s\n", temp_dbmname,
209 strerror(errno));
f1e894f3 210 (void)fclose(f);
059ec3d9
PH
211 exit(1);
212 }
213
214/* Unless using native db calls, see if we have created <name>.db; if not,
215assume .dir & .pag */
216
217#if !defined(USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
218sprintf(CS real_dbmname, "%s.db", temp_dbmname);
219is_db = Ustat(real_dbmname, &statbuf) == 0;
220#endif
221
222/* Now do the business */
223
224bptr = buffer;
225started = 0;
226
227while (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);
aa2b5c79
PH
237 yield = 2;
238 goto TIDYUP;
059ec3d9
PH
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);
aa2b5c79
PH
264 yield = 2;
265 goto TIDYUP;
059ec3d9
PH
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
376if (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
406TIDYUP:
407
408EXIM_DBCLOSE(d);
f1e894f3 409(void)fclose(f);
059ec3d9
PH
410
411/* If successful, output the number of entries and rename the temporary
412files. */
413
414if (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
471else
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
492return yield;
493}
494
495/* End of exim_dbmbuild.c */