Fix taint issue with retry records. Bug 2492
[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 /* 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
10 alias files, this program fulfils the function of the newaliases program used
11 by other mailers, but it can be used for other dbm data files too. It operates
12 by writing a new file or files, and then renaming; otherwise old entries can
13 never get flushed out.
14
15 This 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
18 defined) there is no extension to the output filename. This is also handled. If
19 there are any other variants, the program won't cope.
20
21 The first argument to the program is the name of the serial file; the second
22 is the base name for the DBM file(s). When native db is in use, these must be
23 different.
24
25 Input lines beginning with # are ignored, as are blank lines. Entries begin
26 with a key terminated by a colon or end of line or whitespace and continue with
27 indented lines. Keys may be quoted if they contain colons or whitespace or #
28 characters. */
29
30
31 #include "exim.h"
32
33 uschar * spool_directory = NULL; /* dummy for dbstuff.h */
34
35 /******************************************************************************/
36 /* dummies needed by Solaris build */
37 void
38 millisleep(int msec)
39 {}
40 uschar *
41 readconf_printtime(int t)
42 { return NULL; }
43 void *
44 store_get_3(int size, BOOL tainted, const char *filename, int linenumber)
45 { return NULL; }
46 void **
47 store_reset_3(void **ptr, int pool, const char *filename, int linenumber)
48 { return NULL; }
49 void
50 store_release_above_3(void *ptr, const char *func, int linenumber)
51 { }
52 gstring *
53 string_vformat_trc(gstring * g, const uschar * func, unsigned line,
54 unsigned size_limit, unsigned flags, const char *format, va_list ap)
55 { return NULL; }
56 uschar *
57 string_sprintf_trc(const char * a, const uschar * b, unsigned c, ...)
58 { return NULL; }
59
60 struct global_flags f;
61 unsigned int log_selector[1];
62 uschar * queue_name;
63 BOOL split_spool_directory;
64 /******************************************************************************/
65
66
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
71 if it is made static. */
72
73 const uschar *hex_digits = CUS"0123456789abcdef";
74
75
76 #ifdef STRERROR_FROM_ERRLIST
77 /* Some old-fashioned systems still around (e.g. SunOS4) don't have strerror()
78 in their libraries, but can provide the same facility by this simple
79 alternative function. */
80
81 char *
82 strerror(int n)
83 {
84 if (n < 0 || n >= sys_nerr) return "unknown error number";
85 return 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
91 errors. This should help with debugging strange DB problems, e.g. getting "File
92 exists" when you try to open a db file. The API changed at release 4.3. */
93
94 #if defined(USE_DB) && defined(DB_VERSION_STRING)
95 void
96 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
97 dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
98 {
99 dbenv = dbenv;
100 #else
101 dbfn_bdb_error_callback(const char *pfx, char *msg)
102 {
103 #endif
104 pfx = pfx;
105 printf("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
117 Arguments:
118 pp points a pointer to the initiating "\" in the string;
119 the pointer gets updated to point to the final character
120 Returns: the value of the character escape
121 */
122
123 int
124 string_interpret_escape(const uschar **pp)
125 {
126 int ch;
127 const uschar *p = *pp;
128 ch = *(++p);
129 if (ch == '\0') return **pp;
130 if (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 }
140 else 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;
157 return ch;
158 }
159
160
161 /*************************************************
162 * Main Program *
163 *************************************************/
164
165 int main(int argc, char **argv)
166 {
167 int started;
168 int count = 0;
169 int dupcount = 0;
170 int yield = 0;
171 int arg = 1;
172 int add_zero = 1;
173 BOOL lowercase = TRUE;
174 BOOL warn = TRUE;
175 BOOL duperr = TRUE;
176 BOOL lastdup = FALSE;
177 #if !defined (USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
178 int is_db = 0;
179 struct stat statbuf;
180 #endif
181 FILE *f;
182 EXIM_DB *d;
183 EXIM_DATUM key, content;
184 uschar *bptr;
185 uschar keybuffer[256];
186 uschar temp_dbmname[512];
187 uschar real_dbmname[512];
188 uschar dirname[512];
189 uschar *buffer = malloc(max_outsize);
190 uschar *line = malloc(max_insize);
191
192 while (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
204 if (argc != 3)
205 {
206 printf("usage: exim_dbmbuild [-nolc] <source file> <dbm base name>\n");
207 exit(1);
208 }
209
210 if (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
221 can be painful! */
222
223 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
224 if (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
231 /* Check length of filename; allow for adding .dbmbuild_temp and .db or
232 .dir/.pag later. */
233
234 if (strlen(argv[arg+1]) > sizeof(temp_dbmname) - 20)
235 {
236 printf("exim_dbmbuild: output filename is ridiculously long\n");
237 exit(1);
238 }
239
240 Ustrcpy(temp_dbmname, US argv[arg+1]);
241 Ustrcat(temp_dbmname, US".dbmbuild_temp");
242
243 Ustrcpy(dirname, temp_dbmname);
244 if ((bptr = Ustrrchr(dirname, '/')))
245 *bptr = '\0';
246 else
247 Ustrcpy(dirname, US".");
248
249 /* It is apparently necessary to open with O_RDWR for this to work
250 with gdbm-1.7.3, though no reading is actually going to be done. */
251
252 EXIM_DBOPEN(temp_dbmname, dirname, O_RDWR|O_CREAT|O_EXCL, 0644, &d);
253
254 if (d == NULL)
255 {
256 printf("exim_dbmbuild: unable to create %s: %s\n", temp_dbmname,
257 strerror(errno));
258 (void)fclose(f);
259 exit(1);
260 }
261
262 /* Unless using native db calls, see if we have created <name>.db; if not,
263 assume .dir & .pag */
264
265 #if !defined(USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
266 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
267 is_db = Ustat(real_dbmname, &statbuf) == 0;
268 #endif
269
270 /* Now do the business */
271
272 bptr = buffer;
273 started = 0;
274
275 while (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);
285 yield = 2;
286 goto TIDYUP;
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);
312 yield = 2;
313 goto TIDYUP;
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:
340 count++;
341 break;
342
343 case EXIM_DBPUTB_DUP:
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;
349
350 default:
351 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
352 keybuffer, errno);
353 yield = 2;
354 goto TIDYUP;
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 {
372 *t++ = *s == '\\'
373 ? string_interpret_escape((const uschar **)&s)
374 : *s;
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)
396 for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
397 keybuffer[i] = tolower(keystart[i]);
398 else
399 for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
400 keybuffer[i] = keystart[i];
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
420 if (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
450 TIDYUP:
451
452 EXIM_DBCLOSE(d);
453 (void)fclose(f);
454
455 /* If successful, output the number of entries and rename the temporary
456 files. */
457
458 if (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);
468 Ustrcpy(buffer, US argv[arg+1]);
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
515 else
516 {
517 printf("dbmbuild abandoned\n");
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 */
520 /* coverity[tainted_string] */
521 Uunlink(temp_dbmname);
522 #else
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 }
535 #endif /* USE_DB || USE_TDB */
536 }
537
538 return yield;
539 }
540
541 /* End of exim_dbmbuild.c */