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 #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 (ch == '\0') return *pp;
98 if (isdigit(ch) && ch != '8' && ch != '9')
101 if (isdigit(p[1]) && p[1] != '8' && p[1] != '9')
103 ch = ch * 8 + *(++p) - '0';
104 if (isdigit(p[1]) && p[1] != '8' && p[1] != '9')
105 ch = ch * 8 + *(++p) - '0';
110 case 'n': ch = '\n'; break;
111 case 'r': ch = '\r'; break;
112 case 't': ch = '\t'; break;
118 Ustrchr(hex_digits, tolower(*(++p))) - hex_digits;
119 if (isxdigit(p[1])) ch = ch * 16 +
120 Ustrchr(hex_digits, tolower(*(++p))) - hex_digits;
129 /*************************************************
131 *************************************************/
133 int main(int argc, char **argv)
141 BOOL lowercase = TRUE;
144 BOOL lastdup = FALSE;
145 #if !defined (USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
151 EXIM_DATUM key, content;
153 uschar keybuffer[256];
154 uschar temp_dbmname[512];
155 uschar real_dbmname[512];
157 uschar *buffer = malloc(max_outsize);
158 uschar *line = malloc(max_insize);
162 if (Ustrcmp(argv[arg], "-nolc") == 0) lowercase = FALSE;
163 else if (Ustrcmp(argv[arg], "-nowarn") == 0) warn = FALSE;
164 else if (Ustrcmp(argv[arg], "-lastdup") == 0) lastdup = TRUE;
165 else if (Ustrcmp(argv[arg], "-noduperr") == 0) duperr = FALSE;
166 else if (Ustrcmp(argv[arg], "-nozero") == 0) add_zero = 0;
174 printf("usage: exim_dbmbuild [-nolc] <source file> <dbm base name>\n");
178 if (Ustrcmp(argv[arg], "-") == 0) f = stdin; else
180 f = fopen(argv[arg], "rb");
183 printf("exim_dbmbuild: unable to open %s: %s\n", argv[arg], strerror(errno));
188 /* By default Berkeley db does not put extensions on... which
191 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
192 if (Ustrcmp(argv[arg], argv[arg+1]) == 0)
194 printf("exim_dbmbuild: input and output filenames are the same\n");
199 /* Check length of filename; allow for adding .dbmbuild_temp and .db or
202 if (strlen(argv[arg+1]) > sizeof(temp_dbmname) - 20)
204 printf("exim_dbmbuild: output filename is ridiculously long\n");
208 Ustrcpy(temp_dbmname, argv[arg+1]);
209 Ustrcat(temp_dbmname, ".dbmbuild_temp");
211 Ustrcpy(dirname, temp_dbmname);
212 if ((bptr = Ustrrchr(dirname, '/')))
215 Ustrcpy(dirname, ".");
217 /* It is apparently necessary to open with O_RDWR for this to work
218 with gdbm-1.7.3, though no reading is actually going to be done. */
220 EXIM_DBOPEN(temp_dbmname, dirname, O_RDWR|O_CREAT|O_EXCL, 0644, &d);
224 printf("exim_dbmbuild: unable to create %s: %s\n", temp_dbmname,
230 /* Unless using native db calls, see if we have created <name>.db; if not,
231 assume .dir & .pag */
233 #if !defined(USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
234 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
235 is_db = Ustat(real_dbmname, &statbuf) == 0;
238 /* Now do the business */
243 while (Ufgets(line, max_insize, f) != NULL)
246 int len = Ustrlen(line);
250 if (len >= max_insize - 1 && p[-1] != '\n')
252 printf("Overlong line read: max permitted length is %d\n", max_insize - 1);
257 if (line[0] == '#') continue;
258 while (p > line && isspace(p[-1])) p--;
260 if (line[0] == 0) continue;
262 /* A continuation line is valid only if there was a previous first
265 if (isspace(line[0]))
270 printf("Unexpected continuation line ignored\n%s\n\n", line);
273 while (isspace(*s)) s++;
276 if (bptr - buffer + p - s >= max_outsize - 1)
278 printf("Continued set of lines is too long: max permitted length is %d\n",
288 /* A first line must have a name followed by a colon or whitespace or
289 end of line, but first finish with a previous line. The key is lower
290 cased by default - this is what the newaliases program for sendmail does.
291 However, there's an option not to do this. */
301 EXIM_DATUM_INIT(content);
302 EXIM_DATUM_DATA(content) = CS buffer;
303 EXIM_DATUM_SIZE(content) = bptr - buffer + add_zero;
305 switch(rc = EXIM_DBPUTB(d, key, content))
311 case EXIM_DBPUTB_DUP:
312 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n",
315 if(duperr) yield = 1;
316 if (lastdup) EXIM_DBPUT(d, key, content);
320 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
329 EXIM_DATUM_INIT(key);
330 EXIM_DATUM_DATA(key) = CS keybuffer;
332 /* Deal with quoted keys. Escape sequences always make one character
333 out of several, so we can re-build in place. */
339 while (*s != 0 && *s != '\"')
341 if (*s == '\\') *t++ = string_interpret_escape((const uschar **)&s);
345 if (*s != 0) s++; /* Past terminating " */
346 EXIM_DATUM_SIZE(key) = t - keystart + add_zero;
351 while (*s != 0 && *s != ':' && !isspace(*s)) s++;
352 EXIM_DATUM_SIZE(key) = s - keystart + add_zero;
355 if (EXIM_DATUM_SIZE(key) > 256)
357 printf("Keys longer than 255 characters cannot be handled\n");
365 for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
366 keybuffer[i] = tolower(keystart[i]);
370 for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
371 keybuffer[i] = keystart[i];
377 while (isspace(*s))s++;
381 while (isspace(*s))s++;
395 EXIM_DATUM_INIT(content);
396 EXIM_DATUM_DATA(content) = CS buffer;
397 EXIM_DATUM_SIZE(content) = bptr - buffer + add_zero;
399 switch(rc = EXIM_DBPUTB(d, key, content))
405 case EXIM_DBPUTB_DUP:
406 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer);
408 if (duperr) yield = 1;
409 if (lastdup) EXIM_DBPUT(d, key, content);
413 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
420 /* Close files, rename or abandon the temporary files, and exit */
427 /* If successful, output the number of entries and rename the temporary
430 if (yield == 0 || yield == 1)
432 printf("%d entr%s written\n", count, (count == 1)? "y" : "ies");
435 printf("%d duplicate key%s \n", dupcount, (dupcount > 1)? "s" : "");
438 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
439 Ustrcpy(real_dbmname, temp_dbmname);
440 Ustrcpy(buffer, argv[arg+1]);
441 if (Urename(real_dbmname, buffer) != 0)
443 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
448 /* Rename a single .db file */
452 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
453 sprintf(CS buffer, "%s.db", argv[arg+1]);
454 if (Urename(real_dbmname, buffer) != 0)
456 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
461 /* Rename .dir and .pag files */
465 sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
466 sprintf(CS buffer, "%s.dir", argv[arg+1]);
467 if (Urename(real_dbmname, buffer) != 0)
469 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
473 sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
474 sprintf(CS buffer, "%s.pag", argv[arg+1]);
475 if (Urename(real_dbmname, buffer) != 0)
477 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
482 #endif /* USE_DB || USE_TDB || USE_GDBM */
485 /* Otherwise unlink the temporary files. */
489 printf("dbmbuild abandoned\n");
490 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
491 /* We created it, so safe to delete despite the name coming from outside */
492 /* coverity[tainted_string] */
493 Uunlink(temp_dbmname);
497 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
498 Uunlink(real_dbmname);
502 sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
503 Uunlink(real_dbmname);
504 sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
505 Uunlink(real_dbmname);
507 #endif /* USE_DB || USE_TDB */
513 /* End of exim_dbmbuild.c */