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