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