1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) The Exim Maintainers 2020 - 2024 */
6 /* Copyright (c) University of Cambridge 1995 - 2018 */
7 /* See the file NOTICE for conditions of use and distribution. */
8 /* SPDX-License-Identifier: GPL-2.0-or-later */
11 /* This single source file is used to compile three utility programs for
12 maintaining Exim hints databases.
14 exim_dumpdb dumps out the contents
15 exim_fixdb patches the database (really for Exim maintenance/testing)
16 exim_tidydb removed obsolete data
18 In all cases, the first argument is the name of the spool directory. The second
19 argument is the name of the database file. The available names are:
21 callout: callout verification cache
22 misc: miscellaneous hints data
23 ratelimit: record for ACL "ratelimit" condition
24 retry: etry delivery information
25 seen: imestamp records for ACL "seen" condition
26 tls: TLS session resumption cache
27 wait-<t>: message waiting information; <t> is a transport name
29 There are a number of common subroutines, followed by three main programs,
30 whose inclusion is controlled by -D on the compilation command. */
36 /* Identifiers for the different database types. */
41 #define type_callout 4
42 #define type_ratelimit 5
47 /* This is used by our cut-down dbfn_open(). */
49 uschar *spool_directory;
55 /******************************************************************************/
56 /* dummies needed by Solaris build */
61 readconf_printtime(int t)
64 string_catn(gstring * g, const uschar * s, int count)
67 string_vformat_trc(gstring * g, const uschar * func, unsigned line,
68 unsigned size_limit, unsigned flags, const char *format, va_list ap)
71 string_sprintf_trc(const char * fmt, const uschar * func, unsigned line, ...)
74 string_format_trc(uschar * buf, int len, const uschar * func, unsigned line,
75 const char * fmt, ...)
78 struct global_flags f;
79 unsigned int log_selector[1];
81 BOOL split_spool_directory;
84 /* These introduced by the taintwarn handling */
85 #ifdef ALLOW_INSECURE_TAINTED_DATA
86 BOOL allow_insecure_tainted_data;
89 /******************************************************************************/
92 /*************************************************
94 *************************************************/
96 SIGNAL_BOOL sigalrm_seen;
99 sigalrm_handler(int sig)
106 /*************************************************
107 * Output usage message and exit *
108 *************************************************/
111 usage(uschar *name, uschar *options)
113 printf("Usage: exim_%s%s <spool-directory> <database-name>\n", name, options);
114 printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit | tls | seen\n");
120 /*************************************************
121 * Sort out the command arguments *
122 *************************************************/
124 /* This function checks that there are exactly 2 arguments, and checks the
125 second of them to be sure it is a known database name. */
128 check_args(int argc, uschar **argv, uschar *name, uschar *options)
130 uschar * aname = argv[optind + 1];
131 if (argc - optind == 2)
133 if (Ustrcmp(aname, "retry") == 0) return type_retry;
134 if (Ustrcmp(aname, "misc") == 0) return type_misc;
135 if (Ustrncmp(aname, "wait-", 5) == 0) return type_wait;
136 if (Ustrcmp(aname, "callout") == 0) return type_callout;
137 if (Ustrcmp(aname, "ratelimit") == 0) return type_ratelimit;
138 if (Ustrcmp(aname, "tls") == 0) return type_tls;
139 if (Ustrcmp(aname, "seen") == 0) return type_seen;
141 usage(name, options);
142 return -1; /* Never obeyed */
148 options(int argc, uschar * argv[], uschar * name, const uschar * opts)
153 while ((opt = getopt(argc, (char * const *)argv, CCS opts)) != -1)
156 case 'k': keyonly = TRUE; break;
157 case 'z': utc = TRUE; break;
158 default: usage(name, US" [-z] [-k]");
165 /*************************************************
166 * Handle attempts to write the log *
167 *************************************************/
169 /* The message gets written to stderr when log_write() is called from a
170 utility. The message always gets '\n' added on the end of it. These calls come
171 from modules such as store.c when things go drastically wrong (e.g. malloc()
172 failing). In normal use they won't get obeyed.
175 selector not relevant when running a utility
176 flags not relevant when running a utility
177 format a printf() format
178 ... arguments for format
184 log_write(unsigned int selector, int flags, const char *format, ...)
187 va_start(ap, format);
188 vfprintf(stderr, format, ap);
189 fprintf(stderr, "\n");
195 /*************************************************
196 * Format a time value for printing *
197 *************************************************/
199 static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss ")];
204 struct tm *tmstr = utc ? gmtime(&t) : localtime(&t);
205 Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
211 /*************************************************
212 * Format a cache value for printing *
213 *************************************************/
216 print_cache(int value)
218 return value == ccache_accept ? US"accept" :
219 value == ccache_reject ? US"reject" :
225 /*************************************************
227 *************************************************/
234 time_t now = time(NULL);
235 struct tm *tm = localtime(&now);
240 for (uschar * t = s + Ustrlen(s) - 1; t >= s; t--)
242 if (*t == ':') continue;
243 if (!isdigit((uschar)*t)) return -1;
248 if (!isdigit((uschar)*t)) return -1;
249 value = value + (*t - '0')*10;
254 case 0: tm->tm_min = value; break;
255 case 1: tm->tm_hour = value; break;
256 case 2: tm->tm_mday = value; break;
257 case 3: tm->tm_mon = value - 1; break;
258 case 4: tm->tm_year = (value < 90)? value + 100 : value; break;
265 #endif /* EXIM_FIXDB */
269 /*************************************************
270 * Open and lock a database file *
271 *************************************************/
273 /* This is a cut-down version from the function in dbfn.h that Exim itself
274 uses. We assume the database exists, and therefore give up if we cannot open
278 name The single-component name of one of Exim's database files.
279 flags O_RDONLY or O_RDWR
280 dbblock Points to an open_db block to be filled in.
284 Returns: NULL if the open failed, or the locking failed.
285 On success, dbblock is returned. This contains the dbm pointer and
286 the fd of the locked lock file.
290 dbfn_open(const uschar * name, int flags, open_db * dbblock,
291 BOOL lof, BOOL panic)
294 struct flock lock_data;
295 BOOL read_only = flags & O_RDONLY;
296 uschar * dirname, * filename;
298 /* The first thing to do is to open a separate file on which to lock. This
299 ensures that Exim has exclusive use of the database before it even tries to
300 open it. If there is a database, there should be a lock file in existence. */
302 if ( asprintf(CSS &dirname, "%s/db", spool_directory) < 0
303 || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
306 if ((dbblock->lockfd = Uopen(filename, O_RDWR|O_CREAT, 0)) < 0)
308 printf("** Failed to open database lock file %s: %s\n", filename,
313 /* Now we must get a lock on the opened lock file; do this with a blocking
314 lock that times out. */
316 lock_data.l_type = read_only ? F_RDLCK : F_WRLCK;
317 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
319 sigalrm_seen = FALSE;
320 os_non_restarting_signal(SIGALRM, sigalrm_handler);
321 ALARM(EXIMDB_LOCK_TIMEOUT);
322 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
325 if (sigalrm_seen) errno = ETIMEDOUT;
328 printf("** Failed to get %s lock for %s: %s",
329 read_only ? "read" : "write",
331 errno == ETIMEDOUT ? "timed out" : strerror(errno));
332 (void)close(dbblock->lockfd);
336 /* At this point we have an opened and locked separate lock file, that is,
337 exclusive access to the database, so we can go ahead and open it. */
339 if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
341 if (flags & O_RDWR) flags |= O_CREAT;
343 if (!(dbblock->dbptr = exim_dbopen(filename, dirname, flags, 0)))
345 printf("** Failed to open DBM file %s for %s:\n %s%s\n", filename,
346 read_only? "reading" : "writing", strerror(errno),
348 " (or Berkeley DB error while opening)"
353 (void)close(dbblock->lockfd);
363 /*************************************************
364 * Unlock and close a database file *
365 *************************************************/
367 /* Closing a file automatically unlocks it, so after closing the database, just
370 Argument: a pointer to an open database block
375 dbfn_close(open_db *dbblock)
377 exim_dbclose(dbblock->dbptr);
378 (void)close(dbblock->lockfd);
384 /*************************************************
385 * Read from database file *
386 *************************************************/
388 /* Passing back the pointer unchanged is useless, because there is no guarantee
389 of alignment. Since all the records used by Exim need to be properly aligned to
390 pick out the timestamps, etc., do the copying centrally here.
393 dbblock a pointer to an open database block
394 key the key of the record to be read
395 length where to put the length (or NULL if length not wanted). Includes overhead.
397 Returns: a pointer to the retrieved record, or
398 NULL if the record is not found
402 dbfn_read_with_length(open_db * dbblock, const uschar * key, int * length)
405 EXIM_DATUM key_datum, result_datum;
406 int klen = Ustrlen(key) + 1;
407 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 dlen = exim_datum_size_get(&result_datum);
423 yield = store_get(dlen, GET_TAINTED);
424 memcpy(yield, exim_datum_data_get(&result_datum), dlen);
425 if (length) *length = dlen;
427 exim_datum_free(&result_datum); /* Some DBM libs require freeing */
433 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
435 /*************************************************
436 * Write to database file *
437 *************************************************/
441 dbblock a pointer to an open database block
442 key the key of the record to be written
443 ptr a pointer to the record to be written
444 length the length of the record to be written
446 Returns: the yield of the underlying dbm or db "write" function. If this
447 is dbm, the value is zero for OK.
451 dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
453 EXIM_DATUM key_datum, value_datum;
454 dbdata_generic *gptr = (dbdata_generic *)ptr;
455 int klen = Ustrlen(key) + 1;
456 uschar * key_copy = store_get(klen, key);
458 memcpy(key_copy, key, klen);
459 gptr->time_stamp = time(NULL);
461 exim_datum_init(&key_datum); /* Some DBM libraries require the datum */
462 exim_datum_init(&value_datum); /* to be cleared before use. */
463 exim_datum_data_set(&key_datum, key_copy);
464 exim_datum_size_set(&key_datum, klen);
465 exim_datum_data_set(&value_datum, ptr);
466 exim_datum_size_set(&value_datum, length);
467 return exim_dbput(dbblock->dbptr, &key_datum, &value_datum);
472 /*************************************************
473 * Delete record from database file *
474 *************************************************/
478 dbblock a pointer to an open database block
479 key the key of the record to be deleted
481 Returns: the yield of the underlying dbm or db "delete" function.
485 dbfn_delete(open_db *dbblock, const uschar *key)
487 int klen = Ustrlen(key) + 1;
488 uschar * key_copy = store_get(klen, key);
489 EXIM_DATUM key_datum;
491 memcpy(key_copy, key, klen);
492 exim_datum_init(&key_datum); /* Some DBM libraries require clearing */
493 exim_datum_data_set(&key_datum, key_copy);
494 exim_datum_size_set(&key_datum, klen);
495 return exim_dbdel(dbblock->dbptr, &key_datum);
498 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
502 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
503 /*************************************************
504 * Scan the keys of a database file *
505 *************************************************/
509 dbblock a pointer to an open database block
510 start TRUE if starting a new scan
511 FALSE if continuing with the current scan
512 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
513 that use the notion of a cursor
515 Returns: the next *key* (nul-terminated) from the file, or
516 NULL if there are no more
520 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
522 EXIM_DATUM key_datum, value_datum;
525 /* Some dbm require an initialization */
527 if (start) *cursor = exim_dbcreate_cursor(dbblock->dbptr);
529 exim_datum_init(&key_datum); /* Some DBM libraries require the datum */
530 exim_datum_init(&value_datum); /* to be cleared before use. */
532 yield = exim_dbscan(dbblock->dbptr, &key_datum, &value_datum, start, *cursor)
533 ? US exim_datum_data_get(&key_datum) : NULL;
535 /* Some dbm require a termination */
537 if (!yield) exim_dbdelete_cursor(*cursor);
540 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
545 /*************************************************
546 * The exim_dumpdb main program *
547 *************************************************/
550 main(int argc, char **cargv)
557 uschar **argv = USS cargv;
558 uschar keybuffer[1024];
561 options(argc, argv, US"dumpdb", US"kz");
563 /* Check the arguments, and open the database */
565 dbdata_type = check_args(argc, argv, US"dumpdb", US" [-z] [-k]");
566 argc -= optind; argv += optind;
567 spool_directory = argv[0];
569 if (!(dbm = dbfn_open(argv[1], O_RDONLY, &dbblock, FALSE, TRUE)))
572 /* Scan the file, formatting the information for each entry. Note
573 that data is returned in a malloc'ed block, in order that it be
574 correctly aligned. */
576 for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
578 key = dbfn_scan(dbm, FALSE, &cursor))
582 dbdata_callout_cache *callout;
583 dbdata_ratelimit *ratelimit;
584 dbdata_ratelimit_unique *rate_unique;
585 dbdata_tls_session *session;
590 uschar name[MESSAGE_ID_LENGTH + 1];
592 rmark reset_point = store_mark();
594 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
595 which might change. */
597 if (Ustrlen(key) > sizeof(keybuffer) - 1)
599 printf("**** Overlong key encountered: %s\n", key);
602 Ustrcpy(keybuffer, key);
605 printf(" %s\n", keybuffer);
606 else if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
607 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
608 "was not found in the file - something is wrong!\n",
611 /* Note: don't use print_time more than once in one statement, since
612 it uses a single buffer. */
617 retry = (dbdata_retry *)value;
618 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
619 retry->more_errno, retry->text,
620 print_time(retry->first_failed));
621 printf("%s ", print_time(retry->last_try));
622 printf("%s %s\n", print_time(retry->next_try),
623 retry->expired ? "*" : "");
627 wait = (dbdata_wait *)value;
628 printf("%s ", keybuffer);
630 name[MESSAGE_ID_LENGTH] = 0;
632 /* Leave corrupt records alone */
633 if (wait->count > WAIT_NAME_MAX)
636 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
637 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
638 wait->count = WAIT_NAME_MAX;
639 yield = count_bad = 1;
641 for (int i = 1; i <= wait->count; i++)
643 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
644 if (count_bad && name[0] == 0) break;
645 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
646 Ustrspn(name, "0123456789"
647 "abcdefghijklmnopqrstuvwxyz"
648 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
651 "**** Data for %s corrupted: bad character in message id\n",
653 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
654 fprintf(stderr, "%02x ", name[j]);
655 fprintf(stderr, "\n");
660 t += MESSAGE_ID_LENGTH;
666 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
671 callout = (dbdata_callout_cache *)value;
673 /* New-style address record */
675 if (length == sizeof(dbdata_callout_cache_address))
677 printf("%s %s callout=%s\n",
678 print_time(((dbdata_generic *)value)->time_stamp),
680 print_cache(callout->result));
683 /* New-style domain record */
685 else if (length == sizeof(dbdata_callout_cache))
687 printf("%s %s callout=%s postmaster=%s",
688 print_time(((dbdata_generic *)value)->time_stamp),
690 print_cache(callout->result),
691 print_cache(callout->postmaster_result));
692 if (callout->postmaster_result != ccache_unknown)
693 printf(" (%s)", print_time(callout->postmaster_stamp));
694 printf(" random=%s", print_cache(callout->random_result));
695 if (callout->random_result != ccache_unknown)
696 printf(" (%s)", print_time(callout->random_stamp));
703 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
705 ratelimit = (dbdata_ratelimit *)value;
706 rate_unique = (dbdata_ratelimit_unique *)value;
707 printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
708 print_time(ratelimit->time_stamp),
709 ratelimit->time_usec, ratelimit->rate,
710 print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
715 ratelimit = (dbdata_ratelimit *)value;
716 printf("%s.%06d rate: %10.3f key: %s\n",
717 print_time(ratelimit->time_stamp),
718 ratelimit->time_usec, ratelimit->rate,
724 session = (dbdata_tls_session *)value;
725 printf(" %s %.*s\n", keybuffer, length, session->session);
729 seen = (dbdata_seen *)value;
730 printf("%s\t%s\n", keybuffer, print_time(seen->time_stamp));
733 store_reset(reset_point);
740 #endif /* EXIM_DUMPDB */
746 /*************************************************
747 * The exim_fixdb main program *
748 *************************************************/
750 /* In order not to hold the database lock any longer than is necessary, each
751 operation on the database uses a separate open/close call. This is expensive,
752 but then using this utility is not expected to be very common. Its main use is
753 to provide a way of patching up hints databases in order to run tests.
758 This causes the data from the given record to be displayed, or "not found"
759 to be output. Note that in the retry database, destination names are
760 preceded by R: or T: for router or transport retry info.
763 This causes the given record to be deleted or "not found" to be output.
765 (3) <record name> <field number> <value>
766 This sets the given value into the given field, identified by a number
767 which is output by the display command. Not all types of record can
771 This exits from exim_fixdb.
773 If the record name is omitted from (2) or (3), the previously used record name
778 main(int argc, char **cargv)
781 uschar **argv = USS cargv;
788 options(argc, argv, US"fixdb", US"z");
789 name[0] = 0; /* No name set */
791 /* Sort out the database type, verify what we are working on and then process
794 dbdata_type = check_args(argc, argv, US"fixdb", US" [-z]");
795 argc -= optind; argv += optind;
796 spool_directory = argv[0];
799 printf("Modifying Exim hints database %s/db/%s\n", spool_directory, aname);
801 for(; (reset_point = store_mark()); store_reset(reset_point))
808 dbdata_callout_cache *callout;
809 dbdata_ratelimit *ratelimit;
810 dbdata_ratelimit_unique *rate_unique;
811 dbdata_tls_session *session;
814 uschar field[256], value[256];
817 if (Ufgets(buffer, 256, stdin) == NULL) break;
819 buffer[Ustrlen(buffer)-1] = 0;
820 field[0] = value[0] = 0;
822 /* If the buffer contains just one digit, or just consists of "d", use the
823 previous name for an update. */
825 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
826 || Ustrcmp(buffer, "d") == 0)
830 printf("No previous record name is set\n");
833 (void)sscanf(CS buffer, "%s %s", field, value);
838 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
841 /* Handle an update request */
847 if (!(dbm = dbfn_open(aname, O_RDWR, &dbblock, FALSE, TRUE)))
850 if (Ustrcmp(field, "d") == 0)
852 if (value[0] != 0) printf("unexpected value after \"d\"\n");
853 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
854 "not found" : "deleted");
859 else if (isdigit((uschar)field[0]))
861 int fieldno = Uatoi(field);
864 printf("value missing\n");
870 record = dbfn_read_with_length(dbm, name, &oldlength);
871 if (record == NULL) printf("not found\n"); else
874 /*int length = 0; Stops compiler warning */
879 retry = (dbdata_retry *)record;
880 /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
884 case 0: retry->basic_errno = Uatoi(value);
886 case 1: retry->more_errno = Uatoi(value);
888 case 2: if ((tt = read_time(value)) > 0) retry->first_failed = tt;
889 else printf("bad time value\n");
891 case 3: if ((tt = read_time(value)) > 0) retry->last_try = tt;
892 else printf("bad time value\n");
894 case 4: if ((tt = read_time(value)) > 0) retry->next_try = tt;
895 else printf("bad time value\n");
897 case 5: if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
898 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
899 else printf("\"yes\" or \"no\" expected=n");
901 default: printf("unknown field number\n");
908 printf("Can't change contents of wait database record\n");
912 printf("Can't change contents of misc database record\n");
916 callout = (dbdata_callout_cache *)record;
917 /* length = sizeof(dbdata_callout_cache); */
920 case 0: callout->result = Uatoi(value);
922 case 1: callout->postmaster_result = Uatoi(value);
924 case 2: callout->random_result = Uatoi(value);
926 default: printf("unknown field number\n");
933 ratelimit = (dbdata_ratelimit *)record;
936 case 0: if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
937 else printf("bad time value\n");
939 case 1: ratelimit->time_usec = Uatoi(value);
941 case 2: ratelimit->rate = Ustrtod(value, NULL);
943 case 3: if (Ustrstr(name, "/unique/") != NULL
944 && oldlength >= sizeof(dbdata_ratelimit_unique))
946 rate_unique = (dbdata_ratelimit_unique *)record;
947 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
948 else printf("bad time value\n");
951 /* else fall through */
953 case 5: if (Ustrstr(name, "/unique/") != NULL
954 && oldlength >= sizeof(dbdata_ratelimit_unique))
962 md5_end(&md5info, value, Ustrlen(value), md5sum);
963 hash = md5sum[0] << 0 | md5sum[1] << 8
964 | md5sum[2] << 16 | md5sum[3] << 24;
965 hinc = md5sum[4] << 0 | md5sum[5] << 8
966 | md5sum[6] << 16 | md5sum[7] << 24;
967 rate_unique = (dbdata_ratelimit_unique *)record;
969 for (unsigned n = 0; n < 8; n++, hash += hinc)
971 int bit = 1 << (hash % 8);
972 int byte = (hash / 8) % rate_unique->bloom_size;
973 if ((rate_unique->bloom[byte] & bit) == 0)
976 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
980 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
983 /* else fall through */
984 default: printf("unknown field number\n");
991 printf("Can't change contents of tls database record\n");
995 dbfn_write(dbm, name, record, oldlength);
1002 printf("field number or d expected\n");
1007 if (!verify) continue;
1010 /* The "name" q causes an exit */
1012 else if (Ustrcmp(name, "q") == 0) return 0;
1014 /* Handle a read request, or verify after an update. */
1016 if (!(dbm = dbfn_open(aname, O_RDONLY, &dbblock, FALSE, TRUE)))
1019 if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
1021 printf("record %s not found\n", name);
1027 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
1031 retry = (dbdata_retry *)record;
1032 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
1033 printf("1 extra data: %d\n", retry->more_errno);
1034 printf("2 first failed: %s\n", print_time(retry->first_failed));
1035 printf("3 last try: %s\n", print_time(retry->last_try));
1036 printf("4 next try: %s\n", print_time(retry->next_try));
1037 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
1041 wait = (dbdata_wait *)record;
1043 printf("Sequence: %d\n", wait->sequence);
1044 if (wait->count > WAIT_NAME_MAX)
1046 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
1047 wait->count, WAIT_NAME_MAX);
1048 wait->count = WAIT_NAME_MAX;
1051 for (int i = 1; i <= wait->count; i++)
1053 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1054 value[MESSAGE_ID_LENGTH] = 0;
1055 if (count_bad && value[0] == 0) break;
1056 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1057 Ustrspn(value, "0123456789"
1058 "abcdefghijklmnopqrstuvwxyz"
1059 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1061 printf("\n**** Data corrupted: bad character in message id ****\n");
1062 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
1063 printf("%02x ", value[j]);
1067 printf("%s ", value);
1068 t += MESSAGE_ID_LENGTH;
1077 callout = (dbdata_callout_cache *)record;
1078 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1080 if (oldlength > sizeof(dbdata_callout_cache_address))
1082 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1083 callout->postmaster_result);
1084 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1085 callout->random_result);
1089 case type_ratelimit:
1090 ratelimit = (dbdata_ratelimit *)record;
1091 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1092 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1093 printf("2 sender rate: % .3f\n", ratelimit->rate);
1094 if (Ustrstr(name, "/unique/") != NULL
1095 && oldlength >= sizeof(dbdata_ratelimit_unique))
1097 rate_unique = (dbdata_ratelimit_unique *)record;
1098 printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1099 printf("4 test filter membership\n");
1100 printf("5 add element to filter\n");
1105 session = (dbdata_tls_session *)value;
1106 printf("0 time stamp: %s\n", print_time(session->time_stamp));
1107 printf("1 session: .%s\n", session->session);
1112 /* The database is closed after each request */
1121 #endif /* EXIM_FIXDB */
1126 /*************************************************
1127 * The exim_tidydb main program *
1128 *************************************************/
1131 /* Utility program to tidy the contents of an exim database file. There is one
1134 -t <time> expiry time for old records - default 30 days
1136 For backwards compatibility, an -f option is recognized and ignored. (It used
1137 to request a "full" tidy. This version always does the whole job.) */
1140 typedef struct key_item {
1141 struct key_item *next;
1147 main(int argc, char **cargv)
1149 struct stat statbuf;
1150 int maxkeep = 30 * 24 * 60 * 60;
1151 int dbdata_type, i, oldest, path_len;
1152 key_item *keychain = NULL;
1156 EXIM_CURSOR *cursor;
1157 uschar **argv = USS cargv;
1163 /* Scan the options */
1165 for (i = 1; i < argc; i++)
1167 if (argv[i][0] != '-') break;
1168 if (Ustrcmp(argv[i], "-f") == 0) continue;
1169 if (Ustrcmp(argv[i], "-t") == 0)
1177 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1178 (void)sscanf(CS s, "%d%n", &value, &count);
1182 case 'w': value *= 7;
1183 case 'd': value *= 24;
1184 case 'h': value *= 60;
1185 case 'm': value *= 60;
1188 default: usage(US"tidydb", US" [-t <time>]");
1193 else usage(US"tidydb", US" [-t <time>]");
1196 /* Adjust argument values and process arguments */
1201 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1203 /* Compute the oldest keep time, verify what we are doing, and open the
1206 oldest = time(NULL) - maxkeep;
1207 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1209 spool_directory = argv[1];
1210 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
1213 /* Prepare for building file names */
1215 sprintf(CS buffer, "%s/input/", argv[1]);
1216 path_len = Ustrlen(buffer);
1219 /* It appears, by experiment, that it is a bad idea to make changes
1220 to the file while scanning it. Pity the man page doesn't warn you about that.
1221 Therefore, we scan and build a list of all the keys. Then we use that to
1222 read the records and possibly update them. */
1224 for (key = dbfn_scan(dbm, TRUE, &cursor);
1226 key = dbfn_scan(dbm, FALSE, &cursor))
1228 key_item * k = store_get(sizeof(key_item) + Ustrlen(key), key);
1231 Ustrcpy(k->key, key);
1234 /* Now scan the collected keys and operate on the records, resetting
1235 the store each time round. */
1237 for (; keychain && (reset_point = store_mark()); store_reset(reset_point))
1239 dbdata_generic *value;
1241 key = keychain->key;
1242 keychain = keychain->next;
1243 value = dbfn_read_with_length(dbm, key, NULL);
1245 /* A continuation record may have been deleted or renamed already, so
1246 non-existence is not serious. */
1248 if (!value) continue;
1250 /* Delete if too old */
1252 if (value->time_stamp < oldest)
1254 printf("deleted %s (too old)\n", key);
1255 dbfn_delete(dbm, key);
1259 /* Do database-specific tidying for wait databases, and message-
1260 specific tidying for the retry database. */
1262 if (dbdata_type == type_wait)
1264 dbdata_wait *wait = (dbdata_wait *)value;
1265 BOOL update = FALSE;
1267 /* Leave corrupt records alone */
1269 if (wait->time_stamp > time(NULL))
1271 printf("**** Data for '%s' corrupted\n time in future: %s\n",
1272 key, print_time(((dbdata_generic *)value)->time_stamp));
1275 if (wait->count > WAIT_NAME_MAX)
1277 printf("**** Data for '%s' corrupted\n count=%d=0x%x max=%d\n",
1278 key, wait->count, wait->count, WAIT_NAME_MAX);
1281 if (wait->sequence > WAIT_CONT_MAX)
1283 printf("**** Data for '%s' corrupted\n sequence=%d=0x%x max=%d\n",
1284 key, wait->sequence, wait->sequence, WAIT_CONT_MAX);
1288 /* Record over 1 year old; just remove it */
1290 if (wait->time_stamp < time(NULL) - 365*24*60*60)
1292 dbfn_delete(dbm, key);
1293 printf("deleted %s (too old)\n", key);
1297 /* Loop for renamed continuation records. For each message id,
1298 check to see if the message exists, and if not, remove its entry
1299 from the record. Because of the possibility of split input directories,
1300 we must look in both possible places for a -D file. */
1304 int length = wait->count * MESSAGE_ID_LENGTH;
1306 for (int offset = length - MESSAGE_ID_LENGTH;
1307 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1309 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1310 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1312 if (Ustat(buffer, &statbuf) != 0)
1314 buffer[path_len] = wait->text[offset+5];
1315 buffer[path_len+1] = '/';
1316 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1317 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1319 if (Ustat(buffer, &statbuf) != 0)
1321 int left = length - offset - MESSAGE_ID_LENGTH;
1322 if (left > 0) Ustrncpy(wait->text + offset,
1323 wait->text + offset + MESSAGE_ID_LENGTH, left);
1325 length -= MESSAGE_ID_LENGTH;
1331 /* If record is empty and the main record, either delete it or rename
1332 the next continuation, repeating if that is also empty. */
1334 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1336 while (wait->count == 0 && wait->sequence > 0)
1339 dbdata_generic *newvalue;
1340 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1341 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1342 if (newvalue != NULL)
1345 wait = (dbdata_wait *)newvalue;
1346 dbfn_delete(dbm, newkey);
1347 printf("renamed %s\n", newkey);
1350 else wait->sequence--;
1353 /* If we have ended up with an empty main record, delete it
1354 and break the loop. Otherwise the new record will be scanned. */
1356 if (wait->count == 0 && wait->sequence == 0)
1358 dbfn_delete(dbm, key);
1359 printf("deleted %s (empty)\n", key);
1365 /* If not an empty main record, break the loop */
1370 /* Re-write the record if required */
1374 printf("updated %s\n", key);
1375 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1376 wait->count * MESSAGE_ID_LENGTH);
1380 /* If a retry record's key ends with a message-id, check that that message
1381 still exists; if not, remove this record. */
1383 else if (dbdata_type == type_retry)
1386 int len = Ustrlen(key);
1388 if (len < MESSAGE_ID_LENGTH + 1) continue;
1389 id = key + len - MESSAGE_ID_LENGTH - 1;
1390 if (*id++ != ':') continue;
1392 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1393 if (i == 6 || i == 13)
1394 { if (id[i] != '-') break; }
1396 { if (!isalnum(id[i])) break; }
1397 if (i < MESSAGE_ID_LENGTH) continue;
1399 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1400 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1402 if (Ustat(buffer, &statbuf) != 0)
1404 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1405 if (Ustat(buffer, &statbuf) != 0)
1407 dbfn_delete(dbm, key);
1408 printf("deleted %s (no message)\n", key);
1415 printf("Tidying complete\n");
1419 #endif /* EXIM_TIDYDB */
1421 /* End of exim_dbutil.c */