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_vformat_trc(gstring * g, const uschar * func, unsigned line,
56 unsigned size_limit, unsigned flags, const char *format, va_list ap)
59 string_sprintf_trc(const char * a, const uschar * b, unsigned c, ...)
62 string_format_trc(uschar * buf, int len, const uschar * func, unsigned line,
63 const char * fmt, ...)
66 log_write(unsigned int selector, int flags, const char *format, ...)
70 struct global_flags f;
71 unsigned int log_selector[1];
73 BOOL split_spool_directory;
76 /* These introduced by the taintwarn handling */
78 store_mark_3(const char *func, int linenumber)
80 #ifdef ALLOW_INSECURE_TAINTED_DATA
81 BOOL allow_insecure_tainted_data;
84 /******************************************************************************/
87 #define max_insize 20000
88 #define max_outsize 100000
90 /* This is global because it's defined in the headers and compilers grumble
91 if it is made static. */
93 const uschar *hex_digits = CUS"0123456789abcdef";
96 #ifdef STRERROR_FROM_ERRLIST
97 /* Some old-fashioned systems still around (e.g. SunOS4) don't have strerror()
98 in their libraries, but can provide the same facility by this simple
99 alternative function. */
104 if (n < 0 || n >= sys_nerr) return "unknown error number";
105 return sys_errlist[n];
107 #endif /* STRERROR_FROM_ERRLIST */
111 /*************************************************
112 * Interpret escape sequence *
113 *************************************************/
115 /* This function is copied from the main Exim code.
118 pp points a pointer to the initiating "\" in the string;
119 the pointer gets updated to point to the final character
120 Returns: the value of the character escape
124 string_interpret_escape(const uschar **pp)
127 const uschar *p = *pp;
129 if (ch == '\0') return **pp;
130 if (isdigit(ch) && ch != '8' && ch != '9')
133 if (isdigit(p[1]) && p[1] != '8' && p[1] != '9')
135 ch = ch * 8 + *(++p) - '0';
136 if (isdigit(p[1]) && p[1] != '8' && p[1] != '9')
137 ch = ch * 8 + *(++p) - '0';
142 case 'n': ch = '\n'; break;
143 case 'r': ch = '\r'; break;
144 case 't': ch = '\t'; break;
150 Ustrchr(hex_digits, tolower(*(++p))) - hex_digits;
151 if (isxdigit(p[1])) ch = ch * 16 +
152 Ustrchr(hex_digits, tolower(*(++p))) - hex_digits;
161 /*************************************************
163 *************************************************/
165 int main(int argc, char **argv)
173 BOOL lowercase = TRUE;
176 BOOL lastdup = FALSE;
177 #if !defined (USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
183 EXIM_DATUM key, content;
185 uschar keybuffer[256];
186 uschar temp_dbmname[512];
187 uschar real_dbmname[512];
189 uschar *buffer = malloc(max_outsize);
190 uschar *line = malloc(max_insize);
194 if (Ustrcmp(argv[arg], "-nolc") == 0) lowercase = FALSE;
195 else if (Ustrcmp(argv[arg], "-nowarn") == 0) warn = FALSE;
196 else if (Ustrcmp(argv[arg], "-lastdup") == 0) lastdup = TRUE;
197 else if (Ustrcmp(argv[arg], "-noduperr") == 0) duperr = FALSE;
198 else if (Ustrcmp(argv[arg], "-nozero") == 0) add_zero = 0;
206 printf("usage: exim_dbmbuild [-nolc] <source file> <dbm base name>\n");
210 if (Ustrcmp(argv[arg], "-") == 0)
212 else if (!(f = fopen(argv[arg], "rb")))
214 printf("exim_dbmbuild: unable to open %s: %s\n", argv[arg], strerror(errno));
218 /* By default Berkeley db does not put extensions on... which
221 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
222 if (Ustrcmp(argv[arg], argv[arg+1]) == 0)
224 printf("exim_dbmbuild: input and output filenames are the same\n");
229 /* Check length of filename; allow for adding .dbmbuild_temp and .db or
232 if (strlen(argv[arg+1]) > sizeof(temp_dbmname) - 20)
234 printf("exim_dbmbuild: output filename is ridiculously long\n");
238 Ustrcpy(temp_dbmname, US argv[arg+1]);
239 Ustrcat(temp_dbmname, US".dbmbuild_temp");
241 Ustrcpy(dirname, temp_dbmname);
242 if ((bptr = Ustrrchr(dirname, '/')))
245 Ustrcpy(dirname, US".");
247 /* It is apparently necessary to open with O_RDWR for this to work
248 with gdbm-1.7.3, though no reading is actually going to be done. */
250 if (!(d = exim_dbopen(temp_dbmname, dirname, O_RDWR|O_CREAT|O_EXCL, 0644)))
252 printf("exim_dbmbuild: unable to create %s: %s\n", temp_dbmname,
258 /* Unless using native db calls, see if we have created <name>.db; if not,
259 assume .dir & .pag */
261 #if !defined(USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
262 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
263 is_db = Ustat(real_dbmname, &statbuf) == 0;
266 /* Now do the business */
271 while (Ufgets(line, max_insize, f) != NULL)
274 int len = Ustrlen(line);
278 if (len >= max_insize - 1 && p[-1] != '\n')
280 printf("Overlong line read: max permitted length is %d\n", max_insize - 1);
285 if (line[0] == '#') continue;
286 while (p > line && isspace(p[-1])) p--;
288 if (line[0] == 0) continue;
290 /* A continuation line is valid only if there was a previous first
293 if (isspace(line[0]))
298 printf("Unexpected continuation line ignored\n%s\n\n", line);
301 while (isspace(*s)) s++;
304 if (bptr - buffer + p - s >= max_outsize - 1)
306 printf("Continued set of lines is too long: max permitted length is %d\n",
316 /* A first line must have a name followed by a colon or whitespace or
317 end of line, but first finish with a previous line. The key is lower
318 cased by default - this is what the newaliases program for sendmail does.
319 However, there's an option not to do this. */
329 exim_datum_init(&content);
330 exim_datum_data_set(&content, buffer);
331 exim_datum_size_set(&content, bptr - buffer + add_zero);
333 switch(rc = exim_dbputb(d, &key, &content))
339 case EXIM_DBPUTB_DUP:
340 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer);
342 if(duperr) yield = 1;
343 if (lastdup) exim_dbput(d, &key, &content);
347 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
356 exim_datum_init(&key);
357 exim_datum_data_set(&key, keybuffer);
359 /* Deal with quoted keys. Escape sequences always make one character
360 out of several, so we can re-build in place. */
366 while (*s != 0 && *s != '\"')
369 ? string_interpret_escape((const uschar **)&s)
373 if (*s != 0) s++; /* Past terminating " */
374 exim_datum_size_set(&key, t - keystart + add_zero);
379 while (*s != 0 && *s != ':' && !isspace(*s)) s++;
380 exim_datum_size_set(&key, s - keystart + add_zero);
383 if (exim_datum_size_get(&key) > 256)
385 printf("Keys longer than 255 characters cannot be handled\n");
392 for (i = 0; i < exim_datum_size_get(&key) - add_zero; i++)
393 keybuffer[i] = tolower(keystart[i]);
395 for (i = 0; i < exim_datum_size_get(&key) - add_zero; i++)
396 keybuffer[i] = keystart[i];
401 while (isspace(*s))s++;
405 while (isspace(*s))s++;
419 exim_datum_init(&content);
420 exim_datum_data_set(&content, buffer);
421 exim_datum_size_set(&content, bptr - buffer + add_zero);
423 switch(rc = exim_dbputb(d, &key, &content))
429 case EXIM_DBPUTB_DUP:
430 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer);
432 if (duperr) yield = 1;
433 if (lastdup) exim_dbput(d, &key, &content);
437 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
444 /* Close files, rename or abandon the temporary files, and exit */
451 /* If successful, output the number of entries and rename the temporary
454 if (yield == 0 || yield == 1)
456 printf("%d entr%s written\n", count, (count == 1)? "y" : "ies");
459 printf("%d duplicate key%s \n", dupcount, (dupcount > 1)? "s" : "");
462 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
463 Ustrcpy(real_dbmname, temp_dbmname);
464 Ustrcpy(buffer, US argv[arg+1]);
465 if (Urename(real_dbmname, buffer) != 0)
467 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
472 /* Rename a single .db file */
476 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
477 sprintf(CS buffer, "%s.db", argv[arg+1]);
478 if (Urename(real_dbmname, buffer) != 0)
480 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
485 /* Rename .dir and .pag files */
489 sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
490 sprintf(CS buffer, "%s.dir", argv[arg+1]);
491 if (Urename(real_dbmname, buffer) != 0)
493 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
497 sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
498 sprintf(CS buffer, "%s.pag", argv[arg+1]);
499 if (Urename(real_dbmname, buffer) != 0)
501 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
506 #endif /* USE_DB || USE_TDB || USE_GDBM */
509 /* Otherwise unlink the temporary files. */
513 printf("dbmbuild abandoned\n");
514 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
515 /* We created it, so safe to delete despite the name coming from outside */
516 /* coverity[tainted_string] */
517 Uunlink(temp_dbmname);
521 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
522 Uunlink(real_dbmname);
526 sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
527 Uunlink(real_dbmname);
528 sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
529 Uunlink(real_dbmname);
531 #endif /* USE_DB || USE_TDB */
537 /* End of exim_dbmbuild.c */