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