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