Applied Alex Kiernan's patch for the API change in BDB 4.3.
[exim.git] / src / src / exim_dbmbuild.c
CommitLineData
1f922db1 1/* $Cambridge: exim/src/src/exim_dbmbuild.c,v 1.3 2005/06/14 10:32:01 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));
210 fclose(f);
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);
237 return 1;
238 }
239
240 if (line[0] == '#') continue;
241 while (p > line && isspace(p[-1])) p--;
242 *p = 0;
243 if (line[0] == 0) continue;
244
245 /* A continuation line is valid only if there was a previous first
246 line. */
247
248 if (isspace(line[0]))
249 {
250 uschar *s = line;
251 if (!started)
252 {
253 printf("Unexpected continuation line ignored\n%s\n\n", line);
254 continue;
255 }
256 while (isspace(*s)) s++;
257 *(--s) = ' ';
258
259 if (bptr - buffer + p - s >= max_outsize - 1)
260 {
261 printf("Continued set of lines is too long: max permitted length is %d\n",
262 max_outsize -1);
263 return 1;
264 }
265
266 Ustrcpy(bptr, s);
267 bptr += p - s;
268 }
269
270 /* A first line must have a name followed by a colon or whitespace or
271 end of line, but first finish with a previous line. The key is lower
272 cased by default - this is what the newaliases program for sendmail does.
273 However, there's an option not to do this. */
274
275 else
276 {
277 int i, rc;
278 uschar *s = line;
279 uschar *keystart;
280
281 if (started)
282 {
283 EXIM_DATUM_INIT(content);
284 EXIM_DATUM_DATA(content) = CS buffer;
285 EXIM_DATUM_SIZE(content) = bptr - buffer + add_zero;
286
287 switch(rc = EXIM_DBPUTB(d, key, content))
288 {
289 case EXIM_DBPUTB_OK:
290 count++;
291 break;
292
293 case EXIM_DBPUTB_DUP:
294 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n",
295 keybuffer);
296 dupcount++;
297 if(duperr) yield = 1;
298 if (lastdup) EXIM_DBPUT(d, key, content);
299 break;
300
301 default:
302 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
303 keybuffer, errno);
304 yield = 2;
305 goto TIDYUP;
306 }
307
308 bptr = buffer;
309 }
310
311 EXIM_DATUM_INIT(key);
312 EXIM_DATUM_DATA(key) = CS keybuffer;
313
314 /* Deal with quoted keys. Escape sequences always make one character
315 out of several, so we can re-build in place. */
316
317 if (*s == '\"')
318 {
319 uschar *t = s++;
320 keystart = t;
321 while (*s != 0 && *s != '\"')
322 {
323 if (*s == '\\') *t++ = string_interpret_escape(&s);
324 else *t++ = *s;
325 s++;
326 }
327 if (*s != 0) s++; /* Past terminating " */
328 EXIM_DATUM_SIZE(key) = t - keystart + add_zero;
329 }
330 else
331 {
332 keystart = s;
333 while (*s != 0 && *s != ':' && !isspace(*s)) s++;
334 EXIM_DATUM_SIZE(key) = s - keystart + add_zero;
335 }
336
337 if (EXIM_DATUM_SIZE(key) > 256)
338 {
339 printf("Keys longer than 255 characters cannot be handled\n");
340 started = 0;
341 yield = 2;
342 goto TIDYUP;
343 }
344
345 if (lowercase)
346 {
347 for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
348 keybuffer[i] = tolower(keystart[i]);
349 }
350 else
351 {
352 for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
353 keybuffer[i] = keystart[i];
354 }
355
356 keybuffer[i] = 0;
357 started = 1;
358
359 while (isspace(*s))s++;
360 if (*s == ':')
361 {
362 s++;
363 while (isspace(*s))s++;
364 }
365 if (*s != 0)
366 {
367 Ustrcpy(bptr, s);
368 bptr += p - s;
369 }
370 else buffer[0] = 0;
371 }
372 }
373
374if (started)
375 {
376 int rc;
377 EXIM_DATUM_INIT(content);
378 EXIM_DATUM_DATA(content) = CS buffer;
379 EXIM_DATUM_SIZE(content) = bptr - buffer + add_zero;
380
381 switch(rc = EXIM_DBPUTB(d, key, content))
382 {
383 case EXIM_DBPUTB_OK:
384 count++;
385 break;
386
387 case EXIM_DBPUTB_DUP:
388 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer);
389 dupcount++;
390 if (duperr) yield = 1;
391 if (lastdup) EXIM_DBPUT(d, key, content);
392 break;
393
394 default:
395 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
396 keybuffer, errno);
397 yield = 2;
398 break;
399 }
400 }
401
402/* Close files, rename or abandon the temporary files, and exit */
403
404TIDYUP:
405
406EXIM_DBCLOSE(d);
407fclose(f);
408
409/* If successful, output the number of entries and rename the temporary
410files. */
411
412if (yield == 0 || yield == 1)
413 {
414 printf("%d entr%s written\n", count, (count == 1)? "y" : "ies");
415 if (dupcount > 0)
416 {
417 printf("%d duplicate key%s \n", dupcount, (dupcount > 1)? "s" : "");
418 }
419
420 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
421 Ustrcpy(real_dbmname, temp_dbmname);
422 Ustrcpy(buffer, argv[arg+1]);
423 if (Urename(real_dbmname, buffer) != 0)
424 {
425 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
426 return 1;
427 }
428 #else
429
430 /* Rename a single .db file */
431
432 if (is_db)
433 {
434 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
435 sprintf(CS buffer, "%s.db", argv[arg+1]);
436 if (Urename(real_dbmname, buffer) != 0)
437 {
438 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
439 return 1;
440 }
441 }
442
443 /* Rename .dir and .pag files */
444
445 else
446 {
447 sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
448 sprintf(CS buffer, "%s.dir", argv[arg+1]);
449 if (Urename(real_dbmname, buffer) != 0)
450 {
451 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
452 return 1;
453 }
454
455 sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
456 sprintf(CS buffer, "%s.pag", argv[arg+1]);
457 if (Urename(real_dbmname, buffer) != 0)
458 {
459 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
460 return 1;
461 }
462 }
463
464 #endif /* USE_DB || USE_TDB || USE_GDBM */
465 }
466
467/* Otherwise unlink the temporary files. */
468
469else
470 {
471 printf("dbmbuild abandoned\n");
472 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
473 Uunlink(temp_dbmname);
474 #else
475 if (is_db)
476 {
477 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
478 Uunlink(real_dbmname);
479 }
480 else
481 {
482 sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
483 Uunlink(real_dbmname);
484 sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
485 Uunlink(real_dbmname);
486 }
487 #endif /* USE_DB || USE_TDB */
488 }
489
490return yield;
491}
492
493/* End of exim_dbmbuild.c */