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. */
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 hintsdb.h */
36 /******************************************************************************/
37 /* dummies needed by Solaris build */
42 readconf_printtime(int t)
45 store_get_3(int size, const void * proto_mem, const char *filename, int linenumber)
48 store_reset_3(void **ptr, 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;
75 /* These introduced by the taintwarn handling */
77 store_mark_3(const char *func, int linenumber)
79 #ifdef ALLOW_INSECURE_TAINTED_DATA
80 BOOL allow_insecure_tainted_data;
83 /******************************************************************************/
86 #define max_insize 20000
87 #define max_outsize 100000
89 /* This is global because it's defined in the headers and compilers grumble
90 if it is made static. */
92 const uschar *hex_digits = CUS"0123456789abcdef";
95 #ifdef STRERROR_FROM_ERRLIST
96 /* Some old-fashioned systems still around (e.g. SunOS4) don't have strerror()
97 in their libraries, but can provide the same facility by this simple
98 alternative function. */
103 if (n < 0 || n >= sys_nerr) return "unknown error number";
104 return sys_errlist[n];
106 #endif /* STRERROR_FROM_ERRLIST */
110 /*************************************************
111 * Interpret escape sequence *
112 *************************************************/
114 /* This function is copied from the main Exim code.
117 pp points a pointer to the initiating "\" in the string;
118 the pointer gets updated to point to the final character
119 Returns: the value of the character escape
123 string_interpret_escape(const uschar **pp)
126 const uschar *p = *pp;
128 if (ch == '\0') return **pp;
129 if (isdigit(ch) && ch != '8' && ch != '9')
132 if (isdigit(p[1]) && p[1] != '8' && p[1] != '9')
134 ch = ch * 8 + *(++p) - '0';
135 if (isdigit(p[1]) && p[1] != '8' && p[1] != '9')
136 ch = ch * 8 + *(++p) - '0';
141 case 'n': ch = '\n'; break;
142 case 'r': ch = '\r'; break;
143 case 't': ch = '\t'; break;
149 Ustrchr(hex_digits, tolower(*(++p))) - hex_digits;
150 if (isxdigit(p[1])) ch = ch * 16 +
151 Ustrchr(hex_digits, tolower(*(++p))) - hex_digits;
160 /*************************************************
162 *************************************************/
164 int main(int argc, char **argv)
172 BOOL lowercase = TRUE;
175 BOOL lastdup = FALSE;
176 #if !defined (USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
182 EXIM_DATUM key, content;
184 uschar keybuffer[256];
185 uschar temp_dbmname[512];
186 uschar real_dbmname[512];
188 uschar *buffer = malloc(max_outsize);
189 uschar *line = malloc(max_insize);
193 if (Ustrcmp(argv[arg], "-nolc") == 0) lowercase = FALSE;
194 else if (Ustrcmp(argv[arg], "-nowarn") == 0) warn = FALSE;
195 else if (Ustrcmp(argv[arg], "-lastdup") == 0) lastdup = TRUE;
196 else if (Ustrcmp(argv[arg], "-noduperr") == 0) duperr = FALSE;
197 else if (Ustrcmp(argv[arg], "-nozero") == 0) add_zero = 0;
205 printf("usage: exim_dbmbuild [-nolc] <source file> <dbm base name>\n");
209 if (Ustrcmp(argv[arg], "-") == 0)
211 else if (!(f = fopen(argv[arg], "rb")))
213 printf("exim_dbmbuild: unable to open %s: %s\n", argv[arg], strerror(errno));
217 /* By default Berkeley db does not put extensions on... which
220 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
221 if (Ustrcmp(argv[arg], argv[arg+1]) == 0)
223 printf("exim_dbmbuild: input and output filenames are the same\n");
228 /* Check length of filename; allow for adding .dbmbuild_temp and .db or
231 if (strlen(argv[arg+1]) > sizeof(temp_dbmname) - 20)
233 printf("exim_dbmbuild: output filename is ridiculously long\n");
237 Ustrcpy(temp_dbmname, US argv[arg+1]);
238 Ustrcat(temp_dbmname, US".dbmbuild_temp");
240 Ustrcpy(dirname, temp_dbmname);
241 if ((bptr = Ustrrchr(dirname, '/')))
244 Ustrcpy(dirname, US".");
246 /* It is apparently necessary to open with O_RDWR for this to work
247 with gdbm-1.7.3, though no reading is actually going to be done. */
249 if (!(d = exim_dbopen(temp_dbmname, dirname, O_RDWR|O_CREAT|O_EXCL, 0644)))
251 printf("exim_dbmbuild: unable to create %s: %s\n", temp_dbmname,
257 /* Unless using native db calls, see if we have created <name>.db; if not,
258 assume .dir & .pag */
260 #if !defined(USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
261 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
262 is_db = Ustat(real_dbmname, &statbuf) == 0;
265 /* Now do the business */
270 while (Ufgets(line, max_insize, f) != NULL)
273 int len = Ustrlen(line);
277 if (len >= max_insize - 1 && p[-1] != '\n')
279 printf("Overlong line read: max permitted length is %d\n", max_insize - 1);
284 if (line[0] == '#') continue;
285 while (p > line && isspace(p[-1])) p--;
287 if (line[0] == 0) continue;
289 /* A continuation line is valid only if there was a previous first
292 if (isspace(line[0]))
297 printf("Unexpected continuation line ignored\n%s\n\n", line);
300 while (isspace(*s)) s++;
303 if (bptr - buffer + p - s >= max_outsize - 1)
305 printf("Continued set of lines is too long: max permitted length is %d\n",
315 /* A first line must have a name followed by a colon or whitespace or
316 end of line, but first finish with a previous line. The key is lower
317 cased by default - this is what the newaliases program for sendmail does.
318 However, there's an option not to do this. */
328 exim_datum_init(&content);
329 exim_datum_data_set(&content, buffer);
330 exim_datum_size_set(&content, bptr - buffer + add_zero);
332 switch(rc = exim_dbputb(d, &key, &content))
338 case EXIM_DBPUTB_DUP:
339 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer);
341 if(duperr) yield = 1;
342 if (lastdup) exim_dbput(d, &key, &content);
346 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
355 exim_datum_init(&key);
356 exim_datum_data_set(&key, keybuffer);
358 /* Deal with quoted keys. Escape sequences always make one character
359 out of several, so we can re-build in place. */
365 while (*s != 0 && *s != '\"')
368 ? string_interpret_escape((const uschar **)&s)
372 if (*s != 0) s++; /* Past terminating " */
373 exim_datum_size_set(&key, t - keystart + add_zero);
378 while (*s != 0 && *s != ':' && !isspace(*s)) s++;
379 exim_datum_size_set(&key, s - keystart + add_zero);
382 if (exim_datum_size_get(&key) > 256)
384 printf("Keys longer than 255 characters cannot be handled\n");
391 for (i = 0; i < exim_datum_size_get(&key) - add_zero; i++)
392 keybuffer[i] = tolower(keystart[i]);
394 for (i = 0; i < exim_datum_size_get(&key) - add_zero; i++)
395 keybuffer[i] = keystart[i];
400 while (isspace(*s))s++;
404 while (isspace(*s))s++;
418 exim_datum_init(&content);
419 exim_datum_data_set(&content, buffer);
420 exim_datum_size_set(&content, bptr - buffer + add_zero);
422 switch(rc = exim_dbputb(d, &key, &content))
428 case EXIM_DBPUTB_DUP:
429 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer);
431 if (duperr) yield = 1;
432 if (lastdup) exim_dbput(d, &key, &content);
436 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
443 /* Close files, rename or abandon the temporary files, and exit */
450 /* If successful, output the number of entries and rename the temporary
453 if (yield == 0 || yield == 1)
455 printf("%d entr%s written\n", count, (count == 1)? "y" : "ies");
458 printf("%d duplicate key%s \n", dupcount, (dupcount > 1)? "s" : "");
461 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
462 Ustrcpy(real_dbmname, temp_dbmname);
463 Ustrcpy(buffer, US argv[arg+1]);
464 if (Urename(real_dbmname, buffer) != 0)
466 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
471 /* Rename a single .db file */
475 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
476 sprintf(CS buffer, "%s.db", argv[arg+1]);
477 if (Urename(real_dbmname, buffer) != 0)
479 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
484 /* Rename .dir and .pag files */
488 sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
489 sprintf(CS buffer, "%s.dir", argv[arg+1]);
490 if (Urename(real_dbmname, buffer) != 0)
492 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
496 sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
497 sprintf(CS buffer, "%s.pag", argv[arg+1]);
498 if (Urename(real_dbmname, buffer) != 0)
500 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
505 #endif /* USE_DB || USE_TDB || USE_GDBM */
508 /* Otherwise unlink the temporary files. */
512 printf("dbmbuild abandoned\n");
513 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
514 /* We created it, so safe to delete despite the name coming from outside */
515 /* coverity[tainted_string] */
516 Uunlink(temp_dbmname);
520 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
521 Uunlink(real_dbmname);
525 sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
526 Uunlink(real_dbmname);
527 sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
528 Uunlink(real_dbmname);
530 #endif /* USE_DB || USE_TDB */
536 /* End of exim_dbmbuild.c */