Add directory name as new arg to EXIM_DBOPEN
[exim.git] / src / src / exim_dbmbuild.c
CommitLineData
059ec3d9
PH
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
4
3386088d 5/* Copyright (c) University of Cambridge 1995 - 2015 */
059ec3d9
PH
6/* See the file NOTICE for conditions of use and distribution. */
7
8
9/* A small freestanding program to build dbm databases from serial input. For
10alias files, this program fulfils the function of the newaliases program used
11by other mailers, but it can be used for other dbm data files too. It operates
12by writing a new file or files, and then renaming; otherwise old entries can
13never get flushed out.
14
15This 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
18defined) there is no extension to the output filename. This is also handled. If
19there are any other variants, the program won't cope.
20
21The first argument to the program is the name of the serial file; the second
22is the base name for the DBM file(s). When native db is in use, these must be
23different.
24
25Input lines beginning with # are ignored, as are blank lines. Entries begin
26with a key terminated by a colon or end of line or whitespace and continue with
27indented lines. Keys may be quoted if they contain colons or whitespace or #
28characters. */
29
30
31#include "exim.h"
32
33
34#define max_insize 20000
35#define max_outsize 100000
36
37/* This is global because it's defined in the headers and compilers grumble
38if it is made static. */
39
1ba28e2b 40const uschar *hex_digits = CUS"0123456789abcdef";
059ec3d9
PH
41
42
43#ifdef STRERROR_FROM_ERRLIST
44/* Some old-fashioned systems still around (e.g. SunOS4) don't have strerror()
45in their libraries, but can provide the same facility by this simple
46alternative function. */
47
48char *
49strerror(int n)
50{
51if (n < 0 || n >= sys_nerr) return "unknown error number";
52return sys_errlist[n];
53}
54#endif /* STRERROR_FROM_ERRLIST */
55
56
57/* For Berkeley DB >= 2, we can define a function to be called in case of DB
58errors. This should help with debugging strange DB problems, e.g. getting "File
1f922db1 59exists" when you try to open a db file. The API changed at release 4.3. */
059ec3d9
PH
60
61#if defined(USE_DB) && defined(DB_VERSION_STRING)
62void
1f922db1
PH
63#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
64dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
65{
66dbenv = dbenv;
67#else
059ec3d9
PH
68dbfn_bdb_error_callback(const char *pfx, char *msg)
69{
1f922db1 70#endif
059ec3d9
PH
71pfx = pfx;
72printf("Berkeley DB error: %s\n", msg);
73}
74#endif
75
76
77
78/*************************************************
79* Interpret escape sequence *
80*************************************************/
81
82/* This function is copied from the main Exim code.
83
84Arguments:
85 pp points a pointer to the initiating "\" in the string;
86 the pointer gets updated to point to the final character
87Returns: the value of the character escape
88*/
89
90int
55414b25 91string_interpret_escape(const uschar **pp)
059ec3d9
PH
92{
93int ch;
55414b25 94const uschar *p = *pp;
059ec3d9
PH
95ch = *(++p);
96if (isdigit(ch) && ch != '8' && ch != '9')
97 {
98 ch -= '0';
99 if (isdigit(p[1]) && p[1] != '8' && p[1] != '9')
100 {
101 ch = ch * 8 + *(++p) - '0';
102 if (isdigit(p[1]) && p[1] != '8' && p[1] != '9')
103 ch = ch * 8 + *(++p) - '0';
104 }
105 }
106else switch(ch)
107 {
108 case 'n': ch = '\n'; break;
109 case 'r': ch = '\r'; break;
110 case 't': ch = '\t'; break;
111 case 'x':
112 ch = 0;
113 if (isxdigit(p[1]))
114 {
115 ch = ch * 16 +
116 Ustrchr(hex_digits, tolower(*(++p))) - hex_digits;
117 if (isxdigit(p[1])) ch = ch * 16 +
118 Ustrchr(hex_digits, tolower(*(++p))) - hex_digits;
119 }
120 break;
121 }
122*pp = p;
123return ch;
124}
125
126
127/*************************************************
128* Main Program *
129*************************************************/
130
131int main(int argc, char **argv)
132{
133int started;
134int count = 0;
135int dupcount = 0;
136int yield = 0;
137int arg = 1;
138int add_zero = 1;
139BOOL lowercase = TRUE;
140BOOL warn = TRUE;
141BOOL duperr = TRUE;
142BOOL lastdup = FALSE;
143#if !defined (USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
144int is_db = 0;
145struct stat statbuf;
146#endif
147FILE *f;
148EXIM_DB *d;
149EXIM_DATUM key, content;
150uschar *bptr;
151uschar keybuffer[256];
a0540757
PH
152uschar temp_dbmname[512];
153uschar real_dbmname[512];
cfb9cf20 154uschar dirname[512];
059ec3d9
PH
155uschar *buffer = malloc(max_outsize);
156uschar *line = malloc(max_insize);
157
158while (argc > 1)
159 {
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;
165 else break;
166 arg++;
167 argc--;
168 }
169
170if (argc != 3)
171 {
172 printf("usage: exim_dbmbuild [-nolc] <source file> <dbm base name>\n");
173 exit(1);
174 }
175
176if (Ustrcmp(argv[arg], "-") == 0) f = stdin; else
177 {
178 f = fopen(argv[arg], "rb");
179 if (f == NULL)
180 {
181 printf("exim_dbmbuild: unable to open %s: %s\n", argv[arg], strerror(errno));
182 exit(1);
183 }
184 }
185
186/* By default Berkeley db does not put extensions on... which
187can be painful! */
188
189#if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
190if (Ustrcmp(argv[arg], argv[arg+1]) == 0)
191 {
192 printf("exim_dbmbuild: input and output filenames are the same\n");
193 exit(1);
194 }
195#endif
196
a0540757
PH
197/* Check length of filename; allow for adding .dbmbuild_temp and .db or
198.dir/.pag later. */
199
200if (strlen(argv[arg+1]) > sizeof(temp_dbmname) - 20)
201 {
202 printf("exim_dbmbuild: output filename is ridiculously long\n");
203 exit(1);
204 }
205
059ec3d9
PH
206Ustrcpy(temp_dbmname, argv[arg+1]);
207Ustrcat(temp_dbmname, ".dbmbuild_temp");
208
cfb9cf20
JH
209Ustrcpy(dirname, temp_dbmname);
210if ((bptr = Ustrrchr(dirname, '/')))
211 *bptr = '\0';
212
059ec3d9
PH
213/* It is apparently necessary to open with O_RDWR for this to work
214with gdbm-1.7.3, though no reading is actually going to be done. */
215
cfb9cf20 216EXIM_DBOPEN(temp_dbmname, dirname, O_RDWR|O_CREAT|O_EXCL, 0644, &d);
059ec3d9
PH
217
218if (d == NULL)
219 {
220 printf("exim_dbmbuild: unable to create %s: %s\n", temp_dbmname,
221 strerror(errno));
f1e894f3 222 (void)fclose(f);
059ec3d9
PH
223 exit(1);
224 }
225
226/* Unless using native db calls, see if we have created <name>.db; if not,
227assume .dir & .pag */
228
229#if !defined(USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
230sprintf(CS real_dbmname, "%s.db", temp_dbmname);
231is_db = Ustat(real_dbmname, &statbuf) == 0;
232#endif
233
234/* Now do the business */
235
236bptr = buffer;
237started = 0;
238
239while (Ufgets(line, max_insize, f) != NULL)
240 {
241 uschar *p;
242 int len = Ustrlen(line);
243
244 p = line + len;
245
246 if (len >= max_insize - 1 && p[-1] != '\n')
247 {
248 printf("Overlong line read: max permitted length is %d\n", max_insize - 1);
aa2b5c79
PH
249 yield = 2;
250 goto TIDYUP;
059ec3d9
PH
251 }
252
253 if (line[0] == '#') continue;
254 while (p > line && isspace(p[-1])) p--;
255 *p = 0;
256 if (line[0] == 0) continue;
257
258 /* A continuation line is valid only if there was a previous first
259 line. */
260
261 if (isspace(line[0]))
262 {
263 uschar *s = line;
264 if (!started)
265 {
266 printf("Unexpected continuation line ignored\n%s\n\n", line);
267 continue;
268 }
269 while (isspace(*s)) s++;
270 *(--s) = ' ';
271
272 if (bptr - buffer + p - s >= max_outsize - 1)
273 {
274 printf("Continued set of lines is too long: max permitted length is %d\n",
275 max_outsize -1);
aa2b5c79
PH
276 yield = 2;
277 goto TIDYUP;
059ec3d9
PH
278 }
279
280 Ustrcpy(bptr, s);
281 bptr += p - s;
282 }
283
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. */
288
289 else
290 {
291 int i, rc;
292 uschar *s = line;
293 uschar *keystart;
294
295 if (started)
296 {
297 EXIM_DATUM_INIT(content);
298 EXIM_DATUM_DATA(content) = CS buffer;
299 EXIM_DATUM_SIZE(content) = bptr - buffer + add_zero;
300
301 switch(rc = EXIM_DBPUTB(d, key, content))
302 {
303 case EXIM_DBPUTB_OK:
304 count++;
305 break;
306
307 case EXIM_DBPUTB_DUP:
308 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n",
309 keybuffer);
310 dupcount++;
311 if(duperr) yield = 1;
312 if (lastdup) EXIM_DBPUT(d, key, content);
313 break;
314
315 default:
316 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
317 keybuffer, errno);
318 yield = 2;
319 goto TIDYUP;
320 }
321
322 bptr = buffer;
323 }
324
325 EXIM_DATUM_INIT(key);
326 EXIM_DATUM_DATA(key) = CS keybuffer;
327
328 /* Deal with quoted keys. Escape sequences always make one character
329 out of several, so we can re-build in place. */
330
331 if (*s == '\"')
332 {
333 uschar *t = s++;
334 keystart = t;
335 while (*s != 0 && *s != '\"')
336 {
55414b25 337 if (*s == '\\') *t++ = string_interpret_escape((const uschar **)&s);
059ec3d9
PH
338 else *t++ = *s;
339 s++;
340 }
341 if (*s != 0) s++; /* Past terminating " */
342 EXIM_DATUM_SIZE(key) = t - keystart + add_zero;
343 }
344 else
345 {
346 keystart = s;
347 while (*s != 0 && *s != ':' && !isspace(*s)) s++;
348 EXIM_DATUM_SIZE(key) = s - keystart + add_zero;
349 }
350
351 if (EXIM_DATUM_SIZE(key) > 256)
352 {
353 printf("Keys longer than 255 characters cannot be handled\n");
354 started = 0;
355 yield = 2;
356 goto TIDYUP;
357 }
358
359 if (lowercase)
360 {
361 for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
362 keybuffer[i] = tolower(keystart[i]);
363 }
364 else
365 {
366 for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
367 keybuffer[i] = keystart[i];
368 }
369
370 keybuffer[i] = 0;
371 started = 1;
372
373 while (isspace(*s))s++;
374 if (*s == ':')
375 {
376 s++;
377 while (isspace(*s))s++;
378 }
379 if (*s != 0)
380 {
381 Ustrcpy(bptr, s);
382 bptr += p - s;
383 }
384 else buffer[0] = 0;
385 }
386 }
387
388if (started)
389 {
390 int rc;
391 EXIM_DATUM_INIT(content);
392 EXIM_DATUM_DATA(content) = CS buffer;
393 EXIM_DATUM_SIZE(content) = bptr - buffer + add_zero;
394
395 switch(rc = EXIM_DBPUTB(d, key, content))
396 {
397 case EXIM_DBPUTB_OK:
398 count++;
399 break;
400
401 case EXIM_DBPUTB_DUP:
402 if (warn) fprintf(stderr, "** Duplicate key \"%s\"\n", keybuffer);
403 dupcount++;
404 if (duperr) yield = 1;
405 if (lastdup) EXIM_DBPUT(d, key, content);
406 break;
407
408 default:
409 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
410 keybuffer, errno);
411 yield = 2;
412 break;
413 }
414 }
415
416/* Close files, rename or abandon the temporary files, and exit */
417
418TIDYUP:
419
420EXIM_DBCLOSE(d);
f1e894f3 421(void)fclose(f);
059ec3d9
PH
422
423/* If successful, output the number of entries and rename the temporary
424files. */
425
426if (yield == 0 || yield == 1)
427 {
428 printf("%d entr%s written\n", count, (count == 1)? "y" : "ies");
429 if (dupcount > 0)
430 {
431 printf("%d duplicate key%s \n", dupcount, (dupcount > 1)? "s" : "");
432 }
433
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)
438 {
439 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
440 return 1;
441 }
442 #else
443
444 /* Rename a single .db file */
445
446 if (is_db)
447 {
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)
451 {
452 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
453 return 1;
454 }
455 }
456
457 /* Rename .dir and .pag files */
458
459 else
460 {
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)
464 {
465 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
466 return 1;
467 }
468
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)
472 {
473 printf("Unable to rename %s as %s\n", real_dbmname, buffer);
474 return 1;
475 }
476 }
477
478 #endif /* USE_DB || USE_TDB || USE_GDBM */
479 }
480
481/* Otherwise unlink the temporary files. */
482
483else
484 {
485 printf("dbmbuild abandoned\n");
d88f0784
JH
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 */
6e92b3ae 488 /* coverity[tainted_string] */
059ec3d9 489 Uunlink(temp_dbmname);
d88f0784 490#else
059ec3d9
PH
491 if (is_db)
492 {
493 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
494 Uunlink(real_dbmname);
495 }
496 else
497 {
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);
502 }
d88f0784 503#endif /* USE_DB || USE_TDB */
059ec3d9
PH
504 }
505
506return yield;
507}
508
509/* End of exim_dbmbuild.c */