Rework SPA fix to avoid overflows. Bug 2571
[exim.git] / src / src / exim_dbmbuild.c
1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
4
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
6 /* Copyright (c) The Exim Maintainers 2020 */
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
11 alias files, this program fulfils the function of the newaliases program used
12 by other mailers, but it can be used for other dbm data files too. It operates
13 by writing a new file or files, and then renaming; otherwise old entries can
14 never get flushed out.
15
16 This 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
19 defined) there is no extension to the output filename. This is also handled. If
20 there are any other variants, the program won't cope.
21
22 The first argument to the program is the name of the serial file; the second
23 is the base name for the DBM file(s). When native db is in use, these must be
24 different.
25
26 Input lines beginning with # are ignored, as are blank lines. Entries begin
27 with a key terminated by a colon or end of line or whitespace and continue with
28 indented lines. Keys may be quoted if they contain colons or whitespace or #
29 characters. */
30
31
32 #include "exim.h"
33
34 uschar * spool_directory = NULL; /* dummy for dbstuff.h */
35
36 /******************************************************************************/
37 /* dummies needed by Solaris build */
38 void
39 millisleep(int msec)
40 {}
41 uschar *
42 readconf_printtime(int t)
43 { return NULL; }
44 void *
45 store_get_3(int size, BOOL tainted, const char *filename, int linenumber)
46 { return NULL; }
47 void **
48 store_reset_3(void **ptr, int pool, const char *filename, int linenumber)
49 { return NULL; }
50 void
51 store_release_above_3(void *ptr, const char *func, int linenumber)
52 { }
53 gstring *
54 string_vformat_trc(gstring * g, const uschar * func, unsigned line,
55 unsigned size_limit, unsigned flags, const char *format, va_list ap)
56 { return NULL; }
57 uschar *
58 string_sprintf_trc(const char * a, const uschar * b, unsigned c, ...)
59 { return NULL; }
60 BOOL
61 string_format_trc(uschar * buf, int len, const uschar * func, unsigned line,
62 const char * fmt, ...)
63 { return FALSE; }
64 void
65 log_write(unsigned int selector, int flags, const char *format, ...)
66 { }
67
68
69 struct global_flags f;
70 unsigned int log_selector[1];
71 uschar * queue_name;
72 BOOL split_spool_directory;
73 /******************************************************************************/
74
75
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
80 if it is made static. */
81
82 const uschar *hex_digits = CUS"0123456789abcdef";
83
84
85 #ifdef STRERROR_FROM_ERRLIST
86 /* Some old-fashioned systems still around (e.g. SunOS4) don't have strerror()
87 in their libraries, but can provide the same facility by this simple
88 alternative function. */
89
90 char *
91 strerror(int n)
92 {
93 if (n < 0 || n >= sys_nerr) return "unknown error number";
94 return 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
100 errors. This should help with debugging strange DB problems, e.g. getting "File
101 exists" when you try to open a db file. The API changed at release 4.3. */
102
103 #if defined(USE_DB) && defined(DB_VERSION_STRING)
104 void
105 # if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
106 dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
107 {
108 dbenv = dbenv;
109 # else
110 dbfn_bdb_error_callback(const char *pfx, char *msg)
111 {
112 # endif
113 pfx = pfx;
114 printf("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
126 Arguments:
127 pp points a pointer to the initiating "\" in the string;
128 the pointer gets updated to point to the final character
129 Returns: the value of the character escape
130 */
131
132 int
133 string_interpret_escape(const uschar **pp)
134 {
135 int ch;
136 const uschar *p = *pp;
137 ch = *(++p);
138 if (ch == '\0') return **pp;
139 if (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 }
149 else 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;
166 return ch;
167 }
168
169
170 /*************************************************
171 * Main Program *
172 *************************************************/
173
174 int main(int argc, char **argv)
175 {
176 int started;
177 int count = 0;
178 int dupcount = 0;
179 int yield = 0;
180 int arg = 1;
181 int add_zero = 1;
182 BOOL lowercase = TRUE;
183 BOOL warn = TRUE;
184 BOOL duperr = TRUE;
185 BOOL lastdup = FALSE;
186 #if !defined (USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
187 int is_db = 0;
188 struct stat statbuf;
189 #endif
190 FILE *f;
191 EXIM_DB *d;
192 EXIM_DATUM key, content;
193 uschar *bptr;
194 uschar keybuffer[256];
195 uschar temp_dbmname[512];
196 uschar real_dbmname[512];
197 uschar dirname[512];
198 uschar *buffer = malloc(max_outsize);
199 uschar *line = malloc(max_insize);
200
201 while (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
213 if (argc != 3)
214 {
215 printf("usage: exim_dbmbuild [-nolc] <source file> <dbm base name>\n");
216 exit(1);
217 }
218
219 if (Ustrcmp(argv[arg], "-") == 0)
220 f = stdin;
221 else if (!(f = fopen(argv[arg], "rb")))
222 {
223 printf("exim_dbmbuild: unable to open %s: %s\n", argv[arg], strerror(errno));
224 exit(1);
225 }
226
227 /* By default Berkeley db does not put extensions on... which
228 can be painful! */
229
230 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
231 if (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
238 /* Check length of filename; allow for adding .dbmbuild_temp and .db or
239 .dir/.pag later. */
240
241 if (strlen(argv[arg+1]) > sizeof(temp_dbmname) - 20)
242 {
243 printf("exim_dbmbuild: output filename is ridiculously long\n");
244 exit(1);
245 }
246
247 Ustrcpy(temp_dbmname, US argv[arg+1]);
248 Ustrcat(temp_dbmname, US".dbmbuild_temp");
249
250 Ustrcpy(dirname, temp_dbmname);
251 if ((bptr = Ustrrchr(dirname, '/')))
252 *bptr = '\0';
253 else
254 Ustrcpy(dirname, US".");
255
256 /* It is apparently necessary to open with O_RDWR for this to work
257 with gdbm-1.7.3, though no reading is actually going to be done. */
258
259 EXIM_DBOPEN(temp_dbmname, dirname, O_RDWR|O_CREAT|O_EXCL, 0644, &d);
260
261 if (d == NULL)
262 {
263 printf("exim_dbmbuild: unable to create %s: %s\n", temp_dbmname,
264 strerror(errno));
265 (void)fclose(f);
266 exit(1);
267 }
268
269 /* Unless using native db calls, see if we have created <name>.db; if not,
270 assume .dir & .pag */
271
272 #if !defined(USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
273 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
274 is_db = Ustat(real_dbmname, &statbuf) == 0;
275 #endif
276
277 /* Now do the business */
278
279 bptr = buffer;
280 started = 0;
281
282 while (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);
292 yield = 2;
293 goto TIDYUP;
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);
319 yield = 2;
320 goto TIDYUP;
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:
347 count++;
348 break;
349
350 case EXIM_DBPUTB_DUP:
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;
356
357 default:
358 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
359 keybuffer, errno);
360 yield = 2;
361 goto TIDYUP;
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 {
379 *t++ = *s == '\\'
380 ? string_interpret_escape((const uschar **)&s)
381 : *s;
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)
403 for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
404 keybuffer[i] = tolower(keystart[i]);
405 else
406 for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
407 keybuffer[i] = keystart[i];
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
427 if (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
457 TIDYUP:
458
459 EXIM_DBCLOSE(d);
460 (void)fclose(f);
461
462 /* If successful, output the number of entries and rename the temporary
463 files. */
464
465 if (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);
475 Ustrcpy(buffer, US argv[arg+1]);
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
522 else
523 {
524 printf("dbmbuild abandoned\n");
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 */
527 /* coverity[tainted_string] */
528 Uunlink(temp_dbmname);
529 #else
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 }
542 #endif /* USE_DB || USE_TDB */
543 }
544
545 return yield;
546 }
547
548 /* End of exim_dbmbuild.c */