1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
6 /* See the file NOTICE for conditions of use and distribution. */
9 /* This single source file is used to compile three utility programs for
10 maintaining Exim hints databases.
12 exim_dumpdb dumps out the contents
13 exim_fixdb patches the database (really for Exim maintenance/testing)
14 exim_tidydb removed obsolete data
16 In all cases, the first argument is the name of the spool directory. The second
17 argument is the name of the database file. The available names are:
19 retry: retry delivery information
20 misc: miscellaneous hints data
21 wait-<t>: message waiting information; <t> is a transport name
22 callout: callout verification cache
23 tls: TLS session resumption cache
25 There are a number of common subroutines, followed by three main programs,
26 whose inclusion is controlled by -D on the compilation command. */
32 /* Identifiers for the different database types. */
37 #define type_callout 4
38 #define type_ratelimit 5
42 /* This is used by our cut-down dbfn_open(). */
44 uschar *spool_directory;
47 /******************************************************************************/
48 /* dummies needed by Solaris build */
53 readconf_printtime(int t)
56 string_vformat_trc(gstring * g, const uschar * func, unsigned line,
57 unsigned size_limit, unsigned flags, const char *format, va_list ap)
60 string_sprintf_trc(const char * fmt, const uschar * func, unsigned line, ...)
63 struct global_flags f;
64 unsigned int log_selector[1];
66 BOOL split_spool_directory;
67 /******************************************************************************/
70 /*************************************************
71 * Berkeley DB error callback *
72 *************************************************/
74 /* For Berkeley DB >= 2, we can define a function to be called in case of DB
75 errors. This should help with debugging strange DB problems, e.g. getting "File
76 exists" when you try to open a db file. The API changed at release 4.3. */
78 #if defined(USE_DB) && defined(DB_VERSION_STRING)
80 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
81 dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
85 dbfn_bdb_error_callback(const char *pfx, char *msg)
89 printf("Berkeley DB error: %s\n", msg);
95 /*************************************************
97 *************************************************/
99 SIGNAL_BOOL sigalrm_seen;
102 sigalrm_handler(int sig)
104 sig = sig; /* Keep picky compilers happy */
110 /*************************************************
111 * Output usage message and exit *
112 *************************************************/
115 usage(uschar *name, uschar *options)
117 printf("Usage: exim_%s%s <spool-directory> <database-name>\n", name, options);
118 printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit | tls\n");
124 /*************************************************
125 * Sort out the command arguments *
126 *************************************************/
128 /* This function checks that there are exactly 2 arguments, and checks the
129 second of them to be sure it is a known database name. */
132 check_args(int argc, uschar **argv, uschar *name, uschar *options)
136 if (Ustrcmp(argv[2], "retry") == 0) return type_retry;
137 if (Ustrcmp(argv[2], "misc") == 0) return type_misc;
138 if (Ustrncmp(argv[2], "wait-", 5) == 0) return type_wait;
139 if (Ustrcmp(argv[2], "callout") == 0) return type_callout;
140 if (Ustrcmp(argv[2], "ratelimit") == 0) return type_ratelimit;
141 if (Ustrcmp(argv[2], "tls") == 0) return type_tls;
143 usage(name, options);
144 return -1; /* Never obeyed */
149 /*************************************************
150 * Handle attempts to write the log *
151 *************************************************/
153 /* The message gets written to stderr when log_write() is called from a
154 utility. The message always gets '\n' added on the end of it. These calls come
155 from modules such as store.c when things go drastically wrong (e.g. malloc()
156 failing). In normal use they won't get obeyed.
159 selector not relevant when running a utility
160 flags not relevant when running a utility
161 format a printf() format
162 ... arguments for format
168 log_write(unsigned int selector, int flags, const char *format, ...)
171 va_start(ap, format);
172 vfprintf(stderr, format, ap);
173 fprintf(stderr, "\n");
175 selector = selector; /* Keep picky compilers happy */
181 /*************************************************
182 * Format a time value for printing *
183 *************************************************/
185 static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss ")];
190 struct tm *tmstr = localtime(&t);
191 Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
197 /*************************************************
198 * Format a cache value for printing *
199 *************************************************/
202 print_cache(int value)
204 return (value == ccache_accept)? US"accept" :
205 (value == ccache_reject)? US"reject" :
211 /*************************************************
213 *************************************************/
220 time_t now = time(NULL);
221 struct tm *tm = localtime(&now);
226 for (uschar * t = s + Ustrlen(s) - 1; t >= s; t--)
228 if (*t == ':') continue;
229 if (!isdigit((uschar)*t)) return -1;
234 if (!isdigit((uschar)*t)) return -1;
235 value = value + (*t - '0')*10;
240 case 0: tm->tm_min = value; break;
241 case 1: tm->tm_hour = value; break;
242 case 2: tm->tm_mday = value; break;
243 case 3: tm->tm_mon = value - 1; break;
244 case 4: tm->tm_year = (value < 90)? value + 100 : value; break;
251 #endif /* EXIM_FIXDB */
255 /*************************************************
256 * Open and lock a database file *
257 *************************************************/
259 /* This is a cut-down version from the function in dbfn.h that Exim itself
260 uses. We assume the database exists, and therefore give up if we cannot open
264 name The single-component name of one of Exim's database files.
265 flags O_RDONLY or O_RDWR
266 dbblock Points to an open_db block to be filled in.
270 Returns: NULL if the open failed, or the locking failed.
271 On success, dbblock is returned. This contains the dbm pointer and
272 the fd of the locked lock file.
276 dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof, BOOL panic)
279 struct flock lock_data;
280 BOOL read_only = flags == O_RDONLY;
281 uschar * dirname, * filename;
283 /* The first thing to do is to open a separate file on which to lock. This
284 ensures that Exim has exclusive use of the database before it even tries to
285 open it. If there is a database, there should be a lock file in existence. */
287 #ifdef COMPILE_UTILITY
288 if ( asprintf(CSS &dirname, "%s/db", spool_directory) < 0
289 || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
292 dirname = string_sprintf("%s/db", spool_directory);
293 filename = string_sprintf("%s/%s.lockfile", dirname, name);
296 dbblock->lockfd = Uopen(filename, flags, 0);
297 if (dbblock->lockfd < 0)
299 printf("** Failed to open database lock file %s: %s\n", filename,
304 /* Now we must get a lock on the opened lock file; do this with a blocking
305 lock that times out. */
307 lock_data.l_type = read_only ? F_RDLCK : F_WRLCK;
308 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
310 sigalrm_seen = FALSE;
311 os_non_restarting_signal(SIGALRM, sigalrm_handler);
312 ALARM(EXIMDB_LOCK_TIMEOUT);
313 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
316 if (sigalrm_seen) errno = ETIMEDOUT;
319 printf("** Failed to get %s lock for %s: %s",
320 flags & O_WRONLY ? "write" : "read",
322 errno == ETIMEDOUT ? "timed out" : strerror(errno));
323 (void)close(dbblock->lockfd);
327 /* At this point we have an opened and locked separate lock file, that is,
328 exclusive access to the database, so we can go ahead and open it. */
330 #ifdef COMPILE_UTILITY
331 if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
333 filename = string_sprintf("%s/%s", dirname, name);
335 EXIM_DBOPEN(filename, dirname, flags, 0, &(dbblock->dbptr));
339 printf("** Failed to open DBM file %s for %s:\n %s%s\n", filename,
340 read_only? "reading" : "writing", strerror(errno),
342 " (or Berkeley DB error while opening)"
347 (void)close(dbblock->lockfd);
357 /*************************************************
358 * Unlock and close a database file *
359 *************************************************/
361 /* Closing a file automatically unlocks it, so after closing the database, just
364 Argument: a pointer to an open database block
369 dbfn_close(open_db *dbblock)
371 EXIM_DBCLOSE(dbblock->dbptr);
372 (void)close(dbblock->lockfd);
378 /*************************************************
379 * Read from database file *
380 *************************************************/
382 /* Passing back the pointer unchanged is useless, because there is no guarantee
383 of alignment. Since all the records used by Exim need to be properly aligned to
384 pick out the timestamps, etc., do the copying centrally here.
387 dbblock a pointer to an open database block
388 key the key of the record to be read
389 length where to put the length (or NULL if length not wanted)
391 Returns: a pointer to the retrieved record, or
392 NULL if the record is not found
396 dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
399 EXIM_DATUM key_datum, result_datum;
400 int klen = Ustrlen(key) + 1;
401 uschar * key_copy = store_get(klen, is_tainted(key));
403 memcpy(key_copy, key, klen);
405 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
406 EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */
407 EXIM_DATUM_DATA(key_datum) = CS key_copy;
408 EXIM_DATUM_SIZE(key_datum) = klen;
410 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
412 /* Assume for now that anything stored could have been tainted. Properly
413 we should store the taint status along with the data. */
415 yield = store_get(EXIM_DATUM_SIZE(result_datum), TRUE);
416 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
417 if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
419 EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */
425 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
427 /*************************************************
428 * Write to database file *
429 *************************************************/
433 dbblock a pointer to an open database block
434 key the key of the record to be written
435 ptr a pointer to the record to be written
436 length the length of the record to be written
438 Returns: the yield of the underlying dbm or db "write" function. If this
439 is dbm, the value is zero for OK.
443 dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
445 EXIM_DATUM key_datum, value_datum;
446 dbdata_generic *gptr = (dbdata_generic *)ptr;
447 int klen = Ustrlen(key) + 1;
448 uschar * key_copy = store_get(klen, is_tainted(key));
450 memcpy(key_copy, key, klen);
451 gptr->time_stamp = time(NULL);
453 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
454 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
455 EXIM_DATUM_DATA(key_datum) = CS key_copy;
456 EXIM_DATUM_SIZE(key_datum) = klen;
457 EXIM_DATUM_DATA(value_datum) = CS ptr;
458 EXIM_DATUM_SIZE(value_datum) = length;
459 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
464 /*************************************************
465 * Delete record from database file *
466 *************************************************/
470 dbblock a pointer to an open database block
471 key the key of the record to be deleted
473 Returns: the yield of the underlying dbm or db "delete" function.
477 dbfn_delete(open_db *dbblock, const uschar *key)
479 int klen = Ustrlen(key) + 1;
480 uschar * key_copy = store_get(klen, is_tainted(key));
482 memcpy(key_copy, key, klen);
483 EXIM_DATUM key_datum;
484 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
485 EXIM_DATUM_DATA(key_datum) = CS key_copy;
486 EXIM_DATUM_SIZE(key_datum) = klen;
487 return EXIM_DBDEL(dbblock->dbptr, key_datum);
490 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
494 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
495 /*************************************************
496 * Scan the keys of a database file *
497 *************************************************/
501 dbblock a pointer to an open database block
502 start TRUE if starting a new scan
503 FALSE if continuing with the current scan
504 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
505 that use the notion of a cursor
507 Returns: the next record from the file, or
508 NULL if there are no more
512 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
514 EXIM_DATUM key_datum, value_datum;
516 value_datum = value_datum; /* dummy; not all db libraries use this */
518 /* Some dbm require an initialization */
520 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
522 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
523 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
525 yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
526 US EXIM_DATUM_DATA(key_datum) : NULL;
528 /* Some dbm require a termination */
530 if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
533 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
538 /*************************************************
539 * The exim_dumpdb main program *
540 *************************************************/
543 main(int argc, char **cargv)
550 uschar **argv = USS cargv;
551 uschar keybuffer[1024];
553 /* Check the arguments, and open the database */
555 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
556 spool_directory = argv[1];
557 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
560 /* Scan the file, formatting the information for each entry. Note
561 that data is returned in a malloc'ed block, in order that it be
562 correctly aligned. */
564 for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
566 key = dbfn_scan(dbm, FALSE, &cursor))
570 dbdata_callout_cache *callout;
571 dbdata_ratelimit *ratelimit;
572 dbdata_ratelimit_unique *rate_unique;
573 dbdata_tls_session *session;
577 uschar name[MESSAGE_ID_LENGTH + 1];
579 rmark reset_point = store_mark();
581 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
582 which might change. */
584 if (Ustrlen(key) > sizeof(keybuffer) - 1)
586 printf("**** Overlong key encountered: %s\n", key);
589 Ustrcpy(keybuffer, key);
591 if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
592 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
593 "was not found in the file - something is wrong!\n",
597 /* Note: don't use print_time more than once in one statement, since
598 it uses a single buffer. */
603 retry = (dbdata_retry *)value;
604 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
605 retry->more_errno, retry->text,
606 print_time(retry->first_failed));
607 printf("%s ", print_time(retry->last_try));
608 printf("%s %s\n", print_time(retry->next_try),
609 (retry->expired)? "*" : "");
613 wait = (dbdata_wait *)value;
614 printf("%s ", keybuffer);
616 name[MESSAGE_ID_LENGTH] = 0;
618 if (wait->count > WAIT_NAME_MAX)
621 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
622 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
623 wait->count = WAIT_NAME_MAX;
624 yield = count_bad = 1;
626 for (int i = 1; i <= wait->count; i++)
628 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
629 if (count_bad && name[0] == 0) break;
630 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
631 Ustrspn(name, "0123456789"
632 "abcdefghijklmnopqrstuvwxyz"
633 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
636 "**** Data for %s corrupted: bad character in message id\n",
638 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
639 fprintf(stderr, "%02x ", name[j]);
640 fprintf(stderr, "\n");
645 t += MESSAGE_ID_LENGTH;
651 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
656 callout = (dbdata_callout_cache *)value;
658 /* New-style address record */
660 if (length == sizeof(dbdata_callout_cache_address))
662 printf("%s %s callout=%s\n",
663 print_time(((dbdata_generic *)value)->time_stamp),
665 print_cache(callout->result));
668 /* New-style domain record */
670 else if (length == sizeof(dbdata_callout_cache))
672 printf("%s %s callout=%s postmaster=%s",
673 print_time(((dbdata_generic *)value)->time_stamp),
675 print_cache(callout->result),
676 print_cache(callout->postmaster_result));
677 if (callout->postmaster_result != ccache_unknown)
678 printf(" (%s)", print_time(callout->postmaster_stamp));
679 printf(" random=%s", print_cache(callout->random_result));
680 if (callout->random_result != ccache_unknown)
681 printf(" (%s)", print_time(callout->random_stamp));
688 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
690 ratelimit = (dbdata_ratelimit *)value;
691 rate_unique = (dbdata_ratelimit_unique *)value;
692 printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
693 print_time(ratelimit->time_stamp),
694 ratelimit->time_usec, ratelimit->rate,
695 print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
700 ratelimit = (dbdata_ratelimit *)value;
701 printf("%s.%06d rate: %10.3f key: %s\n",
702 print_time(ratelimit->time_stamp),
703 ratelimit->time_usec, ratelimit->rate,
709 session = (dbdata_tls_session *)value;
710 printf(" %s %.*s\n", keybuffer, length, session->session);
714 store_reset(reset_point);
721 #endif /* EXIM_DUMPDB */
727 /*************************************************
728 * The exim_fixdb main program *
729 *************************************************/
731 /* In order not to hold the database lock any longer than is necessary, each
732 operation on the database uses a separate open/close call. This is expensive,
733 but then using this utility is not expected to be very common. Its main use is
734 to provide a way of patching up hints databases in order to run tests.
739 This causes the data from the given record to be displayed, or "not found"
740 to be output. Note that in the retry database, destination names are
741 preceded by R: or T: for router or transport retry info.
744 This causes the given record to be deleted or "not found" to be output.
746 (3) <record name> <field number> <value>
747 This sets the given value into the given field, identified by a number
748 which is output by the display command. Not all types of record can
752 This exits from exim_fixdb.
754 If the record name is omitted from (2) or (3), the previously used record name
758 int main(int argc, char **cargv)
761 uschar **argv = USS cargv;
766 name[0] = 0; /* No name set */
768 /* Sort out the database type, verify what we are working on and then process
771 dbdata_type = check_args(argc, argv, US"fixdb", US"");
772 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
774 for(; (reset_point = store_mark()); store_reset(reset_point))
781 dbdata_callout_cache *callout;
782 dbdata_ratelimit *ratelimit;
783 dbdata_ratelimit_unique *rate_unique;
784 dbdata_tls_session *session;
787 uschar field[256], value[256];
790 if (Ufgets(buffer, 256, stdin) == NULL) break;
792 buffer[Ustrlen(buffer)-1] = 0;
793 field[0] = value[0] = 0;
795 /* If the buffer contains just one digit, or just consists of "d", use the
796 previous name for an update. */
798 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
799 || Ustrcmp(buffer, "d") == 0)
803 printf("No previous record name is set\n");
806 (void)sscanf(CS buffer, "%s %s", field, value);
811 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
814 /* Handle an update request */
819 spool_directory = argv[1];
821 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
824 if (Ustrcmp(field, "d") == 0)
826 if (value[0] != 0) printf("unexpected value after \"d\"\n");
827 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
828 "not found" : "deleted");
833 else if (isdigit((uschar)field[0]))
835 int fieldno = Uatoi(field);
838 printf("value missing\n");
844 record = dbfn_read_with_length(dbm, name, &oldlength);
845 if (record == NULL) printf("not found\n"); else
848 /*int length = 0; Stops compiler warning */
853 retry = (dbdata_retry *)record;
854 /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
858 case 0: retry->basic_errno = Uatoi(value);
860 case 1: retry->more_errno = Uatoi(value);
862 case 2: if ((tt = read_time(value)) > 0) retry->first_failed = tt;
863 else printf("bad time value\n");
865 case 3: if ((tt = read_time(value)) > 0) retry->last_try = tt;
866 else printf("bad time value\n");
868 case 4: if ((tt = read_time(value)) > 0) retry->next_try = tt;
869 else printf("bad time value\n");
871 case 5: if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
872 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
873 else printf("\"yes\" or \"no\" expected=n");
875 default: printf("unknown field number\n");
882 printf("Can't change contents of wait database record\n");
886 printf("Can't change contents of misc database record\n");
890 callout = (dbdata_callout_cache *)record;
891 /* length = sizeof(dbdata_callout_cache); */
894 case 0: callout->result = Uatoi(value);
896 case 1: callout->postmaster_result = Uatoi(value);
898 case 2: callout->random_result = Uatoi(value);
900 default: printf("unknown field number\n");
907 ratelimit = (dbdata_ratelimit *)record;
910 case 0: if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
911 else printf("bad time value\n");
913 case 1: ratelimit->time_usec = Uatoi(value);
915 case 2: ratelimit->rate = Ustrtod(value, NULL);
917 case 3: if (Ustrstr(name, "/unique/") != NULL
918 && oldlength >= sizeof(dbdata_ratelimit_unique))
920 rate_unique = (dbdata_ratelimit_unique *)record;
921 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
922 else printf("bad time value\n");
925 /* else fall through */
927 case 5: if (Ustrstr(name, "/unique/") != NULL
928 && oldlength >= sizeof(dbdata_ratelimit_unique))
936 md5_end(&md5info, value, Ustrlen(value), md5sum);
937 hash = md5sum[0] << 0 | md5sum[1] << 8
938 | md5sum[2] << 16 | md5sum[3] << 24;
939 hinc = md5sum[4] << 0 | md5sum[5] << 8
940 | md5sum[6] << 16 | md5sum[7] << 24;
941 rate_unique = (dbdata_ratelimit_unique *)record;
943 for (unsigned n = 0; n < 8; n++, hash += hinc)
945 int bit = 1 << (hash % 8);
946 int byte = (hash / 8) % rate_unique->bloom_size;
947 if ((rate_unique->bloom[byte] & bit) == 0)
950 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
954 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
957 /* else fall through */
958 default: printf("unknown field number\n");
965 printf("Can't change contents of tls database record\n");
969 dbfn_write(dbm, name, record, oldlength);
976 printf("field number or d expected\n");
981 if (!verify) continue;
984 /* The "name" q causes an exit */
986 else if (Ustrcmp(name, "q") == 0) return 0;
988 /* Handle a read request, or verify after an update. */
990 spool_directory = argv[1];
991 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
994 if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
996 printf("record %s not found\n", name);
1002 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
1006 retry = (dbdata_retry *)record;
1007 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
1008 printf("1 extra data: %d\n", retry->more_errno);
1009 printf("2 first failed: %s\n", print_time(retry->first_failed));
1010 printf("3 last try: %s\n", print_time(retry->last_try));
1011 printf("4 next try: %s\n", print_time(retry->next_try));
1012 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
1016 wait = (dbdata_wait *)record;
1018 printf("Sequence: %d\n", wait->sequence);
1019 if (wait->count > WAIT_NAME_MAX)
1021 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
1022 wait->count, WAIT_NAME_MAX);
1023 wait->count = WAIT_NAME_MAX;
1026 for (int i = 1; i <= wait->count; i++)
1028 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1029 value[MESSAGE_ID_LENGTH] = 0;
1030 if (count_bad && value[0] == 0) break;
1031 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1032 Ustrspn(value, "0123456789"
1033 "abcdefghijklmnopqrstuvwxyz"
1034 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1036 printf("\n**** Data corrupted: bad character in message id ****\n");
1037 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
1038 printf("%02x ", value[j]);
1042 printf("%s ", value);
1043 t += MESSAGE_ID_LENGTH;
1052 callout = (dbdata_callout_cache *)record;
1053 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1055 if (oldlength > sizeof(dbdata_callout_cache_address))
1057 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1058 callout->postmaster_result);
1059 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1060 callout->random_result);
1064 case type_ratelimit:
1065 ratelimit = (dbdata_ratelimit *)record;
1066 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1067 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1068 printf("2 sender rate: % .3f\n", ratelimit->rate);
1069 if (Ustrstr(name, "/unique/") != NULL
1070 && oldlength >= sizeof(dbdata_ratelimit_unique))
1072 rate_unique = (dbdata_ratelimit_unique *)record;
1073 printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1074 printf("4 test filter membership\n");
1075 printf("5 add element to filter\n");
1080 session = (dbdata_tls_session *)value;
1081 printf("0 time stamp: %s\n", print_time(session->time_stamp));
1082 printf("1 session: .%s\n", session->session);
1087 /* The database is closed after each request */
1096 #endif /* EXIM_FIXDB */
1101 /*************************************************
1102 * The exim_tidydb main program *
1103 *************************************************/
1106 /* Utility program to tidy the contents of an exim database file. There is one
1109 -t <time> expiry time for old records - default 30 days
1111 For backwards compatibility, an -f option is recognized and ignored. (It used
1112 to request a "full" tidy. This version always does the whole job.) */
1115 typedef struct key_item {
1116 struct key_item *next;
1121 int main(int argc, char **cargv)
1123 struct stat statbuf;
1124 int maxkeep = 30 * 24 * 60 * 60;
1125 int dbdata_type, i, oldest, path_len;
1126 key_item *keychain = NULL;
1130 EXIM_CURSOR *cursor;
1131 uschar **argv = USS cargv;
1135 /* Scan the options */
1137 for (i = 1; i < argc; i++)
1139 if (argv[i][0] != '-') break;
1140 if (Ustrcmp(argv[i], "-f") == 0) continue;
1141 if (Ustrcmp(argv[i], "-t") == 0)
1149 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1150 (void)sscanf(CS s, "%d%n", &value, &count);
1154 case 'w': value *= 7;
1155 case 'd': value *= 24;
1156 case 'h': value *= 60;
1157 case 'm': value *= 60;
1160 default: usage(US"tidydb", US" [-t <time>]");
1165 else usage(US"tidydb", US" [-t <time>]");
1168 /* Adjust argument values and process arguments */
1173 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1175 /* Compute the oldest keep time, verify what we are doing, and open the
1178 oldest = time(NULL) - maxkeep;
1179 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1181 spool_directory = argv[1];
1182 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
1185 /* Prepare for building file names */
1187 sprintf(CS buffer, "%s/input/", argv[1]);
1188 path_len = Ustrlen(buffer);
1191 /* It appears, by experiment, that it is a bad idea to make changes
1192 to the file while scanning it. Pity the man page doesn't warn you about that.
1193 Therefore, we scan and build a list of all the keys. Then we use that to
1194 read the records and possibly update them. */
1196 for (key = dbfn_scan(dbm, TRUE, &cursor);
1198 key = dbfn_scan(dbm, FALSE, &cursor))
1200 key_item *k = store_get(sizeof(key_item) + Ustrlen(key), is_tainted(key));
1203 Ustrcpy(k->key, key);
1206 /* Now scan the collected keys and operate on the records, resetting
1207 the store each time round. */
1209 for (; keychain && (reset_point = store_mark()); store_reset(reset_point))
1211 dbdata_generic *value;
1213 key = keychain->key;
1214 keychain = keychain->next;
1215 value = dbfn_read_with_length(dbm, key, NULL);
1217 /* A continuation record may have been deleted or renamed already, so
1218 non-existence is not serious. */
1220 if (value == NULL) continue;
1222 /* Delete if too old */
1224 if (value->time_stamp < oldest)
1226 printf("deleted %s (too old)\n", key);
1227 dbfn_delete(dbm, key);
1231 /* Do database-specific tidying for wait databases, and message-
1232 specific tidying for the retry database. */
1234 if (dbdata_type == type_wait)
1236 dbdata_wait *wait = (dbdata_wait *)value;
1237 BOOL update = FALSE;
1239 /* Leave corrupt records alone */
1241 if (wait->count > WAIT_NAME_MAX)
1243 printf("**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
1244 key, wait->count, wait->count, WAIT_NAME_MAX);
1248 /* Loop for renamed continuation records. For each message id,
1249 check to see if the message exists, and if not, remove its entry
1250 from the record. Because of the possibility of split input directories,
1251 we must look in both possible places for a -D file. */
1255 int length = wait->count * MESSAGE_ID_LENGTH;
1257 for (int offset = length - MESSAGE_ID_LENGTH;
1258 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1260 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1261 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1263 if (Ustat(buffer, &statbuf) != 0)
1265 buffer[path_len] = wait->text[offset+5];
1266 buffer[path_len+1] = '/';
1267 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1268 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1270 if (Ustat(buffer, &statbuf) != 0)
1272 int left = length - offset - MESSAGE_ID_LENGTH;
1273 if (left > 0) Ustrncpy(wait->text + offset,
1274 wait->text + offset + MESSAGE_ID_LENGTH, left);
1276 length -= MESSAGE_ID_LENGTH;
1282 /* If record is empty and the main record, either delete it or rename
1283 the next continuation, repeating if that is also empty. */
1285 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1287 while (wait->count == 0 && wait->sequence > 0)
1290 dbdata_generic *newvalue;
1291 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1292 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1293 if (newvalue != NULL)
1296 wait = (dbdata_wait *)newvalue;
1297 dbfn_delete(dbm, newkey);
1298 printf("renamed %s\n", newkey);
1301 else wait->sequence--;
1304 /* If we have ended up with an empty main record, delete it
1305 and break the loop. Otherwise the new record will be scanned. */
1307 if (wait->count == 0 && wait->sequence == 0)
1309 dbfn_delete(dbm, key);
1310 printf("deleted %s (empty)\n", key);
1316 /* If not an empty main record, break the loop */
1321 /* Re-write the record if required */
1325 printf("updated %s\n", key);
1326 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1327 wait->count * MESSAGE_ID_LENGTH);
1331 /* If a retry record's key ends with a message-id, check that that message
1332 still exists; if not, remove this record. */
1334 else if (dbdata_type == type_retry)
1337 int len = Ustrlen(key);
1339 if (len < MESSAGE_ID_LENGTH + 1) continue;
1340 id = key + len - MESSAGE_ID_LENGTH - 1;
1341 if (*id++ != ':') continue;
1343 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1344 if (i == 6 || i == 13)
1345 { if (id[i] != '-') break; }
1347 { if (!isalnum(id[i])) break; }
1348 if (i < MESSAGE_ID_LENGTH) continue;
1350 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1351 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1353 if (Ustat(buffer, &statbuf) != 0)
1355 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1356 if (Ustat(buffer, &statbuf) != 0)
1358 dbfn_delete(dbm, key);
1359 printf("deleted %s (no message)\n", key);
1366 printf("Tidying complete\n");
1370 #endif /* EXIM_TIDYDB */
1372 /* End of exim_dbutil.c */