1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2017 */
6 /* See the file NOTICE for conditions of use and distribution. */
9 /* A small freestanding program to build dbm databases from serial input. For
10 alias files, this program fulfils the function of the newaliases program used
11 by other mailers, but it can be used for other dbm data files too. It operates
12 by writing a new file or files, and then renaming; otherwise old entries can
13 never get flushed out.
15 This program is clever enough to cope with ndbm, which creates two files called
16 <name>.dir and <name>.pag, or with db, which creates a single file called
17 <name>.db. If native db is in use (USE_DB defined) or tdb is in use (USE_TDB
18 defined) there is no extension to the output filename. This is also handled. If
19 there are any other variants, the program won't cope.
21 The first argument to the program is the name of the serial file; the second
22 is the base name for the DBM file(s). When native db is in use, these must be
25 Input lines beginning with # are ignored, as are blank lines. Entries begin
26 with a key terminated by a colon or end of line or whitespace and continue with
27 indented lines. Keys may be quoted if they contain colons or whitespace or #
33 uschar * spool_directory = NULL; /* dummy for dbstuff.h */
35 #define max_insize 20000
36 #define max_outsize 100000
38 /* This is global because it's defined in the headers and compilers grumble
39 if it is made static. */
41 const uschar *hex_digits = CUS"0123456789abcdef";
44 #ifdef STRERROR_FROM_ERRLIST
45 /* Some old-fashioned systems still around (e.g. SunOS4) don't have strerror()
46 in their libraries, but can provide the same facility by this simple
47 alternative function. */
52 if (n < 0 || n >= sys_nerr) return "unknown error number";
53 return sys_errlist[n];
55 #endif /* STRERROR_FROM_ERRLIST */
58 /* For Berkeley DB >= 2, we can define a function to be called in case of DB
59 errors. This should help with debugging strange DB problems, e.g. getting "File
60 exists" when you try to open a db file. The API changed at release 4.3. */
62 #if defined(USE_DB) && defined(DB_VERSION_STRING)
64 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
65 dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
69 dbfn_bdb_error_callback(const char *pfx, char *msg)
73 printf("Berkeley DB error: %s\n", msg);
79 /*************************************************
80 * Interpret escape sequence *
81 *************************************************/
83 /* This function is copied from the main Exim code.
86 pp points a pointer to the initiating "\" in the string;
87 the pointer gets updated to point to the final character
88 Returns: the value of the character escape
92 string_interpret_escape(const uschar **pp)
95 const uschar *p = *pp;
97 if (isdigit(ch) && ch != '8' && ch != '9')
100 if (isdigit(p[1]) && p[1] != '8' && p[1] != '9')
102 ch = ch * 8 + *(++p) - '0';
103 if (isdigit(p[1]) && p[1] != '8' && p[1] != '9')
104 ch = ch * 8 + *(++p) - '0';
109 case 'n': ch = '\n'; break;
110 case 'r': ch = '\r'; break;
111 case 't': ch = '\t'; break;
117 Ustrchr(hex_digits, tolower(*(++p))) - hex_digits;
118 if (isxdigit(p[1])) ch = ch * 16 +
119 Ustrchr(hex_digits, tolower(*(++p))) - hex_digits;
128 /*************************************************
130 *************************************************/
132 int main(int argc, char **argv)
140 BOOL lowercase = TRUE;
143 BOOL lastdup = FALSE;
144 #if !defined (USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
150 EXIM_DATUM key, content;
152 uschar keybuffer[256];
153 uschar temp_dbmname[512];
154 uschar real_dbmname[512];
156 uschar *buffer = malloc(max_outsize);
157 uschar *line = malloc(max_insize);
161 if (Ustrcmp(argv[arg], "-nolc") == 0) lowercase = FALSE;
162 else if (Ustrcmp(argv[arg], "-nowarn") == 0) warn = FALSE;
163 else if (Ustrcmp(argv[arg], "-lastdup") == 0) lastdup = TRUE;
164 else if (Ustrcmp(argv[arg], "-noduperr") == 0) duperr = FALSE;
165 else if (Ustrcmp(argv[arg], "-nozero") == 0) add_zero = 0;
173 printf("usage: exim_dbmbuild [-nolc] <source file> <dbm base name>\n");
177 if (Ustrcmp(argv[arg], "-") == 0) f = stdin; else
179 f = fopen(argv[arg], "rb");
182 printf("exim_dbmbuild: unable to open %s: %s\n", argv[arg], strerror(errno));
187 /* By default Berkeley db does not put extensions on... which
190 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
191 if (Ustrcmp(argv[arg], argv[arg+1]) == 0)
193 printf("exim_dbmbuild: input and output filenames are the same\n");
198 /* Check length of filename; allow for adding .dbmbuild_temp and .db or
201 if (strlen(argv[arg+1]) > sizeof(temp_dbmname) - 20)
203 printf("exim_dbmbuild: output filename is ridiculously long\n");
207 Ustrcpy(temp_dbmname, argv[arg+1]);
208 Ustrcat(temp_dbmname, ".dbmbuild_temp");
210 Ustrcpy(dirname, temp_dbmname);
211 if ((bptr = Ustrrchr(dirname, '/')))
214 /* It is apparently necessary to open with O_RDWR for this to work
215 with gdbm-1.7.3, though no reading is actually going to be done. */
217 EXIM_DBOPEN(temp_dbmname, dirname, O_RDWR|O_CREAT|O_EXCL, 0644, &d);
221 printf("exim_dbmbuild: unable to create %s: %s\n", temp_dbmname,
227 /* Unless using native db calls, see if we have created <name>.db; if not,
228 assume .dir & .pag */
230 #if !defined(USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
231 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
232 is_db = Ustat(real_dbmname, &statbuf) == 0;
235 /* Now do the business */
240 while (Ufgets(line, max_insize, f) != NULL)
243 int len = Ustrlen(line);
247 if (len >= max_insize - 1 && p[-1] != '\n')
249 printf("Overlong line read: max permitted length is %d\n", max_insize - 1);
254 if (line[0] == '#') continue;
255 while (p > line && isspace(p[-1])) p--;
257 if (line[0] == 0) continue;
259 /* A continuation line is valid only if there was a previous first
262 if (isspace(line[0]))
267 printf("Unexpected continuation line ignored\n%s\n\n", line);
270 while (isspace(*s)) s++;
273 if (bptr - buffer + p - s >= max_outsize - 1)
275 printf("Continued set of lines is too long: max permitted length is %d\n",
285 /* A first line must have a name followed by a colon or whitespace or
286 end of line, but first finish with a previous line. The key is lower
287 cased by default - this is what the newaliases program for sendmail does.
288 However, there's an option not to do this. */
298 EXIM_DATUM_INIT(content);
299 EXIM_DATUM_DATA(content) = CS buffer;
300 EXIM_DATUM_SIZE(content) = bptr - buffer + add_zero;
302 switch(rc = EXIM_DBPUTB(d, key, content))
308 case EXIM_DBPUTB_DUP:
309 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n",
312 if(duperr) yield = 1;
313 if (lastdup) EXIM_DBPUT(d, key, content);
317 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
326 EXIM_DATUM_INIT(key);
327 EXIM_DATUM_DATA(key) = CS keybuffer;
329 /* Deal with quoted keys. Escape sequences always make one character
330 out of several, so we can re-build in place. */
336 while (*s != 0 && *s != '\"')
338 if (*s == '\\') *t++ = string_interpret_escape((const uschar **)&s);
342 if (*s != 0) s++; /* Past terminating " */
343 EXIM_DATUM_SIZE(key) = t - keystart + add_zero;
348 while (*s != 0 && *s != ':' && !isspace(*s)) s++;
349 EXIM_DATUM_SIZE(key) = s - keystart + add_zero;
352 if (EXIM_DATUM_SIZE(key) > 256)
354 printf("Keys longer than 255 characters cannot be handled\n");
362 for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
363 keybuffer[i] = tolower(keystart[i]);
367 for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
368 keybuffer[i] = keystart[i];
374 while (isspace(*s))s++;
378 while (isspace(*s))s++;
392 EXIM_DATUM_INIT(content);
393 EXIM_DATUM_DATA(content) = CS buffer;
394 EXIM_DATUM_SIZE(content) = bptr - buffer + add_zero;
396 switch(rc = EXIM_DBPUTB(d, key, content))
402 case EXIM_DBPUTB_DUP:
403 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer);
405 if (duperr) yield = 1;
406 if (lastdup) EXIM_DBPUT(d, key, content);
410 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
417 /* Close files, rename or abandon the temporary files, and exit */
424 /* If successful, output the number of entries and rename the temporary
427 if (yield == 0 || yield == 1)
429 printf("%d entr%s written\n", count, (count == 1)? "y" : "ies");
432 printf("%d duplicate key%s \n", dupcount, (dupcount > 1)? "s" : "");
435 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
436 Ustrcpy(real_dbmname, temp_dbmname);
437 Ustrcpy(buffer, argv[arg+1]);
438 if (Urename(real_dbmname, buffer) != 0)
440 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
445 /* Rename a single .db file */
449 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
450 sprintf(CS buffer, "%s.db", argv[arg+1]);
451 if (Urename(real_dbmname, buffer) != 0)
453 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
458 /* Rename .dir and .pag files */
462 sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
463 sprintf(CS buffer, "%s.dir", argv[arg+1]);
464 if (Urename(real_dbmname, buffer) != 0)
466 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
470 sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
471 sprintf(CS buffer, "%s.pag", argv[arg+1]);
472 if (Urename(real_dbmname, buffer) != 0)
474 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
479 #endif /* USE_DB || USE_TDB || USE_GDBM */
482 /* Otherwise unlink the temporary files. */
486 printf("dbmbuild abandoned\n");
487 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
488 /* We created it, so safe to delete despite the name coming from outside */
489 /* coverity[tainted_string] */
490 Uunlink(temp_dbmname);
494 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
495 Uunlink(real_dbmname);
499 sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
500 Uunlink(real_dbmname);
501 sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
502 Uunlink(real_dbmname);
504 #endif /* USE_DB || USE_TDB */
510 /* End of exim_dbmbuild.c */