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 ratelimit: ACL 'ratelimit' condition
25 tls: TLS session resumption cache
26 seen: ACL 'seen' condition
28 There are a number of common subroutines, followed by three main programs,
29 whose inclusion is controlled by -D on the compilation command. */
35 /* Identifiers for the different database types. */
40 #define type_callout 4
41 #define type_ratelimit 5
46 /* This is used by our cut-down dbfn_open(). */
48 uschar *spool_directory;
51 /******************************************************************************/
52 /* dummies needed by Solaris build */
57 readconf_printtime(int t)
60 string_vformat_trc(gstring * g, const uschar * func, unsigned line,
61 unsigned size_limit, unsigned flags, const char *format, va_list ap)
64 string_sprintf_trc(const char * fmt, const uschar * func, unsigned line, ...)
67 string_format_trc(uschar * buf, int len, const uschar * func, unsigned line,
68 const char * fmt, ...)
71 struct global_flags f;
72 unsigned int log_selector[1];
74 BOOL split_spool_directory;
77 /* These introduced by the taintwarn handling */
78 #ifdef ALLOW_INSECURE_TAINTED_DATA
79 BOOL allow_insecure_tainted_data;
82 /******************************************************************************/
85 /*************************************************
86 * Berkeley DB error callback *
87 *************************************************/
89 /* For Berkeley DB >= 2, we can define a function to be called in case of DB
90 errors. This should help with debugging strange DB problems, e.g. getting "File
91 exists" when you try to open a db file. The API changed at release 4.3. */
93 #if defined(USE_DB) && defined(DB_VERSION_STRING)
95 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
96 dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
100 dbfn_bdb_error_callback(const char *pfx, char *msg)
104 printf("Berkeley DB error: %s\n", msg);
110 /*************************************************
112 *************************************************/
114 SIGNAL_BOOL sigalrm_seen;
117 sigalrm_handler(int sig)
124 /*************************************************
125 * Output usage message and exit *
126 *************************************************/
129 usage(uschar *name, uschar *options)
131 printf("Usage: exim_%s%s <spool-directory> <database-name>\n", name, options);
132 printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit | tls | seen\n");
138 /*************************************************
139 * Sort out the command arguments *
140 *************************************************/
142 /* This function checks that there are exactly 2 arguments, and checks the
143 second of them to be sure it is a known database name. */
146 check_args(int argc, uschar **argv, uschar *name, uschar *options)
150 if (Ustrcmp(argv[2], "retry") == 0) return type_retry;
151 if (Ustrcmp(argv[2], "misc") == 0) return type_misc;
152 if (Ustrncmp(argv[2], "wait-", 5) == 0) return type_wait;
153 if (Ustrcmp(argv[2], "callout") == 0) return type_callout;
154 if (Ustrcmp(argv[2], "ratelimit") == 0) return type_ratelimit;
155 if (Ustrcmp(argv[2], "tls") == 0) return type_tls;
156 if (Ustrcmp(argv[2], "seen") == 0) return type_seen;
158 usage(name, options);
159 return -1; /* Never obeyed */
164 /*************************************************
165 * Handle attempts to write the log *
166 *************************************************/
168 /* The message gets written to stderr when log_write() is called from a
169 utility. The message always gets '\n' added on the end of it. These calls come
170 from modules such as store.c when things go drastically wrong (e.g. malloc()
171 failing). In normal use they won't get obeyed.
174 selector not relevant when running a utility
175 flags not relevant when running a utility
176 format a printf() format
177 ... arguments for format
183 log_write(unsigned int selector, int flags, const char *format, ...)
186 va_start(ap, format);
187 vfprintf(stderr, format, ap);
188 fprintf(stderr, "\n");
194 /*************************************************
195 * Format a time value for printing *
196 *************************************************/
198 static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss ")];
203 struct tm *tmstr = localtime(&t);
204 Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
210 /*************************************************
211 * Format a cache value for printing *
212 *************************************************/
215 print_cache(int value)
217 return (value == ccache_accept)? US"accept" :
218 (value == ccache_reject)? US"reject" :
224 /*************************************************
226 *************************************************/
233 time_t now = time(NULL);
234 struct tm *tm = localtime(&now);
239 for (uschar * t = s + Ustrlen(s) - 1; t >= s; t--)
241 if (*t == ':') continue;
242 if (!isdigit((uschar)*t)) return -1;
247 if (!isdigit((uschar)*t)) return -1;
248 value = value + (*t - '0')*10;
253 case 0: tm->tm_min = value; break;
254 case 1: tm->tm_hour = value; break;
255 case 2: tm->tm_mday = value; break;
256 case 3: tm->tm_mon = value - 1; break;
257 case 4: tm->tm_year = (value < 90)? value + 100 : value; break;
264 #endif /* EXIM_FIXDB */
268 /*************************************************
269 * Open and lock a database file *
270 *************************************************/
272 /* This is a cut-down version from the function in dbfn.h that Exim itself
273 uses. We assume the database exists, and therefore give up if we cannot open
277 name The single-component name of one of Exim's database files.
278 flags O_RDONLY or O_RDWR
279 dbblock Points to an open_db block to be filled in.
283 Returns: NULL if the open failed, or the locking failed.
284 On success, dbblock is returned. This contains the dbm pointer and
285 the fd of the locked lock file.
289 dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof, BOOL panic)
292 struct flock lock_data;
293 BOOL read_only = flags == O_RDONLY;
294 uschar * dirname, * filename;
296 /* The first thing to do is to open a separate file on which to lock. This
297 ensures that Exim has exclusive use of the database before it even tries to
298 open it. If there is a database, there should be a lock file in existence. */
300 #ifdef COMPILE_UTILITY
301 if ( asprintf(CSS &dirname, "%s/db", spool_directory) < 0
302 || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
305 dirname = string_sprintf("%s/db", spool_directory);
306 filename = string_sprintf("%s/%s.lockfile", dirname, name);
309 dbblock->lockfd = Uopen(filename, flags, 0);
310 if (dbblock->lockfd < 0)
312 printf("** Failed to open database lock file %s: %s\n", filename,
317 /* Now we must get a lock on the opened lock file; do this with a blocking
318 lock that times out. */
320 lock_data.l_type = read_only ? F_RDLCK : F_WRLCK;
321 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
323 sigalrm_seen = FALSE;
324 os_non_restarting_signal(SIGALRM, sigalrm_handler);
325 ALARM(EXIMDB_LOCK_TIMEOUT);
326 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
329 if (sigalrm_seen) errno = ETIMEDOUT;
332 printf("** Failed to get %s lock for %s: %s",
333 flags & O_WRONLY ? "write" : "read",
335 errno == ETIMEDOUT ? "timed out" : strerror(errno));
336 (void)close(dbblock->lockfd);
340 /* At this point we have an opened and locked separate lock file, that is,
341 exclusive access to the database, so we can go ahead and open it. */
343 #ifdef COMPILE_UTILITY
344 if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
346 filename = string_sprintf("%s/%s", dirname, name);
348 EXIM_DBOPEN(filename, dirname, flags, 0, &dbblock->dbptr);
352 printf("** Failed to open DBM file %s for %s:\n %s%s\n", filename,
353 read_only? "reading" : "writing", strerror(errno),
355 " (or Berkeley DB error while opening)"
360 (void)close(dbblock->lockfd);
370 /*************************************************
371 * Unlock and close a database file *
372 *************************************************/
374 /* Closing a file automatically unlocks it, so after closing the database, just
377 Argument: a pointer to an open database block
382 dbfn_close(open_db *dbblock)
384 EXIM_DBCLOSE(dbblock->dbptr);
385 (void)close(dbblock->lockfd);
391 /*************************************************
392 * Read from database file *
393 *************************************************/
395 /* Passing back the pointer unchanged is useless, because there is no guarantee
396 of alignment. Since all the records used by Exim need to be properly aligned to
397 pick out the timestamps, etc., do the copying centrally here.
400 dbblock a pointer to an open database block
401 key the key of the record to be read
402 length where to put the length (or NULL if length not wanted). Includes overhead.
404 Returns: a pointer to the retrieved record, or
405 NULL if the record is not found
409 dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
412 EXIM_DATUM key_datum, result_datum;
413 int klen = Ustrlen(key) + 1;
414 uschar * key_copy = store_get(klen, is_tainted(key));
416 memcpy(key_copy, key, klen);
418 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
419 EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */
420 EXIM_DATUM_DATA(key_datum) = CS key_copy;
421 EXIM_DATUM_SIZE(key_datum) = klen;
423 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
425 /* Assume for now that anything stored could have been tainted. Properly
426 we should store the taint status along with the data. */
428 yield = store_get(EXIM_DATUM_SIZE(result_datum), TRUE);
429 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
430 if (length) *length = EXIM_DATUM_SIZE(result_datum);
432 EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */
438 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
440 /*************************************************
441 * Write to database file *
442 *************************************************/
446 dbblock a pointer to an open database block
447 key the key of the record to be written
448 ptr a pointer to the record to be written
449 length the length of the record to be written
451 Returns: the yield of the underlying dbm or db "write" function. If this
452 is dbm, the value is zero for OK.
456 dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
458 EXIM_DATUM key_datum, value_datum;
459 dbdata_generic *gptr = (dbdata_generic *)ptr;
460 int klen = Ustrlen(key) + 1;
461 uschar * key_copy = store_get(klen, is_tainted(key));
463 memcpy(key_copy, key, klen);
464 gptr->time_stamp = time(NULL);
466 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
467 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
468 EXIM_DATUM_DATA(key_datum) = CS key_copy;
469 EXIM_DATUM_SIZE(key_datum) = klen;
470 EXIM_DATUM_DATA(value_datum) = CS ptr;
471 EXIM_DATUM_SIZE(value_datum) = length;
472 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
477 /*************************************************
478 * Delete record from database file *
479 *************************************************/
483 dbblock a pointer to an open database block
484 key the key of the record to be deleted
486 Returns: the yield of the underlying dbm or db "delete" function.
490 dbfn_delete(open_db *dbblock, const uschar *key)
492 int klen = Ustrlen(key) + 1;
493 uschar * key_copy = store_get(klen, is_tainted(key));
495 memcpy(key_copy, key, klen);
496 EXIM_DATUM key_datum;
497 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
498 EXIM_DATUM_DATA(key_datum) = CS key_copy;
499 EXIM_DATUM_SIZE(key_datum) = klen;
500 return EXIM_DBDEL(dbblock->dbptr, key_datum);
503 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
507 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
508 /*************************************************
509 * Scan the keys of a database file *
510 *************************************************/
514 dbblock a pointer to an open database block
515 start TRUE if starting a new scan
516 FALSE if continuing with the current scan
517 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
518 that use the notion of a cursor
520 Returns: the next record from the file, or
521 NULL if there are no more
525 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
527 EXIM_DATUM key_datum, value_datum;
530 /* Some dbm require an initialization */
532 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
534 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
535 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
537 yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
538 US EXIM_DATUM_DATA(key_datum) : NULL;
540 /* Some dbm require a termination */
542 if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
545 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
550 /*************************************************
551 * The exim_dumpdb main program *
552 *************************************************/
555 main(int argc, char **cargv)
562 uschar **argv = USS cargv;
563 uschar keybuffer[1024];
567 /* Check the arguments, and open the database */
569 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
570 spool_directory = argv[1];
571 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
574 /* Scan the file, formatting the information for each entry. Note
575 that data is returned in a malloc'ed block, in order that it be
576 correctly aligned. */
578 for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
580 key = dbfn_scan(dbm, FALSE, &cursor))
584 dbdata_callout_cache *callout;
585 dbdata_ratelimit *ratelimit;
586 dbdata_ratelimit_unique *rate_unique;
587 dbdata_tls_session *session;
592 uschar name[MESSAGE_ID_LENGTH + 1];
594 rmark reset_point = store_mark();
596 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
597 which might change. */
599 if (Ustrlen(key) > sizeof(keybuffer) - 1)
601 printf("**** Overlong key encountered: %s\n", key);
604 Ustrcpy(keybuffer, key);
606 if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
607 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
608 "was not found in the file - something is wrong!\n",
612 /* Note: don't use print_time more than once in one statement, since
613 it uses a single buffer. */
618 retry = (dbdata_retry *)value;
619 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
620 retry->more_errno, retry->text,
621 print_time(retry->first_failed));
622 printf("%s ", print_time(retry->last_try));
623 printf("%s %s\n", print_time(retry->next_try),
624 (retry->expired)? "*" : "");
628 wait = (dbdata_wait *)value;
629 printf("%s ", keybuffer);
631 name[MESSAGE_ID_LENGTH] = 0;
633 /* Leave corrupt records alone */
634 if (wait->count > WAIT_NAME_MAX)
637 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
638 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
639 wait->count = WAIT_NAME_MAX;
640 yield = count_bad = 1;
642 for (int i = 1; i <= wait->count; i++)
644 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
645 if (count_bad && name[0] == 0) break;
646 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
647 Ustrspn(name, "0123456789"
648 "abcdefghijklmnopqrstuvwxyz"
649 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
652 "**** Data for %s corrupted: bad character in message id\n",
654 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
655 fprintf(stderr, "%02x ", name[j]);
656 fprintf(stderr, "\n");
661 t += MESSAGE_ID_LENGTH;
667 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
672 callout = (dbdata_callout_cache *)value;
674 /* New-style address record */
676 if (length == sizeof(dbdata_callout_cache_address))
678 printf("%s %s callout=%s\n",
679 print_time(((dbdata_generic *)value)->time_stamp),
681 print_cache(callout->result));
684 /* New-style domain record */
686 else if (length == sizeof(dbdata_callout_cache))
688 printf("%s %s callout=%s postmaster=%s",
689 print_time(((dbdata_generic *)value)->time_stamp),
691 print_cache(callout->result),
692 print_cache(callout->postmaster_result));
693 if (callout->postmaster_result != ccache_unknown)
694 printf(" (%s)", print_time(callout->postmaster_stamp));
695 printf(" random=%s", print_cache(callout->random_result));
696 if (callout->random_result != ccache_unknown)
697 printf(" (%s)", print_time(callout->random_stamp));
704 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
706 ratelimit = (dbdata_ratelimit *)value;
707 rate_unique = (dbdata_ratelimit_unique *)value;
708 printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
709 print_time(ratelimit->time_stamp),
710 ratelimit->time_usec, ratelimit->rate,
711 print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
716 ratelimit = (dbdata_ratelimit *)value;
717 printf("%s.%06d rate: %10.3f key: %s\n",
718 print_time(ratelimit->time_stamp),
719 ratelimit->time_usec, ratelimit->rate,
725 session = (dbdata_tls_session *)value;
726 printf(" %s %.*s\n", keybuffer, length, session->session);
730 seen = (dbdata_seen *)value;
731 printf("%s\t%s\n", keybuffer, print_time(seen->time_stamp));
735 store_reset(reset_point);
742 #endif /* EXIM_DUMPDB */
748 /*************************************************
749 * The exim_fixdb main program *
750 *************************************************/
752 /* In order not to hold the database lock any longer than is necessary, each
753 operation on the database uses a separate open/close call. This is expensive,
754 but then using this utility is not expected to be very common. Its main use is
755 to provide a way of patching up hints databases in order to run tests.
760 This causes the data from the given record to be displayed, or "not found"
761 to be output. Note that in the retry database, destination names are
762 preceded by R: or T: for router or transport retry info.
765 This causes the given record to be deleted or "not found" to be output.
767 (3) <record name> <field number> <value>
768 This sets the given value into the given field, identified by a number
769 which is output by the display command. Not all types of record can
773 This exits from exim_fixdb.
775 If the record name is omitted from (2) or (3), the previously used record name
779 int main(int argc, char **cargv)
782 uschar **argv = USS cargv;
788 name[0] = 0; /* No name set */
790 /* Sort out the database type, verify what we are working on and then process
793 dbdata_type = check_args(argc, argv, US"fixdb", US"");
794 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
796 for(; (reset_point = store_mark()); store_reset(reset_point))
803 dbdata_callout_cache *callout;
804 dbdata_ratelimit *ratelimit;
805 dbdata_ratelimit_unique *rate_unique;
806 dbdata_tls_session *session;
809 uschar field[256], value[256];
812 if (Ufgets(buffer, 256, stdin) == NULL) break;
814 buffer[Ustrlen(buffer)-1] = 0;
815 field[0] = value[0] = 0;
817 /* If the buffer contains just one digit, or just consists of "d", use the
818 previous name for an update. */
820 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
821 || Ustrcmp(buffer, "d") == 0)
825 printf("No previous record name is set\n");
828 (void)sscanf(CS buffer, "%s %s", field, value);
833 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
836 /* Handle an update request */
841 spool_directory = argv[1];
843 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
846 if (Ustrcmp(field, "d") == 0)
848 if (value[0] != 0) printf("unexpected value after \"d\"\n");
849 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
850 "not found" : "deleted");
855 else if (isdigit((uschar)field[0]))
857 int fieldno = Uatoi(field);
860 printf("value missing\n");
866 record = dbfn_read_with_length(dbm, name, &oldlength);
867 if (record == NULL) printf("not found\n"); else
870 /*int length = 0; Stops compiler warning */
875 retry = (dbdata_retry *)record;
876 /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
880 case 0: retry->basic_errno = Uatoi(value);
882 case 1: retry->more_errno = Uatoi(value);
884 case 2: if ((tt = read_time(value)) > 0) retry->first_failed = tt;
885 else printf("bad time value\n");
887 case 3: if ((tt = read_time(value)) > 0) retry->last_try = tt;
888 else printf("bad time value\n");
890 case 4: if ((tt = read_time(value)) > 0) retry->next_try = tt;
891 else printf("bad time value\n");
893 case 5: if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
894 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
895 else printf("\"yes\" or \"no\" expected=n");
897 default: printf("unknown field number\n");
904 printf("Can't change contents of wait database record\n");
908 printf("Can't change contents of misc database record\n");
912 callout = (dbdata_callout_cache *)record;
913 /* length = sizeof(dbdata_callout_cache); */
916 case 0: callout->result = Uatoi(value);
918 case 1: callout->postmaster_result = Uatoi(value);
920 case 2: callout->random_result = Uatoi(value);
922 default: printf("unknown field number\n");
929 ratelimit = (dbdata_ratelimit *)record;
932 case 0: if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
933 else printf("bad time value\n");
935 case 1: ratelimit->time_usec = Uatoi(value);
937 case 2: ratelimit->rate = Ustrtod(value, NULL);
939 case 3: if (Ustrstr(name, "/unique/") != NULL
940 && oldlength >= sizeof(dbdata_ratelimit_unique))
942 rate_unique = (dbdata_ratelimit_unique *)record;
943 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
944 else printf("bad time value\n");
947 /* else fall through */
949 case 5: if (Ustrstr(name, "/unique/") != NULL
950 && oldlength >= sizeof(dbdata_ratelimit_unique))
958 md5_end(&md5info, value, Ustrlen(value), md5sum);
959 hash = md5sum[0] << 0 | md5sum[1] << 8
960 | md5sum[2] << 16 | md5sum[3] << 24;
961 hinc = md5sum[4] << 0 | md5sum[5] << 8
962 | md5sum[6] << 16 | md5sum[7] << 24;
963 rate_unique = (dbdata_ratelimit_unique *)record;
965 for (unsigned n = 0; n < 8; n++, hash += hinc)
967 int bit = 1 << (hash % 8);
968 int byte = (hash / 8) % rate_unique->bloom_size;
969 if ((rate_unique->bloom[byte] & bit) == 0)
972 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
976 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
979 /* else fall through */
980 default: printf("unknown field number\n");
987 printf("Can't change contents of tls database record\n");
991 dbfn_write(dbm, name, record, oldlength);
998 printf("field number or d expected\n");
1003 if (!verify) continue;
1006 /* The "name" q causes an exit */
1008 else if (Ustrcmp(name, "q") == 0) return 0;
1010 /* Handle a read request, or verify after an update. */
1012 spool_directory = argv[1];
1013 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
1016 if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
1018 printf("record %s not found\n", name);
1024 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
1028 retry = (dbdata_retry *)record;
1029 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
1030 printf("1 extra data: %d\n", retry->more_errno);
1031 printf("2 first failed: %s\n", print_time(retry->first_failed));
1032 printf("3 last try: %s\n", print_time(retry->last_try));
1033 printf("4 next try: %s\n", print_time(retry->next_try));
1034 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
1038 wait = (dbdata_wait *)record;
1040 printf("Sequence: %d\n", wait->sequence);
1041 if (wait->count > WAIT_NAME_MAX)
1043 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
1044 wait->count, WAIT_NAME_MAX);
1045 wait->count = WAIT_NAME_MAX;
1048 for (int i = 1; i <= wait->count; i++)
1050 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1051 value[MESSAGE_ID_LENGTH] = 0;
1052 if (count_bad && value[0] == 0) break;
1053 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1054 Ustrspn(value, "0123456789"
1055 "abcdefghijklmnopqrstuvwxyz"
1056 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1058 printf("\n**** Data corrupted: bad character in message id ****\n");
1059 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
1060 printf("%02x ", value[j]);
1064 printf("%s ", value);
1065 t += MESSAGE_ID_LENGTH;
1074 callout = (dbdata_callout_cache *)record;
1075 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1077 if (oldlength > sizeof(dbdata_callout_cache_address))
1079 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1080 callout->postmaster_result);
1081 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1082 callout->random_result);
1086 case type_ratelimit:
1087 ratelimit = (dbdata_ratelimit *)record;
1088 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1089 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1090 printf("2 sender rate: % .3f\n", ratelimit->rate);
1091 if (Ustrstr(name, "/unique/") != NULL
1092 && oldlength >= sizeof(dbdata_ratelimit_unique))
1094 rate_unique = (dbdata_ratelimit_unique *)record;
1095 printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1096 printf("4 test filter membership\n");
1097 printf("5 add element to filter\n");
1102 session = (dbdata_tls_session *)value;
1103 printf("0 time stamp: %s\n", print_time(session->time_stamp));
1104 printf("1 session: .%s\n", session->session);
1109 /* The database is closed after each request */
1118 #endif /* EXIM_FIXDB */
1123 /*************************************************
1124 * The exim_tidydb main program *
1125 *************************************************/
1128 /* Utility program to tidy the contents of an exim database file. There is one
1131 -t <time> expiry time for old records - default 30 days
1133 For backwards compatibility, an -f option is recognized and ignored. (It used
1134 to request a "full" tidy. This version always does the whole job.) */
1137 typedef struct key_item {
1138 struct key_item *next;
1143 int main(int argc, char **cargv)
1145 struct stat statbuf;
1146 int maxkeep = 30 * 24 * 60 * 60;
1147 int dbdata_type, i, oldest, path_len;
1148 key_item *keychain = NULL;
1152 EXIM_CURSOR *cursor;
1153 uschar **argv = USS cargv;
1159 /* Scan the options */
1161 for (i = 1; i < argc; i++)
1163 if (argv[i][0] != '-') break;
1164 if (Ustrcmp(argv[i], "-f") == 0) continue;
1165 if (Ustrcmp(argv[i], "-t") == 0)
1173 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1174 (void)sscanf(CS s, "%d%n", &value, &count);
1178 case 'w': value *= 7;
1179 case 'd': value *= 24;
1180 case 'h': value *= 60;
1181 case 'm': value *= 60;
1184 default: usage(US"tidydb", US" [-t <time>]");
1189 else usage(US"tidydb", US" [-t <time>]");
1192 /* Adjust argument values and process arguments */
1197 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1199 /* Compute the oldest keep time, verify what we are doing, and open the
1202 oldest = time(NULL) - maxkeep;
1203 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1205 spool_directory = argv[1];
1206 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
1209 /* Prepare for building file names */
1211 sprintf(CS buffer, "%s/input/", argv[1]);
1212 path_len = Ustrlen(buffer);
1215 /* It appears, by experiment, that it is a bad idea to make changes
1216 to the file while scanning it. Pity the man page doesn't warn you about that.
1217 Therefore, we scan and build a list of all the keys. Then we use that to
1218 read the records and possibly update them. */
1220 for (key = dbfn_scan(dbm, TRUE, &cursor);
1222 key = dbfn_scan(dbm, FALSE, &cursor))
1224 key_item *k = store_get(sizeof(key_item) + Ustrlen(key), is_tainted(key));
1227 Ustrcpy(k->key, key);
1230 /* Now scan the collected keys and operate on the records, resetting
1231 the store each time round. */
1233 for (; keychain && (reset_point = store_mark()); store_reset(reset_point))
1235 dbdata_generic *value;
1237 key = keychain->key;
1238 keychain = keychain->next;
1239 value = dbfn_read_with_length(dbm, key, NULL);
1241 /* A continuation record may have been deleted or renamed already, so
1242 non-existence is not serious. */
1244 if (!value) continue;
1246 /* Delete if too old */
1248 if (value->time_stamp < oldest)
1250 printf("deleted %s (too old)\n", key);
1251 dbfn_delete(dbm, key);
1255 /* Do database-specific tidying for wait databases, and message-
1256 specific tidying for the retry database. */
1258 if (dbdata_type == type_wait)
1260 dbdata_wait *wait = (dbdata_wait *)value;
1261 BOOL update = FALSE;
1263 /* Leave corrupt records alone */
1265 if (wait->time_stamp > time(NULL))
1267 printf("**** Data for '%s' corrupted\n time in future: %s\n",
1268 key, print_time(((dbdata_generic *)value)->time_stamp));
1271 if (wait->count > WAIT_NAME_MAX)
1273 printf("**** Data for '%s' corrupted\n count=%d=0x%x max=%d\n",
1274 key, wait->count, wait->count, WAIT_NAME_MAX);
1277 if (wait->sequence > WAIT_CONT_MAX)
1279 printf("**** Data for '%s' corrupted\n sequence=%d=0x%x max=%d\n",
1280 key, wait->sequence, wait->sequence, WAIT_CONT_MAX);
1284 /* Record over 1 year old; just remove it */
1286 if (wait->time_stamp < time(NULL) - 365*24*60*60)
1288 dbfn_delete(dbm, key);
1289 printf("deleted %s (too old)\n", key);
1293 /* Loop for renamed continuation records. For each message id,
1294 check to see if the message exists, and if not, remove its entry
1295 from the record. Because of the possibility of split input directories,
1296 we must look in both possible places for a -D file. */
1300 int length = wait->count * MESSAGE_ID_LENGTH;
1302 for (int offset = length - MESSAGE_ID_LENGTH;
1303 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1305 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1306 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1308 if (Ustat(buffer, &statbuf) != 0)
1310 buffer[path_len] = wait->text[offset+5];
1311 buffer[path_len+1] = '/';
1312 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1313 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1315 if (Ustat(buffer, &statbuf) != 0)
1317 int left = length - offset - MESSAGE_ID_LENGTH;
1318 if (left > 0) Ustrncpy(wait->text + offset,
1319 wait->text + offset + MESSAGE_ID_LENGTH, left);
1321 length -= MESSAGE_ID_LENGTH;
1327 /* If record is empty and the main record, either delete it or rename
1328 the next continuation, repeating if that is also empty. */
1330 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1332 while (wait->count == 0 && wait->sequence > 0)
1335 dbdata_generic *newvalue;
1336 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1337 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1338 if (newvalue != NULL)
1341 wait = (dbdata_wait *)newvalue;
1342 dbfn_delete(dbm, newkey);
1343 printf("renamed %s\n", newkey);
1346 else wait->sequence--;
1349 /* If we have ended up with an empty main record, delete it
1350 and break the loop. Otherwise the new record will be scanned. */
1352 if (wait->count == 0 && wait->sequence == 0)
1354 dbfn_delete(dbm, key);
1355 printf("deleted %s (empty)\n", key);
1361 /* If not an empty main record, break the loop */
1366 /* Re-write the record if required */
1370 printf("updated %s\n", key);
1371 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1372 wait->count * MESSAGE_ID_LENGTH);
1376 /* If a retry record's key ends with a message-id, check that that message
1377 still exists; if not, remove this record. */
1379 else if (dbdata_type == type_retry)
1382 int len = Ustrlen(key);
1384 if (len < MESSAGE_ID_LENGTH + 1) continue;
1385 id = key + len - MESSAGE_ID_LENGTH - 1;
1386 if (*id++ != ':') continue;
1388 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1389 if (i == 6 || i == 13)
1390 { if (id[i] != '-') break; }
1392 { if (!isalnum(id[i])) break; }
1393 if (i < MESSAGE_ID_LENGTH) continue;
1395 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1396 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1398 if (Ustat(buffer, &statbuf) != 0)
1400 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1401 if (Ustat(buffer, &statbuf) != 0)
1403 dbfn_delete(dbm, key);
1404 printf("deleted %s (no message)\n", key);
1411 printf("Tidying complete\n");
1415 #endif /* EXIM_TIDYDB */
1417 /* End of exim_dbutil.c */