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