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