typos
[exim.git] / src / src / exim_dbmbuild.c
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
10 alias files, this program fulfils the function of the newaliases program used
11 by other mailers, but it can be used for other dbm data files too. It operates
12 by writing a new file or files, and then renaming; otherwise old entries can
13 never get flushed out.
14
15 This 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
18 defined) there is no extension to the output filename. This is also handled. If
19 there are any other variants, the program won't cope.
20
21 The first argument to the program is the name of the serial file; the second
22 is the base name for the DBM file(s). When native db is in use, these must be
23 different.
24
25 Input lines beginning with # are ignored, as are blank lines. Entries begin
26 with a key terminated by a colon or end of line or whitespace and continue with
27 indented lines. Keys may be quoted if they contain colons or whitespace or #
28 characters. */
29
30
31 #include "exim.h"
32
33 uschar * spool_directory = NULL; /* dummy for dbstuff.h */
34
35 /******************************************************************************/
36 /* dummies needed by Solaris build */
37 void *
38 store_get_3(int size, BOOL tainted, const char *filename, int linenumber)
39 { return NULL; }
40 void **
41 store_reset_3(void **ptr, int pool, const char *filename, int linenumber)
42 { return NULL; }
43 void
44 store_release_above_3(void *ptr, const char *func, int linenumber)
45 { }
46 gstring *
47 string_vformat_trc(gstring * g, const uschar * func, unsigned line,
48 unsigned size_limit, unsigned flags, const char *format, va_list ap)
49 { return NULL; }
50 uschar *
51 string_sprintf_trc(const char * a, const uschar * b, unsigned c, ...)
52 { return NULL; }
53 BOOL split_spool_directory;
54 uschar * queue_name;
55 /******************************************************************************/
56
57
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
62 if it is made static. */
63
64 const uschar *hex_digits = CUS"0123456789abcdef";
65
66
67 #ifdef STRERROR_FROM_ERRLIST
68 /* Some old-fashioned systems still around (e.g. SunOS4) don't have strerror()
69 in their libraries, but can provide the same facility by this simple
70 alternative function. */
71
72 char *
73 strerror(int n)
74 {
75 if (n < 0 || n >= sys_nerr) return "unknown error number";
76 return 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
82 errors. This should help with debugging strange DB problems, e.g. getting "File
83 exists" when you try to open a db file. The API changed at release 4.3. */
84
85 #if defined(USE_DB) && defined(DB_VERSION_STRING)
86 void
87 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
88 dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
89 {
90 dbenv = dbenv;
91 #else
92 dbfn_bdb_error_callback(const char *pfx, char *msg)
93 {
94 #endif
95 pfx = pfx;
96 printf("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
108 Arguments:
109 pp points a pointer to the initiating "\" in the string;
110 the pointer gets updated to point to the final character
111 Returns: the value of the character escape
112 */
113
114 int
115 string_interpret_escape(const uschar **pp)
116 {
117 int ch;
118 const uschar *p = *pp;
119 ch = *(++p);
120 if (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 }
130 else 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;
147 return ch;
148 }
149
150
151 /*************************************************
152 * Main Program *
153 *************************************************/
154
155 int main(int argc, char **argv)
156 {
157 int started;
158 int count = 0;
159 int dupcount = 0;
160 int yield = 0;
161 int arg = 1;
162 int add_zero = 1;
163 BOOL lowercase = TRUE;
164 BOOL warn = TRUE;
165 BOOL duperr = TRUE;
166 BOOL lastdup = FALSE;
167 #if !defined (USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
168 int is_db = 0;
169 struct stat statbuf;
170 #endif
171 FILE *f;
172 EXIM_DB *d;
173 EXIM_DATUM key, content;
174 uschar *bptr;
175 uschar keybuffer[256];
176 uschar temp_dbmname[512];
177 uschar real_dbmname[512];
178 uschar dirname[512];
179 uschar *buffer = malloc(max_outsize);
180 uschar *line = malloc(max_insize);
181
182 while (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
194 if (argc != 3)
195 {
196 printf("usage: exim_dbmbuild [-nolc] <source file> <dbm base name>\n");
197 exit(1);
198 }
199
200 if (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
211 can be painful! */
212
213 #if defined(USE_DB) || defined(USE_TDB) || defined(USE_GDBM)
214 if (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
221 /* Check length of filename; allow for adding .dbmbuild_temp and .db or
222 .dir/.pag later. */
223
224 if (strlen(argv[arg+1]) > sizeof(temp_dbmname) - 20)
225 {
226 printf("exim_dbmbuild: output filename is ridiculously long\n");
227 exit(1);
228 }
229
230 Ustrcpy(temp_dbmname, US argv[arg+1]);
231 Ustrcat(temp_dbmname, US".dbmbuild_temp");
232
233 Ustrcpy(dirname, temp_dbmname);
234 if ((bptr = Ustrrchr(dirname, '/')))
235 *bptr = '\0';
236 else
237 Ustrcpy(dirname, US".");
238
239 /* It is apparently necessary to open with O_RDWR for this to work
240 with gdbm-1.7.3, though no reading is actually going to be done. */
241
242 EXIM_DBOPEN(temp_dbmname, dirname, O_RDWR|O_CREAT|O_EXCL, 0644, &d);
243
244 if (d == NULL)
245 {
246 printf("exim_dbmbuild: unable to create %s: %s\n", temp_dbmname,
247 strerror(errno));
248 (void)fclose(f);
249 exit(1);
250 }
251
252 /* Unless using native db calls, see if we have created <name>.db; if not,
253 assume .dir & .pag */
254
255 #if !defined(USE_DB) && !defined(USE_TDB) && !defined(USE_GDBM)
256 sprintf(CS real_dbmname, "%s.db", temp_dbmname);
257 is_db = Ustat(real_dbmname, &statbuf) == 0;
258 #endif
259
260 /* Now do the business */
261
262 bptr = buffer;
263 started = 0;
264
265 while (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);
275 yield = 2;
276 goto TIDYUP;
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);
302 yield = 2;
303 goto TIDYUP;
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:
330 count++;
331 break;
332
333 case EXIM_DBPUTB_DUP:
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;
339
340 default:
341 fprintf(stderr, "Error %d while writing key %s: errno=%d\n", rc,
342 keybuffer, errno);
343 yield = 2;
344 goto TIDYUP;
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 {
362 *t++ = *s == '\\'
363 ? string_interpret_escape((const uschar **)&s)
364 : *s;
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)
386 for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
387 keybuffer[i] = tolower(keystart[i]);
388 else
389 for (i = 0; i < EXIM_DATUM_SIZE(key) - add_zero; i++)
390 keybuffer[i] = keystart[i];
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
410 if (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
440 TIDYUP:
441
442 EXIM_DBCLOSE(d);
443 (void)fclose(f);
444
445 /* If successful, output the number of entries and rename the temporary
446 files. */
447
448 if (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);
458 Ustrcpy(buffer, US argv[arg+1]);
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
505 else
506 {
507 printf("dbmbuild abandoned\n");
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 */
510 /* coverity[tainted_string] */
511 Uunlink(temp_dbmname);
512 #else
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 }
525 #endif /* USE_DB || USE_TDB */
526 }
527
528 return yield;
529 }
530
531 /* End of exim_dbmbuild.c */