1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
6 /* Copyright (c) The Exim Maintainers 2020 */
7 /* See the file NOTICE for conditions of use and distribution. */
10 /* A small freestanding program to build dbm databases from serial input. For
11 alias files, this program fulfils the function of the newaliases program used
12 by other mailers, but it can be used for other dbm data files too. It operates
13 by writing a new file or files, and then renaming; otherwise old entries can
14 never get flushed out.
16 This program is clever enough to cope with ndbm, which creates two files called
17 <name>.dir and <name>.pag, or with db, which creates a single file called
18 <name>.db. If native db is in use (USE_DB defined) or tdb is in use (USE_TDB
19 defined) there is no extension to the output filename. This is also handled. If
20 there are any other variants, the program won't cope.
22 The first argument to the program is the name of the serial file; the second
23 is the base name for the DBM file(s). When native db is in use, these must be
26 Input lines beginning with # are ignored, as are blank lines. Entries begin
27 with a key terminated by a colon or end of line or whitespace and continue with
28 indented lines. Keys may be quoted if they contain colons or whitespace or #
34 uschar * spool_directory = NULL; /* dummy for dbstuff.h */
36 /******************************************************************************/
37 /* dummies needed by Solaris build */
42 readconf_printtime(int t)
45 store_get_3(int size, BOOL tainted, const char *filename, int linenumber)
48 store_reset_3(void **ptr, int pool, const char *filename, int linenumber)
51 store_release_above_3(void *ptr, const char *func, int linenumber)
54 string_vformat_trc(gstring * g, const uschar * func, unsigned line,
55 unsigned size_limit, unsigned flags, const char *format, va_list ap)
58 string_sprintf_trc(const char * a, const uschar * b, unsigned c, ...)
61 string_format_trc(uschar * buf, int len, const uschar * func, unsigned line,
62 const char * fmt, ...)
65 log_write(unsigned int selector, int flags, const char *format, ...)
69 struct global_flags f;
70 unsigned int log_selector[1];
72 BOOL split_spool_directory;
73 /******************************************************************************/
76 #define max_insize 20000
77 #define max_outsize 100000
79 /* This is global because it's defined in the headers and compilers grumble
80 if it is made static. */
82 const uschar *hex_digits = CUS"0123456789abcdef";
85 #ifdef STRERROR_FROM_ERRLIST
86 /* Some old-fashioned systems still around (e.g. SunOS4) don't have strerror()
87 in their libraries, but can provide the same facility by this simple
88 alternative function. */
93 if (n < 0 || n >= sys_nerr) return "unknown error number";
94 return sys_errlist[n];
96 #endif /* STRERROR_FROM_ERRLIST */
99 /* For Berkeley DB >= 2, we can define a function to be called in case of DB
100 errors. This should help with debugging strange DB problems, e.g. getting "File
101 exists" when you try to open a db file. The API changed at release 4.3. */
103 #if defined(USE_DB) && defined(DB_VERSION_STRING)
105 # if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
106 dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
110 dbfn_bdb_error_callback(const char *pfx, char *msg)
114 printf("Berkeley DB error: %s\n", msg);
120 /*************************************************
121 * Interpret escape sequence *
122 *************************************************/
124 /* This function is copied from the main Exim code.
127 pp points a pointer to the initiating "\" in the string;
128 the pointer gets updated to point to the final character
129 Returns: the value of the character escape
133 string_interpret_escape(const uschar **pp)
136 const uschar *p = *pp;
138 if (ch == '\0') return **pp;
139 if (isdigit(ch) && ch != '8' && ch != '9')
142 if (isdigit(p[1]) && p[1] != '8' && p[1] != '9')
144 ch = ch * 8 + *(++p) - '0';
145 if (isdigit(p[1]) && p[1] != '8' && p[1] != '9')
146 ch = ch * 8 + *(++p) - '0';
151 case 'n': ch = '\n'; break;
152 case 'r': ch = '\r'; break;
153 case 't': ch = '\t'; break;
159 Ustrchr(hex_digits, tolower(*(++p))) - hex_digits;
160 if (isxdigit(p[1])) ch = ch * 16 +
161 Ustrchr(hex_digits, tolower(*(++p))) - hex_digits;
170 /*************************************************
172 *************************************************/
174 int main(int argc, char **argv)
182 BOOL lowercase = TRUE;
185 BOOL lastdup = FALSE;
186 #if !defined (USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
192 EXIM_DATUM key, content;
194 uschar keybuffer[256];
195 uschar temp_dbmname[512];
196 uschar real_dbmname[512];
198 uschar *buffer = malloc(max_outsize);
199 uschar *line = malloc(max_insize);
203 if (Ustrcmp(argv[arg], "-nolc") == 0) lowercase = FALSE;
204 else if (Ustrcmp(argv[arg], "-nowarn") == 0) warn = FALSE;
205 else if (Ustrcmp(argv[arg], "-lastdup") == 0) lastdup = TRUE;
206 else if (Ustrcmp(argv[arg], "-noduperr") == 0) duperr = FALSE;
207 else if (Ustrcmp(argv[arg], "-nozero") == 0) add_zero = 0;
215 printf("usage: exim_dbmbuild [-nolc] <source file> <dbm base name>\n");
219 if (Ustrcmp(argv[arg], "-") == 0)
221 else if (!(f = fopen(argv[arg], "rb")))
223 printf("exim_dbmbuild: unable to open %s: %s\n", argv[arg], strerror(errno));
227 /* By default Berkeley db does not put extensions on... which
230 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
231 if (Ustrcmp(argv[arg], argv[arg+1]) == 0)
233 printf("exim_dbmbuild: input and output filenames are the same\n");
238 /* Check length of filename; allow for adding .dbmbuild_temp and .db or
241 if (strlen(argv[arg+1]) > sizeof(temp_dbmname) - 20)
243 printf("exim_dbmbuild: output filename is ridiculously long\n");
247 Ustrcpy(temp_dbmname, US argv[arg+1]);
248 Ustrcat(temp_dbmname, US".dbmbuild_temp");
250 Ustrcpy(dirname, temp_dbmname);
251 if ((bptr = Ustrrchr(dirname, '/')))
254 Ustrcpy(dirname, US".");
256 /* It is apparently necessary to open with O_RDWR for this to work
257 with gdbm-1.7.3, though no reading is actually going to be done. */
259 EXIM_DBOPEN(temp_dbmname, dirname, O_RDWR|O_CREAT|O_EXCL, 0644, &d);
263 printf("exim_dbmbuild: unable to create %s: %s\n", temp_dbmname,
269 /* Unless using native db calls, see if we have created <name>.db; if not,
270 assume .dir & .pag */
272 #if !defined(USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
273 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
274 is_db = Ustat(real_dbmname, &statbuf) == 0;
277 /* Now do the business */
282 while (Ufgets(line, max_insize, f) != NULL)
285 int len = Ustrlen(line);
289 if (len >= max_insize - 1 && p[-1] != '\n')
291 printf("Overlong line read: max permitted length is %d\n", max_insize - 1);
296 if (line[0] == '#') continue;
297 while (p > line && isspace(p[-1])) p--;
299 if (line[0] == 0) continue;
301 /* A continuation line is valid only if there was a previous first
304 if (isspace(line[0]))
309 printf("Unexpected continuation line ignored\n%s\n\n", line);
312 while (isspace(*s)) s++;
315 if (bptr - buffer + p - s >= max_outsize - 1)
317 printf("Continued set of lines is too long: max permitted length is %d\n",
327 /* A first line must have a name followed by a colon or whitespace or
328 end of line, but first finish with a previous line. The key is lower
329 cased by default - this is what the newaliases program for sendmail does.
330 However, there's an option not to do this. */
340 EXIM_DATUM_INIT(content);
341 EXIM_DATUM_DATA(content) = CS buffer;
342 EXIM_DATUM_SIZE(content) = bptr - buffer + add_zero;
344 switch(rc = EXIM_DBPUTB(d, key, content))
350 case EXIM_DBPUTB_DUP:
351 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer);
353 if(duperr) yield = 1;
354 if (lastdup) EXIM_DBPUT(d, key, content);
358 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
367 EXIM_DATUM_INIT(key);
368 EXIM_DATUM_DATA(key) = CS keybuffer;
370 /* Deal with quoted keys. Escape sequences always make one character
371 out of several, so we can re-build in place. */
377 while (*s != 0 && *s != '\"')
380 ? string_interpret_escape((const uschar **)&s)
384 if (*s != 0) s++; /* Past terminating " */
385 EXIM_DATUM_SIZE(key) = t - keystart + add_zero;
390 while (*s != 0 && *s != ':' && !isspace(*s)) s++;
391 EXIM_DATUM_SIZE(key) = s - keystart + add_zero;
394 if (EXIM_DATUM_SIZE(key) > 256)
396 printf("Keys longer than 255 characters cannot be handled\n");
403 for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
404 keybuffer[i] = tolower(keystart[i]);
406 for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
407 keybuffer[i] = keystart[i];
412 while (isspace(*s))s++;
416 while (isspace(*s))s++;
430 EXIM_DATUM_INIT(content);
431 EXIM_DATUM_DATA(content) = CS buffer;
432 EXIM_DATUM_SIZE(content) = bptr - buffer + add_zero;
434 switch(rc = EXIM_DBPUTB(d, key, content))
440 case EXIM_DBPUTB_DUP:
441 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer);
443 if (duperr) yield = 1;
444 if (lastdup) EXIM_DBPUT(d, key, content);
448 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
455 /* Close files, rename or abandon the temporary files, and exit */
462 /* If successful, output the number of entries and rename the temporary
465 if (yield == 0 || yield == 1)
467 printf("%d entr%s written\n", count, (count == 1)? "y" : "ies");
470 printf("%d duplicate key%s \n", dupcount, (dupcount > 1)? "s" : "");
473 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
474 Ustrcpy(real_dbmname, temp_dbmname);
475 Ustrcpy(buffer, US argv[arg+1]);
476 if (Urename(real_dbmname, buffer) != 0)
478 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
483 /* Rename a single .db file */
487 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
488 sprintf(CS buffer, "%s.db", argv[arg+1]);
489 if (Urename(real_dbmname, buffer) != 0)
491 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
496 /* Rename .dir and .pag files */
500 sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
501 sprintf(CS buffer, "%s.dir", argv[arg+1]);
502 if (Urename(real_dbmname, buffer) != 0)
504 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
508 sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
509 sprintf(CS buffer, "%s.pag", argv[arg+1]);
510 if (Urename(real_dbmname, buffer) != 0)
512 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
517 #endif /* USE_DB || USE_TDB || USE_GDBM */
520 /* Otherwise unlink the temporary files. */
524 printf("dbmbuild abandoned\n");
525 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
526 /* We created it, so safe to delete despite the name coming from outside */
527 /* coverity[tainted_string] */
528 Uunlink(temp_dbmname);
532 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
533 Uunlink(real_dbmname);
537 sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
538 Uunlink(real_dbmname);
539 sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
540 Uunlink(real_dbmname);
542 #endif /* USE_DB || USE_TDB */
548 /* End of exim_dbmbuild.c */