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