1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
6 /* Copyright (c) The Exim Maintainers 2020 */
7 /* See the file NOTICE for conditions of use and distribution. */
10 /* This single source file is used to compile three utility programs for
11 maintaining Exim hints databases.
13 exim_dumpdb dumps out the contents
14 exim_fixdb patches the database (really for Exim maintenance/testing)
15 exim_tidydb removed obsolete data
17 In all cases, the first argument is the name of the spool directory. The second
18 argument is the name of the database file. The available names are:
20 callout: callout verification cache
21 misc: miscellaneous hints data
22 ratelimit: record for ACL "ratelimit" condition
23 retry: etry delivery information
24 seen: imestamp records for ACL "seen" condition
25 tls: TLS session resumption cache
26 wait-<t>: message waiting information; <t> is a transport name
28 There are a number of common subroutines, followed by three main programs,
29 whose inclusion is controlled by -D on the compilation command. */
35 /* Identifiers for the different database types. */
40 #define type_callout 4
41 #define type_ratelimit 5
46 /* This is used by our cut-down dbfn_open(). */
48 uschar *spool_directory;
53 /******************************************************************************/
54 /* dummies needed by Solaris build */
59 readconf_printtime(int t)
62 string_vformat_trc(gstring * g, const uschar * func, unsigned line,
63 unsigned size_limit, unsigned flags, const char *format, va_list ap)
66 string_sprintf_trc(const char * fmt, const uschar * func, unsigned line, ...)
69 string_format_trc(uschar * buf, int len, const uschar * func, unsigned line,
70 const char * fmt, ...)
73 struct global_flags f;
74 unsigned int log_selector[1];
76 BOOL split_spool_directory;
79 /* These introduced by the taintwarn handling */
80 #ifdef ALLOW_INSECURE_TAINTED_DATA
81 BOOL allow_insecure_tainted_data;
84 /******************************************************************************/
87 /*************************************************
88 * Berkeley DB error callback *
89 *************************************************/
91 /* For Berkeley DB >= 2, we can define a function to be called in case of DB
92 errors. This should help with debugging strange DB problems, e.g. getting "File
93 exists" when you try to open a db file. The API changed at release 4.3. */
95 #if defined(USE_DB) && defined(DB_VERSION_STRING)
97 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
98 dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
102 dbfn_bdb_error_callback(const char *pfx, char *msg)
106 printf("Berkeley DB error: %s\n", msg);
112 /*************************************************
114 *************************************************/
116 SIGNAL_BOOL sigalrm_seen;
119 sigalrm_handler(int sig)
126 /*************************************************
127 * Output usage message and exit *
128 *************************************************/
131 usage(uschar *name, uschar *options)
133 printf("Usage: exim_%s%s <spool-directory> <database-name>\n", name, options);
134 printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit | tls | seen\n");
140 /*************************************************
141 * Sort out the command arguments *
142 *************************************************/
144 /* This function checks that there are exactly 2 arguments, and checks the
145 second of them to be sure it is a known database name. */
148 check_args(int argc, uschar **argv, uschar *name, uschar *options)
150 uschar * aname = argv[optind + 1];
151 if (argc - optind == 2)
153 if (Ustrcmp(aname, "retry") == 0) return type_retry;
154 if (Ustrcmp(aname, "misc") == 0) return type_misc;
155 if (Ustrncmp(aname, "wait-", 5) == 0) return type_wait;
156 if (Ustrcmp(aname, "callout") == 0) return type_callout;
157 if (Ustrcmp(aname, "ratelimit") == 0) return type_ratelimit;
158 if (Ustrcmp(aname, "tls") == 0) return type_tls;
159 if (Ustrcmp(aname, "seen") == 0) return type_seen;
161 usage(name, options);
162 return -1; /* Never obeyed */
167 options(int argc, uschar * argv[], uschar * name)
172 while ((opt = getopt(argc, (char * const *)argv, "z")) != -1)
175 case 'z': utc = TRUE; break;
176 default: usage(name, US" [-z]");
183 /*************************************************
184 * Handle attempts to write the log *
185 *************************************************/
187 /* The message gets written to stderr when log_write() is called from a
188 utility. The message always gets '\n' added on the end of it. These calls come
189 from modules such as store.c when things go drastically wrong (e.g. malloc()
190 failing). In normal use they won't get obeyed.
193 selector not relevant when running a utility
194 flags not relevant when running a utility
195 format a printf() format
196 ... arguments for format
202 log_write(unsigned int selector, int flags, const char *format, ...)
205 va_start(ap, format);
206 vfprintf(stderr, format, ap);
207 fprintf(stderr, "\n");
213 /*************************************************
214 * Format a time value for printing *
215 *************************************************/
217 static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss ")];
222 struct tm *tmstr = utc ? gmtime(&t) : localtime(&t);
223 Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
229 /*************************************************
230 * Format a cache value for printing *
231 *************************************************/
234 print_cache(int value)
236 return value == ccache_accept ? US"accept" :
237 value == ccache_reject ? US"reject" :
243 /*************************************************
245 *************************************************/
252 time_t now = time(NULL);
253 struct tm *tm = localtime(&now);
258 for (uschar * t = s + Ustrlen(s) - 1; t >= s; t--)
260 if (*t == ':') continue;
261 if (!isdigit((uschar)*t)) return -1;
266 if (!isdigit((uschar)*t)) return -1;
267 value = value + (*t - '0')*10;
272 case 0: tm->tm_min = value; break;
273 case 1: tm->tm_hour = value; break;
274 case 2: tm->tm_mday = value; break;
275 case 3: tm->tm_mon = value - 1; break;
276 case 4: tm->tm_year = (value < 90)? value + 100 : value; break;
283 #endif /* EXIM_FIXDB */
287 /*************************************************
288 * Open and lock a database file *
289 *************************************************/
291 /* This is a cut-down version from the function in dbfn.h that Exim itself
292 uses. We assume the database exists, and therefore give up if we cannot open
296 name The single-component name of one of Exim's database files.
297 flags O_RDONLY or O_RDWR
298 dbblock Points to an open_db block to be filled in.
302 Returns: NULL if the open failed, or the locking failed.
303 On success, dbblock is returned. This contains the dbm pointer and
304 the fd of the locked lock file.
308 dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof, BOOL panic)
311 struct flock lock_data;
312 BOOL read_only = flags == O_RDONLY;
313 uschar * dirname, * filename;
315 /* The first thing to do is to open a separate file on which to lock. This
316 ensures that Exim has exclusive use of the database before it even tries to
317 open it. If there is a database, there should be a lock file in existence. */
319 #ifdef COMPILE_UTILITY
320 if ( asprintf(CSS &dirname, "%s/db", spool_directory) < 0
321 || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
324 dirname = string_sprintf("%s/db", spool_directory);
325 filename = string_sprintf("%s/%s.lockfile", dirname, name);
328 dbblock->lockfd = Uopen(filename, flags, 0);
329 if (dbblock->lockfd < 0)
331 printf("** Failed to open database lock file %s: %s\n", filename,
336 /* Now we must get a lock on the opened lock file; do this with a blocking
337 lock that times out. */
339 lock_data.l_type = read_only ? F_RDLCK : F_WRLCK;
340 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
342 sigalrm_seen = FALSE;
343 os_non_restarting_signal(SIGALRM, sigalrm_handler);
344 ALARM(EXIMDB_LOCK_TIMEOUT);
345 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
348 if (sigalrm_seen) errno = ETIMEDOUT;
351 printf("** Failed to get %s lock for %s: %s",
352 flags & O_WRONLY ? "write" : "read",
354 errno == ETIMEDOUT ? "timed out" : strerror(errno));
355 (void)close(dbblock->lockfd);
359 /* At this point we have an opened and locked separate lock file, that is,
360 exclusive access to the database, so we can go ahead and open it. */
362 #ifdef COMPILE_UTILITY
363 if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
365 filename = string_sprintf("%s/%s", dirname, name);
367 EXIM_DBOPEN(filename, dirname, flags, 0, &dbblock->dbptr);
371 printf("** Failed to open DBM file %s for %s:\n %s%s\n", filename,
372 read_only? "reading" : "writing", strerror(errno),
374 " (or Berkeley DB error while opening)"
379 (void)close(dbblock->lockfd);
389 /*************************************************
390 * Unlock and close a database file *
391 *************************************************/
393 /* Closing a file automatically unlocks it, so after closing the database, just
396 Argument: a pointer to an open database block
401 dbfn_close(open_db *dbblock)
403 EXIM_DBCLOSE(dbblock->dbptr);
404 (void)close(dbblock->lockfd);
410 /*************************************************
411 * Read from database file *
412 *************************************************/
414 /* Passing back the pointer unchanged is useless, because there is no guarantee
415 of alignment. Since all the records used by Exim need to be properly aligned to
416 pick out the timestamps, etc., do the copying centrally here.
419 dbblock a pointer to an open database block
420 key the key of the record to be read
421 length where to put the length (or NULL if length not wanted). Includes overhead.
423 Returns: a pointer to the retrieved record, or
424 NULL if the record is not found
428 dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
431 EXIM_DATUM key_datum, result_datum;
432 int klen = Ustrlen(key) + 1;
433 uschar * key_copy = store_get(klen, is_tainted(key));
435 memcpy(key_copy, key, klen);
437 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
438 EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */
439 EXIM_DATUM_DATA(key_datum) = CS key_copy;
440 EXIM_DATUM_SIZE(key_datum) = klen;
442 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
444 /* Assume for now that anything stored could have been tainted. Properly
445 we should store the taint status along with the data. */
447 yield = store_get(EXIM_DATUM_SIZE(result_datum), TRUE);
448 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
449 if (length) *length = EXIM_DATUM_SIZE(result_datum);
451 EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */
457 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
459 /*************************************************
460 * Write to database file *
461 *************************************************/
465 dbblock a pointer to an open database block
466 key the key of the record to be written
467 ptr a pointer to the record to be written
468 length the length of the record to be written
470 Returns: the yield of the underlying dbm or db "write" function. If this
471 is dbm, the value is zero for OK.
475 dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
477 EXIM_DATUM key_datum, value_datum;
478 dbdata_generic *gptr = (dbdata_generic *)ptr;
479 int klen = Ustrlen(key) + 1;
480 uschar * key_copy = store_get(klen, is_tainted(key));
482 memcpy(key_copy, key, klen);
483 gptr->time_stamp = time(NULL);
485 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
486 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
487 EXIM_DATUM_DATA(key_datum) = CS key_copy;
488 EXIM_DATUM_SIZE(key_datum) = klen;
489 EXIM_DATUM_DATA(value_datum) = CS ptr;
490 EXIM_DATUM_SIZE(value_datum) = length;
491 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
496 /*************************************************
497 * Delete record from database file *
498 *************************************************/
502 dbblock a pointer to an open database block
503 key the key of the record to be deleted
505 Returns: the yield of the underlying dbm or db "delete" function.
509 dbfn_delete(open_db *dbblock, const uschar *key)
511 int klen = Ustrlen(key) + 1;
512 uschar * key_copy = store_get(klen, is_tainted(key));
514 memcpy(key_copy, key, klen);
515 EXIM_DATUM key_datum;
516 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
517 EXIM_DATUM_DATA(key_datum) = CS key_copy;
518 EXIM_DATUM_SIZE(key_datum) = klen;
519 return EXIM_DBDEL(dbblock->dbptr, key_datum);
522 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
526 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
527 /*************************************************
528 * Scan the keys of a database file *
529 *************************************************/
533 dbblock a pointer to an open database block
534 start TRUE if starting a new scan
535 FALSE if continuing with the current scan
536 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
537 that use the notion of a cursor
539 Returns: the next record from the file, or
540 NULL if there are no more
544 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
546 EXIM_DATUM key_datum, value_datum;
549 /* Some dbm require an initialization */
551 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
553 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
554 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
556 yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
557 US EXIM_DATUM_DATA(key_datum) : NULL;
559 /* Some dbm require a termination */
561 if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
564 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
569 /*************************************************
570 * The exim_dumpdb main program *
571 *************************************************/
574 main(int argc, char **cargv)
581 uschar **argv = USS cargv;
582 uschar keybuffer[1024];
585 options(argc, argv, US"dumpdb");
587 /* Check the arguments, and open the database */
589 dbdata_type = check_args(argc, argv, US"dumpdb", US" [-z]");
590 argc -= optind; argv += optind;
591 spool_directory = argv[0];
593 if (!(dbm = dbfn_open(argv[1], O_RDONLY, &dbblock, FALSE, TRUE)))
596 /* Scan the file, formatting the information for each entry. Note
597 that data is returned in a malloc'ed block, in order that it be
598 correctly aligned. */
600 for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
602 key = dbfn_scan(dbm, FALSE, &cursor))
606 dbdata_callout_cache *callout;
607 dbdata_ratelimit *ratelimit;
608 dbdata_ratelimit_unique *rate_unique;
609 dbdata_tls_session *session;
614 uschar name[MESSAGE_ID_LENGTH + 1];
616 rmark reset_point = store_mark();
618 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
619 which might change. */
621 if (Ustrlen(key) > sizeof(keybuffer) - 1)
623 printf("**** Overlong key encountered: %s\n", key);
626 Ustrcpy(keybuffer, key);
628 if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
629 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
630 "was not found in the file - something is wrong!\n",
634 /* Note: don't use print_time more than once in one statement, since
635 it uses a single buffer. */
640 retry = (dbdata_retry *)value;
641 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
642 retry->more_errno, retry->text,
643 print_time(retry->first_failed));
644 printf("%s ", print_time(retry->last_try));
645 printf("%s %s\n", print_time(retry->next_try),
646 (retry->expired)? "*" : "");
650 wait = (dbdata_wait *)value;
651 printf("%s ", keybuffer);
653 name[MESSAGE_ID_LENGTH] = 0;
655 /* Leave corrupt records alone */
656 if (wait->count > WAIT_NAME_MAX)
659 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
660 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
661 wait->count = WAIT_NAME_MAX;
662 yield = count_bad = 1;
664 for (int i = 1; i <= wait->count; i++)
666 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
667 if (count_bad && name[0] == 0) break;
668 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
669 Ustrspn(name, "0123456789"
670 "abcdefghijklmnopqrstuvwxyz"
671 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
674 "**** Data for %s corrupted: bad character in message id\n",
676 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
677 fprintf(stderr, "%02x ", name[j]);
678 fprintf(stderr, "\n");
683 t += MESSAGE_ID_LENGTH;
689 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
694 callout = (dbdata_callout_cache *)value;
696 /* New-style address record */
698 if (length == sizeof(dbdata_callout_cache_address))
700 printf("%s %s callout=%s\n",
701 print_time(((dbdata_generic *)value)->time_stamp),
703 print_cache(callout->result));
706 /* New-style domain record */
708 else if (length == sizeof(dbdata_callout_cache))
710 printf("%s %s callout=%s postmaster=%s",
711 print_time(((dbdata_generic *)value)->time_stamp),
713 print_cache(callout->result),
714 print_cache(callout->postmaster_result));
715 if (callout->postmaster_result != ccache_unknown)
716 printf(" (%s)", print_time(callout->postmaster_stamp));
717 printf(" random=%s", print_cache(callout->random_result));
718 if (callout->random_result != ccache_unknown)
719 printf(" (%s)", print_time(callout->random_stamp));
726 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
728 ratelimit = (dbdata_ratelimit *)value;
729 rate_unique = (dbdata_ratelimit_unique *)value;
730 printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
731 print_time(ratelimit->time_stamp),
732 ratelimit->time_usec, ratelimit->rate,
733 print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
738 ratelimit = (dbdata_ratelimit *)value;
739 printf("%s.%06d rate: %10.3f key: %s\n",
740 print_time(ratelimit->time_stamp),
741 ratelimit->time_usec, ratelimit->rate,
747 session = (dbdata_tls_session *)value;
748 printf(" %s %.*s\n", keybuffer, length, session->session);
752 seen = (dbdata_seen *)value;
753 printf("%s\t%s\n", keybuffer, print_time(seen->time_stamp));
757 store_reset(reset_point);
764 #endif /* EXIM_DUMPDB */
770 /*************************************************
771 * The exim_fixdb main program *
772 *************************************************/
774 /* In order not to hold the database lock any longer than is necessary, each
775 operation on the database uses a separate open/close call. This is expensive,
776 but then using this utility is not expected to be very common. Its main use is
777 to provide a way of patching up hints databases in order to run tests.
782 This causes the data from the given record to be displayed, or "not found"
783 to be output. Note that in the retry database, destination names are
784 preceded by R: or T: for router or transport retry info.
787 This causes the given record to be deleted or "not found" to be output.
789 (3) <record name> <field number> <value>
790 This sets the given value into the given field, identified by a number
791 which is output by the display command. Not all types of record can
795 This exits from exim_fixdb.
797 If the record name is omitted from (2) or (3), the previously used record name
802 main(int argc, char **cargv)
805 uschar **argv = USS cargv;
812 options(argc, argv, US"fixdb");
813 name[0] = 0; /* No name set */
815 /* Sort out the database type, verify what we are working on and then process
818 dbdata_type = check_args(argc, argv, US"fixdb", US" [-z]");
819 argc -= optind; argv += optind;
820 spool_directory = argv[0];
823 printf("Modifying Exim hints database %s/db/%s\n", spool_directory, aname);
825 for(; (reset_point = store_mark()); store_reset(reset_point))
832 dbdata_callout_cache *callout;
833 dbdata_ratelimit *ratelimit;
834 dbdata_ratelimit_unique *rate_unique;
835 dbdata_tls_session *session;
838 uschar field[256], value[256];
841 if (Ufgets(buffer, 256, stdin) == NULL) break;
843 buffer[Ustrlen(buffer)-1] = 0;
844 field[0] = value[0] = 0;
846 /* If the buffer contains just one digit, or just consists of "d", use the
847 previous name for an update. */
849 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
850 || Ustrcmp(buffer, "d") == 0)
854 printf("No previous record name is set\n");
857 (void)sscanf(CS buffer, "%s %s", field, value);
862 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
865 /* Handle an update request */
871 if (!(dbm = dbfn_open(aname, O_RDWR, &dbblock, FALSE, TRUE)))
874 if (Ustrcmp(field, "d") == 0)
876 if (value[0] != 0) printf("unexpected value after \"d\"\n");
877 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
878 "not found" : "deleted");
883 else if (isdigit((uschar)field[0]))
885 int fieldno = Uatoi(field);
888 printf("value missing\n");
894 record = dbfn_read_with_length(dbm, name, &oldlength);
895 if (record == NULL) printf("not found\n"); else
898 /*int length = 0; Stops compiler warning */
903 retry = (dbdata_retry *)record;
904 /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
908 case 0: retry->basic_errno = Uatoi(value);
910 case 1: retry->more_errno = Uatoi(value);
912 case 2: if ((tt = read_time(value)) > 0) retry->first_failed = tt;
913 else printf("bad time value\n");
915 case 3: if ((tt = read_time(value)) > 0) retry->last_try = tt;
916 else printf("bad time value\n");
918 case 4: if ((tt = read_time(value)) > 0) retry->next_try = tt;
919 else printf("bad time value\n");
921 case 5: if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
922 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
923 else printf("\"yes\" or \"no\" expected=n");
925 default: printf("unknown field number\n");
932 printf("Can't change contents of wait database record\n");
936 printf("Can't change contents of misc database record\n");
940 callout = (dbdata_callout_cache *)record;
941 /* length = sizeof(dbdata_callout_cache); */
944 case 0: callout->result = Uatoi(value);
946 case 1: callout->postmaster_result = Uatoi(value);
948 case 2: callout->random_result = Uatoi(value);
950 default: printf("unknown field number\n");
957 ratelimit = (dbdata_ratelimit *)record;
960 case 0: if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
961 else printf("bad time value\n");
963 case 1: ratelimit->time_usec = Uatoi(value);
965 case 2: ratelimit->rate = Ustrtod(value, NULL);
967 case 3: if (Ustrstr(name, "/unique/") != NULL
968 && oldlength >= sizeof(dbdata_ratelimit_unique))
970 rate_unique = (dbdata_ratelimit_unique *)record;
971 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
972 else printf("bad time value\n");
975 /* else fall through */
977 case 5: if (Ustrstr(name, "/unique/") != NULL
978 && oldlength >= sizeof(dbdata_ratelimit_unique))
986 md5_end(&md5info, value, Ustrlen(value), md5sum);
987 hash = md5sum[0] << 0 | md5sum[1] << 8
988 | md5sum[2] << 16 | md5sum[3] << 24;
989 hinc = md5sum[4] << 0 | md5sum[5] << 8
990 | md5sum[6] << 16 | md5sum[7] << 24;
991 rate_unique = (dbdata_ratelimit_unique *)record;
993 for (unsigned n = 0; n < 8; n++, hash += hinc)
995 int bit = 1 << (hash % 8);
996 int byte = (hash / 8) % rate_unique->bloom_size;
997 if ((rate_unique->bloom[byte] & bit) == 0)
1000 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
1004 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
1007 /* else fall through */
1008 default: printf("unknown field number\n");
1015 printf("Can't change contents of tls database record\n");
1019 dbfn_write(dbm, name, record, oldlength);
1026 printf("field number or d expected\n");
1031 if (!verify) continue;
1034 /* The "name" q causes an exit */
1036 else if (Ustrcmp(name, "q") == 0) return 0;
1038 /* Handle a read request, or verify after an update. */
1040 if (!(dbm = dbfn_open(aname, O_RDONLY, &dbblock, FALSE, TRUE)))
1043 if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
1045 printf("record %s not found\n", name);
1051 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
1055 retry = (dbdata_retry *)record;
1056 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
1057 printf("1 extra data: %d\n", retry->more_errno);
1058 printf("2 first failed: %s\n", print_time(retry->first_failed));
1059 printf("3 last try: %s\n", print_time(retry->last_try));
1060 printf("4 next try: %s\n", print_time(retry->next_try));
1061 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
1065 wait = (dbdata_wait *)record;
1067 printf("Sequence: %d\n", wait->sequence);
1068 if (wait->count > WAIT_NAME_MAX)
1070 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
1071 wait->count, WAIT_NAME_MAX);
1072 wait->count = WAIT_NAME_MAX;
1075 for (int i = 1; i <= wait->count; i++)
1077 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1078 value[MESSAGE_ID_LENGTH] = 0;
1079 if (count_bad && value[0] == 0) break;
1080 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1081 Ustrspn(value, "0123456789"
1082 "abcdefghijklmnopqrstuvwxyz"
1083 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1085 printf("\n**** Data corrupted: bad character in message id ****\n");
1086 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
1087 printf("%02x ", value[j]);
1091 printf("%s ", value);
1092 t += MESSAGE_ID_LENGTH;
1101 callout = (dbdata_callout_cache *)record;
1102 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1104 if (oldlength > sizeof(dbdata_callout_cache_address))
1106 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1107 callout->postmaster_result);
1108 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1109 callout->random_result);
1113 case type_ratelimit:
1114 ratelimit = (dbdata_ratelimit *)record;
1115 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1116 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1117 printf("2 sender rate: % .3f\n", ratelimit->rate);
1118 if (Ustrstr(name, "/unique/") != NULL
1119 && oldlength >= sizeof(dbdata_ratelimit_unique))
1121 rate_unique = (dbdata_ratelimit_unique *)record;
1122 printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1123 printf("4 test filter membership\n");
1124 printf("5 add element to filter\n");
1129 session = (dbdata_tls_session *)value;
1130 printf("0 time stamp: %s\n", print_time(session->time_stamp));
1131 printf("1 session: .%s\n", session->session);
1136 /* The database is closed after each request */
1145 #endif /* EXIM_FIXDB */
1150 /*************************************************
1151 * The exim_tidydb main program *
1152 *************************************************/
1155 /* Utility program to tidy the contents of an exim database file. There is one
1158 -t <time> expiry time for old records - default 30 days
1160 For backwards compatibility, an -f option is recognized and ignored. (It used
1161 to request a "full" tidy. This version always does the whole job.) */
1164 typedef struct key_item {
1165 struct key_item *next;
1171 main(int argc, char **cargv)
1173 struct stat statbuf;
1174 int maxkeep = 30 * 24 * 60 * 60;
1175 int dbdata_type, i, oldest, path_len;
1176 key_item *keychain = NULL;
1180 EXIM_CURSOR *cursor;
1181 uschar **argv = USS cargv;
1187 /* Scan the options */
1189 for (i = 1; i < argc; i++)
1191 if (argv[i][0] != '-') break;
1192 if (Ustrcmp(argv[i], "-f") == 0) continue;
1193 if (Ustrcmp(argv[i], "-t") == 0)
1201 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1202 (void)sscanf(CS s, "%d%n", &value, &count);
1206 case 'w': value *= 7;
1207 case 'd': value *= 24;
1208 case 'h': value *= 60;
1209 case 'm': value *= 60;
1212 default: usage(US"tidydb", US" [-t <time>]");
1217 else usage(US"tidydb", US" [-t <time>]");
1220 /* Adjust argument values and process arguments */
1225 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1227 /* Compute the oldest keep time, verify what we are doing, and open the
1230 oldest = time(NULL) - maxkeep;
1231 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1233 spool_directory = argv[1];
1234 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
1237 /* Prepare for building file names */
1239 sprintf(CS buffer, "%s/input/", argv[1]);
1240 path_len = Ustrlen(buffer);
1243 /* It appears, by experiment, that it is a bad idea to make changes
1244 to the file while scanning it. Pity the man page doesn't warn you about that.
1245 Therefore, we scan and build a list of all the keys. Then we use that to
1246 read the records and possibly update them. */
1248 for (key = dbfn_scan(dbm, TRUE, &cursor);
1250 key = dbfn_scan(dbm, FALSE, &cursor))
1252 key_item *k = store_get(sizeof(key_item) + Ustrlen(key), is_tainted(key));
1255 Ustrcpy(k->key, key);
1258 /* Now scan the collected keys and operate on the records, resetting
1259 the store each time round. */
1261 for (; keychain && (reset_point = store_mark()); store_reset(reset_point))
1263 dbdata_generic *value;
1265 key = keychain->key;
1266 keychain = keychain->next;
1267 value = dbfn_read_with_length(dbm, key, NULL);
1269 /* A continuation record may have been deleted or renamed already, so
1270 non-existence is not serious. */
1272 if (!value) continue;
1274 /* Delete if too old */
1276 if (value->time_stamp < oldest)
1278 printf("deleted %s (too old)\n", key);
1279 dbfn_delete(dbm, key);
1283 /* Do database-specific tidying for wait databases, and message-
1284 specific tidying for the retry database. */
1286 if (dbdata_type == type_wait)
1288 dbdata_wait *wait = (dbdata_wait *)value;
1289 BOOL update = FALSE;
1291 /* Leave corrupt records alone */
1293 if (wait->time_stamp > time(NULL))
1295 printf("**** Data for '%s' corrupted\n time in future: %s\n",
1296 key, print_time(((dbdata_generic *)value)->time_stamp));
1299 if (wait->count > WAIT_NAME_MAX)
1301 printf("**** Data for '%s' corrupted\n count=%d=0x%x max=%d\n",
1302 key, wait->count, wait->count, WAIT_NAME_MAX);
1305 if (wait->sequence > WAIT_CONT_MAX)
1307 printf("**** Data for '%s' corrupted\n sequence=%d=0x%x max=%d\n",
1308 key, wait->sequence, wait->sequence, WAIT_CONT_MAX);
1312 /* Record over 1 year old; just remove it */
1314 if (wait->time_stamp < time(NULL) - 365*24*60*60)
1316 dbfn_delete(dbm, key);
1317 printf("deleted %s (too old)\n", key);
1321 /* Loop for renamed continuation records. For each message id,
1322 check to see if the message exists, and if not, remove its entry
1323 from the record. Because of the possibility of split input directories,
1324 we must look in both possible places for a -D file. */
1328 int length = wait->count * MESSAGE_ID_LENGTH;
1330 for (int offset = length - MESSAGE_ID_LENGTH;
1331 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1333 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1334 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1336 if (Ustat(buffer, &statbuf) != 0)
1338 buffer[path_len] = wait->text[offset+5];
1339 buffer[path_len+1] = '/';
1340 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1341 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1343 if (Ustat(buffer, &statbuf) != 0)
1345 int left = length - offset - MESSAGE_ID_LENGTH;
1346 if (left > 0) Ustrncpy(wait->text + offset,
1347 wait->text + offset + MESSAGE_ID_LENGTH, left);
1349 length -= MESSAGE_ID_LENGTH;
1355 /* If record is empty and the main record, either delete it or rename
1356 the next continuation, repeating if that is also empty. */
1358 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1360 while (wait->count == 0 && wait->sequence > 0)
1363 dbdata_generic *newvalue;
1364 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1365 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1366 if (newvalue != NULL)
1369 wait = (dbdata_wait *)newvalue;
1370 dbfn_delete(dbm, newkey);
1371 printf("renamed %s\n", newkey);
1374 else wait->sequence--;
1377 /* If we have ended up with an empty main record, delete it
1378 and break the loop. Otherwise the new record will be scanned. */
1380 if (wait->count == 0 && wait->sequence == 0)
1382 dbfn_delete(dbm, key);
1383 printf("deleted %s (empty)\n", key);
1389 /* If not an empty main record, break the loop */
1394 /* Re-write the record if required */
1398 printf("updated %s\n", key);
1399 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1400 wait->count * MESSAGE_ID_LENGTH);
1404 /* If a retry record's key ends with a message-id, check that that message
1405 still exists; if not, remove this record. */
1407 else if (dbdata_type == type_retry)
1410 int len = Ustrlen(key);
1412 if (len < MESSAGE_ID_LENGTH + 1) continue;
1413 id = key + len - MESSAGE_ID_LENGTH - 1;
1414 if (*id++ != ':') continue;
1416 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1417 if (i == 6 || i == 13)
1418 { if (id[i] != '-') break; }
1420 { if (!isalnum(id[i])) break; }
1421 if (i < MESSAGE_ID_LENGTH) continue;
1423 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1424 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1426 if (Ustat(buffer, &statbuf) != 0)
1428 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1429 if (Ustat(buffer, &statbuf) != 0)
1431 dbfn_delete(dbm, key);
1432 printf("deleted %s (no message)\n", key);
1439 printf("Tidying complete\n");
1443 #endif /* EXIM_TIDYDB */
1445 /* End of exim_dbutil.c */