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