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