1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) The Exim Maintainers 2020 - 2024 */
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 /******************************************************************************/
82 #define max_insize 20000
83 #define max_outsize 100000
85 /* This is global because it's defined in the headers and compilers grumble
86 if it is made static. */
88 const uschar *hex_digits = CUS"0123456789abcdef";
91 #ifdef STRERROR_FROM_ERRLIST
92 /* Some old-fashioned systems still around (e.g. SunOS4) don't have strerror()
93 in their libraries, but can provide the same facility by this simple
94 alternative function. */
99 if (n < 0 || n >= sys_nerr) return "unknown error number";
100 return sys_errlist[n];
102 #endif /* STRERROR_FROM_ERRLIST */
106 /*************************************************
107 * Interpret escape sequence *
108 *************************************************/
110 /* This function is copied from the main Exim code.
113 pp points a pointer to the initiating "\" in the string;
114 the pointer gets updated to point to the final character
115 Returns: the value of the character escape
119 string_interpret_escape(const uschar **pp)
122 const uschar *p = *pp;
124 if (ch == '\0') return **pp;
125 if (isdigit(ch) && ch != '8' && ch != '9')
128 if (isdigit(p[1]) && p[1] != '8' && p[1] != '9')
130 ch = ch * 8 + *(++p) - '0';
131 if (isdigit(p[1]) && p[1] != '8' && p[1] != '9')
132 ch = ch * 8 + *(++p) - '0';
137 case 'n': ch = '\n'; break;
138 case 'r': ch = '\r'; break;
139 case 't': ch = '\t'; break;
145 Ustrchr(hex_digits, tolower(*(++p))) - hex_digits;
146 if (isxdigit(p[1])) ch = ch * 16 +
147 Ustrchr(hex_digits, tolower(*(++p))) - hex_digits;
156 /*************************************************
158 *************************************************/
160 int main(int argc, char **argv)
168 BOOL lowercase = TRUE;
171 BOOL lastdup = FALSE;
172 #if !defined (USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM) && !defined(USE_SQLITE)
178 EXIM_DATUM key, content;
180 uschar keybuffer[256];
181 uschar temp_dbmname[512];
182 uschar real_dbmname[512];
184 uschar *buffer = malloc(max_outsize);
185 uschar *line = malloc(max_insize);
189 if (Ustrcmp(argv[arg], "-nolc") == 0) lowercase = FALSE;
190 else if (Ustrcmp(argv[arg], "-nowarn") == 0) warn = FALSE;
191 else if (Ustrcmp(argv[arg], "-lastdup") == 0) lastdup = TRUE;
192 else if (Ustrcmp(argv[arg], "-noduperr") == 0) duperr = FALSE;
193 else if (Ustrcmp(argv[arg], "-nozero") == 0) add_zero = 0;
201 printf("usage: exim_dbmbuild [-nolc] <source file> <dbm base name>\n");
205 if (Ustrcmp(argv[arg], "-") == 0)
207 else if (!(f = fopen(argv[arg], "rb")))
209 printf("exim_dbmbuild: unable to open %s: %s\n", argv[arg], strerror(errno));
213 /* By default Berkeley db does not put extensions on... which
216 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM) && !defined(USE_SQLITE)
217 if (Ustrcmp(argv[arg], argv[arg+1]) == 0)
219 printf("exim_dbmbuild: input and output filenames are the same\n");
224 /* Check length of filename; allow for adding .dbmbuild_temp and .db or
227 if (strlen(argv[arg+1]) > sizeof(temp_dbmname) - 20)
229 printf("exim_dbmbuild: output filename is ridiculously long\n");
233 Ustrcpy(temp_dbmname, US argv[arg+1]);
234 Ustrcat(temp_dbmname, US".dbmbuild_temp");
236 Ustrcpy(dirname, temp_dbmname);
237 if ((bptr = Ustrrchr(dirname, '/')))
240 Ustrcpy(dirname, US".");
242 /* It is apparently necessary to open with O_RDWR for this to work
243 with gdbm-1.7.3, though no reading is actually going to be done. */
245 if (!(d = exim_dbopen(temp_dbmname, dirname, O_RDWR|O_CREAT|O_EXCL, 0644)))
247 printf("exim_dbmbuild: unable to create %s: %s\n", temp_dbmname,
253 /* Unless using native db calls, see if we have created <name>.db; if not,
254 assume .dir & .pag */
256 #if !defined(USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM) && !defined(USE_SQLITE)
257 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
258 is_db = Ustat(real_dbmname, &statbuf) == 0;
261 /* Now do the business */
266 while (Ufgets(line, max_insize, f) != NULL)
269 int len = Ustrlen(line);
273 if (len >= max_insize - 1 && p[-1] != '\n')
275 printf("Overlong line read: max permitted length is %d\n", max_insize - 1);
280 if (line[0] == '#') continue;
281 while (p > line && isspace(p[-1])) p--;
283 if (line[0] == 0) continue;
285 /* A continuation line is valid only if there was a previous first
288 if (isspace(line[0]))
293 printf("Unexpected continuation line ignored\n%s\n\n", line);
296 while (isspace(*s)) s++;
299 if (bptr - buffer + p - s >= max_outsize - 1)
301 printf("Continued set of lines is too long: max permitted length is %d\n",
311 /* A first line must have a name followed by a colon or whitespace or
312 end of line, but first finish with a previous line. The key is lower
313 cased by default - this is what the newaliases program for sendmail does.
314 However, there's an option not to do this. */
324 exim_datum_init(&content);
325 exim_datum_data_set(&content, buffer);
326 exim_datum_size_set(&content, bptr - buffer + add_zero);
328 rc = exim_dbputb(d, &key, &content);
335 case EXIM_DBPUTB_DUP:
336 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer);
338 if(duperr) yield = 1;
339 if (lastdup) exim_dbput(d, &key, &content);
343 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
352 exim_datum_init(&key);
353 exim_datum_data_set(&key, keybuffer);
355 /* Deal with quoted keys. Escape sequences always make one character
356 out of several, so we can re-build in place. */
362 while (*s != 0 && *s != '\"')
365 ? string_interpret_escape((const uschar **)&s)
369 if (*s != 0) s++; /* Past terminating " */
370 exim_datum_size_set(&key, t - keystart + add_zero);
375 while (*s && *s != ':' && !isspace(*s)) s++;
376 exim_datum_size_set(&key, s - keystart + add_zero);
379 if (exim_datum_size_get(&key) > 256)
381 printf("Keys longer than 255 characters cannot be handled\n");
388 for (i = 0; i < exim_datum_size_get(&key) - add_zero; i++)
389 keybuffer[i] = tolower(keystart[i]);
391 for (i = 0; i < exim_datum_size_get(&key) - add_zero; i++)
392 keybuffer[i] = keystart[i];
397 while (isspace(*s)) s++;
401 while (isspace(*s)) s++;
415 exim_datum_init(&content);
416 exim_datum_data_set(&content, buffer);
417 exim_datum_size_set(&content, bptr - buffer + add_zero);
419 rc = exim_dbputb(d, &key, &content);
426 case EXIM_DBPUTB_DUP:
427 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer);
429 if (duperr) yield = 1;
430 if (lastdup) exim_dbput(d, &key, &content);
434 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
441 /* Close files, rename or abandon the temporary files, and exit */
448 /* If successful, output the number of entries and rename the temporary
451 if (yield == 0 || yield == 1)
453 printf("%d entr%s written\n", count, (count == 1)? "y" : "ies");
456 printf("%d duplicate key%s \n", dupcount, (dupcount > 1)? "s" : "");
459 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM) || defined(USE_SQLITE)
460 Ustrcpy(real_dbmname, temp_dbmname);
461 Ustrcpy(buffer, US argv[arg+1]);
462 if (Urename(real_dbmname, buffer) != 0)
464 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
469 /* Rename a single .db file */
473 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
474 sprintf(CS buffer, "%s.db", argv[arg+1]);
475 if (Urename(real_dbmname, buffer) != 0)
477 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
482 /* Rename .dir and .pag files */
486 sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
487 sprintf(CS buffer, "%s.dir", argv[arg+1]);
488 if (Urename(real_dbmname, buffer) != 0)
490 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
494 sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
495 sprintf(CS buffer, "%s.pag", argv[arg+1]);
496 if (Urename(real_dbmname, buffer) != 0)
498 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
503 #endif /* USE_DB || USE_TDB || USE_GDBM || USE_SQLITE */
506 /* Otherwise unlink the temporary files. */
510 printf("dbmbuild abandoned\n");
511 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM) || defined(USE_SQLITE)
512 /* We created it, so safe to delete despite the name coming from outside */
513 /* coverity[tainted_string] */
514 Uunlink(temp_dbmname);
518 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
519 Uunlink(real_dbmname);
523 sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
524 Uunlink(real_dbmname);
525 sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
526 Uunlink(real_dbmname);
528 #endif /* USE_DB || USE_TDB || USE_GDBM || USE_SQLITE */
534 /* End of exim_dbmbuild.c */