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