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 extern uschar *string_sprintf_trc(const char *, const uschar *, unsigned , ...);
54 extern BOOL split_spool_directory;
55 extern uschar * queue_name;
56 /******************************************************************************/
59 /*************************************************
60 * Berkeley DB error callback *
61 *************************************************/
63 /* For Berkeley DB >= 2, we can define a function to be called in case of DB
64 errors. This should help with debugging strange DB problems, e.g. getting "File
65 exists" when you try to open a db file. The API changed at release 4.3. */
67 #if defined(USE_DB) && defined(DB_VERSION_STRING)
69 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
70 dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
74 dbfn_bdb_error_callback(const char *pfx, char *msg)
78 printf("Berkeley DB error: %s\n", msg);
84 /*************************************************
86 *************************************************/
88 SIGNAL_BOOL sigalrm_seen;
91 sigalrm_handler(int sig)
93 sig = sig; /* Keep picky compilers happy */
99 /*************************************************
100 * Output usage message and exit *
101 *************************************************/
104 usage(uschar *name, uschar *options)
106 printf("Usage: exim_%s%s <spool-directory> <database-name>\n", name, options);
107 printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit | tls\n");
113 /*************************************************
114 * Sort out the command arguments *
115 *************************************************/
117 /* This function checks that there are exactly 2 arguments, and checks the
118 second of them to be sure it is a known database name. */
121 check_args(int argc, uschar **argv, uschar *name, uschar *options)
125 if (Ustrcmp(argv[2], "retry") == 0) return type_retry;
126 if (Ustrcmp(argv[2], "misc") == 0) return type_misc;
127 if (Ustrncmp(argv[2], "wait-", 5) == 0) return type_wait;
128 if (Ustrcmp(argv[2], "callout") == 0) return type_callout;
129 if (Ustrcmp(argv[2], "ratelimit") == 0) return type_ratelimit;
130 if (Ustrcmp(argv[2], "tls") == 0) return type_tls;
132 usage(name, options);
133 return -1; /* Never obeyed */
138 /*************************************************
139 * Handle attempts to write the log *
140 *************************************************/
142 /* The message gets written to stderr when log_write() is called from a
143 utility. The message always gets '\n' added on the end of it. These calls come
144 from modules such as store.c when things go drastically wrong (e.g. malloc()
145 failing). In normal use they won't get obeyed.
148 selector not relevant when running a utility
149 flags not relevant when running a utility
150 format a printf() format
151 ... arguments for format
157 log_write(unsigned int selector, int flags, const char *format, ...)
160 va_start(ap, format);
161 vfprintf(stderr, format, ap);
162 fprintf(stderr, "\n");
164 selector = selector; /* Keep picky compilers happy */
170 /*************************************************
171 * Format a time value for printing *
172 *************************************************/
174 static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss ")];
179 struct tm *tmstr = localtime(&t);
180 Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
186 /*************************************************
187 * Format a cache value for printing *
188 *************************************************/
191 print_cache(int value)
193 return (value == ccache_accept)? US"accept" :
194 (value == ccache_reject)? US"reject" :
200 /*************************************************
202 *************************************************/
209 time_t now = time(NULL);
210 struct tm *tm = localtime(&now);
215 for (uschar * t = s + Ustrlen(s) - 1; t >= s; t--)
217 if (*t == ':') continue;
218 if (!isdigit((uschar)*t)) return -1;
223 if (!isdigit((uschar)*t)) return -1;
224 value = value + (*t - '0')*10;
229 case 0: tm->tm_min = value; break;
230 case 1: tm->tm_hour = value; break;
231 case 2: tm->tm_mday = value; break;
232 case 3: tm->tm_mon = value - 1; break;
233 case 4: tm->tm_year = (value < 90)? value + 100 : value; break;
240 #endif /* EXIM_FIXDB */
244 /*************************************************
245 * Open and lock a database file *
246 *************************************************/
248 /* This is a cut-down version from the function in dbfn.h that Exim itself
249 uses. We assume the database exists, and therefore give up if we cannot open
253 name The single-component name of one of Exim's database files.
254 flags O_RDONLY or O_RDWR
255 dbblock Points to an open_db block to be filled in.
259 Returns: NULL if the open failed, or the locking failed.
260 On success, dbblock is returned. This contains the dbm pointer and
261 the fd of the locked lock file.
265 dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof, BOOL panic)
268 struct flock lock_data;
269 BOOL read_only = flags == O_RDONLY;
270 uschar * dirname, * filename;
272 /* The first thing to do is to open a separate file on which to lock. This
273 ensures that Exim has exclusive use of the database before it even tries to
274 open it. If there is a database, there should be a lock file in existence. */
276 #ifdef COMPILE_UTILITY
277 if ( asprintf(CSS &dirname, "%s/db", spool_directory) < 0
278 || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
281 dirname = string_sprintf("%s/db", spool_directory);
282 filename = string_sprintf("%s/%s.lockfile", dirname, name);
285 dbblock->lockfd = Uopen(filename, flags, 0);
286 if (dbblock->lockfd < 0)
288 printf("** Failed to open database lock file %s: %s\n", filename,
293 /* Now we must get a lock on the opened lock file; do this with a blocking
294 lock that times out. */
296 lock_data.l_type = read_only ? F_RDLCK : F_WRLCK;
297 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
299 sigalrm_seen = FALSE;
300 os_non_restarting_signal(SIGALRM, sigalrm_handler);
301 ALARM(EXIMDB_LOCK_TIMEOUT);
302 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
305 if (sigalrm_seen) errno = ETIMEDOUT;
308 printf("** Failed to get %s lock for %s: %s",
309 flags & O_WRONLY ? "write" : "read",
311 errno == ETIMEDOUT ? "timed out" : strerror(errno));
312 (void)close(dbblock->lockfd);
316 /* At this point we have an opened and locked separate lock file, that is,
317 exclusive access to the database, so we can go ahead and open it. */
319 #ifdef COMPILE_UTILITY
320 if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
322 filename = string_sprintf("%s/%s", dirname, name);
324 EXIM_DBOPEN(filename, dirname, flags, 0, &(dbblock->dbptr));
328 printf("** Failed to open DBM file %s for %s:\n %s%s\n", filename,
329 read_only? "reading" : "writing", strerror(errno),
331 " (or Berkeley DB error while opening)"
336 (void)close(dbblock->lockfd);
346 /*************************************************
347 * Unlock and close a database file *
348 *************************************************/
350 /* Closing a file automatically unlocks it, so after closing the database, just
353 Argument: a pointer to an open database block
358 dbfn_close(open_db *dbblock)
360 EXIM_DBCLOSE(dbblock->dbptr);
361 (void)close(dbblock->lockfd);
367 /*************************************************
368 * Read from database file *
369 *************************************************/
371 /* Passing back the pointer unchanged is useless, because there is no guarantee
372 of alignment. Since all the records used by Exim need to be properly aligned to
373 pick out the timestamps, etc., do the copying centrally here.
376 dbblock a pointer to an open database block
377 key the key of the record to be read
378 length where to put the length (or NULL if length not wanted)
380 Returns: a pointer to the retrieved record, or
381 NULL if the record is not found
385 dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
388 EXIM_DATUM key_datum, result_datum;
389 int klen = Ustrlen(key) + 1;
390 uschar * key_copy = store_get(klen, is_tainted(key));
392 memcpy(key_copy, key, klen);
394 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
395 EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */
396 EXIM_DATUM_DATA(key_datum) = CS key_copy;
397 EXIM_DATUM_SIZE(key_datum) = klen;
399 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
401 /* Assume for now that anything stored could have been tainted. Properly
402 we should store the taint status along with the data. */
404 yield = store_get(EXIM_DATUM_SIZE(result_datum), TRUE);
405 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
406 if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
408 EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */
414 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
416 /*************************************************
417 * Write to database file *
418 *************************************************/
422 dbblock a pointer to an open database block
423 key the key of the record to be written
424 ptr a pointer to the record to be written
425 length the length of the record to be written
427 Returns: the yield of the underlying dbm or db "write" function. If this
428 is dbm, the value is zero for OK.
432 dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
434 EXIM_DATUM key_datum, value_datum;
435 dbdata_generic *gptr = (dbdata_generic *)ptr;
436 int klen = Ustrlen(key) + 1;
437 uschar * key_copy = store_get(klen, is_tainted(key));
439 memcpy(key_copy, key, klen);
440 gptr->time_stamp = time(NULL);
442 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
443 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
444 EXIM_DATUM_DATA(key_datum) = CS key_copy;
445 EXIM_DATUM_SIZE(key_datum) = klen;
446 EXIM_DATUM_DATA(value_datum) = CS ptr;
447 EXIM_DATUM_SIZE(value_datum) = length;
448 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
453 /*************************************************
454 * Delete record from database file *
455 *************************************************/
459 dbblock a pointer to an open database block
460 key the key of the record to be deleted
462 Returns: the yield of the underlying dbm or db "delete" function.
466 dbfn_delete(open_db *dbblock, const uschar *key)
468 int klen = Ustrlen(key) + 1;
469 uschar * key_copy = store_get(klen, is_tainted(key));
471 memcpy(key_copy, key, klen);
472 EXIM_DATUM key_datum;
473 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
474 EXIM_DATUM_DATA(key_datum) = CS key_copy;
475 EXIM_DATUM_SIZE(key_datum) = klen;
476 return EXIM_DBDEL(dbblock->dbptr, key_datum);
479 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
483 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
484 /*************************************************
485 * Scan the keys of a database file *
486 *************************************************/
490 dbblock a pointer to an open database block
491 start TRUE if starting a new scan
492 FALSE if continuing with the current scan
493 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
494 that use the notion of a cursor
496 Returns: the next record from the file, or
497 NULL if there are no more
501 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
503 EXIM_DATUM key_datum, value_datum;
505 value_datum = value_datum; /* dummy; not all db libraries use this */
507 /* Some dbm require an initialization */
509 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
511 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
512 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
514 yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
515 US EXIM_DATUM_DATA(key_datum) : NULL;
517 /* Some dbm require a termination */
519 if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
522 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
527 /*************************************************
528 * The exim_dumpdb main program *
529 *************************************************/
532 main(int argc, char **cargv)
539 uschar **argv = USS cargv;
540 uschar keybuffer[1024];
542 /* Check the arguments, and open the database */
544 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
545 spool_directory = argv[1];
546 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
549 /* Scan the file, formatting the information for each entry. Note
550 that data is returned in a malloc'ed block, in order that it be
551 correctly aligned. */
553 for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
555 key = dbfn_scan(dbm, FALSE, &cursor))
559 dbdata_callout_cache *callout;
560 dbdata_ratelimit *ratelimit;
561 dbdata_ratelimit_unique *rate_unique;
562 dbdata_tls_session *session;
566 uschar name[MESSAGE_ID_LENGTH + 1];
568 rmark reset_point = store_mark();
570 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
571 which might change. */
573 if (Ustrlen(key) > sizeof(keybuffer) - 1)
575 printf("**** Overlong key encountered: %s\n", key);
578 Ustrcpy(keybuffer, key);
580 if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
581 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
582 "was not found in the file - something is wrong!\n",
586 /* Note: don't use print_time more than once in one statement, since
587 it uses a single buffer. */
592 retry = (dbdata_retry *)value;
593 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
594 retry->more_errno, retry->text,
595 print_time(retry->first_failed));
596 printf("%s ", print_time(retry->last_try));
597 printf("%s %s\n", print_time(retry->next_try),
598 (retry->expired)? "*" : "");
602 wait = (dbdata_wait *)value;
603 printf("%s ", keybuffer);
605 name[MESSAGE_ID_LENGTH] = 0;
607 if (wait->count > WAIT_NAME_MAX)
610 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
611 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
612 wait->count = WAIT_NAME_MAX;
613 yield = count_bad = 1;
615 for (int i = 1; i <= wait->count; i++)
617 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
618 if (count_bad && name[0] == 0) break;
619 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
620 Ustrspn(name, "0123456789"
621 "abcdefghijklmnopqrstuvwxyz"
622 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
625 "**** Data for %s corrupted: bad character in message id\n",
627 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
628 fprintf(stderr, "%02x ", name[j]);
629 fprintf(stderr, "\n");
634 t += MESSAGE_ID_LENGTH;
640 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
645 callout = (dbdata_callout_cache *)value;
647 /* New-style address record */
649 if (length == sizeof(dbdata_callout_cache_address))
651 printf("%s %s callout=%s\n",
652 print_time(((dbdata_generic *)value)->time_stamp),
654 print_cache(callout->result));
657 /* New-style domain record */
659 else if (length == sizeof(dbdata_callout_cache))
661 printf("%s %s callout=%s postmaster=%s",
662 print_time(((dbdata_generic *)value)->time_stamp),
664 print_cache(callout->result),
665 print_cache(callout->postmaster_result));
666 if (callout->postmaster_result != ccache_unknown)
667 printf(" (%s)", print_time(callout->postmaster_stamp));
668 printf(" random=%s", print_cache(callout->random_result));
669 if (callout->random_result != ccache_unknown)
670 printf(" (%s)", print_time(callout->random_stamp));
677 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
679 ratelimit = (dbdata_ratelimit *)value;
680 rate_unique = (dbdata_ratelimit_unique *)value;
681 printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
682 print_time(ratelimit->time_stamp),
683 ratelimit->time_usec, ratelimit->rate,
684 print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
689 ratelimit = (dbdata_ratelimit *)value;
690 printf("%s.%06d rate: %10.3f key: %s\n",
691 print_time(ratelimit->time_stamp),
692 ratelimit->time_usec, ratelimit->rate,
698 session = (dbdata_tls_session *)value;
699 printf(" %s %.*s\n", keybuffer, length, session->session);
703 store_reset(reset_point);
710 #endif /* EXIM_DUMPDB */
716 /*************************************************
717 * The exim_fixdb main program *
718 *************************************************/
720 /* In order not to hold the database lock any longer than is necessary, each
721 operation on the database uses a separate open/close call. This is expensive,
722 but then using this utility is not expected to be very common. Its main use is
723 to provide a way of patching up hints databases in order to run tests.
728 This causes the data from the given record to be displayed, or "not found"
729 to be output. Note that in the retry database, destination names are
730 preceded by R: or T: for router or transport retry info.
733 This causes the given record to be deleted or "not found" to be output.
735 (3) <record name> <field number> <value>
736 This sets the given value into the given field, identified by a number
737 which is output by the display command. Not all types of record can
741 This exits from exim_fixdb.
743 If the record name is omitted from (2) or (3), the previously used record name
747 int main(int argc, char **cargv)
750 uschar **argv = USS cargv;
755 name[0] = 0; /* No name set */
757 /* Sort out the database type, verify what we are working on and then process
760 dbdata_type = check_args(argc, argv, US"fixdb", US"");
761 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
763 for(; (reset_point = store_mark()); store_reset(reset_point))
770 dbdata_callout_cache *callout;
771 dbdata_ratelimit *ratelimit;
772 dbdata_ratelimit_unique *rate_unique;
773 dbdata_tls_session *session;
776 uschar field[256], value[256];
779 if (Ufgets(buffer, 256, stdin) == NULL) break;
781 buffer[Ustrlen(buffer)-1] = 0;
782 field[0] = value[0] = 0;
784 /* If the buffer contains just one digit, or just consists of "d", use the
785 previous name for an update. */
787 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
788 || Ustrcmp(buffer, "d") == 0)
792 printf("No previous record name is set\n");
795 (void)sscanf(CS buffer, "%s %s", field, value);
800 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
803 /* Handle an update request */
808 spool_directory = argv[1];
810 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
813 if (Ustrcmp(field, "d") == 0)
815 if (value[0] != 0) printf("unexpected value after \"d\"\n");
816 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
817 "not found" : "deleted");
822 else if (isdigit((uschar)field[0]))
824 int fieldno = Uatoi(field);
827 printf("value missing\n");
833 record = dbfn_read_with_length(dbm, name, &oldlength);
834 if (record == NULL) printf("not found\n"); else
837 /*int length = 0; Stops compiler warning */
842 retry = (dbdata_retry *)record;
843 /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
847 case 0: retry->basic_errno = Uatoi(value);
849 case 1: retry->more_errno = Uatoi(value);
851 case 2: if ((tt = read_time(value)) > 0) retry->first_failed = tt;
852 else printf("bad time value\n");
854 case 3: if ((tt = read_time(value)) > 0) retry->last_try = tt;
855 else printf("bad time value\n");
857 case 4: if ((tt = read_time(value)) > 0) retry->next_try = tt;
858 else printf("bad time value\n");
860 case 5: if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
861 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
862 else printf("\"yes\" or \"no\" expected=n");
864 default: printf("unknown field number\n");
871 printf("Can't change contents of wait database record\n");
875 printf("Can't change contents of misc database record\n");
879 callout = (dbdata_callout_cache *)record;
880 /* length = sizeof(dbdata_callout_cache); */
883 case 0: callout->result = Uatoi(value);
885 case 1: callout->postmaster_result = Uatoi(value);
887 case 2: callout->random_result = Uatoi(value);
889 default: printf("unknown field number\n");
896 ratelimit = (dbdata_ratelimit *)record;
899 case 0: if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
900 else printf("bad time value\n");
902 case 1: ratelimit->time_usec = Uatoi(value);
904 case 2: ratelimit->rate = Ustrtod(value, NULL);
906 case 3: if (Ustrstr(name, "/unique/") != NULL
907 && oldlength >= sizeof(dbdata_ratelimit_unique))
909 rate_unique = (dbdata_ratelimit_unique *)record;
910 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
911 else printf("bad time value\n");
914 /* else fall through */
916 case 5: if (Ustrstr(name, "/unique/") != NULL
917 && oldlength >= sizeof(dbdata_ratelimit_unique))
925 md5_end(&md5info, value, Ustrlen(value), md5sum);
926 hash = md5sum[0] << 0 | md5sum[1] << 8
927 | md5sum[2] << 16 | md5sum[3] << 24;
928 hinc = md5sum[4] << 0 | md5sum[5] << 8
929 | md5sum[6] << 16 | md5sum[7] << 24;
930 rate_unique = (dbdata_ratelimit_unique *)record;
932 for (unsigned n = 0; n < 8; n++, hash += hinc)
934 int bit = 1 << (hash % 8);
935 int byte = (hash / 8) % rate_unique->bloom_size;
936 if ((rate_unique->bloom[byte] & bit) == 0)
939 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
943 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
946 /* else fall through */
947 default: printf("unknown field number\n");
954 printf("Can't change contents of tls database record\n");
958 dbfn_write(dbm, name, record, oldlength);
965 printf("field number or d expected\n");
970 if (!verify) continue;
973 /* The "name" q causes an exit */
975 else if (Ustrcmp(name, "q") == 0) return 0;
977 /* Handle a read request, or verify after an update. */
979 spool_directory = argv[1];
980 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
983 if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
985 printf("record %s not found\n", name);
991 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
995 retry = (dbdata_retry *)record;
996 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
997 printf("1 extra data: %d\n", retry->more_errno);
998 printf("2 first failed: %s\n", print_time(retry->first_failed));
999 printf("3 last try: %s\n", print_time(retry->last_try));
1000 printf("4 next try: %s\n", print_time(retry->next_try));
1001 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
1005 wait = (dbdata_wait *)record;
1007 printf("Sequence: %d\n", wait->sequence);
1008 if (wait->count > WAIT_NAME_MAX)
1010 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
1011 wait->count, WAIT_NAME_MAX);
1012 wait->count = WAIT_NAME_MAX;
1015 for (int i = 1; i <= wait->count; i++)
1017 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1018 value[MESSAGE_ID_LENGTH] = 0;
1019 if (count_bad && value[0] == 0) break;
1020 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1021 Ustrspn(value, "0123456789"
1022 "abcdefghijklmnopqrstuvwxyz"
1023 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1025 printf("\n**** Data corrupted: bad character in message id ****\n");
1026 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
1027 printf("%02x ", value[j]);
1031 printf("%s ", value);
1032 t += MESSAGE_ID_LENGTH;
1041 callout = (dbdata_callout_cache *)record;
1042 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1044 if (oldlength > sizeof(dbdata_callout_cache_address))
1046 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1047 callout->postmaster_result);
1048 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1049 callout->random_result);
1053 case type_ratelimit:
1054 ratelimit = (dbdata_ratelimit *)record;
1055 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1056 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1057 printf("2 sender rate: % .3f\n", ratelimit->rate);
1058 if (Ustrstr(name, "/unique/") != NULL
1059 && oldlength >= sizeof(dbdata_ratelimit_unique))
1061 rate_unique = (dbdata_ratelimit_unique *)record;
1062 printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1063 printf("4 test filter membership\n");
1064 printf("5 add element to filter\n");
1069 session = (dbdata_tls_session *)value;
1070 printf("0 time stamp: %s\n", print_time(session->time_stamp));
1071 printf("1 session: .%s\n", session->session);
1076 /* The database is closed after each request */
1085 #endif /* EXIM_FIXDB */
1090 /*************************************************
1091 * The exim_tidydb main program *
1092 *************************************************/
1095 /* Utility program to tidy the contents of an exim database file. There is one
1098 -t <time> expiry time for old records - default 30 days
1100 For backwards compatibility, an -f option is recognized and ignored. (It used
1101 to request a "full" tidy. This version always does the whole job.) */
1104 typedef struct key_item {
1105 struct key_item *next;
1110 int main(int argc, char **cargv)
1112 struct stat statbuf;
1113 int maxkeep = 30 * 24 * 60 * 60;
1114 int dbdata_type, i, oldest, path_len;
1115 key_item *keychain = NULL;
1119 EXIM_CURSOR *cursor;
1120 uschar **argv = USS cargv;
1124 /* Scan the options */
1126 for (i = 1; i < argc; i++)
1128 if (argv[i][0] != '-') break;
1129 if (Ustrcmp(argv[i], "-f") == 0) continue;
1130 if (Ustrcmp(argv[i], "-t") == 0)
1138 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1139 (void)sscanf(CS s, "%d%n", &value, &count);
1143 case 'w': value *= 7;
1144 case 'd': value *= 24;
1145 case 'h': value *= 60;
1146 case 'm': value *= 60;
1149 default: usage(US"tidydb", US" [-t <time>]");
1154 else usage(US"tidydb", US" [-t <time>]");
1157 /* Adjust argument values and process arguments */
1162 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1164 /* Compute the oldest keep time, verify what we are doing, and open the
1167 oldest = time(NULL) - maxkeep;
1168 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1170 spool_directory = argv[1];
1171 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
1174 /* Prepare for building file names */
1176 sprintf(CS buffer, "%s/input/", argv[1]);
1177 path_len = Ustrlen(buffer);
1180 /* It appears, by experiment, that it is a bad idea to make changes
1181 to the file while scanning it. Pity the man page doesn't warn you about that.
1182 Therefore, we scan and build a list of all the keys. Then we use that to
1183 read the records and possibly update them. */
1185 for (key = dbfn_scan(dbm, TRUE, &cursor);
1187 key = dbfn_scan(dbm, FALSE, &cursor))
1189 key_item *k = store_get(sizeof(key_item) + Ustrlen(key), is_tainted(key));
1192 Ustrcpy(k->key, key);
1195 /* Now scan the collected keys and operate on the records, resetting
1196 the store each time round. */
1198 for (; keychain && (reset_point = store_mark()); store_reset(reset_point))
1200 dbdata_generic *value;
1202 key = keychain->key;
1203 keychain = keychain->next;
1204 value = dbfn_read_with_length(dbm, key, NULL);
1206 /* A continuation record may have been deleted or renamed already, so
1207 non-existence is not serious. */
1209 if (value == NULL) continue;
1211 /* Delete if too old */
1213 if (value->time_stamp < oldest)
1215 printf("deleted %s (too old)\n", key);
1216 dbfn_delete(dbm, key);
1220 /* Do database-specific tidying for wait databases, and message-
1221 specific tidying for the retry database. */
1223 if (dbdata_type == type_wait)
1225 dbdata_wait *wait = (dbdata_wait *)value;
1226 BOOL update = FALSE;
1228 /* Leave corrupt records alone */
1230 if (wait->count > WAIT_NAME_MAX)
1232 printf("**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
1233 key, wait->count, wait->count, WAIT_NAME_MAX);
1237 /* Loop for renamed continuation records. For each message id,
1238 check to see if the message exists, and if not, remove its entry
1239 from the record. Because of the possibility of split input directories,
1240 we must look in both possible places for a -D file. */
1244 int length = wait->count * MESSAGE_ID_LENGTH;
1246 for (int offset = length - MESSAGE_ID_LENGTH;
1247 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1249 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1250 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1252 if (Ustat(buffer, &statbuf) != 0)
1254 buffer[path_len] = wait->text[offset+5];
1255 buffer[path_len+1] = '/';
1256 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1257 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1259 if (Ustat(buffer, &statbuf) != 0)
1261 int left = length - offset - MESSAGE_ID_LENGTH;
1262 if (left > 0) Ustrncpy(wait->text + offset,
1263 wait->text + offset + MESSAGE_ID_LENGTH, left);
1265 length -= MESSAGE_ID_LENGTH;
1271 /* If record is empty and the main record, either delete it or rename
1272 the next continuation, repeating if that is also empty. */
1274 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1276 while (wait->count == 0 && wait->sequence > 0)
1279 dbdata_generic *newvalue;
1280 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1281 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1282 if (newvalue != NULL)
1285 wait = (dbdata_wait *)newvalue;
1286 dbfn_delete(dbm, newkey);
1287 printf("renamed %s\n", newkey);
1290 else wait->sequence--;
1293 /* If we have ended up with an empty main record, delete it
1294 and break the loop. Otherwise the new record will be scanned. */
1296 if (wait->count == 0 && wait->sequence == 0)
1298 dbfn_delete(dbm, key);
1299 printf("deleted %s (empty)\n", key);
1305 /* If not an empty main record, break the loop */
1310 /* Re-write the record if required */
1314 printf("updated %s\n", key);
1315 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1316 wait->count * MESSAGE_ID_LENGTH);
1320 /* If a retry record's key ends with a message-id, check that that message
1321 still exists; if not, remove this record. */
1323 else if (dbdata_type == type_retry)
1326 int len = Ustrlen(key);
1328 if (len < MESSAGE_ID_LENGTH + 1) continue;
1329 id = key + len - MESSAGE_ID_LENGTH - 1;
1330 if (*id++ != ':') continue;
1332 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1333 if (i == 6 || i == 13)
1334 { if (id[i] != '-') break; }
1336 { if (!isalnum(id[i])) break; }
1337 if (i < MESSAGE_ID_LENGTH) continue;
1339 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1340 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1342 if (Ustat(buffer, &statbuf) != 0)
1344 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1345 if (Ustat(buffer, &statbuf) != 0)
1347 dbfn_delete(dbm, key);
1348 printf("deleted %s (no message)\n", key);
1355 printf("Tidying complete\n");
1359 #endif /* EXIM_TIDYDB */
1361 /* End of exim_dbutil.c */