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