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