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