1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
6 /* Copyright (c) The Exim Maintainers 2020 - 2021 */
7 /* See the file NOTICE for conditions of use and distribution. */
10 /* This single source file is used to compile three utility programs for
11 maintaining Exim hints databases.
13 exim_dumpdb dumps out the contents
14 exim_fixdb patches the database (really for Exim maintenance/testing)
15 exim_tidydb removed obsolete data
17 In all cases, the first argument is the name of the spool directory. The second
18 argument is the name of the database file. The available names are:
20 callout: callout verification cache
21 misc: miscellaneous hints data
22 ratelimit: record for ACL "ratelimit" condition
23 retry: etry delivery information
24 seen: imestamp records for ACL "seen" condition
25 tls: TLS session resumption cache
26 wait-<t>: message waiting information; <t> is a transport name
28 There are a number of common subroutines, followed by three main programs,
29 whose inclusion is controlled by -D on the compilation command. */
35 /* Identifiers for the different database types. */
40 #define type_callout 4
41 #define type_ratelimit 5
46 /* This is used by our cut-down dbfn_open(). */
48 uschar *spool_directory;
53 /******************************************************************************/
54 /* dummies needed by Solaris build */
59 readconf_printtime(int t)
62 string_vformat_trc(gstring * g, const uschar * func, unsigned line,
63 unsigned size_limit, unsigned flags, const char *format, va_list ap)
66 string_sprintf_trc(const char * fmt, const uschar * func, unsigned line, ...)
69 string_format_trc(uschar * buf, int len, const uschar * func, unsigned line,
70 const char * fmt, ...)
73 struct global_flags f;
74 unsigned int log_selector[1];
76 BOOL split_spool_directory;
79 /* These introduced by the taintwarn handling */
80 #ifdef ALLOW_INSECURE_TAINTED_DATA
81 BOOL allow_insecure_tainted_data;
84 /******************************************************************************/
87 /*************************************************
89 *************************************************/
91 SIGNAL_BOOL sigalrm_seen;
94 sigalrm_handler(int sig)
101 /*************************************************
102 * Output usage message and exit *
103 *************************************************/
106 usage(uschar *name, uschar *options)
108 printf("Usage: exim_%s%s <spool-directory> <database-name>\n", name, options);
109 printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit | tls | seen\n");
115 /*************************************************
116 * Sort out the command arguments *
117 *************************************************/
119 /* This function checks that there are exactly 2 arguments, and checks the
120 second of them to be sure it is a known database name. */
123 check_args(int argc, uschar **argv, uschar *name, uschar *options)
125 uschar * aname = argv[optind + 1];
126 if (argc - optind == 2)
128 if (Ustrcmp(aname, "retry") == 0) return type_retry;
129 if (Ustrcmp(aname, "misc") == 0) return type_misc;
130 if (Ustrncmp(aname, "wait-", 5) == 0) return type_wait;
131 if (Ustrcmp(aname, "callout") == 0) return type_callout;
132 if (Ustrcmp(aname, "ratelimit") == 0) return type_ratelimit;
133 if (Ustrcmp(aname, "tls") == 0) return type_tls;
134 if (Ustrcmp(aname, "seen") == 0) return type_seen;
136 usage(name, options);
137 return -1; /* Never obeyed */
142 options(int argc, uschar * argv[], uschar * name)
147 while ((opt = getopt(argc, (char * const *)argv, "z")) != -1)
150 case 'z': utc = TRUE; break;
151 default: usage(name, US" [-z]");
158 /*************************************************
159 * Handle attempts to write the log *
160 *************************************************/
162 /* The message gets written to stderr when log_write() is called from a
163 utility. The message always gets '\n' added on the end of it. These calls come
164 from modules such as store.c when things go drastically wrong (e.g. malloc()
165 failing). In normal use they won't get obeyed.
168 selector not relevant when running a utility
169 flags not relevant when running a utility
170 format a printf() format
171 ... arguments for format
177 log_write(unsigned int selector, int flags, const char *format, ...)
180 va_start(ap, format);
181 vfprintf(stderr, format, ap);
182 fprintf(stderr, "\n");
188 /*************************************************
189 * Format a time value for printing *
190 *************************************************/
192 static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss ")];
197 struct tm *tmstr = utc ? gmtime(&t) : localtime(&t);
198 Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
204 /*************************************************
205 * Format a cache value for printing *
206 *************************************************/
209 print_cache(int value)
211 return value == ccache_accept ? US"accept" :
212 value == ccache_reject ? US"reject" :
218 /*************************************************
220 *************************************************/
227 time_t now = time(NULL);
228 struct tm *tm = localtime(&now);
233 for (uschar * t = s + Ustrlen(s) - 1; t >= s; t--)
235 if (*t == ':') continue;
236 if (!isdigit((uschar)*t)) return -1;
241 if (!isdigit((uschar)*t)) return -1;
242 value = value + (*t - '0')*10;
247 case 0: tm->tm_min = value; break;
248 case 1: tm->tm_hour = value; break;
249 case 2: tm->tm_mday = value; break;
250 case 3: tm->tm_mon = value - 1; break;
251 case 4: tm->tm_year = (value < 90)? value + 100 : value; break;
258 #endif /* EXIM_FIXDB */
262 /*************************************************
263 * Open and lock a database file *
264 *************************************************/
266 /* This is a cut-down version from the function in dbfn.h that Exim itself
267 uses. We assume the database exists, and therefore give up if we cannot open
271 name The single-component name of one of Exim's database files.
272 flags O_RDONLY or O_RDWR
273 dbblock Points to an open_db block to be filled in.
277 Returns: NULL if the open failed, or the locking failed.
278 On success, dbblock is returned. This contains the dbm pointer and
279 the fd of the locked lock file.
283 dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof, BOOL panic)
286 struct flock lock_data;
287 BOOL read_only = flags == O_RDONLY;
288 uschar * dirname, * filename;
290 /* The first thing to do is to open a separate file on which to lock. This
291 ensures that Exim has exclusive use of the database before it even tries to
292 open it. If there is a database, there should be a lock file in existence. */
294 #ifdef COMPILE_UTILITY
295 if ( asprintf(CSS &dirname, "%s/db", spool_directory) < 0
296 || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
299 dirname = string_sprintf("%s/db", spool_directory);
300 filename = string_sprintf("%s/%s.lockfile", dirname, name);
303 dbblock->lockfd = Uopen(filename, flags, 0);
304 if (dbblock->lockfd < 0)
306 printf("** Failed to open database lock file %s: %s\n", filename,
311 /* Now we must get a lock on the opened lock file; do this with a blocking
312 lock that times out. */
314 lock_data.l_type = read_only ? F_RDLCK : F_WRLCK;
315 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
317 sigalrm_seen = FALSE;
318 os_non_restarting_signal(SIGALRM, sigalrm_handler);
319 ALARM(EXIMDB_LOCK_TIMEOUT);
320 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
323 if (sigalrm_seen) errno = ETIMEDOUT;
326 printf("** Failed to get %s lock for %s: %s",
327 flags & O_WRONLY ? "write" : "read",
329 errno == ETIMEDOUT ? "timed out" : strerror(errno));
330 (void)close(dbblock->lockfd);
334 /* At this point we have an opened and locked separate lock file, that is,
335 exclusive access to the database, so we can go ahead and open it. */
337 #ifdef COMPILE_UTILITY
338 if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
340 filename = string_sprintf("%s/%s", dirname, name);
342 dbblock->dbptr = exim_dbopen(filename, dirname, flags, 0);
346 printf("** Failed to open DBM file %s for %s:\n %s%s\n", filename,
347 read_only? "reading" : "writing", strerror(errno),
349 " (or Berkeley DB error while opening)"
354 (void)close(dbblock->lockfd);
364 /*************************************************
365 * Unlock and close a database file *
366 *************************************************/
368 /* Closing a file automatically unlocks it, so after closing the database, just
371 Argument: a pointer to an open database block
376 dbfn_close(open_db *dbblock)
378 exim_dbclose(dbblock->dbptr);
379 (void)close(dbblock->lockfd);
385 /*************************************************
386 * Read from database file *
387 *************************************************/
389 /* Passing back the pointer unchanged is useless, because there is no guarantee
390 of alignment. Since all the records used by Exim need to be properly aligned to
391 pick out the timestamps, etc., do the copying centrally here.
394 dbblock a pointer to an open database block
395 key the key of the record to be read
396 length where to put the length (or NULL if length not wanted). Includes overhead.
398 Returns: a pointer to the retrieved record, or
399 NULL if the record is not found
403 dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
406 EXIM_DATUM key_datum, result_datum;
407 int klen = Ustrlen(key) + 1;
408 uschar * key_copy = store_get(klen, key);
410 memcpy(key_copy, key, klen);
412 exim_datum_init(&key_datum); /* Some DBM libraries require the datum */
413 exim_datum_init(&result_datum); /* to be cleared before use. */
414 exim_datum_data_set(&key_datum, key_copy);
415 exim_datum_size_set(&key_datum, klen);
417 if (!exim_dbget(dbblock->dbptr, &key_datum, &result_datum)) return NULL;
419 /* Assume for now that anything stored could have been tainted. Properly
420 we should store the taint status along with the data. */
422 yield = store_get(exim_datum_size_get(&result_datum), GET_TAINTED);
423 memcpy(yield, exim_datum_data_get(&result_datum), exim_datum_size_get(&result_datum));
424 if (length) *length = exim_datum_size_get(&result_datum);
426 exim_datum_free(&result_datum); /* Some DBM libs require freeing */
432 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
434 /*************************************************
435 * Write to database file *
436 *************************************************/
440 dbblock a pointer to an open database block
441 key the key of the record to be written
442 ptr a pointer to the record to be written
443 length the length of the record to be written
445 Returns: the yield of the underlying dbm or db "write" function. If this
446 is dbm, the value is zero for OK.
450 dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
452 EXIM_DATUM key_datum, value_datum;
453 dbdata_generic *gptr = (dbdata_generic *)ptr;
454 int klen = Ustrlen(key) + 1;
455 uschar * key_copy = store_get(klen, key);
457 memcpy(key_copy, key, klen);
458 gptr->time_stamp = time(NULL);
460 exim_datum_init(&key_datum); /* Some DBM libraries require the datum */
461 exim_datum_init(&value_datum); /* to be cleared before use. */
462 exim_datum_data_set(&key_datum, key_copy);
463 exim_datum_size_set(&key_datum, klen);
464 exim_datum_data_set(&value_datum, ptr);
465 exim_datum_size_set(&value_datum, length);
466 return exim_dbput(dbblock->dbptr, &key_datum, &value_datum);
471 /*************************************************
472 * Delete record from database file *
473 *************************************************/
477 dbblock a pointer to an open database block
478 key the key of the record to be deleted
480 Returns: the yield of the underlying dbm or db "delete" function.
484 dbfn_delete(open_db *dbblock, const uschar *key)
486 int klen = Ustrlen(key) + 1;
487 uschar * key_copy = store_get(klen, key);
488 EXIM_DATUM key_datum;
490 memcpy(key_copy, key, klen);
491 exim_datum_init(&key_datum); /* Some DBM libraries require clearing */
492 exim_datum_data_set(&key_datum, key_copy);
493 exim_datum_size_set(&key_datum, klen);
494 return exim_dbdel(dbblock->dbptr, &key_datum);
497 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
501 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
502 /*************************************************
503 * Scan the keys of a database file *
504 *************************************************/
508 dbblock a pointer to an open database block
509 start TRUE if starting a new scan
510 FALSE if continuing with the current scan
511 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
512 that use the notion of a cursor
514 Returns: the next record from the file, or
515 NULL if there are no more
519 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
521 EXIM_DATUM key_datum, value_datum;
524 /* Some dbm require an initialization */
526 if (start) *cursor = exim_dbcreate_cursor(dbblock->dbptr);
528 exim_datum_init(&key_datum); /* Some DBM libraries require the datum */
529 exim_datum_init(&value_datum); /* to be cleared before use. */
531 yield = exim_dbscan(dbblock->dbptr, &key_datum, &value_datum, start, *cursor)
532 ? US exim_datum_data_get(&key_datum) : NULL;
534 /* Some dbm require a termination */
536 if (!yield) exim_dbdelete_cursor(*cursor);
539 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
544 /*************************************************
545 * The exim_dumpdb main program *
546 *************************************************/
549 main(int argc, char **cargv)
556 uschar **argv = USS cargv;
557 uschar keybuffer[1024];
560 options(argc, argv, US"dumpdb");
562 /* Check the arguments, and open the database */
564 dbdata_type = check_args(argc, argv, US"dumpdb", US" [-z]");
565 argc -= optind; argv += optind;
566 spool_directory = argv[0];
568 if (!(dbm = dbfn_open(argv[1], O_RDONLY, &dbblock, FALSE, TRUE)))
571 /* Scan the file, formatting the information for each entry. Note
572 that data is returned in a malloc'ed block, in order that it be
573 correctly aligned. */
575 for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
577 key = dbfn_scan(dbm, FALSE, &cursor))
581 dbdata_callout_cache *callout;
582 dbdata_ratelimit *ratelimit;
583 dbdata_ratelimit_unique *rate_unique;
584 dbdata_tls_session *session;
589 uschar name[MESSAGE_ID_LENGTH + 1];
591 rmark reset_point = store_mark();
593 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
594 which might change. */
596 if (Ustrlen(key) > sizeof(keybuffer) - 1)
598 printf("**** Overlong key encountered: %s\n", key);
601 Ustrcpy(keybuffer, key);
603 if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
604 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
605 "was not found in the file - something is wrong!\n",
609 /* Note: don't use print_time more than once in one statement, since
610 it uses a single buffer. */
615 retry = (dbdata_retry *)value;
616 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
617 retry->more_errno, retry->text,
618 print_time(retry->first_failed));
619 printf("%s ", print_time(retry->last_try));
620 printf("%s %s\n", print_time(retry->next_try),
621 (retry->expired)? "*" : "");
625 wait = (dbdata_wait *)value;
626 printf("%s ", keybuffer);
628 name[MESSAGE_ID_LENGTH] = 0;
630 /* Leave corrupt records alone */
631 if (wait->count > WAIT_NAME_MAX)
634 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
635 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
636 wait->count = WAIT_NAME_MAX;
637 yield = count_bad = 1;
639 for (int i = 1; i <= wait->count; i++)
641 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
642 if (count_bad && name[0] == 0) break;
643 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
644 Ustrspn(name, "0123456789"
645 "abcdefghijklmnopqrstuvwxyz"
646 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
649 "**** Data for %s corrupted: bad character in message id\n",
651 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
652 fprintf(stderr, "%02x ", name[j]);
653 fprintf(stderr, "\n");
658 t += MESSAGE_ID_LENGTH;
664 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
669 callout = (dbdata_callout_cache *)value;
671 /* New-style address record */
673 if (length == sizeof(dbdata_callout_cache_address))
675 printf("%s %s callout=%s\n",
676 print_time(((dbdata_generic *)value)->time_stamp),
678 print_cache(callout->result));
681 /* New-style domain record */
683 else if (length == sizeof(dbdata_callout_cache))
685 printf("%s %s callout=%s postmaster=%s",
686 print_time(((dbdata_generic *)value)->time_stamp),
688 print_cache(callout->result),
689 print_cache(callout->postmaster_result));
690 if (callout->postmaster_result != ccache_unknown)
691 printf(" (%s)", print_time(callout->postmaster_stamp));
692 printf(" random=%s", print_cache(callout->random_result));
693 if (callout->random_result != ccache_unknown)
694 printf(" (%s)", print_time(callout->random_stamp));
701 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
703 ratelimit = (dbdata_ratelimit *)value;
704 rate_unique = (dbdata_ratelimit_unique *)value;
705 printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
706 print_time(ratelimit->time_stamp),
707 ratelimit->time_usec, ratelimit->rate,
708 print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
713 ratelimit = (dbdata_ratelimit *)value;
714 printf("%s.%06d rate: %10.3f key: %s\n",
715 print_time(ratelimit->time_stamp),
716 ratelimit->time_usec, ratelimit->rate,
722 session = (dbdata_tls_session *)value;
723 printf(" %s %.*s\n", keybuffer, length, session->session);
727 seen = (dbdata_seen *)value;
728 printf("%s\t%s\n", keybuffer, print_time(seen->time_stamp));
732 store_reset(reset_point);
739 #endif /* EXIM_DUMPDB */
745 /*************************************************
746 * The exim_fixdb main program *
747 *************************************************/
749 /* In order not to hold the database lock any longer than is necessary, each
750 operation on the database uses a separate open/close call. This is expensive,
751 but then using this utility is not expected to be very common. Its main use is
752 to provide a way of patching up hints databases in order to run tests.
757 This causes the data from the given record to be displayed, or "not found"
758 to be output. Note that in the retry database, destination names are
759 preceded by R: or T: for router or transport retry info.
762 This causes the given record to be deleted or "not found" to be output.
764 (3) <record name> <field number> <value>
765 This sets the given value into the given field, identified by a number
766 which is output by the display command. Not all types of record can
770 This exits from exim_fixdb.
772 If the record name is omitted from (2) or (3), the previously used record name
777 main(int argc, char **cargv)
780 uschar **argv = USS cargv;
787 options(argc, argv, US"fixdb");
788 name[0] = 0; /* No name set */
790 /* Sort out the database type, verify what we are working on and then process
793 dbdata_type = check_args(argc, argv, US"fixdb", US" [-z]");
794 argc -= optind; argv += optind;
795 spool_directory = argv[0];
798 printf("Modifying Exim hints database %s/db/%s\n", spool_directory, aname);
800 for(; (reset_point = store_mark()); store_reset(reset_point))
807 dbdata_callout_cache *callout;
808 dbdata_ratelimit *ratelimit;
809 dbdata_ratelimit_unique *rate_unique;
810 dbdata_tls_session *session;
813 uschar field[256], value[256];
816 if (Ufgets(buffer, 256, stdin) == NULL) break;
818 buffer[Ustrlen(buffer)-1] = 0;
819 field[0] = value[0] = 0;
821 /* If the buffer contains just one digit, or just consists of "d", use the
822 previous name for an update. */
824 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
825 || Ustrcmp(buffer, "d") == 0)
829 printf("No previous record name is set\n");
832 (void)sscanf(CS buffer, "%s %s", field, value);
837 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
840 /* Handle an update request */
846 if (!(dbm = dbfn_open(aname, O_RDWR, &dbblock, FALSE, TRUE)))
849 if (Ustrcmp(field, "d") == 0)
851 if (value[0] != 0) printf("unexpected value after \"d\"\n");
852 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
853 "not found" : "deleted");
858 else if (isdigit((uschar)field[0]))
860 int fieldno = Uatoi(field);
863 printf("value missing\n");
869 record = dbfn_read_with_length(dbm, name, &oldlength);
870 if (record == NULL) printf("not found\n"); else
873 /*int length = 0; Stops compiler warning */
878 retry = (dbdata_retry *)record;
879 /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
883 case 0: retry->basic_errno = Uatoi(value);
885 case 1: retry->more_errno = Uatoi(value);
887 case 2: if ((tt = read_time(value)) > 0) retry->first_failed = tt;
888 else printf("bad time value\n");
890 case 3: if ((tt = read_time(value)) > 0) retry->last_try = tt;
891 else printf("bad time value\n");
893 case 4: if ((tt = read_time(value)) > 0) retry->next_try = tt;
894 else printf("bad time value\n");
896 case 5: if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
897 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
898 else printf("\"yes\" or \"no\" expected=n");
900 default: printf("unknown field number\n");
907 printf("Can't change contents of wait database record\n");
911 printf("Can't change contents of misc database record\n");
915 callout = (dbdata_callout_cache *)record;
916 /* length = sizeof(dbdata_callout_cache); */
919 case 0: callout->result = Uatoi(value);
921 case 1: callout->postmaster_result = Uatoi(value);
923 case 2: callout->random_result = Uatoi(value);
925 default: printf("unknown field number\n");
932 ratelimit = (dbdata_ratelimit *)record;
935 case 0: if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
936 else printf("bad time value\n");
938 case 1: ratelimit->time_usec = Uatoi(value);
940 case 2: ratelimit->rate = Ustrtod(value, NULL);
942 case 3: if (Ustrstr(name, "/unique/") != NULL
943 && oldlength >= sizeof(dbdata_ratelimit_unique))
945 rate_unique = (dbdata_ratelimit_unique *)record;
946 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
947 else printf("bad time value\n");
950 /* else fall through */
952 case 5: if (Ustrstr(name, "/unique/") != NULL
953 && oldlength >= sizeof(dbdata_ratelimit_unique))
961 md5_end(&md5info, value, Ustrlen(value), md5sum);
962 hash = md5sum[0] << 0 | md5sum[1] << 8
963 | md5sum[2] << 16 | md5sum[3] << 24;
964 hinc = md5sum[4] << 0 | md5sum[5] << 8
965 | md5sum[6] << 16 | md5sum[7] << 24;
966 rate_unique = (dbdata_ratelimit_unique *)record;
968 for (unsigned n = 0; n < 8; n++, hash += hinc)
970 int bit = 1 << (hash % 8);
971 int byte = (hash / 8) % rate_unique->bloom_size;
972 if ((rate_unique->bloom[byte] & bit) == 0)
975 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
979 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
982 /* else fall through */
983 default: printf("unknown field number\n");
990 printf("Can't change contents of tls database record\n");
994 dbfn_write(dbm, name, record, oldlength);
1001 printf("field number or d expected\n");
1006 if (!verify) continue;
1009 /* The "name" q causes an exit */
1011 else if (Ustrcmp(name, "q") == 0) return 0;
1013 /* Handle a read request, or verify after an update. */
1015 if (!(dbm = dbfn_open(aname, O_RDONLY, &dbblock, FALSE, TRUE)))
1018 if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
1020 printf("record %s not found\n", name);
1026 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
1030 retry = (dbdata_retry *)record;
1031 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
1032 printf("1 extra data: %d\n", retry->more_errno);
1033 printf("2 first failed: %s\n", print_time(retry->first_failed));
1034 printf("3 last try: %s\n", print_time(retry->last_try));
1035 printf("4 next try: %s\n", print_time(retry->next_try));
1036 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
1040 wait = (dbdata_wait *)record;
1042 printf("Sequence: %d\n", wait->sequence);
1043 if (wait->count > WAIT_NAME_MAX)
1045 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
1046 wait->count, WAIT_NAME_MAX);
1047 wait->count = WAIT_NAME_MAX;
1050 for (int i = 1; i <= wait->count; i++)
1052 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1053 value[MESSAGE_ID_LENGTH] = 0;
1054 if (count_bad && value[0] == 0) break;
1055 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1056 Ustrspn(value, "0123456789"
1057 "abcdefghijklmnopqrstuvwxyz"
1058 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1060 printf("\n**** Data corrupted: bad character in message id ****\n");
1061 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
1062 printf("%02x ", value[j]);
1066 printf("%s ", value);
1067 t += MESSAGE_ID_LENGTH;
1076 callout = (dbdata_callout_cache *)record;
1077 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1079 if (oldlength > sizeof(dbdata_callout_cache_address))
1081 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1082 callout->postmaster_result);
1083 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1084 callout->random_result);
1088 case type_ratelimit:
1089 ratelimit = (dbdata_ratelimit *)record;
1090 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1091 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1092 printf("2 sender rate: % .3f\n", ratelimit->rate);
1093 if (Ustrstr(name, "/unique/") != NULL
1094 && oldlength >= sizeof(dbdata_ratelimit_unique))
1096 rate_unique = (dbdata_ratelimit_unique *)record;
1097 printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1098 printf("4 test filter membership\n");
1099 printf("5 add element to filter\n");
1104 session = (dbdata_tls_session *)value;
1105 printf("0 time stamp: %s\n", print_time(session->time_stamp));
1106 printf("1 session: .%s\n", session->session);
1111 /* The database is closed after each request */
1120 #endif /* EXIM_FIXDB */
1125 /*************************************************
1126 * The exim_tidydb main program *
1127 *************************************************/
1130 /* Utility program to tidy the contents of an exim database file. There is one
1133 -t <time> expiry time for old records - default 30 days
1135 For backwards compatibility, an -f option is recognized and ignored. (It used
1136 to request a "full" tidy. This version always does the whole job.) */
1139 typedef struct key_item {
1140 struct key_item *next;
1146 main(int argc, char **cargv)
1148 struct stat statbuf;
1149 int maxkeep = 30 * 24 * 60 * 60;
1150 int dbdata_type, i, oldest, path_len;
1151 key_item *keychain = NULL;
1155 EXIM_CURSOR *cursor;
1156 uschar **argv = USS cargv;
1162 /* Scan the options */
1164 for (i = 1; i < argc; i++)
1166 if (argv[i][0] != '-') break;
1167 if (Ustrcmp(argv[i], "-f") == 0) continue;
1168 if (Ustrcmp(argv[i], "-t") == 0)
1176 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1177 (void)sscanf(CS s, "%d%n", &value, &count);
1181 case 'w': value *= 7;
1182 case 'd': value *= 24;
1183 case 'h': value *= 60;
1184 case 'm': value *= 60;
1187 default: usage(US"tidydb", US" [-t <time>]");
1192 else usage(US"tidydb", US" [-t <time>]");
1195 /* Adjust argument values and process arguments */
1200 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1202 /* Compute the oldest keep time, verify what we are doing, and open the
1205 oldest = time(NULL) - maxkeep;
1206 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1208 spool_directory = argv[1];
1209 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
1212 /* Prepare for building file names */
1214 sprintf(CS buffer, "%s/input/", argv[1]);
1215 path_len = Ustrlen(buffer);
1218 /* It appears, by experiment, that it is a bad idea to make changes
1219 to the file while scanning it. Pity the man page doesn't warn you about that.
1220 Therefore, we scan and build a list of all the keys. Then we use that to
1221 read the records and possibly update them. */
1223 for (key = dbfn_scan(dbm, TRUE, &cursor);
1225 key = dbfn_scan(dbm, FALSE, &cursor))
1227 key_item * k = store_get(sizeof(key_item) + Ustrlen(key), key);
1230 Ustrcpy(k->key, key);
1233 /* Now scan the collected keys and operate on the records, resetting
1234 the store each time round. */
1236 for (; keychain && (reset_point = store_mark()); store_reset(reset_point))
1238 dbdata_generic *value;
1240 key = keychain->key;
1241 keychain = keychain->next;
1242 value = dbfn_read_with_length(dbm, key, NULL);
1244 /* A continuation record may have been deleted or renamed already, so
1245 non-existence is not serious. */
1247 if (!value) continue;
1249 /* Delete if too old */
1251 if (value->time_stamp < oldest)
1253 printf("deleted %s (too old)\n", key);
1254 dbfn_delete(dbm, key);
1258 /* Do database-specific tidying for wait databases, and message-
1259 specific tidying for the retry database. */
1261 if (dbdata_type == type_wait)
1263 dbdata_wait *wait = (dbdata_wait *)value;
1264 BOOL update = FALSE;
1266 /* Leave corrupt records alone */
1268 if (wait->time_stamp > time(NULL))
1270 printf("**** Data for '%s' corrupted\n time in future: %s\n",
1271 key, print_time(((dbdata_generic *)value)->time_stamp));
1274 if (wait->count > WAIT_NAME_MAX)
1276 printf("**** Data for '%s' corrupted\n count=%d=0x%x max=%d\n",
1277 key, wait->count, wait->count, WAIT_NAME_MAX);
1280 if (wait->sequence > WAIT_CONT_MAX)
1282 printf("**** Data for '%s' corrupted\n sequence=%d=0x%x max=%d\n",
1283 key, wait->sequence, wait->sequence, WAIT_CONT_MAX);
1287 /* Record over 1 year old; just remove it */
1289 if (wait->time_stamp < time(NULL) - 365*24*60*60)
1291 dbfn_delete(dbm, key);
1292 printf("deleted %s (too old)\n", key);
1296 /* Loop for renamed continuation records. For each message id,
1297 check to see if the message exists, and if not, remove its entry
1298 from the record. Because of the possibility of split input directories,
1299 we must look in both possible places for a -D file. */
1303 int length = wait->count * MESSAGE_ID_LENGTH;
1305 for (int offset = length - MESSAGE_ID_LENGTH;
1306 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1308 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1309 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1311 if (Ustat(buffer, &statbuf) != 0)
1313 buffer[path_len] = wait->text[offset+5];
1314 buffer[path_len+1] = '/';
1315 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1316 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1318 if (Ustat(buffer, &statbuf) != 0)
1320 int left = length - offset - MESSAGE_ID_LENGTH;
1321 if (left > 0) Ustrncpy(wait->text + offset,
1322 wait->text + offset + MESSAGE_ID_LENGTH, left);
1324 length -= MESSAGE_ID_LENGTH;
1330 /* If record is empty and the main record, either delete it or rename
1331 the next continuation, repeating if that is also empty. */
1333 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1335 while (wait->count == 0 && wait->sequence > 0)
1338 dbdata_generic *newvalue;
1339 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1340 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1341 if (newvalue != NULL)
1344 wait = (dbdata_wait *)newvalue;
1345 dbfn_delete(dbm, newkey);
1346 printf("renamed %s\n", newkey);
1349 else wait->sequence--;
1352 /* If we have ended up with an empty main record, delete it
1353 and break the loop. Otherwise the new record will be scanned. */
1355 if (wait->count == 0 && wait->sequence == 0)
1357 dbfn_delete(dbm, key);
1358 printf("deleted %s (empty)\n", key);
1364 /* If not an empty main record, break the loop */
1369 /* Re-write the record if required */
1373 printf("updated %s\n", key);
1374 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1375 wait->count * MESSAGE_ID_LENGTH);
1379 /* If a retry record's key ends with a message-id, check that that message
1380 still exists; if not, remove this record. */
1382 else if (dbdata_type == type_retry)
1385 int len = Ustrlen(key);
1387 if (len < MESSAGE_ID_LENGTH + 1) continue;
1388 id = key + len - MESSAGE_ID_LENGTH - 1;
1389 if (*id++ != ':') continue;
1391 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1392 if (i == 6 || i == 13)
1393 { if (id[i] != '-') break; }
1395 { if (!isalnum(id[i])) break; }
1396 if (i < MESSAGE_ID_LENGTH) continue;
1398 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1399 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1401 if (Ustat(buffer, &statbuf) != 0)
1403 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1404 if (Ustat(buffer, &statbuf) != 0)
1406 dbfn_delete(dbm, key);
1407 printf("deleted %s (no message)\n", key);
1414 printf("Tidying complete\n");
1418 #endif /* EXIM_TIDYDB */
1420 /* End of exim_dbutil.c */