1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) The Exim Maintainers 2020 - 2024 */
6 /* Copyright (c) University of Cambridge 1995 - 2018 */
7 /* See the file NOTICE for conditions of use and distribution. */
8 /* SPDX-License-Identifier: GPL-2.0-or-later */
11 /* This single source file is used to compile three utility programs for
12 maintaining Exim hints databases.
14 exim_dumpdb dumps out the contents
15 exim_fixdb patches the database (really for Exim maintenance/testing)
16 exim_tidydb removed obsolete data
18 In all cases, the first argument is the name of the spool directory. The second
19 argument is the name of the database file. The available names are:
21 callout: callout verification cache
22 misc: miscellaneous hints data
23 ratelimit: record for ACL "ratelimit" condition
24 retry: etry delivery information
25 seen: imestamp records for ACL "seen" condition
26 tls: TLS session resumption cache
27 wait-<t>: message waiting information; <t> is a transport name
29 There are a number of common subroutines, followed by three main programs,
30 whose inclusion is controlled by -D on the compilation command. */
36 /* Identifiers for the different database types. */
41 #define type_callout 4
42 #define type_ratelimit 5
47 /* This is used by our cut-down dbfn_open(). */
49 uschar *spool_directory;
55 /******************************************************************************/
56 /* dummies needed by Solaris build */
61 readconf_printtime(int t)
64 string_catn(gstring * g, const uschar * s, int count)
67 string_vformat_trc(gstring * g, const uschar * func, unsigned line,
68 unsigned size_limit, unsigned flags, const char *format, va_list ap)
71 string_sprintf_trc(const char * fmt, const uschar * func, unsigned line, ...)
74 string_format_trc(uschar * buf, int len, const uschar * func, unsigned line,
75 const char * fmt, ...)
78 struct global_flags f;
79 unsigned int log_selector[1];
81 BOOL split_spool_directory;
84 /******************************************************************************/
87 /*************************************************
89 *************************************************/
91 SIGNAL_BOOL sigalrm_seen;
94 sigalrm_handler(int sig)
101 /*************************************************
102 * Output usage message and exit *
103 *************************************************/
106 usage(uschar *name, uschar *options)
108 printf("Usage: exim_%s%s <spool-directory> <database-name>\n", name, options);
109 printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit | tls | seen\n");
115 /*************************************************
116 * Sort out the command arguments *
117 *************************************************/
119 /* This function checks that there are exactly 2 arguments, and checks the
120 second of them to be sure it is a known database name. */
123 check_args(int argc, uschar **argv, uschar *name, uschar *options)
125 uschar * aname = argv[optind + 1];
126 if (argc - optind == 2)
128 if (Ustrcmp(aname, "retry") == 0) return type_retry;
129 if (Ustrcmp(aname, "misc") == 0) return type_misc;
130 if (Ustrncmp(aname, "wait-", 5) == 0) return type_wait;
131 if (Ustrcmp(aname, "callout") == 0) return type_callout;
132 if (Ustrcmp(aname, "ratelimit") == 0) return type_ratelimit;
133 if (Ustrcmp(aname, "tls") == 0) return type_tls;
134 if (Ustrcmp(aname, "seen") == 0) return type_seen;
136 usage(name, options);
137 return -1; /* Never obeyed */
143 options(int argc, uschar * argv[], uschar * name, const uschar * opts)
148 while ((opt = getopt(argc, (char * const *)argv, CCS opts)) != -1)
151 case 'k': keyonly = TRUE; break;
152 case 'z': utc = TRUE; break;
153 default: usage(name, US" [-z] [-k]");
160 /*************************************************
161 * Handle attempts to write the log *
162 *************************************************/
164 /* The message gets written to stderr when log_write() is called from a
165 utility. The message always gets '\n' added on the end of it. These calls come
166 from modules such as store.c when things go drastically wrong (e.g. malloc()
167 failing). In normal use they won't get obeyed.
170 selector not relevant when running a utility
171 flags not relevant when running a utility
172 format a printf() format
173 ... arguments for format
179 log_write(unsigned int selector, int flags, const char *format, ...)
182 va_start(ap, format);
183 vfprintf(stderr, format, ap);
184 fprintf(stderr, "\n");
190 /*************************************************
191 * Format a time value for printing *
192 *************************************************/
194 static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss ")];
199 struct tm *tmstr = utc ? gmtime(&t) : localtime(&t);
200 Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
206 /*************************************************
207 * Format a cache value for printing *
208 *************************************************/
211 print_cache(int value)
213 return value == ccache_accept ? US"accept" :
214 value == ccache_reject ? US"reject" :
220 /*************************************************
222 *************************************************/
229 time_t now = time(NULL);
230 struct tm *tm = localtime(&now);
235 for (uschar * t = s + Ustrlen(s) - 1; t >= s; t--)
237 if (*t == ':') continue;
238 if (!isdigit((uschar)*t)) return -1;
243 if (!isdigit((uschar)*t)) return -1;
244 value = value + (*t - '0')*10;
249 case 0: tm->tm_min = value; break;
250 case 1: tm->tm_hour = value; break;
251 case 2: tm->tm_mday = value; break;
252 case 3: tm->tm_mon = value - 1; break;
253 case 4: tm->tm_year = (value < 90)? value + 100 : value; break;
260 #endif /* EXIM_FIXDB */
264 /*************************************************
265 * Open and lock a database file *
266 *************************************************/
268 /* This is a cut-down version from the function in dbfn.h that Exim itself
269 uses. We assume the database exists, and therefore give up if we cannot open
273 name The single-component name of one of Exim's database files.
274 flags O_RDONLY or O_RDWR
275 dbblock Points to an open_db block to be filled in.
279 Returns: NULL if the open failed, or the locking failed.
280 On success, dbblock is returned. This contains the dbm pointer and
281 the fd of the locked lock file.
285 dbfn_open(const uschar * name, int flags, open_db * dbblock,
286 BOOL lof, BOOL panic)
289 struct flock lock_data;
290 BOOL read_only = (flags & (O_WRONLY|O_RDWR)) == O_RDONLY;
291 uschar * dirname, * filename;
293 /* The first thing to do is to open a separate file on which to lock. This
294 ensures that Exim has exclusive use of the database before it even tries to
295 open it. If there is a database, there should be a lock file in existence;
296 if no lockfile we infer there is no database and error out. We open the
297 lockfile using the r/w mode requested for the DB, users lacking permission
298 for the DB access mode will error out here. */
300 if ( asprintf(CSS &dirname, "%s/db", spool_directory) < 0
301 || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
304 dbblock->lockfd = -1;
305 if (exim_lockfile_needed())
307 if ((dbblock->lockfd = Uopen(filename, flags, 0)) < 0)
309 printf("** Failed to open database lock file %s: %s\n", filename,
314 /* Now we must get a lock on the opened lock file; do this with a blocking
315 lock that times out. */
317 lock_data.l_type = read_only ? F_RDLCK : F_WRLCK;
318 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
320 sigalrm_seen = FALSE;
321 os_non_restarting_signal(SIGALRM, sigalrm_handler);
322 ALARM(EXIMDB_LOCK_TIMEOUT);
323 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
326 if (sigalrm_seen) errno = ETIMEDOUT;
329 printf("** Failed to get %s lock for %s: %s",
330 read_only ? "read" : "write",
332 errno == ETIMEDOUT ? "timed out" : strerror(errno));
333 (void)close(dbblock->lockfd);
337 /* At this point we have an opened and locked separate lock file, that is,
338 exclusive access to the database, so we can go ahead and open it. */
341 if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
343 if (flags & O_RDWR) flags |= O_CREAT;
345 if (!(dbblock->dbptr = exim_dbopen(filename, dirname, flags, 0)))
347 printf("** Failed to open DBM file %s for %s: %s%s\n", filename,
348 read_only ? "reading" : "writing", strerror(errno),
350 " (or Berkeley DB error while opening)"
355 if (dbblock->lockfd >= 0) (void)close(dbblock->lockfd);
365 /*************************************************
366 * Unlock and close a database file *
367 *************************************************/
369 /* Closing a file automatically unlocks it, so after closing the database, just
370 close the lock file if there was one.
372 Argument: a pointer to an open database block
377 dbfn_close(open_db * dbp)
379 exim_dbclose(dbp->dbptr);
380 if (dbp->lockfd >= 0) (void) close(dbp->lockfd);
386 /*************************************************
387 * Read from database file *
388 *************************************************/
390 /* Passing back the pointer unchanged is useless, because there is no guarantee
391 of alignment. Since all the records used by Exim need to be properly aligned to
392 pick out the timestamps, etc., do the copying centrally here.
395 dbblock a pointer to an open database block
396 key the key of the record to be read
397 length where to put the length (or NULL if length not wanted). Includes overhead.
399 Returns: a pointer to the retrieved record, or
400 NULL if the record is not found
404 dbfn_read_with_length(open_db * dbblock, const uschar * key, int * length)
407 EXIM_DATUM key_datum, result_datum;
408 int klen = Ustrlen(key) + 1;
409 uschar * key_copy = store_get(klen, key);
412 memcpy(key_copy, key, klen);
414 exim_datum_init(&key_datum); /* Some DBM libraries require the datum */
415 exim_datum_init(&result_datum); /* to be cleared before use. */
416 exim_datum_data_set(&key_datum, key_copy);
417 exim_datum_size_set(&key_datum, klen);
419 if (!exim_dbget(dbblock->dbptr, &key_datum, &result_datum)) return NULL;
421 /* Assume for now that anything stored could have been tainted. Properly
422 we should store the taint status along with the data. */
424 dlen = exim_datum_size_get(&result_datum);
425 yield = store_get(dlen, GET_TAINTED);
426 memcpy(yield, exim_datum_data_get(&result_datum), dlen);
427 if (length) *length = dlen;
429 exim_datum_free(&result_datum); /* Some DBM libs require freeing */
435 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
437 /*************************************************
438 * Write to database file *
439 *************************************************/
443 dbblock a pointer to an open database block
444 key the key of the record to be written
445 ptr a pointer to the record to be written
446 length the length of the record to be written
448 Returns: the yield of the underlying dbm or db "write" function. If this
449 is dbm, the value is zero for OK.
453 dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
455 EXIM_DATUM key_datum, value_datum;
456 dbdata_generic *gptr = (dbdata_generic *)ptr;
457 int klen = Ustrlen(key) + 1;
458 uschar * key_copy = store_get(klen, key);
460 memcpy(key_copy, key, klen);
461 gptr->time_stamp = time(NULL);
463 exim_datum_init(&key_datum); /* Some DBM libraries require the datum */
464 exim_datum_init(&value_datum); /* to be cleared before use. */
465 exim_datum_data_set(&key_datum, key_copy);
466 exim_datum_size_set(&key_datum, klen);
467 exim_datum_data_set(&value_datum, ptr);
468 exim_datum_size_set(&value_datum, length);
469 return exim_dbput(dbblock->dbptr, &key_datum, &value_datum);
474 /*************************************************
475 * Delete record from database file *
476 *************************************************/
480 dbblock a pointer to an open database block
481 key the key of the record to be deleted
483 Returns: the yield of the underlying dbm or db "delete" function.
487 dbfn_delete(open_db *dbblock, const uschar *key)
489 int klen = Ustrlen(key) + 1;
490 uschar * key_copy = store_get(klen, key);
491 EXIM_DATUM key_datum;
493 memcpy(key_copy, key, klen);
494 exim_datum_init(&key_datum); /* Some DBM libraries require clearing */
495 exim_datum_data_set(&key_datum, key_copy);
496 exim_datum_size_set(&key_datum, klen);
497 return exim_dbdel(dbblock->dbptr, &key_datum);
500 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
504 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
505 /*************************************************
506 * Scan the keys of a database file *
507 *************************************************/
511 dbblock a pointer to an open database block
512 start TRUE if starting a new scan
513 FALSE if continuing with the current scan
514 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
515 that use the notion of a cursor
517 Returns: the next *key* (nul-terminated) from the file, or
518 NULL if there are no more
522 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
524 EXIM_DATUM key_datum, value_datum;
527 /* Some dbm require an initialization */
529 if (start) *cursor = exim_dbcreate_cursor(dbblock->dbptr);
531 exim_datum_init(&key_datum); /* Some DBM libraries require the datum */
532 exim_datum_init(&value_datum); /* to be cleared before use. */
534 yield = exim_dbscan(dbblock->dbptr, &key_datum, &value_datum, start, *cursor)
535 ? US exim_datum_data_get(&key_datum) : NULL;
537 /* Some dbm require a termination */
539 if (!yield) exim_dbdelete_cursor(*cursor);
542 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
547 /*************************************************
548 * The exim_dumpdb main program *
549 *************************************************/
552 main(int argc, char **cargv)
559 uschar **argv = USS cargv;
560 uschar keybuffer[1024];
563 options(argc, argv, US"dumpdb", US"kz");
565 /* Check the arguments, and open the database */
567 dbdata_type = check_args(argc, argv, US"dumpdb", US" [-z] [-k]");
568 argc -= optind; argv += optind;
569 spool_directory = argv[0];
571 if (!(dbm = dbfn_open(argv[1], O_RDONLY, &dbblock, FALSE, TRUE)))
574 /* Scan the file, formatting the information for each entry. Note
575 that data is returned in a malloc'ed block, in order that it be
576 correctly aligned. */
578 for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
580 key = dbfn_scan(dbm, FALSE, &cursor))
584 dbdata_callout_cache *callout;
585 dbdata_ratelimit *ratelimit;
586 dbdata_ratelimit_unique *rate_unique;
587 dbdata_tls_session *session;
592 uschar name[MESSAGE_ID_LENGTH + 1];
594 rmark reset_point = store_mark();
596 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
597 which might change. */
599 if (Ustrlen(key) > sizeof(keybuffer) - 1)
601 printf("**** Overlong key encountered: %s\n", key);
604 Ustrcpy(keybuffer, key);
607 printf(" %s\n", keybuffer);
608 else if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
609 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
610 "was not found in the file - something is wrong!\n",
613 /* Note: don't use print_time more than once in one statement, since
614 it uses a single buffer. */
619 retry = (dbdata_retry *)value;
620 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
621 retry->more_errno, retry->text,
622 print_time(retry->first_failed));
623 printf("%s ", print_time(retry->last_try));
624 printf("%s %s\n", print_time(retry->next_try),
625 retry->expired ? "*" : "");
629 wait = (dbdata_wait *)value;
630 printf("%s ", keybuffer);
632 name[MESSAGE_ID_LENGTH] = 0;
634 /* Leave corrupt records alone */
635 if (wait->count > WAIT_NAME_MAX)
638 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
639 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
640 wait->count = WAIT_NAME_MAX;
641 yield = count_bad = 1;
643 for (int i = 1; i <= wait->count; i++)
645 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
646 if (count_bad && name[0] == 0) break;
647 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
648 Ustrspn(name, "0123456789"
649 "abcdefghijklmnopqrstuvwxyz"
650 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
653 "**** Data for %s corrupted: bad character in message id\n",
655 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
656 fprintf(stderr, "%02x ", name[j]);
657 fprintf(stderr, "\n");
662 t += MESSAGE_ID_LENGTH;
668 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
673 callout = (dbdata_callout_cache *)value;
675 /* New-style address record */
677 if (length == sizeof(dbdata_callout_cache_address))
679 printf("%s %s callout=%s\n",
680 print_time(((dbdata_generic *)value)->time_stamp),
682 print_cache(callout->result));
685 /* New-style domain record */
687 else if (length == sizeof(dbdata_callout_cache))
689 printf("%s %s callout=%s postmaster=%s",
690 print_time(((dbdata_generic *)value)->time_stamp),
692 print_cache(callout->result),
693 print_cache(callout->postmaster_result));
694 if (callout->postmaster_result != ccache_unknown)
695 printf(" (%s)", print_time(callout->postmaster_stamp));
696 printf(" random=%s", print_cache(callout->random_result));
697 if (callout->random_result != ccache_unknown)
698 printf(" (%s)", print_time(callout->random_stamp));
705 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
707 ratelimit = (dbdata_ratelimit *)value;
708 rate_unique = (dbdata_ratelimit_unique *)value;
709 printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
710 print_time(ratelimit->time_stamp),
711 ratelimit->time_usec, ratelimit->rate,
712 print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
717 ratelimit = (dbdata_ratelimit *)value;
718 printf("%s.%06d rate: %10.3f key: %s\n",
719 print_time(ratelimit->time_stamp),
720 ratelimit->time_usec, ratelimit->rate,
726 session = (dbdata_tls_session *)value;
727 printf(" %s %.*s\n", keybuffer, length, session->session);
731 seen = (dbdata_seen *)value;
732 printf("%s\t%s\n", keybuffer, print_time(seen->time_stamp));
735 store_reset(reset_point);
742 #endif /* EXIM_DUMPDB */
748 /*************************************************
749 * The exim_fixdb main program *
750 *************************************************/
752 /* In order not to hold the database lock any longer than is necessary, each
753 operation on the database uses a separate open/close call. This is expensive,
754 but then using this utility is not expected to be very common. Its main use is
755 to provide a way of patching up hints databases in order to run tests.
760 This causes the data from the given record to be displayed, or "not found"
761 to be output. Note that in the retry database, destination names are
762 preceded by R: or T: for router or transport retry info.
765 This causes the given record to be deleted or "not found" to be output.
767 (3) <record name> <field number> <value>
768 This sets the given value into the given field, identified by a number
769 which is output by the display command. Not all types of record can
773 This exits from exim_fixdb.
775 If the record name is omitted from (2) or (3), the previously used record name
780 main(int argc, char **cargv)
783 uschar **argv = USS cargv;
790 options(argc, argv, US"fixdb", US"z");
791 name[0] = 0; /* No name set */
793 /* Sort out the database type, verify what we are working on and then process
796 dbdata_type = check_args(argc, argv, US"fixdb", US" [-z]");
797 argc -= optind; argv += optind;
798 spool_directory = argv[0];
801 printf("Modifying Exim hints database %s/db/%s\n", spool_directory, aname);
803 for(; (reset_point = store_mark()); store_reset(reset_point))
810 dbdata_callout_cache *callout;
811 dbdata_ratelimit *ratelimit;
812 dbdata_ratelimit_unique *rate_unique;
813 dbdata_tls_session *session;
816 uschar field[256], value[256];
819 if (Ufgets(buffer, 256, stdin) == NULL) break;
821 buffer[Ustrlen(buffer)-1] = 0;
822 field[0] = value[0] = 0;
824 /* If the buffer contains just one digit, or just consists of "d", use the
825 previous name for an update. */
827 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
828 || Ustrcmp(buffer, "d") == 0)
832 printf("No previous record name is set\n");
835 (void)sscanf(CS buffer, "%s %s", field, value);
840 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
843 /* Handle an update request */
849 if (!(dbm = dbfn_open(aname, O_RDWR, &dbblock, FALSE, TRUE)))
852 if (Ustrcmp(field, "d") == 0)
854 if (value[0] != 0) printf("unexpected value after \"d\"\n");
855 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
856 "not found" : "deleted");
861 else if (isdigit((uschar)field[0]))
863 int fieldno = Uatoi(field);
866 printf("value missing\n");
872 record = dbfn_read_with_length(dbm, name, &oldlength);
873 if (record == NULL) printf("not found\n"); else
876 /*int length = 0; Stops compiler warning */
881 retry = (dbdata_retry *)record;
882 /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
886 case 0: retry->basic_errno = Uatoi(value);
888 case 1: retry->more_errno = Uatoi(value);
890 case 2: if ((tt = read_time(value)) > 0) retry->first_failed = tt;
891 else printf("bad time value\n");
893 case 3: if ((tt = read_time(value)) > 0) retry->last_try = tt;
894 else printf("bad time value\n");
896 case 4: if ((tt = read_time(value)) > 0) retry->next_try = tt;
897 else printf("bad time value\n");
899 case 5: if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
900 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
901 else printf("\"yes\" or \"no\" expected=n");
903 default: printf("unknown field number\n");
910 printf("Can't change contents of wait database record\n");
914 printf("Can't change contents of misc database record\n");
918 callout = (dbdata_callout_cache *)record;
919 /* length = sizeof(dbdata_callout_cache); */
922 case 0: callout->result = Uatoi(value);
924 case 1: callout->postmaster_result = Uatoi(value);
926 case 2: callout->random_result = Uatoi(value);
928 default: printf("unknown field number\n");
935 ratelimit = (dbdata_ratelimit *)record;
938 case 0: if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
939 else printf("bad time value\n");
941 case 1: ratelimit->time_usec = Uatoi(value);
943 case 2: ratelimit->rate = Ustrtod(value, NULL);
945 case 3: if (Ustrstr(name, "/unique/") != NULL
946 && oldlength >= sizeof(dbdata_ratelimit_unique))
948 rate_unique = (dbdata_ratelimit_unique *)record;
949 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
950 else printf("bad time value\n");
953 /* else fall through */
955 case 5: if (Ustrstr(name, "/unique/") != NULL
956 && oldlength >= sizeof(dbdata_ratelimit_unique))
964 md5_end(&md5info, value, Ustrlen(value), md5sum);
965 hash = md5sum[0] << 0 | md5sum[1] << 8
966 | md5sum[2] << 16 | md5sum[3] << 24;
967 hinc = md5sum[4] << 0 | md5sum[5] << 8
968 | md5sum[6] << 16 | md5sum[7] << 24;
969 rate_unique = (dbdata_ratelimit_unique *)record;
971 for (unsigned n = 0; n < 8; n++, hash += hinc)
973 int bit = 1 << (hash % 8);
974 int byte = (hash / 8) % rate_unique->bloom_size;
975 if ((rate_unique->bloom[byte] & bit) == 0)
978 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
982 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
985 /* else fall through */
986 default: printf("unknown field number\n");
993 printf("Can't change contents of tls database record\n");
997 dbfn_write(dbm, name, record, oldlength);
1004 printf("field number or d expected\n");
1009 if (!verify) continue;
1012 /* The "name" q causes an exit */
1014 else if (Ustrcmp(name, "q") == 0) return 0;
1016 /* Handle a read request, or verify after an update. */
1018 if (!(dbm = dbfn_open(aname, O_RDONLY, &dbblock, FALSE, TRUE)))
1021 if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
1023 printf("record %s not found\n", name);
1029 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
1033 retry = (dbdata_retry *)record;
1034 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
1035 printf("1 extra data: %d\n", retry->more_errno);
1036 printf("2 first failed: %s\n", print_time(retry->first_failed));
1037 printf("3 last try: %s\n", print_time(retry->last_try));
1038 printf("4 next try: %s\n", print_time(retry->next_try));
1039 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
1043 wait = (dbdata_wait *)record;
1045 printf("Sequence: %d\n", wait->sequence);
1046 if (wait->count > WAIT_NAME_MAX)
1048 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
1049 wait->count, WAIT_NAME_MAX);
1050 wait->count = WAIT_NAME_MAX;
1053 for (int i = 1; i <= wait->count; i++)
1055 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1056 value[MESSAGE_ID_LENGTH] = 0;
1057 if (count_bad && value[0] == 0) break;
1058 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1059 Ustrspn(value, "0123456789"
1060 "abcdefghijklmnopqrstuvwxyz"
1061 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1063 printf("\n**** Data corrupted: bad character in message id ****\n");
1064 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
1065 printf("%02x ", value[j]);
1069 printf("%s ", value);
1070 t += MESSAGE_ID_LENGTH;
1079 callout = (dbdata_callout_cache *)record;
1080 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1082 if (oldlength > sizeof(dbdata_callout_cache_address))
1084 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1085 callout->postmaster_result);
1086 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1087 callout->random_result);
1091 case type_ratelimit:
1092 ratelimit = (dbdata_ratelimit *)record;
1093 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1094 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1095 printf("2 sender rate: % .3f\n", ratelimit->rate);
1096 if (Ustrstr(name, "/unique/") != NULL
1097 && oldlength >= sizeof(dbdata_ratelimit_unique))
1099 rate_unique = (dbdata_ratelimit_unique *)record;
1100 printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1101 printf("4 test filter membership\n");
1102 printf("5 add element to filter\n");
1107 session = (dbdata_tls_session *)value;
1108 printf("0 time stamp: %s\n", print_time(session->time_stamp));
1109 printf("1 session: .%s\n", session->session);
1114 /* The database is closed after each request */
1123 #endif /* EXIM_FIXDB */
1128 /*************************************************
1129 * The exim_tidydb main program *
1130 *************************************************/
1133 /* Utility program to tidy the contents of an exim database file. There is one
1136 -t <time> expiry time for old records - default 30 days
1138 For backwards compatibility, an -f option is recognized and ignored. (It used
1139 to request a "full" tidy. This version always does the whole job.) */
1142 typedef struct key_item {
1143 struct key_item *next;
1149 main(int argc, char **cargv)
1151 struct stat statbuf;
1152 int maxkeep = 30 * 24 * 60 * 60;
1153 int dbdata_type, i, oldest, path_len;
1154 key_item *keychain = NULL;
1158 EXIM_CURSOR *cursor;
1159 uschar **argv = USS cargv;
1165 /* Scan the options */
1167 for (i = 1; i < argc; i++)
1169 if (argv[i][0] != '-') break;
1170 if (Ustrcmp(argv[i], "-f") == 0) continue;
1171 if (Ustrcmp(argv[i], "-t") == 0)
1179 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1180 (void)sscanf(CS s, "%d%n", &value, &count);
1184 case 'w': value *= 7;
1185 case 'd': value *= 24;
1186 case 'h': value *= 60;
1187 case 'm': value *= 60;
1190 default: usage(US"tidydb", US" [-t <time>]");
1195 else usage(US"tidydb", US" [-t <time>]");
1198 /* Adjust argument values and process arguments */
1203 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1205 /* Compute the oldest keep time, verify what we are doing, and open the
1208 oldest = time(NULL) - maxkeep;
1209 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1211 spool_directory = argv[1];
1212 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
1215 /* Prepare for building file names */
1217 sprintf(CS buffer, "%s/input/", argv[1]);
1218 path_len = Ustrlen(buffer);
1221 /* It appears, by experiment, that it is a bad idea to make changes
1222 to the file while scanning it. Pity the man page doesn't warn you about that.
1223 Therefore, we scan and build a list of all the keys. Then we use that to
1224 read the records and possibly update them. */
1226 for (key = dbfn_scan(dbm, TRUE, &cursor);
1228 key = dbfn_scan(dbm, FALSE, &cursor))
1230 key_item * k = store_get(sizeof(key_item) + Ustrlen(key), key);
1233 Ustrcpy(k->key, key);
1236 /* Now scan the collected keys and operate on the records, resetting
1237 the store each time round. */
1239 for (; keychain && (reset_point = store_mark()); store_reset(reset_point))
1241 dbdata_generic *value;
1243 key = keychain->key;
1244 keychain = keychain->next;
1245 value = dbfn_read_with_length(dbm, key, NULL);
1247 /* A continuation record may have been deleted or renamed already, so
1248 non-existence is not serious. */
1250 if (!value) continue;
1252 /* Delete if too old */
1254 if (value->time_stamp < oldest)
1256 printf("deleted %s (too old)\n", key);
1257 dbfn_delete(dbm, key);
1261 /* Do database-specific tidying for wait databases, and message-
1262 specific tidying for the retry database. */
1264 if (dbdata_type == type_wait)
1266 dbdata_wait *wait = (dbdata_wait *)value;
1267 BOOL update = FALSE;
1269 /* Leave corrupt records alone */
1271 if (wait->time_stamp > time(NULL))
1273 printf("**** Data for '%s' corrupted\n time in future: %s\n",
1274 key, print_time(((dbdata_generic *)value)->time_stamp));
1277 if (wait->count > WAIT_NAME_MAX)
1279 printf("**** Data for '%s' corrupted\n count=%d=0x%x max=%d\n",
1280 key, wait->count, wait->count, WAIT_NAME_MAX);
1283 if (wait->sequence > WAIT_CONT_MAX)
1285 printf("**** Data for '%s' corrupted\n sequence=%d=0x%x max=%d\n",
1286 key, wait->sequence, wait->sequence, WAIT_CONT_MAX);
1290 /* Record over 1 year old; just remove it */
1292 if (wait->time_stamp < time(NULL) - 365*24*60*60)
1294 dbfn_delete(dbm, key);
1295 printf("deleted %s (too old)\n", key);
1299 /* Loop for renamed continuation records. For each message id,
1300 check to see if the message exists, and if not, remove its entry
1301 from the record. Because of the possibility of split input directories,
1302 we must look in both possible places for a -D file. */
1306 int length = wait->count * MESSAGE_ID_LENGTH;
1308 for (int offset = length - MESSAGE_ID_LENGTH;
1309 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1311 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1312 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1314 if (Ustat(buffer, &statbuf) != 0)
1316 buffer[path_len] = wait->text[offset+5];
1317 buffer[path_len+1] = '/';
1318 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1319 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1321 if (Ustat(buffer, &statbuf) != 0)
1323 int left = length - offset - MESSAGE_ID_LENGTH;
1324 if (left > 0) Ustrncpy(wait->text + offset,
1325 wait->text + offset + MESSAGE_ID_LENGTH, left);
1327 length -= MESSAGE_ID_LENGTH;
1333 /* If record is empty and the main record, either delete it or rename
1334 the next continuation, repeating if that is also empty. */
1336 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1338 while (wait->count == 0 && wait->sequence > 0)
1341 dbdata_generic *newvalue;
1342 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1343 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1344 if (newvalue != NULL)
1347 wait = (dbdata_wait *)newvalue;
1348 dbfn_delete(dbm, newkey);
1349 printf("renamed %s\n", newkey);
1352 else wait->sequence--;
1355 /* If we have ended up with an empty main record, delete it
1356 and break the loop. Otherwise the new record will be scanned. */
1358 if (wait->count == 0 && wait->sequence == 0)
1360 dbfn_delete(dbm, key);
1361 printf("deleted %s (empty)\n", key);
1367 /* If not an empty main record, break the loop */
1372 /* Re-write the record if required */
1376 printf("updated %s\n", key);
1377 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1378 wait->count * MESSAGE_ID_LENGTH);
1382 /* If a retry record's key ends with a message-id, check that that message
1383 still exists; if not, remove this record. */
1385 else if (dbdata_type == type_retry)
1388 int len = Ustrlen(key);
1390 if (len < MESSAGE_ID_LENGTH + 1) continue;
1391 id = key + len - MESSAGE_ID_LENGTH - 1;
1392 if (*id++ != ':') continue;
1394 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1395 if (i == 6 || i == 13)
1396 { if (id[i] != '-') break; }
1398 { if (!isalnum(id[i])) break; }
1399 if (i < MESSAGE_ID_LENGTH) continue;
1401 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1402 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1404 if (Ustat(buffer, &statbuf) != 0)
1406 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1407 if (Ustat(buffer, &statbuf) != 0)
1409 dbfn_delete(dbm, key);
1410 printf("deleted %s (no message)\n", key);
1417 printf("Tidying complete\n");
1421 #endif /* EXIM_TIDYDB */
1423 /* End of exim_dbutil.c */