1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2015 */
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 #
34 #define max_insize 20000
35 #define max_outsize 100000
37 /* This is global because it's defined in the headers and compilers grumble
38 if it is made static. */
40 const uschar *hex_digits = CUS"0123456789abcdef";
43 #ifdef STRERROR_FROM_ERRLIST
44 /* Some old-fashioned systems still around (e.g. SunOS4) don't have strerror()
45 in their libraries, but can provide the same facility by this simple
46 alternative function. */
51 if (n < 0 || n >= sys_nerr) return "unknown error number";
52 return sys_errlist[n];
54 #endif /* STRERROR_FROM_ERRLIST */
57 /* For Berkeley DB >= 2, we can define a function to be called in case of DB
58 errors. This should help with debugging strange DB problems, e.g. getting "File
59 exists" when you try to open a db file. The API changed at release 4.3. */
61 #if defined(USE_DB) && defined(DB_VERSION_STRING)
63 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
64 dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
68 dbfn_bdb_error_callback(const char *pfx, char *msg)
72 printf("Berkeley DB error: %s\n", msg);
78 /*************************************************
79 * Interpret escape sequence *
80 *************************************************/
82 /* This function is copied from the main Exim code.
85 pp points a pointer to the initiating "\" in the string;
86 the pointer gets updated to point to the final character
87 Returns: the value of the character escape
91 string_interpret_escape(const uschar **pp)
94 const uschar *p = *pp;
96 if (isdigit(ch) && ch != '8' && ch != '9')
99 if (isdigit(p[1]) && p[1] != '8' && p[1] != '9')
101 ch = ch * 8 + *(++p) - '0';
102 if (isdigit(p[1]) && p[1] != '8' && p[1] != '9')
103 ch = ch * 8 + *(++p) - '0';
108 case 'n': ch = '\n'; break;
109 case 'r': ch = '\r'; break;
110 case 't': ch = '\t'; break;
116 Ustrchr(hex_digits, tolower(*(++p))) - hex_digits;
117 if (isxdigit(p[1])) ch = ch * 16 +
118 Ustrchr(hex_digits, tolower(*(++p))) - hex_digits;
127 /*************************************************
129 *************************************************/
131 int main(int argc, char **argv)
139 BOOL lowercase = TRUE;
142 BOOL lastdup = FALSE;
143 #if !defined (USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
149 EXIM_DATUM key, content;
151 uschar keybuffer[256];
152 uschar temp_dbmname[512];
153 uschar real_dbmname[512];
155 uschar *buffer = malloc(max_outsize);
156 uschar *line = malloc(max_insize);
160 if (Ustrcmp(argv[arg], "-nolc") == 0) lowercase = FALSE;
161 else if (Ustrcmp(argv[arg], "-nowarn") == 0) warn = FALSE;
162 else if (Ustrcmp(argv[arg], "-lastdup") == 0) lastdup = TRUE;
163 else if (Ustrcmp(argv[arg], "-noduperr") == 0) duperr = FALSE;
164 else if (Ustrcmp(argv[arg], "-nozero") == 0) add_zero = 0;
172 printf("usage: exim_dbmbuild [-nolc] <source file> <dbm base name>\n");
176 if (Ustrcmp(argv[arg], "-") == 0) f = stdin; else
178 f = fopen(argv[arg], "rb");
181 printf("exim_dbmbuild: unable to open %s: %s\n", argv[arg], strerror(errno));
186 /* By default Berkeley db does not put extensions on... which
189 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
190 if (Ustrcmp(argv[arg], argv[arg+1]) == 0)
192 printf("exim_dbmbuild: input and output filenames are the same\n");
197 /* Check length of filename; allow for adding .dbmbuild_temp and .db or
200 if (strlen(argv[arg+1]) > sizeof(temp_dbmname) - 20)
202 printf("exim_dbmbuild: output filename is ridiculously long\n");
206 Ustrcpy(temp_dbmname, argv[arg+1]);
207 Ustrcat(temp_dbmname, ".dbmbuild_temp");
209 Ustrcpy(dirname, temp_dbmname);
210 if ((bptr = Ustrrchr(dirname, '/')))
213 /* It is apparently necessary to open with O_RDWR for this to work
214 with gdbm-1.7.3, though no reading is actually going to be done. */
216 EXIM_DBOPEN(temp_dbmname, dirname, O_RDWR|O_CREAT|O_EXCL, 0644, &d);
220 printf("exim_dbmbuild: unable to create %s: %s\n", temp_dbmname,
226 /* Unless using native db calls, see if we have created <name>.db; if not,
227 assume .dir & .pag */
229 #if !defined(USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
230 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
231 is_db = Ustat(real_dbmname, &statbuf) == 0;
234 /* Now do the business */
239 while (Ufgets(line, max_insize, f) != NULL)
242 int len = Ustrlen(line);
246 if (len >= max_insize - 1 && p[-1] != '\n')
248 printf("Overlong line read: max permitted length is %d\n", max_insize - 1);
253 if (line[0] == '#') continue;
254 while (p > line && isspace(p[-1])) p--;
256 if (line[0] == 0) continue;
258 /* A continuation line is valid only if there was a previous first
261 if (isspace(line[0]))
266 printf("Unexpected continuation line ignored\n%s\n\n", line);
269 while (isspace(*s)) s++;
272 if (bptr - buffer + p - s >= max_outsize - 1)
274 printf("Continued set of lines is too long: max permitted length is %d\n",
284 /* A first line must have a name followed by a colon or whitespace or
285 end of line, but first finish with a previous line. The key is lower
286 cased by default - this is what the newaliases program for sendmail does.
287 However, there's an option not to do this. */
297 EXIM_DATUM_INIT(content);
298 EXIM_DATUM_DATA(content) = CS buffer;
299 EXIM_DATUM_SIZE(content) = bptr - buffer + add_zero;
301 switch(rc = EXIM_DBPUTB(d, key, content))
307 case EXIM_DBPUTB_DUP:
308 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n",
311 if(duperr) yield = 1;
312 if (lastdup) EXIM_DBPUT(d, key, content);
316 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
325 EXIM_DATUM_INIT(key);
326 EXIM_DATUM_DATA(key) = CS keybuffer;
328 /* Deal with quoted keys. Escape sequences always make one character
329 out of several, so we can re-build in place. */
335 while (*s != 0 && *s != '\"')
337 if (*s == '\\') *t++ = string_interpret_escape((const uschar **)&s);
341 if (*s != 0) s++; /* Past terminating " */
342 EXIM_DATUM_SIZE(key) = t - keystart + add_zero;
347 while (*s != 0 && *s != ':' && !isspace(*s)) s++;
348 EXIM_DATUM_SIZE(key) = s - keystart + add_zero;
351 if (EXIM_DATUM_SIZE(key) > 256)
353 printf("Keys longer than 255 characters cannot be handled\n");
361 for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
362 keybuffer[i] = tolower(keystart[i]);
366 for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
367 keybuffer[i] = keystart[i];
373 while (isspace(*s))s++;
377 while (isspace(*s))s++;
391 EXIM_DATUM_INIT(content);
392 EXIM_DATUM_DATA(content) = CS buffer;
393 EXIM_DATUM_SIZE(content) = bptr - buffer + add_zero;
395 switch(rc = EXIM_DBPUTB(d, key, content))
401 case EXIM_DBPUTB_DUP:
402 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer);
404 if (duperr) yield = 1;
405 if (lastdup) EXIM_DBPUT(d, key, content);
409 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
416 /* Close files, rename or abandon the temporary files, and exit */
423 /* If successful, output the number of entries and rename the temporary
426 if (yield == 0 || yield == 1)
428 printf("%d entr%s written\n", count, (count == 1)? "y" : "ies");
431 printf("%d duplicate key%s \n", dupcount, (dupcount > 1)? "s" : "");
434 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
435 Ustrcpy(real_dbmname, temp_dbmname);
436 Ustrcpy(buffer, argv[arg+1]);
437 if (Urename(real_dbmname, buffer) != 0)
439 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
444 /* Rename a single .db file */
448 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
449 sprintf(CS buffer, "%s.db", argv[arg+1]);
450 if (Urename(real_dbmname, buffer) != 0)
452 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
457 /* Rename .dir and .pag files */
461 sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
462 sprintf(CS buffer, "%s.dir", argv[arg+1]);
463 if (Urename(real_dbmname, buffer) != 0)
465 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
469 sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
470 sprintf(CS buffer, "%s.pag", argv[arg+1]);
471 if (Urename(real_dbmname, buffer) != 0)
473 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
478 #endif /* USE_DB || USE_TDB || USE_GDBM */
481 /* Otherwise unlink the temporary files. */
485 printf("dbmbuild abandoned\n");
486 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
487 /* We created it, so safe to delete despite the name coming from outside */
488 /* coverity[tainted_string] */
489 Uunlink(temp_dbmname);
493 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
494 Uunlink(real_dbmname);
498 sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
499 Uunlink(real_dbmname);
500 sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
501 Uunlink(real_dbmname);
503 #endif /* USE_DB || USE_TDB */
509 /* End of exim_dbmbuild.c */