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