1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2009 */
6 /* See the file NOTICE for conditions of use and distribution. */
9 /* This single source file is used to compile three utility programs for
10 maintaining Exim hints databases.
12 exim_dumpdb dumps out the contents
13 exim_fixdb patches the database (really for Exim maintenance/testing)
14 exim_tidydb removed obsolete data
16 In all cases, the first argument is the name of the spool directory. The second
17 argument is the name of the database file. The available names are:
19 retry: retry delivery information
20 misc: miscellaneous hints data
21 wait-<t>: message waiting information; <t> is a transport name
22 callout: callout verification cache
24 There are a number of common subroutines, followed by three main programs,
25 whose inclusion is controlled by -D on the compilation command. */
31 /* Identifiers for the different database types. */
36 #define type_callout 4
37 #define type_ratelimit 5
40 /* This is used by our cut-down dbfn_open(). */
42 uschar *spool_directory;
46 /*************************************************
47 * Berkeley DB error callback *
48 *************************************************/
50 /* For Berkeley DB >= 2, we can define a function to be called in case of DB
51 errors. This should help with debugging strange DB problems, e.g. getting "File
52 exists" when you try to open a db file. The API changed at release 4.3. */
54 #if defined(USE_DB) && defined(DB_VERSION_STRING)
56 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
57 dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
61 dbfn_bdb_error_callback(const char *pfx, char *msg)
65 printf("Berkeley DB error: %s\n", msg);
71 /*************************************************
73 *************************************************/
75 SIGNAL_BOOL sigalrm_seen;
78 sigalrm_handler(int sig)
80 sig = sig; /* Keep picky compilers happy */
86 /*************************************************
87 * Output usage message and exit *
88 *************************************************/
91 usage(uschar *name, uschar *options)
93 printf("Usage: exim_%s%s <spool-directory> <database-name>\n", name, options);
94 printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit\n");
100 /*************************************************
101 * Sort out the command arguments *
102 *************************************************/
104 /* This function checks that there are exactly 2 arguments, and checks the
105 second of them to be sure it is a known database name. */
108 check_args(int argc, uschar **argv, uschar *name, uschar *options)
112 if (Ustrcmp(argv[2], "retry") == 0) return type_retry;
113 if (Ustrcmp(argv[2], "misc") == 0) return type_misc;
114 if (Ustrncmp(argv[2], "wait-", 5) == 0) return type_wait;
115 if (Ustrcmp(argv[2], "callout") == 0) return type_callout;
116 if (Ustrcmp(argv[2], "ratelimit") == 0) return type_ratelimit;
118 usage(name, options);
119 return -1; /* Never obeyed */
124 /*************************************************
125 * Handle attempts to write the log *
126 *************************************************/
128 /* The message gets written to stderr when log_write() is called from a
129 utility. The message always gets '\n' added on the end of it. These calls come
130 from modules such as store.c when things go drastically wrong (e.g. malloc()
131 failing). In normal use they won't get obeyed.
134 selector not relevant when running a utility
135 flags not relevant when running a utility
136 format a printf() format
137 ... arguments for format
143 log_write(unsigned int selector, int flags, const char *format, ...)
146 va_start(ap, format);
147 vfprintf(stderr, format, ap);
148 fprintf(stderr, "\n");
150 selector = selector; /* Keep picky compilers happy */
156 /*************************************************
157 * Format a time value for printing *
158 *************************************************/
160 static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss ")];
165 struct tm *tmstr = localtime(&t);
166 Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
172 /*************************************************
173 * Format a cache value for printing *
174 *************************************************/
177 print_cache(int value)
179 return (value == ccache_accept)? US"accept" :
180 (value == ccache_reject)? US"reject" :
186 /*************************************************
188 *************************************************/
196 time_t now = time(NULL);
197 struct tm *tm = localtime(&now);
202 for (t = s + Ustrlen(s) - 1; t >= s; t--)
204 if (*t == ':') continue;
205 if (!isdigit((uschar)*t)) return -1;
210 if (!isdigit((uschar)*t)) return -1;
211 value = value + (*t - '0')*10;
216 case 0: tm->tm_min = value; break;
217 case 1: tm->tm_hour = value; break;
218 case 2: tm->tm_mday = value; break;
219 case 3: tm->tm_mon = value - 1; break;
220 case 4: tm->tm_year = (value < 90)? value + 100 : value; break;
227 #endif /* EXIM_FIXDB */
231 /*************************************************
232 * Open and lock a database file *
233 *************************************************/
235 /* This is a cut-down version from the function in dbfn.h that Exim itself
236 uses. We assume the database exists, and therefore give up if we cannot open
240 name The single-component name of one of Exim's database files.
241 flags O_RDONLY or O_RDWR
242 dbblock Points to an open_db block to be filled in.
245 Returns: NULL if the open failed, or the locking failed.
246 On success, dbblock is returned. This contains the dbm pointer and
247 the fd of the locked lock file.
251 dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof)
254 struct flock lock_data;
255 BOOL read_only = flags == O_RDONLY;
258 /* The first thing to do is to open a separate file on which to lock. This
259 ensures that Exim has exclusive use of the database before it even tries to
260 open it. If there is a database, there should be a lock file in existence. */
262 sprintf(CS buffer, "%s/db/%s.lockfile", spool_directory, name);
264 dbblock->lockfd = Uopen(buffer, flags, 0);
265 if (dbblock->lockfd < 0)
267 printf("** Failed to open database lock file %s: %s\n", buffer,
272 /* Now we must get a lock on the opened lock file; do this with a blocking
273 lock that times out. */
275 lock_data.l_type = read_only? F_RDLCK : F_WRLCK;
276 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
278 sigalrm_seen = FALSE;
279 os_non_restarting_signal(SIGALRM, sigalrm_handler);
280 alarm(EXIMDB_LOCK_TIMEOUT);
281 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
284 if (sigalrm_seen) errno = ETIMEDOUT;
287 printf("** Failed to get %s lock for %s: %s",
288 ((flags & O_RDONLY) != 0)? "read" : "write", buffer,
289 (errno == ETIMEDOUT)? "timed out" : strerror(errno));
290 (void)close(dbblock->lockfd);
294 /* At this point we have an opened and locked separate lock file, that is,
295 exclusive access to the database, so we can go ahead and open it. */
297 sprintf(CS buffer, "%s/db/%s", spool_directory, name);
298 EXIM_DBOPEN(buffer, flags, 0, &(dbblock->dbptr));
300 if (dbblock->dbptr == NULL)
302 printf("** Failed to open DBM file %s for %s:\n %s%s\n", buffer,
303 read_only? "reading" : "writing", strerror(errno),
305 " (or Berkeley DB error while opening)"
310 (void)close(dbblock->lockfd);
320 /*************************************************
321 * Unlock and close a database file *
322 *************************************************/
324 /* Closing a file automatically unlocks it, so after closing the database, just
327 Argument: a pointer to an open database block
332 dbfn_close(open_db *dbblock)
334 EXIM_DBCLOSE(dbblock->dbptr);
335 (void)close(dbblock->lockfd);
341 /*************************************************
342 * Read from database file *
343 *************************************************/
345 /* Passing back the pointer unchanged is useless, because there is no guarantee
346 of alignment. Since all the records used by Exim need to be properly aligned to
347 pick out the timestamps, etc., do the copying centrally here.
350 dbblock a pointer to an open database block
351 key the key of the record to be read
352 length where to put the length (or NULL if length not wanted)
354 Returns: a pointer to the retrieved record, or
355 NULL if the record is not found
359 dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
362 EXIM_DATUM key_datum, result_datum;
363 int klen = Ustrlen(key) + 1;
364 uschar * key_copy = store_get(klen);
366 memcpy(key_copy, key, klen);
368 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
369 EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */
370 EXIM_DATUM_DATA(key_datum) = CS key_copy;
371 EXIM_DATUM_SIZE(key_datum) = klen;
373 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
375 yield = store_get(EXIM_DATUM_SIZE(result_datum));
376 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
377 if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
379 EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */
385 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
387 /*************************************************
388 * Write to database file *
389 *************************************************/
393 dbblock a pointer to an open database block
394 key the key of the record to be written
395 ptr a pointer to the record to be written
396 length the length of the record to be written
398 Returns: the yield of the underlying dbm or db "write" function. If this
399 is dbm, the value is zero for OK.
403 dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
405 EXIM_DATUM key_datum, value_datum;
406 dbdata_generic *gptr = (dbdata_generic *)ptr;
407 int klen = Ustrlen(key) + 1;
408 uschar * key_copy = store_get(klen);
410 memcpy(key_copy, key, klen);
411 gptr->time_stamp = time(NULL);
413 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
414 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
415 EXIM_DATUM_DATA(key_datum) = CS key_copy;
416 EXIM_DATUM_SIZE(key_datum) = klen;
417 EXIM_DATUM_DATA(value_datum) = CS ptr;
418 EXIM_DATUM_SIZE(value_datum) = length;
419 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
424 /*************************************************
425 * Delete record from database file *
426 *************************************************/
430 dbblock a pointer to an open database block
431 key the key of the record to be deleted
433 Returns: the yield of the underlying dbm or db "delete" function.
437 dbfn_delete(open_db *dbblock, const uschar *key)
439 int klen = Ustrlen(key) + 1;
440 uschar * key_copy = store_get(klen);
442 memcpy(key_copy, key, klen);
443 EXIM_DATUM key_datum;
444 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
445 EXIM_DATUM_DATA(key_datum) = CS key_copy;
446 EXIM_DATUM_SIZE(key_datum) = klen;
447 return EXIM_DBDEL(dbblock->dbptr, key_datum);
450 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
454 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
455 /*************************************************
456 * Scan the keys of a database file *
457 *************************************************/
461 dbblock a pointer to an open database block
462 start TRUE if starting a new scan
463 FALSE if continuing with the current scan
464 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
465 that use the notion of a cursor
467 Returns: the next record from the file, or
468 NULL if there are no more
472 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
474 EXIM_DATUM key_datum, value_datum;
476 value_datum = value_datum; /* dummy; not all db libraries use this */
478 /* Some dbm require an initialization */
480 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
482 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
483 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
485 yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
486 US EXIM_DATUM_DATA(key_datum) : NULL;
488 /* Some dbm require a termination */
490 if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
493 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
498 /*************************************************
499 * The exim_dumpdb main program *
500 *************************************************/
503 main(int argc, char **cargv)
510 uschar **argv = USS cargv;
512 uschar keybuffer[1024];
514 /* Check the arguments, and open the database */
516 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
517 spool_directory = argv[1];
518 dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE);
519 if (dbm == NULL) exit(1);
521 /* Scan the file, formatting the information for each entry. Note
522 that data is returned in a malloc'ed block, in order that it be
523 correctly aligned. */
525 key = dbfn_scan(dbm, TRUE, &cursor);
530 dbdata_callout_cache *callout;
531 dbdata_ratelimit *ratelimit;
532 dbdata_ratelimit_unique *rate_unique;
536 uschar name[MESSAGE_ID_LENGTH + 1];
539 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
540 which might change. */
542 if (Ustrlen(key) > sizeof(keybuffer) - 1)
544 printf("**** Overlong key encountered: %s\n", key);
547 Ustrcpy(keybuffer, key);
548 value = dbfn_read_with_length(dbm, keybuffer, &length);
551 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
552 "was not found in the file - something is wrong!\n",
556 /* Note: don't use print_time more than once in one statement, since
557 it uses a single buffer. */
562 retry = (dbdata_retry *)value;
563 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
564 retry->more_errno, retry->text,
565 print_time(retry->first_failed));
566 printf("%s ", print_time(retry->last_try));
567 printf("%s %s\n", print_time(retry->next_try),
568 (retry->expired)? "*" : "");
572 wait = (dbdata_wait *)value;
573 printf("%s ", keybuffer);
575 name[MESSAGE_ID_LENGTH] = 0;
577 if (wait->count > WAIT_NAME_MAX)
580 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
581 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
582 wait->count = WAIT_NAME_MAX;
583 yield = count_bad = 1;
585 for (i = 1; i <= wait->count; i++)
587 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
588 if (count_bad && name[0] == 0) break;
589 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
590 Ustrspn(name, "0123456789"
591 "abcdefghijklmnopqrstuvwxyz"
592 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
596 "**** Data for %s corrupted: bad character in message id\n",
598 for (j = 0; j < MESSAGE_ID_LENGTH; j++)
599 fprintf(stderr, "%02x ", name[j]);
600 fprintf(stderr, "\n");
605 t += MESSAGE_ID_LENGTH;
611 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
616 callout = (dbdata_callout_cache *)value;
618 /* New-style address record */
620 if (length == sizeof(dbdata_callout_cache_address))
622 printf("%s %s callout=%s\n",
623 print_time(((dbdata_generic *)value)->time_stamp),
625 print_cache(callout->result));
628 /* New-style domain record */
630 else if (length == sizeof(dbdata_callout_cache))
632 printf("%s %s callout=%s postmaster=%s",
633 print_time(((dbdata_generic *)value)->time_stamp),
635 print_cache(callout->result),
636 print_cache(callout->postmaster_result));
637 if (callout->postmaster_result != ccache_unknown)
638 printf(" (%s)", print_time(callout->postmaster_stamp));
639 printf(" random=%s", print_cache(callout->random_result));
640 if (callout->random_result != ccache_unknown)
641 printf(" (%s)", print_time(callout->random_stamp));
645 /* Old-style domain record, without separate timestamps. This code can
646 eventually be thrown away, say in 5 years' time (it's now Feb 2003). */
650 printf("%s %s callout=%s postmaster=%s random=%s\n",
651 print_time(((dbdata_generic *)value)->time_stamp),
653 print_cache(callout->result),
654 print_cache(callout->postmaster_result),
655 print_cache(callout->random_result));
661 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
663 ratelimit = (dbdata_ratelimit *)value;
664 rate_unique = (dbdata_ratelimit_unique *)value;
665 printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
666 print_time(ratelimit->time_stamp),
667 ratelimit->time_usec, ratelimit->rate,
668 print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
673 ratelimit = (dbdata_ratelimit *)value;
674 printf("%s.%06d rate: %10.3f key: %s\n",
675 print_time(ratelimit->time_stamp),
676 ratelimit->time_usec, ratelimit->rate,
683 key = dbfn_scan(dbm, FALSE, &cursor);
690 #endif /* EXIM_DUMPDB */
696 /*************************************************
697 * The exim_fixdb main program *
698 *************************************************/
700 /* In order not to hold the database lock any longer than is necessary, each
701 operation on the database uses a separate open/close call. This is expensive,
702 but then using this utility is not expected to be very common. Its main use is
703 to provide a way of patching up hints databases in order to run tests.
708 This causes the data from the given record to be displayed, or "not found"
709 to be output. Note that in the retry database, destination names are
710 preceded by R: or T: for router or transport retry info.
713 This causes the given record to be deleted or "not found" to be output.
715 (3) <record name> <field number> <value>
716 This sets the given value into the given field, identified by a number
717 which is output by the display command. Not all types of record can
721 This exits from exim_fixdb.
723 If the record name is omitted from (2) or (3), the previously used record name
727 int main(int argc, char **cargv)
730 uschar **argv = USS cargv;
733 void *reset_point = store_get(0);
735 name[0] = 0; /* No name set */
737 /* Sort out the database type, verify what we are working on and then process
740 dbdata_type = check_args(argc, argv, US"fixdb", US"");
741 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
750 dbdata_callout_cache *callout;
751 dbdata_ratelimit *ratelimit;
752 dbdata_ratelimit_unique *rate_unique;
755 uschar field[256], value[256];
757 store_reset(reset_point);
760 if (Ufgets(buffer, 256, stdin) == NULL) break;
762 buffer[Ustrlen(buffer)-1] = 0;
763 field[0] = value[0] = 0;
765 /* If the buffer contains just one digit, or just consists of "d", use the
766 previous name for an update. */
768 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
769 || Ustrcmp(buffer, "d") == 0)
773 printf("No previous record name is set\n");
776 (void)sscanf(CS buffer, "%s %s", field, value);
781 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
784 /* Handle an update request */
789 spool_directory = argv[1];
790 dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE);
791 if (dbm == NULL) continue;
793 if (Ustrcmp(field, "d") == 0)
795 if (value[0] != 0) printf("unexpected value after \"d\"\n");
796 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
797 "not found" : "deleted");
802 else if (isdigit((uschar)field[0]))
804 int fieldno = Uatoi(field);
807 printf("value missing\n");
813 record = dbfn_read_with_length(dbm, name, &oldlength);
814 if (record == NULL) printf("not found\n"); else
817 int length = 0; /* Stops compiler warning */
822 retry = (dbdata_retry *)record;
823 length = sizeof(dbdata_retry) + Ustrlen(retry->text);
828 retry->basic_errno = Uatoi(value);
832 retry->more_errno = Uatoi(value);
836 if ((tt = read_time(value)) > 0) retry->first_failed = tt;
837 else printf("bad time value\n");
841 if ((tt = read_time(value)) > 0) retry->last_try = tt;
842 else printf("bad time value\n");
846 if ((tt = read_time(value)) > 0) retry->next_try = tt;
847 else printf("bad time value\n");
851 if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
852 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
853 else printf("\"yes\" or \"no\" expected=n");
857 printf("unknown field number\n");
864 printf("Can't change contents of wait database record\n");
868 printf("Can't change contents of misc database record\n");
872 callout = (dbdata_callout_cache *)record;
873 length = sizeof(dbdata_callout_cache);
877 callout->result = Uatoi(value);
881 callout->postmaster_result = Uatoi(value);
885 callout->random_result = Uatoi(value);
889 printf("unknown field number\n");
896 ratelimit = (dbdata_ratelimit *)record;
900 if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
901 else printf("bad time value\n");
905 ratelimit->time_usec = Uatoi(value);
909 ratelimit->rate = Ustrtod(value, NULL);
913 if (Ustrstr(name, "/unique/") != NULL
914 && oldlength >= sizeof(dbdata_ratelimit_unique))
916 rate_unique = (dbdata_ratelimit_unique *)record;
917 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
918 else printf("bad time value\n");
921 /* else fall through */
925 if (Ustrstr(name, "/unique/") != NULL
926 && oldlength >= sizeof(dbdata_ratelimit_unique))
930 unsigned n, hash, hinc;
934 md5_end(&md5info, value, Ustrlen(value), md5sum);
935 hash = md5sum[0] << 0 | md5sum[1] << 8
936 | md5sum[2] << 16 | md5sum[3] << 24;
937 hinc = md5sum[4] << 0 | md5sum[5] << 8
938 | md5sum[6] << 16 | md5sum[7] << 24;
939 rate_unique = (dbdata_ratelimit_unique *)record;
941 for (n = 0; n < 8; n++, hash += hinc)
943 int bit = 1 << (hash % 8);
944 int byte = (hash / 8) % rate_unique->bloom_size;
945 if ((rate_unique->bloom[byte] & bit) == 0)
948 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
952 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
955 /* else fall through */
958 printf("unknown field number\n");
965 dbfn_write(dbm, name, record, oldlength);
972 printf("field number or d expected\n");
977 if (!verify) continue;
980 /* The "name" q causes an exit */
982 else if (Ustrcmp(name, "q") == 0) return 0;
984 /* Handle a read request, or verify after an update. */
986 spool_directory = argv[1];
987 dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE);
988 if (dbm == NULL) continue;
990 record = dbfn_read_with_length(dbm, name, &oldlength);
993 printf("record %s not found\n", name);
999 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
1003 retry = (dbdata_retry *)record;
1004 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
1005 printf("1 extra data: %d\n", retry->more_errno);
1006 printf("2 first failed: %s\n", print_time(retry->first_failed));
1007 printf("3 last try: %s\n", print_time(retry->last_try));
1008 printf("4 next try: %s\n", print_time(retry->next_try));
1009 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
1013 wait = (dbdata_wait *)record;
1015 printf("Sequence: %d\n", wait->sequence);
1016 if (wait->count > WAIT_NAME_MAX)
1018 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
1019 wait->count, WAIT_NAME_MAX);
1020 wait->count = WAIT_NAME_MAX;
1023 for (i = 1; i <= wait->count; i++)
1025 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1026 value[MESSAGE_ID_LENGTH] = 0;
1027 if (count_bad && value[0] == 0) break;
1028 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1029 Ustrspn(value, "0123456789"
1030 "abcdefghijklmnopqrstuvwxyz"
1031 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1034 printf("\n**** Data corrupted: bad character in message id ****\n");
1035 for (j = 0; j < MESSAGE_ID_LENGTH; j++)
1036 printf("%02x ", value[j]);
1040 printf("%s ", value);
1041 t += MESSAGE_ID_LENGTH;
1050 callout = (dbdata_callout_cache *)record;
1051 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1053 if (oldlength > sizeof(dbdata_callout_cache_address))
1055 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1056 callout->postmaster_result);
1057 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1058 callout->random_result);
1062 case type_ratelimit:
1063 ratelimit = (dbdata_ratelimit *)record;
1064 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1065 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1066 printf("2 sender rate: % .3f\n", ratelimit->rate);
1067 if (Ustrstr(name, "/unique/") != NULL
1068 && oldlength >= sizeof(dbdata_ratelimit_unique))
1070 rate_unique = (dbdata_ratelimit_unique *)record;
1071 printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1072 printf("4 test filter membership\n");
1073 printf("5 add element to filter\n");
1079 /* The database is closed after each request */
1088 #endif /* EXIM_FIXDB */
1093 /*************************************************
1094 * The exim_tidydb main program *
1095 *************************************************/
1098 /* Utility program to tidy the contents of an exim database file. There is one
1101 -t <time> expiry time for old records - default 30 days
1103 For backwards compatibility, an -f option is recognized and ignored. (It used
1104 to request a "full" tidy. This version always does the whole job.) */
1107 typedef struct key_item {
1108 struct key_item *next;
1113 int main(int argc, char **cargv)
1115 struct stat statbuf;
1116 int maxkeep = 30 * 24 * 60 * 60;
1117 int dbdata_type, i, oldest, path_len;
1118 key_item *keychain = NULL;
1122 EXIM_CURSOR *cursor;
1123 uschar **argv = USS cargv;
1127 /* Scan the options */
1129 for (i = 1; i < argc; i++)
1131 if (argv[i][0] != '-') break;
1132 if (Ustrcmp(argv[i], "-f") == 0) continue;
1133 if (Ustrcmp(argv[i], "-t") == 0)
1141 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1142 (void)sscanf(CS s, "%d%n", &value, &count);
1146 case 'w': value *= 7;
1147 case 'd': value *= 24;
1148 case 'h': value *= 60;
1149 case 'm': value *= 60;
1152 default: usage(US"tidydb", US" [-t <time>]");
1157 else usage(US"tidydb", US" [-t <time>]");
1160 /* Adjust argument values and process arguments */
1165 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1167 /* Compute the oldest keep time, verify what we are doing, and open the
1170 oldest = time(NULL) - maxkeep;
1171 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1173 spool_directory = argv[1];
1174 dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE);
1175 if (dbm == NULL) exit(1);
1177 /* Prepare for building file names */
1179 sprintf(CS buffer, "%s/input/", argv[1]);
1180 path_len = Ustrlen(buffer);
1183 /* It appears, by experiment, that it is a bad idea to make changes
1184 to the file while scanning it. Pity the man page doesn't warn you about that.
1185 Therefore, we scan and build a list of all the keys. Then we use that to
1186 read the records and possibly update them. */
1188 key = dbfn_scan(dbm, TRUE, &cursor);
1191 key_item *k = store_get(sizeof(key_item) + Ustrlen(key));
1194 Ustrcpy(k->key, key);
1195 key = dbfn_scan(dbm, FALSE, &cursor);
1198 /* Now scan the collected keys and operate on the records, resetting
1199 the store each time round. */
1201 reset_point = store_get(0);
1203 while (keychain != NULL)
1205 dbdata_generic *value;
1207 store_reset(reset_point);
1208 key = keychain->key;
1209 keychain = keychain->next;
1210 value = dbfn_read_with_length(dbm, key, NULL);
1212 /* A continuation record may have been deleted or renamed already, so
1213 non-existence is not serious. */
1215 if (value == NULL) continue;
1217 /* Delete if too old */
1219 if (value->time_stamp < oldest)
1221 printf("deleted %s (too old)\n", key);
1222 dbfn_delete(dbm, key);
1226 /* Do database-specific tidying for wait databases, and message-
1227 specific tidying for the retry database. */
1229 if (dbdata_type == type_wait)
1231 dbdata_wait *wait = (dbdata_wait *)value;
1232 BOOL update = FALSE;
1234 /* Leave corrupt records alone */
1236 if (wait->count > WAIT_NAME_MAX)
1238 printf("**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
1239 key, wait->count, wait->count, WAIT_NAME_MAX);
1243 /* Loop for renamed continuation records. For each message id,
1244 check to see if the message exists, and if not, remove its entry
1245 from the record. Because of the possibility of split input directories,
1246 we must look in both possible places for a -D file. */
1251 int length = wait->count * MESSAGE_ID_LENGTH;
1253 for (offset = length - MESSAGE_ID_LENGTH;
1254 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1256 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1257 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1259 if (Ustat(buffer, &statbuf) != 0)
1261 buffer[path_len] = wait->text[offset+5];
1262 buffer[path_len+1] = '/';
1263 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1264 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1266 if (Ustat(buffer, &statbuf) != 0)
1268 int left = length - offset - MESSAGE_ID_LENGTH;
1269 if (left > 0) Ustrncpy(wait->text + offset,
1270 wait->text + offset + MESSAGE_ID_LENGTH, left);
1272 length -= MESSAGE_ID_LENGTH;
1278 /* If record is empty and the main record, either delete it or rename
1279 the next continuation, repeating if that is also empty. */
1281 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1283 while (wait->count == 0 && wait->sequence > 0)
1286 dbdata_generic *newvalue;
1287 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1288 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1289 if (newvalue != NULL)
1292 wait = (dbdata_wait *)newvalue;
1293 dbfn_delete(dbm, newkey);
1294 printf("renamed %s\n", newkey);
1297 else wait->sequence--;
1300 /* If we have ended up with an empty main record, delete it
1301 and break the loop. Otherwise the new record will be scanned. */
1303 if (wait->count == 0 && wait->sequence == 0)
1305 dbfn_delete(dbm, key);
1306 printf("deleted %s (empty)\n", key);
1312 /* If not an empty main record, break the loop */
1317 /* Re-write the record if required */
1321 printf("updated %s\n", key);
1322 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1323 wait->count * MESSAGE_ID_LENGTH);
1327 /* If a retry record's key ends with a message-id, check that that message
1328 still exists; if not, remove this record. */
1330 else if (dbdata_type == type_retry)
1333 int len = Ustrlen(key);
1335 if (len < MESSAGE_ID_LENGTH + 1) continue;
1336 id = key + len - MESSAGE_ID_LENGTH - 1;
1337 if (*id++ != ':') continue;
1339 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1341 if (i == 6 || i == 13)
1342 { if (id[i] != '-') break; }
1344 { if (!isalnum(id[i])) break; }
1346 if (i < MESSAGE_ID_LENGTH) continue;
1348 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1349 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1351 if (Ustat(buffer, &statbuf) != 0)
1353 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1354 if (Ustat(buffer, &statbuf) != 0)
1356 dbfn_delete(dbm, key);
1357 printf("deleted %s (no message)\n", key);
1364 printf("Tidying complete\n");
1368 #endif /* EXIM_TIDYDB */
1370 /* End of exim_dbutil.c */