1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) The Exim Maintainers 2020 - 2023 */
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(uschar *name, int flags, open_db *dbblock, BOOL lof, BOOL panic)
293 struct flock lock_data;
294 BOOL read_only = flags == O_RDONLY;
295 uschar * dirname, * filename;
297 /* The first thing to do is to open a separate file on which to lock. This
298 ensures that Exim has exclusive use of the database before it even tries to
299 open it. If there is a database, there should be a lock file in existence. */
301 #ifdef COMPILE_UTILITY
302 if ( asprintf(CSS &dirname, "%s/db", spool_directory) < 0
303 || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
306 dirname = string_sprintf("%s/db", spool_directory);
307 filename = string_sprintf("%s/%s.lockfile", dirname, name);
310 dbblock->lockfd = Uopen(filename, flags, 0);
311 if (dbblock->lockfd < 0)
313 printf("** Failed to open database lock file %s: %s\n", filename,
318 /* Now we must get a lock on the opened lock file; do this with a blocking
319 lock that times out. */
321 lock_data.l_type = read_only ? F_RDLCK : F_WRLCK;
322 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
324 sigalrm_seen = FALSE;
325 os_non_restarting_signal(SIGALRM, sigalrm_handler);
326 ALARM(EXIMDB_LOCK_TIMEOUT);
327 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
330 if (sigalrm_seen) errno = ETIMEDOUT;
333 printf("** Failed to get %s lock for %s: %s",
334 flags & O_WRONLY ? "write" : "read",
336 errno == ETIMEDOUT ? "timed out" : strerror(errno));
337 (void)close(dbblock->lockfd);
341 /* At this point we have an opened and locked separate lock file, that is,
342 exclusive access to the database, so we can go ahead and open it. */
344 #ifdef COMPILE_UTILITY
345 if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
347 filename = string_sprintf("%s/%s", dirname, name);
349 dbblock->dbptr = exim_dbopen(filename, dirname, flags, 0);
353 printf("** Failed to open DBM file %s for %s:\n %s%s\n", filename,
354 read_only? "reading" : "writing", strerror(errno),
356 " (or Berkeley DB error while opening)"
361 (void)close(dbblock->lockfd);
371 /*************************************************
372 * Unlock and close a database file *
373 *************************************************/
375 /* Closing a file automatically unlocks it, so after closing the database, just
378 Argument: a pointer to an open database block
383 dbfn_close(open_db *dbblock)
385 exim_dbclose(dbblock->dbptr);
386 (void)close(dbblock->lockfd);
392 /*************************************************
393 * Read from database file *
394 *************************************************/
396 /* Passing back the pointer unchanged is useless, because there is no guarantee
397 of alignment. Since all the records used by Exim need to be properly aligned to
398 pick out the timestamps, etc., do the copying centrally here.
401 dbblock a pointer to an open database block
402 key the key of the record to be read
403 length where to put the length (or NULL if length not wanted). Includes overhead.
405 Returns: a pointer to the retrieved record, or
406 NULL if the record is not found
410 dbfn_read_with_length(open_db * dbblock, const uschar * key, int * length)
413 EXIM_DATUM key_datum, result_datum;
414 int klen = Ustrlen(key) + 1;
415 uschar * key_copy = store_get(klen, key);
418 memcpy(key_copy, key, klen);
420 exim_datum_init(&key_datum); /* Some DBM libraries require the datum */
421 exim_datum_init(&result_datum); /* to be cleared before use. */
422 exim_datum_data_set(&key_datum, key_copy);
423 exim_datum_size_set(&key_datum, klen);
425 if (!exim_dbget(dbblock->dbptr, &key_datum, &result_datum)) return NULL;
427 /* Assume for now that anything stored could have been tainted. Properly
428 we should store the taint status along with the data. */
430 dlen = exim_datum_size_get(&result_datum);
431 yield = store_get(dlen, GET_TAINTED);
432 memcpy(yield, exim_datum_data_get(&result_datum), dlen);
433 if (length) *length = dlen;
435 exim_datum_free(&result_datum); /* Some DBM libs require freeing */
441 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
443 /*************************************************
444 * Write to database file *
445 *************************************************/
449 dbblock a pointer to an open database block
450 key the key of the record to be written
451 ptr a pointer to the record to be written
452 length the length of the record to be written
454 Returns: the yield of the underlying dbm or db "write" function. If this
455 is dbm, the value is zero for OK.
459 dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
461 EXIM_DATUM key_datum, value_datum;
462 dbdata_generic *gptr = (dbdata_generic *)ptr;
463 int klen = Ustrlen(key) + 1;
464 uschar * key_copy = store_get(klen, key);
466 memcpy(key_copy, key, klen);
467 gptr->time_stamp = time(NULL);
469 exim_datum_init(&key_datum); /* Some DBM libraries require the datum */
470 exim_datum_init(&value_datum); /* to be cleared before use. */
471 exim_datum_data_set(&key_datum, key_copy);
472 exim_datum_size_set(&key_datum, klen);
473 exim_datum_data_set(&value_datum, ptr);
474 exim_datum_size_set(&value_datum, length);
475 return exim_dbput(dbblock->dbptr, &key_datum, &value_datum);
480 /*************************************************
481 * Delete record from database file *
482 *************************************************/
486 dbblock a pointer to an open database block
487 key the key of the record to be deleted
489 Returns: the yield of the underlying dbm or db "delete" function.
493 dbfn_delete(open_db *dbblock, const uschar *key)
495 int klen = Ustrlen(key) + 1;
496 uschar * key_copy = store_get(klen, key);
497 EXIM_DATUM key_datum;
499 memcpy(key_copy, key, klen);
500 exim_datum_init(&key_datum); /* Some DBM libraries require clearing */
501 exim_datum_data_set(&key_datum, key_copy);
502 exim_datum_size_set(&key_datum, klen);
503 return exim_dbdel(dbblock->dbptr, &key_datum);
506 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
510 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
511 /*************************************************
512 * Scan the keys of a database file *
513 *************************************************/
517 dbblock a pointer to an open database block
518 start TRUE if starting a new scan
519 FALSE if continuing with the current scan
520 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
521 that use the notion of a cursor
523 Returns: the next record from the file, or
524 NULL if there are no more
528 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
530 EXIM_DATUM key_datum, value_datum;
533 /* Some dbm require an initialization */
535 if (start) *cursor = exim_dbcreate_cursor(dbblock->dbptr);
537 exim_datum_init(&key_datum); /* Some DBM libraries require the datum */
538 exim_datum_init(&value_datum); /* to be cleared before use. */
540 yield = exim_dbscan(dbblock->dbptr, &key_datum, &value_datum, start, *cursor)
541 ? US exim_datum_data_get(&key_datum) : NULL;
543 /* Some dbm require a termination */
545 if (!yield) exim_dbdelete_cursor(*cursor);
548 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
553 /*************************************************
554 * The exim_dumpdb main program *
555 *************************************************/
558 main(int argc, char **cargv)
565 uschar **argv = USS cargv;
566 uschar keybuffer[1024];
569 options(argc, argv, US"dumpdb", US"kz");
571 /* Check the arguments, and open the database */
573 dbdata_type = check_args(argc, argv, US"dumpdb", US" [-z] [-k]");
574 argc -= optind; argv += optind;
575 spool_directory = argv[0];
577 if (!(dbm = dbfn_open(argv[1], O_RDONLY, &dbblock, FALSE, TRUE)))
580 /* Scan the file, formatting the information for each entry. Note
581 that data is returned in a malloc'ed block, in order that it be
582 correctly aligned. */
584 for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
586 key = dbfn_scan(dbm, FALSE, &cursor))
590 dbdata_callout_cache *callout;
591 dbdata_ratelimit *ratelimit;
592 dbdata_ratelimit_unique *rate_unique;
593 dbdata_tls_session *session;
598 uschar name[MESSAGE_ID_LENGTH + 1];
600 rmark reset_point = store_mark();
602 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
603 which might change. */
605 if (Ustrlen(key) > sizeof(keybuffer) - 1)
607 printf("**** Overlong key encountered: %s\n", key);
610 Ustrcpy(keybuffer, key);
613 printf(" %s\n", keybuffer);
614 else if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
615 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
616 "was not found in the file - something is wrong!\n",
619 /* Note: don't use print_time more than once in one statement, since
620 it uses a single buffer. */
625 retry = (dbdata_retry *)value;
626 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
627 retry->more_errno, retry->text,
628 print_time(retry->first_failed));
629 printf("%s ", print_time(retry->last_try));
630 printf("%s %s\n", print_time(retry->next_try),
631 (retry->expired)? "*" : "");
635 wait = (dbdata_wait *)value;
636 printf("%s ", keybuffer);
638 name[MESSAGE_ID_LENGTH] = 0;
640 /* Leave corrupt records alone */
641 if (wait->count > WAIT_NAME_MAX)
644 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
645 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
646 wait->count = WAIT_NAME_MAX;
647 yield = count_bad = 1;
649 for (int i = 1; i <= wait->count; i++)
651 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
652 if (count_bad && name[0] == 0) break;
653 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
654 Ustrspn(name, "0123456789"
655 "abcdefghijklmnopqrstuvwxyz"
656 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
659 "**** Data for %s corrupted: bad character in message id\n",
661 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
662 fprintf(stderr, "%02x ", name[j]);
663 fprintf(stderr, "\n");
668 t += MESSAGE_ID_LENGTH;
674 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
679 callout = (dbdata_callout_cache *)value;
681 /* New-style address record */
683 if (length == sizeof(dbdata_callout_cache_address))
685 printf("%s %s callout=%s\n",
686 print_time(((dbdata_generic *)value)->time_stamp),
688 print_cache(callout->result));
691 /* New-style domain record */
693 else if (length == sizeof(dbdata_callout_cache))
695 printf("%s %s callout=%s postmaster=%s",
696 print_time(((dbdata_generic *)value)->time_stamp),
698 print_cache(callout->result),
699 print_cache(callout->postmaster_result));
700 if (callout->postmaster_result != ccache_unknown)
701 printf(" (%s)", print_time(callout->postmaster_stamp));
702 printf(" random=%s", print_cache(callout->random_result));
703 if (callout->random_result != ccache_unknown)
704 printf(" (%s)", print_time(callout->random_stamp));
711 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
713 ratelimit = (dbdata_ratelimit *)value;
714 rate_unique = (dbdata_ratelimit_unique *)value;
715 printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
716 print_time(ratelimit->time_stamp),
717 ratelimit->time_usec, ratelimit->rate,
718 print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
723 ratelimit = (dbdata_ratelimit *)value;
724 printf("%s.%06d rate: %10.3f key: %s\n",
725 print_time(ratelimit->time_stamp),
726 ratelimit->time_usec, ratelimit->rate,
732 session = (dbdata_tls_session *)value;
733 printf(" %s %.*s\n", keybuffer, length, session->session);
737 seen = (dbdata_seen *)value;
738 printf("%s\t%s\n", keybuffer, print_time(seen->time_stamp));
741 store_reset(reset_point);
748 #endif /* EXIM_DUMPDB */
754 /*************************************************
755 * The exim_fixdb main program *
756 *************************************************/
758 /* In order not to hold the database lock any longer than is necessary, each
759 operation on the database uses a separate open/close call. This is expensive,
760 but then using this utility is not expected to be very common. Its main use is
761 to provide a way of patching up hints databases in order to run tests.
766 This causes the data from the given record to be displayed, or "not found"
767 to be output. Note that in the retry database, destination names are
768 preceded by R: or T: for router or transport retry info.
771 This causes the given record to be deleted or "not found" to be output.
773 (3) <record name> <field number> <value>
774 This sets the given value into the given field, identified by a number
775 which is output by the display command. Not all types of record can
779 This exits from exim_fixdb.
781 If the record name is omitted from (2) or (3), the previously used record name
786 main(int argc, char **cargv)
789 uschar **argv = USS cargv;
796 options(argc, argv, US"fixdb", US"z");
797 name[0] = 0; /* No name set */
799 /* Sort out the database type, verify what we are working on and then process
802 dbdata_type = check_args(argc, argv, US"fixdb", US" [-z]");
803 argc -= optind; argv += optind;
804 spool_directory = argv[0];
807 printf("Modifying Exim hints database %s/db/%s\n", spool_directory, aname);
809 for(; (reset_point = store_mark()); store_reset(reset_point))
816 dbdata_callout_cache *callout;
817 dbdata_ratelimit *ratelimit;
818 dbdata_ratelimit_unique *rate_unique;
819 dbdata_tls_session *session;
822 uschar field[256], value[256];
825 if (Ufgets(buffer, 256, stdin) == NULL) break;
827 buffer[Ustrlen(buffer)-1] = 0;
828 field[0] = value[0] = 0;
830 /* If the buffer contains just one digit, or just consists of "d", use the
831 previous name for an update. */
833 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
834 || Ustrcmp(buffer, "d") == 0)
838 printf("No previous record name is set\n");
841 (void)sscanf(CS buffer, "%s %s", field, value);
846 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
849 /* Handle an update request */
855 if (!(dbm = dbfn_open(aname, O_RDWR, &dbblock, FALSE, TRUE)))
858 if (Ustrcmp(field, "d") == 0)
860 if (value[0] != 0) printf("unexpected value after \"d\"\n");
861 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
862 "not found" : "deleted");
867 else if (isdigit((uschar)field[0]))
869 int fieldno = Uatoi(field);
872 printf("value missing\n");
878 record = dbfn_read_with_length(dbm, name, &oldlength);
879 if (record == NULL) printf("not found\n"); else
882 /*int length = 0; Stops compiler warning */
887 retry = (dbdata_retry *)record;
888 /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
892 case 0: retry->basic_errno = Uatoi(value);
894 case 1: retry->more_errno = Uatoi(value);
896 case 2: if ((tt = read_time(value)) > 0) retry->first_failed = tt;
897 else printf("bad time value\n");
899 case 3: if ((tt = read_time(value)) > 0) retry->last_try = tt;
900 else printf("bad time value\n");
902 case 4: if ((tt = read_time(value)) > 0) retry->next_try = tt;
903 else printf("bad time value\n");
905 case 5: if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
906 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
907 else printf("\"yes\" or \"no\" expected=n");
909 default: printf("unknown field number\n");
916 printf("Can't change contents of wait database record\n");
920 printf("Can't change contents of misc database record\n");
924 callout = (dbdata_callout_cache *)record;
925 /* length = sizeof(dbdata_callout_cache); */
928 case 0: callout->result = Uatoi(value);
930 case 1: callout->postmaster_result = Uatoi(value);
932 case 2: callout->random_result = Uatoi(value);
934 default: printf("unknown field number\n");
941 ratelimit = (dbdata_ratelimit *)record;
944 case 0: if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
945 else printf("bad time value\n");
947 case 1: ratelimit->time_usec = Uatoi(value);
949 case 2: ratelimit->rate = Ustrtod(value, NULL);
951 case 3: if (Ustrstr(name, "/unique/") != NULL
952 && oldlength >= sizeof(dbdata_ratelimit_unique))
954 rate_unique = (dbdata_ratelimit_unique *)record;
955 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
956 else printf("bad time value\n");
959 /* else fall through */
961 case 5: if (Ustrstr(name, "/unique/") != NULL
962 && oldlength >= sizeof(dbdata_ratelimit_unique))
970 md5_end(&md5info, value, Ustrlen(value), md5sum);
971 hash = md5sum[0] << 0 | md5sum[1] << 8
972 | md5sum[2] << 16 | md5sum[3] << 24;
973 hinc = md5sum[4] << 0 | md5sum[5] << 8
974 | md5sum[6] << 16 | md5sum[7] << 24;
975 rate_unique = (dbdata_ratelimit_unique *)record;
977 for (unsigned n = 0; n < 8; n++, hash += hinc)
979 int bit = 1 << (hash % 8);
980 int byte = (hash / 8) % rate_unique->bloom_size;
981 if ((rate_unique->bloom[byte] & bit) == 0)
984 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
988 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
991 /* else fall through */
992 default: printf("unknown field number\n");
999 printf("Can't change contents of tls database record\n");
1003 dbfn_write(dbm, name, record, oldlength);
1010 printf("field number or d expected\n");
1015 if (!verify) continue;
1018 /* The "name" q causes an exit */
1020 else if (Ustrcmp(name, "q") == 0) return 0;
1022 /* Handle a read request, or verify after an update. */
1024 if (!(dbm = dbfn_open(aname, O_RDONLY, &dbblock, FALSE, TRUE)))
1027 if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
1029 printf("record %s not found\n", name);
1035 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
1039 retry = (dbdata_retry *)record;
1040 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
1041 printf("1 extra data: %d\n", retry->more_errno);
1042 printf("2 first failed: %s\n", print_time(retry->first_failed));
1043 printf("3 last try: %s\n", print_time(retry->last_try));
1044 printf("4 next try: %s\n", print_time(retry->next_try));
1045 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
1049 wait = (dbdata_wait *)record;
1051 printf("Sequence: %d\n", wait->sequence);
1052 if (wait->count > WAIT_NAME_MAX)
1054 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
1055 wait->count, WAIT_NAME_MAX);
1056 wait->count = WAIT_NAME_MAX;
1059 for (int i = 1; i <= wait->count; i++)
1061 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1062 value[MESSAGE_ID_LENGTH] = 0;
1063 if (count_bad && value[0] == 0) break;
1064 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1065 Ustrspn(value, "0123456789"
1066 "abcdefghijklmnopqrstuvwxyz"
1067 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1069 printf("\n**** Data corrupted: bad character in message id ****\n");
1070 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
1071 printf("%02x ", value[j]);
1075 printf("%s ", value);
1076 t += MESSAGE_ID_LENGTH;
1085 callout = (dbdata_callout_cache *)record;
1086 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1088 if (oldlength > sizeof(dbdata_callout_cache_address))
1090 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1091 callout->postmaster_result);
1092 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1093 callout->random_result);
1097 case type_ratelimit:
1098 ratelimit = (dbdata_ratelimit *)record;
1099 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1100 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1101 printf("2 sender rate: % .3f\n", ratelimit->rate);
1102 if (Ustrstr(name, "/unique/") != NULL
1103 && oldlength >= sizeof(dbdata_ratelimit_unique))
1105 rate_unique = (dbdata_ratelimit_unique *)record;
1106 printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1107 printf("4 test filter membership\n");
1108 printf("5 add element to filter\n");
1113 session = (dbdata_tls_session *)value;
1114 printf("0 time stamp: %s\n", print_time(session->time_stamp));
1115 printf("1 session: .%s\n", session->session);
1120 /* The database is closed after each request */
1129 #endif /* EXIM_FIXDB */
1134 /*************************************************
1135 * The exim_tidydb main program *
1136 *************************************************/
1139 /* Utility program to tidy the contents of an exim database file. There is one
1142 -t <time> expiry time for old records - default 30 days
1144 For backwards compatibility, an -f option is recognized and ignored. (It used
1145 to request a "full" tidy. This version always does the whole job.) */
1148 typedef struct key_item {
1149 struct key_item *next;
1155 main(int argc, char **cargv)
1157 struct stat statbuf;
1158 int maxkeep = 30 * 24 * 60 * 60;
1159 int dbdata_type, i, oldest, path_len;
1160 key_item *keychain = NULL;
1164 EXIM_CURSOR *cursor;
1165 uschar **argv = USS cargv;
1171 /* Scan the options */
1173 for (i = 1; i < argc; i++)
1175 if (argv[i][0] != '-') break;
1176 if (Ustrcmp(argv[i], "-f") == 0) continue;
1177 if (Ustrcmp(argv[i], "-t") == 0)
1185 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1186 (void)sscanf(CS s, "%d%n", &value, &count);
1190 case 'w': value *= 7;
1191 case 'd': value *= 24;
1192 case 'h': value *= 60;
1193 case 'm': value *= 60;
1196 default: usage(US"tidydb", US" [-t <time>]");
1201 else usage(US"tidydb", US" [-t <time>]");
1204 /* Adjust argument values and process arguments */
1209 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1211 /* Compute the oldest keep time, verify what we are doing, and open the
1214 oldest = time(NULL) - maxkeep;
1215 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1217 spool_directory = argv[1];
1218 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
1221 /* Prepare for building file names */
1223 sprintf(CS buffer, "%s/input/", argv[1]);
1224 path_len = Ustrlen(buffer);
1227 /* It appears, by experiment, that it is a bad idea to make changes
1228 to the file while scanning it. Pity the man page doesn't warn you about that.
1229 Therefore, we scan and build a list of all the keys. Then we use that to
1230 read the records and possibly update them. */
1232 for (key = dbfn_scan(dbm, TRUE, &cursor);
1234 key = dbfn_scan(dbm, FALSE, &cursor))
1236 key_item * k = store_get(sizeof(key_item) + Ustrlen(key), key);
1239 Ustrcpy(k->key, key);
1242 /* Now scan the collected keys and operate on the records, resetting
1243 the store each time round. */
1245 for (; keychain && (reset_point = store_mark()); store_reset(reset_point))
1247 dbdata_generic *value;
1249 key = keychain->key;
1250 keychain = keychain->next;
1251 value = dbfn_read_with_length(dbm, key, NULL);
1253 /* A continuation record may have been deleted or renamed already, so
1254 non-existence is not serious. */
1256 if (!value) continue;
1258 /* Delete if too old */
1260 if (value->time_stamp < oldest)
1262 printf("deleted %s (too old)\n", key);
1263 dbfn_delete(dbm, key);
1267 /* Do database-specific tidying for wait databases, and message-
1268 specific tidying for the retry database. */
1270 if (dbdata_type == type_wait)
1272 dbdata_wait *wait = (dbdata_wait *)value;
1273 BOOL update = FALSE;
1275 /* Leave corrupt records alone */
1277 if (wait->time_stamp > time(NULL))
1279 printf("**** Data for '%s' corrupted\n time in future: %s\n",
1280 key, print_time(((dbdata_generic *)value)->time_stamp));
1283 if (wait->count > WAIT_NAME_MAX)
1285 printf("**** Data for '%s' corrupted\n count=%d=0x%x max=%d\n",
1286 key, wait->count, wait->count, WAIT_NAME_MAX);
1289 if (wait->sequence > WAIT_CONT_MAX)
1291 printf("**** Data for '%s' corrupted\n sequence=%d=0x%x max=%d\n",
1292 key, wait->sequence, wait->sequence, WAIT_CONT_MAX);
1296 /* Record over 1 year old; just remove it */
1298 if (wait->time_stamp < time(NULL) - 365*24*60*60)
1300 dbfn_delete(dbm, key);
1301 printf("deleted %s (too old)\n", key);
1305 /* Loop for renamed continuation records. For each message id,
1306 check to see if the message exists, and if not, remove its entry
1307 from the record. Because of the possibility of split input directories,
1308 we must look in both possible places for a -D file. */
1312 int length = wait->count * MESSAGE_ID_LENGTH;
1314 for (int offset = length - MESSAGE_ID_LENGTH;
1315 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1317 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1318 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1320 if (Ustat(buffer, &statbuf) != 0)
1322 buffer[path_len] = wait->text[offset+5];
1323 buffer[path_len+1] = '/';
1324 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1325 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1327 if (Ustat(buffer, &statbuf) != 0)
1329 int left = length - offset - MESSAGE_ID_LENGTH;
1330 if (left > 0) Ustrncpy(wait->text + offset,
1331 wait->text + offset + MESSAGE_ID_LENGTH, left);
1333 length -= MESSAGE_ID_LENGTH;
1339 /* If record is empty and the main record, either delete it or rename
1340 the next continuation, repeating if that is also empty. */
1342 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1344 while (wait->count == 0 && wait->sequence > 0)
1347 dbdata_generic *newvalue;
1348 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1349 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1350 if (newvalue != NULL)
1353 wait = (dbdata_wait *)newvalue;
1354 dbfn_delete(dbm, newkey);
1355 printf("renamed %s\n", newkey);
1358 else wait->sequence--;
1361 /* If we have ended up with an empty main record, delete it
1362 and break the loop. Otherwise the new record will be scanned. */
1364 if (wait->count == 0 && wait->sequence == 0)
1366 dbfn_delete(dbm, key);
1367 printf("deleted %s (empty)\n", key);
1373 /* If not an empty main record, break the loop */
1378 /* Re-write the record if required */
1382 printf("updated %s\n", key);
1383 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1384 wait->count * MESSAGE_ID_LENGTH);
1388 /* If a retry record's key ends with a message-id, check that that message
1389 still exists; if not, remove this record. */
1391 else if (dbdata_type == type_retry)
1394 int len = Ustrlen(key);
1396 if (len < MESSAGE_ID_LENGTH + 1) continue;
1397 id = key + len - MESSAGE_ID_LENGTH - 1;
1398 if (*id++ != ':') continue;
1400 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1401 if (i == 6 || i == 13)
1402 { if (id[i] != '-') break; }
1404 { if (!isalnum(id[i])) break; }
1405 if (i < MESSAGE_ID_LENGTH) continue;
1407 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1408 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1410 if (Ustat(buffer, &statbuf) != 0)
1412 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1413 if (Ustat(buffer, &statbuf) != 0)
1415 dbfn_delete(dbm, key);
1416 printf("deleted %s (no message)\n", key);
1423 printf("Tidying complete\n");
1427 #endif /* EXIM_TIDYDB */
1429 /* End of exim_dbutil.c */