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
24 There are a number of common subroutines, followed by three main programs,
25 whose inclusion is controlled by -D on the compilation command. */
31 /* Identifiers for the different database types. */
36 #define type_callout 4
37 #define type_ratelimit 5
40 /* This is used by our cut-down dbfn_open(). */
42 uschar *spool_directory;
46 /*************************************************
47 * Berkeley DB error callback *
48 *************************************************/
50 /* For Berkeley DB >= 2, we can define a function to be called in case of DB
51 errors. This should help with debugging strange DB problems, e.g. getting "File
52 exists" when you try to open a db file. The API changed at release 4.3. */
54 #if defined(USE_DB) && defined(DB_VERSION_STRING)
56 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
57 dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
61 dbfn_bdb_error_callback(const char *pfx, char *msg)
65 printf("Berkeley DB error: %s\n", msg);
71 /*************************************************
73 *************************************************/
75 SIGNAL_BOOL sigalrm_seen;
78 sigalrm_handler(int sig)
80 sig = sig; /* Keep picky compilers happy */
86 /*************************************************
87 * Output usage message and exit *
88 *************************************************/
91 usage(uschar *name, uschar *options)
93 printf("Usage: exim_%s%s <spool-directory> <database-name>\n", name, options);
94 printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit\n");
100 /*************************************************
101 * Sort out the command arguments *
102 *************************************************/
104 /* This function checks that there are exactly 2 arguments, and checks the
105 second of them to be sure it is a known database name. */
108 check_args(int argc, uschar **argv, uschar *name, uschar *options)
112 if (Ustrcmp(argv[2], "retry") == 0) return type_retry;
113 if (Ustrcmp(argv[2], "misc") == 0) return type_misc;
114 if (Ustrncmp(argv[2], "wait-", 5) == 0) return type_wait;
115 if (Ustrcmp(argv[2], "callout") == 0) return type_callout;
116 if (Ustrcmp(argv[2], "ratelimit") == 0) return type_ratelimit;
118 usage(name, options);
119 return -1; /* Never obeyed */
124 /*************************************************
125 * Handle attempts to write the log *
126 *************************************************/
128 /* The message gets written to stderr when log_write() is called from a
129 utility. The message always gets '\n' added on the end of it. These calls come
130 from modules such as store.c when things go drastically wrong (e.g. malloc()
131 failing). In normal use they won't get obeyed.
134 selector not relevant when running a utility
135 flags not relevant when running a utility
136 format a printf() format
137 ... arguments for format
143 log_write(unsigned int selector, int flags, const char *format, ...)
146 va_start(ap, format);
147 vfprintf(stderr, format, ap);
148 fprintf(stderr, "\n");
150 selector = selector; /* Keep picky compilers happy */
156 /*************************************************
157 * Format a time value for printing *
158 *************************************************/
160 static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss ")];
165 struct tm *tmstr = localtime(&t);
166 Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
172 /*************************************************
173 * Format a cache value for printing *
174 *************************************************/
177 print_cache(int value)
179 return (value == ccache_accept)? US"accept" :
180 (value == ccache_reject)? US"reject" :
186 /*************************************************
188 *************************************************/
195 time_t now = time(NULL);
196 struct tm *tm = localtime(&now);
201 for (uschar * t = s + Ustrlen(s) - 1; t >= s; t--)
203 if (*t == ':') continue;
204 if (!isdigit((uschar)*t)) return -1;
209 if (!isdigit((uschar)*t)) return -1;
210 value = value + (*t - '0')*10;
215 case 0: tm->tm_min = value; break;
216 case 1: tm->tm_hour = value; break;
217 case 2: tm->tm_mday = value; break;
218 case 3: tm->tm_mon = value - 1; break;
219 case 4: tm->tm_year = (value < 90)? value + 100 : value; break;
226 #endif /* EXIM_FIXDB */
230 /*************************************************
231 * Open and lock a database file *
232 *************************************************/
234 /* This is a cut-down version from the function in dbfn.h that Exim itself
235 uses. We assume the database exists, and therefore give up if we cannot open
239 name The single-component name of one of Exim's database files.
240 flags O_RDONLY or O_RDWR
241 dbblock Points to an open_db block to be filled in.
244 Returns: NULL if the open failed, or the locking failed.
245 On success, dbblock is returned. This contains the dbm pointer and
246 the fd of the locked lock file.
250 dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof)
253 struct flock lock_data;
254 BOOL read_only = flags == O_RDONLY;
255 uschar * dirname, * filename;
257 /* The first thing to do is to open a separate file on which to lock. This
258 ensures that Exim has exclusive use of the database before it even tries to
259 open it. If there is a database, there should be a lock file in existence. */
261 #ifdef COMPILE_UTILITY
262 if ( asprintf(CSS &dirname, "%s/db", spool_directory) < 0
263 || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
266 dirname = string_sprintf("%s/db", spool_directory);
267 filename = string_sprintf("%s/%s.lockfile", dirname, name);
270 dbblock->lockfd = Uopen(filename, flags, 0);
271 if (dbblock->lockfd < 0)
273 printf("** Failed to open database lock file %s: %s\n", filename,
278 /* Now we must get a lock on the opened lock file; do this with a blocking
279 lock that times out. */
281 lock_data.l_type = read_only ? F_RDLCK : F_WRLCK;
282 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
284 sigalrm_seen = FALSE;
285 os_non_restarting_signal(SIGALRM, sigalrm_handler);
286 ALARM(EXIMDB_LOCK_TIMEOUT);
287 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
290 if (sigalrm_seen) errno = ETIMEDOUT;
293 printf("** Failed to get %s lock for %s: %s",
294 flags & O_WRONLY ? "write" : "read",
296 errno == ETIMEDOUT ? "timed out" : strerror(errno));
297 (void)close(dbblock->lockfd);
301 /* At this point we have an opened and locked separate lock file, that is,
302 exclusive access to the database, so we can go ahead and open it. */
304 #ifdef COMPILE_UTILITY
305 if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
307 filename = string_sprintf("%s/%s", dirname, name);
309 EXIM_DBOPEN(filename, dirname, flags, 0, &(dbblock->dbptr));
313 printf("** Failed to open DBM file %s for %s:\n %s%s\n", filename,
314 read_only? "reading" : "writing", strerror(errno),
316 " (or Berkeley DB error while opening)"
321 (void)close(dbblock->lockfd);
331 /*************************************************
332 * Unlock and close a database file *
333 *************************************************/
335 /* Closing a file automatically unlocks it, so after closing the database, just
338 Argument: a pointer to an open database block
343 dbfn_close(open_db *dbblock)
345 EXIM_DBCLOSE(dbblock->dbptr);
346 (void)close(dbblock->lockfd);
352 /*************************************************
353 * Read from database file *
354 *************************************************/
356 /* Passing back the pointer unchanged is useless, because there is no guarantee
357 of alignment. Since all the records used by Exim need to be properly aligned to
358 pick out the timestamps, etc., do the copying centrally here.
361 dbblock a pointer to an open database block
362 key the key of the record to be read
363 length where to put the length (or NULL if length not wanted)
365 Returns: a pointer to the retrieved record, or
366 NULL if the record is not found
370 dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
373 EXIM_DATUM key_datum, result_datum;
374 int klen = Ustrlen(key) + 1;
375 uschar * key_copy = store_get(klen);
377 memcpy(key_copy, key, klen);
379 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
380 EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */
381 EXIM_DATUM_DATA(key_datum) = CS key_copy;
382 EXIM_DATUM_SIZE(key_datum) = klen;
384 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
386 yield = store_get(EXIM_DATUM_SIZE(result_datum));
387 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
388 if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
390 EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */
396 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
398 /*************************************************
399 * Write to database file *
400 *************************************************/
404 dbblock a pointer to an open database block
405 key the key of the record to be written
406 ptr a pointer to the record to be written
407 length the length of the record to be written
409 Returns: the yield of the underlying dbm or db "write" function. If this
410 is dbm, the value is zero for OK.
414 dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
416 EXIM_DATUM key_datum, value_datum;
417 dbdata_generic *gptr = (dbdata_generic *)ptr;
418 int klen = Ustrlen(key) + 1;
419 uschar * key_copy = store_get(klen);
421 memcpy(key_copy, key, klen);
422 gptr->time_stamp = time(NULL);
424 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
425 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
426 EXIM_DATUM_DATA(key_datum) = CS key_copy;
427 EXIM_DATUM_SIZE(key_datum) = klen;
428 EXIM_DATUM_DATA(value_datum) = CS ptr;
429 EXIM_DATUM_SIZE(value_datum) = length;
430 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
435 /*************************************************
436 * Delete record from database file *
437 *************************************************/
441 dbblock a pointer to an open database block
442 key the key of the record to be deleted
444 Returns: the yield of the underlying dbm or db "delete" function.
448 dbfn_delete(open_db *dbblock, const uschar *key)
450 int klen = Ustrlen(key) + 1;
451 uschar * key_copy = store_get(klen);
453 memcpy(key_copy, key, klen);
454 EXIM_DATUM key_datum;
455 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
456 EXIM_DATUM_DATA(key_datum) = CS key_copy;
457 EXIM_DATUM_SIZE(key_datum) = klen;
458 return EXIM_DBDEL(dbblock->dbptr, key_datum);
461 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
465 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
466 /*************************************************
467 * Scan the keys of a database file *
468 *************************************************/
472 dbblock a pointer to an open database block
473 start TRUE if starting a new scan
474 FALSE if continuing with the current scan
475 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
476 that use the notion of a cursor
478 Returns: the next record from the file, or
479 NULL if there are no more
483 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
485 EXIM_DATUM key_datum, value_datum;
487 value_datum = value_datum; /* dummy; not all db libraries use this */
489 /* Some dbm require an initialization */
491 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
493 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
494 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
496 yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
497 US EXIM_DATUM_DATA(key_datum) : NULL;
499 /* Some dbm require a termination */
501 if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
504 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
509 /*************************************************
510 * The exim_dumpdb main program *
511 *************************************************/
514 main(int argc, char **cargv)
521 uschar **argv = USS cargv;
522 uschar keybuffer[1024];
524 /* Check the arguments, and open the database */
526 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
527 spool_directory = argv[1];
528 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE)))
531 /* Scan the file, formatting the information for each entry. Note
532 that data is returned in a malloc'ed block, in order that it be
533 correctly aligned. */
535 for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
537 key = dbfn_scan(dbm, FALSE, &cursor))
541 dbdata_callout_cache *callout;
542 dbdata_ratelimit *ratelimit;
543 dbdata_ratelimit_unique *rate_unique;
547 uschar name[MESSAGE_ID_LENGTH + 1];
550 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
551 which might change. */
553 if (Ustrlen(key) > sizeof(keybuffer) - 1)
555 printf("**** Overlong key encountered: %s\n", key);
558 Ustrcpy(keybuffer, key);
560 if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
561 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
562 "was not found in the file - something is wrong!\n",
566 /* Note: don't use print_time more than once in one statement, since
567 it uses a single buffer. */
572 retry = (dbdata_retry *)value;
573 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
574 retry->more_errno, retry->text,
575 print_time(retry->first_failed));
576 printf("%s ", print_time(retry->last_try));
577 printf("%s %s\n", print_time(retry->next_try),
578 (retry->expired)? "*" : "");
582 wait = (dbdata_wait *)value;
583 printf("%s ", keybuffer);
585 name[MESSAGE_ID_LENGTH] = 0;
587 if (wait->count > WAIT_NAME_MAX)
590 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
591 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
592 wait->count = WAIT_NAME_MAX;
593 yield = count_bad = 1;
595 for (int i = 1; i <= wait->count; i++)
597 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
598 if (count_bad && name[0] == 0) break;
599 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
600 Ustrspn(name, "0123456789"
601 "abcdefghijklmnopqrstuvwxyz"
602 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
605 "**** Data for %s corrupted: bad character in message id\n",
607 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
608 fprintf(stderr, "%02x ", name[j]);
609 fprintf(stderr, "\n");
614 t += MESSAGE_ID_LENGTH;
620 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
625 callout = (dbdata_callout_cache *)value;
627 /* New-style address record */
629 if (length == sizeof(dbdata_callout_cache_address))
631 printf("%s %s callout=%s\n",
632 print_time(((dbdata_generic *)value)->time_stamp),
634 print_cache(callout->result));
637 /* New-style domain record */
639 else if (length == sizeof(dbdata_callout_cache))
641 printf("%s %s callout=%s postmaster=%s",
642 print_time(((dbdata_generic *)value)->time_stamp),
644 print_cache(callout->result),
645 print_cache(callout->postmaster_result));
646 if (callout->postmaster_result != ccache_unknown)
647 printf(" (%s)", print_time(callout->postmaster_stamp));
648 printf(" random=%s", print_cache(callout->random_result));
649 if (callout->random_result != ccache_unknown)
650 printf(" (%s)", print_time(callout->random_stamp));
657 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
659 ratelimit = (dbdata_ratelimit *)value;
660 rate_unique = (dbdata_ratelimit_unique *)value;
661 printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
662 print_time(ratelimit->time_stamp),
663 ratelimit->time_usec, ratelimit->rate,
664 print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
669 ratelimit = (dbdata_ratelimit *)value;
670 printf("%s.%06d rate: %10.3f key: %s\n",
671 print_time(ratelimit->time_stamp),
672 ratelimit->time_usec, ratelimit->rate,
685 #endif /* EXIM_DUMPDB */
691 /*************************************************
692 * The exim_fixdb main program *
693 *************************************************/
695 /* In order not to hold the database lock any longer than is necessary, each
696 operation on the database uses a separate open/close call. This is expensive,
697 but then using this utility is not expected to be very common. Its main use is
698 to provide a way of patching up hints databases in order to run tests.
703 This causes the data from the given record to be displayed, or "not found"
704 to be output. Note that in the retry database, destination names are
705 preceded by R: or T: for router or transport retry info.
708 This causes the given record to be deleted or "not found" to be output.
710 (3) <record name> <field number> <value>
711 This sets the given value into the given field, identified by a number
712 which is output by the display command. Not all types of record can
716 This exits from exim_fixdb.
718 If the record name is omitted from (2) or (3), the previously used record name
722 int main(int argc, char **cargv)
725 uschar **argv = USS cargv;
728 void *reset_point = store_get(0);
730 name[0] = 0; /* No name set */
732 /* Sort out the database type, verify what we are working on and then process
735 dbdata_type = check_args(argc, argv, US"fixdb", US"");
736 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
745 dbdata_callout_cache *callout;
746 dbdata_ratelimit *ratelimit;
747 dbdata_ratelimit_unique *rate_unique;
750 uschar field[256], value[256];
752 store_reset(reset_point);
755 if (Ufgets(buffer, 256, stdin) == NULL) break;
757 buffer[Ustrlen(buffer)-1] = 0;
758 field[0] = value[0] = 0;
760 /* If the buffer contains just one digit, or just consists of "d", use the
761 previous name for an update. */
763 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
764 || Ustrcmp(buffer, "d") == 0)
768 printf("No previous record name is set\n");
771 (void)sscanf(CS buffer, "%s %s", field, value);
776 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
779 /* Handle an update request */
784 spool_directory = argv[1];
786 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE)))
789 if (Ustrcmp(field, "d") == 0)
791 if (value[0] != 0) printf("unexpected value after \"d\"\n");
792 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
793 "not found" : "deleted");
798 else if (isdigit((uschar)field[0]))
800 int fieldno = Uatoi(field);
803 printf("value missing\n");
809 record = dbfn_read_with_length(dbm, name, &oldlength);
810 if (record == NULL) printf("not found\n"); else
813 /*int length = 0; Stops compiler warning */
818 retry = (dbdata_retry *)record;
819 /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
823 case 0: retry->basic_errno = Uatoi(value);
825 case 1: retry->more_errno = Uatoi(value);
827 case 2: if ((tt = read_time(value)) > 0) retry->first_failed = tt;
828 else printf("bad time value\n");
830 case 3: if ((tt = read_time(value)) > 0) retry->last_try = tt;
831 else printf("bad time value\n");
833 case 4: if ((tt = read_time(value)) > 0) retry->next_try = tt;
834 else printf("bad time value\n");
836 case 5: if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
837 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
838 else printf("\"yes\" or \"no\" expected=n");
840 default: printf("unknown field number\n");
847 printf("Can't change contents of wait database record\n");
851 printf("Can't change contents of misc database record\n");
855 callout = (dbdata_callout_cache *)record;
856 /* length = sizeof(dbdata_callout_cache); */
859 case 0: callout->result = Uatoi(value);
861 case 1: callout->postmaster_result = Uatoi(value);
863 case 2: callout->random_result = Uatoi(value);
865 default: printf("unknown field number\n");
872 ratelimit = (dbdata_ratelimit *)record;
875 case 0: if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
876 else printf("bad time value\n");
878 case 1: ratelimit->time_usec = Uatoi(value);
880 case 2: ratelimit->rate = Ustrtod(value, NULL);
882 case 3: if (Ustrstr(name, "/unique/") != NULL
883 && oldlength >= sizeof(dbdata_ratelimit_unique))
885 rate_unique = (dbdata_ratelimit_unique *)record;
886 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
887 else printf("bad time value\n");
890 /* else fall through */
892 case 5: if (Ustrstr(name, "/unique/") != NULL
893 && oldlength >= sizeof(dbdata_ratelimit_unique))
901 md5_end(&md5info, value, Ustrlen(value), md5sum);
902 hash = md5sum[0] << 0 | md5sum[1] << 8
903 | md5sum[2] << 16 | md5sum[3] << 24;
904 hinc = md5sum[4] << 0 | md5sum[5] << 8
905 | md5sum[6] << 16 | md5sum[7] << 24;
906 rate_unique = (dbdata_ratelimit_unique *)record;
908 for (unsigned n = 0; n < 8; n++, hash += hinc)
910 int bit = 1 << (hash % 8);
911 int byte = (hash / 8) % rate_unique->bloom_size;
912 if ((rate_unique->bloom[byte] & bit) == 0)
915 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
919 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
922 /* else fall through */
923 default: printf("unknown field number\n");
930 dbfn_write(dbm, name, record, oldlength);
937 printf("field number or d expected\n");
942 if (!verify) continue;
945 /* The "name" q causes an exit */
947 else if (Ustrcmp(name, "q") == 0) return 0;
949 /* Handle a read request, or verify after an update. */
951 spool_directory = argv[1];
952 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE)))
955 if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
957 printf("record %s not found\n", name);
963 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
967 retry = (dbdata_retry *)record;
968 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
969 printf("1 extra data: %d\n", retry->more_errno);
970 printf("2 first failed: %s\n", print_time(retry->first_failed));
971 printf("3 last try: %s\n", print_time(retry->last_try));
972 printf("4 next try: %s\n", print_time(retry->next_try));
973 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
977 wait = (dbdata_wait *)record;
979 printf("Sequence: %d\n", wait->sequence);
980 if (wait->count > WAIT_NAME_MAX)
982 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
983 wait->count, WAIT_NAME_MAX);
984 wait->count = WAIT_NAME_MAX;
987 for (int i = 1; i <= wait->count; i++)
989 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
990 value[MESSAGE_ID_LENGTH] = 0;
991 if (count_bad && value[0] == 0) break;
992 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
993 Ustrspn(value, "0123456789"
994 "abcdefghijklmnopqrstuvwxyz"
995 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
997 printf("\n**** Data corrupted: bad character in message id ****\n");
998 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
999 printf("%02x ", value[j]);
1003 printf("%s ", value);
1004 t += MESSAGE_ID_LENGTH;
1013 callout = (dbdata_callout_cache *)record;
1014 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1016 if (oldlength > sizeof(dbdata_callout_cache_address))
1018 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1019 callout->postmaster_result);
1020 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1021 callout->random_result);
1025 case type_ratelimit:
1026 ratelimit = (dbdata_ratelimit *)record;
1027 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1028 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1029 printf("2 sender rate: % .3f\n", ratelimit->rate);
1030 if (Ustrstr(name, "/unique/") != NULL
1031 && oldlength >= sizeof(dbdata_ratelimit_unique))
1033 rate_unique = (dbdata_ratelimit_unique *)record;
1034 printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1035 printf("4 test filter membership\n");
1036 printf("5 add element to filter\n");
1042 /* The database is closed after each request */
1051 #endif /* EXIM_FIXDB */
1056 /*************************************************
1057 * The exim_tidydb main program *
1058 *************************************************/
1061 /* Utility program to tidy the contents of an exim database file. There is one
1064 -t <time> expiry time for old records - default 30 days
1066 For backwards compatibility, an -f option is recognized and ignored. (It used
1067 to request a "full" tidy. This version always does the whole job.) */
1070 typedef struct key_item {
1071 struct key_item *next;
1076 int main(int argc, char **cargv)
1078 struct stat statbuf;
1079 int maxkeep = 30 * 24 * 60 * 60;
1080 int dbdata_type, i, oldest, path_len;
1081 key_item *keychain = NULL;
1085 EXIM_CURSOR *cursor;
1086 uschar **argv = USS cargv;
1090 /* Scan the options */
1092 for (i = 1; i < argc; i++)
1094 if (argv[i][0] != '-') break;
1095 if (Ustrcmp(argv[i], "-f") == 0) continue;
1096 if (Ustrcmp(argv[i], "-t") == 0)
1104 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1105 (void)sscanf(CS s, "%d%n", &value, &count);
1109 case 'w': value *= 7;
1110 case 'd': value *= 24;
1111 case 'h': value *= 60;
1112 case 'm': value *= 60;
1115 default: usage(US"tidydb", US" [-t <time>]");
1120 else usage(US"tidydb", US" [-t <time>]");
1123 /* Adjust argument values and process arguments */
1128 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1130 /* Compute the oldest keep time, verify what we are doing, and open the
1133 oldest = time(NULL) - maxkeep;
1134 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1136 spool_directory = argv[1];
1137 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE)))
1140 /* Prepare for building file names */
1142 sprintf(CS buffer, "%s/input/", argv[1]);
1143 path_len = Ustrlen(buffer);
1146 /* It appears, by experiment, that it is a bad idea to make changes
1147 to the file while scanning it. Pity the man page doesn't warn you about that.
1148 Therefore, we scan and build a list of all the keys. Then we use that to
1149 read the records and possibly update them. */
1151 for (key = dbfn_scan(dbm, TRUE, &cursor);
1153 key = dbfn_scan(dbm, FALSE, &cursor))
1155 key_item *k = store_get(sizeof(key_item) + Ustrlen(key));
1158 Ustrcpy(k->key, key);
1161 /* Now scan the collected keys and operate on the records, resetting
1162 the store each time round. */
1164 reset_point = store_get(0);
1168 dbdata_generic *value;
1170 store_reset(reset_point);
1171 key = keychain->key;
1172 keychain = keychain->next;
1173 value = dbfn_read_with_length(dbm, key, NULL);
1175 /* A continuation record may have been deleted or renamed already, so
1176 non-existence is not serious. */
1178 if (value == NULL) continue;
1180 /* Delete if too old */
1182 if (value->time_stamp < oldest)
1184 printf("deleted %s (too old)\n", key);
1185 dbfn_delete(dbm, key);
1189 /* Do database-specific tidying for wait databases, and message-
1190 specific tidying for the retry database. */
1192 if (dbdata_type == type_wait)
1194 dbdata_wait *wait = (dbdata_wait *)value;
1195 BOOL update = FALSE;
1197 /* Leave corrupt records alone */
1199 if (wait->count > WAIT_NAME_MAX)
1201 printf("**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
1202 key, wait->count, wait->count, WAIT_NAME_MAX);
1206 /* Loop for renamed continuation records. For each message id,
1207 check to see if the message exists, and if not, remove its entry
1208 from the record. Because of the possibility of split input directories,
1209 we must look in both possible places for a -D file. */
1213 int length = wait->count * MESSAGE_ID_LENGTH;
1215 for (int offset = length - MESSAGE_ID_LENGTH;
1216 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1218 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1219 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1221 if (Ustat(buffer, &statbuf) != 0)
1223 buffer[path_len] = wait->text[offset+5];
1224 buffer[path_len+1] = '/';
1225 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1226 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1228 if (Ustat(buffer, &statbuf) != 0)
1230 int left = length - offset - MESSAGE_ID_LENGTH;
1231 if (left > 0) Ustrncpy(wait->text + offset,
1232 wait->text + offset + MESSAGE_ID_LENGTH, left);
1234 length -= MESSAGE_ID_LENGTH;
1240 /* If record is empty and the main record, either delete it or rename
1241 the next continuation, repeating if that is also empty. */
1243 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1245 while (wait->count == 0 && wait->sequence > 0)
1248 dbdata_generic *newvalue;
1249 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1250 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1251 if (newvalue != NULL)
1254 wait = (dbdata_wait *)newvalue;
1255 dbfn_delete(dbm, newkey);
1256 printf("renamed %s\n", newkey);
1259 else wait->sequence--;
1262 /* If we have ended up with an empty main record, delete it
1263 and break the loop. Otherwise the new record will be scanned. */
1265 if (wait->count == 0 && wait->sequence == 0)
1267 dbfn_delete(dbm, key);
1268 printf("deleted %s (empty)\n", key);
1274 /* If not an empty main record, break the loop */
1279 /* Re-write the record if required */
1283 printf("updated %s\n", key);
1284 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1285 wait->count * MESSAGE_ID_LENGTH);
1289 /* If a retry record's key ends with a message-id, check that that message
1290 still exists; if not, remove this record. */
1292 else if (dbdata_type == type_retry)
1295 int len = Ustrlen(key);
1297 if (len < MESSAGE_ID_LENGTH + 1) continue;
1298 id = key + len - MESSAGE_ID_LENGTH - 1;
1299 if (*id++ != ':') continue;
1301 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1302 if (i == 6 || i == 13)
1303 { if (id[i] != '-') break; }
1305 { if (!isalnum(id[i])) break; }
1306 if (i < MESSAGE_ID_LENGTH) continue;
1308 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1309 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1311 if (Ustat(buffer, &statbuf) != 0)
1313 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1314 if (Ustat(buffer, &statbuf) != 0)
1316 dbfn_delete(dbm, key);
1317 printf("deleted %s (no message)\n", key);
1324 printf("Tidying complete\n");
1328 #endif /* EXIM_TIDYDB */
1330 /* End of exim_dbutil.c */