1 /* $Cambridge: exim/src/src/exim_dbmbuild.c,v 1.3 2005/06/14 10:32:01 ph10 Exp $ */
3 /*************************************************
4 * Exim - an Internet mail transport agent *
5 *************************************************/
7 /* Copyright (c) University of Cambridge 1995 - 2005 */
8 /* See the file NOTICE for conditions of use and distribution. */
11 /* A small freestanding program to build dbm databases from serial input. For
12 alias files, this program fulfils the function of the newaliases program used
13 by other mailers, but it can be used for other dbm data files too. It operates
14 by writing a new file or files, and then renaming; otherwise old entries can
15 never get flushed out.
17 This program is clever enough to cope with ndbm, which creates two files called
18 <name>.dir and <name>.pag, or with db, which creates a single file called
19 <name>.db. If native db is in use (USE_DB defined) or tdb is in use (USE_TDB
20 defined) there is no extension to the output filename. This is also handled. If
21 there are any other variants, the program won't cope.
23 The first argument to the program is the name of the serial file; the second
24 is the base name for the DBM file(s). When native db is in use, these must be
27 Input lines beginning with # are ignored, as are blank lines. Entries begin
28 with a key terminated by a colon or end of line or whitespace and continue with
29 indented lines. Keys may be quoted if they contain colons or whitespace or #
36 #define max_insize 20000
37 #define max_outsize 100000
39 /* This is global because it's defined in the headers and compilers grumble
40 if it is made static. */
42 uschar *hex_digits = US"0123456789abcdef";
45 #ifdef STRERROR_FROM_ERRLIST
46 /* Some old-fashioned systems still around (e.g. SunOS4) don't have strerror()
47 in their libraries, but can provide the same facility by this simple
48 alternative function. */
53 if (n < 0 || n >= sys_nerr) return "unknown error number";
54 return sys_errlist[n];
56 #endif /* STRERROR_FROM_ERRLIST */
59 /* For Berkeley DB >= 2, we can define a function to be called in case of DB
60 errors. This should help with debugging strange DB problems, e.g. getting "File
61 exists" when you try to open a db file. The API changed at release 4.3. */
63 #if defined(USE_DB) && defined(DB_VERSION_STRING)
65 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
66 dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
70 dbfn_bdb_error_callback(const char *pfx, char *msg)
74 printf("Berkeley DB error: %s\n", msg);
80 /*************************************************
81 * Interpret escape sequence *
82 *************************************************/
84 /* This function is copied from the main Exim code.
87 pp points a pointer to the initiating "\" in the string;
88 the pointer gets updated to point to the final character
89 Returns: the value of the character escape
93 string_interpret_escape(uschar **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[256];
155 uschar real_dbmname[256];
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 Ustrcpy(temp_dbmname, argv[arg+1]);
199 Ustrcat(temp_dbmname, ".dbmbuild_temp");
201 /* It is apparently necessary to open with O_RDWR for this to work
202 with gdbm-1.7.3, though no reading is actually going to be done. */
204 EXIM_DBOPEN(temp_dbmname, O_RDWR|O_CREAT|O_EXCL, 0644, &d);
208 printf("exim_dbmbuild: unable to create %s: %s\n", temp_dbmname,
214 /* Unless using native db calls, see if we have created <name>.db; if not,
215 assume .dir & .pag */
217 #if !defined(USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
218 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
219 is_db = Ustat(real_dbmname, &statbuf) == 0;
222 /* Now do the business */
227 while (Ufgets(line, max_insize, f) != NULL)
230 int len = Ustrlen(line);
234 if (len >= max_insize - 1 && p[-1] != '\n')
236 printf("Overlong line read: max permitted length is %d\n", max_insize - 1);
240 if (line[0] == '#') continue;
241 while (p > line && isspace(p[-1])) p--;
243 if (line[0] == 0) continue;
245 /* A continuation line is valid only if there was a previous first
248 if (isspace(line[0]))
253 printf("Unexpected continuation line ignored\n%s\n\n", line);
256 while (isspace(*s)) s++;
259 if (bptr - buffer + p - s >= max_outsize - 1)
261 printf("Continued set of lines is too long: max permitted length is %d\n",
270 /* A first line must have a name followed by a colon or whitespace or
271 end of line, but first finish with a previous line. The key is lower
272 cased by default - this is what the newaliases program for sendmail does.
273 However, there's an option not to do this. */
283 EXIM_DATUM_INIT(content);
284 EXIM_DATUM_DATA(content) = CS buffer;
285 EXIM_DATUM_SIZE(content) = bptr - buffer + add_zero;
287 switch(rc = EXIM_DBPUTB(d, key, content))
293 case EXIM_DBPUTB_DUP:
294 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n",
297 if(duperr) yield = 1;
298 if (lastdup) EXIM_DBPUT(d, key, content);
302 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
311 EXIM_DATUM_INIT(key);
312 EXIM_DATUM_DATA(key) = CS keybuffer;
314 /* Deal with quoted keys. Escape sequences always make one character
315 out of several, so we can re-build in place. */
321 while (*s != 0 && *s != '\"')
323 if (*s == '\\') *t++ = string_interpret_escape(&s);
327 if (*s != 0) s++; /* Past terminating " */
328 EXIM_DATUM_SIZE(key) = t - keystart + add_zero;
333 while (*s != 0 && *s != ':' && !isspace(*s)) s++;
334 EXIM_DATUM_SIZE(key) = s - keystart + add_zero;
337 if (EXIM_DATUM_SIZE(key) > 256)
339 printf("Keys longer than 255 characters cannot be handled\n");
347 for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
348 keybuffer[i] = tolower(keystart[i]);
352 for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
353 keybuffer[i] = keystart[i];
359 while (isspace(*s))s++;
363 while (isspace(*s))s++;
377 EXIM_DATUM_INIT(content);
378 EXIM_DATUM_DATA(content) = CS buffer;
379 EXIM_DATUM_SIZE(content) = bptr - buffer + add_zero;
381 switch(rc = EXIM_DBPUTB(d, key, content))
387 case EXIM_DBPUTB_DUP:
388 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer);
390 if (duperr) yield = 1;
391 if (lastdup) EXIM_DBPUT(d, key, content);
395 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
402 /* Close files, rename or abandon the temporary files, and exit */
409 /* If successful, output the number of entries and rename the temporary
412 if (yield == 0 || yield == 1)
414 printf("%d entr%s written\n", count, (count == 1)? "y" : "ies");
417 printf("%d duplicate key%s \n", dupcount, (dupcount > 1)? "s" : "");
420 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
421 Ustrcpy(real_dbmname, temp_dbmname);
422 Ustrcpy(buffer, argv[arg+1]);
423 if (Urename(real_dbmname, buffer) != 0)
425 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
430 /* Rename a single .db file */
434 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
435 sprintf(CS buffer, "%s.db", argv[arg+1]);
436 if (Urename(real_dbmname, buffer) != 0)
438 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
443 /* Rename .dir and .pag files */
447 sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
448 sprintf(CS buffer, "%s.dir", argv[arg+1]);
449 if (Urename(real_dbmname, buffer) != 0)
451 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
455 sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
456 sprintf(CS buffer, "%s.pag", argv[arg+1]);
457 if (Urename(real_dbmname, buffer) != 0)
459 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
464 #endif /* USE_DB || USE_TDB || USE_GDBM */
467 /* Otherwise unlink the temporary files. */
471 printf("dbmbuild abandoned\n");
472 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
473 Uunlink(temp_dbmname);
477 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
478 Uunlink(real_dbmname);
482 sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
483 Uunlink(real_dbmname);
484 sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
485 Uunlink(real_dbmname);
487 #endif /* USE_DB || USE_TDB */
493 /* End of exim_dbmbuild.c */