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