1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
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
23 tls: TLS session resumption cache
25 There are a number of common subroutines, followed by three main programs,
26 whose inclusion is controlled by -D on the compilation command. */
32 /* Identifiers for the different database types. */
37 #define type_callout 4
38 #define type_ratelimit 5
42 /* This is used by our cut-down dbfn_open(). */
44 uschar *spool_directory;
48 /*************************************************
49 * Berkeley DB error callback *
50 *************************************************/
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. */
56 #if defined(USE_DB) && defined(DB_VERSION_STRING)
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)
63 dbfn_bdb_error_callback(const char *pfx, char *msg)
67 printf("Berkeley DB error: %s\n", msg);
73 /*************************************************
75 *************************************************/
77 SIGNAL_BOOL sigalrm_seen;
80 sigalrm_handler(int sig)
82 sig = sig; /* Keep picky compilers happy */
88 /*************************************************
89 * Output usage message and exit *
90 *************************************************/
93 usage(uschar *name, uschar *options)
95 printf("Usage: exim_%s%s <spool-directory> <database-name>\n", name, options);
96 printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit | tls\n");
102 /*************************************************
103 * Sort out the command arguments *
104 *************************************************/
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. */
110 check_args(int argc, uschar **argv, uschar *name, uschar *options)
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 if (Ustrcmp(argv[2], "tls") == 0) return type_tls;
121 usage(name, options);
122 return -1; /* Never obeyed */
127 /*************************************************
128 * Handle attempts to write the log *
129 *************************************************/
131 /* The message gets written to stderr when log_write() is called from a
132 utility. The message always gets '\n' added on the end of it. These calls come
133 from modules such as store.c when things go drastically wrong (e.g. malloc()
134 failing). In normal use they won't get obeyed.
137 selector not relevant when running a utility
138 flags not relevant when running a utility
139 format a printf() format
140 ... arguments for format
146 log_write(unsigned int selector, int flags, const char *format, ...)
149 va_start(ap, format);
150 vfprintf(stderr, format, ap);
151 fprintf(stderr, "\n");
153 selector = selector; /* Keep picky compilers happy */
159 /*************************************************
160 * Format a time value for printing *
161 *************************************************/
163 static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss ")];
168 struct tm *tmstr = localtime(&t);
169 Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
175 /*************************************************
176 * Format a cache value for printing *
177 *************************************************/
180 print_cache(int value)
182 return (value == ccache_accept)? US"accept" :
183 (value == ccache_reject)? US"reject" :
189 /*************************************************
191 *************************************************/
198 time_t now = time(NULL);
199 struct tm *tm = localtime(&now);
204 for (uschar * t = s + Ustrlen(s) - 1; t >= s; t--)
206 if (*t == ':') continue;
207 if (!isdigit((uschar)*t)) return -1;
212 if (!isdigit((uschar)*t)) return -1;
213 value = value + (*t - '0')*10;
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;
229 #endif /* EXIM_FIXDB */
233 /*************************************************
234 * Open and lock a database file *
235 *************************************************/
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
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.
248 Returns: NULL if the open failed, or the locking failed.
249 On success, dbblock is returned. This contains the dbm pointer and
250 the fd of the locked lock file.
254 dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof, BOOL panic)
257 struct flock lock_data;
258 BOOL read_only = flags == O_RDONLY;
259 uschar * dirname, * filename;
261 /* The first thing to do is to open a separate file on which to lock. This
262 ensures that Exim has exclusive use of the database before it even tries to
263 open it. If there is a database, there should be a lock file in existence. */
265 #ifdef COMPILE_UTILITY
266 if ( asprintf(CSS &dirname, "%s/db", spool_directory) < 0
267 || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
270 dirname = string_sprintf("%s/db", spool_directory);
271 filename = string_sprintf("%s/%s.lockfile", dirname, name);
274 dbblock->lockfd = Uopen(filename, flags, 0);
275 if (dbblock->lockfd < 0)
277 printf("** Failed to open database lock file %s: %s\n", filename,
282 /* Now we must get a lock on the opened lock file; do this with a blocking
283 lock that times out. */
285 lock_data.l_type = read_only ? F_RDLCK : F_WRLCK;
286 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
288 sigalrm_seen = FALSE;
289 os_non_restarting_signal(SIGALRM, sigalrm_handler);
290 ALARM(EXIMDB_LOCK_TIMEOUT);
291 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
294 if (sigalrm_seen) errno = ETIMEDOUT;
297 printf("** Failed to get %s lock for %s: %s",
298 flags & O_WRONLY ? "write" : "read",
300 errno == ETIMEDOUT ? "timed out" : strerror(errno));
301 (void)close(dbblock->lockfd);
305 /* At this point we have an opened and locked separate lock file, that is,
306 exclusive access to the database, so we can go ahead and open it. */
308 #ifdef COMPILE_UTILITY
309 if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
311 filename = string_sprintf("%s/%s", dirname, name);
313 EXIM_DBOPEN(filename, dirname, flags, 0, &(dbblock->dbptr));
317 printf("** Failed to open DBM file %s for %s:\n %s%s\n", filename,
318 read_only? "reading" : "writing", strerror(errno),
320 " (or Berkeley DB error while opening)"
325 (void)close(dbblock->lockfd);
335 /*************************************************
336 * Unlock and close a database file *
337 *************************************************/
339 /* Closing a file automatically unlocks it, so after closing the database, just
342 Argument: a pointer to an open database block
347 dbfn_close(open_db *dbblock)
349 EXIM_DBCLOSE(dbblock->dbptr);
350 (void)close(dbblock->lockfd);
356 /*************************************************
357 * Read from database file *
358 *************************************************/
360 /* Passing back the pointer unchanged is useless, because there is no guarantee
361 of alignment. Since all the records used by Exim need to be properly aligned to
362 pick out the timestamps, etc., do the copying centrally here.
365 dbblock a pointer to an open database block
366 key the key of the record to be read
367 length where to put the length (or NULL if length not wanted)
369 Returns: a pointer to the retrieved record, or
370 NULL if the record is not found
374 dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
377 EXIM_DATUM key_datum, result_datum;
378 int klen = Ustrlen(key) + 1;
379 uschar * key_copy = store_get(klen);
381 memcpy(key_copy, key, klen);
383 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
384 EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */
385 EXIM_DATUM_DATA(key_datum) = CS key_copy;
386 EXIM_DATUM_SIZE(key_datum) = klen;
388 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
390 yield = store_get(EXIM_DATUM_SIZE(result_datum));
391 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
392 if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
394 EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */
400 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
402 /*************************************************
403 * Write to database file *
404 *************************************************/
408 dbblock a pointer to an open database block
409 key the key of the record to be written
410 ptr a pointer to the record to be written
411 length the length of the record to be written
413 Returns: the yield of the underlying dbm or db "write" function. If this
414 is dbm, the value is zero for OK.
418 dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
420 EXIM_DATUM key_datum, value_datum;
421 dbdata_generic *gptr = (dbdata_generic *)ptr;
422 int klen = Ustrlen(key) + 1;
423 uschar * key_copy = store_get(klen);
425 memcpy(key_copy, key, klen);
426 gptr->time_stamp = time(NULL);
428 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
429 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
430 EXIM_DATUM_DATA(key_datum) = CS key_copy;
431 EXIM_DATUM_SIZE(key_datum) = klen;
432 EXIM_DATUM_DATA(value_datum) = CS ptr;
433 EXIM_DATUM_SIZE(value_datum) = length;
434 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
439 /*************************************************
440 * Delete record from database file *
441 *************************************************/
445 dbblock a pointer to an open database block
446 key the key of the record to be deleted
448 Returns: the yield of the underlying dbm or db "delete" function.
452 dbfn_delete(open_db *dbblock, const uschar *key)
454 int klen = Ustrlen(key) + 1;
455 uschar * key_copy = store_get(klen);
457 memcpy(key_copy, key, klen);
458 EXIM_DATUM key_datum;
459 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
460 EXIM_DATUM_DATA(key_datum) = CS key_copy;
461 EXIM_DATUM_SIZE(key_datum) = klen;
462 return EXIM_DBDEL(dbblock->dbptr, key_datum);
465 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
469 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
470 /*************************************************
471 * Scan the keys of a database file *
472 *************************************************/
476 dbblock a pointer to an open database block
477 start TRUE if starting a new scan
478 FALSE if continuing with the current scan
479 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
480 that use the notion of a cursor
482 Returns: the next record from the file, or
483 NULL if there are no more
487 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
489 EXIM_DATUM key_datum, value_datum;
491 value_datum = value_datum; /* dummy; not all db libraries use this */
493 /* Some dbm require an initialization */
495 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
497 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
498 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
500 yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
501 US EXIM_DATUM_DATA(key_datum) : NULL;
503 /* Some dbm require a termination */
505 if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
508 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
513 /*************************************************
514 * The exim_dumpdb main program *
515 *************************************************/
518 main(int argc, char **cargv)
525 uschar **argv = USS cargv;
526 uschar keybuffer[1024];
528 /* Check the arguments, and open the database */
530 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
531 spool_directory = argv[1];
532 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
535 /* Scan the file, formatting the information for each entry. Note
536 that data is returned in a malloc'ed block, in order that it be
537 correctly aligned. */
539 for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
541 key = dbfn_scan(dbm, FALSE, &cursor))
545 dbdata_callout_cache *callout;
546 dbdata_ratelimit *ratelimit;
547 dbdata_ratelimit_unique *rate_unique;
548 dbdata_tls_session *session;
552 uschar name[MESSAGE_ID_LENGTH + 1];
555 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
556 which might change. */
558 if (Ustrlen(key) > sizeof(keybuffer) - 1)
560 printf("**** Overlong key encountered: %s\n", key);
563 Ustrcpy(keybuffer, key);
565 if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
566 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
567 "was not found in the file - something is wrong!\n",
571 /* Note: don't use print_time more than once in one statement, since
572 it uses a single buffer. */
577 retry = (dbdata_retry *)value;
578 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
579 retry->more_errno, retry->text,
580 print_time(retry->first_failed));
581 printf("%s ", print_time(retry->last_try));
582 printf("%s %s\n", print_time(retry->next_try),
583 (retry->expired)? "*" : "");
587 wait = (dbdata_wait *)value;
588 printf("%s ", keybuffer);
590 name[MESSAGE_ID_LENGTH] = 0;
592 if (wait->count > WAIT_NAME_MAX)
595 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
596 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
597 wait->count = WAIT_NAME_MAX;
598 yield = count_bad = 1;
600 for (int i = 1; i <= wait->count; i++)
602 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
603 if (count_bad && name[0] == 0) break;
604 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
605 Ustrspn(name, "0123456789"
606 "abcdefghijklmnopqrstuvwxyz"
607 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
610 "**** Data for %s corrupted: bad character in message id\n",
612 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
613 fprintf(stderr, "%02x ", name[j]);
614 fprintf(stderr, "\n");
619 t += MESSAGE_ID_LENGTH;
625 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
630 callout = (dbdata_callout_cache *)value;
632 /* New-style address record */
634 if (length == sizeof(dbdata_callout_cache_address))
636 printf("%s %s callout=%s\n",
637 print_time(((dbdata_generic *)value)->time_stamp),
639 print_cache(callout->result));
642 /* New-style domain record */
644 else if (length == sizeof(dbdata_callout_cache))
646 printf("%s %s callout=%s postmaster=%s",
647 print_time(((dbdata_generic *)value)->time_stamp),
649 print_cache(callout->result),
650 print_cache(callout->postmaster_result));
651 if (callout->postmaster_result != ccache_unknown)
652 printf(" (%s)", print_time(callout->postmaster_stamp));
653 printf(" random=%s", print_cache(callout->random_result));
654 if (callout->random_result != ccache_unknown)
655 printf(" (%s)", print_time(callout->random_stamp));
662 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
664 ratelimit = (dbdata_ratelimit *)value;
665 rate_unique = (dbdata_ratelimit_unique *)value;
666 printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
667 print_time(ratelimit->time_stamp),
668 ratelimit->time_usec, ratelimit->rate,
669 print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
674 ratelimit = (dbdata_ratelimit *)value;
675 printf("%s.%06d rate: %10.3f key: %s\n",
676 print_time(ratelimit->time_stamp),
677 ratelimit->time_usec, ratelimit->rate,
683 session = (dbdata_tls_session *)value;
684 printf(" %s %.*s\n", keybuffer, length, session->session);
695 #endif /* EXIM_DUMPDB */
701 /*************************************************
702 * The exim_fixdb main program *
703 *************************************************/
705 /* In order not to hold the database lock any longer than is necessary, each
706 operation on the database uses a separate open/close call. This is expensive,
707 but then using this utility is not expected to be very common. Its main use is
708 to provide a way of patching up hints databases in order to run tests.
713 This causes the data from the given record to be displayed, or "not found"
714 to be output. Note that in the retry database, destination names are
715 preceded by R: or T: for router or transport retry info.
718 This causes the given record to be deleted or "not found" to be output.
720 (3) <record name> <field number> <value>
721 This sets the given value into the given field, identified by a number
722 which is output by the display command. Not all types of record can
726 This exits from exim_fixdb.
728 If the record name is omitted from (2) or (3), the previously used record name
732 int main(int argc, char **cargv)
735 uschar **argv = USS cargv;
738 void *reset_point = store_get(0);
740 name[0] = 0; /* No name set */
742 /* Sort out the database type, verify what we are working on and then process
745 dbdata_type = check_args(argc, argv, US"fixdb", US"");
746 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
755 dbdata_callout_cache *callout;
756 dbdata_ratelimit *ratelimit;
757 dbdata_ratelimit_unique *rate_unique;
758 dbdata_tls_session *session;
761 uschar field[256], value[256];
763 store_reset(reset_point);
766 if (Ufgets(buffer, 256, stdin) == NULL) break;
768 buffer[Ustrlen(buffer)-1] = 0;
769 field[0] = value[0] = 0;
771 /* If the buffer contains just one digit, or just consists of "d", use the
772 previous name for an update. */
774 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
775 || Ustrcmp(buffer, "d") == 0)
779 printf("No previous record name is set\n");
782 (void)sscanf(CS buffer, "%s %s", field, value);
787 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
790 /* Handle an update request */
795 spool_directory = argv[1];
797 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
800 if (Ustrcmp(field, "d") == 0)
802 if (value[0] != 0) printf("unexpected value after \"d\"\n");
803 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
804 "not found" : "deleted");
809 else if (isdigit((uschar)field[0]))
811 int fieldno = Uatoi(field);
814 printf("value missing\n");
820 record = dbfn_read_with_length(dbm, name, &oldlength);
821 if (record == NULL) printf("not found\n"); else
824 /*int length = 0; Stops compiler warning */
829 retry = (dbdata_retry *)record;
830 /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
834 case 0: retry->basic_errno = Uatoi(value);
836 case 1: retry->more_errno = Uatoi(value);
838 case 2: if ((tt = read_time(value)) > 0) retry->first_failed = tt;
839 else printf("bad time value\n");
841 case 3: if ((tt = read_time(value)) > 0) retry->last_try = tt;
842 else printf("bad time value\n");
844 case 4: if ((tt = read_time(value)) > 0) retry->next_try = tt;
845 else printf("bad time value\n");
847 case 5: if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
848 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
849 else printf("\"yes\" or \"no\" expected=n");
851 default: printf("unknown field number\n");
858 printf("Can't change contents of wait database record\n");
862 printf("Can't change contents of misc database record\n");
866 callout = (dbdata_callout_cache *)record;
867 /* length = sizeof(dbdata_callout_cache); */
870 case 0: callout->result = Uatoi(value);
872 case 1: callout->postmaster_result = Uatoi(value);
874 case 2: callout->random_result = Uatoi(value);
876 default: printf("unknown field number\n");
883 ratelimit = (dbdata_ratelimit *)record;
886 case 0: if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
887 else printf("bad time value\n");
889 case 1: ratelimit->time_usec = Uatoi(value);
891 case 2: ratelimit->rate = Ustrtod(value, NULL);
893 case 3: if (Ustrstr(name, "/unique/") != NULL
894 && oldlength >= sizeof(dbdata_ratelimit_unique))
896 rate_unique = (dbdata_ratelimit_unique *)record;
897 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
898 else printf("bad time value\n");
901 /* else fall through */
903 case 5: if (Ustrstr(name, "/unique/") != NULL
904 && oldlength >= sizeof(dbdata_ratelimit_unique))
912 md5_end(&md5info, value, Ustrlen(value), md5sum);
913 hash = md5sum[0] << 0 | md5sum[1] << 8
914 | md5sum[2] << 16 | md5sum[3] << 24;
915 hinc = md5sum[4] << 0 | md5sum[5] << 8
916 | md5sum[6] << 16 | md5sum[7] << 24;
917 rate_unique = (dbdata_ratelimit_unique *)record;
919 for (unsigned n = 0; n < 8; n++, hash += hinc)
921 int bit = 1 << (hash % 8);
922 int byte = (hash / 8) % rate_unique->bloom_size;
923 if ((rate_unique->bloom[byte] & bit) == 0)
926 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
930 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
933 /* else fall through */
934 default: printf("unknown field number\n");
941 printf("Can't change contents of tls database record\n");
945 dbfn_write(dbm, name, record, oldlength);
952 printf("field number or d expected\n");
957 if (!verify) continue;
960 /* The "name" q causes an exit */
962 else if (Ustrcmp(name, "q") == 0) return 0;
964 /* Handle a read request, or verify after an update. */
966 spool_directory = argv[1];
967 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
970 if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
972 printf("record %s not found\n", name);
978 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
982 retry = (dbdata_retry *)record;
983 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
984 printf("1 extra data: %d\n", retry->more_errno);
985 printf("2 first failed: %s\n", print_time(retry->first_failed));
986 printf("3 last try: %s\n", print_time(retry->last_try));
987 printf("4 next try: %s\n", print_time(retry->next_try));
988 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
992 wait = (dbdata_wait *)record;
994 printf("Sequence: %d\n", wait->sequence);
995 if (wait->count > WAIT_NAME_MAX)
997 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
998 wait->count, WAIT_NAME_MAX);
999 wait->count = WAIT_NAME_MAX;
1002 for (int i = 1; i <= wait->count; i++)
1004 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1005 value[MESSAGE_ID_LENGTH] = 0;
1006 if (count_bad && value[0] == 0) break;
1007 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1008 Ustrspn(value, "0123456789"
1009 "abcdefghijklmnopqrstuvwxyz"
1010 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1012 printf("\n**** Data corrupted: bad character in message id ****\n");
1013 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
1014 printf("%02x ", value[j]);
1018 printf("%s ", value);
1019 t += MESSAGE_ID_LENGTH;
1028 callout = (dbdata_callout_cache *)record;
1029 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1031 if (oldlength > sizeof(dbdata_callout_cache_address))
1033 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1034 callout->postmaster_result);
1035 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1036 callout->random_result);
1040 case type_ratelimit:
1041 ratelimit = (dbdata_ratelimit *)record;
1042 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1043 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1044 printf("2 sender rate: % .3f\n", ratelimit->rate);
1045 if (Ustrstr(name, "/unique/") != NULL
1046 && oldlength >= sizeof(dbdata_ratelimit_unique))
1048 rate_unique = (dbdata_ratelimit_unique *)record;
1049 printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1050 printf("4 test filter membership\n");
1051 printf("5 add element to filter\n");
1056 session = (dbdata_tls_session *)value;
1057 printf("0 time stamp: %s\n", print_time(session->time_stamp));
1058 printf("1 session: .%s\n", session->session);
1063 /* The database is closed after each request */
1072 #endif /* EXIM_FIXDB */
1077 /*************************************************
1078 * The exim_tidydb main program *
1079 *************************************************/
1082 /* Utility program to tidy the contents of an exim database file. There is one
1085 -t <time> expiry time for old records - default 30 days
1087 For backwards compatibility, an -f option is recognized and ignored. (It used
1088 to request a "full" tidy. This version always does the whole job.) */
1091 typedef struct key_item {
1092 struct key_item *next;
1097 int main(int argc, char **cargv)
1099 struct stat statbuf;
1100 int maxkeep = 30 * 24 * 60 * 60;
1101 int dbdata_type, i, oldest, path_len;
1102 key_item *keychain = NULL;
1106 EXIM_CURSOR *cursor;
1107 uschar **argv = USS cargv;
1111 /* Scan the options */
1113 for (i = 1; i < argc; i++)
1115 if (argv[i][0] != '-') break;
1116 if (Ustrcmp(argv[i], "-f") == 0) continue;
1117 if (Ustrcmp(argv[i], "-t") == 0)
1125 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1126 (void)sscanf(CS s, "%d%n", &value, &count);
1130 case 'w': value *= 7;
1131 case 'd': value *= 24;
1132 case 'h': value *= 60;
1133 case 'm': value *= 60;
1136 default: usage(US"tidydb", US" [-t <time>]");
1141 else usage(US"tidydb", US" [-t <time>]");
1144 /* Adjust argument values and process arguments */
1149 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1151 /* Compute the oldest keep time, verify what we are doing, and open the
1154 oldest = time(NULL) - maxkeep;
1155 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1157 spool_directory = argv[1];
1158 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
1161 /* Prepare for building file names */
1163 sprintf(CS buffer, "%s/input/", argv[1]);
1164 path_len = Ustrlen(buffer);
1167 /* It appears, by experiment, that it is a bad idea to make changes
1168 to the file while scanning it. Pity the man page doesn't warn you about that.
1169 Therefore, we scan and build a list of all the keys. Then we use that to
1170 read the records and possibly update them. */
1172 for (key = dbfn_scan(dbm, TRUE, &cursor);
1174 key = dbfn_scan(dbm, FALSE, &cursor))
1176 key_item *k = store_get(sizeof(key_item) + Ustrlen(key));
1179 Ustrcpy(k->key, key);
1182 /* Now scan the collected keys and operate on the records, resetting
1183 the store each time round. */
1185 reset_point = store_get(0);
1189 dbdata_generic *value;
1191 store_reset(reset_point);
1192 key = keychain->key;
1193 keychain = keychain->next;
1194 value = dbfn_read_with_length(dbm, key, NULL);
1196 /* A continuation record may have been deleted or renamed already, so
1197 non-existence is not serious. */
1199 if (value == NULL) continue;
1201 /* Delete if too old */
1203 if (value->time_stamp < oldest)
1205 printf("deleted %s (too old)\n", key);
1206 dbfn_delete(dbm, key);
1210 /* Do database-specific tidying for wait databases, and message-
1211 specific tidying for the retry database. */
1213 if (dbdata_type == type_wait)
1215 dbdata_wait *wait = (dbdata_wait *)value;
1216 BOOL update = FALSE;
1218 /* Leave corrupt records alone */
1220 if (wait->count > WAIT_NAME_MAX)
1222 printf("**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
1223 key, wait->count, wait->count, WAIT_NAME_MAX);
1227 /* Loop for renamed continuation records. For each message id,
1228 check to see if the message exists, and if not, remove its entry
1229 from the record. Because of the possibility of split input directories,
1230 we must look in both possible places for a -D file. */
1234 int length = wait->count * MESSAGE_ID_LENGTH;
1236 for (int offset = length - MESSAGE_ID_LENGTH;
1237 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1239 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1240 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1242 if (Ustat(buffer, &statbuf) != 0)
1244 buffer[path_len] = wait->text[offset+5];
1245 buffer[path_len+1] = '/';
1246 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1247 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1249 if (Ustat(buffer, &statbuf) != 0)
1251 int left = length - offset - MESSAGE_ID_LENGTH;
1252 if (left > 0) Ustrncpy(wait->text + offset,
1253 wait->text + offset + MESSAGE_ID_LENGTH, left);
1255 length -= MESSAGE_ID_LENGTH;
1261 /* If record is empty and the main record, either delete it or rename
1262 the next continuation, repeating if that is also empty. */
1264 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1266 while (wait->count == 0 && wait->sequence > 0)
1269 dbdata_generic *newvalue;
1270 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1271 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1272 if (newvalue != NULL)
1275 wait = (dbdata_wait *)newvalue;
1276 dbfn_delete(dbm, newkey);
1277 printf("renamed %s\n", newkey);
1280 else wait->sequence--;
1283 /* If we have ended up with an empty main record, delete it
1284 and break the loop. Otherwise the new record will be scanned. */
1286 if (wait->count == 0 && wait->sequence == 0)
1288 dbfn_delete(dbm, key);
1289 printf("deleted %s (empty)\n", key);
1295 /* If not an empty main record, break the loop */
1300 /* Re-write the record if required */
1304 printf("updated %s\n", key);
1305 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1306 wait->count * MESSAGE_ID_LENGTH);
1310 /* If a retry record's key ends with a message-id, check that that message
1311 still exists; if not, remove this record. */
1313 else if (dbdata_type == type_retry)
1316 int len = Ustrlen(key);
1318 if (len < MESSAGE_ID_LENGTH + 1) continue;
1319 id = key + len - MESSAGE_ID_LENGTH - 1;
1320 if (*id++ != ':') continue;
1322 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1323 if (i == 6 || i == 13)
1324 { if (id[i] != '-') break; }
1326 { if (!isalnum(id[i])) break; }
1327 if (i < MESSAGE_ID_LENGTH) continue;
1329 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1330 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1332 if (Ustat(buffer, &statbuf) != 0)
1334 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1335 if (Ustat(buffer, &statbuf) != 0)
1337 dbfn_delete(dbm, key);
1338 printf("deleted %s (no message)\n", key);
1345 printf("Tidying complete\n");
1349 #endif /* EXIM_TIDYDB */
1351 /* End of exim_dbutil.c */