1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2016 */
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[256], filename[256];
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 snprintf(CS dirname, sizeof(dirname), "%s/db", spool_directory);
263 snprintf(CS filename, sizeof(filename), "%s/%.200s.lockfile", dirname, name);
265 dbblock->lockfd = Uopen(filename, flags, 0);
266 if (dbblock->lockfd < 0)
268 printf("** Failed to open database lock file %s: %s\n", filename,
273 /* Now we must get a lock on the opened lock file; do this with a blocking
274 lock that times out. */
276 lock_data.l_type = read_only? F_RDLCK : F_WRLCK;
277 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
279 sigalrm_seen = FALSE;
280 os_non_restarting_signal(SIGALRM, sigalrm_handler);
281 alarm(EXIMDB_LOCK_TIMEOUT);
282 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
285 if (sigalrm_seen) errno = ETIMEDOUT;
288 printf("** Failed to get %s lock for %s: %s",
289 flags & O_WRONLY ? "write" : "read",
291 errno == ETIMEDOUT ? "timed out" : strerror(errno));
292 (void)close(dbblock->lockfd);
296 /* At this point we have an opened and locked separate lock file, that is,
297 exclusive access to the database, so we can go ahead and open it. */
299 sprintf(CS filename, "%s/%s", dirname, name);
300 EXIM_DBOPEN(filename, dirname, flags, 0, &(dbblock->dbptr));
302 if (dbblock->dbptr == NULL)
304 printf("** Failed to open DBM file %s for %s:\n %s%s\n", filename,
305 read_only? "reading" : "writing", strerror(errno),
307 " (or Berkeley DB error while opening)"
312 (void)close(dbblock->lockfd);
322 /*************************************************
323 * Unlock and close a database file *
324 *************************************************/
326 /* Closing a file automatically unlocks it, so after closing the database, just
329 Argument: a pointer to an open database block
334 dbfn_close(open_db *dbblock)
336 EXIM_DBCLOSE(dbblock->dbptr);
337 (void)close(dbblock->lockfd);
343 /*************************************************
344 * Read from database file *
345 *************************************************/
347 /* Passing back the pointer unchanged is useless, because there is no guarantee
348 of alignment. Since all the records used by Exim need to be properly aligned to
349 pick out the timestamps, etc., do the copying centrally here.
352 dbblock a pointer to an open database block
353 key the key of the record to be read
354 length where to put the length (or NULL if length not wanted)
356 Returns: a pointer to the retrieved record, or
357 NULL if the record is not found
361 dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
364 EXIM_DATUM key_datum, result_datum;
365 int klen = Ustrlen(key) + 1;
366 uschar * key_copy = store_get(klen);
368 memcpy(key_copy, key, klen);
370 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
371 EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */
372 EXIM_DATUM_DATA(key_datum) = CS key_copy;
373 EXIM_DATUM_SIZE(key_datum) = klen;
375 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
377 yield = store_get(EXIM_DATUM_SIZE(result_datum));
378 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
379 if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
381 EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */
387 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
389 /*************************************************
390 * Write to database file *
391 *************************************************/
395 dbblock a pointer to an open database block
396 key the key of the record to be written
397 ptr a pointer to the record to be written
398 length the length of the record to be written
400 Returns: the yield of the underlying dbm or db "write" function. If this
401 is dbm, the value is zero for OK.
405 dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
407 EXIM_DATUM key_datum, value_datum;
408 dbdata_generic *gptr = (dbdata_generic *)ptr;
409 int klen = Ustrlen(key) + 1;
410 uschar * key_copy = store_get(klen);
412 memcpy(key_copy, key, klen);
413 gptr->time_stamp = time(NULL);
415 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
416 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
417 EXIM_DATUM_DATA(key_datum) = CS key_copy;
418 EXIM_DATUM_SIZE(key_datum) = klen;
419 EXIM_DATUM_DATA(value_datum) = CS ptr;
420 EXIM_DATUM_SIZE(value_datum) = length;
421 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
426 /*************************************************
427 * Delete record from database file *
428 *************************************************/
432 dbblock a pointer to an open database block
433 key the key of the record to be deleted
435 Returns: the yield of the underlying dbm or db "delete" function.
439 dbfn_delete(open_db *dbblock, const uschar *key)
441 int klen = Ustrlen(key) + 1;
442 uschar * key_copy = store_get(klen);
444 memcpy(key_copy, key, klen);
445 EXIM_DATUM key_datum;
446 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
447 EXIM_DATUM_DATA(key_datum) = CS key_copy;
448 EXIM_DATUM_SIZE(key_datum) = klen;
449 return EXIM_DBDEL(dbblock->dbptr, key_datum);
452 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
456 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
457 /*************************************************
458 * Scan the keys of a database file *
459 *************************************************/
463 dbblock a pointer to an open database block
464 start TRUE if starting a new scan
465 FALSE if continuing with the current scan
466 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
467 that use the notion of a cursor
469 Returns: the next record from the file, or
470 NULL if there are no more
474 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
476 EXIM_DATUM key_datum, value_datum;
478 value_datum = value_datum; /* dummy; not all db libraries use this */
480 /* Some dbm require an initialization */
482 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
484 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
485 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
487 yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
488 US EXIM_DATUM_DATA(key_datum) : NULL;
490 /* Some dbm require a termination */
492 if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
495 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
500 /*************************************************
501 * The exim_dumpdb main program *
502 *************************************************/
505 main(int argc, char **cargv)
512 uschar **argv = USS cargv;
514 uschar keybuffer[1024];
516 /* Check the arguments, and open the database */
518 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
519 spool_directory = argv[1];
520 dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE);
521 if (dbm == NULL) exit(1);
523 /* Scan the file, formatting the information for each entry. Note
524 that data is returned in a malloc'ed block, in order that it be
525 correctly aligned. */
527 key = dbfn_scan(dbm, TRUE, &cursor);
532 dbdata_callout_cache *callout;
533 dbdata_ratelimit *ratelimit;
534 dbdata_ratelimit_unique *rate_unique;
538 uschar name[MESSAGE_ID_LENGTH + 1];
541 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
542 which might change. */
544 if (Ustrlen(key) > sizeof(keybuffer) - 1)
546 printf("**** Overlong key encountered: %s\n", key);
549 Ustrcpy(keybuffer, key);
550 value = dbfn_read_with_length(dbm, keybuffer, &length);
553 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
554 "was not found in the file - something is wrong!\n",
558 /* Note: don't use print_time more than once in one statement, since
559 it uses a single buffer. */
564 retry = (dbdata_retry *)value;
565 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
566 retry->more_errno, retry->text,
567 print_time(retry->first_failed));
568 printf("%s ", print_time(retry->last_try));
569 printf("%s %s\n", print_time(retry->next_try),
570 (retry->expired)? "*" : "");
574 wait = (dbdata_wait *)value;
575 printf("%s ", keybuffer);
577 name[MESSAGE_ID_LENGTH] = 0;
579 if (wait->count > WAIT_NAME_MAX)
582 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
583 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
584 wait->count = WAIT_NAME_MAX;
585 yield = count_bad = 1;
587 for (i = 1; i <= wait->count; i++)
589 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
590 if (count_bad && name[0] == 0) break;
591 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
592 Ustrspn(name, "0123456789"
593 "abcdefghijklmnopqrstuvwxyz"
594 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
598 "**** Data for %s corrupted: bad character in message id\n",
600 for (j = 0; j < MESSAGE_ID_LENGTH; j++)
601 fprintf(stderr, "%02x ", name[j]);
602 fprintf(stderr, "\n");
607 t += MESSAGE_ID_LENGTH;
613 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
618 callout = (dbdata_callout_cache *)value;
620 /* New-style address record */
622 if (length == sizeof(dbdata_callout_cache_address))
624 printf("%s %s callout=%s\n",
625 print_time(((dbdata_generic *)value)->time_stamp),
627 print_cache(callout->result));
630 /* New-style domain record */
632 else if (length == sizeof(dbdata_callout_cache))
634 printf("%s %s callout=%s postmaster=%s",
635 print_time(((dbdata_generic *)value)->time_stamp),
637 print_cache(callout->result),
638 print_cache(callout->postmaster_result));
639 if (callout->postmaster_result != ccache_unknown)
640 printf(" (%s)", print_time(callout->postmaster_stamp));
641 printf(" random=%s", print_cache(callout->random_result));
642 if (callout->random_result != ccache_unknown)
643 printf(" (%s)", print_time(callout->random_stamp));
650 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
652 ratelimit = (dbdata_ratelimit *)value;
653 rate_unique = (dbdata_ratelimit_unique *)value;
654 printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
655 print_time(ratelimit->time_stamp),
656 ratelimit->time_usec, ratelimit->rate,
657 print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
662 ratelimit = (dbdata_ratelimit *)value;
663 printf("%s.%06d rate: %10.3f key: %s\n",
664 print_time(ratelimit->time_stamp),
665 ratelimit->time_usec, ratelimit->rate,
672 key = dbfn_scan(dbm, FALSE, &cursor);
679 #endif /* EXIM_DUMPDB */
685 /*************************************************
686 * The exim_fixdb main program *
687 *************************************************/
689 /* In order not to hold the database lock any longer than is necessary, each
690 operation on the database uses a separate open/close call. This is expensive,
691 but then using this utility is not expected to be very common. Its main use is
692 to provide a way of patching up hints databases in order to run tests.
697 This causes the data from the given record to be displayed, or "not found"
698 to be output. Note that in the retry database, destination names are
699 preceded by R: or T: for router or transport retry info.
702 This causes the given record to be deleted or "not found" to be output.
704 (3) <record name> <field number> <value>
705 This sets the given value into the given field, identified by a number
706 which is output by the display command. Not all types of record can
710 This exits from exim_fixdb.
712 If the record name is omitted from (2) or (3), the previously used record name
716 int main(int argc, char **cargv)
719 uschar **argv = USS cargv;
722 void *reset_point = store_get(0);
724 name[0] = 0; /* No name set */
726 /* Sort out the database type, verify what we are working on and then process
729 dbdata_type = check_args(argc, argv, US"fixdb", US"");
730 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
739 dbdata_callout_cache *callout;
740 dbdata_ratelimit *ratelimit;
741 dbdata_ratelimit_unique *rate_unique;
744 uschar field[256], value[256];
746 store_reset(reset_point);
749 if (Ufgets(buffer, 256, stdin) == NULL) break;
751 buffer[Ustrlen(buffer)-1] = 0;
752 field[0] = value[0] = 0;
754 /* If the buffer contains just one digit, or just consists of "d", use the
755 previous name for an update. */
757 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
758 || Ustrcmp(buffer, "d") == 0)
762 printf("No previous record name is set\n");
765 (void)sscanf(CS buffer, "%s %s", field, value);
770 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
773 /* Handle an update request */
778 spool_directory = argv[1];
779 dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE);
780 if (dbm == NULL) continue;
782 if (Ustrcmp(field, "d") == 0)
784 if (value[0] != 0) printf("unexpected value after \"d\"\n");
785 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
786 "not found" : "deleted");
791 else if (isdigit((uschar)field[0]))
793 int fieldno = Uatoi(field);
796 printf("value missing\n");
802 record = dbfn_read_with_length(dbm, name, &oldlength);
803 if (record == NULL) printf("not found\n"); else
806 /*int length = 0; Stops compiler warning */
811 retry = (dbdata_retry *)record;
812 /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
817 retry->basic_errno = Uatoi(value);
821 retry->more_errno = Uatoi(value);
825 if ((tt = read_time(value)) > 0) retry->first_failed = tt;
826 else printf("bad time value\n");
830 if ((tt = read_time(value)) > 0) retry->last_try = tt;
831 else printf("bad time value\n");
835 if ((tt = read_time(value)) > 0) retry->next_try = tt;
836 else printf("bad time value\n");
840 if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
841 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
842 else printf("\"yes\" or \"no\" expected=n");
846 printf("unknown field number\n");
853 printf("Can't change contents of wait database record\n");
857 printf("Can't change contents of misc database record\n");
861 callout = (dbdata_callout_cache *)record;
862 /* length = sizeof(dbdata_callout_cache); */
866 callout->result = Uatoi(value);
870 callout->postmaster_result = Uatoi(value);
874 callout->random_result = Uatoi(value);
878 printf("unknown field number\n");
885 ratelimit = (dbdata_ratelimit *)record;
889 if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
890 else printf("bad time value\n");
894 ratelimit->time_usec = Uatoi(value);
898 ratelimit->rate = Ustrtod(value, NULL);
902 if (Ustrstr(name, "/unique/") != NULL
903 && oldlength >= sizeof(dbdata_ratelimit_unique))
905 rate_unique = (dbdata_ratelimit_unique *)record;
906 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
907 else printf("bad time value\n");
910 /* else fall through */
914 if (Ustrstr(name, "/unique/") != NULL
915 && oldlength >= sizeof(dbdata_ratelimit_unique))
919 unsigned n, hash, hinc;
923 md5_end(&md5info, value, Ustrlen(value), md5sum);
924 hash = md5sum[0] << 0 | md5sum[1] << 8
925 | md5sum[2] << 16 | md5sum[3] << 24;
926 hinc = md5sum[4] << 0 | md5sum[5] << 8
927 | md5sum[6] << 16 | md5sum[7] << 24;
928 rate_unique = (dbdata_ratelimit_unique *)record;
930 for (n = 0; n < 8; n++, hash += hinc)
932 int bit = 1 << (hash % 8);
933 int byte = (hash / 8) % rate_unique->bloom_size;
934 if ((rate_unique->bloom[byte] & bit) == 0)
937 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
941 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
944 /* else fall through */
947 printf("unknown field number\n");
954 dbfn_write(dbm, name, record, oldlength);
961 printf("field number or d expected\n");
966 if (!verify) continue;
969 /* The "name" q causes an exit */
971 else if (Ustrcmp(name, "q") == 0) return 0;
973 /* Handle a read request, or verify after an update. */
975 spool_directory = argv[1];
976 dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE);
977 if (dbm == NULL) continue;
979 record = dbfn_read_with_length(dbm, name, &oldlength);
982 printf("record %s not found\n", name);
988 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
992 retry = (dbdata_retry *)record;
993 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
994 printf("1 extra data: %d\n", retry->more_errno);
995 printf("2 first failed: %s\n", print_time(retry->first_failed));
996 printf("3 last try: %s\n", print_time(retry->last_try));
997 printf("4 next try: %s\n", print_time(retry->next_try));
998 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
1002 wait = (dbdata_wait *)record;
1004 printf("Sequence: %d\n", wait->sequence);
1005 if (wait->count > WAIT_NAME_MAX)
1007 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
1008 wait->count, WAIT_NAME_MAX);
1009 wait->count = WAIT_NAME_MAX;
1012 for (i = 1; i <= wait->count; i++)
1014 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1015 value[MESSAGE_ID_LENGTH] = 0;
1016 if (count_bad && value[0] == 0) break;
1017 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1018 Ustrspn(value, "0123456789"
1019 "abcdefghijklmnopqrstuvwxyz"
1020 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1023 printf("\n**** Data corrupted: bad character in message id ****\n");
1024 for (j = 0; j < MESSAGE_ID_LENGTH; j++)
1025 printf("%02x ", value[j]);
1029 printf("%s ", value);
1030 t += MESSAGE_ID_LENGTH;
1039 callout = (dbdata_callout_cache *)record;
1040 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1042 if (oldlength > sizeof(dbdata_callout_cache_address))
1044 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1045 callout->postmaster_result);
1046 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1047 callout->random_result);
1051 case type_ratelimit:
1052 ratelimit = (dbdata_ratelimit *)record;
1053 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1054 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1055 printf("2 sender rate: % .3f\n", ratelimit->rate);
1056 if (Ustrstr(name, "/unique/") != NULL
1057 && oldlength >= sizeof(dbdata_ratelimit_unique))
1059 rate_unique = (dbdata_ratelimit_unique *)record;
1060 printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1061 printf("4 test filter membership\n");
1062 printf("5 add element to filter\n");
1068 /* The database is closed after each request */
1077 #endif /* EXIM_FIXDB */
1082 /*************************************************
1083 * The exim_tidydb main program *
1084 *************************************************/
1087 /* Utility program to tidy the contents of an exim database file. There is one
1090 -t <time> expiry time for old records - default 30 days
1092 For backwards compatibility, an -f option is recognized and ignored. (It used
1093 to request a "full" tidy. This version always does the whole job.) */
1096 typedef struct key_item {
1097 struct key_item *next;
1102 int main(int argc, char **cargv)
1104 struct stat statbuf;
1105 int maxkeep = 30 * 24 * 60 * 60;
1106 int dbdata_type, i, oldest, path_len;
1107 key_item *keychain = NULL;
1111 EXIM_CURSOR *cursor;
1112 uschar **argv = USS cargv;
1116 /* Scan the options */
1118 for (i = 1; i < argc; i++)
1120 if (argv[i][0] != '-') break;
1121 if (Ustrcmp(argv[i], "-f") == 0) continue;
1122 if (Ustrcmp(argv[i], "-t") == 0)
1130 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1131 (void)sscanf(CS s, "%d%n", &value, &count);
1135 case 'w': value *= 7;
1136 case 'd': value *= 24;
1137 case 'h': value *= 60;
1138 case 'm': value *= 60;
1141 default: usage(US"tidydb", US" [-t <time>]");
1146 else usage(US"tidydb", US" [-t <time>]");
1149 /* Adjust argument values and process arguments */
1154 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1156 /* Compute the oldest keep time, verify what we are doing, and open the
1159 oldest = time(NULL) - maxkeep;
1160 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1162 spool_directory = argv[1];
1163 dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE);
1164 if (dbm == NULL) exit(1);
1166 /* Prepare for building file names */
1168 sprintf(CS buffer, "%s/input/", argv[1]);
1169 path_len = Ustrlen(buffer);
1172 /* It appears, by experiment, that it is a bad idea to make changes
1173 to the file while scanning it. Pity the man page doesn't warn you about that.
1174 Therefore, we scan and build a list of all the keys. Then we use that to
1175 read the records and possibly update them. */
1177 key = dbfn_scan(dbm, TRUE, &cursor);
1180 key_item *k = store_get(sizeof(key_item) + Ustrlen(key));
1183 Ustrcpy(k->key, key);
1184 key = dbfn_scan(dbm, FALSE, &cursor);
1187 /* Now scan the collected keys and operate on the records, resetting
1188 the store each time round. */
1190 reset_point = store_get(0);
1192 while (keychain != NULL)
1194 dbdata_generic *value;
1196 store_reset(reset_point);
1197 key = keychain->key;
1198 keychain = keychain->next;
1199 value = dbfn_read_with_length(dbm, key, NULL);
1201 /* A continuation record may have been deleted or renamed already, so
1202 non-existence is not serious. */
1204 if (value == NULL) continue;
1206 /* Delete if too old */
1208 if (value->time_stamp < oldest)
1210 printf("deleted %s (too old)\n", key);
1211 dbfn_delete(dbm, key);
1215 /* Do database-specific tidying for wait databases, and message-
1216 specific tidying for the retry database. */
1218 if (dbdata_type == type_wait)
1220 dbdata_wait *wait = (dbdata_wait *)value;
1221 BOOL update = FALSE;
1223 /* Leave corrupt records alone */
1225 if (wait->count > WAIT_NAME_MAX)
1227 printf("**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
1228 key, wait->count, wait->count, WAIT_NAME_MAX);
1232 /* Loop for renamed continuation records. For each message id,
1233 check to see if the message exists, and if not, remove its entry
1234 from the record. Because of the possibility of split input directories,
1235 we must look in both possible places for a -D file. */
1240 int length = wait->count * MESSAGE_ID_LENGTH;
1242 for (offset = length - MESSAGE_ID_LENGTH;
1243 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1245 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1246 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1248 if (Ustat(buffer, &statbuf) != 0)
1250 buffer[path_len] = wait->text[offset+5];
1251 buffer[path_len+1] = '/';
1252 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1253 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1255 if (Ustat(buffer, &statbuf) != 0)
1257 int left = length - offset - MESSAGE_ID_LENGTH;
1258 if (left > 0) Ustrncpy(wait->text + offset,
1259 wait->text + offset + MESSAGE_ID_LENGTH, left);
1261 length -= MESSAGE_ID_LENGTH;
1267 /* If record is empty and the main record, either delete it or rename
1268 the next continuation, repeating if that is also empty. */
1270 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1272 while (wait->count == 0 && wait->sequence > 0)
1275 dbdata_generic *newvalue;
1276 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1277 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1278 if (newvalue != NULL)
1281 wait = (dbdata_wait *)newvalue;
1282 dbfn_delete(dbm, newkey);
1283 printf("renamed %s\n", newkey);
1286 else wait->sequence--;
1289 /* If we have ended up with an empty main record, delete it
1290 and break the loop. Otherwise the new record will be scanned. */
1292 if (wait->count == 0 && wait->sequence == 0)
1294 dbfn_delete(dbm, key);
1295 printf("deleted %s (empty)\n", key);
1301 /* If not an empty main record, break the loop */
1306 /* Re-write the record if required */
1310 printf("updated %s\n", key);
1311 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1312 wait->count * MESSAGE_ID_LENGTH);
1316 /* If a retry record's key ends with a message-id, check that that message
1317 still exists; if not, remove this record. */
1319 else if (dbdata_type == type_retry)
1322 int len = Ustrlen(key);
1324 if (len < MESSAGE_ID_LENGTH + 1) continue;
1325 id = key + len - MESSAGE_ID_LENGTH - 1;
1326 if (*id++ != ':') continue;
1328 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1330 if (i == 6 || i == 13)
1331 { if (id[i] != '-') break; }
1333 { if (!isalnum(id[i])) break; }
1335 if (i < MESSAGE_ID_LENGTH) continue;
1337 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1338 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1340 if (Ustat(buffer, &statbuf) != 0)
1342 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1343 if (Ustat(buffer, &statbuf) != 0)
1345 dbfn_delete(dbm, key);
1346 printf("deleted %s (no message)\n", key);
1353 printf("Tidying complete\n");
1357 #endif /* EXIM_TIDYDB */
1359 /* End of exim_dbutil.c */