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_WRONLY|O_RDWR)) == 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;
301 if no lockfile we infer there is no database and error out. We open the
302 lockfile using the r/w mode requested for the DB, users lacking permission
303 for the DB access mode will error out here. */
305 if ( asprintf(CSS &dirname, "%s/db", spool_directory) < 0
306 || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
309 if ((dbblock->lockfd = Uopen(filename, flags, 0)) < 0)
311 printf("** Failed to open database lock file %s: %s\n", filename,
316 /* Now we must get a lock on the opened lock file; do this with a blocking
317 lock that times out. */
319 lock_data.l_type = read_only ? F_RDLCK : F_WRLCK;
320 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
322 sigalrm_seen = FALSE;
323 os_non_restarting_signal(SIGALRM, sigalrm_handler);
324 ALARM(EXIMDB_LOCK_TIMEOUT);
325 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
328 if (sigalrm_seen) errno = ETIMEDOUT;
331 printf("** Failed to get %s lock for %s: %s",
332 read_only ? "read" : "write",
334 errno == ETIMEDOUT ? "timed out" : strerror(errno));
335 (void)close(dbblock->lockfd);
339 /* At this point we have an opened and locked separate lock file, that is,
340 exclusive access to the database, so we can go ahead and open it. */
342 if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
344 if (flags & O_RDWR) flags |= O_CREAT;
346 if (!(dbblock->dbptr = exim_dbopen(filename, dirname, flags, 0)))
348 printf("** Failed to open DBM file %s for %s:\n %s%s\n", filename,
349 read_only? "reading" : "writing", strerror(errno),
351 " (or Berkeley DB error while opening)"
356 (void)close(dbblock->lockfd);
366 /*************************************************
367 * Unlock and close a database file *
368 *************************************************/
370 /* Closing a file automatically unlocks it, so after closing the database, just
373 Argument: a pointer to an open database block
378 dbfn_close(open_db *dbblock)
380 exim_dbclose(dbblock->dbptr);
381 (void)close(dbblock->lockfd);
387 /*************************************************
388 * Read from database file *
389 *************************************************/
391 /* Passing back the pointer unchanged is useless, because there is no guarantee
392 of alignment. Since all the records used by Exim need to be properly aligned to
393 pick out the timestamps, etc., do the copying centrally here.
396 dbblock a pointer to an open database block
397 key the key of the record to be read
398 length where to put the length (or NULL if length not wanted). Includes overhead.
400 Returns: a pointer to the retrieved record, or
401 NULL if the record is not found
405 dbfn_read_with_length(open_db * dbblock, const uschar * key, int * length)
408 EXIM_DATUM key_datum, result_datum;
409 int klen = Ustrlen(key) + 1;
410 uschar * key_copy = store_get(klen, key);
413 memcpy(key_copy, key, klen);
415 exim_datum_init(&key_datum); /* Some DBM libraries require the datum */
416 exim_datum_init(&result_datum); /* to be cleared before use. */
417 exim_datum_data_set(&key_datum, key_copy);
418 exim_datum_size_set(&key_datum, klen);
420 if (!exim_dbget(dbblock->dbptr, &key_datum, &result_datum)) return NULL;
422 /* Assume for now that anything stored could have been tainted. Properly
423 we should store the taint status along with the data. */
425 dlen = exim_datum_size_get(&result_datum);
426 yield = store_get(dlen, GET_TAINTED);
427 memcpy(yield, exim_datum_data_get(&result_datum), dlen);
428 if (length) *length = dlen;
430 exim_datum_free(&result_datum); /* Some DBM libs require freeing */
436 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
438 /*************************************************
439 * Write to database file *
440 *************************************************/
444 dbblock a pointer to an open database block
445 key the key of the record to be written
446 ptr a pointer to the record to be written
447 length the length of the record to be written
449 Returns: the yield of the underlying dbm or db "write" function. If this
450 is dbm, the value is zero for OK.
454 dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
456 EXIM_DATUM key_datum, value_datum;
457 dbdata_generic *gptr = (dbdata_generic *)ptr;
458 int klen = Ustrlen(key) + 1;
459 uschar * key_copy = store_get(klen, key);
461 memcpy(key_copy, key, klen);
462 gptr->time_stamp = time(NULL);
464 exim_datum_init(&key_datum); /* Some DBM libraries require the datum */
465 exim_datum_init(&value_datum); /* to be cleared before use. */
466 exim_datum_data_set(&key_datum, key_copy);
467 exim_datum_size_set(&key_datum, klen);
468 exim_datum_data_set(&value_datum, ptr);
469 exim_datum_size_set(&value_datum, length);
470 return exim_dbput(dbblock->dbptr, &key_datum, &value_datum);
475 /*************************************************
476 * Delete record from database file *
477 *************************************************/
481 dbblock a pointer to an open database block
482 key the key of the record to be deleted
484 Returns: the yield of the underlying dbm or db "delete" function.
488 dbfn_delete(open_db *dbblock, const uschar *key)
490 int klen = Ustrlen(key) + 1;
491 uschar * key_copy = store_get(klen, key);
492 EXIM_DATUM key_datum;
494 memcpy(key_copy, key, klen);
495 exim_datum_init(&key_datum); /* Some DBM libraries require clearing */
496 exim_datum_data_set(&key_datum, key_copy);
497 exim_datum_size_set(&key_datum, klen);
498 return exim_dbdel(dbblock->dbptr, &key_datum);
501 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
505 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
506 /*************************************************
507 * Scan the keys of a database file *
508 *************************************************/
512 dbblock a pointer to an open database block
513 start TRUE if starting a new scan
514 FALSE if continuing with the current scan
515 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
516 that use the notion of a cursor
518 Returns: the next *key* (nul-terminated) from the file, or
519 NULL if there are no more
523 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
525 EXIM_DATUM key_datum, value_datum;
528 /* Some dbm require an initialization */
530 if (start) *cursor = exim_dbcreate_cursor(dbblock->dbptr);
532 exim_datum_init(&key_datum); /* Some DBM libraries require the datum */
533 exim_datum_init(&value_datum); /* to be cleared before use. */
535 yield = exim_dbscan(dbblock->dbptr, &key_datum, &value_datum, start, *cursor)
536 ? US exim_datum_data_get(&key_datum) : NULL;
538 /* Some dbm require a termination */
540 if (!yield) exim_dbdelete_cursor(*cursor);
543 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
548 /*************************************************
549 * The exim_dumpdb main program *
550 *************************************************/
553 main(int argc, char **cargv)
560 uschar **argv = USS cargv;
561 uschar keybuffer[1024];
564 options(argc, argv, US"dumpdb", US"kz");
566 /* Check the arguments, and open the database */
568 dbdata_type = check_args(argc, argv, US"dumpdb", US" [-z] [-k]");
569 argc -= optind; argv += optind;
570 spool_directory = argv[0];
572 if (!(dbm = dbfn_open(argv[1], O_RDONLY, &dbblock, FALSE, TRUE)))
575 /* Scan the file, formatting the information for each entry. Note
576 that data is returned in a malloc'ed block, in order that it be
577 correctly aligned. */
579 for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
581 key = dbfn_scan(dbm, FALSE, &cursor))
585 dbdata_callout_cache *callout;
586 dbdata_ratelimit *ratelimit;
587 dbdata_ratelimit_unique *rate_unique;
588 dbdata_tls_session *session;
593 uschar name[MESSAGE_ID_LENGTH + 1];
595 rmark reset_point = store_mark();
597 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
598 which might change. */
600 if (Ustrlen(key) > sizeof(keybuffer) - 1)
602 printf("**** Overlong key encountered: %s\n", key);
605 Ustrcpy(keybuffer, key);
608 printf(" %s\n", keybuffer);
609 else if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
610 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
611 "was not found in the file - something is wrong!\n",
614 /* Note: don't use print_time more than once in one statement, since
615 it uses a single buffer. */
620 retry = (dbdata_retry *)value;
621 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
622 retry->more_errno, retry->text,
623 print_time(retry->first_failed));
624 printf("%s ", print_time(retry->last_try));
625 printf("%s %s\n", print_time(retry->next_try),
626 retry->expired ? "*" : "");
630 wait = (dbdata_wait *)value;
631 printf("%s ", keybuffer);
633 name[MESSAGE_ID_LENGTH] = 0;
635 /* Leave corrupt records alone */
636 if (wait->count > WAIT_NAME_MAX)
639 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
640 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
641 wait->count = WAIT_NAME_MAX;
642 yield = count_bad = 1;
644 for (int i = 1; i <= wait->count; i++)
646 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
647 if (count_bad && name[0] == 0) break;
648 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
649 Ustrspn(name, "0123456789"
650 "abcdefghijklmnopqrstuvwxyz"
651 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
654 "**** Data for %s corrupted: bad character in message id\n",
656 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
657 fprintf(stderr, "%02x ", name[j]);
658 fprintf(stderr, "\n");
663 t += MESSAGE_ID_LENGTH;
669 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
674 callout = (dbdata_callout_cache *)value;
676 /* New-style address record */
678 if (length == sizeof(dbdata_callout_cache_address))
680 printf("%s %s callout=%s\n",
681 print_time(((dbdata_generic *)value)->time_stamp),
683 print_cache(callout->result));
686 /* New-style domain record */
688 else if (length == sizeof(dbdata_callout_cache))
690 printf("%s %s callout=%s postmaster=%s",
691 print_time(((dbdata_generic *)value)->time_stamp),
693 print_cache(callout->result),
694 print_cache(callout->postmaster_result));
695 if (callout->postmaster_result != ccache_unknown)
696 printf(" (%s)", print_time(callout->postmaster_stamp));
697 printf(" random=%s", print_cache(callout->random_result));
698 if (callout->random_result != ccache_unknown)
699 printf(" (%s)", print_time(callout->random_stamp));
706 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
708 ratelimit = (dbdata_ratelimit *)value;
709 rate_unique = (dbdata_ratelimit_unique *)value;
710 printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
711 print_time(ratelimit->time_stamp),
712 ratelimit->time_usec, ratelimit->rate,
713 print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
718 ratelimit = (dbdata_ratelimit *)value;
719 printf("%s.%06d rate: %10.3f key: %s\n",
720 print_time(ratelimit->time_stamp),
721 ratelimit->time_usec, ratelimit->rate,
727 session = (dbdata_tls_session *)value;
728 printf(" %s %.*s\n", keybuffer, length, session->session);
732 seen = (dbdata_seen *)value;
733 printf("%s\t%s\n", keybuffer, print_time(seen->time_stamp));
736 store_reset(reset_point);
743 #endif /* EXIM_DUMPDB */
749 /*************************************************
750 * The exim_fixdb main program *
751 *************************************************/
753 /* In order not to hold the database lock any longer than is necessary, each
754 operation on the database uses a separate open/close call. This is expensive,
755 but then using this utility is not expected to be very common. Its main use is
756 to provide a way of patching up hints databases in order to run tests.
761 This causes the data from the given record to be displayed, or "not found"
762 to be output. Note that in the retry database, destination names are
763 preceded by R: or T: for router or transport retry info.
766 This causes the given record to be deleted or "not found" to be output.
768 (3) <record name> <field number> <value>
769 This sets the given value into the given field, identified by a number
770 which is output by the display command. Not all types of record can
774 This exits from exim_fixdb.
776 If the record name is omitted from (2) or (3), the previously used record name
781 main(int argc, char **cargv)
784 uschar **argv = USS cargv;
791 options(argc, argv, US"fixdb", US"z");
792 name[0] = 0; /* No name set */
794 /* Sort out the database type, verify what we are working on and then process
797 dbdata_type = check_args(argc, argv, US"fixdb", US" [-z]");
798 argc -= optind; argv += optind;
799 spool_directory = argv[0];
802 printf("Modifying Exim hints database %s/db/%s\n", spool_directory, aname);
804 for(; (reset_point = store_mark()); store_reset(reset_point))
811 dbdata_callout_cache *callout;
812 dbdata_ratelimit *ratelimit;
813 dbdata_ratelimit_unique *rate_unique;
814 dbdata_tls_session *session;
817 uschar field[256], value[256];
820 if (Ufgets(buffer, 256, stdin) == NULL) break;
822 buffer[Ustrlen(buffer)-1] = 0;
823 field[0] = value[0] = 0;
825 /* If the buffer contains just one digit, or just consists of "d", use the
826 previous name for an update. */
828 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
829 || Ustrcmp(buffer, "d") == 0)
833 printf("No previous record name is set\n");
836 (void)sscanf(CS buffer, "%s %s", field, value);
841 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
844 /* Handle an update request */
850 if (!(dbm = dbfn_open(aname, O_RDWR, &dbblock, FALSE, TRUE)))
853 if (Ustrcmp(field, "d") == 0)
855 if (value[0] != 0) printf("unexpected value after \"d\"\n");
856 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
857 "not found" : "deleted");
862 else if (isdigit((uschar)field[0]))
864 int fieldno = Uatoi(field);
867 printf("value missing\n");
873 record = dbfn_read_with_length(dbm, name, &oldlength);
874 if (record == NULL) printf("not found\n"); else
877 /*int length = 0; Stops compiler warning */
882 retry = (dbdata_retry *)record;
883 /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
887 case 0: retry->basic_errno = Uatoi(value);
889 case 1: retry->more_errno = Uatoi(value);
891 case 2: if ((tt = read_time(value)) > 0) retry->first_failed = tt;
892 else printf("bad time value\n");
894 case 3: if ((tt = read_time(value)) > 0) retry->last_try = tt;
895 else printf("bad time value\n");
897 case 4: if ((tt = read_time(value)) > 0) retry->next_try = tt;
898 else printf("bad time value\n");
900 case 5: if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
901 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
902 else printf("\"yes\" or \"no\" expected=n");
904 default: printf("unknown field number\n");
911 printf("Can't change contents of wait database record\n");
915 printf("Can't change contents of misc database record\n");
919 callout = (dbdata_callout_cache *)record;
920 /* length = sizeof(dbdata_callout_cache); */
923 case 0: callout->result = Uatoi(value);
925 case 1: callout->postmaster_result = Uatoi(value);
927 case 2: callout->random_result = Uatoi(value);
929 default: printf("unknown field number\n");
936 ratelimit = (dbdata_ratelimit *)record;
939 case 0: if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
940 else printf("bad time value\n");
942 case 1: ratelimit->time_usec = Uatoi(value);
944 case 2: ratelimit->rate = Ustrtod(value, NULL);
946 case 3: if (Ustrstr(name, "/unique/") != NULL
947 && oldlength >= sizeof(dbdata_ratelimit_unique))
949 rate_unique = (dbdata_ratelimit_unique *)record;
950 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
951 else printf("bad time value\n");
954 /* else fall through */
956 case 5: if (Ustrstr(name, "/unique/") != NULL
957 && oldlength >= sizeof(dbdata_ratelimit_unique))
965 md5_end(&md5info, value, Ustrlen(value), md5sum);
966 hash = md5sum[0] << 0 | md5sum[1] << 8
967 | md5sum[2] << 16 | md5sum[3] << 24;
968 hinc = md5sum[4] << 0 | md5sum[5] << 8
969 | md5sum[6] << 16 | md5sum[7] << 24;
970 rate_unique = (dbdata_ratelimit_unique *)record;
972 for (unsigned n = 0; n < 8; n++, hash += hinc)
974 int bit = 1 << (hash % 8);
975 int byte = (hash / 8) % rate_unique->bloom_size;
976 if ((rate_unique->bloom[byte] & bit) == 0)
979 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
983 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
986 /* else fall through */
987 default: printf("unknown field number\n");
994 printf("Can't change contents of tls database record\n");
998 dbfn_write(dbm, name, record, oldlength);
1005 printf("field number or d expected\n");
1010 if (!verify) continue;
1013 /* The "name" q causes an exit */
1015 else if (Ustrcmp(name, "q") == 0) return 0;
1017 /* Handle a read request, or verify after an update. */
1019 if (!(dbm = dbfn_open(aname, O_RDONLY, &dbblock, FALSE, TRUE)))
1022 if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
1024 printf("record %s not found\n", name);
1030 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
1034 retry = (dbdata_retry *)record;
1035 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
1036 printf("1 extra data: %d\n", retry->more_errno);
1037 printf("2 first failed: %s\n", print_time(retry->first_failed));
1038 printf("3 last try: %s\n", print_time(retry->last_try));
1039 printf("4 next try: %s\n", print_time(retry->next_try));
1040 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
1044 wait = (dbdata_wait *)record;
1046 printf("Sequence: %d\n", wait->sequence);
1047 if (wait->count > WAIT_NAME_MAX)
1049 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
1050 wait->count, WAIT_NAME_MAX);
1051 wait->count = WAIT_NAME_MAX;
1054 for (int i = 1; i <= wait->count; i++)
1056 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1057 value[MESSAGE_ID_LENGTH] = 0;
1058 if (count_bad && value[0] == 0) break;
1059 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1060 Ustrspn(value, "0123456789"
1061 "abcdefghijklmnopqrstuvwxyz"
1062 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1064 printf("\n**** Data corrupted: bad character in message id ****\n");
1065 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
1066 printf("%02x ", value[j]);
1070 printf("%s ", value);
1071 t += MESSAGE_ID_LENGTH;
1080 callout = (dbdata_callout_cache *)record;
1081 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1083 if (oldlength > sizeof(dbdata_callout_cache_address))
1085 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1086 callout->postmaster_result);
1087 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1088 callout->random_result);
1092 case type_ratelimit:
1093 ratelimit = (dbdata_ratelimit *)record;
1094 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1095 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1096 printf("2 sender rate: % .3f\n", ratelimit->rate);
1097 if (Ustrstr(name, "/unique/") != NULL
1098 && oldlength >= sizeof(dbdata_ratelimit_unique))
1100 rate_unique = (dbdata_ratelimit_unique *)record;
1101 printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1102 printf("4 test filter membership\n");
1103 printf("5 add element to filter\n");
1108 session = (dbdata_tls_session *)value;
1109 printf("0 time stamp: %s\n", print_time(session->time_stamp));
1110 printf("1 session: .%s\n", session->session);
1115 /* The database is closed after each request */
1124 #endif /* EXIM_FIXDB */
1129 /*************************************************
1130 * The exim_tidydb main program *
1131 *************************************************/
1134 /* Utility program to tidy the contents of an exim database file. There is one
1137 -t <time> expiry time for old records - default 30 days
1139 For backwards compatibility, an -f option is recognized and ignored. (It used
1140 to request a "full" tidy. This version always does the whole job.) */
1143 typedef struct key_item {
1144 struct key_item *next;
1150 main(int argc, char **cargv)
1152 struct stat statbuf;
1153 int maxkeep = 30 * 24 * 60 * 60;
1154 int dbdata_type, i, oldest, path_len;
1155 key_item *keychain = NULL;
1159 EXIM_CURSOR *cursor;
1160 uschar **argv = USS cargv;
1166 /* Scan the options */
1168 for (i = 1; i < argc; i++)
1170 if (argv[i][0] != '-') break;
1171 if (Ustrcmp(argv[i], "-f") == 0) continue;
1172 if (Ustrcmp(argv[i], "-t") == 0)
1180 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1181 (void)sscanf(CS s, "%d%n", &value, &count);
1185 case 'w': value *= 7;
1186 case 'd': value *= 24;
1187 case 'h': value *= 60;
1188 case 'm': value *= 60;
1191 default: usage(US"tidydb", US" [-t <time>]");
1196 else usage(US"tidydb", US" [-t <time>]");
1199 /* Adjust argument values and process arguments */
1204 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1206 /* Compute the oldest keep time, verify what we are doing, and open the
1209 oldest = time(NULL) - maxkeep;
1210 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1212 spool_directory = argv[1];
1213 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
1216 /* Prepare for building file names */
1218 sprintf(CS buffer, "%s/input/", argv[1]);
1219 path_len = Ustrlen(buffer);
1222 /* It appears, by experiment, that it is a bad idea to make changes
1223 to the file while scanning it. Pity the man page doesn't warn you about that.
1224 Therefore, we scan and build a list of all the keys. Then we use that to
1225 read the records and possibly update them. */
1227 for (key = dbfn_scan(dbm, TRUE, &cursor);
1229 key = dbfn_scan(dbm, FALSE, &cursor))
1231 key_item * k = store_get(sizeof(key_item) + Ustrlen(key), key);
1234 Ustrcpy(k->key, key);
1237 /* Now scan the collected keys and operate on the records, resetting
1238 the store each time round. */
1240 for (; keychain && (reset_point = store_mark()); store_reset(reset_point))
1242 dbdata_generic *value;
1244 key = keychain->key;
1245 keychain = keychain->next;
1246 value = dbfn_read_with_length(dbm, key, NULL);
1248 /* A continuation record may have been deleted or renamed already, so
1249 non-existence is not serious. */
1251 if (!value) continue;
1253 /* Delete if too old */
1255 if (value->time_stamp < oldest)
1257 printf("deleted %s (too old)\n", key);
1258 dbfn_delete(dbm, key);
1262 /* Do database-specific tidying for wait databases, and message-
1263 specific tidying for the retry database. */
1265 if (dbdata_type == type_wait)
1267 dbdata_wait *wait = (dbdata_wait *)value;
1268 BOOL update = FALSE;
1270 /* Leave corrupt records alone */
1272 if (wait->time_stamp > time(NULL))
1274 printf("**** Data for '%s' corrupted\n time in future: %s\n",
1275 key, print_time(((dbdata_generic *)value)->time_stamp));
1278 if (wait->count > WAIT_NAME_MAX)
1280 printf("**** Data for '%s' corrupted\n count=%d=0x%x max=%d\n",
1281 key, wait->count, wait->count, WAIT_NAME_MAX);
1284 if (wait->sequence > WAIT_CONT_MAX)
1286 printf("**** Data for '%s' corrupted\n sequence=%d=0x%x max=%d\n",
1287 key, wait->sequence, wait->sequence, WAIT_CONT_MAX);
1291 /* Record over 1 year old; just remove it */
1293 if (wait->time_stamp < time(NULL) - 365*24*60*60)
1295 dbfn_delete(dbm, key);
1296 printf("deleted %s (too old)\n", key);
1300 /* Loop for renamed continuation records. For each message id,
1301 check to see if the message exists, and if not, remove its entry
1302 from the record. Because of the possibility of split input directories,
1303 we must look in both possible places for a -D file. */
1307 int length = wait->count * MESSAGE_ID_LENGTH;
1309 for (int offset = length - MESSAGE_ID_LENGTH;
1310 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1312 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1313 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1315 if (Ustat(buffer, &statbuf) != 0)
1317 buffer[path_len] = wait->text[offset+5];
1318 buffer[path_len+1] = '/';
1319 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1320 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1322 if (Ustat(buffer, &statbuf) != 0)
1324 int left = length - offset - MESSAGE_ID_LENGTH;
1325 if (left > 0) Ustrncpy(wait->text + offset,
1326 wait->text + offset + MESSAGE_ID_LENGTH, left);
1328 length -= MESSAGE_ID_LENGTH;
1334 /* If record is empty and the main record, either delete it or rename
1335 the next continuation, repeating if that is also empty. */
1337 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1339 while (wait->count == 0 && wait->sequence > 0)
1342 dbdata_generic *newvalue;
1343 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1344 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1345 if (newvalue != NULL)
1348 wait = (dbdata_wait *)newvalue;
1349 dbfn_delete(dbm, newkey);
1350 printf("renamed %s\n", newkey);
1353 else wait->sequence--;
1356 /* If we have ended up with an empty main record, delete it
1357 and break the loop. Otherwise the new record will be scanned. */
1359 if (wait->count == 0 && wait->sequence == 0)
1361 dbfn_delete(dbm, key);
1362 printf("deleted %s (empty)\n", key);
1368 /* If not an empty main record, break the loop */
1373 /* Re-write the record if required */
1377 printf("updated %s\n", key);
1378 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1379 wait->count * MESSAGE_ID_LENGTH);
1383 /* If a retry record's key ends with a message-id, check that that message
1384 still exists; if not, remove this record. */
1386 else if (dbdata_type == type_retry)
1389 int len = Ustrlen(key);
1391 if (len < MESSAGE_ID_LENGTH + 1) continue;
1392 id = key + len - MESSAGE_ID_LENGTH - 1;
1393 if (*id++ != ':') continue;
1395 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1396 if (i == 6 || i == 13)
1397 { if (id[i] != '-') break; }
1399 { if (!isalnum(id[i])) break; }
1400 if (i < MESSAGE_ID_LENGTH) continue;
1402 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1403 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1405 if (Ustat(buffer, &statbuf) != 0)
1407 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1408 if (Ustat(buffer, &statbuf) != 0)
1410 dbfn_delete(dbm, key);
1411 printf("deleted %s (no message)\n", key);
1418 printf("Tidying complete\n");
1422 #endif /* EXIM_TIDYDB */
1424 /* End of exim_dbutil.c */