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