1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
6 /* See the file NOTICE for conditions of use and distribution. */
9 /* This single source file is used to compile three utility programs for
10 maintaining Exim hints databases.
12 exim_dumpdb dumps out the contents
13 exim_fixdb patches the database (really for Exim maintenance/testing)
14 exim_tidydb removed obsolete data
16 In all cases, the first argument is the name of the spool directory. The second
17 argument is the name of the database file. The available names are:
19 retry: retry delivery information
20 misc: miscellaneous hints data
21 wait-<t>: message waiting information; <t> is a transport name
22 callout: callout verification cache
23 tls: TLS session resumption cache
25 There are a number of common subroutines, followed by three main programs,
26 whose inclusion is controlled by -D on the compilation command. */
32 /* Identifiers for the different database types. */
37 #define type_callout 4
38 #define type_ratelimit 5
42 /* This is used by our cut-down dbfn_open(). */
44 uschar *spool_directory;
47 /******************************************************************************/
48 /* dummies needed by Solaris build */
53 readconf_printtime(int t)
56 string_vformat_trc(gstring * g, const uschar * func, unsigned line,
57 unsigned size_limit, unsigned flags, const char *format, va_list ap)
60 string_sprintf_trc(const char * fmt, const uschar * func, unsigned line, ...)
63 string_format_trc(uschar * buf, int len, const uschar * func, unsigned line,
64 const char * fmt, ...)
67 struct global_flags f;
68 unsigned int log_selector[1];
70 BOOL split_spool_directory;
71 /******************************************************************************/
74 /*************************************************
75 * Berkeley DB error callback *
76 *************************************************/
78 /* For Berkeley DB >= 2, we can define a function to be called in case of DB
79 errors. This should help with debugging strange DB problems, e.g. getting "File
80 exists" when you try to open a db file. The API changed at release 4.3. */
82 #if defined(USE_DB) && defined(DB_VERSION_STRING)
84 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
85 dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
89 dbfn_bdb_error_callback(const char *pfx, char *msg)
93 printf("Berkeley DB error: %s\n", msg);
99 /*************************************************
101 *************************************************/
103 SIGNAL_BOOL sigalrm_seen;
106 sigalrm_handler(int sig)
108 sig = sig; /* Keep picky compilers happy */
114 /*************************************************
115 * Output usage message and exit *
116 *************************************************/
119 usage(uschar *name, uschar *options)
121 printf("Usage: exim_%s%s <spool-directory> <database-name>\n", name, options);
122 printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit | tls\n");
128 /*************************************************
129 * Sort out the command arguments *
130 *************************************************/
132 /* This function checks that there are exactly 2 arguments, and checks the
133 second of them to be sure it is a known database name. */
136 check_args(int argc, uschar **argv, uschar *name, uschar *options)
140 if (Ustrcmp(argv[2], "retry") == 0) return type_retry;
141 if (Ustrcmp(argv[2], "misc") == 0) return type_misc;
142 if (Ustrncmp(argv[2], "wait-", 5) == 0) return type_wait;
143 if (Ustrcmp(argv[2], "callout") == 0) return type_callout;
144 if (Ustrcmp(argv[2], "ratelimit") == 0) return type_ratelimit;
145 if (Ustrcmp(argv[2], "tls") == 0) return type_tls;
147 usage(name, options);
148 return -1; /* Never obeyed */
153 /*************************************************
154 * Handle attempts to write the log *
155 *************************************************/
157 /* The message gets written to stderr when log_write() is called from a
158 utility. The message always gets '\n' added on the end of it. These calls come
159 from modules such as store.c when things go drastically wrong (e.g. malloc()
160 failing). In normal use they won't get obeyed.
163 selector not relevant when running a utility
164 flags not relevant when running a utility
165 format a printf() format
166 ... arguments for format
172 log_write(unsigned int selector, int flags, const char *format, ...)
175 va_start(ap, format);
176 vfprintf(stderr, format, ap);
177 fprintf(stderr, "\n");
179 selector = selector; /* Keep picky compilers happy */
185 /*************************************************
186 * Format a time value for printing *
187 *************************************************/
189 static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss ")];
194 struct tm *tmstr = localtime(&t);
195 Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
201 /*************************************************
202 * Format a cache value for printing *
203 *************************************************/
206 print_cache(int value)
208 return (value == ccache_accept)? US"accept" :
209 (value == ccache_reject)? US"reject" :
215 /*************************************************
217 *************************************************/
224 time_t now = time(NULL);
225 struct tm *tm = localtime(&now);
230 for (uschar * t = s + Ustrlen(s) - 1; t >= s; t--)
232 if (*t == ':') continue;
233 if (!isdigit((uschar)*t)) return -1;
238 if (!isdigit((uschar)*t)) return -1;
239 value = value + (*t - '0')*10;
244 case 0: tm->tm_min = value; break;
245 case 1: tm->tm_hour = value; break;
246 case 2: tm->tm_mday = value; break;
247 case 3: tm->tm_mon = value - 1; break;
248 case 4: tm->tm_year = (value < 90)? value + 100 : value; break;
255 #endif /* EXIM_FIXDB */
259 /*************************************************
260 * Open and lock a database file *
261 *************************************************/
263 /* This is a cut-down version from the function in dbfn.h that Exim itself
264 uses. We assume the database exists, and therefore give up if we cannot open
268 name The single-component name of one of Exim's database files.
269 flags O_RDONLY or O_RDWR
270 dbblock Points to an open_db block to be filled in.
274 Returns: NULL if the open failed, or the locking failed.
275 On success, dbblock is returned. This contains the dbm pointer and
276 the fd of the locked lock file.
280 dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof, BOOL panic)
283 struct flock lock_data;
284 BOOL read_only = flags == O_RDONLY;
285 uschar * dirname, * filename;
287 /* The first thing to do is to open a separate file on which to lock. This
288 ensures that Exim has exclusive use of the database before it even tries to
289 open it. If there is a database, there should be a lock file in existence. */
291 #ifdef COMPILE_UTILITY
292 if ( asprintf(CSS &dirname, "%s/db", spool_directory) < 0
293 || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
296 dirname = string_sprintf("%s/db", spool_directory);
297 filename = string_sprintf("%s/%s.lockfile", dirname, name);
300 dbblock->lockfd = Uopen(filename, flags, 0);
301 if (dbblock->lockfd < 0)
303 printf("** Failed to open database lock file %s: %s\n", filename,
308 /* Now we must get a lock on the opened lock file; do this with a blocking
309 lock that times out. */
311 lock_data.l_type = read_only ? F_RDLCK : F_WRLCK;
312 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
314 sigalrm_seen = FALSE;
315 os_non_restarting_signal(SIGALRM, sigalrm_handler);
316 ALARM(EXIMDB_LOCK_TIMEOUT);
317 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
320 if (sigalrm_seen) errno = ETIMEDOUT;
323 printf("** Failed to get %s lock for %s: %s",
324 flags & O_WRONLY ? "write" : "read",
326 errno == ETIMEDOUT ? "timed out" : strerror(errno));
327 (void)close(dbblock->lockfd);
331 /* At this point we have an opened and locked separate lock file, that is,
332 exclusive access to the database, so we can go ahead and open it. */
334 #ifdef COMPILE_UTILITY
335 if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
337 filename = string_sprintf("%s/%s", dirname, name);
339 EXIM_DBOPEN(filename, dirname, flags, 0, &dbblock->dbptr);
343 printf("** Failed to open DBM file %s for %s:\n %s%s\n", filename,
344 read_only? "reading" : "writing", strerror(errno),
346 " (or Berkeley DB error while opening)"
351 (void)close(dbblock->lockfd);
361 /*************************************************
362 * Unlock and close a database file *
363 *************************************************/
365 /* Closing a file automatically unlocks it, so after closing the database, just
368 Argument: a pointer to an open database block
373 dbfn_close(open_db *dbblock)
375 EXIM_DBCLOSE(dbblock->dbptr);
376 (void)close(dbblock->lockfd);
382 /*************************************************
383 * Read from database file *
384 *************************************************/
386 /* Passing back the pointer unchanged is useless, because there is no guarantee
387 of alignment. Since all the records used by Exim need to be properly aligned to
388 pick out the timestamps, etc., do the copying centrally here.
391 dbblock a pointer to an open database block
392 key the key of the record to be read
393 length where to put the length (or NULL if length not wanted)
395 Returns: a pointer to the retrieved record, or
396 NULL if the record is not found
400 dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
403 EXIM_DATUM key_datum, result_datum;
404 int klen = Ustrlen(key) + 1;
405 uschar * key_copy = store_get(klen, is_tainted(key));
407 memcpy(key_copy, key, klen);
409 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
410 EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */
411 EXIM_DATUM_DATA(key_datum) = CS key_copy;
412 EXIM_DATUM_SIZE(key_datum) = klen;
414 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
416 /* Assume for now that anything stored could have been tainted. Properly
417 we should store the taint status along with the data. */
419 yield = store_get(EXIM_DATUM_SIZE(result_datum), TRUE);
420 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
421 if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
423 EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */
429 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
431 /*************************************************
432 * Write to database file *
433 *************************************************/
437 dbblock a pointer to an open database block
438 key the key of the record to be written
439 ptr a pointer to the record to be written
440 length the length of the record to be written
442 Returns: the yield of the underlying dbm or db "write" function. If this
443 is dbm, the value is zero for OK.
447 dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
449 EXIM_DATUM key_datum, value_datum;
450 dbdata_generic *gptr = (dbdata_generic *)ptr;
451 int klen = Ustrlen(key) + 1;
452 uschar * key_copy = store_get(klen, is_tainted(key));
454 memcpy(key_copy, key, klen);
455 gptr->time_stamp = time(NULL);
457 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
458 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
459 EXIM_DATUM_DATA(key_datum) = CS key_copy;
460 EXIM_DATUM_SIZE(key_datum) = klen;
461 EXIM_DATUM_DATA(value_datum) = CS ptr;
462 EXIM_DATUM_SIZE(value_datum) = length;
463 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
468 /*************************************************
469 * Delete record from database file *
470 *************************************************/
474 dbblock a pointer to an open database block
475 key the key of the record to be deleted
477 Returns: the yield of the underlying dbm or db "delete" function.
481 dbfn_delete(open_db *dbblock, const uschar *key)
483 int klen = Ustrlen(key) + 1;
484 uschar * key_copy = store_get(klen, is_tainted(key));
486 memcpy(key_copy, key, klen);
487 EXIM_DATUM key_datum;
488 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
489 EXIM_DATUM_DATA(key_datum) = CS key_copy;
490 EXIM_DATUM_SIZE(key_datum) = klen;
491 return EXIM_DBDEL(dbblock->dbptr, key_datum);
494 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
498 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
499 /*************************************************
500 * Scan the keys of a database file *
501 *************************************************/
505 dbblock a pointer to an open database block
506 start TRUE if starting a new scan
507 FALSE if continuing with the current scan
508 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
509 that use the notion of a cursor
511 Returns: the next record from the file, or
512 NULL if there are no more
516 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
518 EXIM_DATUM key_datum, value_datum;
520 value_datum = value_datum; /* dummy; not all db libraries use this */
522 /* Some dbm require an initialization */
524 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
526 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
527 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
529 yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
530 US EXIM_DATUM_DATA(key_datum) : NULL;
532 /* Some dbm require a termination */
534 if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
537 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
542 /*************************************************
543 * The exim_dumpdb main program *
544 *************************************************/
547 main(int argc, char **cargv)
554 uschar **argv = USS cargv;
555 uschar keybuffer[1024];
557 /* Check the arguments, and open the database */
559 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
560 spool_directory = argv[1];
561 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
564 /* Scan the file, formatting the information for each entry. Note
565 that data is returned in a malloc'ed block, in order that it be
566 correctly aligned. */
568 for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
570 key = dbfn_scan(dbm, FALSE, &cursor))
574 dbdata_callout_cache *callout;
575 dbdata_ratelimit *ratelimit;
576 dbdata_ratelimit_unique *rate_unique;
577 dbdata_tls_session *session;
581 uschar name[MESSAGE_ID_LENGTH + 1];
583 rmark reset_point = store_mark();
585 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
586 which might change. */
588 if (Ustrlen(key) > sizeof(keybuffer) - 1)
590 printf("**** Overlong key encountered: %s\n", key);
593 Ustrcpy(keybuffer, key);
595 if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
596 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
597 "was not found in the file - something is wrong!\n",
601 /* Note: don't use print_time more than once in one statement, since
602 it uses a single buffer. */
607 retry = (dbdata_retry *)value;
608 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
609 retry->more_errno, retry->text,
610 print_time(retry->first_failed));
611 printf("%s ", print_time(retry->last_try));
612 printf("%s %s\n", print_time(retry->next_try),
613 (retry->expired)? "*" : "");
617 wait = (dbdata_wait *)value;
618 printf("%s ", keybuffer);
620 name[MESSAGE_ID_LENGTH] = 0;
622 if (wait->count > WAIT_NAME_MAX)
625 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
626 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
627 wait->count = WAIT_NAME_MAX;
628 yield = count_bad = 1;
630 for (int i = 1; i <= wait->count; i++)
632 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
633 if (count_bad && name[0] == 0) break;
634 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
635 Ustrspn(name, "0123456789"
636 "abcdefghijklmnopqrstuvwxyz"
637 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
640 "**** Data for %s corrupted: bad character in message id\n",
642 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
643 fprintf(stderr, "%02x ", name[j]);
644 fprintf(stderr, "\n");
649 t += MESSAGE_ID_LENGTH;
655 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
660 callout = (dbdata_callout_cache *)value;
662 /* New-style address record */
664 if (length == sizeof(dbdata_callout_cache_address))
666 printf("%s %s callout=%s\n",
667 print_time(((dbdata_generic *)value)->time_stamp),
669 print_cache(callout->result));
672 /* New-style domain record */
674 else if (length == sizeof(dbdata_callout_cache))
676 printf("%s %s callout=%s postmaster=%s",
677 print_time(((dbdata_generic *)value)->time_stamp),
679 print_cache(callout->result),
680 print_cache(callout->postmaster_result));
681 if (callout->postmaster_result != ccache_unknown)
682 printf(" (%s)", print_time(callout->postmaster_stamp));
683 printf(" random=%s", print_cache(callout->random_result));
684 if (callout->random_result != ccache_unknown)
685 printf(" (%s)", print_time(callout->random_stamp));
692 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
694 ratelimit = (dbdata_ratelimit *)value;
695 rate_unique = (dbdata_ratelimit_unique *)value;
696 printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
697 print_time(ratelimit->time_stamp),
698 ratelimit->time_usec, ratelimit->rate,
699 print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
704 ratelimit = (dbdata_ratelimit *)value;
705 printf("%s.%06d rate: %10.3f key: %s\n",
706 print_time(ratelimit->time_stamp),
707 ratelimit->time_usec, ratelimit->rate,
713 session = (dbdata_tls_session *)value;
714 printf(" %s %.*s\n", keybuffer, length, session->session);
718 store_reset(reset_point);
725 #endif /* EXIM_DUMPDB */
731 /*************************************************
732 * The exim_fixdb main program *
733 *************************************************/
735 /* In order not to hold the database lock any longer than is necessary, each
736 operation on the database uses a separate open/close call. This is expensive,
737 but then using this utility is not expected to be very common. Its main use is
738 to provide a way of patching up hints databases in order to run tests.
743 This causes the data from the given record to be displayed, or "not found"
744 to be output. Note that in the retry database, destination names are
745 preceded by R: or T: for router or transport retry info.
748 This causes the given record to be deleted or "not found" to be output.
750 (3) <record name> <field number> <value>
751 This sets the given value into the given field, identified by a number
752 which is output by the display command. Not all types of record can
756 This exits from exim_fixdb.
758 If the record name is omitted from (2) or (3), the previously used record name
762 int main(int argc, char **cargv)
765 uschar **argv = USS cargv;
770 name[0] = 0; /* No name set */
772 /* Sort out the database type, verify what we are working on and then process
775 dbdata_type = check_args(argc, argv, US"fixdb", US"");
776 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
778 for(; (reset_point = store_mark()); store_reset(reset_point))
785 dbdata_callout_cache *callout;
786 dbdata_ratelimit *ratelimit;
787 dbdata_ratelimit_unique *rate_unique;
788 dbdata_tls_session *session;
791 uschar field[256], value[256];
794 if (Ufgets(buffer, 256, stdin) == NULL) break;
796 buffer[Ustrlen(buffer)-1] = 0;
797 field[0] = value[0] = 0;
799 /* If the buffer contains just one digit, or just consists of "d", use the
800 previous name for an update. */
802 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
803 || Ustrcmp(buffer, "d") == 0)
807 printf("No previous record name is set\n");
810 (void)sscanf(CS buffer, "%s %s", field, value);
815 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
818 /* Handle an update request */
823 spool_directory = argv[1];
825 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
828 if (Ustrcmp(field, "d") == 0)
830 if (value[0] != 0) printf("unexpected value after \"d\"\n");
831 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
832 "not found" : "deleted");
837 else if (isdigit((uschar)field[0]))
839 int fieldno = Uatoi(field);
842 printf("value missing\n");
848 record = dbfn_read_with_length(dbm, name, &oldlength);
849 if (record == NULL) printf("not found\n"); else
852 /*int length = 0; Stops compiler warning */
857 retry = (dbdata_retry *)record;
858 /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
862 case 0: retry->basic_errno = Uatoi(value);
864 case 1: retry->more_errno = Uatoi(value);
866 case 2: if ((tt = read_time(value)) > 0) retry->first_failed = tt;
867 else printf("bad time value\n");
869 case 3: if ((tt = read_time(value)) > 0) retry->last_try = tt;
870 else printf("bad time value\n");
872 case 4: if ((tt = read_time(value)) > 0) retry->next_try = tt;
873 else printf("bad time value\n");
875 case 5: if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
876 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
877 else printf("\"yes\" or \"no\" expected=n");
879 default: printf("unknown field number\n");
886 printf("Can't change contents of wait database record\n");
890 printf("Can't change contents of misc database record\n");
894 callout = (dbdata_callout_cache *)record;
895 /* length = sizeof(dbdata_callout_cache); */
898 case 0: callout->result = Uatoi(value);
900 case 1: callout->postmaster_result = Uatoi(value);
902 case 2: callout->random_result = Uatoi(value);
904 default: printf("unknown field number\n");
911 ratelimit = (dbdata_ratelimit *)record;
914 case 0: if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
915 else printf("bad time value\n");
917 case 1: ratelimit->time_usec = Uatoi(value);
919 case 2: ratelimit->rate = Ustrtod(value, NULL);
921 case 3: if (Ustrstr(name, "/unique/") != NULL
922 && oldlength >= sizeof(dbdata_ratelimit_unique))
924 rate_unique = (dbdata_ratelimit_unique *)record;
925 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
926 else printf("bad time value\n");
929 /* else fall through */
931 case 5: if (Ustrstr(name, "/unique/") != NULL
932 && oldlength >= sizeof(dbdata_ratelimit_unique))
940 md5_end(&md5info, value, Ustrlen(value), md5sum);
941 hash = md5sum[0] << 0 | md5sum[1] << 8
942 | md5sum[2] << 16 | md5sum[3] << 24;
943 hinc = md5sum[4] << 0 | md5sum[5] << 8
944 | md5sum[6] << 16 | md5sum[7] << 24;
945 rate_unique = (dbdata_ratelimit_unique *)record;
947 for (unsigned n = 0; n < 8; n++, hash += hinc)
949 int bit = 1 << (hash % 8);
950 int byte = (hash / 8) % rate_unique->bloom_size;
951 if ((rate_unique->bloom[byte] & bit) == 0)
954 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
958 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
961 /* else fall through */
962 default: printf("unknown field number\n");
969 printf("Can't change contents of tls database record\n");
973 dbfn_write(dbm, name, record, oldlength);
980 printf("field number or d expected\n");
985 if (!verify) continue;
988 /* The "name" q causes an exit */
990 else if (Ustrcmp(name, "q") == 0) return 0;
992 /* Handle a read request, or verify after an update. */
994 spool_directory = argv[1];
995 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
998 if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
1000 printf("record %s not found\n", name);
1006 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
1010 retry = (dbdata_retry *)record;
1011 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
1012 printf("1 extra data: %d\n", retry->more_errno);
1013 printf("2 first failed: %s\n", print_time(retry->first_failed));
1014 printf("3 last try: %s\n", print_time(retry->last_try));
1015 printf("4 next try: %s\n", print_time(retry->next_try));
1016 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
1020 wait = (dbdata_wait *)record;
1022 printf("Sequence: %d\n", wait->sequence);
1023 if (wait->count > WAIT_NAME_MAX)
1025 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
1026 wait->count, WAIT_NAME_MAX);
1027 wait->count = WAIT_NAME_MAX;
1030 for (int i = 1; i <= wait->count; i++)
1032 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1033 value[MESSAGE_ID_LENGTH] = 0;
1034 if (count_bad && value[0] == 0) break;
1035 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1036 Ustrspn(value, "0123456789"
1037 "abcdefghijklmnopqrstuvwxyz"
1038 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1040 printf("\n**** Data corrupted: bad character in message id ****\n");
1041 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
1042 printf("%02x ", value[j]);
1046 printf("%s ", value);
1047 t += MESSAGE_ID_LENGTH;
1056 callout = (dbdata_callout_cache *)record;
1057 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1059 if (oldlength > sizeof(dbdata_callout_cache_address))
1061 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1062 callout->postmaster_result);
1063 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1064 callout->random_result);
1068 case type_ratelimit:
1069 ratelimit = (dbdata_ratelimit *)record;
1070 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1071 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1072 printf("2 sender rate: % .3f\n", ratelimit->rate);
1073 if (Ustrstr(name, "/unique/") != NULL
1074 && oldlength >= sizeof(dbdata_ratelimit_unique))
1076 rate_unique = (dbdata_ratelimit_unique *)record;
1077 printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1078 printf("4 test filter membership\n");
1079 printf("5 add element to filter\n");
1084 session = (dbdata_tls_session *)value;
1085 printf("0 time stamp: %s\n", print_time(session->time_stamp));
1086 printf("1 session: .%s\n", session->session);
1091 /* The database is closed after each request */
1100 #endif /* EXIM_FIXDB */
1105 /*************************************************
1106 * The exim_tidydb main program *
1107 *************************************************/
1110 /* Utility program to tidy the contents of an exim database file. There is one
1113 -t <time> expiry time for old records - default 30 days
1115 For backwards compatibility, an -f option is recognized and ignored. (It used
1116 to request a "full" tidy. This version always does the whole job.) */
1119 typedef struct key_item {
1120 struct key_item *next;
1125 int main(int argc, char **cargv)
1127 struct stat statbuf;
1128 int maxkeep = 30 * 24 * 60 * 60;
1129 int dbdata_type, i, oldest, path_len;
1130 key_item *keychain = NULL;
1134 EXIM_CURSOR *cursor;
1135 uschar **argv = USS cargv;
1139 /* Scan the options */
1141 for (i = 1; i < argc; i++)
1143 if (argv[i][0] != '-') break;
1144 if (Ustrcmp(argv[i], "-f") == 0) continue;
1145 if (Ustrcmp(argv[i], "-t") == 0)
1153 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1154 (void)sscanf(CS s, "%d%n", &value, &count);
1158 case 'w': value *= 7;
1159 case 'd': value *= 24;
1160 case 'h': value *= 60;
1161 case 'm': value *= 60;
1164 default: usage(US"tidydb", US" [-t <time>]");
1169 else usage(US"tidydb", US" [-t <time>]");
1172 /* Adjust argument values and process arguments */
1177 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1179 /* Compute the oldest keep time, verify what we are doing, and open the
1182 oldest = time(NULL) - maxkeep;
1183 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1185 spool_directory = argv[1];
1186 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
1189 /* Prepare for building file names */
1191 sprintf(CS buffer, "%s/input/", argv[1]);
1192 path_len = Ustrlen(buffer);
1195 /* It appears, by experiment, that it is a bad idea to make changes
1196 to the file while scanning it. Pity the man page doesn't warn you about that.
1197 Therefore, we scan and build a list of all the keys. Then we use that to
1198 read the records and possibly update them. */
1200 for (key = dbfn_scan(dbm, TRUE, &cursor);
1202 key = dbfn_scan(dbm, FALSE, &cursor))
1204 key_item *k = store_get(sizeof(key_item) + Ustrlen(key), is_tainted(key));
1207 Ustrcpy(k->key, key);
1210 /* Now scan the collected keys and operate on the records, resetting
1211 the store each time round. */
1213 for (; keychain && (reset_point = store_mark()); store_reset(reset_point))
1215 dbdata_generic *value;
1217 key = keychain->key;
1218 keychain = keychain->next;
1219 value = dbfn_read_with_length(dbm, key, NULL);
1221 /* A continuation record may have been deleted or renamed already, so
1222 non-existence is not serious. */
1224 if (value == NULL) continue;
1226 /* Delete if too old */
1228 if (value->time_stamp < oldest)
1230 printf("deleted %s (too old)\n", key);
1231 dbfn_delete(dbm, key);
1235 /* Do database-specific tidying for wait databases, and message-
1236 specific tidying for the retry database. */
1238 if (dbdata_type == type_wait)
1240 dbdata_wait *wait = (dbdata_wait *)value;
1241 BOOL update = FALSE;
1243 /* Leave corrupt records alone */
1245 if (wait->count > WAIT_NAME_MAX)
1247 printf("**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
1248 key, wait->count, wait->count, WAIT_NAME_MAX);
1252 /* Loop for renamed continuation records. For each message id,
1253 check to see if the message exists, and if not, remove its entry
1254 from the record. Because of the possibility of split input directories,
1255 we must look in both possible places for a -D file. */
1259 int length = wait->count * MESSAGE_ID_LENGTH;
1261 for (int offset = length - MESSAGE_ID_LENGTH;
1262 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1264 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1265 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1267 if (Ustat(buffer, &statbuf) != 0)
1269 buffer[path_len] = wait->text[offset+5];
1270 buffer[path_len+1] = '/';
1271 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1272 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1274 if (Ustat(buffer, &statbuf) != 0)
1276 int left = length - offset - MESSAGE_ID_LENGTH;
1277 if (left > 0) Ustrncpy(wait->text + offset,
1278 wait->text + offset + MESSAGE_ID_LENGTH, left);
1280 length -= MESSAGE_ID_LENGTH;
1286 /* If record is empty and the main record, either delete it or rename
1287 the next continuation, repeating if that is also empty. */
1289 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1291 while (wait->count == 0 && wait->sequence > 0)
1294 dbdata_generic *newvalue;
1295 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1296 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1297 if (newvalue != NULL)
1300 wait = (dbdata_wait *)newvalue;
1301 dbfn_delete(dbm, newkey);
1302 printf("renamed %s\n", newkey);
1305 else wait->sequence--;
1308 /* If we have ended up with an empty main record, delete it
1309 and break the loop. Otherwise the new record will be scanned. */
1311 if (wait->count == 0 && wait->sequence == 0)
1313 dbfn_delete(dbm, key);
1314 printf("deleted %s (empty)\n", key);
1320 /* If not an empty main record, break the loop */
1325 /* Re-write the record if required */
1329 printf("updated %s\n", key);
1330 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1331 wait->count * MESSAGE_ID_LENGTH);
1335 /* If a retry record's key ends with a message-id, check that that message
1336 still exists; if not, remove this record. */
1338 else if (dbdata_type == type_retry)
1341 int len = Ustrlen(key);
1343 if (len < MESSAGE_ID_LENGTH + 1) continue;
1344 id = key + len - MESSAGE_ID_LENGTH - 1;
1345 if (*id++ != ':') continue;
1347 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1348 if (i == 6 || i == 13)
1349 { if (id[i] != '-') break; }
1351 { if (!isalnum(id[i])) break; }
1352 if (i < MESSAGE_ID_LENGTH) continue;
1354 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1355 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1357 if (Ustat(buffer, &statbuf) != 0)
1359 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1360 if (Ustat(buffer, &statbuf) != 0)
1362 dbfn_delete(dbm, key);
1363 printf("deleted %s (no message)\n", key);
1370 printf("Tidying complete\n");
1374 #endif /* EXIM_TIDYDB */
1376 /* End of exim_dbutil.c */