1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
6 /* Copyright (c) The Exim Maintainers 2020 */
7 /* See the file NOTICE for conditions of use and distribution. */
10 /* This single source file is used to compile three utility programs for
11 maintaining Exim hints databases.
13 exim_dumpdb dumps out the contents
14 exim_fixdb patches the database (really for Exim maintenance/testing)
15 exim_tidydb removed obsolete data
17 In all cases, the first argument is the name of the spool directory. The second
18 argument is the name of the database file. The available names are:
20 retry: retry delivery information
21 misc: miscellaneous hints data
22 wait-<t>: message waiting information; <t> is a transport name
23 callout: callout verification cache
24 tls: TLS session resumption cache
26 There are a number of common subroutines, followed by three main programs,
27 whose inclusion is controlled by -D on the compilation command. */
33 /* Identifiers for the different database types. */
38 #define type_callout 4
39 #define type_ratelimit 5
43 /* This is used by our cut-down dbfn_open(). */
45 uschar *spool_directory;
48 /******************************************************************************/
49 /* dummies needed by Solaris build */
54 readconf_printtime(int t)
57 string_vformat_trc(gstring * g, const uschar * func, unsigned line,
58 unsigned size_limit, unsigned flags, const char *format, va_list ap)
61 string_sprintf_trc(const char * fmt, const uschar * func, unsigned line, ...)
64 string_format_trc(uschar * buf, int len, const uschar * func, unsigned line,
65 const char * fmt, ...)
68 struct global_flags f;
69 unsigned int log_selector[1];
71 BOOL split_spool_directory;
72 /******************************************************************************/
75 /*************************************************
76 * Berkeley DB error callback *
77 *************************************************/
79 /* For Berkeley DB >= 2, we can define a function to be called in case of DB
80 errors. This should help with debugging strange DB problems, e.g. getting "File
81 exists" when you try to open a db file. The API changed at release 4.3. */
83 #if defined(USE_DB) && defined(DB_VERSION_STRING)
85 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
86 dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
90 dbfn_bdb_error_callback(const char *pfx, char *msg)
94 printf("Berkeley DB error: %s\n", msg);
100 /*************************************************
102 *************************************************/
104 SIGNAL_BOOL sigalrm_seen;
107 sigalrm_handler(int sig)
109 sig = sig; /* Keep picky compilers happy */
115 /*************************************************
116 * Output usage message and exit *
117 *************************************************/
120 usage(uschar *name, uschar *options)
122 printf("Usage: exim_%s%s <spool-directory> <database-name>\n", name, options);
123 printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit | tls\n");
129 /*************************************************
130 * Sort out the command arguments *
131 *************************************************/
133 /* This function checks that there are exactly 2 arguments, and checks the
134 second of them to be sure it is a known database name. */
137 check_args(int argc, uschar **argv, uschar *name, uschar *options)
141 if (Ustrcmp(argv[2], "retry") == 0) return type_retry;
142 if (Ustrcmp(argv[2], "misc") == 0) return type_misc;
143 if (Ustrncmp(argv[2], "wait-", 5) == 0) return type_wait;
144 if (Ustrcmp(argv[2], "callout") == 0) return type_callout;
145 if (Ustrcmp(argv[2], "ratelimit") == 0) return type_ratelimit;
146 if (Ustrcmp(argv[2], "tls") == 0) return type_tls;
148 usage(name, options);
149 return -1; /* Never obeyed */
154 /*************************************************
155 * Handle attempts to write the log *
156 *************************************************/
158 /* The message gets written to stderr when log_write() is called from a
159 utility. The message always gets '\n' added on the end of it. These calls come
160 from modules such as store.c when things go drastically wrong (e.g. malloc()
161 failing). In normal use they won't get obeyed.
164 selector not relevant when running a utility
165 flags not relevant when running a utility
166 format a printf() format
167 ... arguments for format
173 log_write(unsigned int selector, int flags, const char *format, ...)
176 va_start(ap, format);
177 vfprintf(stderr, format, ap);
178 fprintf(stderr, "\n");
180 selector = selector; /* Keep picky compilers happy */
186 /*************************************************
187 * Format a time value for printing *
188 *************************************************/
190 static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss ")];
195 struct tm *tmstr = localtime(&t);
196 Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
202 /*************************************************
203 * Format a cache value for printing *
204 *************************************************/
207 print_cache(int value)
209 return (value == ccache_accept)? US"accept" :
210 (value == ccache_reject)? US"reject" :
216 /*************************************************
218 *************************************************/
225 time_t now = time(NULL);
226 struct tm *tm = localtime(&now);
231 for (uschar * t = s + Ustrlen(s) - 1; t >= s; t--)
233 if (*t == ':') continue;
234 if (!isdigit((uschar)*t)) return -1;
239 if (!isdigit((uschar)*t)) return -1;
240 value = value + (*t - '0')*10;
245 case 0: tm->tm_min = value; break;
246 case 1: tm->tm_hour = value; break;
247 case 2: tm->tm_mday = value; break;
248 case 3: tm->tm_mon = value - 1; break;
249 case 4: tm->tm_year = (value < 90)? value + 100 : value; break;
256 #endif /* EXIM_FIXDB */
260 /*************************************************
261 * Open and lock a database file *
262 *************************************************/
264 /* This is a cut-down version from the function in dbfn.h that Exim itself
265 uses. We assume the database exists, and therefore give up if we cannot open
269 name The single-component name of one of Exim's database files.
270 flags O_RDONLY or O_RDWR
271 dbblock Points to an open_db block to be filled in.
275 Returns: NULL if the open failed, or the locking failed.
276 On success, dbblock is returned. This contains the dbm pointer and
277 the fd of the locked lock file.
281 dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof, BOOL panic)
284 struct flock lock_data;
285 BOOL read_only = flags == O_RDONLY;
286 uschar * dirname, * filename;
288 /* The first thing to do is to open a separate file on which to lock. This
289 ensures that Exim has exclusive use of the database before it even tries to
290 open it. If there is a database, there should be a lock file in existence. */
292 #ifdef COMPILE_UTILITY
293 if ( asprintf(CSS &dirname, "%s/db", spool_directory) < 0
294 || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
297 dirname = string_sprintf("%s/db", spool_directory);
298 filename = string_sprintf("%s/%s.lockfile", dirname, name);
301 dbblock->lockfd = Uopen(filename, flags, 0);
302 if (dbblock->lockfd < 0)
304 printf("** Failed to open database lock file %s: %s\n", filename,
309 /* Now we must get a lock on the opened lock file; do this with a blocking
310 lock that times out. */
312 lock_data.l_type = read_only ? F_RDLCK : F_WRLCK;
313 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
315 sigalrm_seen = FALSE;
316 os_non_restarting_signal(SIGALRM, sigalrm_handler);
317 ALARM(EXIMDB_LOCK_TIMEOUT);
318 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
321 if (sigalrm_seen) errno = ETIMEDOUT;
324 printf("** Failed to get %s lock for %s: %s",
325 flags & O_WRONLY ? "write" : "read",
327 errno == ETIMEDOUT ? "timed out" : strerror(errno));
328 (void)close(dbblock->lockfd);
332 /* At this point we have an opened and locked separate lock file, that is,
333 exclusive access to the database, so we can go ahead and open it. */
335 #ifdef COMPILE_UTILITY
336 if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
338 filename = string_sprintf("%s/%s", dirname, name);
340 EXIM_DBOPEN(filename, dirname, flags, 0, &dbblock->dbptr);
344 printf("** Failed to open DBM file %s for %s:\n %s%s\n", filename,
345 read_only? "reading" : "writing", strerror(errno),
347 " (or Berkeley DB error while opening)"
352 (void)close(dbblock->lockfd);
362 /*************************************************
363 * Unlock and close a database file *
364 *************************************************/
366 /* Closing a file automatically unlocks it, so after closing the database, just
369 Argument: a pointer to an open database block
374 dbfn_close(open_db *dbblock)
376 EXIM_DBCLOSE(dbblock->dbptr);
377 (void)close(dbblock->lockfd);
383 /*************************************************
384 * Read from database file *
385 *************************************************/
387 /* Passing back the pointer unchanged is useless, because there is no guarantee
388 of alignment. Since all the records used by Exim need to be properly aligned to
389 pick out the timestamps, etc., do the copying centrally here.
392 dbblock a pointer to an open database block
393 key the key of the record to be read
394 length where to put the length (or NULL if length not wanted). Includes overhead.
396 Returns: a pointer to the retrieved record, or
397 NULL if the record is not found
401 dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
404 EXIM_DATUM key_datum, result_datum;
405 int klen = Ustrlen(key) + 1;
406 uschar * key_copy = store_get(klen, is_tainted(key));
408 memcpy(key_copy, key, klen);
410 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
411 EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */
412 EXIM_DATUM_DATA(key_datum) = CS key_copy;
413 EXIM_DATUM_SIZE(key_datum) = klen;
415 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
417 /* Assume for now that anything stored could have been tainted. Properly
418 we should store the taint status along with the data. */
420 yield = store_get(EXIM_DATUM_SIZE(result_datum), TRUE);
421 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
422 if (length) *length = EXIM_DATUM_SIZE(result_datum);
424 EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */
430 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
432 /*************************************************
433 * Write to database file *
434 *************************************************/
438 dbblock a pointer to an open database block
439 key the key of the record to be written
440 ptr a pointer to the record to be written
441 length the length of the record to be written
443 Returns: the yield of the underlying dbm or db "write" function. If this
444 is dbm, the value is zero for OK.
448 dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
450 EXIM_DATUM key_datum, value_datum;
451 dbdata_generic *gptr = (dbdata_generic *)ptr;
452 int klen = Ustrlen(key) + 1;
453 uschar * key_copy = store_get(klen, is_tainted(key));
455 memcpy(key_copy, key, klen);
456 gptr->time_stamp = time(NULL);
458 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
459 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
460 EXIM_DATUM_DATA(key_datum) = CS key_copy;
461 EXIM_DATUM_SIZE(key_datum) = klen;
462 EXIM_DATUM_DATA(value_datum) = CS ptr;
463 EXIM_DATUM_SIZE(value_datum) = length;
464 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
469 /*************************************************
470 * Delete record from database file *
471 *************************************************/
475 dbblock a pointer to an open database block
476 key the key of the record to be deleted
478 Returns: the yield of the underlying dbm or db "delete" function.
482 dbfn_delete(open_db *dbblock, const uschar *key)
484 int klen = Ustrlen(key) + 1;
485 uschar * key_copy = store_get(klen, is_tainted(key));
487 memcpy(key_copy, key, klen);
488 EXIM_DATUM key_datum;
489 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
490 EXIM_DATUM_DATA(key_datum) = CS key_copy;
491 EXIM_DATUM_SIZE(key_datum) = klen;
492 return EXIM_DBDEL(dbblock->dbptr, key_datum);
495 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
499 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
500 /*************************************************
501 * Scan the keys of a database file *
502 *************************************************/
506 dbblock a pointer to an open database block
507 start TRUE if starting a new scan
508 FALSE if continuing with the current scan
509 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
510 that use the notion of a cursor
512 Returns: the next record from the file, or
513 NULL if there are no more
517 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
519 EXIM_DATUM key_datum, value_datum;
521 value_datum = value_datum; /* dummy; not all db libraries use this */
523 /* Some dbm require an initialization */
525 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
527 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
528 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
530 yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
531 US EXIM_DATUM_DATA(key_datum) : NULL;
533 /* Some dbm require a termination */
535 if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
538 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
543 /*************************************************
544 * The exim_dumpdb main program *
545 *************************************************/
548 main(int argc, char **cargv)
555 uschar **argv = USS cargv;
556 uschar keybuffer[1024];
558 /* Check the arguments, and open the database */
560 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
561 spool_directory = argv[1];
562 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
565 /* Scan the file, formatting the information for each entry. Note
566 that data is returned in a malloc'ed block, in order that it be
567 correctly aligned. */
569 for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
571 key = dbfn_scan(dbm, FALSE, &cursor))
575 dbdata_callout_cache *callout;
576 dbdata_ratelimit *ratelimit;
577 dbdata_ratelimit_unique *rate_unique;
578 dbdata_tls_session *session;
582 uschar name[MESSAGE_ID_LENGTH + 1];
584 rmark reset_point = store_mark();
586 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
587 which might change. */
589 if (Ustrlen(key) > sizeof(keybuffer) - 1)
591 printf("**** Overlong key encountered: %s\n", key);
594 Ustrcpy(keybuffer, key);
596 if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
597 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
598 "was not found in the file - something is wrong!\n",
602 /* Note: don't use print_time more than once in one statement, since
603 it uses a single buffer. */
608 retry = (dbdata_retry *)value;
609 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
610 retry->more_errno, retry->text,
611 print_time(retry->first_failed));
612 printf("%s ", print_time(retry->last_try));
613 printf("%s %s\n", print_time(retry->next_try),
614 (retry->expired)? "*" : "");
618 wait = (dbdata_wait *)value;
619 printf("%s ", keybuffer);
621 name[MESSAGE_ID_LENGTH] = 0;
623 /* Leave corrupt records alone */
624 if (wait->count > WAIT_NAME_MAX)
627 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
628 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
629 wait->count = WAIT_NAME_MAX;
630 yield = count_bad = 1;
632 for (int i = 1; i <= wait->count; i++)
634 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
635 if (count_bad && name[0] == 0) break;
636 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
637 Ustrspn(name, "0123456789"
638 "abcdefghijklmnopqrstuvwxyz"
639 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
642 "**** Data for %s corrupted: bad character in message id\n",
644 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
645 fprintf(stderr, "%02x ", name[j]);
646 fprintf(stderr, "\n");
651 t += MESSAGE_ID_LENGTH;
657 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
662 callout = (dbdata_callout_cache *)value;
664 /* New-style address record */
666 if (length == sizeof(dbdata_callout_cache_address))
668 printf("%s %s callout=%s\n",
669 print_time(((dbdata_generic *)value)->time_stamp),
671 print_cache(callout->result));
674 /* New-style domain record */
676 else if (length == sizeof(dbdata_callout_cache))
678 printf("%s %s callout=%s postmaster=%s",
679 print_time(((dbdata_generic *)value)->time_stamp),
681 print_cache(callout->result),
682 print_cache(callout->postmaster_result));
683 if (callout->postmaster_result != ccache_unknown)
684 printf(" (%s)", print_time(callout->postmaster_stamp));
685 printf(" random=%s", print_cache(callout->random_result));
686 if (callout->random_result != ccache_unknown)
687 printf(" (%s)", print_time(callout->random_stamp));
694 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
696 ratelimit = (dbdata_ratelimit *)value;
697 rate_unique = (dbdata_ratelimit_unique *)value;
698 printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
699 print_time(ratelimit->time_stamp),
700 ratelimit->time_usec, ratelimit->rate,
701 print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
706 ratelimit = (dbdata_ratelimit *)value;
707 printf("%s.%06d rate: %10.3f key: %s\n",
708 print_time(ratelimit->time_stamp),
709 ratelimit->time_usec, ratelimit->rate,
715 session = (dbdata_tls_session *)value;
716 printf(" %s %.*s\n", keybuffer, length, session->session);
720 store_reset(reset_point);
727 #endif /* EXIM_DUMPDB */
733 /*************************************************
734 * The exim_fixdb main program *
735 *************************************************/
737 /* In order not to hold the database lock any longer than is necessary, each
738 operation on the database uses a separate open/close call. This is expensive,
739 but then using this utility is not expected to be very common. Its main use is
740 to provide a way of patching up hints databases in order to run tests.
745 This causes the data from the given record to be displayed, or "not found"
746 to be output. Note that in the retry database, destination names are
747 preceded by R: or T: for router or transport retry info.
750 This causes the given record to be deleted or "not found" to be output.
752 (3) <record name> <field number> <value>
753 This sets the given value into the given field, identified by a number
754 which is output by the display command. Not all types of record can
758 This exits from exim_fixdb.
760 If the record name is omitted from (2) or (3), the previously used record name
764 int main(int argc, char **cargv)
767 uschar **argv = USS cargv;
772 name[0] = 0; /* No name set */
774 /* Sort out the database type, verify what we are working on and then process
777 dbdata_type = check_args(argc, argv, US"fixdb", US"");
778 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
780 for(; (reset_point = store_mark()); store_reset(reset_point))
787 dbdata_callout_cache *callout;
788 dbdata_ratelimit *ratelimit;
789 dbdata_ratelimit_unique *rate_unique;
790 dbdata_tls_session *session;
793 uschar field[256], value[256];
796 if (Ufgets(buffer, 256, stdin) == NULL) break;
798 buffer[Ustrlen(buffer)-1] = 0;
799 field[0] = value[0] = 0;
801 /* If the buffer contains just one digit, or just consists of "d", use the
802 previous name for an update. */
804 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
805 || Ustrcmp(buffer, "d") == 0)
809 printf("No previous record name is set\n");
812 (void)sscanf(CS buffer, "%s %s", field, value);
817 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
820 /* Handle an update request */
825 spool_directory = argv[1];
827 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
830 if (Ustrcmp(field, "d") == 0)
832 if (value[0] != 0) printf("unexpected value after \"d\"\n");
833 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
834 "not found" : "deleted");
839 else if (isdigit((uschar)field[0]))
841 int fieldno = Uatoi(field);
844 printf("value missing\n");
850 record = dbfn_read_with_length(dbm, name, &oldlength);
851 if (record == NULL) printf("not found\n"); else
854 /*int length = 0; Stops compiler warning */
859 retry = (dbdata_retry *)record;
860 /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
864 case 0: retry->basic_errno = Uatoi(value);
866 case 1: retry->more_errno = Uatoi(value);
868 case 2: if ((tt = read_time(value)) > 0) retry->first_failed = tt;
869 else printf("bad time value\n");
871 case 3: if ((tt = read_time(value)) > 0) retry->last_try = tt;
872 else printf("bad time value\n");
874 case 4: if ((tt = read_time(value)) > 0) retry->next_try = tt;
875 else printf("bad time value\n");
877 case 5: if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
878 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
879 else printf("\"yes\" or \"no\" expected=n");
881 default: printf("unknown field number\n");
888 printf("Can't change contents of wait database record\n");
892 printf("Can't change contents of misc database record\n");
896 callout = (dbdata_callout_cache *)record;
897 /* length = sizeof(dbdata_callout_cache); */
900 case 0: callout->result = Uatoi(value);
902 case 1: callout->postmaster_result = Uatoi(value);
904 case 2: callout->random_result = Uatoi(value);
906 default: printf("unknown field number\n");
913 ratelimit = (dbdata_ratelimit *)record;
916 case 0: if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
917 else printf("bad time value\n");
919 case 1: ratelimit->time_usec = Uatoi(value);
921 case 2: ratelimit->rate = Ustrtod(value, NULL);
923 case 3: if (Ustrstr(name, "/unique/") != NULL
924 && oldlength >= sizeof(dbdata_ratelimit_unique))
926 rate_unique = (dbdata_ratelimit_unique *)record;
927 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
928 else printf("bad time value\n");
931 /* else fall through */
933 case 5: if (Ustrstr(name, "/unique/") != NULL
934 && oldlength >= sizeof(dbdata_ratelimit_unique))
942 md5_end(&md5info, value, Ustrlen(value), md5sum);
943 hash = md5sum[0] << 0 | md5sum[1] << 8
944 | md5sum[2] << 16 | md5sum[3] << 24;
945 hinc = md5sum[4] << 0 | md5sum[5] << 8
946 | md5sum[6] << 16 | md5sum[7] << 24;
947 rate_unique = (dbdata_ratelimit_unique *)record;
949 for (unsigned n = 0; n < 8; n++, hash += hinc)
951 int bit = 1 << (hash % 8);
952 int byte = (hash / 8) % rate_unique->bloom_size;
953 if ((rate_unique->bloom[byte] & bit) == 0)
956 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
960 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
963 /* else fall through */
964 default: printf("unknown field number\n");
971 printf("Can't change contents of tls database record\n");
975 dbfn_write(dbm, name, record, oldlength);
982 printf("field number or d expected\n");
987 if (!verify) continue;
990 /* The "name" q causes an exit */
992 else if (Ustrcmp(name, "q") == 0) return 0;
994 /* Handle a read request, or verify after an update. */
996 spool_directory = argv[1];
997 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
1000 if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
1002 printf("record %s not found\n", name);
1008 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
1012 retry = (dbdata_retry *)record;
1013 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
1014 printf("1 extra data: %d\n", retry->more_errno);
1015 printf("2 first failed: %s\n", print_time(retry->first_failed));
1016 printf("3 last try: %s\n", print_time(retry->last_try));
1017 printf("4 next try: %s\n", print_time(retry->next_try));
1018 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
1022 wait = (dbdata_wait *)record;
1024 printf("Sequence: %d\n", wait->sequence);
1025 if (wait->count > WAIT_NAME_MAX)
1027 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
1028 wait->count, WAIT_NAME_MAX);
1029 wait->count = WAIT_NAME_MAX;
1032 for (int i = 1; i <= wait->count; i++)
1034 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1035 value[MESSAGE_ID_LENGTH] = 0;
1036 if (count_bad && value[0] == 0) break;
1037 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1038 Ustrspn(value, "0123456789"
1039 "abcdefghijklmnopqrstuvwxyz"
1040 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1042 printf("\n**** Data corrupted: bad character in message id ****\n");
1043 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
1044 printf("%02x ", value[j]);
1048 printf("%s ", value);
1049 t += MESSAGE_ID_LENGTH;
1058 callout = (dbdata_callout_cache *)record;
1059 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1061 if (oldlength > sizeof(dbdata_callout_cache_address))
1063 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1064 callout->postmaster_result);
1065 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1066 callout->random_result);
1070 case type_ratelimit:
1071 ratelimit = (dbdata_ratelimit *)record;
1072 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1073 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1074 printf("2 sender rate: % .3f\n", ratelimit->rate);
1075 if (Ustrstr(name, "/unique/") != NULL
1076 && oldlength >= sizeof(dbdata_ratelimit_unique))
1078 rate_unique = (dbdata_ratelimit_unique *)record;
1079 printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1080 printf("4 test filter membership\n");
1081 printf("5 add element to filter\n");
1086 session = (dbdata_tls_session *)value;
1087 printf("0 time stamp: %s\n", print_time(session->time_stamp));
1088 printf("1 session: .%s\n", session->session);
1093 /* The database is closed after each request */
1102 #endif /* EXIM_FIXDB */
1107 /*************************************************
1108 * The exim_tidydb main program *
1109 *************************************************/
1112 /* Utility program to tidy the contents of an exim database file. There is one
1115 -t <time> expiry time for old records - default 30 days
1117 For backwards compatibility, an -f option is recognized and ignored. (It used
1118 to request a "full" tidy. This version always does the whole job.) */
1121 typedef struct key_item {
1122 struct key_item *next;
1127 int main(int argc, char **cargv)
1129 struct stat statbuf;
1130 int maxkeep = 30 * 24 * 60 * 60;
1131 int dbdata_type, i, oldest, path_len;
1132 key_item *keychain = NULL;
1136 EXIM_CURSOR *cursor;
1137 uschar **argv = USS cargv;
1141 /* Scan the options */
1143 for (i = 1; i < argc; i++)
1145 if (argv[i][0] != '-') break;
1146 if (Ustrcmp(argv[i], "-f") == 0) continue;
1147 if (Ustrcmp(argv[i], "-t") == 0)
1155 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1156 (void)sscanf(CS s, "%d%n", &value, &count);
1160 case 'w': value *= 7;
1161 case 'd': value *= 24;
1162 case 'h': value *= 60;
1163 case 'm': value *= 60;
1166 default: usage(US"tidydb", US" [-t <time>]");
1171 else usage(US"tidydb", US" [-t <time>]");
1174 /* Adjust argument values and process arguments */
1179 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1181 /* Compute the oldest keep time, verify what we are doing, and open the
1184 oldest = time(NULL) - maxkeep;
1185 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1187 spool_directory = argv[1];
1188 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
1191 /* Prepare for building file names */
1193 sprintf(CS buffer, "%s/input/", argv[1]);
1194 path_len = Ustrlen(buffer);
1197 /* It appears, by experiment, that it is a bad idea to make changes
1198 to the file while scanning it. Pity the man page doesn't warn you about that.
1199 Therefore, we scan and build a list of all the keys. Then we use that to
1200 read the records and possibly update them. */
1202 for (key = dbfn_scan(dbm, TRUE, &cursor);
1204 key = dbfn_scan(dbm, FALSE, &cursor))
1206 key_item *k = store_get(sizeof(key_item) + Ustrlen(key), is_tainted(key));
1209 Ustrcpy(k->key, key);
1212 /* Now scan the collected keys and operate on the records, resetting
1213 the store each time round. */
1215 for (; keychain && (reset_point = store_mark()); store_reset(reset_point))
1217 dbdata_generic *value;
1219 key = keychain->key;
1220 keychain = keychain->next;
1221 value = dbfn_read_with_length(dbm, key, NULL);
1223 /* A continuation record may have been deleted or renamed already, so
1224 non-existence is not serious. */
1226 if (!value) continue;
1228 /* Delete if too old */
1230 if (value->time_stamp < oldest)
1232 printf("deleted %s (too old)\n", key);
1233 dbfn_delete(dbm, key);
1237 /* Do database-specific tidying for wait databases, and message-
1238 specific tidying for the retry database. */
1240 if (dbdata_type == type_wait)
1242 dbdata_wait *wait = (dbdata_wait *)value;
1243 BOOL update = FALSE;
1245 /* Leave corrupt records alone */
1247 if (wait->time_stamp > time(NULL))
1249 printf("**** Data for '%s' corrupted\n time in future: %s\n",
1250 key, print_time(((dbdata_generic *)value)->time_stamp));
1253 if (wait->count > WAIT_NAME_MAX)
1255 printf("**** Data for '%s' corrupted\n count=%d=0x%x max=%d\n",
1256 key, wait->count, wait->count, WAIT_NAME_MAX);
1259 if (wait->sequence > WAIT_CONT_MAX)
1261 printf("**** Data for '%s' corrupted\n sequence=%d=0x%x max=%d\n",
1262 key, wait->sequence, wait->sequence, WAIT_CONT_MAX);
1266 /* Record over 1 year old; just remove it */
1268 if (wait->time_stamp < time(NULL) - 365*24*60*60)
1270 dbfn_delete(dbm, key);
1271 printf("deleted %s (too old)\n", key);
1275 /* Loop for renamed continuation records. For each message id,
1276 check to see if the message exists, and if not, remove its entry
1277 from the record. Because of the possibility of split input directories,
1278 we must look in both possible places for a -D file. */
1282 int length = wait->count * MESSAGE_ID_LENGTH;
1284 for (int offset = length - MESSAGE_ID_LENGTH;
1285 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1287 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1288 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1290 if (Ustat(buffer, &statbuf) != 0)
1292 buffer[path_len] = wait->text[offset+5];
1293 buffer[path_len+1] = '/';
1294 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1295 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1297 if (Ustat(buffer, &statbuf) != 0)
1299 int left = length - offset - MESSAGE_ID_LENGTH;
1300 if (left > 0) Ustrncpy(wait->text + offset,
1301 wait->text + offset + MESSAGE_ID_LENGTH, left);
1303 length -= MESSAGE_ID_LENGTH;
1309 /* If record is empty and the main record, either delete it or rename
1310 the next continuation, repeating if that is also empty. */
1312 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1314 while (wait->count == 0 && wait->sequence > 0)
1317 dbdata_generic *newvalue;
1318 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1319 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1320 if (newvalue != NULL)
1323 wait = (dbdata_wait *)newvalue;
1324 dbfn_delete(dbm, newkey);
1325 printf("renamed %s\n", newkey);
1328 else wait->sequence--;
1331 /* If we have ended up with an empty main record, delete it
1332 and break the loop. Otherwise the new record will be scanned. */
1334 if (wait->count == 0 && wait->sequence == 0)
1336 dbfn_delete(dbm, key);
1337 printf("deleted %s (empty)\n", key);
1343 /* If not an empty main record, break the loop */
1348 /* Re-write the record if required */
1352 printf("updated %s\n", key);
1353 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1354 wait->count * MESSAGE_ID_LENGTH);
1358 /* If a retry record's key ends with a message-id, check that that message
1359 still exists; if not, remove this record. */
1361 else if (dbdata_type == type_retry)
1364 int len = Ustrlen(key);
1366 if (len < MESSAGE_ID_LENGTH + 1) continue;
1367 id = key + len - MESSAGE_ID_LENGTH - 1;
1368 if (*id++ != ':') continue;
1370 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1371 if (i == 6 || i == 13)
1372 { if (id[i] != '-') break; }
1374 { if (!isalnum(id[i])) break; }
1375 if (i < MESSAGE_ID_LENGTH) continue;
1377 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1378 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1380 if (Ustat(buffer, &statbuf) != 0)
1382 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1383 if (Ustat(buffer, &statbuf) != 0)
1385 dbfn_delete(dbm, key);
1386 printf("deleted %s (no message)\n", key);
1393 printf("Tidying complete\n");
1397 #endif /* EXIM_TIDYDB */
1399 /* End of exim_dbutil.c */