1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
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 /* dummies needed by Solaris build */
37 store_get_3(int size, BOOL tainted, const char *filename, int linenumber)
40 store_reset_3(void **ptr, int pool, const char *filename, int linenumber)
44 #define max_insize 20000
45 #define max_outsize 100000
47 /* This is global because it's defined in the headers and compilers grumble
48 if it is made static. */
50 const uschar *hex_digits = CUS"0123456789abcdef";
53 #ifdef STRERROR_FROM_ERRLIST
54 /* Some old-fashioned systems still around (e.g. SunOS4) don't have strerror()
55 in their libraries, but can provide the same facility by this simple
56 alternative function. */
61 if (n < 0 || n >= sys_nerr) return "unknown error number";
62 return sys_errlist[n];
64 #endif /* STRERROR_FROM_ERRLIST */
67 /* For Berkeley DB >= 2, we can define a function to be called in case of DB
68 errors. This should help with debugging strange DB problems, e.g. getting "File
69 exists" when you try to open a db file. The API changed at release 4.3. */
71 #if defined(USE_DB) && defined(DB_VERSION_STRING)
73 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
74 dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
78 dbfn_bdb_error_callback(const char *pfx, char *msg)
82 printf("Berkeley DB error: %s\n", msg);
88 /*************************************************
89 * Interpret escape sequence *
90 *************************************************/
92 /* This function is copied from the main Exim code.
95 pp points a pointer to the initiating "\" in the string;
96 the pointer gets updated to point to the final character
97 Returns: the value of the character escape
101 string_interpret_escape(const uschar **pp)
104 const uschar *p = *pp;
106 if (isdigit(ch) && ch != '8' && ch != '9')
109 if (isdigit(p[1]) && p[1] != '8' && p[1] != '9')
111 ch = ch * 8 + *(++p) - '0';
112 if (isdigit(p[1]) && p[1] != '8' && p[1] != '9')
113 ch = ch * 8 + *(++p) - '0';
118 case 'n': ch = '\n'; break;
119 case 'r': ch = '\r'; break;
120 case 't': ch = '\t'; break;
126 Ustrchr(hex_digits, tolower(*(++p))) - hex_digits;
127 if (isxdigit(p[1])) ch = ch * 16 +
128 Ustrchr(hex_digits, tolower(*(++p))) - hex_digits;
137 /*************************************************
139 *************************************************/
141 int main(int argc, char **argv)
149 BOOL lowercase = TRUE;
152 BOOL lastdup = FALSE;
153 #if !defined (USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
159 EXIM_DATUM key, content;
161 uschar keybuffer[256];
162 uschar temp_dbmname[512];
163 uschar real_dbmname[512];
165 uschar *buffer = malloc(max_outsize);
166 uschar *line = malloc(max_insize);
170 if (Ustrcmp(argv[arg], "-nolc") == 0) lowercase = FALSE;
171 else if (Ustrcmp(argv[arg], "-nowarn") == 0) warn = FALSE;
172 else if (Ustrcmp(argv[arg], "-lastdup") == 0) lastdup = TRUE;
173 else if (Ustrcmp(argv[arg], "-noduperr") == 0) duperr = FALSE;
174 else if (Ustrcmp(argv[arg], "-nozero") == 0) add_zero = 0;
182 printf("usage: exim_dbmbuild [-nolc] <source file> <dbm base name>\n");
186 if (Ustrcmp(argv[arg], "-") == 0) f = stdin; else
188 f = fopen(argv[arg], "rb");
191 printf("exim_dbmbuild: unable to open %s: %s\n", argv[arg], strerror(errno));
196 /* By default Berkeley db does not put extensions on... which
199 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
200 if (Ustrcmp(argv[arg], argv[arg+1]) == 0)
202 printf("exim_dbmbuild: input and output filenames are the same\n");
207 /* Check length of filename; allow for adding .dbmbuild_temp and .db or
210 if (strlen(argv[arg+1]) > sizeof(temp_dbmname) - 20)
212 printf("exim_dbmbuild: output filename is ridiculously long\n");
216 Ustrcpy(temp_dbmname, US argv[arg+1]);
217 Ustrcat(temp_dbmname, US".dbmbuild_temp");
219 Ustrcpy(dirname, temp_dbmname);
220 if ((bptr = Ustrrchr(dirname, '/')))
223 Ustrcpy(dirname, US".");
225 /* It is apparently necessary to open with O_RDWR for this to work
226 with gdbm-1.7.3, though no reading is actually going to be done. */
228 EXIM_DBOPEN(temp_dbmname, dirname, O_RDWR|O_CREAT|O_EXCL, 0644, &d);
232 printf("exim_dbmbuild: unable to create %s: %s\n", temp_dbmname,
238 /* Unless using native db calls, see if we have created <name>.db; if not,
239 assume .dir & .pag */
241 #if !defined(USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
242 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
243 is_db = Ustat(real_dbmname, &statbuf) == 0;
246 /* Now do the business */
251 while (Ufgets(line, max_insize, f) != NULL)
254 int len = Ustrlen(line);
258 if (len >= max_insize - 1 && p[-1] != '\n')
260 printf("Overlong line read: max permitted length is %d\n", max_insize - 1);
265 if (line[0] == '#') continue;
266 while (p > line && isspace(p[-1])) p--;
268 if (line[0] == 0) continue;
270 /* A continuation line is valid only if there was a previous first
273 if (isspace(line[0]))
278 printf("Unexpected continuation line ignored\n%s\n\n", line);
281 while (isspace(*s)) s++;
284 if (bptr - buffer + p - s >= max_outsize - 1)
286 printf("Continued set of lines is too long: max permitted length is %d\n",
296 /* A first line must have a name followed by a colon or whitespace or
297 end of line, but first finish with a previous line. The key is lower
298 cased by default - this is what the newaliases program for sendmail does.
299 However, there's an option not to do this. */
309 EXIM_DATUM_INIT(content);
310 EXIM_DATUM_DATA(content) = CS buffer;
311 EXIM_DATUM_SIZE(content) = bptr - buffer + add_zero;
313 switch(rc = EXIM_DBPUTB(d, key, content))
319 case EXIM_DBPUTB_DUP:
320 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer);
322 if(duperr) yield = 1;
323 if (lastdup) EXIM_DBPUT(d, key, content);
327 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
336 EXIM_DATUM_INIT(key);
337 EXIM_DATUM_DATA(key) = CS keybuffer;
339 /* Deal with quoted keys. Escape sequences always make one character
340 out of several, so we can re-build in place. */
346 while (*s != 0 && *s != '\"')
349 ? string_interpret_escape((const uschar **)&s)
353 if (*s != 0) s++; /* Past terminating " */
354 EXIM_DATUM_SIZE(key) = t - keystart + add_zero;
359 while (*s != 0 && *s != ':' && !isspace(*s)) s++;
360 EXIM_DATUM_SIZE(key) = s - keystart + add_zero;
363 if (EXIM_DATUM_SIZE(key) > 256)
365 printf("Keys longer than 255 characters cannot be handled\n");
372 for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
373 keybuffer[i] = tolower(keystart[i]);
375 for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
376 keybuffer[i] = keystart[i];
381 while (isspace(*s))s++;
385 while (isspace(*s))s++;
399 EXIM_DATUM_INIT(content);
400 EXIM_DATUM_DATA(content) = CS buffer;
401 EXIM_DATUM_SIZE(content) = bptr - buffer + add_zero;
403 switch(rc = EXIM_DBPUTB(d, key, content))
409 case EXIM_DBPUTB_DUP:
410 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer);
412 if (duperr) yield = 1;
413 if (lastdup) EXIM_DBPUT(d, key, content);
417 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
424 /* Close files, rename or abandon the temporary files, and exit */
431 /* If successful, output the number of entries and rename the temporary
434 if (yield == 0 || yield == 1)
436 printf("%d entr%s written\n", count, (count == 1)? "y" : "ies");
439 printf("%d duplicate key%s \n", dupcount, (dupcount > 1)? "s" : "");
442 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
443 Ustrcpy(real_dbmname, temp_dbmname);
444 Ustrcpy(buffer, US argv[arg+1]);
445 if (Urename(real_dbmname, buffer) != 0)
447 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
452 /* Rename a single .db file */
456 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
457 sprintf(CS buffer, "%s.db", argv[arg+1]);
458 if (Urename(real_dbmname, buffer) != 0)
460 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
465 /* Rename .dir and .pag files */
469 sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
470 sprintf(CS buffer, "%s.dir", argv[arg+1]);
471 if (Urename(real_dbmname, buffer) != 0)
473 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
477 sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
478 sprintf(CS buffer, "%s.pag", argv[arg+1]);
479 if (Urename(real_dbmname, buffer) != 0)
481 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
486 #endif /* USE_DB || USE_TDB || USE_GDBM */
489 /* Otherwise unlink the temporary files. */
493 printf("dbmbuild abandoned\n");
494 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
495 /* We created it, so safe to delete despite the name coming from outside */
496 /* coverity[tainted_string] */
497 Uunlink(temp_dbmname);
501 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
502 Uunlink(real_dbmname);
506 sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
507 Uunlink(real_dbmname);
508 sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
509 Uunlink(real_dbmname);
511 #endif /* USE_DB || USE_TDB */
517 /* End of exim_dbmbuild.c */