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 *************************************************/
196 time_t now = time(NULL);
197 struct tm *tm = localtime(&now);
202 for (t = s + Ustrlen(s) - 1; t >= s; t--)
204 if (*t == ':') continue;
205 if (!isdigit((uschar)*t)) return -1;
210 if (!isdigit((uschar)*t)) return -1;
211 value = value + (*t - '0')*10;
216 case 0: tm->tm_min = value; break;
217 case 1: tm->tm_hour = value; break;
218 case 2: tm->tm_mday = value; break;
219 case 3: tm->tm_mon = value - 1; break;
220 case 4: tm->tm_year = (value < 90)? value + 100 : value; break;
227 #endif /* EXIM_FIXDB */
231 /*************************************************
232 * Open and lock a database file *
233 *************************************************/
235 /* This is a cut-down version from the function in dbfn.h that Exim itself
236 uses. We assume the database exists, and therefore give up if we cannot open
240 name The single-component name of one of Exim's database files.
241 flags O_RDONLY or O_RDWR
242 dbblock Points to an open_db block to be filled in.
245 Returns: NULL if the open failed, or the locking failed.
246 On success, dbblock is returned. This contains the dbm pointer and
247 the fd of the locked lock file.
251 dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof)
254 struct flock lock_data;
255 BOOL read_only = flags == O_RDONLY;
256 uschar * dirname, * filename;
258 /* The first thing to do is to open a separate file on which to lock. This
259 ensures that Exim has exclusive use of the database before it even tries to
260 open it. If there is a database, there should be a lock file in existence. */
262 #ifdef COMPILE_UTILITY
263 asprintf(CSS &dirname, "%s/db", spool_directory);
264 asprintf(CSS &filename, "%s/%s.lockfile", dirname, name);
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 asprintf(CSS &filename, "%s/%s", dirname, name);
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;
523 uschar keybuffer[1024];
525 /* Check the arguments, and open the database */
527 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
528 spool_directory = argv[1];
529 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE)))
532 /* Scan the file, formatting the information for each entry. Note
533 that data is returned in a malloc'ed block, in order that it be
534 correctly aligned. */
536 for (key = dbfn_scan(dbm, TRUE, &cursor);
538 key = dbfn_scan(dbm, FALSE, &cursor))
542 dbdata_callout_cache *callout;
543 dbdata_ratelimit *ratelimit;
544 dbdata_ratelimit_unique *rate_unique;
548 uschar name[MESSAGE_ID_LENGTH + 1];
551 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
552 which might change. */
554 if (Ustrlen(key) > sizeof(keybuffer) - 1)
556 printf("**** Overlong key encountered: %s\n", key);
559 Ustrcpy(keybuffer, key);
561 if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
562 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
563 "was not found in the file - something is wrong!\n",
567 /* Note: don't use print_time more than once in one statement, since
568 it uses a single buffer. */
573 retry = (dbdata_retry *)value;
574 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
575 retry->more_errno, retry->text,
576 print_time(retry->first_failed));
577 printf("%s ", print_time(retry->last_try));
578 printf("%s %s\n", print_time(retry->next_try),
579 (retry->expired)? "*" : "");
583 wait = (dbdata_wait *)value;
584 printf("%s ", keybuffer);
586 name[MESSAGE_ID_LENGTH] = 0;
588 if (wait->count > WAIT_NAME_MAX)
591 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
592 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
593 wait->count = WAIT_NAME_MAX;
594 yield = count_bad = 1;
596 for (i = 1; i <= wait->count; i++)
598 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
599 if (count_bad && name[0] == 0) break;
600 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
601 Ustrspn(name, "0123456789"
602 "abcdefghijklmnopqrstuvwxyz"
603 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
607 "**** Data for %s corrupted: bad character in message id\n",
609 for (j = 0; j < MESSAGE_ID_LENGTH; j++)
610 fprintf(stderr, "%02x ", name[j]);
611 fprintf(stderr, "\n");
616 t += MESSAGE_ID_LENGTH;
622 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
627 callout = (dbdata_callout_cache *)value;
629 /* New-style address record */
631 if (length == sizeof(dbdata_callout_cache_address))
633 printf("%s %s callout=%s\n",
634 print_time(((dbdata_generic *)value)->time_stamp),
636 print_cache(callout->result));
639 /* New-style domain record */
641 else if (length == sizeof(dbdata_callout_cache))
643 printf("%s %s callout=%s postmaster=%s",
644 print_time(((dbdata_generic *)value)->time_stamp),
646 print_cache(callout->result),
647 print_cache(callout->postmaster_result));
648 if (callout->postmaster_result != ccache_unknown)
649 printf(" (%s)", print_time(callout->postmaster_stamp));
650 printf(" random=%s", print_cache(callout->random_result));
651 if (callout->random_result != ccache_unknown)
652 printf(" (%s)", print_time(callout->random_stamp));
659 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
661 ratelimit = (dbdata_ratelimit *)value;
662 rate_unique = (dbdata_ratelimit_unique *)value;
663 printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
664 print_time(ratelimit->time_stamp),
665 ratelimit->time_usec, ratelimit->rate,
666 print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
671 ratelimit = (dbdata_ratelimit *)value;
672 printf("%s.%06d rate: %10.3f key: %s\n",
673 print_time(ratelimit->time_stamp),
674 ratelimit->time_usec, ratelimit->rate,
687 #endif /* EXIM_DUMPDB */
693 /*************************************************
694 * The exim_fixdb main program *
695 *************************************************/
697 /* In order not to hold the database lock any longer than is necessary, each
698 operation on the database uses a separate open/close call. This is expensive,
699 but then using this utility is not expected to be very common. Its main use is
700 to provide a way of patching up hints databases in order to run tests.
705 This causes the data from the given record to be displayed, or "not found"
706 to be output. Note that in the retry database, destination names are
707 preceded by R: or T: for router or transport retry info.
710 This causes the given record to be deleted or "not found" to be output.
712 (3) <record name> <field number> <value>
713 This sets the given value into the given field, identified by a number
714 which is output by the display command. Not all types of record can
718 This exits from exim_fixdb.
720 If the record name is omitted from (2) or (3), the previously used record name
724 int main(int argc, char **cargv)
727 uschar **argv = USS cargv;
730 void *reset_point = store_get(0);
732 name[0] = 0; /* No name set */
734 /* Sort out the database type, verify what we are working on and then process
737 dbdata_type = check_args(argc, argv, US"fixdb", US"");
738 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
747 dbdata_callout_cache *callout;
748 dbdata_ratelimit *ratelimit;
749 dbdata_ratelimit_unique *rate_unique;
752 uschar field[256], value[256];
754 store_reset(reset_point);
757 if (Ufgets(buffer, 256, stdin) == NULL) break;
759 buffer[Ustrlen(buffer)-1] = 0;
760 field[0] = value[0] = 0;
762 /* If the buffer contains just one digit, or just consists of "d", use the
763 previous name for an update. */
765 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
766 || Ustrcmp(buffer, "d") == 0)
770 printf("No previous record name is set\n");
773 (void)sscanf(CS buffer, "%s %s", field, value);
778 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
781 /* Handle an update request */
786 spool_directory = argv[1];
788 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE)))
791 if (Ustrcmp(field, "d") == 0)
793 if (value[0] != 0) printf("unexpected value after \"d\"\n");
794 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
795 "not found" : "deleted");
800 else if (isdigit((uschar)field[0]))
802 int fieldno = Uatoi(field);
805 printf("value missing\n");
811 record = dbfn_read_with_length(dbm, name, &oldlength);
812 if (record == NULL) printf("not found\n"); else
815 /*int length = 0; Stops compiler warning */
820 retry = (dbdata_retry *)record;
821 /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
826 retry->basic_errno = Uatoi(value);
830 retry->more_errno = Uatoi(value);
834 if ((tt = read_time(value)) > 0) retry->first_failed = tt;
835 else printf("bad time value\n");
839 if ((tt = read_time(value)) > 0) retry->last_try = tt;
840 else printf("bad time value\n");
844 if ((tt = read_time(value)) > 0) retry->next_try = tt;
845 else printf("bad time value\n");
849 if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
850 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
851 else printf("\"yes\" or \"no\" expected=n");
855 printf("unknown field number\n");
862 printf("Can't change contents of wait database record\n");
866 printf("Can't change contents of misc database record\n");
870 callout = (dbdata_callout_cache *)record;
871 /* length = sizeof(dbdata_callout_cache); */
875 callout->result = Uatoi(value);
879 callout->postmaster_result = Uatoi(value);
883 callout->random_result = Uatoi(value);
887 printf("unknown field number\n");
894 ratelimit = (dbdata_ratelimit *)record;
898 if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
899 else printf("bad time value\n");
903 ratelimit->time_usec = Uatoi(value);
907 ratelimit->rate = Ustrtod(value, NULL);
911 if (Ustrstr(name, "/unique/") != NULL
912 && oldlength >= sizeof(dbdata_ratelimit_unique))
914 rate_unique = (dbdata_ratelimit_unique *)record;
915 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
916 else printf("bad time value\n");
919 /* else fall through */
923 if (Ustrstr(name, "/unique/") != NULL
924 && oldlength >= sizeof(dbdata_ratelimit_unique))
928 unsigned n, hash, hinc;
932 md5_end(&md5info, value, Ustrlen(value), md5sum);
933 hash = md5sum[0] << 0 | md5sum[1] << 8
934 | md5sum[2] << 16 | md5sum[3] << 24;
935 hinc = md5sum[4] << 0 | md5sum[5] << 8
936 | md5sum[6] << 16 | md5sum[7] << 24;
937 rate_unique = (dbdata_ratelimit_unique *)record;
939 for (n = 0; n < 8; n++, hash += hinc)
941 int bit = 1 << (hash % 8);
942 int byte = (hash / 8) % rate_unique->bloom_size;
943 if ((rate_unique->bloom[byte] & bit) == 0)
946 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
950 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
953 /* else fall through */
956 printf("unknown field number\n");
963 dbfn_write(dbm, name, record, oldlength);
970 printf("field number or d expected\n");
975 if (!verify) continue;
978 /* The "name" q causes an exit */
980 else if (Ustrcmp(name, "q") == 0) return 0;
982 /* Handle a read request, or verify after an update. */
984 spool_directory = argv[1];
985 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE)))
988 if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
990 printf("record %s not found\n", name);
996 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
1000 retry = (dbdata_retry *)record;
1001 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
1002 printf("1 extra data: %d\n", retry->more_errno);
1003 printf("2 first failed: %s\n", print_time(retry->first_failed));
1004 printf("3 last try: %s\n", print_time(retry->last_try));
1005 printf("4 next try: %s\n", print_time(retry->next_try));
1006 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
1010 wait = (dbdata_wait *)record;
1012 printf("Sequence: %d\n", wait->sequence);
1013 if (wait->count > WAIT_NAME_MAX)
1015 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
1016 wait->count, WAIT_NAME_MAX);
1017 wait->count = WAIT_NAME_MAX;
1020 for (i = 1; i <= wait->count; i++)
1022 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1023 value[MESSAGE_ID_LENGTH] = 0;
1024 if (count_bad && value[0] == 0) break;
1025 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1026 Ustrspn(value, "0123456789"
1027 "abcdefghijklmnopqrstuvwxyz"
1028 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1031 printf("\n**** Data corrupted: bad character in message id ****\n");
1032 for (j = 0; j < MESSAGE_ID_LENGTH; j++)
1033 printf("%02x ", value[j]);
1037 printf("%s ", value);
1038 t += MESSAGE_ID_LENGTH;
1047 callout = (dbdata_callout_cache *)record;
1048 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1050 if (oldlength > sizeof(dbdata_callout_cache_address))
1052 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1053 callout->postmaster_result);
1054 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1055 callout->random_result);
1059 case type_ratelimit:
1060 ratelimit = (dbdata_ratelimit *)record;
1061 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1062 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1063 printf("2 sender rate: % .3f\n", ratelimit->rate);
1064 if (Ustrstr(name, "/unique/") != NULL
1065 && oldlength >= sizeof(dbdata_ratelimit_unique))
1067 rate_unique = (dbdata_ratelimit_unique *)record;
1068 printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1069 printf("4 test filter membership\n");
1070 printf("5 add element to filter\n");
1076 /* The database is closed after each request */
1085 #endif /* EXIM_FIXDB */
1090 /*************************************************
1091 * The exim_tidydb main program *
1092 *************************************************/
1095 /* Utility program to tidy the contents of an exim database file. There is one
1098 -t <time> expiry time for old records - default 30 days
1100 For backwards compatibility, an -f option is recognized and ignored. (It used
1101 to request a "full" tidy. This version always does the whole job.) */
1104 typedef struct key_item {
1105 struct key_item *next;
1110 int main(int argc, char **cargv)
1112 struct stat statbuf;
1113 int maxkeep = 30 * 24 * 60 * 60;
1114 int dbdata_type, i, oldest, path_len;
1115 key_item *keychain = NULL;
1119 EXIM_CURSOR *cursor;
1120 uschar **argv = USS cargv;
1124 /* Scan the options */
1126 for (i = 1; i < argc; i++)
1128 if (argv[i][0] != '-') break;
1129 if (Ustrcmp(argv[i], "-f") == 0) continue;
1130 if (Ustrcmp(argv[i], "-t") == 0)
1138 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1139 (void)sscanf(CS s, "%d%n", &value, &count);
1143 case 'w': value *= 7;
1144 case 'd': value *= 24;
1145 case 'h': value *= 60;
1146 case 'm': value *= 60;
1149 default: usage(US"tidydb", US" [-t <time>]");
1154 else usage(US"tidydb", US" [-t <time>]");
1157 /* Adjust argument values and process arguments */
1162 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1164 /* Compute the oldest keep time, verify what we are doing, and open the
1167 oldest = time(NULL) - maxkeep;
1168 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1170 spool_directory = argv[1];
1171 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE)))
1174 /* Prepare for building file names */
1176 sprintf(CS buffer, "%s/input/", argv[1]);
1177 path_len = Ustrlen(buffer);
1180 /* It appears, by experiment, that it is a bad idea to make changes
1181 to the file while scanning it. Pity the man page doesn't warn you about that.
1182 Therefore, we scan and build a list of all the keys. Then we use that to
1183 read the records and possibly update them. */
1185 for (key = dbfn_scan(dbm, TRUE, &cursor);
1187 key = dbfn_scan(dbm, FALSE, &cursor))
1189 key_item *k = store_get(sizeof(key_item) + Ustrlen(key));
1192 Ustrcpy(k->key, key);
1195 /* Now scan the collected keys and operate on the records, resetting
1196 the store each time round. */
1198 reset_point = store_get(0);
1202 dbdata_generic *value;
1204 store_reset(reset_point);
1205 key = keychain->key;
1206 keychain = keychain->next;
1207 value = dbfn_read_with_length(dbm, key, NULL);
1209 /* A continuation record may have been deleted or renamed already, so
1210 non-existence is not serious. */
1212 if (value == NULL) continue;
1214 /* Delete if too old */
1216 if (value->time_stamp < oldest)
1218 printf("deleted %s (too old)\n", key);
1219 dbfn_delete(dbm, key);
1223 /* Do database-specific tidying for wait databases, and message-
1224 specific tidying for the retry database. */
1226 if (dbdata_type == type_wait)
1228 dbdata_wait *wait = (dbdata_wait *)value;
1229 BOOL update = FALSE;
1231 /* Leave corrupt records alone */
1233 if (wait->count > WAIT_NAME_MAX)
1235 printf("**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
1236 key, wait->count, wait->count, WAIT_NAME_MAX);
1240 /* Loop for renamed continuation records. For each message id,
1241 check to see if the message exists, and if not, remove its entry
1242 from the record. Because of the possibility of split input directories,
1243 we must look in both possible places for a -D file. */
1248 int length = wait->count * MESSAGE_ID_LENGTH;
1250 for (offset = length - MESSAGE_ID_LENGTH;
1251 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1253 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1254 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1256 if (Ustat(buffer, &statbuf) != 0)
1258 buffer[path_len] = wait->text[offset+5];
1259 buffer[path_len+1] = '/';
1260 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1261 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1263 if (Ustat(buffer, &statbuf) != 0)
1265 int left = length - offset - MESSAGE_ID_LENGTH;
1266 if (left > 0) Ustrncpy(wait->text + offset,
1267 wait->text + offset + MESSAGE_ID_LENGTH, left);
1269 length -= MESSAGE_ID_LENGTH;
1275 /* If record is empty and the main record, either delete it or rename
1276 the next continuation, repeating if that is also empty. */
1278 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1280 while (wait->count == 0 && wait->sequence > 0)
1283 dbdata_generic *newvalue;
1284 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1285 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1286 if (newvalue != NULL)
1289 wait = (dbdata_wait *)newvalue;
1290 dbfn_delete(dbm, newkey);
1291 printf("renamed %s\n", newkey);
1294 else wait->sequence--;
1297 /* If we have ended up with an empty main record, delete it
1298 and break the loop. Otherwise the new record will be scanned. */
1300 if (wait->count == 0 && wait->sequence == 0)
1302 dbfn_delete(dbm, key);
1303 printf("deleted %s (empty)\n", key);
1309 /* If not an empty main record, break the loop */
1314 /* Re-write the record if required */
1318 printf("updated %s\n", key);
1319 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1320 wait->count * MESSAGE_ID_LENGTH);
1324 /* If a retry record's key ends with a message-id, check that that message
1325 still exists; if not, remove this record. */
1327 else if (dbdata_type == type_retry)
1330 int len = Ustrlen(key);
1332 if (len < MESSAGE_ID_LENGTH + 1) continue;
1333 id = key + len - MESSAGE_ID_LENGTH - 1;
1334 if (*id++ != ':') continue;
1336 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1338 if (i == 6 || i == 13)
1339 { if (id[i] != '-') break; }
1341 { if (!isalnum(id[i])) break; }
1343 if (i < MESSAGE_ID_LENGTH) continue;
1345 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1346 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1348 if (Ustat(buffer, &statbuf) != 0)
1350 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1351 if (Ustat(buffer, &statbuf) != 0)
1353 dbfn_delete(dbm, key);
1354 printf("deleted %s (no message)\n", key);
1361 printf("Tidying complete\n");
1365 #endif /* EXIM_TIDYDB */
1367 /* End of exim_dbutil.c */