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 */
50 string_vformat_trc(gstring * g, const uschar * func, unsigned line,
51 unsigned size_limit, unsigned flags, const char *format, va_list ap)
54 string_sprintf_trc(const char * fmt, const uschar * func, unsigned line, ...)
56 extern BOOL split_spool_directory;
57 extern uschar * queue_name;
58 /******************************************************************************/
61 /*************************************************
62 * Berkeley DB error callback *
63 *************************************************/
65 /* For Berkeley DB >= 2, we can define a function to be called in case of DB
66 errors. This should help with debugging strange DB problems, e.g. getting "File
67 exists" when you try to open a db file. The API changed at release 4.3. */
69 #if defined(USE_DB) && defined(DB_VERSION_STRING)
71 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
72 dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
76 dbfn_bdb_error_callback(const char *pfx, char *msg)
80 printf("Berkeley DB error: %s\n", msg);
86 /*************************************************
88 *************************************************/
90 SIGNAL_BOOL sigalrm_seen;
93 sigalrm_handler(int sig)
95 sig = sig; /* Keep picky compilers happy */
101 /*************************************************
102 * Output usage message and exit *
103 *************************************************/
106 usage(uschar *name, uschar *options)
108 printf("Usage: exim_%s%s <spool-directory> <database-name>\n", name, options);
109 printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit | tls\n");
115 /*************************************************
116 * Sort out the command arguments *
117 *************************************************/
119 /* This function checks that there are exactly 2 arguments, and checks the
120 second of them to be sure it is a known database name. */
123 check_args(int argc, uschar **argv, uschar *name, uschar *options)
127 if (Ustrcmp(argv[2], "retry") == 0) return type_retry;
128 if (Ustrcmp(argv[2], "misc") == 0) return type_misc;
129 if (Ustrncmp(argv[2], "wait-", 5) == 0) return type_wait;
130 if (Ustrcmp(argv[2], "callout") == 0) return type_callout;
131 if (Ustrcmp(argv[2], "ratelimit") == 0) return type_ratelimit;
132 if (Ustrcmp(argv[2], "tls") == 0) return type_tls;
134 usage(name, options);
135 return -1; /* Never obeyed */
140 /*************************************************
141 * Handle attempts to write the log *
142 *************************************************/
144 /* The message gets written to stderr when log_write() is called from a
145 utility. The message always gets '\n' added on the end of it. These calls come
146 from modules such as store.c when things go drastically wrong (e.g. malloc()
147 failing). In normal use they won't get obeyed.
150 selector not relevant when running a utility
151 flags not relevant when running a utility
152 format a printf() format
153 ... arguments for format
159 log_write(unsigned int selector, int flags, const char *format, ...)
162 va_start(ap, format);
163 vfprintf(stderr, format, ap);
164 fprintf(stderr, "\n");
166 selector = selector; /* Keep picky compilers happy */
172 /*************************************************
173 * Format a time value for printing *
174 *************************************************/
176 static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss ")];
181 struct tm *tmstr = localtime(&t);
182 Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
188 /*************************************************
189 * Format a cache value for printing *
190 *************************************************/
193 print_cache(int value)
195 return (value == ccache_accept)? US"accept" :
196 (value == ccache_reject)? US"reject" :
202 /*************************************************
204 *************************************************/
211 time_t now = time(NULL);
212 struct tm *tm = localtime(&now);
217 for (uschar * t = s + Ustrlen(s) - 1; t >= s; t--)
219 if (*t == ':') continue;
220 if (!isdigit((uschar)*t)) return -1;
225 if (!isdigit((uschar)*t)) return -1;
226 value = value + (*t - '0')*10;
231 case 0: tm->tm_min = value; break;
232 case 1: tm->tm_hour = value; break;
233 case 2: tm->tm_mday = value; break;
234 case 3: tm->tm_mon = value - 1; break;
235 case 4: tm->tm_year = (value < 90)? value + 100 : value; break;
242 #endif /* EXIM_FIXDB */
246 /*************************************************
247 * Open and lock a database file *
248 *************************************************/
250 /* This is a cut-down version from the function in dbfn.h that Exim itself
251 uses. We assume the database exists, and therefore give up if we cannot open
255 name The single-component name of one of Exim's database files.
256 flags O_RDONLY or O_RDWR
257 dbblock Points to an open_db block to be filled in.
261 Returns: NULL if the open failed, or the locking failed.
262 On success, dbblock is returned. This contains the dbm pointer and
263 the fd of the locked lock file.
267 dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof, BOOL panic)
270 struct flock lock_data;
271 BOOL read_only = flags == O_RDONLY;
272 uschar * dirname, * filename;
274 /* The first thing to do is to open a separate file on which to lock. This
275 ensures that Exim has exclusive use of the database before it even tries to
276 open it. If there is a database, there should be a lock file in existence. */
278 #ifdef COMPILE_UTILITY
279 if ( asprintf(CSS &dirname, "%s/db", spool_directory) < 0
280 || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
283 dirname = string_sprintf("%s/db", spool_directory);
284 filename = string_sprintf("%s/%s.lockfile", dirname, name);
287 dbblock->lockfd = Uopen(filename, flags, 0);
288 if (dbblock->lockfd < 0)
290 printf("** Failed to open database lock file %s: %s\n", filename,
295 /* Now we must get a lock on the opened lock file; do this with a blocking
296 lock that times out. */
298 lock_data.l_type = read_only ? F_RDLCK : F_WRLCK;
299 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
301 sigalrm_seen = FALSE;
302 os_non_restarting_signal(SIGALRM, sigalrm_handler);
303 ALARM(EXIMDB_LOCK_TIMEOUT);
304 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
307 if (sigalrm_seen) errno = ETIMEDOUT;
310 printf("** Failed to get %s lock for %s: %s",
311 flags & O_WRONLY ? "write" : "read",
313 errno == ETIMEDOUT ? "timed out" : strerror(errno));
314 (void)close(dbblock->lockfd);
318 /* At this point we have an opened and locked separate lock file, that is,
319 exclusive access to the database, so we can go ahead and open it. */
321 #ifdef COMPILE_UTILITY
322 if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
324 filename = string_sprintf("%s/%s", dirname, name);
326 EXIM_DBOPEN(filename, dirname, flags, 0, &(dbblock->dbptr));
330 printf("** Failed to open DBM file %s for %s:\n %s%s\n", filename,
331 read_only? "reading" : "writing", strerror(errno),
333 " (or Berkeley DB error while opening)"
338 (void)close(dbblock->lockfd);
348 /*************************************************
349 * Unlock and close a database file *
350 *************************************************/
352 /* Closing a file automatically unlocks it, so after closing the database, just
355 Argument: a pointer to an open database block
360 dbfn_close(open_db *dbblock)
362 EXIM_DBCLOSE(dbblock->dbptr);
363 (void)close(dbblock->lockfd);
369 /*************************************************
370 * Read from database file *
371 *************************************************/
373 /* Passing back the pointer unchanged is useless, because there is no guarantee
374 of alignment. Since all the records used by Exim need to be properly aligned to
375 pick out the timestamps, etc., do the copying centrally here.
378 dbblock a pointer to an open database block
379 key the key of the record to be read
380 length where to put the length (or NULL if length not wanted)
382 Returns: a pointer to the retrieved record, or
383 NULL if the record is not found
387 dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
390 EXIM_DATUM key_datum, result_datum;
391 int klen = Ustrlen(key) + 1;
392 uschar * key_copy = store_get(klen, is_tainted(key));
394 memcpy(key_copy, key, klen);
396 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
397 EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */
398 EXIM_DATUM_DATA(key_datum) = CS key_copy;
399 EXIM_DATUM_SIZE(key_datum) = klen;
401 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
403 /* Assume for now that anything stored could have been tainted. Properly
404 we should store the taint status along with the data. */
406 yield = store_get(EXIM_DATUM_SIZE(result_datum), TRUE);
407 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
408 if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
410 EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */
416 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
418 /*************************************************
419 * Write to database file *
420 *************************************************/
424 dbblock a pointer to an open database block
425 key the key of the record to be written
426 ptr a pointer to the record to be written
427 length the length of the record to be written
429 Returns: the yield of the underlying dbm or db "write" function. If this
430 is dbm, the value is zero for OK.
434 dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
436 EXIM_DATUM key_datum, value_datum;
437 dbdata_generic *gptr = (dbdata_generic *)ptr;
438 int klen = Ustrlen(key) + 1;
439 uschar * key_copy = store_get(klen, is_tainted(key));
441 memcpy(key_copy, key, klen);
442 gptr->time_stamp = time(NULL);
444 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
445 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
446 EXIM_DATUM_DATA(key_datum) = CS key_copy;
447 EXIM_DATUM_SIZE(key_datum) = klen;
448 EXIM_DATUM_DATA(value_datum) = CS ptr;
449 EXIM_DATUM_SIZE(value_datum) = length;
450 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
455 /*************************************************
456 * Delete record from database file *
457 *************************************************/
461 dbblock a pointer to an open database block
462 key the key of the record to be deleted
464 Returns: the yield of the underlying dbm or db "delete" function.
468 dbfn_delete(open_db *dbblock, const uschar *key)
470 int klen = Ustrlen(key) + 1;
471 uschar * key_copy = store_get(klen, is_tainted(key));
473 memcpy(key_copy, key, klen);
474 EXIM_DATUM key_datum;
475 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
476 EXIM_DATUM_DATA(key_datum) = CS key_copy;
477 EXIM_DATUM_SIZE(key_datum) = klen;
478 return EXIM_DBDEL(dbblock->dbptr, key_datum);
481 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
485 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
486 /*************************************************
487 * Scan the keys of a database file *
488 *************************************************/
492 dbblock a pointer to an open database block
493 start TRUE if starting a new scan
494 FALSE if continuing with the current scan
495 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
496 that use the notion of a cursor
498 Returns: the next record from the file, or
499 NULL if there are no more
503 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
505 EXIM_DATUM key_datum, value_datum;
507 value_datum = value_datum; /* dummy; not all db libraries use this */
509 /* Some dbm require an initialization */
511 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
513 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
514 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
516 yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
517 US EXIM_DATUM_DATA(key_datum) : NULL;
519 /* Some dbm require a termination */
521 if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
524 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
529 /*************************************************
530 * The exim_dumpdb main program *
531 *************************************************/
534 main(int argc, char **cargv)
541 uschar **argv = USS cargv;
542 uschar keybuffer[1024];
544 /* Check the arguments, and open the database */
546 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
547 spool_directory = argv[1];
548 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
551 /* Scan the file, formatting the information for each entry. Note
552 that data is returned in a malloc'ed block, in order that it be
553 correctly aligned. */
555 for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
557 key = dbfn_scan(dbm, FALSE, &cursor))
561 dbdata_callout_cache *callout;
562 dbdata_ratelimit *ratelimit;
563 dbdata_ratelimit_unique *rate_unique;
564 dbdata_tls_session *session;
568 uschar name[MESSAGE_ID_LENGTH + 1];
570 rmark reset_point = store_mark();
572 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
573 which might change. */
575 if (Ustrlen(key) > sizeof(keybuffer) - 1)
577 printf("**** Overlong key encountered: %s\n", key);
580 Ustrcpy(keybuffer, key);
582 if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
583 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
584 "was not found in the file - something is wrong!\n",
588 /* Note: don't use print_time more than once in one statement, since
589 it uses a single buffer. */
594 retry = (dbdata_retry *)value;
595 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
596 retry->more_errno, retry->text,
597 print_time(retry->first_failed));
598 printf("%s ", print_time(retry->last_try));
599 printf("%s %s\n", print_time(retry->next_try),
600 (retry->expired)? "*" : "");
604 wait = (dbdata_wait *)value;
605 printf("%s ", keybuffer);
607 name[MESSAGE_ID_LENGTH] = 0;
609 if (wait->count > WAIT_NAME_MAX)
612 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
613 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
614 wait->count = WAIT_NAME_MAX;
615 yield = count_bad = 1;
617 for (int i = 1; i <= wait->count; i++)
619 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
620 if (count_bad && name[0] == 0) break;
621 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
622 Ustrspn(name, "0123456789"
623 "abcdefghijklmnopqrstuvwxyz"
624 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
627 "**** Data for %s corrupted: bad character in message id\n",
629 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
630 fprintf(stderr, "%02x ", name[j]);
631 fprintf(stderr, "\n");
636 t += MESSAGE_ID_LENGTH;
642 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
647 callout = (dbdata_callout_cache *)value;
649 /* New-style address record */
651 if (length == sizeof(dbdata_callout_cache_address))
653 printf("%s %s callout=%s\n",
654 print_time(((dbdata_generic *)value)->time_stamp),
656 print_cache(callout->result));
659 /* New-style domain record */
661 else if (length == sizeof(dbdata_callout_cache))
663 printf("%s %s callout=%s postmaster=%s",
664 print_time(((dbdata_generic *)value)->time_stamp),
666 print_cache(callout->result),
667 print_cache(callout->postmaster_result));
668 if (callout->postmaster_result != ccache_unknown)
669 printf(" (%s)", print_time(callout->postmaster_stamp));
670 printf(" random=%s", print_cache(callout->random_result));
671 if (callout->random_result != ccache_unknown)
672 printf(" (%s)", print_time(callout->random_stamp));
679 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
681 ratelimit = (dbdata_ratelimit *)value;
682 rate_unique = (dbdata_ratelimit_unique *)value;
683 printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
684 print_time(ratelimit->time_stamp),
685 ratelimit->time_usec, ratelimit->rate,
686 print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
691 ratelimit = (dbdata_ratelimit *)value;
692 printf("%s.%06d rate: %10.3f key: %s\n",
693 print_time(ratelimit->time_stamp),
694 ratelimit->time_usec, ratelimit->rate,
700 session = (dbdata_tls_session *)value;
701 printf(" %s %.*s\n", keybuffer, length, session->session);
705 store_reset(reset_point);
712 #endif /* EXIM_DUMPDB */
718 /*************************************************
719 * The exim_fixdb main program *
720 *************************************************/
722 /* In order not to hold the database lock any longer than is necessary, each
723 operation on the database uses a separate open/close call. This is expensive,
724 but then using this utility is not expected to be very common. Its main use is
725 to provide a way of patching up hints databases in order to run tests.
730 This causes the data from the given record to be displayed, or "not found"
731 to be output. Note that in the retry database, destination names are
732 preceded by R: or T: for router or transport retry info.
735 This causes the given record to be deleted or "not found" to be output.
737 (3) <record name> <field number> <value>
738 This sets the given value into the given field, identified by a number
739 which is output by the display command. Not all types of record can
743 This exits from exim_fixdb.
745 If the record name is omitted from (2) or (3), the previously used record name
749 int main(int argc, char **cargv)
752 uschar **argv = USS cargv;
757 name[0] = 0; /* No name set */
759 /* Sort out the database type, verify what we are working on and then process
762 dbdata_type = check_args(argc, argv, US"fixdb", US"");
763 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
765 for(; (reset_point = store_mark()); store_reset(reset_point))
772 dbdata_callout_cache *callout;
773 dbdata_ratelimit *ratelimit;
774 dbdata_ratelimit_unique *rate_unique;
775 dbdata_tls_session *session;
778 uschar field[256], value[256];
781 if (Ufgets(buffer, 256, stdin) == NULL) break;
783 buffer[Ustrlen(buffer)-1] = 0;
784 field[0] = value[0] = 0;
786 /* If the buffer contains just one digit, or just consists of "d", use the
787 previous name for an update. */
789 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
790 || Ustrcmp(buffer, "d") == 0)
794 printf("No previous record name is set\n");
797 (void)sscanf(CS buffer, "%s %s", field, value);
802 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
805 /* Handle an update request */
810 spool_directory = argv[1];
812 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
815 if (Ustrcmp(field, "d") == 0)
817 if (value[0] != 0) printf("unexpected value after \"d\"\n");
818 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
819 "not found" : "deleted");
824 else if (isdigit((uschar)field[0]))
826 int fieldno = Uatoi(field);
829 printf("value missing\n");
835 record = dbfn_read_with_length(dbm, name, &oldlength);
836 if (record == NULL) printf("not found\n"); else
839 /*int length = 0; Stops compiler warning */
844 retry = (dbdata_retry *)record;
845 /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
849 case 0: retry->basic_errno = Uatoi(value);
851 case 1: retry->more_errno = Uatoi(value);
853 case 2: if ((tt = read_time(value)) > 0) retry->first_failed = tt;
854 else printf("bad time value\n");
856 case 3: if ((tt = read_time(value)) > 0) retry->last_try = tt;
857 else printf("bad time value\n");
859 case 4: if ((tt = read_time(value)) > 0) retry->next_try = tt;
860 else printf("bad time value\n");
862 case 5: if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
863 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
864 else printf("\"yes\" or \"no\" expected=n");
866 default: printf("unknown field number\n");
873 printf("Can't change contents of wait database record\n");
877 printf("Can't change contents of misc database record\n");
881 callout = (dbdata_callout_cache *)record;
882 /* length = sizeof(dbdata_callout_cache); */
885 case 0: callout->result = Uatoi(value);
887 case 1: callout->postmaster_result = Uatoi(value);
889 case 2: callout->random_result = Uatoi(value);
891 default: printf("unknown field number\n");
898 ratelimit = (dbdata_ratelimit *)record;
901 case 0: if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
902 else printf("bad time value\n");
904 case 1: ratelimit->time_usec = Uatoi(value);
906 case 2: ratelimit->rate = Ustrtod(value, NULL);
908 case 3: if (Ustrstr(name, "/unique/") != NULL
909 && oldlength >= sizeof(dbdata_ratelimit_unique))
911 rate_unique = (dbdata_ratelimit_unique *)record;
912 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
913 else printf("bad time value\n");
916 /* else fall through */
918 case 5: if (Ustrstr(name, "/unique/") != NULL
919 && oldlength >= sizeof(dbdata_ratelimit_unique))
927 md5_end(&md5info, value, Ustrlen(value), md5sum);
928 hash = md5sum[0] << 0 | md5sum[1] << 8
929 | md5sum[2] << 16 | md5sum[3] << 24;
930 hinc = md5sum[4] << 0 | md5sum[5] << 8
931 | md5sum[6] << 16 | md5sum[7] << 24;
932 rate_unique = (dbdata_ratelimit_unique *)record;
934 for (unsigned n = 0; n < 8; n++, hash += hinc)
936 int bit = 1 << (hash % 8);
937 int byte = (hash / 8) % rate_unique->bloom_size;
938 if ((rate_unique->bloom[byte] & bit) == 0)
941 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
945 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
948 /* else fall through */
949 default: printf("unknown field number\n");
956 printf("Can't change contents of tls database record\n");
960 dbfn_write(dbm, name, record, oldlength);
967 printf("field number or d expected\n");
972 if (!verify) continue;
975 /* The "name" q causes an exit */
977 else if (Ustrcmp(name, "q") == 0) return 0;
979 /* Handle a read request, or verify after an update. */
981 spool_directory = argv[1];
982 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
985 if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
987 printf("record %s not found\n", name);
993 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
997 retry = (dbdata_retry *)record;
998 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
999 printf("1 extra data: %d\n", retry->more_errno);
1000 printf("2 first failed: %s\n", print_time(retry->first_failed));
1001 printf("3 last try: %s\n", print_time(retry->last_try));
1002 printf("4 next try: %s\n", print_time(retry->next_try));
1003 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
1007 wait = (dbdata_wait *)record;
1009 printf("Sequence: %d\n", wait->sequence);
1010 if (wait->count > WAIT_NAME_MAX)
1012 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
1013 wait->count, WAIT_NAME_MAX);
1014 wait->count = WAIT_NAME_MAX;
1017 for (int i = 1; i <= wait->count; i++)
1019 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1020 value[MESSAGE_ID_LENGTH] = 0;
1021 if (count_bad && value[0] == 0) break;
1022 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1023 Ustrspn(value, "0123456789"
1024 "abcdefghijklmnopqrstuvwxyz"
1025 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1027 printf("\n**** Data corrupted: bad character in message id ****\n");
1028 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
1029 printf("%02x ", value[j]);
1033 printf("%s ", value);
1034 t += MESSAGE_ID_LENGTH;
1043 callout = (dbdata_callout_cache *)record;
1044 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1046 if (oldlength > sizeof(dbdata_callout_cache_address))
1048 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1049 callout->postmaster_result);
1050 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1051 callout->random_result);
1055 case type_ratelimit:
1056 ratelimit = (dbdata_ratelimit *)record;
1057 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1058 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1059 printf("2 sender rate: % .3f\n", ratelimit->rate);
1060 if (Ustrstr(name, "/unique/") != NULL
1061 && oldlength >= sizeof(dbdata_ratelimit_unique))
1063 rate_unique = (dbdata_ratelimit_unique *)record;
1064 printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1065 printf("4 test filter membership\n");
1066 printf("5 add element to filter\n");
1071 session = (dbdata_tls_session *)value;
1072 printf("0 time stamp: %s\n", print_time(session->time_stamp));
1073 printf("1 session: .%s\n", session->session);
1078 /* The database is closed after each request */
1087 #endif /* EXIM_FIXDB */
1092 /*************************************************
1093 * The exim_tidydb main program *
1094 *************************************************/
1097 /* Utility program to tidy the contents of an exim database file. There is one
1100 -t <time> expiry time for old records - default 30 days
1102 For backwards compatibility, an -f option is recognized and ignored. (It used
1103 to request a "full" tidy. This version always does the whole job.) */
1106 typedef struct key_item {
1107 struct key_item *next;
1112 int main(int argc, char **cargv)
1114 struct stat statbuf;
1115 int maxkeep = 30 * 24 * 60 * 60;
1116 int dbdata_type, i, oldest, path_len;
1117 key_item *keychain = NULL;
1121 EXIM_CURSOR *cursor;
1122 uschar **argv = USS cargv;
1126 /* Scan the options */
1128 for (i = 1; i < argc; i++)
1130 if (argv[i][0] != '-') break;
1131 if (Ustrcmp(argv[i], "-f") == 0) continue;
1132 if (Ustrcmp(argv[i], "-t") == 0)
1140 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1141 (void)sscanf(CS s, "%d%n", &value, &count);
1145 case 'w': value *= 7;
1146 case 'd': value *= 24;
1147 case 'h': value *= 60;
1148 case 'm': value *= 60;
1151 default: usage(US"tidydb", US" [-t <time>]");
1156 else usage(US"tidydb", US" [-t <time>]");
1159 /* Adjust argument values and process arguments */
1164 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1166 /* Compute the oldest keep time, verify what we are doing, and open the
1169 oldest = time(NULL) - maxkeep;
1170 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1172 spool_directory = argv[1];
1173 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
1176 /* Prepare for building file names */
1178 sprintf(CS buffer, "%s/input/", argv[1]);
1179 path_len = Ustrlen(buffer);
1182 /* It appears, by experiment, that it is a bad idea to make changes
1183 to the file while scanning it. Pity the man page doesn't warn you about that.
1184 Therefore, we scan and build a list of all the keys. Then we use that to
1185 read the records and possibly update them. */
1187 for (key = dbfn_scan(dbm, TRUE, &cursor);
1189 key = dbfn_scan(dbm, FALSE, &cursor))
1191 key_item *k = store_get(sizeof(key_item) + Ustrlen(key), is_tainted(key));
1194 Ustrcpy(k->key, key);
1197 /* Now scan the collected keys and operate on the records, resetting
1198 the store each time round. */
1200 for (; keychain && (reset_point = store_mark()); store_reset(reset_point))
1202 dbdata_generic *value;
1204 key = keychain->key;
1205 keychain = keychain->next;
1206 value = dbfn_read_with_length(dbm, key, NULL);
1208 /* A continuation record may have been deleted or renamed already, so
1209 non-existence is not serious. */
1211 if (value == NULL) continue;
1213 /* Delete if too old */
1215 if (value->time_stamp < oldest)
1217 printf("deleted %s (too old)\n", key);
1218 dbfn_delete(dbm, key);
1222 /* Do database-specific tidying for wait databases, and message-
1223 specific tidying for the retry database. */
1225 if (dbdata_type == type_wait)
1227 dbdata_wait *wait = (dbdata_wait *)value;
1228 BOOL update = FALSE;
1230 /* Leave corrupt records alone */
1232 if (wait->count > WAIT_NAME_MAX)
1234 printf("**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
1235 key, wait->count, wait->count, WAIT_NAME_MAX);
1239 /* Loop for renamed continuation records. For each message id,
1240 check to see if the message exists, and if not, remove its entry
1241 from the record. Because of the possibility of split input directories,
1242 we must look in both possible places for a -D file. */
1246 int length = wait->count * MESSAGE_ID_LENGTH;
1248 for (int offset = length - MESSAGE_ID_LENGTH;
1249 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1251 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1252 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1254 if (Ustat(buffer, &statbuf) != 0)
1256 buffer[path_len] = wait->text[offset+5];
1257 buffer[path_len+1] = '/';
1258 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1259 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1261 if (Ustat(buffer, &statbuf) != 0)
1263 int left = length - offset - MESSAGE_ID_LENGTH;
1264 if (left > 0) Ustrncpy(wait->text + offset,
1265 wait->text + offset + MESSAGE_ID_LENGTH, left);
1267 length -= MESSAGE_ID_LENGTH;
1273 /* If record is empty and the main record, either delete it or rename
1274 the next continuation, repeating if that is also empty. */
1276 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1278 while (wait->count == 0 && wait->sequence > 0)
1281 dbdata_generic *newvalue;
1282 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1283 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1284 if (newvalue != NULL)
1287 wait = (dbdata_wait *)newvalue;
1288 dbfn_delete(dbm, newkey);
1289 printf("renamed %s\n", newkey);
1292 else wait->sequence--;
1295 /* If we have ended up with an empty main record, delete it
1296 and break the loop. Otherwise the new record will be scanned. */
1298 if (wait->count == 0 && wait->sequence == 0)
1300 dbfn_delete(dbm, key);
1301 printf("deleted %s (empty)\n", key);
1307 /* If not an empty main record, break the loop */
1312 /* Re-write the record if required */
1316 printf("updated %s\n", key);
1317 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1318 wait->count * MESSAGE_ID_LENGTH);
1322 /* If a retry record's key ends with a message-id, check that that message
1323 still exists; if not, remove this record. */
1325 else if (dbdata_type == type_retry)
1328 int len = Ustrlen(key);
1330 if (len < MESSAGE_ID_LENGTH + 1) continue;
1331 id = key + len - MESSAGE_ID_LENGTH - 1;
1332 if (*id++ != ':') continue;
1334 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1335 if (i == 6 || i == 13)
1336 { if (id[i] != '-') break; }
1338 { if (!isalnum(id[i])) break; }
1339 if (i < MESSAGE_ID_LENGTH) continue;
1341 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1342 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1344 if (Ustat(buffer, &statbuf) != 0)
1346 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1347 if (Ustat(buffer, &statbuf) != 0)
1349 dbfn_delete(dbm, key);
1350 printf("deleted %s (no message)\n", key);
1357 printf("Tidying complete\n");
1361 #endif /* EXIM_TIDYDB */
1363 /* End of exim_dbutil.c */