1 /* $Cambridge: exim/src/src/exim_dbmbuild.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */
3 /*************************************************
4 * Exim - an Internet mail transport agent *
5 *************************************************/
7 /* Copyright (c) University of Cambridge 1995 - 2004 */
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. */
63 #if defined(USE_DB) && defined(DB_VERSION_STRING)
65 dbfn_bdb_error_callback(const char *pfx, char *msg)
68 printf("Berkeley DB error: %s\n", msg);
74 /*************************************************
75 * Interpret escape sequence *
76 *************************************************/
78 /* This function is copied from the main Exim code.
81 pp points a pointer to the initiating "\" in the string;
82 the pointer gets updated to point to the final character
83 Returns: the value of the character escape
87 string_interpret_escape(uschar **pp)
92 if (isdigit(ch) && ch != '8' && ch != '9')
95 if (isdigit(p[1]) && p[1] != '8' && p[1] != '9')
97 ch = ch * 8 + *(++p) - '0';
98 if (isdigit(p[1]) && p[1] != '8' && p[1] != '9')
99 ch = ch * 8 + *(++p) - '0';
104 case 'n': ch = '\n'; break;
105 case 'r': ch = '\r'; break;
106 case 't': ch = '\t'; break;
112 Ustrchr(hex_digits, tolower(*(++p))) - hex_digits;
113 if (isxdigit(p[1])) ch = ch * 16 +
114 Ustrchr(hex_digits, tolower(*(++p))) - hex_digits;
123 /*************************************************
125 *************************************************/
127 int main(int argc, char **argv)
135 BOOL lowercase = TRUE;
138 BOOL lastdup = FALSE;
139 #if !defined (USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
145 EXIM_DATUM key, content;
147 uschar keybuffer[256];
148 uschar temp_dbmname[256];
149 uschar real_dbmname[256];
150 uschar *buffer = malloc(max_outsize);
151 uschar *line = malloc(max_insize);
155 if (Ustrcmp(argv[arg], "-nolc") == 0) lowercase = FALSE;
156 else if (Ustrcmp(argv[arg], "-nowarn") == 0) warn = FALSE;
157 else if (Ustrcmp(argv[arg], "-lastdup") == 0) lastdup = TRUE;
158 else if (Ustrcmp(argv[arg], "-noduperr") == 0) duperr = FALSE;
159 else if (Ustrcmp(argv[arg], "-nozero") == 0) add_zero = 0;
167 printf("usage: exim_dbmbuild [-nolc] <source file> <dbm base name>\n");
171 if (Ustrcmp(argv[arg], "-") == 0) f = stdin; else
173 f = fopen(argv[arg], "rb");
176 printf("exim_dbmbuild: unable to open %s: %s\n", argv[arg], strerror(errno));
181 /* By default Berkeley db does not put extensions on... which
184 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
185 if (Ustrcmp(argv[arg], argv[arg+1]) == 0)
187 printf("exim_dbmbuild: input and output filenames are the same\n");
192 Ustrcpy(temp_dbmname, argv[arg+1]);
193 Ustrcat(temp_dbmname, ".dbmbuild_temp");
195 /* It is apparently necessary to open with O_RDWR for this to work
196 with gdbm-1.7.3, though no reading is actually going to be done. */
198 EXIM_DBOPEN(temp_dbmname, O_RDWR|O_CREAT|O_EXCL, 0644, &d);
202 printf("exim_dbmbuild: unable to create %s: %s\n", temp_dbmname,
208 /* Unless using native db calls, see if we have created <name>.db; if not,
209 assume .dir & .pag */
211 #if !defined(USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
212 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
213 is_db = Ustat(real_dbmname, &statbuf) == 0;
216 /* Now do the business */
221 while (Ufgets(line, max_insize, f) != NULL)
224 int len = Ustrlen(line);
228 if (len >= max_insize - 1 && p[-1] != '\n')
230 printf("Overlong line read: max permitted length is %d\n", max_insize - 1);
234 if (line[0] == '#') continue;
235 while (p > line && isspace(p[-1])) p--;
237 if (line[0] == 0) continue;
239 /* A continuation line is valid only if there was a previous first
242 if (isspace(line[0]))
247 printf("Unexpected continuation line ignored\n%s\n\n", line);
250 while (isspace(*s)) s++;
253 if (bptr - buffer + p - s >= max_outsize - 1)
255 printf("Continued set of lines is too long: max permitted length is %d\n",
264 /* A first line must have a name followed by a colon or whitespace or
265 end of line, but first finish with a previous line. The key is lower
266 cased by default - this is what the newaliases program for sendmail does.
267 However, there's an option not to do this. */
277 EXIM_DATUM_INIT(content);
278 EXIM_DATUM_DATA(content) = CS buffer;
279 EXIM_DATUM_SIZE(content) = bptr - buffer + add_zero;
281 switch(rc = EXIM_DBPUTB(d, key, content))
287 case EXIM_DBPUTB_DUP:
288 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n",
291 if(duperr) yield = 1;
292 if (lastdup) EXIM_DBPUT(d, key, content);
296 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
305 EXIM_DATUM_INIT(key);
306 EXIM_DATUM_DATA(key) = CS keybuffer;
308 /* Deal with quoted keys. Escape sequences always make one character
309 out of several, so we can re-build in place. */
315 while (*s != 0 && *s != '\"')
317 if (*s == '\\') *t++ = string_interpret_escape(&s);
321 if (*s != 0) s++; /* Past terminating " */
322 EXIM_DATUM_SIZE(key) = t - keystart + add_zero;
327 while (*s != 0 && *s != ':' && !isspace(*s)) s++;
328 EXIM_DATUM_SIZE(key) = s - keystart + add_zero;
331 if (EXIM_DATUM_SIZE(key) > 256)
333 printf("Keys longer than 255 characters cannot be handled\n");
341 for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
342 keybuffer[i] = tolower(keystart[i]);
346 for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
347 keybuffer[i] = keystart[i];
353 while (isspace(*s))s++;
357 while (isspace(*s))s++;
371 EXIM_DATUM_INIT(content);
372 EXIM_DATUM_DATA(content) = CS buffer;
373 EXIM_DATUM_SIZE(content) = bptr - buffer + add_zero;
375 switch(rc = EXIM_DBPUTB(d, key, content))
381 case EXIM_DBPUTB_DUP:
382 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer);
384 if (duperr) yield = 1;
385 if (lastdup) EXIM_DBPUT(d, key, content);
389 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
396 /* Close files, rename or abandon the temporary files, and exit */
403 /* If successful, output the number of entries and rename the temporary
406 if (yield == 0 || yield == 1)
408 printf("%d entr%s written\n", count, (count == 1)? "y" : "ies");
411 printf("%d duplicate key%s \n", dupcount, (dupcount > 1)? "s" : "");
414 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
415 Ustrcpy(real_dbmname, temp_dbmname);
416 Ustrcpy(buffer, argv[arg+1]);
417 if (Urename(real_dbmname, buffer) != 0)
419 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
424 /* Rename a single .db file */
428 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
429 sprintf(CS buffer, "%s.db", argv[arg+1]);
430 if (Urename(real_dbmname, buffer) != 0)
432 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
437 /* Rename .dir and .pag files */
441 sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
442 sprintf(CS buffer, "%s.dir", argv[arg+1]);
443 if (Urename(real_dbmname, buffer) != 0)
445 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
449 sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
450 sprintf(CS buffer, "%s.pag", argv[arg+1]);
451 if (Urename(real_dbmname, buffer) != 0)
453 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
458 #endif /* USE_DB || USE_TDB || USE_GDBM */
461 /* Otherwise unlink the temporary files. */
465 printf("dbmbuild abandoned\n");
466 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
467 Uunlink(temp_dbmname);
471 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
472 Uunlink(real_dbmname);
476 sprintf(CS real_dbmname, "%s.dir", temp_dbmname);
477 Uunlink(real_dbmname);
478 sprintf(CS real_dbmname, "%s.pag", temp_dbmname);
479 Uunlink(real_dbmname);
481 #endif /* USE_DB || USE_TDB */
487 /* End of exim_dbmbuild.c */