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