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