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);
417 memcpy(key_copy, key, klen);
419 exim_datum_init(&key_datum); /* Some DBM libraries require the datum */
420 exim_datum_init(&result_datum); /* to be cleared before use. */
421 exim_datum_data_set(&key_datum, key_copy);
422 exim_datum_size_set(&key_datum, klen);
424 if (!exim_dbget(dbblock->dbptr, &key_datum, &result_datum)) return NULL;
426 /* Assume for now that anything stored could have been tainted. Properly
427 we should store the taint status along with the data. */
429 yield = store_get(exim_datum_size_get(&result_datum), GET_TAINTED);
430 memcpy(yield, exim_datum_data_get(&result_datum), exim_datum_size_get(&result_datum));
431 if (length) *length = exim_datum_size_get(&result_datum);
433 exim_datum_free(&result_datum); /* Some DBM libs require freeing */
439 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
441 /*************************************************
442 * Write to database file *
443 *************************************************/
447 dbblock a pointer to an open database block
448 key the key of the record to be written
449 ptr a pointer to the record to be written
450 length the length of the record to be written
452 Returns: the yield of the underlying dbm or db "write" function. If this
453 is dbm, the value is zero for OK.
457 dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
459 EXIM_DATUM key_datum, value_datum;
460 dbdata_generic *gptr = (dbdata_generic *)ptr;
461 int klen = Ustrlen(key) + 1;
462 uschar * key_copy = store_get(klen, key);
464 memcpy(key_copy, key, klen);
465 gptr->time_stamp = time(NULL);
467 exim_datum_init(&key_datum); /* Some DBM libraries require the datum */
468 exim_datum_init(&value_datum); /* to be cleared before use. */
469 exim_datum_data_set(&key_datum, key_copy);
470 exim_datum_size_set(&key_datum, klen);
471 exim_datum_data_set(&value_datum, ptr);
472 exim_datum_size_set(&value_datum, length);
473 return exim_dbput(dbblock->dbptr, &key_datum, &value_datum);
478 /*************************************************
479 * Delete record from database file *
480 *************************************************/
484 dbblock a pointer to an open database block
485 key the key of the record to be deleted
487 Returns: the yield of the underlying dbm or db "delete" function.
491 dbfn_delete(open_db *dbblock, const uschar *key)
493 int klen = Ustrlen(key) + 1;
494 uschar * key_copy = store_get(klen, key);
495 EXIM_DATUM key_datum;
497 memcpy(key_copy, key, klen);
498 exim_datum_init(&key_datum); /* Some DBM libraries require clearing */
499 exim_datum_data_set(&key_datum, key_copy);
500 exim_datum_size_set(&key_datum, klen);
501 return exim_dbdel(dbblock->dbptr, &key_datum);
504 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
508 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
509 /*************************************************
510 * Scan the keys of a database file *
511 *************************************************/
515 dbblock a pointer to an open database block
516 start TRUE if starting a new scan
517 FALSE if continuing with the current scan
518 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
519 that use the notion of a cursor
521 Returns: the next record from the file, or
522 NULL if there are no more
526 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
528 EXIM_DATUM key_datum, value_datum;
531 /* Some dbm require an initialization */
533 if (start) *cursor = exim_dbcreate_cursor(dbblock->dbptr);
535 exim_datum_init(&key_datum); /* Some DBM libraries require the datum */
536 exim_datum_init(&value_datum); /* to be cleared before use. */
538 yield = exim_dbscan(dbblock->dbptr, &key_datum, &value_datum, start, *cursor)
539 ? US exim_datum_data_get(&key_datum) : NULL;
541 /* Some dbm require a termination */
543 if (!yield) exim_dbdelete_cursor(*cursor);
546 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
551 /*************************************************
552 * The exim_dumpdb main program *
553 *************************************************/
556 main(int argc, char **cargv)
563 uschar **argv = USS cargv;
564 uschar keybuffer[1024];
567 options(argc, argv, US"dumpdb", US"kz");
569 /* Check the arguments, and open the database */
571 dbdata_type = check_args(argc, argv, US"dumpdb", US" [-z] [-k]");
572 argc -= optind; argv += optind;
573 spool_directory = argv[0];
575 if (!(dbm = dbfn_open(argv[1], O_RDONLY, &dbblock, FALSE, TRUE)))
578 /* Scan the file, formatting the information for each entry. Note
579 that data is returned in a malloc'ed block, in order that it be
580 correctly aligned. */
582 for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
584 key = dbfn_scan(dbm, FALSE, &cursor))
588 dbdata_callout_cache *callout;
589 dbdata_ratelimit *ratelimit;
590 dbdata_ratelimit_unique *rate_unique;
591 dbdata_tls_session *session;
596 uschar name[MESSAGE_ID_LENGTH + 1];
598 rmark reset_point = store_mark();
600 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
601 which might change. */
603 if (Ustrlen(key) > sizeof(keybuffer) - 1)
605 printf("**** Overlong key encountered: %s\n", key);
608 Ustrcpy(keybuffer, key);
611 printf(" %s\n", keybuffer);
612 else if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
613 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
614 "was not found in the file - something is wrong!\n",
617 /* Note: don't use print_time more than once in one statement, since
618 it uses a single buffer. */
623 retry = (dbdata_retry *)value;
624 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
625 retry->more_errno, retry->text,
626 print_time(retry->first_failed));
627 printf("%s ", print_time(retry->last_try));
628 printf("%s %s\n", print_time(retry->next_try),
629 (retry->expired)? "*" : "");
633 wait = (dbdata_wait *)value;
634 printf("%s ", keybuffer);
636 name[MESSAGE_ID_LENGTH] = 0;
638 /* Leave corrupt records alone */
639 if (wait->count > WAIT_NAME_MAX)
642 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
643 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
644 wait->count = WAIT_NAME_MAX;
645 yield = count_bad = 1;
647 for (int i = 1; i <= wait->count; i++)
649 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
650 if (count_bad && name[0] == 0) break;
651 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
652 Ustrspn(name, "0123456789"
653 "abcdefghijklmnopqrstuvwxyz"
654 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
657 "**** Data for %s corrupted: bad character in message id\n",
659 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
660 fprintf(stderr, "%02x ", name[j]);
661 fprintf(stderr, "\n");
666 t += MESSAGE_ID_LENGTH;
672 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
677 callout = (dbdata_callout_cache *)value;
679 /* New-style address record */
681 if (length == sizeof(dbdata_callout_cache_address))
683 printf("%s %s callout=%s\n",
684 print_time(((dbdata_generic *)value)->time_stamp),
686 print_cache(callout->result));
689 /* New-style domain record */
691 else if (length == sizeof(dbdata_callout_cache))
693 printf("%s %s callout=%s postmaster=%s",
694 print_time(((dbdata_generic *)value)->time_stamp),
696 print_cache(callout->result),
697 print_cache(callout->postmaster_result));
698 if (callout->postmaster_result != ccache_unknown)
699 printf(" (%s)", print_time(callout->postmaster_stamp));
700 printf(" random=%s", print_cache(callout->random_result));
701 if (callout->random_result != ccache_unknown)
702 printf(" (%s)", print_time(callout->random_stamp));
709 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
711 ratelimit = (dbdata_ratelimit *)value;
712 rate_unique = (dbdata_ratelimit_unique *)value;
713 printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
714 print_time(ratelimit->time_stamp),
715 ratelimit->time_usec, ratelimit->rate,
716 print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
721 ratelimit = (dbdata_ratelimit *)value;
722 printf("%s.%06d rate: %10.3f key: %s\n",
723 print_time(ratelimit->time_stamp),
724 ratelimit->time_usec, ratelimit->rate,
730 session = (dbdata_tls_session *)value;
731 printf(" %s %.*s\n", keybuffer, length, session->session);
735 seen = (dbdata_seen *)value;
736 printf("%s\t%s\n", keybuffer, print_time(seen->time_stamp));
739 store_reset(reset_point);
746 #endif /* EXIM_DUMPDB */
752 /*************************************************
753 * The exim_fixdb main program *
754 *************************************************/
756 /* In order not to hold the database lock any longer than is necessary, each
757 operation on the database uses a separate open/close call. This is expensive,
758 but then using this utility is not expected to be very common. Its main use is
759 to provide a way of patching up hints databases in order to run tests.
764 This causes the data from the given record to be displayed, or "not found"
765 to be output. Note that in the retry database, destination names are
766 preceded by R: or T: for router or transport retry info.
769 This causes the given record to be deleted or "not found" to be output.
771 (3) <record name> <field number> <value>
772 This sets the given value into the given field, identified by a number
773 which is output by the display command. Not all types of record can
777 This exits from exim_fixdb.
779 If the record name is omitted from (2) or (3), the previously used record name
784 main(int argc, char **cargv)
787 uschar **argv = USS cargv;
794 options(argc, argv, US"fixdb", US"z");
795 name[0] = 0; /* No name set */
797 /* Sort out the database type, verify what we are working on and then process
800 dbdata_type = check_args(argc, argv, US"fixdb", US" [-z]");
801 argc -= optind; argv += optind;
802 spool_directory = argv[0];
805 printf("Modifying Exim hints database %s/db/%s\n", spool_directory, aname);
807 for(; (reset_point = store_mark()); store_reset(reset_point))
814 dbdata_callout_cache *callout;
815 dbdata_ratelimit *ratelimit;
816 dbdata_ratelimit_unique *rate_unique;
817 dbdata_tls_session *session;
820 uschar field[256], value[256];
823 if (Ufgets(buffer, 256, stdin) == NULL) break;
825 buffer[Ustrlen(buffer)-1] = 0;
826 field[0] = value[0] = 0;
828 /* If the buffer contains just one digit, or just consists of "d", use the
829 previous name for an update. */
831 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
832 || Ustrcmp(buffer, "d") == 0)
836 printf("No previous record name is set\n");
839 (void)sscanf(CS buffer, "%s %s", field, value);
844 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
847 /* Handle an update request */
853 if (!(dbm = dbfn_open(aname, O_RDWR, &dbblock, FALSE, TRUE)))
856 if (Ustrcmp(field, "d") == 0)
858 if (value[0] != 0) printf("unexpected value after \"d\"\n");
859 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
860 "not found" : "deleted");
865 else if (isdigit((uschar)field[0]))
867 int fieldno = Uatoi(field);
870 printf("value missing\n");
876 record = dbfn_read_with_length(dbm, name, &oldlength);
877 if (record == NULL) printf("not found\n"); else
880 /*int length = 0; Stops compiler warning */
885 retry = (dbdata_retry *)record;
886 /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
890 case 0: retry->basic_errno = Uatoi(value);
892 case 1: retry->more_errno = Uatoi(value);
894 case 2: if ((tt = read_time(value)) > 0) retry->first_failed = tt;
895 else printf("bad time value\n");
897 case 3: if ((tt = read_time(value)) > 0) retry->last_try = tt;
898 else printf("bad time value\n");
900 case 4: if ((tt = read_time(value)) > 0) retry->next_try = tt;
901 else printf("bad time value\n");
903 case 5: if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
904 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
905 else printf("\"yes\" or \"no\" expected=n");
907 default: printf("unknown field number\n");
914 printf("Can't change contents of wait database record\n");
918 printf("Can't change contents of misc database record\n");
922 callout = (dbdata_callout_cache *)record;
923 /* length = sizeof(dbdata_callout_cache); */
926 case 0: callout->result = Uatoi(value);
928 case 1: callout->postmaster_result = Uatoi(value);
930 case 2: callout->random_result = Uatoi(value);
932 default: printf("unknown field number\n");
939 ratelimit = (dbdata_ratelimit *)record;
942 case 0: if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
943 else printf("bad time value\n");
945 case 1: ratelimit->time_usec = Uatoi(value);
947 case 2: ratelimit->rate = Ustrtod(value, NULL);
949 case 3: if (Ustrstr(name, "/unique/") != NULL
950 && oldlength >= sizeof(dbdata_ratelimit_unique))
952 rate_unique = (dbdata_ratelimit_unique *)record;
953 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
954 else printf("bad time value\n");
957 /* else fall through */
959 case 5: if (Ustrstr(name, "/unique/") != NULL
960 && oldlength >= sizeof(dbdata_ratelimit_unique))
968 md5_end(&md5info, value, Ustrlen(value), md5sum);
969 hash = md5sum[0] << 0 | md5sum[1] << 8
970 | md5sum[2] << 16 | md5sum[3] << 24;
971 hinc = md5sum[4] << 0 | md5sum[5] << 8
972 | md5sum[6] << 16 | md5sum[7] << 24;
973 rate_unique = (dbdata_ratelimit_unique *)record;
975 for (unsigned n = 0; n < 8; n++, hash += hinc)
977 int bit = 1 << (hash % 8);
978 int byte = (hash / 8) % rate_unique->bloom_size;
979 if ((rate_unique->bloom[byte] & bit) == 0)
982 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
986 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
989 /* else fall through */
990 default: printf("unknown field number\n");
997 printf("Can't change contents of tls database record\n");
1001 dbfn_write(dbm, name, record, oldlength);
1008 printf("field number or d expected\n");
1013 if (!verify) continue;
1016 /* The "name" q causes an exit */
1018 else if (Ustrcmp(name, "q") == 0) return 0;
1020 /* Handle a read request, or verify after an update. */
1022 if (!(dbm = dbfn_open(aname, O_RDONLY, &dbblock, FALSE, TRUE)))
1025 if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
1027 printf("record %s not found\n", name);
1033 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
1037 retry = (dbdata_retry *)record;
1038 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
1039 printf("1 extra data: %d\n", retry->more_errno);
1040 printf("2 first failed: %s\n", print_time(retry->first_failed));
1041 printf("3 last try: %s\n", print_time(retry->last_try));
1042 printf("4 next try: %s\n", print_time(retry->next_try));
1043 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
1047 wait = (dbdata_wait *)record;
1049 printf("Sequence: %d\n", wait->sequence);
1050 if (wait->count > WAIT_NAME_MAX)
1052 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
1053 wait->count, WAIT_NAME_MAX);
1054 wait->count = WAIT_NAME_MAX;
1057 for (int i = 1; i <= wait->count; i++)
1059 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1060 value[MESSAGE_ID_LENGTH] = 0;
1061 if (count_bad && value[0] == 0) break;
1062 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1063 Ustrspn(value, "0123456789"
1064 "abcdefghijklmnopqrstuvwxyz"
1065 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1067 printf("\n**** Data corrupted: bad character in message id ****\n");
1068 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
1069 printf("%02x ", value[j]);
1073 printf("%s ", value);
1074 t += MESSAGE_ID_LENGTH;
1083 callout = (dbdata_callout_cache *)record;
1084 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1086 if (oldlength > sizeof(dbdata_callout_cache_address))
1088 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1089 callout->postmaster_result);
1090 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1091 callout->random_result);
1095 case type_ratelimit:
1096 ratelimit = (dbdata_ratelimit *)record;
1097 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1098 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1099 printf("2 sender rate: % .3f\n", ratelimit->rate);
1100 if (Ustrstr(name, "/unique/") != NULL
1101 && oldlength >= sizeof(dbdata_ratelimit_unique))
1103 rate_unique = (dbdata_ratelimit_unique *)record;
1104 printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1105 printf("4 test filter membership\n");
1106 printf("5 add element to filter\n");
1111 session = (dbdata_tls_session *)value;
1112 printf("0 time stamp: %s\n", print_time(session->time_stamp));
1113 printf("1 session: .%s\n", session->session);
1118 /* The database is closed after each request */
1127 #endif /* EXIM_FIXDB */
1132 /*************************************************
1133 * The exim_tidydb main program *
1134 *************************************************/
1137 /* Utility program to tidy the contents of an exim database file. There is one
1140 -t <time> expiry time for old records - default 30 days
1142 For backwards compatibility, an -f option is recognized and ignored. (It used
1143 to request a "full" tidy. This version always does the whole job.) */
1146 typedef struct key_item {
1147 struct key_item *next;
1153 main(int argc, char **cargv)
1155 struct stat statbuf;
1156 int maxkeep = 30 * 24 * 60 * 60;
1157 int dbdata_type, i, oldest, path_len;
1158 key_item *keychain = NULL;
1162 EXIM_CURSOR *cursor;
1163 uschar **argv = USS cargv;
1169 /* Scan the options */
1171 for (i = 1; i < argc; i++)
1173 if (argv[i][0] != '-') break;
1174 if (Ustrcmp(argv[i], "-f") == 0) continue;
1175 if (Ustrcmp(argv[i], "-t") == 0)
1183 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1184 (void)sscanf(CS s, "%d%n", &value, &count);
1188 case 'w': value *= 7;
1189 case 'd': value *= 24;
1190 case 'h': value *= 60;
1191 case 'm': value *= 60;
1194 default: usage(US"tidydb", US" [-t <time>]");
1199 else usage(US"tidydb", US" [-t <time>]");
1202 /* Adjust argument values and process arguments */
1207 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1209 /* Compute the oldest keep time, verify what we are doing, and open the
1212 oldest = time(NULL) - maxkeep;
1213 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1215 spool_directory = argv[1];
1216 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
1219 /* Prepare for building file names */
1221 sprintf(CS buffer, "%s/input/", argv[1]);
1222 path_len = Ustrlen(buffer);
1225 /* It appears, by experiment, that it is a bad idea to make changes
1226 to the file while scanning it. Pity the man page doesn't warn you about that.
1227 Therefore, we scan and build a list of all the keys. Then we use that to
1228 read the records and possibly update them. */
1230 for (key = dbfn_scan(dbm, TRUE, &cursor);
1232 key = dbfn_scan(dbm, FALSE, &cursor))
1234 key_item * k = store_get(sizeof(key_item) + Ustrlen(key), key);
1237 Ustrcpy(k->key, key);
1240 /* Now scan the collected keys and operate on the records, resetting
1241 the store each time round. */
1243 for (; keychain && (reset_point = store_mark()); store_reset(reset_point))
1245 dbdata_generic *value;
1247 key = keychain->key;
1248 keychain = keychain->next;
1249 value = dbfn_read_with_length(dbm, key, NULL);
1251 /* A continuation record may have been deleted or renamed already, so
1252 non-existence is not serious. */
1254 if (!value) continue;
1256 /* Delete if too old */
1258 if (value->time_stamp < oldest)
1260 printf("deleted %s (too old)\n", key);
1261 dbfn_delete(dbm, key);
1265 /* Do database-specific tidying for wait databases, and message-
1266 specific tidying for the retry database. */
1268 if (dbdata_type == type_wait)
1270 dbdata_wait *wait = (dbdata_wait *)value;
1271 BOOL update = FALSE;
1273 /* Leave corrupt records alone */
1275 if (wait->time_stamp > time(NULL))
1277 printf("**** Data for '%s' corrupted\n time in future: %s\n",
1278 key, print_time(((dbdata_generic *)value)->time_stamp));
1281 if (wait->count > WAIT_NAME_MAX)
1283 printf("**** Data for '%s' corrupted\n count=%d=0x%x max=%d\n",
1284 key, wait->count, wait->count, WAIT_NAME_MAX);
1287 if (wait->sequence > WAIT_CONT_MAX)
1289 printf("**** Data for '%s' corrupted\n sequence=%d=0x%x max=%d\n",
1290 key, wait->sequence, wait->sequence, WAIT_CONT_MAX);
1294 /* Record over 1 year old; just remove it */
1296 if (wait->time_stamp < time(NULL) - 365*24*60*60)
1298 dbfn_delete(dbm, key);
1299 printf("deleted %s (too old)\n", key);
1303 /* Loop for renamed continuation records. For each message id,
1304 check to see if the message exists, and if not, remove its entry
1305 from the record. Because of the possibility of split input directories,
1306 we must look in both possible places for a -D file. */
1310 int length = wait->count * MESSAGE_ID_LENGTH;
1312 for (int offset = length - MESSAGE_ID_LENGTH;
1313 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1315 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1316 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1318 if (Ustat(buffer, &statbuf) != 0)
1320 buffer[path_len] = wait->text[offset+5];
1321 buffer[path_len+1] = '/';
1322 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1323 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1325 if (Ustat(buffer, &statbuf) != 0)
1327 int left = length - offset - MESSAGE_ID_LENGTH;
1328 if (left > 0) Ustrncpy(wait->text + offset,
1329 wait->text + offset + MESSAGE_ID_LENGTH, left);
1331 length -= MESSAGE_ID_LENGTH;
1337 /* If record is empty and the main record, either delete it or rename
1338 the next continuation, repeating if that is also empty. */
1340 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1342 while (wait->count == 0 && wait->sequence > 0)
1345 dbdata_generic *newvalue;
1346 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1347 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1348 if (newvalue != NULL)
1351 wait = (dbdata_wait *)newvalue;
1352 dbfn_delete(dbm, newkey);
1353 printf("renamed %s\n", newkey);
1356 else wait->sequence--;
1359 /* If we have ended up with an empty main record, delete it
1360 and break the loop. Otherwise the new record will be scanned. */
1362 if (wait->count == 0 && wait->sequence == 0)
1364 dbfn_delete(dbm, key);
1365 printf("deleted %s (empty)\n", key);
1371 /* If not an empty main record, break the loop */
1376 /* Re-write the record if required */
1380 printf("updated %s\n", key);
1381 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1382 wait->count * MESSAGE_ID_LENGTH);
1386 /* If a retry record's key ends with a message-id, check that that message
1387 still exists; if not, remove this record. */
1389 else if (dbdata_type == type_retry)
1392 int len = Ustrlen(key);
1394 if (len < MESSAGE_ID_LENGTH + 1) continue;
1395 id = key + len - MESSAGE_ID_LENGTH - 1;
1396 if (*id++ != ':') continue;
1398 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1399 if (i == 6 || i == 13)
1400 { if (id[i] != '-') break; }
1402 { if (!isalnum(id[i])) break; }
1403 if (i < MESSAGE_ID_LENGTH) continue;
1405 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1406 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1408 if (Ustat(buffer, &statbuf) != 0)
1410 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1411 if (Ustat(buffer, &statbuf) != 0)
1413 dbfn_delete(dbm, key);
1414 printf("deleted %s (no message)\n", key);
1421 printf("Tidying complete\n");
1425 #endif /* EXIM_TIDYDB */
1427 /* End of exim_dbutil.c */