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, is_tainted(key));
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 /* Assume for now that anything stored could have been tainted. Properly
391 we should store the taint status along with the data. */
393 yield = store_get(EXIM_DATUM_SIZE(result_datum), TRUE);
394 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
395 if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
397 EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */
403 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
405 /*************************************************
406 * Write to database file *
407 *************************************************/
411 dbblock a pointer to an open database block
412 key the key of the record to be written
413 ptr a pointer to the record to be written
414 length the length of the record to be written
416 Returns: the yield of the underlying dbm or db "write" function. If this
417 is dbm, the value is zero for OK.
421 dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
423 EXIM_DATUM key_datum, value_datum;
424 dbdata_generic *gptr = (dbdata_generic *)ptr;
425 int klen = Ustrlen(key) + 1;
426 uschar * key_copy = store_get(klen, is_tainted(key));
428 memcpy(key_copy, key, klen);
429 gptr->time_stamp = time(NULL);
431 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
432 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
433 EXIM_DATUM_DATA(key_datum) = CS key_copy;
434 EXIM_DATUM_SIZE(key_datum) = klen;
435 EXIM_DATUM_DATA(value_datum) = CS ptr;
436 EXIM_DATUM_SIZE(value_datum) = length;
437 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
442 /*************************************************
443 * Delete record from database file *
444 *************************************************/
448 dbblock a pointer to an open database block
449 key the key of the record to be deleted
451 Returns: the yield of the underlying dbm or db "delete" function.
455 dbfn_delete(open_db *dbblock, const uschar *key)
457 int klen = Ustrlen(key) + 1;
458 uschar * key_copy = store_get(klen, is_tainted(key));
460 memcpy(key_copy, key, klen);
461 EXIM_DATUM key_datum;
462 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
463 EXIM_DATUM_DATA(key_datum) = CS key_copy;
464 EXIM_DATUM_SIZE(key_datum) = klen;
465 return EXIM_DBDEL(dbblock->dbptr, key_datum);
468 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
472 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
473 /*************************************************
474 * Scan the keys of a database file *
475 *************************************************/
479 dbblock a pointer to an open database block
480 start TRUE if starting a new scan
481 FALSE if continuing with the current scan
482 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
483 that use the notion of a cursor
485 Returns: the next record from the file, or
486 NULL if there are no more
490 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
492 EXIM_DATUM key_datum, value_datum;
494 value_datum = value_datum; /* dummy; not all db libraries use this */
496 /* Some dbm require an initialization */
498 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
500 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
501 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
503 yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
504 US EXIM_DATUM_DATA(key_datum) : NULL;
506 /* Some dbm require a termination */
508 if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
511 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
516 /*************************************************
517 * The exim_dumpdb main program *
518 *************************************************/
521 main(int argc, char **cargv)
528 uschar **argv = USS cargv;
529 uschar keybuffer[1024];
531 /* Check the arguments, and open the database */
533 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
534 spool_directory = argv[1];
535 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
538 /* Scan the file, formatting the information for each entry. Note
539 that data is returned in a malloc'ed block, in order that it be
540 correctly aligned. */
542 for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
544 key = dbfn_scan(dbm, FALSE, &cursor))
548 dbdata_callout_cache *callout;
549 dbdata_ratelimit *ratelimit;
550 dbdata_ratelimit_unique *rate_unique;
551 dbdata_tls_session *session;
555 uschar name[MESSAGE_ID_LENGTH + 1];
557 rmark reset_point = store_mark();
559 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
560 which might change. */
562 if (Ustrlen(key) > sizeof(keybuffer) - 1)
564 printf("**** Overlong key encountered: %s\n", key);
567 Ustrcpy(keybuffer, key);
569 if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
570 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
571 "was not found in the file - something is wrong!\n",
575 /* Note: don't use print_time more than once in one statement, since
576 it uses a single buffer. */
581 retry = (dbdata_retry *)value;
582 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
583 retry->more_errno, retry->text,
584 print_time(retry->first_failed));
585 printf("%s ", print_time(retry->last_try));
586 printf("%s %s\n", print_time(retry->next_try),
587 (retry->expired)? "*" : "");
591 wait = (dbdata_wait *)value;
592 printf("%s ", keybuffer);
594 name[MESSAGE_ID_LENGTH] = 0;
596 if (wait->count > WAIT_NAME_MAX)
599 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
600 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
601 wait->count = WAIT_NAME_MAX;
602 yield = count_bad = 1;
604 for (int i = 1; i <= wait->count; i++)
606 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
607 if (count_bad && name[0] == 0) break;
608 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
609 Ustrspn(name, "0123456789"
610 "abcdefghijklmnopqrstuvwxyz"
611 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
614 "**** Data for %s corrupted: bad character in message id\n",
616 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
617 fprintf(stderr, "%02x ", name[j]);
618 fprintf(stderr, "\n");
623 t += MESSAGE_ID_LENGTH;
629 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
634 callout = (dbdata_callout_cache *)value;
636 /* New-style address record */
638 if (length == sizeof(dbdata_callout_cache_address))
640 printf("%s %s callout=%s\n",
641 print_time(((dbdata_generic *)value)->time_stamp),
643 print_cache(callout->result));
646 /* New-style domain record */
648 else if (length == sizeof(dbdata_callout_cache))
650 printf("%s %s callout=%s postmaster=%s",
651 print_time(((dbdata_generic *)value)->time_stamp),
653 print_cache(callout->result),
654 print_cache(callout->postmaster_result));
655 if (callout->postmaster_result != ccache_unknown)
656 printf(" (%s)", print_time(callout->postmaster_stamp));
657 printf(" random=%s", print_cache(callout->random_result));
658 if (callout->random_result != ccache_unknown)
659 printf(" (%s)", print_time(callout->random_stamp));
666 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
668 ratelimit = (dbdata_ratelimit *)value;
669 rate_unique = (dbdata_ratelimit_unique *)value;
670 printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
671 print_time(ratelimit->time_stamp),
672 ratelimit->time_usec, ratelimit->rate,
673 print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
678 ratelimit = (dbdata_ratelimit *)value;
679 printf("%s.%06d rate: %10.3f key: %s\n",
680 print_time(ratelimit->time_stamp),
681 ratelimit->time_usec, ratelimit->rate,
687 session = (dbdata_tls_session *)value;
688 printf(" %s %.*s\n", keybuffer, length, session->session);
692 store_reset(reset_point);
699 #endif /* EXIM_DUMPDB */
705 /*************************************************
706 * The exim_fixdb main program *
707 *************************************************/
709 /* In order not to hold the database lock any longer than is necessary, each
710 operation on the database uses a separate open/close call. This is expensive,
711 but then using this utility is not expected to be very common. Its main use is
712 to provide a way of patching up hints databases in order to run tests.
717 This causes the data from the given record to be displayed, or "not found"
718 to be output. Note that in the retry database, destination names are
719 preceded by R: or T: for router or transport retry info.
722 This causes the given record to be deleted or "not found" to be output.
724 (3) <record name> <field number> <value>
725 This sets the given value into the given field, identified by a number
726 which is output by the display command. Not all types of record can
730 This exits from exim_fixdb.
732 If the record name is omitted from (2) or (3), the previously used record name
736 int main(int argc, char **cargv)
739 uschar **argv = USS cargv;
744 name[0] = 0; /* No name set */
746 /* Sort out the database type, verify what we are working on and then process
749 dbdata_type = check_args(argc, argv, US"fixdb", US"");
750 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
752 for(; (reset_point = store_mark()); store_reset(reset_point))
759 dbdata_callout_cache *callout;
760 dbdata_ratelimit *ratelimit;
761 dbdata_ratelimit_unique *rate_unique;
762 dbdata_tls_session *session;
765 uschar field[256], value[256];
768 if (Ufgets(buffer, 256, stdin) == NULL) break;
770 buffer[Ustrlen(buffer)-1] = 0;
771 field[0] = value[0] = 0;
773 /* If the buffer contains just one digit, or just consists of "d", use the
774 previous name for an update. */
776 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
777 || Ustrcmp(buffer, "d") == 0)
781 printf("No previous record name is set\n");
784 (void)sscanf(CS buffer, "%s %s", field, value);
789 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
792 /* Handle an update request */
797 spool_directory = argv[1];
799 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
802 if (Ustrcmp(field, "d") == 0)
804 if (value[0] != 0) printf("unexpected value after \"d\"\n");
805 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
806 "not found" : "deleted");
811 else if (isdigit((uschar)field[0]))
813 int fieldno = Uatoi(field);
816 printf("value missing\n");
822 record = dbfn_read_with_length(dbm, name, &oldlength);
823 if (record == NULL) printf("not found\n"); else
826 /*int length = 0; Stops compiler warning */
831 retry = (dbdata_retry *)record;
832 /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
836 case 0: retry->basic_errno = Uatoi(value);
838 case 1: retry->more_errno = Uatoi(value);
840 case 2: if ((tt = read_time(value)) > 0) retry->first_failed = tt;
841 else printf("bad time value\n");
843 case 3: if ((tt = read_time(value)) > 0) retry->last_try = tt;
844 else printf("bad time value\n");
846 case 4: if ((tt = read_time(value)) > 0) retry->next_try = tt;
847 else printf("bad time value\n");
849 case 5: if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
850 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
851 else printf("\"yes\" or \"no\" expected=n");
853 default: printf("unknown field number\n");
860 printf("Can't change contents of wait database record\n");
864 printf("Can't change contents of misc database record\n");
868 callout = (dbdata_callout_cache *)record;
869 /* length = sizeof(dbdata_callout_cache); */
872 case 0: callout->result = Uatoi(value);
874 case 1: callout->postmaster_result = Uatoi(value);
876 case 2: callout->random_result = Uatoi(value);
878 default: printf("unknown field number\n");
885 ratelimit = (dbdata_ratelimit *)record;
888 case 0: if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
889 else printf("bad time value\n");
891 case 1: ratelimit->time_usec = Uatoi(value);
893 case 2: ratelimit->rate = Ustrtod(value, NULL);
895 case 3: if (Ustrstr(name, "/unique/") != NULL
896 && oldlength >= sizeof(dbdata_ratelimit_unique))
898 rate_unique = (dbdata_ratelimit_unique *)record;
899 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
900 else printf("bad time value\n");
903 /* else fall through */
905 case 5: if (Ustrstr(name, "/unique/") != NULL
906 && oldlength >= sizeof(dbdata_ratelimit_unique))
914 md5_end(&md5info, value, Ustrlen(value), md5sum);
915 hash = md5sum[0] << 0 | md5sum[1] << 8
916 | md5sum[2] << 16 | md5sum[3] << 24;
917 hinc = md5sum[4] << 0 | md5sum[5] << 8
918 | md5sum[6] << 16 | md5sum[7] << 24;
919 rate_unique = (dbdata_ratelimit_unique *)record;
921 for (unsigned n = 0; n < 8; n++, hash += hinc)
923 int bit = 1 << (hash % 8);
924 int byte = (hash / 8) % rate_unique->bloom_size;
925 if ((rate_unique->bloom[byte] & bit) == 0)
928 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
932 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
935 /* else fall through */
936 default: printf("unknown field number\n");
943 printf("Can't change contents of tls database record\n");
947 dbfn_write(dbm, name, record, oldlength);
954 printf("field number or d expected\n");
959 if (!verify) continue;
962 /* The "name" q causes an exit */
964 else if (Ustrcmp(name, "q") == 0) return 0;
966 /* Handle a read request, or verify after an update. */
968 spool_directory = argv[1];
969 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
972 if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
974 printf("record %s not found\n", name);
980 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
984 retry = (dbdata_retry *)record;
985 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
986 printf("1 extra data: %d\n", retry->more_errno);
987 printf("2 first failed: %s\n", print_time(retry->first_failed));
988 printf("3 last try: %s\n", print_time(retry->last_try));
989 printf("4 next try: %s\n", print_time(retry->next_try));
990 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
994 wait = (dbdata_wait *)record;
996 printf("Sequence: %d\n", wait->sequence);
997 if (wait->count > WAIT_NAME_MAX)
999 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
1000 wait->count, WAIT_NAME_MAX);
1001 wait->count = WAIT_NAME_MAX;
1004 for (int i = 1; i <= wait->count; i++)
1006 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1007 value[MESSAGE_ID_LENGTH] = 0;
1008 if (count_bad && value[0] == 0) break;
1009 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1010 Ustrspn(value, "0123456789"
1011 "abcdefghijklmnopqrstuvwxyz"
1012 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1014 printf("\n**** Data corrupted: bad character in message id ****\n");
1015 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
1016 printf("%02x ", value[j]);
1020 printf("%s ", value);
1021 t += MESSAGE_ID_LENGTH;
1030 callout = (dbdata_callout_cache *)record;
1031 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1033 if (oldlength > sizeof(dbdata_callout_cache_address))
1035 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1036 callout->postmaster_result);
1037 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1038 callout->random_result);
1042 case type_ratelimit:
1043 ratelimit = (dbdata_ratelimit *)record;
1044 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1045 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1046 printf("2 sender rate: % .3f\n", ratelimit->rate);
1047 if (Ustrstr(name, "/unique/") != NULL
1048 && oldlength >= sizeof(dbdata_ratelimit_unique))
1050 rate_unique = (dbdata_ratelimit_unique *)record;
1051 printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1052 printf("4 test filter membership\n");
1053 printf("5 add element to filter\n");
1058 session = (dbdata_tls_session *)value;
1059 printf("0 time stamp: %s\n", print_time(session->time_stamp));
1060 printf("1 session: .%s\n", session->session);
1065 /* The database is closed after each request */
1074 #endif /* EXIM_FIXDB */
1079 /*************************************************
1080 * The exim_tidydb main program *
1081 *************************************************/
1084 /* Utility program to tidy the contents of an exim database file. There is one
1087 -t <time> expiry time for old records - default 30 days
1089 For backwards compatibility, an -f option is recognized and ignored. (It used
1090 to request a "full" tidy. This version always does the whole job.) */
1093 typedef struct key_item {
1094 struct key_item *next;
1099 int main(int argc, char **cargv)
1101 struct stat statbuf;
1102 int maxkeep = 30 * 24 * 60 * 60;
1103 int dbdata_type, i, oldest, path_len;
1104 key_item *keychain = NULL;
1108 EXIM_CURSOR *cursor;
1109 uschar **argv = USS cargv;
1113 /* Scan the options */
1115 for (i = 1; i < argc; i++)
1117 if (argv[i][0] != '-') break;
1118 if (Ustrcmp(argv[i], "-f") == 0) continue;
1119 if (Ustrcmp(argv[i], "-t") == 0)
1127 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1128 (void)sscanf(CS s, "%d%n", &value, &count);
1132 case 'w': value *= 7;
1133 case 'd': value *= 24;
1134 case 'h': value *= 60;
1135 case 'm': value *= 60;
1138 default: usage(US"tidydb", US" [-t <time>]");
1143 else usage(US"tidydb", US" [-t <time>]");
1146 /* Adjust argument values and process arguments */
1151 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1153 /* Compute the oldest keep time, verify what we are doing, and open the
1156 oldest = time(NULL) - maxkeep;
1157 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1159 spool_directory = argv[1];
1160 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
1163 /* Prepare for building file names */
1165 sprintf(CS buffer, "%s/input/", argv[1]);
1166 path_len = Ustrlen(buffer);
1169 /* It appears, by experiment, that it is a bad idea to make changes
1170 to the file while scanning it. Pity the man page doesn't warn you about that.
1171 Therefore, we scan and build a list of all the keys. Then we use that to
1172 read the records and possibly update them. */
1174 for (key = dbfn_scan(dbm, TRUE, &cursor);
1176 key = dbfn_scan(dbm, FALSE, &cursor))
1178 key_item *k = store_get(sizeof(key_item) + Ustrlen(key), is_tainted(key));
1181 Ustrcpy(k->key, key);
1184 /* Now scan the collected keys and operate on the records, resetting
1185 the store each time round. */
1187 for (; keychain && (reset_point = store_mark()); store_reset(reset_point))
1189 dbdata_generic *value;
1191 key = keychain->key;
1192 keychain = keychain->next;
1193 value = dbfn_read_with_length(dbm, key, NULL);
1195 /* A continuation record may have been deleted or renamed already, so
1196 non-existence is not serious. */
1198 if (value == NULL) continue;
1200 /* Delete if too old */
1202 if (value->time_stamp < oldest)
1204 printf("deleted %s (too old)\n", key);
1205 dbfn_delete(dbm, key);
1209 /* Do database-specific tidying for wait databases, and message-
1210 specific tidying for the retry database. */
1212 if (dbdata_type == type_wait)
1214 dbdata_wait *wait = (dbdata_wait *)value;
1215 BOOL update = FALSE;
1217 /* Leave corrupt records alone */
1219 if (wait->count > WAIT_NAME_MAX)
1221 printf("**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
1222 key, wait->count, wait->count, WAIT_NAME_MAX);
1226 /* Loop for renamed continuation records. For each message id,
1227 check to see if the message exists, and if not, remove its entry
1228 from the record. Because of the possibility of split input directories,
1229 we must look in both possible places for a -D file. */
1233 int length = wait->count * MESSAGE_ID_LENGTH;
1235 for (int offset = length - MESSAGE_ID_LENGTH;
1236 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1238 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1239 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1241 if (Ustat(buffer, &statbuf) != 0)
1243 buffer[path_len] = wait->text[offset+5];
1244 buffer[path_len+1] = '/';
1245 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1246 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1248 if (Ustat(buffer, &statbuf) != 0)
1250 int left = length - offset - MESSAGE_ID_LENGTH;
1251 if (left > 0) Ustrncpy(wait->text + offset,
1252 wait->text + offset + MESSAGE_ID_LENGTH, left);
1254 length -= MESSAGE_ID_LENGTH;
1260 /* If record is empty and the main record, either delete it or rename
1261 the next continuation, repeating if that is also empty. */
1263 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1265 while (wait->count == 0 && wait->sequence > 0)
1268 dbdata_generic *newvalue;
1269 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1270 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1271 if (newvalue != NULL)
1274 wait = (dbdata_wait *)newvalue;
1275 dbfn_delete(dbm, newkey);
1276 printf("renamed %s\n", newkey);
1279 else wait->sequence--;
1282 /* If we have ended up with an empty main record, delete it
1283 and break the loop. Otherwise the new record will be scanned. */
1285 if (wait->count == 0 && wait->sequence == 0)
1287 dbfn_delete(dbm, key);
1288 printf("deleted %s (empty)\n", key);
1294 /* If not an empty main record, break the loop */
1299 /* Re-write the record if required */
1303 printf("updated %s\n", key);
1304 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1305 wait->count * MESSAGE_ID_LENGTH);
1309 /* If a retry record's key ends with a message-id, check that that message
1310 still exists; if not, remove this record. */
1312 else if (dbdata_type == type_retry)
1315 int len = Ustrlen(key);
1317 if (len < MESSAGE_ID_LENGTH + 1) continue;
1318 id = key + len - MESSAGE_ID_LENGTH - 1;
1319 if (*id++ != ':') continue;
1321 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1322 if (i == 6 || i == 13)
1323 { if (id[i] != '-') break; }
1325 { if (!isalnum(id[i])) break; }
1326 if (i < MESSAGE_ID_LENGTH) continue;
1328 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1329 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1331 if (Ustat(buffer, &statbuf) != 0)
1333 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1334 if (Ustat(buffer, &statbuf) != 0)
1336 dbfn_delete(dbm, key);
1337 printf("deleted %s (no message)\n", key);
1344 printf("Tidying complete\n");
1348 #endif /* EXIM_TIDYDB */
1350 /* End of exim_dbutil.c */