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)
53 /******************************************************************************/
56 /*************************************************
57 * Berkeley DB error callback *
58 *************************************************/
60 /* For Berkeley DB >= 2, we can define a function to be called in case of DB
61 errors. This should help with debugging strange DB problems, e.g. getting "File
62 exists" when you try to open a db file. The API changed at release 4.3. */
64 #if defined(USE_DB) && defined(DB_VERSION_STRING)
66 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
67 dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
71 dbfn_bdb_error_callback(const char *pfx, char *msg)
75 printf("Berkeley DB error: %s\n", msg);
81 /*************************************************
83 *************************************************/
85 SIGNAL_BOOL sigalrm_seen;
88 sigalrm_handler(int sig)
90 sig = sig; /* Keep picky compilers happy */
96 /*************************************************
97 * Output usage message and exit *
98 *************************************************/
101 usage(uschar *name, uschar *options)
103 printf("Usage: exim_%s%s <spool-directory> <database-name>\n", name, options);
104 printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit | tls\n");
110 /*************************************************
111 * Sort out the command arguments *
112 *************************************************/
114 /* This function checks that there are exactly 2 arguments, and checks the
115 second of them to be sure it is a known database name. */
118 check_args(int argc, uschar **argv, uschar *name, uschar *options)
122 if (Ustrcmp(argv[2], "retry") == 0) return type_retry;
123 if (Ustrcmp(argv[2], "misc") == 0) return type_misc;
124 if (Ustrncmp(argv[2], "wait-", 5) == 0) return type_wait;
125 if (Ustrcmp(argv[2], "callout") == 0) return type_callout;
126 if (Ustrcmp(argv[2], "ratelimit") == 0) return type_ratelimit;
127 if (Ustrcmp(argv[2], "tls") == 0) return type_tls;
129 usage(name, options);
130 return -1; /* Never obeyed */
135 /*************************************************
136 * Handle attempts to write the log *
137 *************************************************/
139 /* The message gets written to stderr when log_write() is called from a
140 utility. The message always gets '\n' added on the end of it. These calls come
141 from modules such as store.c when things go drastically wrong (e.g. malloc()
142 failing). In normal use they won't get obeyed.
145 selector not relevant when running a utility
146 flags not relevant when running a utility
147 format a printf() format
148 ... arguments for format
154 log_write(unsigned int selector, int flags, const char *format, ...)
157 va_start(ap, format);
158 vfprintf(stderr, format, ap);
159 fprintf(stderr, "\n");
161 selector = selector; /* Keep picky compilers happy */
167 /*************************************************
168 * Format a time value for printing *
169 *************************************************/
171 static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss ")];
176 struct tm *tmstr = localtime(&t);
177 Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
183 /*************************************************
184 * Format a cache value for printing *
185 *************************************************/
188 print_cache(int value)
190 return (value == ccache_accept)? US"accept" :
191 (value == ccache_reject)? US"reject" :
197 /*************************************************
199 *************************************************/
206 time_t now = time(NULL);
207 struct tm *tm = localtime(&now);
212 for (uschar * t = s + Ustrlen(s) - 1; t >= s; t--)
214 if (*t == ':') continue;
215 if (!isdigit((uschar)*t)) return -1;
220 if (!isdigit((uschar)*t)) return -1;
221 value = value + (*t - '0')*10;
226 case 0: tm->tm_min = value; break;
227 case 1: tm->tm_hour = value; break;
228 case 2: tm->tm_mday = value; break;
229 case 3: tm->tm_mon = value - 1; break;
230 case 4: tm->tm_year = (value < 90)? value + 100 : value; break;
237 #endif /* EXIM_FIXDB */
241 /*************************************************
242 * Open and lock a database file *
243 *************************************************/
245 /* This is a cut-down version from the function in dbfn.h that Exim itself
246 uses. We assume the database exists, and therefore give up if we cannot open
250 name The single-component name of one of Exim's database files.
251 flags O_RDONLY or O_RDWR
252 dbblock Points to an open_db block to be filled in.
256 Returns: NULL if the open failed, or the locking failed.
257 On success, dbblock is returned. This contains the dbm pointer and
258 the fd of the locked lock file.
262 dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof, BOOL panic)
265 struct flock lock_data;
266 BOOL read_only = flags == O_RDONLY;
267 uschar * dirname, * filename;
269 /* The first thing to do is to open a separate file on which to lock. This
270 ensures that Exim has exclusive use of the database before it even tries to
271 open it. If there is a database, there should be a lock file in existence. */
273 #ifdef COMPILE_UTILITY
274 if ( asprintf(CSS &dirname, "%s/db", spool_directory) < 0
275 || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
278 dirname = string_sprintf("%s/db", spool_directory);
279 filename = string_sprintf("%s/%s.lockfile", dirname, name);
282 dbblock->lockfd = Uopen(filename, flags, 0);
283 if (dbblock->lockfd < 0)
285 printf("** Failed to open database lock file %s: %s\n", filename,
290 /* Now we must get a lock on the opened lock file; do this with a blocking
291 lock that times out. */
293 lock_data.l_type = read_only ? F_RDLCK : F_WRLCK;
294 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
296 sigalrm_seen = FALSE;
297 os_non_restarting_signal(SIGALRM, sigalrm_handler);
298 ALARM(EXIMDB_LOCK_TIMEOUT);
299 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
302 if (sigalrm_seen) errno = ETIMEDOUT;
305 printf("** Failed to get %s lock for %s: %s",
306 flags & O_WRONLY ? "write" : "read",
308 errno == ETIMEDOUT ? "timed out" : strerror(errno));
309 (void)close(dbblock->lockfd);
313 /* At this point we have an opened and locked separate lock file, that is,
314 exclusive access to the database, so we can go ahead and open it. */
316 #ifdef COMPILE_UTILITY
317 if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
319 filename = string_sprintf("%s/%s", dirname, name);
321 EXIM_DBOPEN(filename, dirname, flags, 0, &(dbblock->dbptr));
325 printf("** Failed to open DBM file %s for %s:\n %s%s\n", filename,
326 read_only? "reading" : "writing", strerror(errno),
328 " (or Berkeley DB error while opening)"
333 (void)close(dbblock->lockfd);
343 /*************************************************
344 * Unlock and close a database file *
345 *************************************************/
347 /* Closing a file automatically unlocks it, so after closing the database, just
350 Argument: a pointer to an open database block
355 dbfn_close(open_db *dbblock)
357 EXIM_DBCLOSE(dbblock->dbptr);
358 (void)close(dbblock->lockfd);
364 /*************************************************
365 * Read from database file *
366 *************************************************/
368 /* Passing back the pointer unchanged is useless, because there is no guarantee
369 of alignment. Since all the records used by Exim need to be properly aligned to
370 pick out the timestamps, etc., do the copying centrally here.
373 dbblock a pointer to an open database block
374 key the key of the record to be read
375 length where to put the length (or NULL if length not wanted)
377 Returns: a pointer to the retrieved record, or
378 NULL if the record is not found
382 dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
385 EXIM_DATUM key_datum, result_datum;
386 int klen = Ustrlen(key) + 1;
387 uschar * key_copy = store_get(klen, is_tainted(key));
389 memcpy(key_copy, key, klen);
391 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
392 EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */
393 EXIM_DATUM_DATA(key_datum) = CS key_copy;
394 EXIM_DATUM_SIZE(key_datum) = klen;
396 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
398 /* Assume for now that anything stored could have been tainted. Properly
399 we should store the taint status along with the data. */
401 yield = store_get(EXIM_DATUM_SIZE(result_datum), TRUE);
402 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
403 if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
405 EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */
411 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
413 /*************************************************
414 * Write to database file *
415 *************************************************/
419 dbblock a pointer to an open database block
420 key the key of the record to be written
421 ptr a pointer to the record to be written
422 length the length of the record to be written
424 Returns: the yield of the underlying dbm or db "write" function. If this
425 is dbm, the value is zero for OK.
429 dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
431 EXIM_DATUM key_datum, value_datum;
432 dbdata_generic *gptr = (dbdata_generic *)ptr;
433 int klen = Ustrlen(key) + 1;
434 uschar * key_copy = store_get(klen, is_tainted(key));
436 memcpy(key_copy, key, klen);
437 gptr->time_stamp = time(NULL);
439 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
440 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
441 EXIM_DATUM_DATA(key_datum) = CS key_copy;
442 EXIM_DATUM_SIZE(key_datum) = klen;
443 EXIM_DATUM_DATA(value_datum) = CS ptr;
444 EXIM_DATUM_SIZE(value_datum) = length;
445 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
450 /*************************************************
451 * Delete record from database file *
452 *************************************************/
456 dbblock a pointer to an open database block
457 key the key of the record to be deleted
459 Returns: the yield of the underlying dbm or db "delete" function.
463 dbfn_delete(open_db *dbblock, const uschar *key)
465 int klen = Ustrlen(key) + 1;
466 uschar * key_copy = store_get(klen, is_tainted(key));
468 memcpy(key_copy, key, klen);
469 EXIM_DATUM key_datum;
470 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
471 EXIM_DATUM_DATA(key_datum) = CS key_copy;
472 EXIM_DATUM_SIZE(key_datum) = klen;
473 return EXIM_DBDEL(dbblock->dbptr, key_datum);
476 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
480 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
481 /*************************************************
482 * Scan the keys of a database file *
483 *************************************************/
487 dbblock a pointer to an open database block
488 start TRUE if starting a new scan
489 FALSE if continuing with the current scan
490 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
491 that use the notion of a cursor
493 Returns: the next record from the file, or
494 NULL if there are no more
498 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
500 EXIM_DATUM key_datum, value_datum;
502 value_datum = value_datum; /* dummy; not all db libraries use this */
504 /* Some dbm require an initialization */
506 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
508 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
509 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
511 yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
512 US EXIM_DATUM_DATA(key_datum) : NULL;
514 /* Some dbm require a termination */
516 if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
519 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
524 /*************************************************
525 * The exim_dumpdb main program *
526 *************************************************/
529 main(int argc, char **cargv)
536 uschar **argv = USS cargv;
537 uschar keybuffer[1024];
539 /* Check the arguments, and open the database */
541 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
542 spool_directory = argv[1];
543 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
546 /* Scan the file, formatting the information for each entry. Note
547 that data is returned in a malloc'ed block, in order that it be
548 correctly aligned. */
550 for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
552 key = dbfn_scan(dbm, FALSE, &cursor))
556 dbdata_callout_cache *callout;
557 dbdata_ratelimit *ratelimit;
558 dbdata_ratelimit_unique *rate_unique;
559 dbdata_tls_session *session;
563 uschar name[MESSAGE_ID_LENGTH + 1];
565 rmark reset_point = store_mark();
567 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
568 which might change. */
570 if (Ustrlen(key) > sizeof(keybuffer) - 1)
572 printf("**** Overlong key encountered: %s\n", key);
575 Ustrcpy(keybuffer, key);
577 if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
578 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
579 "was not found in the file - something is wrong!\n",
583 /* Note: don't use print_time more than once in one statement, since
584 it uses a single buffer. */
589 retry = (dbdata_retry *)value;
590 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
591 retry->more_errno, retry->text,
592 print_time(retry->first_failed));
593 printf("%s ", print_time(retry->last_try));
594 printf("%s %s\n", print_time(retry->next_try),
595 (retry->expired)? "*" : "");
599 wait = (dbdata_wait *)value;
600 printf("%s ", keybuffer);
602 name[MESSAGE_ID_LENGTH] = 0;
604 if (wait->count > WAIT_NAME_MAX)
607 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
608 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
609 wait->count = WAIT_NAME_MAX;
610 yield = count_bad = 1;
612 for (int i = 1; i <= wait->count; i++)
614 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
615 if (count_bad && name[0] == 0) break;
616 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
617 Ustrspn(name, "0123456789"
618 "abcdefghijklmnopqrstuvwxyz"
619 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
622 "**** Data for %s corrupted: bad character in message id\n",
624 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
625 fprintf(stderr, "%02x ", name[j]);
626 fprintf(stderr, "\n");
631 t += MESSAGE_ID_LENGTH;
637 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
642 callout = (dbdata_callout_cache *)value;
644 /* New-style address record */
646 if (length == sizeof(dbdata_callout_cache_address))
648 printf("%s %s callout=%s\n",
649 print_time(((dbdata_generic *)value)->time_stamp),
651 print_cache(callout->result));
654 /* New-style domain record */
656 else if (length == sizeof(dbdata_callout_cache))
658 printf("%s %s callout=%s postmaster=%s",
659 print_time(((dbdata_generic *)value)->time_stamp),
661 print_cache(callout->result),
662 print_cache(callout->postmaster_result));
663 if (callout->postmaster_result != ccache_unknown)
664 printf(" (%s)", print_time(callout->postmaster_stamp));
665 printf(" random=%s", print_cache(callout->random_result));
666 if (callout->random_result != ccache_unknown)
667 printf(" (%s)", print_time(callout->random_stamp));
674 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
676 ratelimit = (dbdata_ratelimit *)value;
677 rate_unique = (dbdata_ratelimit_unique *)value;
678 printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
679 print_time(ratelimit->time_stamp),
680 ratelimit->time_usec, ratelimit->rate,
681 print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
686 ratelimit = (dbdata_ratelimit *)value;
687 printf("%s.%06d rate: %10.3f key: %s\n",
688 print_time(ratelimit->time_stamp),
689 ratelimit->time_usec, ratelimit->rate,
695 session = (dbdata_tls_session *)value;
696 printf(" %s %.*s\n", keybuffer, length, session->session);
700 store_reset(reset_point);
707 #endif /* EXIM_DUMPDB */
713 /*************************************************
714 * The exim_fixdb main program *
715 *************************************************/
717 /* In order not to hold the database lock any longer than is necessary, each
718 operation on the database uses a separate open/close call. This is expensive,
719 but then using this utility is not expected to be very common. Its main use is
720 to provide a way of patching up hints databases in order to run tests.
725 This causes the data from the given record to be displayed, or "not found"
726 to be output. Note that in the retry database, destination names are
727 preceded by R: or T: for router or transport retry info.
730 This causes the given record to be deleted or "not found" to be output.
732 (3) <record name> <field number> <value>
733 This sets the given value into the given field, identified by a number
734 which is output by the display command. Not all types of record can
738 This exits from exim_fixdb.
740 If the record name is omitted from (2) or (3), the previously used record name
744 int main(int argc, char **cargv)
747 uschar **argv = USS cargv;
752 name[0] = 0; /* No name set */
754 /* Sort out the database type, verify what we are working on and then process
757 dbdata_type = check_args(argc, argv, US"fixdb", US"");
758 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
760 for(; (reset_point = store_mark()); store_reset(reset_point))
767 dbdata_callout_cache *callout;
768 dbdata_ratelimit *ratelimit;
769 dbdata_ratelimit_unique *rate_unique;
770 dbdata_tls_session *session;
773 uschar field[256], value[256];
776 if (Ufgets(buffer, 256, stdin) == NULL) break;
778 buffer[Ustrlen(buffer)-1] = 0;
779 field[0] = value[0] = 0;
781 /* If the buffer contains just one digit, or just consists of "d", use the
782 previous name for an update. */
784 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
785 || Ustrcmp(buffer, "d") == 0)
789 printf("No previous record name is set\n");
792 (void)sscanf(CS buffer, "%s %s", field, value);
797 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
800 /* Handle an update request */
805 spool_directory = argv[1];
807 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
810 if (Ustrcmp(field, "d") == 0)
812 if (value[0] != 0) printf("unexpected value after \"d\"\n");
813 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
814 "not found" : "deleted");
819 else if (isdigit((uschar)field[0]))
821 int fieldno = Uatoi(field);
824 printf("value missing\n");
830 record = dbfn_read_with_length(dbm, name, &oldlength);
831 if (record == NULL) printf("not found\n"); else
834 /*int length = 0; Stops compiler warning */
839 retry = (dbdata_retry *)record;
840 /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
844 case 0: retry->basic_errno = Uatoi(value);
846 case 1: retry->more_errno = Uatoi(value);
848 case 2: if ((tt = read_time(value)) > 0) retry->first_failed = tt;
849 else printf("bad time value\n");
851 case 3: if ((tt = read_time(value)) > 0) retry->last_try = tt;
852 else printf("bad time value\n");
854 case 4: if ((tt = read_time(value)) > 0) retry->next_try = tt;
855 else printf("bad time value\n");
857 case 5: if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
858 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
859 else printf("\"yes\" or \"no\" expected=n");
861 default: printf("unknown field number\n");
868 printf("Can't change contents of wait database record\n");
872 printf("Can't change contents of misc database record\n");
876 callout = (dbdata_callout_cache *)record;
877 /* length = sizeof(dbdata_callout_cache); */
880 case 0: callout->result = Uatoi(value);
882 case 1: callout->postmaster_result = Uatoi(value);
884 case 2: callout->random_result = Uatoi(value);
886 default: printf("unknown field number\n");
893 ratelimit = (dbdata_ratelimit *)record;
896 case 0: if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
897 else printf("bad time value\n");
899 case 1: ratelimit->time_usec = Uatoi(value);
901 case 2: ratelimit->rate = Ustrtod(value, NULL);
903 case 3: if (Ustrstr(name, "/unique/") != NULL
904 && oldlength >= sizeof(dbdata_ratelimit_unique))
906 rate_unique = (dbdata_ratelimit_unique *)record;
907 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
908 else printf("bad time value\n");
911 /* else fall through */
913 case 5: if (Ustrstr(name, "/unique/") != NULL
914 && oldlength >= sizeof(dbdata_ratelimit_unique))
922 md5_end(&md5info, value, Ustrlen(value), md5sum);
923 hash = md5sum[0] << 0 | md5sum[1] << 8
924 | md5sum[2] << 16 | md5sum[3] << 24;
925 hinc = md5sum[4] << 0 | md5sum[5] << 8
926 | md5sum[6] << 16 | md5sum[7] << 24;
927 rate_unique = (dbdata_ratelimit_unique *)record;
929 for (unsigned n = 0; n < 8; n++, hash += hinc)
931 int bit = 1 << (hash % 8);
932 int byte = (hash / 8) % rate_unique->bloom_size;
933 if ((rate_unique->bloom[byte] & bit) == 0)
936 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
940 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
943 /* else fall through */
944 default: printf("unknown field number\n");
951 printf("Can't change contents of tls database record\n");
955 dbfn_write(dbm, name, record, oldlength);
962 printf("field number or d expected\n");
967 if (!verify) continue;
970 /* The "name" q causes an exit */
972 else if (Ustrcmp(name, "q") == 0) return 0;
974 /* Handle a read request, or verify after an update. */
976 spool_directory = argv[1];
977 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
980 if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
982 printf("record %s not found\n", name);
988 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
992 retry = (dbdata_retry *)record;
993 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
994 printf("1 extra data: %d\n", retry->more_errno);
995 printf("2 first failed: %s\n", print_time(retry->first_failed));
996 printf("3 last try: %s\n", print_time(retry->last_try));
997 printf("4 next try: %s\n", print_time(retry->next_try));
998 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
1002 wait = (dbdata_wait *)record;
1004 printf("Sequence: %d\n", wait->sequence);
1005 if (wait->count > WAIT_NAME_MAX)
1007 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
1008 wait->count, WAIT_NAME_MAX);
1009 wait->count = WAIT_NAME_MAX;
1012 for (int i = 1; i <= wait->count; i++)
1014 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1015 value[MESSAGE_ID_LENGTH] = 0;
1016 if (count_bad && value[0] == 0) break;
1017 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1018 Ustrspn(value, "0123456789"
1019 "abcdefghijklmnopqrstuvwxyz"
1020 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1022 printf("\n**** Data corrupted: bad character in message id ****\n");
1023 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
1024 printf("%02x ", value[j]);
1028 printf("%s ", value);
1029 t += MESSAGE_ID_LENGTH;
1038 callout = (dbdata_callout_cache *)record;
1039 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1041 if (oldlength > sizeof(dbdata_callout_cache_address))
1043 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1044 callout->postmaster_result);
1045 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1046 callout->random_result);
1050 case type_ratelimit:
1051 ratelimit = (dbdata_ratelimit *)record;
1052 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1053 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1054 printf("2 sender rate: % .3f\n", ratelimit->rate);
1055 if (Ustrstr(name, "/unique/") != NULL
1056 && oldlength >= sizeof(dbdata_ratelimit_unique))
1058 rate_unique = (dbdata_ratelimit_unique *)record;
1059 printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1060 printf("4 test filter membership\n");
1061 printf("5 add element to filter\n");
1066 session = (dbdata_tls_session *)value;
1067 printf("0 time stamp: %s\n", print_time(session->time_stamp));
1068 printf("1 session: .%s\n", session->session);
1073 /* The database is closed after each request */
1082 #endif /* EXIM_FIXDB */
1087 /*************************************************
1088 * The exim_tidydb main program *
1089 *************************************************/
1092 /* Utility program to tidy the contents of an exim database file. There is one
1095 -t <time> expiry time for old records - default 30 days
1097 For backwards compatibility, an -f option is recognized and ignored. (It used
1098 to request a "full" tidy. This version always does the whole job.) */
1101 typedef struct key_item {
1102 struct key_item *next;
1107 int main(int argc, char **cargv)
1109 struct stat statbuf;
1110 int maxkeep = 30 * 24 * 60 * 60;
1111 int dbdata_type, i, oldest, path_len;
1112 key_item *keychain = NULL;
1116 EXIM_CURSOR *cursor;
1117 uschar **argv = USS cargv;
1121 /* Scan the options */
1123 for (i = 1; i < argc; i++)
1125 if (argv[i][0] != '-') break;
1126 if (Ustrcmp(argv[i], "-f") == 0) continue;
1127 if (Ustrcmp(argv[i], "-t") == 0)
1135 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1136 (void)sscanf(CS s, "%d%n", &value, &count);
1140 case 'w': value *= 7;
1141 case 'd': value *= 24;
1142 case 'h': value *= 60;
1143 case 'm': value *= 60;
1146 default: usage(US"tidydb", US" [-t <time>]");
1151 else usage(US"tidydb", US" [-t <time>]");
1154 /* Adjust argument values and process arguments */
1159 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1161 /* Compute the oldest keep time, verify what we are doing, and open the
1164 oldest = time(NULL) - maxkeep;
1165 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1167 spool_directory = argv[1];
1168 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
1171 /* Prepare for building file names */
1173 sprintf(CS buffer, "%s/input/", argv[1]);
1174 path_len = Ustrlen(buffer);
1177 /* It appears, by experiment, that it is a bad idea to make changes
1178 to the file while scanning it. Pity the man page doesn't warn you about that.
1179 Therefore, we scan and build a list of all the keys. Then we use that to
1180 read the records and possibly update them. */
1182 for (key = dbfn_scan(dbm, TRUE, &cursor);
1184 key = dbfn_scan(dbm, FALSE, &cursor))
1186 key_item *k = store_get(sizeof(key_item) + Ustrlen(key), is_tainted(key));
1189 Ustrcpy(k->key, key);
1192 /* Now scan the collected keys and operate on the records, resetting
1193 the store each time round. */
1195 for (; keychain && (reset_point = store_mark()); store_reset(reset_point))
1197 dbdata_generic *value;
1199 key = keychain->key;
1200 keychain = keychain->next;
1201 value = dbfn_read_with_length(dbm, key, NULL);
1203 /* A continuation record may have been deleted or renamed already, so
1204 non-existence is not serious. */
1206 if (value == NULL) continue;
1208 /* Delete if too old */
1210 if (value->time_stamp < oldest)
1212 printf("deleted %s (too old)\n", key);
1213 dbfn_delete(dbm, key);
1217 /* Do database-specific tidying for wait databases, and message-
1218 specific tidying for the retry database. */
1220 if (dbdata_type == type_wait)
1222 dbdata_wait *wait = (dbdata_wait *)value;
1223 BOOL update = FALSE;
1225 /* Leave corrupt records alone */
1227 if (wait->count > WAIT_NAME_MAX)
1229 printf("**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
1230 key, wait->count, wait->count, WAIT_NAME_MAX);
1234 /* Loop for renamed continuation records. For each message id,
1235 check to see if the message exists, and if not, remove its entry
1236 from the record. Because of the possibility of split input directories,
1237 we must look in both possible places for a -D file. */
1241 int length = wait->count * MESSAGE_ID_LENGTH;
1243 for (int offset = length - MESSAGE_ID_LENGTH;
1244 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1246 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1247 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1249 if (Ustat(buffer, &statbuf) != 0)
1251 buffer[path_len] = wait->text[offset+5];
1252 buffer[path_len+1] = '/';
1253 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1254 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1256 if (Ustat(buffer, &statbuf) != 0)
1258 int left = length - offset - MESSAGE_ID_LENGTH;
1259 if (left > 0) Ustrncpy(wait->text + offset,
1260 wait->text + offset + MESSAGE_ID_LENGTH, left);
1262 length -= MESSAGE_ID_LENGTH;
1268 /* If record is empty and the main record, either delete it or rename
1269 the next continuation, repeating if that is also empty. */
1271 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1273 while (wait->count == 0 && wait->sequence > 0)
1276 dbdata_generic *newvalue;
1277 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1278 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1279 if (newvalue != NULL)
1282 wait = (dbdata_wait *)newvalue;
1283 dbfn_delete(dbm, newkey);
1284 printf("renamed %s\n", newkey);
1287 else wait->sequence--;
1290 /* If we have ended up with an empty main record, delete it
1291 and break the loop. Otherwise the new record will be scanned. */
1293 if (wait->count == 0 && wait->sequence == 0)
1295 dbfn_delete(dbm, key);
1296 printf("deleted %s (empty)\n", key);
1302 /* If not an empty main record, break the loop */
1307 /* Re-write the record if required */
1311 printf("updated %s\n", key);
1312 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1313 wait->count * MESSAGE_ID_LENGTH);
1317 /* If a retry record's key ends with a message-id, check that that message
1318 still exists; if not, remove this record. */
1320 else if (dbdata_type == type_retry)
1323 int len = Ustrlen(key);
1325 if (len < MESSAGE_ID_LENGTH + 1) continue;
1326 id = key + len - MESSAGE_ID_LENGTH - 1;
1327 if (*id++ != ':') continue;
1329 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1330 if (i == 6 || i == 13)
1331 { if (id[i] != '-') break; }
1333 { if (!isalnum(id[i])) break; }
1334 if (i < MESSAGE_ID_LENGTH) continue;
1336 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1337 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1339 if (Ustat(buffer, &statbuf) != 0)
1341 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1342 if (Ustat(buffer, &statbuf) != 0)
1344 dbfn_delete(dbm, key);
1345 printf("deleted %s (no message)\n", key);
1352 printf("Tidying complete\n");
1356 #endif /* EXIM_TIDYDB */
1358 /* End of exim_dbutil.c */