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