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 /******************************************************************************/
36 /* dummies needed by Solaris build */
38 store_get_3(int size, BOOL tainted, const char *filename, int linenumber)
41 store_reset_3(void **ptr, int pool, const char *filename, int linenumber)
44 store_release_above_3(void *ptr, const char *func, int linenumber)
47 string_vformat_trc(gstring * g, const uschar * func, unsigned line,
48 unsigned size_limit, unsigned flags, const char *format, va_list ap)
51 string_sprintf_trc(const char * a, const uschar * b, unsigned c, ...)
53 BOOL split_spool_directory;
55 /******************************************************************************/
58 #define max_insize 20000
59 #define max_outsize 100000
61 /* This is global because it's defined in the headers and compilers grumble
62 if it is made static. */
64 const uschar *hex_digits = CUS"0123456789abcdef";
67 #ifdef STRERROR_FROM_ERRLIST
68 /* Some old-fashioned systems still around (e.g. SunOS4) don't have strerror()
69 in their libraries, but can provide the same facility by this simple
70 alternative function. */
75 if (n < 0 || n >= sys_nerr) return "unknown error number";
76 return sys_errlist[n];
78 #endif /* STRERROR_FROM_ERRLIST */
81 /* For Berkeley DB >= 2, we can define a function to be called in case of DB
82 errors. This should help with debugging strange DB problems, e.g. getting "File
83 exists" when you try to open a db file. The API changed at release 4.3. */
85 #if defined(USE_DB) && defined(DB_VERSION_STRING)
87 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
88 dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
92 dbfn_bdb_error_callback(const char *pfx, char *msg)
96 printf("Berkeley DB error: %s\n", msg);
102 /*************************************************
103 * Interpret escape sequence *
104 *************************************************/
106 /* This function is copied from the main Exim code.
109 pp points a pointer to the initiating "\" in the string;
110 the pointer gets updated to point to the final character
111 Returns: the value of the character escape
115 string_interpret_escape(const uschar **pp)
118 const uschar *p = *pp;
120 if (isdigit(ch) && ch != '8' && ch != '9')
123 if (isdigit(p[1]) && p[1] != '8' && p[1] != '9')
125 ch = ch * 8 + *(++p) - '0';
126 if (isdigit(p[1]) && p[1] != '8' && p[1] != '9')
127 ch = ch * 8 + *(++p) - '0';
132 case 'n': ch = '\n'; break;
133 case 'r': ch = '\r'; break;
134 case 't': ch = '\t'; break;
140 Ustrchr(hex_digits, tolower(*(++p))) - hex_digits;
141 if (isxdigit(p[1])) ch = ch * 16 +
142 Ustrchr(hex_digits, tolower(*(++p))) - hex_digits;
151 /*************************************************
153 *************************************************/
155 int main(int argc, char **argv)
163 BOOL lowercase = TRUE;
166 BOOL lastdup = FALSE;
167 #if !defined (USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
173 EXIM_DATUM key, content;
175 uschar keybuffer[256];
176 uschar temp_dbmname[512];
177 uschar real_dbmname[512];
179 uschar *buffer = malloc(max_outsize);
180 uschar *line = malloc(max_insize);
184 if (Ustrcmp(argv[arg], "-nolc") == 0) lowercase = FALSE;
185 else if (Ustrcmp(argv[arg], "-nowarn") == 0) warn = FALSE;
186 else if (Ustrcmp(argv[arg], "-lastdup") == 0) lastdup = TRUE;
187 else if (Ustrcmp(argv[arg], "-noduperr") == 0) duperr = FALSE;
188 else if (Ustrcmp(argv[arg], "-nozero") == 0) add_zero = 0;
196 printf("usage: exim_dbmbuild [-nolc] <source file> <dbm base name>\n");
200 if (Ustrcmp(argv[arg], "-") == 0) f = stdin; else
202 f = fopen(argv[arg], "rb");
205 printf("exim_dbmbuild: unable to open %s: %s\n", argv[arg], strerror(errno));
210 /* By default Berkeley db does not put extensions on... which
213 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
214 if (Ustrcmp(argv[arg], argv[arg+1]) == 0)
216 printf("exim_dbmbuild: input and output filenames are the same\n");
221 /* Check length of filename; allow for adding .dbmbuild_temp and .db or
224 if (strlen(argv[arg+1]) > sizeof(temp_dbmname) - 20)
226 printf("exim_dbmbuild: output filename is ridiculously long\n");
230 Ustrcpy(temp_dbmname, US argv[arg+1]);
231 Ustrcat(temp_dbmname, US".dbmbuild_temp");
233 Ustrcpy(dirname, temp_dbmname);
234 if ((bptr = Ustrrchr(dirname, '/')))
237 Ustrcpy(dirname, US".");
239 /* It is apparently necessary to open with O_RDWR for this to work
240 with gdbm-1.7.3, though no reading is actually going to be done. */
242 EXIM_DBOPEN(temp_dbmname, dirname, O_RDWR|O_CREAT|O_EXCL, 0644, &d);
246 printf("exim_dbmbuild: unable to create %s: %s\n", temp_dbmname,
252 /* Unless using native db calls, see if we have created <name>.db; if not,
253 assume .dir & .pag */
255 #if !defined(USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
256 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
257 is_db = Ustat(real_dbmname, &statbuf) == 0;
260 /* Now do the business */
265 while (Ufgets(line, max_insize, f) != NULL)
268 int len = Ustrlen(line);
272 if (len >= max_insize - 1 && p[-1] != '\n')
274 printf("Overlong line read: max permitted length is %d\n", max_insize - 1);
279 if (line[0] == '#') continue;
280 while (p > line && isspace(p[-1])) p--;
282 if (line[0] == 0) continue;
284 /* A continuation line is valid only if there was a previous first
287 if (isspace(line[0]))
292 printf("Unexpected continuation line ignored\n%s\n\n", line);
295 while (isspace(*s)) s++;
298 if (bptr - buffer + p - s >= max_outsize - 1)
300 printf("Continued set of lines is too long: max permitted length is %d\n",
310 /* A first line must have a name followed by a colon or whitespace or
311 end of line, but first finish with a previous line. The key is lower
312 cased by default - this is what the newaliases program for sendmail does.
313 However, there's an option not to do this. */
323 EXIM_DATUM_INIT(content);
324 EXIM_DATUM_DATA(content) = CS buffer;
325 EXIM_DATUM_SIZE(content) = bptr - buffer + add_zero;
327 switch(rc = EXIM_DBPUTB(d, key, content))
333 case EXIM_DBPUTB_DUP:
334 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer);
336 if(duperr) yield = 1;
337 if (lastdup) EXIM_DBPUT(d, key, content);
341 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
350 EXIM_DATUM_INIT(key);
351 EXIM_DATUM_DATA(key) = CS keybuffer;
353 /* Deal with quoted keys. Escape sequences always make one character
354 out of several, so we can re-build in place. */
360 while (*s != 0 && *s != '\"')
363 ? string_interpret_escape((const uschar **)&s)
367 if (*s != 0) s++; /* Past terminating " */
368 EXIM_DATUM_SIZE(key) = t - keystart + add_zero;
373 while (*s != 0 && *s != ':' && !isspace(*s)) s++;
374 EXIM_DATUM_SIZE(key) = s - keystart + add_zero;
377 if (EXIM_DATUM_SIZE(key) > 256)
379 printf("Keys longer than 255 characters cannot be handled\n");
386 for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
387 keybuffer[i] = tolower(keystart[i]);
389 for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
390 keybuffer[i] = keystart[i];
395 while (isspace(*s))s++;
399 while (isspace(*s))s++;
413 EXIM_DATUM_INIT(content);
414 EXIM_DATUM_DATA(content) = CS buffer;
415 EXIM_DATUM_SIZE(content) = bptr - buffer + add_zero;
417 switch(rc = EXIM_DBPUTB(d, key, content))
423 case EXIM_DBPUTB_DUP:
424 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer);
426 if (duperr) yield = 1;
427 if (lastdup) EXIM_DBPUT(d, key, content);
431 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
438 /* Close files, rename or abandon the temporary files, and exit */
445 /* If successful, output the number of entries and rename the temporary
448 if (yield == 0 || yield == 1)
450 printf("%d entr%s written\n", count, (count == 1)? "y" : "ies");
453 printf("%d duplicate key%s \n", dupcount, (dupcount > 1)? "s" : "");
456 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
457 Ustrcpy(real_dbmname, temp_dbmname);
458 Ustrcpy(buffer, US argv[arg+1]);
459 if (Urename(real_dbmname, buffer) != 0)
461 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
466 /* Rename a single .db file */
470 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
471 sprintf(CS buffer, "%s.db", argv[arg+1]);
472 if (Urename(real_dbmname, buffer) != 0)
474 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
479 /* Rename .dir and .pag files */
483 sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
484 sprintf(CS buffer, "%s.dir", argv[arg+1]);
485 if (Urename(real_dbmname, buffer) != 0)
487 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
491 sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
492 sprintf(CS buffer, "%s.pag", argv[arg+1]);
493 if (Urename(real_dbmname, buffer) != 0)
495 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
500 #endif /* USE_DB || USE_TDB || USE_GDBM */
503 /* Otherwise unlink the temporary files. */
507 printf("dbmbuild abandoned\n");
508 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
509 /* We created it, so safe to delete despite the name coming from outside */
510 /* coverity[tainted_string] */
511 Uunlink(temp_dbmname);
515 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
516 Uunlink(real_dbmname);
520 sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
521 Uunlink(real_dbmname);
522 sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
523 Uunlink(real_dbmname);
525 #endif /* USE_DB || USE_TDB */
531 /* End of exim_dbmbuild.c */