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