1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
6 /* Copyright (c) The Exim Maintainers 2020 */
7 /* See the file NOTICE for conditions of use and distribution. */
10 /* This single source file is used to compile three utility programs for
11 maintaining Exim hints databases.
13 exim_dumpdb dumps out the contents
14 exim_fixdb patches the database (really for Exim maintenance/testing)
15 exim_tidydb removed obsolete data
17 In all cases, the first argument is the name of the spool directory. The second
18 argument is the name of the database file. The available names are:
20 retry: retry delivery information
21 misc: miscellaneous hints data
22 wait-<t>: message waiting information; <t> is a transport name
23 callout: callout verification cache
24 tls: TLS session resumption cache
26 There are a number of common subroutines, followed by three main programs,
27 whose inclusion is controlled by -D on the compilation command. */
33 /* Identifiers for the different database types. */
38 #define type_callout 4
39 #define type_ratelimit 5
43 /* This is used by our cut-down dbfn_open(). */
45 uschar *spool_directory;
48 /******************************************************************************/
49 /* dummies needed by Solaris build */
54 readconf_printtime(int t)
57 string_vformat_trc(gstring * g, const uschar * func, unsigned line,
58 unsigned size_limit, unsigned flags, const char *format, va_list ap)
61 string_sprintf_trc(const char * fmt, const uschar * func, unsigned line, ...)
64 string_format_trc(uschar * buf, int len, const uschar * func, unsigned line,
65 const char * fmt, ...)
68 struct global_flags f;
69 unsigned int log_selector[1];
71 BOOL split_spool_directory;
72 /******************************************************************************/
75 /*************************************************
76 * Berkeley DB error callback *
77 *************************************************/
79 /* For Berkeley DB >= 2, we can define a function to be called in case of DB
80 errors. This should help with debugging strange DB problems, e.g. getting "File
81 exists" when you try to open a db file. The API changed at release 4.3. */
83 #if defined(USE_DB) && defined(DB_VERSION_STRING)
85 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
86 dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
90 dbfn_bdb_error_callback(const char *pfx, char *msg)
94 printf("Berkeley DB error: %s\n", msg);
100 /*************************************************
102 *************************************************/
104 SIGNAL_BOOL sigalrm_seen;
107 sigalrm_handler(int sig)
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");
183 /*************************************************
184 * Format a time value for printing *
185 *************************************************/
187 static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss ")];
192 struct tm *tmstr = localtime(&t);
193 Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
199 /*************************************************
200 * Format a cache value for printing *
201 *************************************************/
204 print_cache(int value)
206 return (value == ccache_accept)? US"accept" :
207 (value == ccache_reject)? US"reject" :
213 /*************************************************
215 *************************************************/
222 time_t now = time(NULL);
223 struct tm *tm = localtime(&now);
228 for (uschar * t = s + Ustrlen(s) - 1; t >= s; t--)
230 if (*t == ':') continue;
231 if (!isdigit((uschar)*t)) return -1;
236 if (!isdigit((uschar)*t)) return -1;
237 value = value + (*t - '0')*10;
242 case 0: tm->tm_min = value; break;
243 case 1: tm->tm_hour = value; break;
244 case 2: tm->tm_mday = value; break;
245 case 3: tm->tm_mon = value - 1; break;
246 case 4: tm->tm_year = (value < 90)? value + 100 : value; break;
253 #endif /* EXIM_FIXDB */
257 /*************************************************
258 * Open and lock a database file *
259 *************************************************/
261 /* This is a cut-down version from the function in dbfn.h that Exim itself
262 uses. We assume the database exists, and therefore give up if we cannot open
266 name The single-component name of one of Exim's database files.
267 flags O_RDONLY or O_RDWR
268 dbblock Points to an open_db block to be filled in.
272 Returns: NULL if the open failed, or the locking failed.
273 On success, dbblock is returned. This contains the dbm pointer and
274 the fd of the locked lock file.
278 dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof, BOOL panic)
281 struct flock lock_data;
282 BOOL read_only = flags == O_RDONLY;
283 uschar * dirname, * filename;
285 /* The first thing to do is to open a separate file on which to lock. This
286 ensures that Exim has exclusive use of the database before it even tries to
287 open it. If there is a database, there should be a lock file in existence. */
289 #ifdef COMPILE_UTILITY
290 if ( asprintf(CSS &dirname, "%s/db", spool_directory) < 0
291 || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
294 dirname = string_sprintf("%s/db", spool_directory);
295 filename = string_sprintf("%s/%s.lockfile", dirname, name);
298 dbblock->lockfd = Uopen(filename, flags, 0);
299 if (dbblock->lockfd < 0)
301 printf("** Failed to open database lock file %s: %s\n", filename,
306 /* Now we must get a lock on the opened lock file; do this with a blocking
307 lock that times out. */
309 lock_data.l_type = read_only ? F_RDLCK : F_WRLCK;
310 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
312 sigalrm_seen = FALSE;
313 os_non_restarting_signal(SIGALRM, sigalrm_handler);
314 ALARM(EXIMDB_LOCK_TIMEOUT);
315 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
318 if (sigalrm_seen) errno = ETIMEDOUT;
321 printf("** Failed to get %s lock for %s: %s",
322 flags & O_WRONLY ? "write" : "read",
324 errno == ETIMEDOUT ? "timed out" : strerror(errno));
325 (void)close(dbblock->lockfd);
329 /* At this point we have an opened and locked separate lock file, that is,
330 exclusive access to the database, so we can go ahead and open it. */
332 #ifdef COMPILE_UTILITY
333 if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
335 filename = string_sprintf("%s/%s", dirname, name);
337 EXIM_DBOPEN(filename, dirname, flags, 0, &dbblock->dbptr);
341 printf("** Failed to open DBM file %s for %s:\n %s%s\n", filename,
342 read_only? "reading" : "writing", strerror(errno),
344 " (or Berkeley DB error while opening)"
349 (void)close(dbblock->lockfd);
359 /*************************************************
360 * Unlock and close a database file *
361 *************************************************/
363 /* Closing a file automatically unlocks it, so after closing the database, just
366 Argument: a pointer to an open database block
371 dbfn_close(open_db *dbblock)
373 EXIM_DBCLOSE(dbblock->dbptr);
374 (void)close(dbblock->lockfd);
380 /*************************************************
381 * Read from database file *
382 *************************************************/
384 /* Passing back the pointer unchanged is useless, because there is no guarantee
385 of alignment. Since all the records used by Exim need to be properly aligned to
386 pick out the timestamps, etc., do the copying centrally here.
389 dbblock a pointer to an open database block
390 key the key of the record to be read
391 length where to put the length (or NULL if length not wanted)
393 Returns: a pointer to the retrieved record, or
394 NULL if the record is not found
398 dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
401 EXIM_DATUM key_datum, result_datum;
402 int klen = Ustrlen(key) + 1;
403 uschar * key_copy = store_get(klen, is_tainted(key));
405 memcpy(key_copy, key, klen);
407 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
408 EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */
409 EXIM_DATUM_DATA(key_datum) = CS key_copy;
410 EXIM_DATUM_SIZE(key_datum) = klen;
412 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
414 /* Assume for now that anything stored could have been tainted. Properly
415 we should store the taint status along with the data. */
417 yield = store_get(EXIM_DATUM_SIZE(result_datum), TRUE);
418 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
419 if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
421 EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */
427 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
429 /*************************************************
430 * Write to database file *
431 *************************************************/
435 dbblock a pointer to an open database block
436 key the key of the record to be written
437 ptr a pointer to the record to be written
438 length the length of the record to be written
440 Returns: the yield of the underlying dbm or db "write" function. If this
441 is dbm, the value is zero for OK.
445 dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
447 EXIM_DATUM key_datum, value_datum;
448 dbdata_generic *gptr = (dbdata_generic *)ptr;
449 int klen = Ustrlen(key) + 1;
450 uschar * key_copy = store_get(klen, is_tainted(key));
452 memcpy(key_copy, key, klen);
453 gptr->time_stamp = time(NULL);
455 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
456 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
457 EXIM_DATUM_DATA(key_datum) = CS key_copy;
458 EXIM_DATUM_SIZE(key_datum) = klen;
459 EXIM_DATUM_DATA(value_datum) = CS ptr;
460 EXIM_DATUM_SIZE(value_datum) = length;
461 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
466 /*************************************************
467 * Delete record from database file *
468 *************************************************/
472 dbblock a pointer to an open database block
473 key the key of the record to be deleted
475 Returns: the yield of the underlying dbm or db "delete" function.
479 dbfn_delete(open_db *dbblock, const uschar *key)
481 int klen = Ustrlen(key) + 1;
482 uschar * key_copy = store_get(klen, is_tainted(key));
484 memcpy(key_copy, key, klen);
485 EXIM_DATUM key_datum;
486 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
487 EXIM_DATUM_DATA(key_datum) = CS key_copy;
488 EXIM_DATUM_SIZE(key_datum) = klen;
489 return EXIM_DBDEL(dbblock->dbptr, key_datum);
492 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
496 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
497 /*************************************************
498 * Scan the keys of a database file *
499 *************************************************/
503 dbblock a pointer to an open database block
504 start TRUE if starting a new scan
505 FALSE if continuing with the current scan
506 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
507 that use the notion of a cursor
509 Returns: the next record from the file, or
510 NULL if there are no more
514 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
516 EXIM_DATUM key_datum, value_datum;
518 value_datum = value_datum; /* dummy; not all db libraries use this */
520 /* Some dbm require an initialization */
522 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
524 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
525 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
527 yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
528 US EXIM_DATUM_DATA(key_datum) : NULL;
530 /* Some dbm require a termination */
532 if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
535 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
540 /*************************************************
541 * The exim_dumpdb main program *
542 *************************************************/
545 main(int argc, char **cargv)
552 uschar **argv = USS cargv;
553 uschar keybuffer[1024];
555 /* Check the arguments, and open the database */
557 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
558 spool_directory = argv[1];
559 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
562 /* Scan the file, formatting the information for each entry. Note
563 that data is returned in a malloc'ed block, in order that it be
564 correctly aligned. */
566 for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
568 key = dbfn_scan(dbm, FALSE, &cursor))
572 dbdata_callout_cache *callout;
573 dbdata_ratelimit *ratelimit;
574 dbdata_ratelimit_unique *rate_unique;
575 dbdata_tls_session *session;
579 uschar name[MESSAGE_ID_LENGTH + 1];
581 rmark reset_point = store_mark();
583 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
584 which might change. */
586 if (Ustrlen(key) > sizeof(keybuffer) - 1)
588 printf("**** Overlong key encountered: %s\n", key);
591 Ustrcpy(keybuffer, key);
593 if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
594 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
595 "was not found in the file - something is wrong!\n",
599 /* Note: don't use print_time more than once in one statement, since
600 it uses a single buffer. */
605 retry = (dbdata_retry *)value;
606 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
607 retry->more_errno, retry->text,
608 print_time(retry->first_failed));
609 printf("%s ", print_time(retry->last_try));
610 printf("%s %s\n", print_time(retry->next_try),
611 (retry->expired)? "*" : "");
615 wait = (dbdata_wait *)value;
616 printf("%s ", keybuffer);
618 name[MESSAGE_ID_LENGTH] = 0;
620 if (wait->count > WAIT_NAME_MAX)
623 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
624 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
625 wait->count = WAIT_NAME_MAX;
626 yield = count_bad = 1;
628 for (int i = 1; i <= wait->count; i++)
630 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
631 if (count_bad && name[0] == 0) break;
632 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
633 Ustrspn(name, "0123456789"
634 "abcdefghijklmnopqrstuvwxyz"
635 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
638 "**** Data for %s corrupted: bad character in message id\n",
640 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
641 fprintf(stderr, "%02x ", name[j]);
642 fprintf(stderr, "\n");
647 t += MESSAGE_ID_LENGTH;
653 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
658 callout = (dbdata_callout_cache *)value;
660 /* New-style address record */
662 if (length == sizeof(dbdata_callout_cache_address))
664 printf("%s %s callout=%s\n",
665 print_time(((dbdata_generic *)value)->time_stamp),
667 print_cache(callout->result));
670 /* New-style domain record */
672 else if (length == sizeof(dbdata_callout_cache))
674 printf("%s %s callout=%s postmaster=%s",
675 print_time(((dbdata_generic *)value)->time_stamp),
677 print_cache(callout->result),
678 print_cache(callout->postmaster_result));
679 if (callout->postmaster_result != ccache_unknown)
680 printf(" (%s)", print_time(callout->postmaster_stamp));
681 printf(" random=%s", print_cache(callout->random_result));
682 if (callout->random_result != ccache_unknown)
683 printf(" (%s)", print_time(callout->random_stamp));
690 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
692 ratelimit = (dbdata_ratelimit *)value;
693 rate_unique = (dbdata_ratelimit_unique *)value;
694 printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
695 print_time(ratelimit->time_stamp),
696 ratelimit->time_usec, ratelimit->rate,
697 print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
702 ratelimit = (dbdata_ratelimit *)value;
703 printf("%s.%06d rate: %10.3f key: %s\n",
704 print_time(ratelimit->time_stamp),
705 ratelimit->time_usec, ratelimit->rate,
711 session = (dbdata_tls_session *)value;
712 printf(" %s %.*s\n", keybuffer, length, session->session);
716 store_reset(reset_point);
723 #endif /* EXIM_DUMPDB */
729 /*************************************************
730 * The exim_fixdb main program *
731 *************************************************/
733 /* In order not to hold the database lock any longer than is necessary, each
734 operation on the database uses a separate open/close call. This is expensive,
735 but then using this utility is not expected to be very common. Its main use is
736 to provide a way of patching up hints databases in order to run tests.
741 This causes the data from the given record to be displayed, or "not found"
742 to be output. Note that in the retry database, destination names are
743 preceded by R: or T: for router or transport retry info.
746 This causes the given record to be deleted or "not found" to be output.
748 (3) <record name> <field number> <value>
749 This sets the given value into the given field, identified by a number
750 which is output by the display command. Not all types of record can
754 This exits from exim_fixdb.
756 If the record name is omitted from (2) or (3), the previously used record name
760 int main(int argc, char **cargv)
763 uschar **argv = USS cargv;
768 name[0] = 0; /* No name set */
770 /* Sort out the database type, verify what we are working on and then process
773 dbdata_type = check_args(argc, argv, US"fixdb", US"");
774 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
776 for(; (reset_point = store_mark()); store_reset(reset_point))
783 dbdata_callout_cache *callout;
784 dbdata_ratelimit *ratelimit;
785 dbdata_ratelimit_unique *rate_unique;
786 dbdata_tls_session *session;
789 uschar field[256], value[256];
792 if (Ufgets(buffer, 256, stdin) == NULL) break;
794 buffer[Ustrlen(buffer)-1] = 0;
795 field[0] = value[0] = 0;
797 /* If the buffer contains just one digit, or just consists of "d", use the
798 previous name for an update. */
800 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
801 || Ustrcmp(buffer, "d") == 0)
805 printf("No previous record name is set\n");
808 (void)sscanf(CS buffer, "%s %s", field, value);
813 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
816 /* Handle an update request */
821 spool_directory = argv[1];
823 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
826 if (Ustrcmp(field, "d") == 0)
828 if (value[0] != 0) printf("unexpected value after \"d\"\n");
829 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
830 "not found" : "deleted");
835 else if (isdigit((uschar)field[0]))
837 int fieldno = Uatoi(field);
840 printf("value missing\n");
846 record = dbfn_read_with_length(dbm, name, &oldlength);
847 if (record == NULL) printf("not found\n"); else
850 /*int length = 0; Stops compiler warning */
855 retry = (dbdata_retry *)record;
856 /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
860 case 0: retry->basic_errno = Uatoi(value);
862 case 1: retry->more_errno = Uatoi(value);
864 case 2: if ((tt = read_time(value)) > 0) retry->first_failed = tt;
865 else printf("bad time value\n");
867 case 3: if ((tt = read_time(value)) > 0) retry->last_try = tt;
868 else printf("bad time value\n");
870 case 4: if ((tt = read_time(value)) > 0) retry->next_try = tt;
871 else printf("bad time value\n");
873 case 5: if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
874 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
875 else printf("\"yes\" or \"no\" expected=n");
877 default: printf("unknown field number\n");
884 printf("Can't change contents of wait database record\n");
888 printf("Can't change contents of misc database record\n");
892 callout = (dbdata_callout_cache *)record;
893 /* length = sizeof(dbdata_callout_cache); */
896 case 0: callout->result = Uatoi(value);
898 case 1: callout->postmaster_result = Uatoi(value);
900 case 2: callout->random_result = Uatoi(value);
902 default: printf("unknown field number\n");
909 ratelimit = (dbdata_ratelimit *)record;
912 case 0: if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
913 else printf("bad time value\n");
915 case 1: ratelimit->time_usec = Uatoi(value);
917 case 2: ratelimit->rate = Ustrtod(value, NULL);
919 case 3: if (Ustrstr(name, "/unique/") != NULL
920 && oldlength >= sizeof(dbdata_ratelimit_unique))
922 rate_unique = (dbdata_ratelimit_unique *)record;
923 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
924 else printf("bad time value\n");
927 /* else fall through */
929 case 5: if (Ustrstr(name, "/unique/") != NULL
930 && oldlength >= sizeof(dbdata_ratelimit_unique))
938 md5_end(&md5info, value, Ustrlen(value), md5sum);
939 hash = md5sum[0] << 0 | md5sum[1] << 8
940 | md5sum[2] << 16 | md5sum[3] << 24;
941 hinc = md5sum[4] << 0 | md5sum[5] << 8
942 | md5sum[6] << 16 | md5sum[7] << 24;
943 rate_unique = (dbdata_ratelimit_unique *)record;
945 for (unsigned n = 0; n < 8; n++, hash += hinc)
947 int bit = 1 << (hash % 8);
948 int byte = (hash / 8) % rate_unique->bloom_size;
949 if ((rate_unique->bloom[byte] & bit) == 0)
952 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
956 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
959 /* else fall through */
960 default: printf("unknown field number\n");
967 printf("Can't change contents of tls database record\n");
971 dbfn_write(dbm, name, record, oldlength);
978 printf("field number or d expected\n");
983 if (!verify) continue;
986 /* The "name" q causes an exit */
988 else if (Ustrcmp(name, "q") == 0) return 0;
990 /* Handle a read request, or verify after an update. */
992 spool_directory = argv[1];
993 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
996 if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
998 printf("record %s not found\n", name);
1004 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
1008 retry = (dbdata_retry *)record;
1009 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
1010 printf("1 extra data: %d\n", retry->more_errno);
1011 printf("2 first failed: %s\n", print_time(retry->first_failed));
1012 printf("3 last try: %s\n", print_time(retry->last_try));
1013 printf("4 next try: %s\n", print_time(retry->next_try));
1014 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
1018 wait = (dbdata_wait *)record;
1020 printf("Sequence: %d\n", wait->sequence);
1021 if (wait->count > WAIT_NAME_MAX)
1023 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
1024 wait->count, WAIT_NAME_MAX);
1025 wait->count = WAIT_NAME_MAX;
1028 for (int i = 1; i <= wait->count; i++)
1030 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1031 value[MESSAGE_ID_LENGTH] = 0;
1032 if (count_bad && value[0] == 0) break;
1033 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1034 Ustrspn(value, "0123456789"
1035 "abcdefghijklmnopqrstuvwxyz"
1036 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1038 printf("\n**** Data corrupted: bad character in message id ****\n");
1039 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
1040 printf("%02x ", value[j]);
1044 printf("%s ", value);
1045 t += MESSAGE_ID_LENGTH;
1054 callout = (dbdata_callout_cache *)record;
1055 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1057 if (oldlength > sizeof(dbdata_callout_cache_address))
1059 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1060 callout->postmaster_result);
1061 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1062 callout->random_result);
1066 case type_ratelimit:
1067 ratelimit = (dbdata_ratelimit *)record;
1068 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1069 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1070 printf("2 sender rate: % .3f\n", ratelimit->rate);
1071 if (Ustrstr(name, "/unique/") != NULL
1072 && oldlength >= sizeof(dbdata_ratelimit_unique))
1074 rate_unique = (dbdata_ratelimit_unique *)record;
1075 printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1076 printf("4 test filter membership\n");
1077 printf("5 add element to filter\n");
1082 session = (dbdata_tls_session *)value;
1083 printf("0 time stamp: %s\n", print_time(session->time_stamp));
1084 printf("1 session: .%s\n", session->session);
1089 /* The database is closed after each request */
1098 #endif /* EXIM_FIXDB */
1103 /*************************************************
1104 * The exim_tidydb main program *
1105 *************************************************/
1108 /* Utility program to tidy the contents of an exim database file. There is one
1111 -t <time> expiry time for old records - default 30 days
1113 For backwards compatibility, an -f option is recognized and ignored. (It used
1114 to request a "full" tidy. This version always does the whole job.) */
1117 typedef struct key_item {
1118 struct key_item *next;
1123 int main(int argc, char **cargv)
1125 struct stat statbuf;
1126 int maxkeep = 30 * 24 * 60 * 60;
1127 int dbdata_type, i, oldest, path_len;
1128 key_item *keychain = NULL;
1132 EXIM_CURSOR *cursor;
1133 uschar **argv = USS cargv;
1137 /* Scan the options */
1139 for (i = 1; i < argc; i++)
1141 if (argv[i][0] != '-') break;
1142 if (Ustrcmp(argv[i], "-f") == 0) continue;
1143 if (Ustrcmp(argv[i], "-t") == 0)
1151 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1152 (void)sscanf(CS s, "%d%n", &value, &count);
1156 case 'w': value *= 7;
1157 case 'd': value *= 24;
1158 case 'h': value *= 60;
1159 case 'm': value *= 60;
1162 default: usage(US"tidydb", US" [-t <time>]");
1167 else usage(US"tidydb", US" [-t <time>]");
1170 /* Adjust argument values and process arguments */
1175 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1177 /* Compute the oldest keep time, verify what we are doing, and open the
1180 oldest = time(NULL) - maxkeep;
1181 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1183 spool_directory = argv[1];
1184 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
1187 /* Prepare for building file names */
1189 sprintf(CS buffer, "%s/input/", argv[1]);
1190 path_len = Ustrlen(buffer);
1193 /* It appears, by experiment, that it is a bad idea to make changes
1194 to the file while scanning it. Pity the man page doesn't warn you about that.
1195 Therefore, we scan and build a list of all the keys. Then we use that to
1196 read the records and possibly update them. */
1198 for (key = dbfn_scan(dbm, TRUE, &cursor);
1200 key = dbfn_scan(dbm, FALSE, &cursor))
1202 key_item *k = store_get(sizeof(key_item) + Ustrlen(key), is_tainted(key));
1205 Ustrcpy(k->key, key);
1208 /* Now scan the collected keys and operate on the records, resetting
1209 the store each time round. */
1211 for (; keychain && (reset_point = store_mark()); store_reset(reset_point))
1213 dbdata_generic *value;
1215 key = keychain->key;
1216 keychain = keychain->next;
1217 value = dbfn_read_with_length(dbm, key, NULL);
1219 /* A continuation record may have been deleted or renamed already, so
1220 non-existence is not serious. */
1222 if (value == NULL) continue;
1224 /* Delete if too old */
1226 if (value->time_stamp < oldest)
1228 printf("deleted %s (too old)\n", key);
1229 dbfn_delete(dbm, key);
1233 /* Do database-specific tidying for wait databases, and message-
1234 specific tidying for the retry database. */
1236 if (dbdata_type == type_wait)
1238 dbdata_wait *wait = (dbdata_wait *)value;
1239 BOOL update = FALSE;
1241 /* Leave corrupt records alone */
1243 if (wait->count > WAIT_NAME_MAX)
1245 printf("**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
1246 key, wait->count, wait->count, WAIT_NAME_MAX);
1250 /* Loop for renamed continuation records. For each message id,
1251 check to see if the message exists, and if not, remove its entry
1252 from the record. Because of the possibility of split input directories,
1253 we must look in both possible places for a -D file. */
1257 int length = wait->count * MESSAGE_ID_LENGTH;
1259 for (int offset = length - MESSAGE_ID_LENGTH;
1260 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1262 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1263 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1265 if (Ustat(buffer, &statbuf) != 0)
1267 buffer[path_len] = wait->text[offset+5];
1268 buffer[path_len+1] = '/';
1269 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1270 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1272 if (Ustat(buffer, &statbuf) != 0)
1274 int left = length - offset - MESSAGE_ID_LENGTH;
1275 if (left > 0) Ustrncpy(wait->text + offset,
1276 wait->text + offset + MESSAGE_ID_LENGTH, left);
1278 length -= MESSAGE_ID_LENGTH;
1284 /* If record is empty and the main record, either delete it or rename
1285 the next continuation, repeating if that is also empty. */
1287 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1289 while (wait->count == 0 && wait->sequence > 0)
1292 dbdata_generic *newvalue;
1293 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1294 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1295 if (newvalue != NULL)
1298 wait = (dbdata_wait *)newvalue;
1299 dbfn_delete(dbm, newkey);
1300 printf("renamed %s\n", newkey);
1303 else wait->sequence--;
1306 /* If we have ended up with an empty main record, delete it
1307 and break the loop. Otherwise the new record will be scanned. */
1309 if (wait->count == 0 && wait->sequence == 0)
1311 dbfn_delete(dbm, key);
1312 printf("deleted %s (empty)\n", key);
1318 /* If not an empty main record, break the loop */
1323 /* Re-write the record if required */
1327 printf("updated %s\n", key);
1328 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1329 wait->count * MESSAGE_ID_LENGTH);
1333 /* If a retry record's key ends with a message-id, check that that message
1334 still exists; if not, remove this record. */
1336 else if (dbdata_type == type_retry)
1339 int len = Ustrlen(key);
1341 if (len < MESSAGE_ID_LENGTH + 1) continue;
1342 id = key + len - MESSAGE_ID_LENGTH - 1;
1343 if (*id++ != ':') continue;
1345 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1346 if (i == 6 || i == 13)
1347 { if (id[i] != '-') break; }
1349 { if (!isalnum(id[i])) break; }
1350 if (i < MESSAGE_ID_LENGTH) continue;
1352 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1353 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1355 if (Ustat(buffer, &statbuf) != 0)
1357 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1358 if (Ustat(buffer, &statbuf) != 0)
1360 dbfn_delete(dbm, key);
1361 printf("deleted %s (no message)\n", key);
1368 printf("Tidying complete\n");
1372 #endif /* EXIM_TIDYDB */
1374 /* End of exim_dbutil.c */