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, 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 */
109 /* For Berkeley DB >= 2, we can define a function to be called in case of DB
110 errors. This should help with debugging strange DB problems, e.g. getting "File
111 exists" when you try to open a db file. The API changed at release 4.3. */
113 #if defined(USE_DB) && defined(DB_VERSION_STRING)
115 # if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
116 dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
120 dbfn_bdb_error_callback(const char *pfx, char *msg)
124 printf("Berkeley DB error: %s\n", msg);
130 /*************************************************
131 * Interpret escape sequence *
132 *************************************************/
134 /* This function is copied from the main Exim code.
137 pp points a pointer to the initiating "\" in the string;
138 the pointer gets updated to point to the final character
139 Returns: the value of the character escape
143 string_interpret_escape(const uschar **pp)
146 const uschar *p = *pp;
148 if (ch == '\0') return **pp;
149 if (isdigit(ch) && ch != '8' && ch != '9')
152 if (isdigit(p[1]) && p[1] != '8' && p[1] != '9')
154 ch = ch * 8 + *(++p) - '0';
155 if (isdigit(p[1]) && p[1] != '8' && p[1] != '9')
156 ch = ch * 8 + *(++p) - '0';
161 case 'n': ch = '\n'; break;
162 case 'r': ch = '\r'; break;
163 case 't': ch = '\t'; break;
169 Ustrchr(hex_digits, tolower(*(++p))) - hex_digits;
170 if (isxdigit(p[1])) ch = ch * 16 +
171 Ustrchr(hex_digits, tolower(*(++p))) - hex_digits;
180 /*************************************************
182 *************************************************/
184 int main(int argc, char **argv)
192 BOOL lowercase = TRUE;
195 BOOL lastdup = FALSE;
196 #if !defined (USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
202 EXIM_DATUM key, content;
204 uschar keybuffer[256];
205 uschar temp_dbmname[512];
206 uschar real_dbmname[512];
208 uschar *buffer = malloc(max_outsize);
209 uschar *line = malloc(max_insize);
213 if (Ustrcmp(argv[arg], "-nolc") == 0) lowercase = FALSE;
214 else if (Ustrcmp(argv[arg], "-nowarn") == 0) warn = FALSE;
215 else if (Ustrcmp(argv[arg], "-lastdup") == 0) lastdup = TRUE;
216 else if (Ustrcmp(argv[arg], "-noduperr") == 0) duperr = FALSE;
217 else if (Ustrcmp(argv[arg], "-nozero") == 0) add_zero = 0;
225 printf("usage: exim_dbmbuild [-nolc] <source file> <dbm base name>\n");
229 if (Ustrcmp(argv[arg], "-") == 0)
231 else if (!(f = fopen(argv[arg], "rb")))
233 printf("exim_dbmbuild: unable to open %s: %s\n", argv[arg], strerror(errno));
237 /* By default Berkeley db does not put extensions on... which
240 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
241 if (Ustrcmp(argv[arg], argv[arg+1]) == 0)
243 printf("exim_dbmbuild: input and output filenames are the same\n");
248 /* Check length of filename; allow for adding .dbmbuild_temp and .db or
251 if (strlen(argv[arg+1]) > sizeof(temp_dbmname) - 20)
253 printf("exim_dbmbuild: output filename is ridiculously long\n");
257 Ustrcpy(temp_dbmname, US argv[arg+1]);
258 Ustrcat(temp_dbmname, US".dbmbuild_temp");
260 Ustrcpy(dirname, temp_dbmname);
261 if ((bptr = Ustrrchr(dirname, '/')))
264 Ustrcpy(dirname, US".");
266 /* It is apparently necessary to open with O_RDWR for this to work
267 with gdbm-1.7.3, though no reading is actually going to be done. */
269 EXIM_DBOPEN(temp_dbmname, dirname, O_RDWR|O_CREAT|O_EXCL, 0644, &d);
273 printf("exim_dbmbuild: unable to create %s: %s\n", temp_dbmname,
279 /* Unless using native db calls, see if we have created <name>.db; if not,
280 assume .dir & .pag */
282 #if !defined(USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
283 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
284 is_db = Ustat(real_dbmname, &statbuf) == 0;
287 /* Now do the business */
292 while (Ufgets(line, max_insize, f) != NULL)
295 int len = Ustrlen(line);
299 if (len >= max_insize - 1 && p[-1] != '\n')
301 printf("Overlong line read: max permitted length is %d\n", max_insize - 1);
306 if (line[0] == '#') continue;
307 while (p > line && isspace(p[-1])) p--;
309 if (line[0] == 0) continue;
311 /* A continuation line is valid only if there was a previous first
314 if (isspace(line[0]))
319 printf("Unexpected continuation line ignored\n%s\n\n", line);
322 while (isspace(*s)) s++;
325 if (bptr - buffer + p - s >= max_outsize - 1)
327 printf("Continued set of lines is too long: max permitted length is %d\n",
337 /* A first line must have a name followed by a colon or whitespace or
338 end of line, but first finish with a previous line. The key is lower
339 cased by default - this is what the newaliases program for sendmail does.
340 However, there's an option not to do this. */
350 EXIM_DATUM_INIT(content);
351 EXIM_DATUM_DATA(content) = CS buffer;
352 EXIM_DATUM_SIZE(content) = bptr - buffer + add_zero;
354 switch(rc = EXIM_DBPUTB(d, key, content))
360 case EXIM_DBPUTB_DUP:
361 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer);
363 if(duperr) yield = 1;
364 if (lastdup) EXIM_DBPUT(d, key, content);
368 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
377 EXIM_DATUM_INIT(key);
378 EXIM_DATUM_DATA(key) = CS keybuffer;
380 /* Deal with quoted keys. Escape sequences always make one character
381 out of several, so we can re-build in place. */
387 while (*s != 0 && *s != '\"')
390 ? string_interpret_escape((const uschar **)&s)
394 if (*s != 0) s++; /* Past terminating " */
395 EXIM_DATUM_SIZE(key) = t - keystart + add_zero;
400 while (*s != 0 && *s != ':' && !isspace(*s)) s++;
401 EXIM_DATUM_SIZE(key) = s - keystart + add_zero;
404 if (EXIM_DATUM_SIZE(key) > 256)
406 printf("Keys longer than 255 characters cannot be handled\n");
413 for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
414 keybuffer[i] = tolower(keystart[i]);
416 for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
417 keybuffer[i] = keystart[i];
422 while (isspace(*s))s++;
426 while (isspace(*s))s++;
440 EXIM_DATUM_INIT(content);
441 EXIM_DATUM_DATA(content) = CS buffer;
442 EXIM_DATUM_SIZE(content) = bptr - buffer + add_zero;
444 switch(rc = EXIM_DBPUTB(d, key, content))
450 case EXIM_DBPUTB_DUP:
451 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer);
453 if (duperr) yield = 1;
454 if (lastdup) EXIM_DBPUT(d, key, content);
458 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
465 /* Close files, rename or abandon the temporary files, and exit */
472 /* If successful, output the number of entries and rename the temporary
475 if (yield == 0 || yield == 1)
477 printf("%d entr%s written\n", count, (count == 1)? "y" : "ies");
480 printf("%d duplicate key%s \n", dupcount, (dupcount > 1)? "s" : "");
483 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
484 Ustrcpy(real_dbmname, temp_dbmname);
485 Ustrcpy(buffer, US argv[arg+1]);
486 if (Urename(real_dbmname, buffer) != 0)
488 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
493 /* Rename a single .db file */
497 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
498 sprintf(CS buffer, "%s.db", argv[arg+1]);
499 if (Urename(real_dbmname, buffer) != 0)
501 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
506 /* Rename .dir and .pag files */
510 sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
511 sprintf(CS buffer, "%s.dir", argv[arg+1]);
512 if (Urename(real_dbmname, buffer) != 0)
514 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
518 sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
519 sprintf(CS buffer, "%s.pag", argv[arg+1]);
520 if (Urename(real_dbmname, buffer) != 0)
522 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
527 #endif /* USE_DB || USE_TDB || USE_GDBM */
530 /* Otherwise unlink the temporary files. */
534 printf("dbmbuild abandoned\n");
535 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
536 /* We created it, so safe to delete despite the name coming from outside */
537 /* coverity[tainted_string] */
538 Uunlink(temp_dbmname);
542 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
543 Uunlink(real_dbmname);
547 sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
548 Uunlink(real_dbmname);
549 sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
550 Uunlink(real_dbmname);
552 #endif /* USE_DB || USE_TDB */
558 /* End of exim_dbmbuild.c */