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