1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) The Exim Maintainers 2020 - 2022 */
6 /* Copyright (c) University of Cambridge 1995 - 2018 */
7 /* See the file NOTICE for conditions of use and distribution. */
8 /* SPDX-License-Identifier: GPL-2.0-or-later */
11 /* A small freestanding program to build dbm databases from serial input. For
12 alias files, this program fulfils the function of the newaliases program used
13 by other mailers, but it can be used for other dbm data files too. It operates
14 by writing a new file or files, and then renaming; otherwise old entries can
15 never get flushed out.
17 This program is clever enough to cope with ndbm, which creates two files called
18 <name>.dir and <name>.pag, or with db, which creates a single file called
19 <name>.db. If native db is in use (USE_DB defined) or tdb is in use (USE_TDB
20 defined) there is no extension to the output filename. This is also handled. If
21 there are any other variants, the program won't cope.
23 The first argument to the program is the name of the serial file; the second
24 is the base name for the DBM file(s). When native db is in use, these must be
27 Input lines beginning with # are ignored, as are blank lines. Entries begin
28 with a key terminated by a colon or end of line or whitespace and continue with
29 indented lines. Keys may be quoted if they contain colons or whitespace or #
35 uschar * spool_directory = NULL; /* dummy for hintsdb.h */
37 /******************************************************************************/
38 /* dummies needed by Solaris build */
43 readconf_printtime(int t)
46 store_get_3(int size, const void * proto_mem, const char *filename, int linenumber)
49 store_reset_3(void **ptr, const char *filename, int linenumber)
52 store_release_above_3(void *ptr, const char *func, int linenumber)
55 string_catn(gstring * g, const uschar * s, int count)
58 string_vformat_trc(gstring * g, const uschar * func, unsigned line,
59 unsigned size_limit, unsigned flags, const char *format, va_list ap)
62 string_sprintf_trc(const char * a, const uschar * b, unsigned c, ...)
65 string_format_trc(uschar * buf, int len, const uschar * func, unsigned line,
66 const char * fmt, ...)
69 log_write(unsigned int selector, int flags, const char *format, ...)
73 struct global_flags f;
74 unsigned int log_selector[1];
76 BOOL split_spool_directory;
79 /* These introduced by the taintwarn handling */
81 store_mark_3(const char *func, int linenumber)
83 #ifdef ALLOW_INSECURE_TAINTED_DATA
84 BOOL allow_insecure_tainted_data;
87 /******************************************************************************/
90 #define max_insize 20000
91 #define max_outsize 100000
93 /* This is global because it's defined in the headers and compilers grumble
94 if it is made static. */
96 const uschar *hex_digits = CUS"0123456789abcdef";
99 #ifdef STRERROR_FROM_ERRLIST
100 /* Some old-fashioned systems still around (e.g. SunOS4) don't have strerror()
101 in their libraries, but can provide the same facility by this simple
102 alternative function. */
107 if (n < 0 || n >= sys_nerr) return "unknown error number";
108 return sys_errlist[n];
110 #endif /* STRERROR_FROM_ERRLIST */
114 /*************************************************
115 * Interpret escape sequence *
116 *************************************************/
118 /* This function is copied from the main Exim code.
121 pp points a pointer to the initiating "\" in the string;
122 the pointer gets updated to point to the final character
123 Returns: the value of the character escape
127 string_interpret_escape(const uschar **pp)
130 const uschar *p = *pp;
132 if (ch == '\0') return **pp;
133 if (isdigit(ch) && ch != '8' && ch != '9')
136 if (isdigit(p[1]) && p[1] != '8' && p[1] != '9')
138 ch = ch * 8 + *(++p) - '0';
139 if (isdigit(p[1]) && p[1] != '8' && p[1] != '9')
140 ch = ch * 8 + *(++p) - '0';
145 case 'n': ch = '\n'; break;
146 case 'r': ch = '\r'; break;
147 case 't': ch = '\t'; break;
153 Ustrchr(hex_digits, tolower(*(++p))) - hex_digits;
154 if (isxdigit(p[1])) ch = ch * 16 +
155 Ustrchr(hex_digits, tolower(*(++p))) - hex_digits;
164 /*************************************************
166 *************************************************/
168 int main(int argc, char **argv)
176 BOOL lowercase = TRUE;
179 BOOL lastdup = FALSE;
180 #if !defined (USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
186 EXIM_DATUM key, content;
188 uschar keybuffer[256];
189 uschar temp_dbmname[512];
190 uschar real_dbmname[512];
192 uschar *buffer = malloc(max_outsize);
193 uschar *line = malloc(max_insize);
197 if (Ustrcmp(argv[arg], "-nolc") == 0) lowercase = FALSE;
198 else if (Ustrcmp(argv[arg], "-nowarn") == 0) warn = FALSE;
199 else if (Ustrcmp(argv[arg], "-lastdup") == 0) lastdup = TRUE;
200 else if (Ustrcmp(argv[arg], "-noduperr") == 0) duperr = FALSE;
201 else if (Ustrcmp(argv[arg], "-nozero") == 0) add_zero = 0;
209 printf("usage: exim_dbmbuild [-nolc] <source file> <dbm base name>\n");
213 if (Ustrcmp(argv[arg], "-") == 0)
215 else if (!(f = fopen(argv[arg], "rb")))
217 printf("exim_dbmbuild: unable to open %s: %s\n", argv[arg], strerror(errno));
221 /* By default Berkeley db does not put extensions on... which
224 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
225 if (Ustrcmp(argv[arg], argv[arg+1]) == 0)
227 printf("exim_dbmbuild: input and output filenames are the same\n");
232 /* Check length of filename; allow for adding .dbmbuild_temp and .db or
235 if (strlen(argv[arg+1]) > sizeof(temp_dbmname) - 20)
237 printf("exim_dbmbuild: output filename is ridiculously long\n");
241 Ustrcpy(temp_dbmname, US argv[arg+1]);
242 Ustrcat(temp_dbmname, US".dbmbuild_temp");
244 Ustrcpy(dirname, temp_dbmname);
245 if ((bptr = Ustrrchr(dirname, '/')))
248 Ustrcpy(dirname, US".");
250 /* It is apparently necessary to open with O_RDWR for this to work
251 with gdbm-1.7.3, though no reading is actually going to be done. */
253 if (!(d = exim_dbopen(temp_dbmname, dirname, O_RDWR|O_CREAT|O_EXCL, 0644)))
255 printf("exim_dbmbuild: unable to create %s: %s\n", temp_dbmname,
261 /* Unless using native db calls, see if we have created <name>.db; if not,
262 assume .dir & .pag */
264 #if !defined(USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
265 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
266 is_db = Ustat(real_dbmname, &statbuf) == 0;
269 /* Now do the business */
274 while (Ufgets(line, max_insize, f) != NULL)
277 int len = Ustrlen(line);
281 if (len >= max_insize - 1 && p[-1] != '\n')
283 printf("Overlong line read: max permitted length is %d\n", max_insize - 1);
288 if (line[0] == '#') continue;
289 while (p > line && isspace(p[-1])) p--;
291 if (line[0] == 0) continue;
293 /* A continuation line is valid only if there was a previous first
296 if (isspace(line[0]))
301 printf("Unexpected continuation line ignored\n%s\n\n", line);
304 while (isspace(*s)) s++;
307 if (bptr - buffer + p - s >= max_outsize - 1)
309 printf("Continued set of lines is too long: max permitted length is %d\n",
319 /* A first line must have a name followed by a colon or whitespace or
320 end of line, but first finish with a previous line. The key is lower
321 cased by default - this is what the newaliases program for sendmail does.
322 However, there's an option not to do this. */
332 exim_datum_init(&content);
333 exim_datum_data_set(&content, buffer);
334 exim_datum_size_set(&content, bptr - buffer + add_zero);
336 switch(rc = exim_dbputb(d, &key, &content))
342 case EXIM_DBPUTB_DUP:
343 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer);
345 if(duperr) yield = 1;
346 if (lastdup) exim_dbput(d, &key, &content);
350 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
359 exim_datum_init(&key);
360 exim_datum_data_set(&key, keybuffer);
362 /* Deal with quoted keys. Escape sequences always make one character
363 out of several, so we can re-build in place. */
369 while (*s != 0 && *s != '\"')
372 ? string_interpret_escape((const uschar **)&s)
376 if (*s != 0) s++; /* Past terminating " */
377 exim_datum_size_set(&key, t - keystart + add_zero);
382 while (*s != 0 && *s != ':' && !isspace(*s)) s++;
383 exim_datum_size_set(&key, s - keystart + add_zero);
386 if (exim_datum_size_get(&key) > 256)
388 printf("Keys longer than 255 characters cannot be handled\n");
395 for (i = 0; i < exim_datum_size_get(&key) - add_zero; i++)
396 keybuffer[i] = tolower(keystart[i]);
398 for (i = 0; i < exim_datum_size_get(&key) - add_zero; i++)
399 keybuffer[i] = keystart[i];
404 while (isspace(*s))s++;
408 while (isspace(*s))s++;
422 exim_datum_init(&content);
423 exim_datum_data_set(&content, buffer);
424 exim_datum_size_set(&content, bptr - buffer + add_zero);
426 switch(rc = exim_dbputb(d, &key, &content))
432 case EXIM_DBPUTB_DUP:
433 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer);
435 if (duperr) yield = 1;
436 if (lastdup) exim_dbput(d, &key, &content);
440 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
447 /* Close files, rename or abandon the temporary files, and exit */
454 /* If successful, output the number of entries and rename the temporary
457 if (yield == 0 || yield == 1)
459 printf("%d entr%s written\n", count, (count == 1)? "y" : "ies");
462 printf("%d duplicate key%s \n", dupcount, (dupcount > 1)? "s" : "");
465 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
466 Ustrcpy(real_dbmname, temp_dbmname);
467 Ustrcpy(buffer, US argv[arg+1]);
468 if (Urename(real_dbmname, buffer) != 0)
470 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
475 /* Rename a single .db file */
479 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
480 sprintf(CS buffer, "%s.db", argv[arg+1]);
481 if (Urename(real_dbmname, buffer) != 0)
483 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
488 /* Rename .dir and .pag files */
492 sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
493 sprintf(CS buffer, "%s.dir", argv[arg+1]);
494 if (Urename(real_dbmname, buffer) != 0)
496 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
500 sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
501 sprintf(CS buffer, "%s.pag", argv[arg+1]);
502 if (Urename(real_dbmname, buffer) != 0)
504 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
509 #endif /* USE_DB || USE_TDB || USE_GDBM */
512 /* Otherwise unlink the temporary files. */
516 printf("dbmbuild abandoned\n");
517 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
518 /* We created it, so safe to delete despite the name coming from outside */
519 /* coverity[tainted_string] */
520 Uunlink(temp_dbmname);
524 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
525 Uunlink(real_dbmname);
529 sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
530 Uunlink(real_dbmname);
531 sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
532 Uunlink(real_dbmname);
534 #endif /* USE_DB || USE_TDB */
540 /* End of exim_dbmbuild.c */