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