Improved ratelimit ACL condition.
[exim.git] / src / src / exim_dbutil.c
1 /* $Cambridge: exim/src/src/exim_dbutil.c,v 1.12 2009/11/16 19:50:36 nm4 Exp $ */
2
3 /*************************************************
4 * Exim - an Internet mail transport agent *
5 *************************************************/
6
7 /* Copyright (c) University of Cambridge 1995 - 2009 */
8 /* See the file NOTICE for conditions of use and distribution. */
9
10
11 /* This single source file is used to compile three utility programs for
12 maintaining Exim hints databases.
13
14 exim_dumpdb dumps out the contents
15 exim_fixdb patches the database (really for Exim maintenance/testing)
16 exim_tidydb removed obsolete data
17
18 In all cases, the first argument is the name of the spool directory. The second
19 argument is the name of the database file. The available names are:
20
21 retry: retry delivery information
22 misc: miscellaneous hints data
23 wait-<t>: message waiting information; <t> is a transport name
24 callout: callout verification cache
25
26 There are a number of common subroutines, followed by three main programs,
27 whose inclusion is controlled by -D on the compilation command. */
28
29
30 #include "exim.h"
31
32
33 /* Identifiers for the different database types. */
34
35 #define type_retry 1
36 #define type_wait 2
37 #define type_misc 3
38 #define type_callout 4
39 #define type_ratelimit 5
40
41
42 /* This is used by our cut-down dbfn_open(). */
43
44 uschar *spool_directory;
45
46
47
48 /*************************************************
49 * Berkeley DB error callback *
50 *************************************************/
51
52 /* For Berkeley DB >= 2, we can define a function to be called in case of DB
53 errors. This should help with debugging strange DB problems, e.g. getting "File
54 exists" when you try to open a db file. The API changed at release 4.3. */
55
56 #if defined(USE_DB) && defined(DB_VERSION_STRING)
57 void
58 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
59 dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
60 {
61 dbenv = dbenv;
62 #else
63 dbfn_bdb_error_callback(const char *pfx, char *msg)
64 {
65 #endif
66 pfx = pfx;
67 printf("Berkeley DB error: %s\n", msg);
68 }
69 #endif
70
71
72
73 /*************************************************
74 * SIGALRM handler *
75 *************************************************/
76
77 SIGNAL_BOOL sigalrm_seen;
78
79 void
80 sigalrm_handler(int sig)
81 {
82 sig = sig; /* Keep picky compilers happy */
83 sigalrm_seen = 1;
84 }
85
86
87
88 /*************************************************
89 * Output usage message and exit *
90 *************************************************/
91
92 static void
93 usage(uschar *name, uschar *options)
94 {
95 printf("Usage: exim_%s%s <spool-directory> <database-name>\n", name, options);
96 printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit\n");
97 exit(1);
98 }
99
100
101
102 /*************************************************
103 * Sort out the command arguments *
104 *************************************************/
105
106 /* This function checks that there are exactly 2 arguments, and checks the
107 second of them to be sure it is a known database name. */
108
109 static int
110 check_args(int argc, uschar **argv, uschar *name, uschar *options)
111 {
112 if (argc == 3)
113 {
114 if (Ustrcmp(argv[2], "retry") == 0) return type_retry;
115 if (Ustrcmp(argv[2], "misc") == 0) return type_misc;
116 if (Ustrncmp(argv[2], "wait-", 5) == 0) return type_wait;
117 if (Ustrcmp(argv[2], "callout") == 0) return type_callout;
118 if (Ustrcmp(argv[2], "ratelimit") == 0) return type_ratelimit;
119 }
120 usage(name, options);
121 return -1; /* Never obeyed */
122 }
123
124
125
126 /*************************************************
127 * Handle attempts to write the log *
128 *************************************************/
129
130 /* The message gets written to stderr when log_write() is called from a
131 utility. The message always gets '\n' added on the end of it. These calls come
132 from modules such as store.c when things go drastically wrong (e.g. malloc()
133 failing). In normal use they won't get obeyed.
134
135 Arguments:
136 selector not relevant when running a utility
137 flags not relevant when running a utility
138 format a printf() format
139 ... arguments for format
140
141 Returns: nothing
142 */
143
144 void
145 log_write(unsigned int selector, int flags, const char *format, ...)
146 {
147 va_list ap;
148 va_start(ap, format);
149 vfprintf(stderr, format, ap);
150 fprintf(stderr, "\n");
151 va_end(ap);
152 selector = selector; /* Keep picky compilers happy */
153 flags = flags;
154 }
155
156
157
158 /*************************************************
159 * Format a time value for printing *
160 *************************************************/
161
162 static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss ")];
163
164 uschar *
165 print_time(time_t t)
166 {
167 struct tm *tmstr = localtime(&t);
168 Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
169 return time_buffer;
170 }
171
172
173
174 /*************************************************
175 * Format a cache value for printing *
176 *************************************************/
177
178 uschar *
179 print_cache(int value)
180 {
181 return (value == ccache_accept)? US"accept" :
182 (value == ccache_reject)? US"reject" :
183 US"unknown";
184 }
185
186
187 #ifdef EXIM_FIXDB
188 /*************************************************
189 * Read time value *
190 *************************************************/
191
192 static time_t
193 read_time(uschar *s)
194 {
195 uschar *t = s;
196 int field = 0;
197 int value;
198 time_t now = time(NULL);
199 struct tm *tm = localtime(&now);
200
201 tm->tm_sec = 0;
202 tm->tm_isdst = -1;
203
204 for (t = s + Ustrlen(s) - 1; t >= s; t--)
205 {
206 if (*t == ':') continue;
207 if (!isdigit((uschar)*t)) return -1;
208
209 value = *t - '0';
210 if (--t >= s)
211 {
212 if (!isdigit((uschar)*t)) return -1;
213 value = value + (*t - '0')*10;
214 }
215
216 switch (field++)
217 {
218 case 0: tm->tm_min = value; break;
219 case 1: tm->tm_hour = value; break;
220 case 2: tm->tm_mday = value; break;
221 case 3: tm->tm_mon = value - 1; break;
222 case 4: tm->tm_year = (value < 90)? value + 100 : value; break;
223 default: return -1;
224 }
225 }
226
227 return mktime(tm);
228 }
229 #endif /* EXIM_FIXDB */
230
231
232
233 /*************************************************
234 * Open and lock a database file *
235 *************************************************/
236
237 /* This is a cut-down version from the function in dbfn.h that Exim itself
238 uses. We assume the database exists, and therefore give up if we cannot open
239 the lock file.
240
241 Arguments:
242 name The single-component name of one of Exim's database files.
243 flags O_RDONLY or O_RDWR
244 dbblock Points to an open_db block to be filled in.
245 lof Unused.
246
247 Returns: NULL if the open failed, or the locking failed.
248 On success, dbblock is returned. This contains the dbm pointer and
249 the fd of the locked lock file.
250 */
251
252 open_db *
253 dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof)
254 {
255 int rc;
256 struct flock lock_data;
257 BOOL read_only = flags == O_RDONLY;
258 uschar buffer[256];
259
260 /* The first thing to do is to open a separate file on which to lock. This
261 ensures that Exim has exclusive use of the database before it even tries to
262 open it. If there is a database, there should be a lock file in existence. */
263
264 sprintf(CS buffer, "%s/db/%s.lockfile", spool_directory, name);
265
266 dbblock->lockfd = Uopen(buffer, flags, 0);
267 if (dbblock->lockfd < 0)
268 {
269 printf("** Failed to open database lock file %s: %s\n", buffer,
270 strerror(errno));
271 return NULL;
272 }
273
274 /* Now we must get a lock on the opened lock file; do this with a blocking
275 lock that times out. */
276
277 lock_data.l_type = read_only? F_RDLCK : F_WRLCK;
278 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
279
280 sigalrm_seen = FALSE;
281 os_non_restarting_signal(SIGALRM, sigalrm_handler);
282 alarm(EXIMDB_LOCK_TIMEOUT);
283 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
284 alarm(0);
285
286 if (sigalrm_seen) errno = ETIMEDOUT;
287 if (rc < 0)
288 {
289 printf("** Failed to get %s lock for %s: %s",
290 ((flags & O_RDONLY) != 0)? "read" : "write", buffer,
291 (errno == ETIMEDOUT)? "timed out" : strerror(errno));
292 (void)close(dbblock->lockfd);
293 return NULL;
294 }
295
296 /* At this point we have an opened and locked separate lock file, that is,
297 exclusive access to the database, so we can go ahead and open it. */
298
299 sprintf(CS buffer, "%s/db/%s", spool_directory, name);
300 EXIM_DBOPEN(buffer, flags, 0, &(dbblock->dbptr));
301
302 if (dbblock->dbptr == NULL)
303 {
304 printf("** Failed to open DBM file %s for %s:\n %s%s\n", buffer,
305 read_only? "reading" : "writing", strerror(errno),
306 #ifdef USE_DB
307 " (or Berkeley DB error while opening)"
308 #else
309 ""
310 #endif
311 );
312 (void)close(dbblock->lockfd);
313 return NULL;
314 }
315
316 return dbblock;
317 }
318
319
320
321
322 /*************************************************
323 * Unlock and close a database file *
324 *************************************************/
325
326 /* Closing a file automatically unlocks it, so after closing the database, just
327 close the lock file.
328
329 Argument: a pointer to an open database block
330 Returns: nothing
331 */
332
333 void
334 dbfn_close(open_db *dbblock)
335 {
336 EXIM_DBCLOSE(dbblock->dbptr);
337 (void)close(dbblock->lockfd);
338 }
339
340
341
342
343 /*************************************************
344 * Read from database file *
345 *************************************************/
346
347 /* Passing back the pointer unchanged is useless, because there is no guarantee
348 of alignment. Since all the records used by Exim need to be properly aligned to
349 pick out the timestamps, etc., do the copying centrally here.
350
351 Arguments:
352 dbblock a pointer to an open database block
353 key the key of the record to be read
354 length where to put the length (or NULL if length not wanted)
355
356 Returns: a pointer to the retrieved record, or
357 NULL if the record is not found
358 */
359
360 void *
361 dbfn_read_with_length(open_db *dbblock, uschar *key, int *length)
362 {
363 void *yield;
364 EXIM_DATUM key_datum, result_datum;
365
366 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
367 EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */
368 EXIM_DATUM_DATA(key_datum) = CS key;
369 EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
370
371 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
372
373 yield = store_get(EXIM_DATUM_SIZE(result_datum));
374 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
375 if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
376
377 EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */
378 return yield;
379 }
380
381
382
383 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
384
385 /*************************************************
386 * Write to database file *
387 *************************************************/
388
389 /*
390 Arguments:
391 dbblock a pointer to an open database block
392 key the key of the record to be written
393 ptr a pointer to the record to be written
394 length the length of the record to be written
395
396 Returns: the yield of the underlying dbm or db "write" function. If this
397 is dbm, the value is zero for OK.
398 */
399
400 int
401 dbfn_write(open_db *dbblock, uschar *key, void *ptr, int length)
402 {
403 EXIM_DATUM key_datum, value_datum;
404 dbdata_generic *gptr = (dbdata_generic *)ptr;
405 gptr->time_stamp = time(NULL);
406
407 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
408 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
409 EXIM_DATUM_DATA(key_datum) = CS key;
410 EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
411 EXIM_DATUM_DATA(value_datum) = CS ptr;
412 EXIM_DATUM_SIZE(value_datum) = length;
413 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
414 }
415
416
417
418 /*************************************************
419 * Delete record from database file *
420 *************************************************/
421
422 /*
423 Arguments:
424 dbblock a pointer to an open database block
425 key the key of the record to be deleted
426
427 Returns: the yield of the underlying dbm or db "delete" function.
428 */
429
430 int
431 dbfn_delete(open_db *dbblock, uschar *key)
432 {
433 EXIM_DATUM key_datum;
434 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
435 EXIM_DATUM_DATA(key_datum) = CS key;
436 EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
437 return EXIM_DBDEL(dbblock->dbptr, key_datum);
438 }
439
440 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
441
442
443
444 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
445 /*************************************************
446 * Scan the keys of a database file *
447 *************************************************/
448
449 /*
450 Arguments:
451 dbblock a pointer to an open database block
452 start TRUE if starting a new scan
453 FALSE if continuing with the current scan
454 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
455 that use the notion of a cursor
456
457 Returns: the next record from the file, or
458 NULL if there are no more
459 */
460
461 uschar *
462 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
463 {
464 EXIM_DATUM key_datum, value_datum;
465 uschar *yield;
466 value_datum = value_datum; /* dummy; not all db libraries use this */
467
468 /* Some dbm require an initialization */
469
470 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
471
472 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
473 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
474
475 yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
476 US EXIM_DATUM_DATA(key_datum) : NULL;
477
478 /* Some dbm require a termination */
479
480 if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
481 return yield;
482 }
483 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
484
485
486
487 #ifdef EXIM_DUMPDB
488 /*************************************************
489 * The exim_dumpdb main program *
490 *************************************************/
491
492 int
493 main(int argc, char **cargv)
494 {
495 int dbdata_type = 0;
496 int yield = 0;
497 open_db dbblock;
498 open_db *dbm;
499 EXIM_CURSOR *cursor;
500 uschar **argv = USS cargv;
501 uschar *key;
502 uschar keybuffer[1024];
503
504 /* Check the arguments, and open the database */
505
506 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
507 spool_directory = argv[1];
508 dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE);
509 if (dbm == NULL) exit(1);
510
511 /* Scan the file, formatting the information for each entry. Note
512 that data is returned in a malloc'ed block, in order that it be
513 correctly aligned. */
514
515 key = dbfn_scan(dbm, TRUE, &cursor);
516 while (key != NULL)
517 {
518 dbdata_retry *retry;
519 dbdata_wait *wait;
520 dbdata_callout_cache *callout;
521 dbdata_ratelimit *ratelimit;
522 dbdata_ratelimit_unique *rate_unique;
523 int count_bad = 0;
524 int i, length;
525 uschar *t;
526 uschar name[MESSAGE_ID_LENGTH + 1];
527 void *value;
528
529 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
530 which might change. */
531
532 if (Ustrlen(key) > sizeof(keybuffer) - 1)
533 {
534 printf("**** Overlong key encountered: %s\n", key);
535 return 1;
536 }
537 Ustrcpy(keybuffer, key);
538 value = dbfn_read_with_length(dbm, keybuffer, &length);
539
540 if (value == NULL)
541 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
542 "was not found in the file - something is wrong!\n",
543 CS keybuffer);
544 else
545 {
546 /* Note: don't use print_time more than once in one statement, since
547 it uses a single buffer. */
548
549 switch(dbdata_type)
550 {
551 case type_retry:
552 retry = (dbdata_retry *)value;
553 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
554 retry->more_errno, retry->text,
555 print_time(retry->first_failed));
556 printf("%s ", print_time(retry->last_try));
557 printf("%s %s\n", print_time(retry->next_try),
558 (retry->expired)? "*" : "");
559 break;
560
561 case type_wait:
562 wait = (dbdata_wait *)value;
563 printf("%s ", keybuffer);
564 t = wait->text;
565 name[MESSAGE_ID_LENGTH] = 0;
566
567 if (wait->count > WAIT_NAME_MAX)
568 {
569 fprintf(stderr,
570 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
571 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
572 wait->count = WAIT_NAME_MAX;
573 yield = count_bad = 1;
574 }
575 for (i = 1; i <= wait->count; i++)
576 {
577 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
578 if (count_bad && name[0] == 0) break;
579 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
580 Ustrspn(name, "0123456789"
581 "abcdefghijklmnopqrstuvwxyz"
582 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
583 {
584 int j;
585 fprintf(stderr,
586 "**** Data for %s corrupted: bad character in message id\n",
587 CS keybuffer);
588 for (j = 0; j < MESSAGE_ID_LENGTH; j++)
589 fprintf(stderr, "%02x ", name[j]);
590 fprintf(stderr, "\n");
591 yield = 1;
592 break;
593 }
594 printf("%s ", name);
595 t += MESSAGE_ID_LENGTH;
596 }
597 printf("\n");
598 break;
599
600 case type_misc:
601 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
602 keybuffer);
603 break;
604
605 case type_callout:
606 callout = (dbdata_callout_cache *)value;
607
608 /* New-style address record */
609
610 if (length == sizeof(dbdata_callout_cache_address))
611 {
612 printf("%s %s callout=%s\n",
613 print_time(((dbdata_generic *)value)->time_stamp),
614 keybuffer,
615 print_cache(callout->result));
616 }
617
618 /* New-style domain record */
619
620 else if (length == sizeof(dbdata_callout_cache))
621 {
622 printf("%s %s callout=%s postmaster=%s",
623 print_time(((dbdata_generic *)value)->time_stamp),
624 keybuffer,
625 print_cache(callout->result),
626 print_cache(callout->postmaster_result));
627 if (callout->postmaster_result != ccache_unknown)
628 printf(" (%s)", print_time(callout->postmaster_stamp));
629 printf(" random=%s", print_cache(callout->random_result));
630 if (callout->random_result != ccache_unknown)
631 printf(" (%s)", print_time(callout->random_stamp));
632 printf("\n");
633 }
634
635 /* Old-style domain record, without separate timestamps. This code can
636 eventually be thrown away, say in 5 years' time (it's now Feb 2003). */
637
638 else
639 {
640 printf("%s %s callout=%s postmaster=%s random=%s\n",
641 print_time(((dbdata_generic *)value)->time_stamp),
642 keybuffer,
643 print_cache(callout->result),
644 print_cache(callout->postmaster_result),
645 print_cache(callout->random_result));
646 }
647
648 break;
649
650 case type_ratelimit:
651 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
652 {
653 ratelimit = (dbdata_ratelimit *)value;
654 rate_unique = (dbdata_ratelimit_unique *)value;
655 printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
656 print_time(ratelimit->time_stamp),
657 ratelimit->time_usec, ratelimit->rate,
658 print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
659 keybuffer);
660 }
661 else
662 {
663 ratelimit = (dbdata_ratelimit *)value;
664 printf("%s.%06d rate: %10.3f key: %s\n",
665 print_time(ratelimit->time_stamp),
666 ratelimit->time_usec, ratelimit->rate,
667 keybuffer);
668 }
669 break;
670 }
671 store_reset(value);
672 }
673 key = dbfn_scan(dbm, FALSE, &cursor);
674 }
675
676 dbfn_close(dbm);
677 return yield;
678 }
679
680 #endif /* EXIM_DUMPDB */
681
682
683
684
685 #ifdef EXIM_FIXDB
686 /*************************************************
687 * The exim_fixdb main program *
688 *************************************************/
689
690 /* In order not to hold the database lock any longer than is necessary, each
691 operation on the database uses a separate open/close call. This is expensive,
692 but then using this utility is not expected to be very common. Its main use is
693 to provide a way of patching up hints databases in order to run tests.
694
695 Syntax of commands:
696
697 (1) <record name>
698 This causes the data from the given record to be displayed, or "not found"
699 to be output. Note that in the retry database, destination names are
700 preceded by R: or T: for router or transport retry info.
701
702 (2) <record name> d
703 This causes the given record to be deleted or "not found" to be output.
704
705 (3) <record name> <field number> <value>
706 This sets the given value into the given field, identified by a number
707 which is output by the display command. Not all types of record can
708 be changed.
709
710 (4) q
711 This exits from exim_fixdb.
712
713 If the record name is omitted from (2) or (3), the previously used record name
714 is re-used. */
715
716
717 int main(int argc, char **cargv)
718 {
719 int dbdata_type;
720 uschar **argv = USS cargv;
721 uschar buffer[256];
722 uschar name[256];
723 void *reset_point = store_get(0);
724
725 name[0] = 0; /* No name set */
726
727 /* Sort out the database type, verify what we are working on and then process
728 user requests */
729
730 dbdata_type = check_args(argc, argv, US"fixdb", US"");
731 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
732
733 for(;;)
734 {
735 open_db dbblock;
736 open_db *dbm;
737 void *record;
738 dbdata_retry *retry;
739 dbdata_wait *wait;
740 dbdata_callout_cache *callout;
741 dbdata_ratelimit *ratelimit;
742 dbdata_ratelimit_unique *rate_unique;
743 int i, oldlength;
744 uschar *t;
745 uschar field[256], value[256];
746
747 store_reset(reset_point);
748
749 printf("> ");
750 if (Ufgets(buffer, 256, stdin) == NULL) break;
751
752 buffer[Ustrlen(buffer)-1] = 0;
753 field[0] = value[0] = 0;
754
755 /* If the buffer contains just one digit, or just consists of "d", use the
756 previous name for an update. */
757
758 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
759 || Ustrcmp(buffer, "d") == 0)
760 {
761 if (name[0] == 0)
762 {
763 printf("No previous record name is set\n");
764 continue;
765 }
766 (void)sscanf(CS buffer, "%s %s", field, value);
767 }
768 else
769 {
770 name[0] = 0;
771 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
772 }
773
774 /* Handle an update request */
775
776 if (field[0] != 0)
777 {
778 int verify = 1;
779 spool_directory = argv[1];
780 dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE);
781 if (dbm == NULL) continue;
782
783 if (Ustrcmp(field, "d") == 0)
784 {
785 if (value[0] != 0) printf("unexpected value after \"d\"\n");
786 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
787 "not found" : "deleted");
788 dbfn_close(dbm);
789 continue;
790 }
791
792 else if (isdigit((uschar)field[0]))
793 {
794 int fieldno = Uatoi(field);
795 if (value[0] == 0)
796 {
797 printf("value missing\n");
798 dbfn_close(dbm);
799 continue;
800 }
801 else
802 {
803 record = dbfn_read_with_length(dbm, name, &oldlength);
804 if (record == NULL) printf("not found\n"); else
805 {
806 time_t tt;
807 int length = 0; /* Stops compiler warning */
808
809 switch(dbdata_type)
810 {
811 case type_retry:
812 retry = (dbdata_retry *)record;
813 length = sizeof(dbdata_retry) + Ustrlen(retry->text);
814
815 switch(fieldno)
816 {
817 case 0:
818 retry->basic_errno = Uatoi(value);
819 break;
820
821 case 1:
822 retry->more_errno = Uatoi(value);
823 break;
824
825 case 2:
826 if ((tt = read_time(value)) > 0) retry->first_failed = tt;
827 else printf("bad time value\n");
828 break;
829
830 case 3:
831 if ((tt = read_time(value)) > 0) retry->last_try = tt;
832 else printf("bad time value\n");
833 break;
834
835 case 4:
836 if ((tt = read_time(value)) > 0) retry->next_try = tt;
837 else printf("bad time value\n");
838 break;
839
840 case 5:
841 if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
842 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
843 else printf("\"yes\" or \"no\" expected=n");
844 break;
845
846 default:
847 printf("unknown field number\n");
848 verify = 0;
849 break;
850 }
851 break;
852
853 case type_wait:
854 printf("Can't change contents of wait database record\n");
855 break;
856
857 case type_misc:
858 printf("Can't change contents of misc database record\n");
859 break;
860
861 case type_callout:
862 callout = (dbdata_callout_cache *)record;
863 length = sizeof(dbdata_callout_cache);
864 switch(fieldno)
865 {
866 case 0:
867 callout->result = Uatoi(value);
868 break;
869
870 case 1:
871 callout->postmaster_result = Uatoi(value);
872 break;
873
874 case 2:
875 callout->random_result = Uatoi(value);
876 break;
877
878 default:
879 printf("unknown field number\n");
880 verify = 0;
881 break;
882 }
883 break;
884
885 case type_ratelimit:
886 ratelimit = (dbdata_ratelimit *)record;
887 switch(fieldno)
888 {
889 case 0:
890 if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
891 else printf("bad time value\n");
892 break;
893
894 case 1:
895 ratelimit->time_usec = Uatoi(value);
896 break;
897
898 case 2:
899 ratelimit->rate = Ustrtod(value, NULL);
900 break;
901
902 case 3:
903 if (Ustrstr(name, "/unique/") != NULL
904 && oldlength >= sizeof(dbdata_ratelimit_unique))
905 {
906 rate_unique = (dbdata_ratelimit_unique *)record;
907 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
908 else printf("bad time value\n");
909 break;
910 }
911 /* else fall through */
912
913 case 4:
914 case 5:
915 if (Ustrstr(name, "/unique/") != NULL
916 && oldlength >= sizeof(dbdata_ratelimit_unique))
917 {
918 /* see acl.c */
919 BOOL seen;
920 unsigned n, hash, hinc;
921 uschar md5sum[16];
922 md5 md5info;
923 md5_start(&md5info);
924 md5_end(&md5info, value, Ustrlen(value), md5sum);
925 hash = md5sum[0] << 0 | md5sum[1] << 8
926 | md5sum[2] << 16 | md5sum[3] << 24;
927 hinc = md5sum[4] << 0 | md5sum[5] << 8
928 | md5sum[6] << 16 | md5sum[7] << 24;
929 rate_unique = (dbdata_ratelimit_unique *)record;
930 seen = TRUE;
931 for (n = 0; n < 8; n++, hash += hinc)
932 {
933 int bit = 1 << (hash % 8);
934 int byte = (hash / 8) % rate_unique->bloom_size;
935 if ((rate_unique->bloom[byte] & bit) == 0)
936 {
937 seen = FALSE;
938 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
939 }
940 }
941 printf("%s %s\n",
942 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
943 break;
944 }
945 /* else fall through */
946
947 default:
948 printf("unknown field number\n");
949 verify = 0;
950 break;
951 }
952 break;
953 }
954
955 dbfn_write(dbm, name, record, oldlength);
956 }
957 }
958 }
959
960 else
961 {
962 printf("field number or d expected\n");
963 verify = 0;
964 }
965
966 dbfn_close(dbm);
967 if (!verify) continue;
968 }
969
970 /* The "name" q causes an exit */
971
972 else if (Ustrcmp(name, "q") == 0) return 0;
973
974 /* Handle a read request, or verify after an update. */
975
976 spool_directory = argv[1];
977 dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE);
978 if (dbm == NULL) continue;
979
980 record = dbfn_read_with_length(dbm, name, &oldlength);
981 if (record == NULL)
982 {
983 printf("record %s not found\n", name);
984 name[0] = 0;
985 }
986 else
987 {
988 int count_bad = 0;
989 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
990 switch(dbdata_type)
991 {
992 case type_retry:
993 retry = (dbdata_retry *)record;
994 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
995 printf("1 extra data: %d\n", retry->more_errno);
996 printf("2 first failed: %s\n", print_time(retry->first_failed));
997 printf("3 last try: %s\n", print_time(retry->last_try));
998 printf("4 next try: %s\n", print_time(retry->next_try));
999 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
1000 break;
1001
1002 case type_wait:
1003 wait = (dbdata_wait *)record;
1004 t = wait->text;
1005 printf("Sequence: %d\n", wait->sequence);
1006 if (wait->count > WAIT_NAME_MAX)
1007 {
1008 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
1009 wait->count, WAIT_NAME_MAX);
1010 wait->count = WAIT_NAME_MAX;
1011 count_bad = 1;
1012 }
1013 for (i = 1; i <= wait->count; i++)
1014 {
1015 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1016 value[MESSAGE_ID_LENGTH] = 0;
1017 if (count_bad && value[0] == 0) break;
1018 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1019 Ustrspn(value, "0123456789"
1020 "abcdefghijklmnopqrstuvwxyz"
1021 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1022 {
1023 int j;
1024 printf("\n**** Data corrupted: bad character in message id ****\n");
1025 for (j = 0; j < MESSAGE_ID_LENGTH; j++)
1026 printf("%02x ", value[j]);
1027 printf("\n");
1028 break;
1029 }
1030 printf("%s ", value);
1031 t += MESSAGE_ID_LENGTH;
1032 }
1033 printf("\n");
1034 break;
1035
1036 case type_misc:
1037 break;
1038
1039 case type_callout:
1040 callout = (dbdata_callout_cache *)record;
1041 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1042 callout->result);
1043 if (oldlength > sizeof(dbdata_callout_cache_address))
1044 {
1045 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1046 callout->postmaster_result);
1047 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1048 callout->random_result);
1049 }
1050 break;
1051
1052 case type_ratelimit:
1053 ratelimit = (dbdata_ratelimit *)record;
1054 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1055 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1056 printf("2 sender rate: % .3f\n", ratelimit->rate);
1057 if (Ustrstr(name, "/unique/") != NULL
1058 && oldlength >= sizeof(dbdata_ratelimit_unique))
1059 {
1060 rate_unique = (dbdata_ratelimit_unique *)record;
1061 printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1062 printf("4 test filter membership\n");
1063 printf("5 add element to filter\n");
1064 }
1065 break;
1066 }
1067 }
1068
1069 /* The database is closed after each request */
1070
1071 dbfn_close(dbm);
1072 }
1073
1074 printf("\n");
1075 return 0;
1076 }
1077
1078 #endif /* EXIM_FIXDB */
1079
1080
1081
1082 #ifdef EXIM_TIDYDB
1083 /*************************************************
1084 * The exim_tidydb main program *
1085 *************************************************/
1086
1087
1088 /* Utility program to tidy the contents of an exim database file. There is one
1089 option:
1090
1091 -t <time> expiry time for old records - default 30 days
1092
1093 For backwards compatibility, an -f option is recognized and ignored. (It used
1094 to request a "full" tidy. This version always does the whole job.) */
1095
1096
1097 typedef struct key_item {
1098 struct key_item *next;
1099 uschar key[1];
1100 } key_item;
1101
1102
1103 int main(int argc, char **cargv)
1104 {
1105 struct stat statbuf;
1106 int maxkeep = 30 * 24 * 60 * 60;
1107 int dbdata_type, i, oldest, path_len;
1108 key_item *keychain = NULL;
1109 void *reset_point;
1110 open_db dbblock;
1111 open_db *dbm;
1112 EXIM_CURSOR *cursor;
1113 uschar **argv = USS cargv;
1114 uschar buffer[256];
1115 uschar *key;
1116
1117 /* Scan the options */
1118
1119 for (i = 1; i < argc; i++)
1120 {
1121 if (argv[i][0] != '-') break;
1122 if (Ustrcmp(argv[i], "-f") == 0) continue;
1123 if (Ustrcmp(argv[i], "-t") == 0)
1124 {
1125 uschar *s;
1126 s = argv[++i];
1127 maxkeep = 0;
1128 while (*s != 0)
1129 {
1130 int value, count;
1131 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1132 (void)sscanf(CS s, "%d%n", &value, &count);
1133 s += count;
1134 switch (*s)
1135 {
1136 case 'w': value *= 7;
1137 case 'd': value *= 24;
1138 case 'h': value *= 60;
1139 case 'm': value *= 60;
1140 case 's': s++;
1141 break;
1142 default: usage(US"tidydb", US" [-t <time>]");
1143 }
1144 maxkeep += value;
1145 }
1146 }
1147 else usage(US"tidydb", US" [-t <time>]");
1148 }
1149
1150 /* Adjust argument values and process arguments */
1151
1152 argc -= --i;
1153 argv += i;
1154
1155 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1156
1157 /* Compute the oldest keep time, verify what we are doing, and open the
1158 database */
1159
1160 oldest = time(NULL) - maxkeep;
1161 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1162
1163 spool_directory = argv[1];
1164 dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE);
1165 if (dbm == NULL) exit(1);
1166
1167 /* Prepare for building file names */
1168
1169 sprintf(CS buffer, "%s/input/", argv[1]);
1170 path_len = Ustrlen(buffer);
1171
1172
1173 /* It appears, by experiment, that it is a bad idea to make changes
1174 to the file while scanning it. Pity the man page doesn't warn you about that.
1175 Therefore, we scan and build a list of all the keys. Then we use that to
1176 read the records and possibly update them. */
1177
1178 key = dbfn_scan(dbm, TRUE, &cursor);
1179 while (key != NULL)
1180 {
1181 key_item *k = store_get(sizeof(key_item) + Ustrlen(key));
1182 k->next = keychain;
1183 keychain = k;
1184 Ustrcpy(k->key, key);
1185 key = dbfn_scan(dbm, FALSE, &cursor);
1186 }
1187
1188 /* Now scan the collected keys and operate on the records, resetting
1189 the store each time round. */
1190
1191 reset_point = store_get(0);
1192
1193 while (keychain != NULL)
1194 {
1195 dbdata_generic *value;
1196
1197 store_reset(reset_point);
1198 key = keychain->key;
1199 keychain = keychain->next;
1200 value = dbfn_read_with_length(dbm, key, NULL);
1201
1202 /* A continuation record may have been deleted or renamed already, so
1203 non-existence is not serious. */
1204
1205 if (value == NULL) continue;
1206
1207 /* Delete if too old */
1208
1209 if (value->time_stamp < oldest)
1210 {
1211 printf("deleted %s (too old)\n", key);
1212 dbfn_delete(dbm, key);
1213 continue;
1214 }
1215
1216 /* Do database-specific tidying for wait databases, and message-
1217 specific tidying for the retry database. */
1218
1219 if (dbdata_type == type_wait)
1220 {
1221 dbdata_wait *wait = (dbdata_wait *)value;
1222 BOOL update = FALSE;
1223
1224 /* Leave corrupt records alone */
1225
1226 if (wait->count > WAIT_NAME_MAX)
1227 {
1228 printf("**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
1229 key, wait->count, wait->count, WAIT_NAME_MAX);
1230 continue;
1231 }
1232
1233 /* Loop for renamed continuation records. For each message id,
1234 check to see if the message exists, and if not, remove its entry
1235 from the record. Because of the possibility of split input directories,
1236 we must look in both possible places for a -D file. */
1237
1238 for (;;)
1239 {
1240 int offset;
1241 int length = wait->count * MESSAGE_ID_LENGTH;
1242
1243 for (offset = length - MESSAGE_ID_LENGTH;
1244 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1245 {
1246 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1247 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1248
1249 if (Ustat(buffer, &statbuf) != 0)
1250 {
1251 buffer[path_len] = wait->text[offset+5];
1252 buffer[path_len+1] = '/';
1253 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1254 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1255
1256 if (Ustat(buffer, &statbuf) != 0)
1257 {
1258 int left = length - offset - MESSAGE_ID_LENGTH;
1259 if (left > 0) Ustrncpy(wait->text + offset,
1260 wait->text + offset + MESSAGE_ID_LENGTH, left);
1261 wait->count--;
1262 length -= MESSAGE_ID_LENGTH;
1263 update = TRUE;
1264 }
1265 }
1266 }
1267
1268 /* If record is empty and the main record, either delete it or rename
1269 the next continuation, repeating if that is also empty. */
1270
1271 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1272 {
1273 while (wait->count == 0 && wait->sequence > 0)
1274 {
1275 uschar newkey[256];
1276 dbdata_generic *newvalue;
1277 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1278 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1279 if (newvalue != NULL)
1280 {
1281 value = newvalue;
1282 wait = (dbdata_wait *)newvalue;
1283 dbfn_delete(dbm, newkey);
1284 printf("renamed %s\n", newkey);
1285 update = TRUE;
1286 }
1287 else wait->sequence--;
1288 }
1289
1290 /* If we have ended up with an empty main record, delete it
1291 and break the loop. Otherwise the new record will be scanned. */
1292
1293 if (wait->count == 0 && wait->sequence == 0)
1294 {
1295 dbfn_delete(dbm, key);
1296 printf("deleted %s (empty)\n", key);
1297 update = FALSE;
1298 break;
1299 }
1300 }
1301
1302 /* If not an empty main record, break the loop */
1303
1304 else break;
1305 }
1306
1307 /* Re-write the record if required */
1308
1309 if (update)
1310 {
1311 printf("updated %s\n", key);
1312 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1313 wait->count * MESSAGE_ID_LENGTH);
1314 }
1315 }
1316
1317 /* If a retry record's key ends with a message-id, check that that message
1318 still exists; if not, remove this record. */
1319
1320 else if (dbdata_type == type_retry)
1321 {
1322 uschar *id;
1323 int len = Ustrlen(key);
1324
1325 if (len < MESSAGE_ID_LENGTH + 1) continue;
1326 id = key + len - MESSAGE_ID_LENGTH - 1;
1327 if (*id++ != ':') continue;
1328
1329 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1330 {
1331 if (i == 6 || i == 13)
1332 { if (id[i] != '-') break; }
1333 else
1334 { if (!isalnum(id[i])) break; }
1335 }
1336 if (i < MESSAGE_ID_LENGTH) continue;
1337
1338 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1339 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1340
1341 if (Ustat(buffer, &statbuf) != 0)
1342 {
1343 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1344 if (Ustat(buffer, &statbuf) != 0)
1345 {
1346 dbfn_delete(dbm, key);
1347 printf("deleted %s (no message)\n", key);
1348 }
1349 }
1350 }
1351 }
1352
1353 dbfn_close(dbm);
1354 printf("Tidying complete\n");
1355 return 0;
1356 }
1357
1358 #endif /* EXIM_TIDYDB */
1359
1360 /* End of exim_dbutil.c */