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 if ( asprintf(CSS &dirname, "%s/db", spool_directory) < 0
264 || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
267 dirname = string_sprintf("%s/db", spool_directory);
268 filename = string_sprintf("%s/%s.lockfile", dirname, name);
271 dbblock->lockfd = Uopen(filename, flags, 0);
272 if (dbblock->lockfd < 0)
274 printf("** Failed to open database lock file %s: %s\n", filename,
279 /* Now we must get a lock on the opened lock file; do this with a blocking
280 lock that times out. */
282 lock_data.l_type = read_only ? F_RDLCK : F_WRLCK;
283 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
285 sigalrm_seen = FALSE;
286 os_non_restarting_signal(SIGALRM, sigalrm_handler);
287 ALARM(EXIMDB_LOCK_TIMEOUT);
288 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
291 if (sigalrm_seen) errno = ETIMEDOUT;
294 printf("** Failed to get %s lock for %s: %s",
295 flags & O_WRONLY ? "write" : "read",
297 errno == ETIMEDOUT ? "timed out" : strerror(errno));
298 (void)close(dbblock->lockfd);
302 /* At this point we have an opened and locked separate lock file, that is,
303 exclusive access to the database, so we can go ahead and open it. */
305 #ifdef COMPILE_UTILITY
306 if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
308 filename = string_sprintf("%s/%s", dirname, name);
310 EXIM_DBOPEN(filename, dirname, flags, 0, &(dbblock->dbptr));
314 printf("** Failed to open DBM file %s for %s:\n %s%s\n", filename,
315 read_only? "reading" : "writing", strerror(errno),
317 " (or Berkeley DB error while opening)"
322 (void)close(dbblock->lockfd);
332 /*************************************************
333 * Unlock and close a database file *
334 *************************************************/
336 /* Closing a file automatically unlocks it, so after closing the database, just
339 Argument: a pointer to an open database block
344 dbfn_close(open_db *dbblock)
346 EXIM_DBCLOSE(dbblock->dbptr);
347 (void)close(dbblock->lockfd);
353 /*************************************************
354 * Read from database file *
355 *************************************************/
357 /* Passing back the pointer unchanged is useless, because there is no guarantee
358 of alignment. Since all the records used by Exim need to be properly aligned to
359 pick out the timestamps, etc., do the copying centrally here.
362 dbblock a pointer to an open database block
363 key the key of the record to be read
364 length where to put the length (or NULL if length not wanted)
366 Returns: a pointer to the retrieved record, or
367 NULL if the record is not found
371 dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
374 EXIM_DATUM key_datum, result_datum;
375 int klen = Ustrlen(key) + 1;
376 uschar * key_copy = store_get(klen);
378 memcpy(key_copy, key, klen);
380 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
381 EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */
382 EXIM_DATUM_DATA(key_datum) = CS key_copy;
383 EXIM_DATUM_SIZE(key_datum) = klen;
385 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
387 yield = store_get(EXIM_DATUM_SIZE(result_datum));
388 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
389 if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
391 EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */
397 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
399 /*************************************************
400 * Write to database file *
401 *************************************************/
405 dbblock a pointer to an open database block
406 key the key of the record to be written
407 ptr a pointer to the record to be written
408 length the length of the record to be written
410 Returns: the yield of the underlying dbm or db "write" function. If this
411 is dbm, the value is zero for OK.
415 dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
417 EXIM_DATUM key_datum, value_datum;
418 dbdata_generic *gptr = (dbdata_generic *)ptr;
419 int klen = Ustrlen(key) + 1;
420 uschar * key_copy = store_get(klen);
422 memcpy(key_copy, key, klen);
423 gptr->time_stamp = time(NULL);
425 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
426 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
427 EXIM_DATUM_DATA(key_datum) = CS key_copy;
428 EXIM_DATUM_SIZE(key_datum) = klen;
429 EXIM_DATUM_DATA(value_datum) = CS ptr;
430 EXIM_DATUM_SIZE(value_datum) = length;
431 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
436 /*************************************************
437 * Delete record from database file *
438 *************************************************/
442 dbblock a pointer to an open database block
443 key the key of the record to be deleted
445 Returns: the yield of the underlying dbm or db "delete" function.
449 dbfn_delete(open_db *dbblock, const uschar *key)
451 int klen = Ustrlen(key) + 1;
452 uschar * key_copy = store_get(klen);
454 memcpy(key_copy, key, klen);
455 EXIM_DATUM key_datum;
456 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
457 EXIM_DATUM_DATA(key_datum) = CS key_copy;
458 EXIM_DATUM_SIZE(key_datum) = klen;
459 return EXIM_DBDEL(dbblock->dbptr, key_datum);
462 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
466 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
467 /*************************************************
468 * Scan the keys of a database file *
469 *************************************************/
473 dbblock a pointer to an open database block
474 start TRUE if starting a new scan
475 FALSE if continuing with the current scan
476 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
477 that use the notion of a cursor
479 Returns: the next record from the file, or
480 NULL if there are no more
484 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
486 EXIM_DATUM key_datum, value_datum;
488 value_datum = value_datum; /* dummy; not all db libraries use this */
490 /* Some dbm require an initialization */
492 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
494 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
495 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
497 yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
498 US EXIM_DATUM_DATA(key_datum) : NULL;
500 /* Some dbm require a termination */
502 if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
505 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
510 /*************************************************
511 * The exim_dumpdb main program *
512 *************************************************/
515 main(int argc, char **cargv)
522 uschar **argv = USS cargv;
524 uschar keybuffer[1024];
526 /* Check the arguments, and open the database */
528 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
529 spool_directory = argv[1];
530 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE)))
533 /* Scan the file, formatting the information for each entry. Note
534 that data is returned in a malloc'ed block, in order that it be
535 correctly aligned. */
537 for (key = dbfn_scan(dbm, TRUE, &cursor);
539 key = dbfn_scan(dbm, FALSE, &cursor))
543 dbdata_callout_cache *callout;
544 dbdata_ratelimit *ratelimit;
545 dbdata_ratelimit_unique *rate_unique;
549 uschar name[MESSAGE_ID_LENGTH + 1];
552 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
553 which might change. */
555 if (Ustrlen(key) > sizeof(keybuffer) - 1)
557 printf("**** Overlong key encountered: %s\n", key);
560 Ustrcpy(keybuffer, key);
562 if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
563 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
564 "was not found in the file - something is wrong!\n",
568 /* Note: don't use print_time more than once in one statement, since
569 it uses a single buffer. */
574 retry = (dbdata_retry *)value;
575 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
576 retry->more_errno, retry->text,
577 print_time(retry->first_failed));
578 printf("%s ", print_time(retry->last_try));
579 printf("%s %s\n", print_time(retry->next_try),
580 (retry->expired)? "*" : "");
584 wait = (dbdata_wait *)value;
585 printf("%s ", keybuffer);
587 name[MESSAGE_ID_LENGTH] = 0;
589 if (wait->count > WAIT_NAME_MAX)
592 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
593 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
594 wait->count = WAIT_NAME_MAX;
595 yield = count_bad = 1;
597 for (i = 1; i <= wait->count; i++)
599 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
600 if (count_bad && name[0] == 0) break;
601 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
602 Ustrspn(name, "0123456789"
603 "abcdefghijklmnopqrstuvwxyz"
604 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
608 "**** Data for %s corrupted: bad character in message id\n",
610 for (j = 0; j < MESSAGE_ID_LENGTH; j++)
611 fprintf(stderr, "%02x ", name[j]);
612 fprintf(stderr, "\n");
617 t += MESSAGE_ID_LENGTH;
623 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
628 callout = (dbdata_callout_cache *)value;
630 /* New-style address record */
632 if (length == sizeof(dbdata_callout_cache_address))
634 printf("%s %s callout=%s\n",
635 print_time(((dbdata_generic *)value)->time_stamp),
637 print_cache(callout->result));
640 /* New-style domain record */
642 else if (length == sizeof(dbdata_callout_cache))
644 printf("%s %s callout=%s postmaster=%s",
645 print_time(((dbdata_generic *)value)->time_stamp),
647 print_cache(callout->result),
648 print_cache(callout->postmaster_result));
649 if (callout->postmaster_result != ccache_unknown)
650 printf(" (%s)", print_time(callout->postmaster_stamp));
651 printf(" random=%s", print_cache(callout->random_result));
652 if (callout->random_result != ccache_unknown)
653 printf(" (%s)", print_time(callout->random_stamp));
660 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
662 ratelimit = (dbdata_ratelimit *)value;
663 rate_unique = (dbdata_ratelimit_unique *)value;
664 printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
665 print_time(ratelimit->time_stamp),
666 ratelimit->time_usec, ratelimit->rate,
667 print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
672 ratelimit = (dbdata_ratelimit *)value;
673 printf("%s.%06d rate: %10.3f key: %s\n",
674 print_time(ratelimit->time_stamp),
675 ratelimit->time_usec, ratelimit->rate,
688 #endif /* EXIM_DUMPDB */
694 /*************************************************
695 * The exim_fixdb main program *
696 *************************************************/
698 /* In order not to hold the database lock any longer than is necessary, each
699 operation on the database uses a separate open/close call. This is expensive,
700 but then using this utility is not expected to be very common. Its main use is
701 to provide a way of patching up hints databases in order to run tests.
706 This causes the data from the given record to be displayed, or "not found"
707 to be output. Note that in the retry database, destination names are
708 preceded by R: or T: for router or transport retry info.
711 This causes the given record to be deleted or "not found" to be output.
713 (3) <record name> <field number> <value>
714 This sets the given value into the given field, identified by a number
715 which is output by the display command. Not all types of record can
719 This exits from exim_fixdb.
721 If the record name is omitted from (2) or (3), the previously used record name
725 int main(int argc, char **cargv)
728 uschar **argv = USS cargv;
731 void *reset_point = store_get(0);
733 name[0] = 0; /* No name set */
735 /* Sort out the database type, verify what we are working on and then process
738 dbdata_type = check_args(argc, argv, US"fixdb", US"");
739 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
748 dbdata_callout_cache *callout;
749 dbdata_ratelimit *ratelimit;
750 dbdata_ratelimit_unique *rate_unique;
753 uschar field[256], value[256];
755 store_reset(reset_point);
758 if (Ufgets(buffer, 256, stdin) == NULL) break;
760 buffer[Ustrlen(buffer)-1] = 0;
761 field[0] = value[0] = 0;
763 /* If the buffer contains just one digit, or just consists of "d", use the
764 previous name for an update. */
766 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
767 || Ustrcmp(buffer, "d") == 0)
771 printf("No previous record name is set\n");
774 (void)sscanf(CS buffer, "%s %s", field, value);
779 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
782 /* Handle an update request */
787 spool_directory = argv[1];
789 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE)))
792 if (Ustrcmp(field, "d") == 0)
794 if (value[0] != 0) printf("unexpected value after \"d\"\n");
795 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
796 "not found" : "deleted");
801 else if (isdigit((uschar)field[0]))
803 int fieldno = Uatoi(field);
806 printf("value missing\n");
812 record = dbfn_read_with_length(dbm, name, &oldlength);
813 if (record == NULL) printf("not found\n"); else
816 /*int length = 0; Stops compiler warning */
821 retry = (dbdata_retry *)record;
822 /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
827 retry->basic_errno = Uatoi(value);
831 retry->more_errno = Uatoi(value);
835 if ((tt = read_time(value)) > 0) retry->first_failed = tt;
836 else printf("bad time value\n");
840 if ((tt = read_time(value)) > 0) retry->last_try = tt;
841 else printf("bad time value\n");
845 if ((tt = read_time(value)) > 0) retry->next_try = tt;
846 else printf("bad time value\n");
850 if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
851 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
852 else printf("\"yes\" or \"no\" expected=n");
856 printf("unknown field number\n");
863 printf("Can't change contents of wait database record\n");
867 printf("Can't change contents of misc database record\n");
871 callout = (dbdata_callout_cache *)record;
872 /* length = sizeof(dbdata_callout_cache); */
876 callout->result = Uatoi(value);
880 callout->postmaster_result = Uatoi(value);
884 callout->random_result = Uatoi(value);
888 printf("unknown field number\n");
895 ratelimit = (dbdata_ratelimit *)record;
899 if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
900 else printf("bad time value\n");
904 ratelimit->time_usec = Uatoi(value);
908 ratelimit->rate = Ustrtod(value, NULL);
912 if (Ustrstr(name, "/unique/") != NULL
913 && oldlength >= sizeof(dbdata_ratelimit_unique))
915 rate_unique = (dbdata_ratelimit_unique *)record;
916 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
917 else printf("bad time value\n");
920 /* else fall through */
924 if (Ustrstr(name, "/unique/") != NULL
925 && oldlength >= sizeof(dbdata_ratelimit_unique))
929 unsigned n, hash, hinc;
933 md5_end(&md5info, value, Ustrlen(value), md5sum);
934 hash = md5sum[0] << 0 | md5sum[1] << 8
935 | md5sum[2] << 16 | md5sum[3] << 24;
936 hinc = md5sum[4] << 0 | md5sum[5] << 8
937 | md5sum[6] << 16 | md5sum[7] << 24;
938 rate_unique = (dbdata_ratelimit_unique *)record;
940 for (n = 0; n < 8; n++, hash += hinc)
942 int bit = 1 << (hash % 8);
943 int byte = (hash / 8) % rate_unique->bloom_size;
944 if ((rate_unique->bloom[byte] & bit) == 0)
947 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
951 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
954 /* else fall through */
957 printf("unknown field number\n");
964 dbfn_write(dbm, name, record, oldlength);
971 printf("field number or d expected\n");
976 if (!verify) continue;
979 /* The "name" q causes an exit */
981 else if (Ustrcmp(name, "q") == 0) return 0;
983 /* Handle a read request, or verify after an update. */
985 spool_directory = argv[1];
986 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE)))
989 if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
991 printf("record %s not found\n", name);
997 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
1001 retry = (dbdata_retry *)record;
1002 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
1003 printf("1 extra data: %d\n", retry->more_errno);
1004 printf("2 first failed: %s\n", print_time(retry->first_failed));
1005 printf("3 last try: %s\n", print_time(retry->last_try));
1006 printf("4 next try: %s\n", print_time(retry->next_try));
1007 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
1011 wait = (dbdata_wait *)record;
1013 printf("Sequence: %d\n", wait->sequence);
1014 if (wait->count > WAIT_NAME_MAX)
1016 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
1017 wait->count, WAIT_NAME_MAX);
1018 wait->count = WAIT_NAME_MAX;
1021 for (i = 1; i <= wait->count; i++)
1023 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1024 value[MESSAGE_ID_LENGTH] = 0;
1025 if (count_bad && value[0] == 0) break;
1026 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1027 Ustrspn(value, "0123456789"
1028 "abcdefghijklmnopqrstuvwxyz"
1029 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1032 printf("\n**** Data corrupted: bad character in message id ****\n");
1033 for (j = 0; j < MESSAGE_ID_LENGTH; j++)
1034 printf("%02x ", value[j]);
1038 printf("%s ", value);
1039 t += MESSAGE_ID_LENGTH;
1048 callout = (dbdata_callout_cache *)record;
1049 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1051 if (oldlength > sizeof(dbdata_callout_cache_address))
1053 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1054 callout->postmaster_result);
1055 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1056 callout->random_result);
1060 case type_ratelimit:
1061 ratelimit = (dbdata_ratelimit *)record;
1062 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1063 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1064 printf("2 sender rate: % .3f\n", ratelimit->rate);
1065 if (Ustrstr(name, "/unique/") != NULL
1066 && oldlength >= sizeof(dbdata_ratelimit_unique))
1068 rate_unique = (dbdata_ratelimit_unique *)record;
1069 printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1070 printf("4 test filter membership\n");
1071 printf("5 add element to filter\n");
1077 /* The database is closed after each request */
1086 #endif /* EXIM_FIXDB */
1091 /*************************************************
1092 * The exim_tidydb main program *
1093 *************************************************/
1096 /* Utility program to tidy the contents of an exim database file. There is one
1099 -t <time> expiry time for old records - default 30 days
1101 For backwards compatibility, an -f option is recognized and ignored. (It used
1102 to request a "full" tidy. This version always does the whole job.) */
1105 typedef struct key_item {
1106 struct key_item *next;
1111 int main(int argc, char **cargv)
1113 struct stat statbuf;
1114 int maxkeep = 30 * 24 * 60 * 60;
1115 int dbdata_type, i, oldest, path_len;
1116 key_item *keychain = NULL;
1120 EXIM_CURSOR *cursor;
1121 uschar **argv = USS cargv;
1125 /* Scan the options */
1127 for (i = 1; i < argc; i++)
1129 if (argv[i][0] != '-') break;
1130 if (Ustrcmp(argv[i], "-f") == 0) continue;
1131 if (Ustrcmp(argv[i], "-t") == 0)
1139 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1140 (void)sscanf(CS s, "%d%n", &value, &count);
1144 case 'w': value *= 7;
1145 case 'd': value *= 24;
1146 case 'h': value *= 60;
1147 case 'm': value *= 60;
1150 default: usage(US"tidydb", US" [-t <time>]");
1155 else usage(US"tidydb", US" [-t <time>]");
1158 /* Adjust argument values and process arguments */
1163 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1165 /* Compute the oldest keep time, verify what we are doing, and open the
1168 oldest = time(NULL) - maxkeep;
1169 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1171 spool_directory = argv[1];
1172 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE)))
1175 /* Prepare for building file names */
1177 sprintf(CS buffer, "%s/input/", argv[1]);
1178 path_len = Ustrlen(buffer);
1181 /* It appears, by experiment, that it is a bad idea to make changes
1182 to the file while scanning it. Pity the man page doesn't warn you about that.
1183 Therefore, we scan and build a list of all the keys. Then we use that to
1184 read the records and possibly update them. */
1186 for (key = dbfn_scan(dbm, TRUE, &cursor);
1188 key = dbfn_scan(dbm, FALSE, &cursor))
1190 key_item *k = store_get(sizeof(key_item) + Ustrlen(key));
1193 Ustrcpy(k->key, key);
1196 /* Now scan the collected keys and operate on the records, resetting
1197 the store each time round. */
1199 reset_point = store_get(0);
1203 dbdata_generic *value;
1205 store_reset(reset_point);
1206 key = keychain->key;
1207 keychain = keychain->next;
1208 value = dbfn_read_with_length(dbm, key, NULL);
1210 /* A continuation record may have been deleted or renamed already, so
1211 non-existence is not serious. */
1213 if (value == NULL) continue;
1215 /* Delete if too old */
1217 if (value->time_stamp < oldest)
1219 printf("deleted %s (too old)\n", key);
1220 dbfn_delete(dbm, key);
1224 /* Do database-specific tidying for wait databases, and message-
1225 specific tidying for the retry database. */
1227 if (dbdata_type == type_wait)
1229 dbdata_wait *wait = (dbdata_wait *)value;
1230 BOOL update = FALSE;
1232 /* Leave corrupt records alone */
1234 if (wait->count > WAIT_NAME_MAX)
1236 printf("**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
1237 key, wait->count, wait->count, WAIT_NAME_MAX);
1241 /* Loop for renamed continuation records. For each message id,
1242 check to see if the message exists, and if not, remove its entry
1243 from the record. Because of the possibility of split input directories,
1244 we must look in both possible places for a -D file. */
1249 int length = wait->count * MESSAGE_ID_LENGTH;
1251 for (offset = length - MESSAGE_ID_LENGTH;
1252 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1254 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1255 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1257 if (Ustat(buffer, &statbuf) != 0)
1259 buffer[path_len] = wait->text[offset+5];
1260 buffer[path_len+1] = '/';
1261 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1262 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1264 if (Ustat(buffer, &statbuf) != 0)
1266 int left = length - offset - MESSAGE_ID_LENGTH;
1267 if (left > 0) Ustrncpy(wait->text + offset,
1268 wait->text + offset + MESSAGE_ID_LENGTH, left);
1270 length -= MESSAGE_ID_LENGTH;
1276 /* If record is empty and the main record, either delete it or rename
1277 the next continuation, repeating if that is also empty. */
1279 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1281 while (wait->count == 0 && wait->sequence > 0)
1284 dbdata_generic *newvalue;
1285 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1286 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1287 if (newvalue != NULL)
1290 wait = (dbdata_wait *)newvalue;
1291 dbfn_delete(dbm, newkey);
1292 printf("renamed %s\n", newkey);
1295 else wait->sequence--;
1298 /* If we have ended up with an empty main record, delete it
1299 and break the loop. Otherwise the new record will be scanned. */
1301 if (wait->count == 0 && wait->sequence == 0)
1303 dbfn_delete(dbm, key);
1304 printf("deleted %s (empty)\n", key);
1310 /* If not an empty main record, break the loop */
1315 /* Re-write the record if required */
1319 printf("updated %s\n", key);
1320 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1321 wait->count * MESSAGE_ID_LENGTH);
1325 /* If a retry record's key ends with a message-id, check that that message
1326 still exists; if not, remove this record. */
1328 else if (dbdata_type == type_retry)
1331 int len = Ustrlen(key);
1333 if (len < MESSAGE_ID_LENGTH + 1) continue;
1334 id = key + len - MESSAGE_ID_LENGTH - 1;
1335 if (*id++ != ':') continue;
1337 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1339 if (i == 6 || i == 13)
1340 { if (id[i] != '-') break; }
1342 { if (!isalnum(id[i])) break; }
1344 if (i < MESSAGE_ID_LENGTH) continue;
1346 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1347 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1349 if (Ustat(buffer, &statbuf) != 0)
1351 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1352 if (Ustat(buffer, &statbuf) != 0)
1354 dbfn_delete(dbm, key);
1355 printf("deleted %s (no message)\n", key);
1362 printf("Tidying complete\n");
1366 #endif /* EXIM_TIDYDB */
1368 /* End of exim_dbutil.c */