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;
74 /* These introduced by the taintwarn handling */
75 #ifdef ALLOW_INSECURE_TAINTED_DATA
76 BOOL allow_insecure_tainted_data;
79 /******************************************************************************/
82 /*************************************************
83 * Berkeley DB error callback *
84 *************************************************/
86 /* For Berkeley DB >= 2, we can define a function to be called in case of DB
87 errors. This should help with debugging strange DB problems, e.g. getting "File
88 exists" when you try to open a db file. The API changed at release 4.3. */
90 #if defined(USE_DB) && defined(DB_VERSION_STRING)
92 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
93 dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
97 dbfn_bdb_error_callback(const char *pfx, char *msg)
101 printf("Berkeley DB error: %s\n", msg);
107 /*************************************************
109 *************************************************/
111 SIGNAL_BOOL sigalrm_seen;
114 sigalrm_handler(int sig)
121 /*************************************************
122 * Output usage message and exit *
123 *************************************************/
126 usage(uschar *name, uschar *options)
128 printf("Usage: exim_%s%s <spool-directory> <database-name>\n", name, options);
129 printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit | tls\n");
135 /*************************************************
136 * Sort out the command arguments *
137 *************************************************/
139 /* This function checks that there are exactly 2 arguments, and checks the
140 second of them to be sure it is a known database name. */
143 check_args(int argc, uschar **argv, uschar *name, uschar *options)
147 if (Ustrcmp(argv[2], "retry") == 0) return type_retry;
148 if (Ustrcmp(argv[2], "misc") == 0) return type_misc;
149 if (Ustrncmp(argv[2], "wait-", 5) == 0) return type_wait;
150 if (Ustrcmp(argv[2], "callout") == 0) return type_callout;
151 if (Ustrcmp(argv[2], "ratelimit") == 0) return type_ratelimit;
152 if (Ustrcmp(argv[2], "tls") == 0) return type_tls;
154 usage(name, options);
155 return -1; /* Never obeyed */
160 /*************************************************
161 * Handle attempts to write the log *
162 *************************************************/
164 /* The message gets written to stderr when log_write() is called from a
165 utility. The message always gets '\n' added on the end of it. These calls come
166 from modules such as store.c when things go drastically wrong (e.g. malloc()
167 failing). In normal use they won't get obeyed.
170 selector not relevant when running a utility
171 flags not relevant when running a utility
172 format a printf() format
173 ... arguments for format
179 log_write(unsigned int selector, int flags, const char *format, ...)
182 va_start(ap, format);
183 vfprintf(stderr, format, ap);
184 fprintf(stderr, "\n");
190 /*************************************************
191 * Format a time value for printing *
192 *************************************************/
194 static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss ")];
199 struct tm *tmstr = localtime(&t);
200 Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
206 /*************************************************
207 * Format a cache value for printing *
208 *************************************************/
211 print_cache(int value)
213 return (value == ccache_accept)? US"accept" :
214 (value == ccache_reject)? US"reject" :
220 /*************************************************
222 *************************************************/
229 time_t now = time(NULL);
230 struct tm *tm = localtime(&now);
235 for (uschar * t = s + Ustrlen(s) - 1; t >= s; t--)
237 if (*t == ':') continue;
238 if (!isdigit((uschar)*t)) return -1;
243 if (!isdigit((uschar)*t)) return -1;
244 value = value + (*t - '0')*10;
249 case 0: tm->tm_min = value; break;
250 case 1: tm->tm_hour = value; break;
251 case 2: tm->tm_mday = value; break;
252 case 3: tm->tm_mon = value - 1; break;
253 case 4: tm->tm_year = (value < 90)? value + 100 : value; break;
260 #endif /* EXIM_FIXDB */
264 /*************************************************
265 * Open and lock a database file *
266 *************************************************/
268 /* This is a cut-down version from the function in dbfn.h that Exim itself
269 uses. We assume the database exists, and therefore give up if we cannot open
273 name The single-component name of one of Exim's database files.
274 flags O_RDONLY or O_RDWR
275 dbblock Points to an open_db block to be filled in.
279 Returns: NULL if the open failed, or the locking failed.
280 On success, dbblock is returned. This contains the dbm pointer and
281 the fd of the locked lock file.
285 dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof, BOOL panic)
288 struct flock lock_data;
289 BOOL read_only = flags == O_RDONLY;
290 uschar * dirname, * filename;
292 /* The first thing to do is to open a separate file on which to lock. This
293 ensures that Exim has exclusive use of the database before it even tries to
294 open it. If there is a database, there should be a lock file in existence. */
296 #ifdef COMPILE_UTILITY
297 if ( asprintf(CSS &dirname, "%s/db", spool_directory) < 0
298 || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
301 dirname = string_sprintf("%s/db", spool_directory);
302 filename = string_sprintf("%s/%s.lockfile", dirname, name);
305 dbblock->lockfd = Uopen(filename, flags, 0);
306 if (dbblock->lockfd < 0)
308 printf("** Failed to open database lock file %s: %s\n", filename,
313 /* Now we must get a lock on the opened lock file; do this with a blocking
314 lock that times out. */
316 lock_data.l_type = read_only ? F_RDLCK : F_WRLCK;
317 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
319 sigalrm_seen = FALSE;
320 os_non_restarting_signal(SIGALRM, sigalrm_handler);
321 ALARM(EXIMDB_LOCK_TIMEOUT);
322 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
325 if (sigalrm_seen) errno = ETIMEDOUT;
328 printf("** Failed to get %s lock for %s: %s",
329 flags & O_WRONLY ? "write" : "read",
331 errno == ETIMEDOUT ? "timed out" : strerror(errno));
332 (void)close(dbblock->lockfd);
336 /* At this point we have an opened and locked separate lock file, that is,
337 exclusive access to the database, so we can go ahead and open it. */
339 #ifdef COMPILE_UTILITY
340 if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
342 filename = string_sprintf("%s/%s", dirname, name);
344 EXIM_DBOPEN(filename, dirname, flags, 0, &dbblock->dbptr);
348 printf("** Failed to open DBM file %s for %s:\n %s%s\n", filename,
349 read_only? "reading" : "writing", strerror(errno),
351 " (or Berkeley DB error while opening)"
356 (void)close(dbblock->lockfd);
366 /*************************************************
367 * Unlock and close a database file *
368 *************************************************/
370 /* Closing a file automatically unlocks it, so after closing the database, just
373 Argument: a pointer to an open database block
378 dbfn_close(open_db *dbblock)
380 EXIM_DBCLOSE(dbblock->dbptr);
381 (void)close(dbblock->lockfd);
387 /*************************************************
388 * Read from database file *
389 *************************************************/
391 /* Passing back the pointer unchanged is useless, because there is no guarantee
392 of alignment. Since all the records used by Exim need to be properly aligned to
393 pick out the timestamps, etc., do the copying centrally here.
396 dbblock a pointer to an open database block
397 key the key of the record to be read
398 length where to put the length (or NULL if length not wanted). Includes overhead.
400 Returns: a pointer to the retrieved record, or
401 NULL if the record is not found
405 dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
408 EXIM_DATUM key_datum, result_datum;
409 int klen = Ustrlen(key) + 1;
410 uschar * key_copy = store_get(klen, is_tainted(key));
412 memcpy(key_copy, key, klen);
414 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
415 EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */
416 EXIM_DATUM_DATA(key_datum) = CS key_copy;
417 EXIM_DATUM_SIZE(key_datum) = klen;
419 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
421 /* Assume for now that anything stored could have been tainted. Properly
422 we should store the taint status along with the data. */
424 yield = store_get(EXIM_DATUM_SIZE(result_datum), TRUE);
425 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
426 if (length) *length = EXIM_DATUM_SIZE(result_datum);
428 EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */
434 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
436 /*************************************************
437 * Write to database file *
438 *************************************************/
442 dbblock a pointer to an open database block
443 key the key of the record to be written
444 ptr a pointer to the record to be written
445 length the length of the record to be written
447 Returns: the yield of the underlying dbm or db "write" function. If this
448 is dbm, the value is zero for OK.
452 dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
454 EXIM_DATUM key_datum, value_datum;
455 dbdata_generic *gptr = (dbdata_generic *)ptr;
456 int klen = Ustrlen(key) + 1;
457 uschar * key_copy = store_get(klen, is_tainted(key));
459 memcpy(key_copy, key, klen);
460 gptr->time_stamp = time(NULL);
462 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
463 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
464 EXIM_DATUM_DATA(key_datum) = CS key_copy;
465 EXIM_DATUM_SIZE(key_datum) = klen;
466 EXIM_DATUM_DATA(value_datum) = CS ptr;
467 EXIM_DATUM_SIZE(value_datum) = length;
468 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
473 /*************************************************
474 * Delete record from database file *
475 *************************************************/
479 dbblock a pointer to an open database block
480 key the key of the record to be deleted
482 Returns: the yield of the underlying dbm or db "delete" function.
486 dbfn_delete(open_db *dbblock, const uschar *key)
488 int klen = Ustrlen(key) + 1;
489 uschar * key_copy = store_get(klen, is_tainted(key));
491 memcpy(key_copy, key, klen);
492 EXIM_DATUM key_datum;
493 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
494 EXIM_DATUM_DATA(key_datum) = CS key_copy;
495 EXIM_DATUM_SIZE(key_datum) = klen;
496 return EXIM_DBDEL(dbblock->dbptr, key_datum);
499 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
503 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
504 /*************************************************
505 * Scan the keys of a database file *
506 *************************************************/
510 dbblock a pointer to an open database block
511 start TRUE if starting a new scan
512 FALSE if continuing with the current scan
513 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
514 that use the notion of a cursor
516 Returns: the next record from the file, or
517 NULL if there are no more
521 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
523 EXIM_DATUM key_datum, value_datum;
526 /* Some dbm require an initialization */
528 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
530 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
531 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
533 yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
534 US EXIM_DATUM_DATA(key_datum) : NULL;
536 /* Some dbm require a termination */
538 if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
541 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
546 /*************************************************
547 * The exim_dumpdb main program *
548 *************************************************/
551 main(int argc, char **cargv)
558 uschar **argv = USS cargv;
559 uschar keybuffer[1024];
563 /* Check the arguments, and open the database */
565 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
566 spool_directory = argv[1];
567 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
570 /* Scan the file, formatting the information for each entry. Note
571 that data is returned in a malloc'ed block, in order that it be
572 correctly aligned. */
574 for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
576 key = dbfn_scan(dbm, FALSE, &cursor))
580 dbdata_callout_cache *callout;
581 dbdata_ratelimit *ratelimit;
582 dbdata_ratelimit_unique *rate_unique;
583 dbdata_tls_session *session;
587 uschar name[MESSAGE_ID_LENGTH + 1];
589 rmark reset_point = store_mark();
591 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
592 which might change. */
594 if (Ustrlen(key) > sizeof(keybuffer) - 1)
596 printf("**** Overlong key encountered: %s\n", key);
599 Ustrcpy(keybuffer, key);
601 if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
602 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
603 "was not found in the file - something is wrong!\n",
607 /* Note: don't use print_time more than once in one statement, since
608 it uses a single buffer. */
613 retry = (dbdata_retry *)value;
614 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
615 retry->more_errno, retry->text,
616 print_time(retry->first_failed));
617 printf("%s ", print_time(retry->last_try));
618 printf("%s %s\n", print_time(retry->next_try),
619 (retry->expired)? "*" : "");
623 wait = (dbdata_wait *)value;
624 printf("%s ", keybuffer);
626 name[MESSAGE_ID_LENGTH] = 0;
628 /* Leave corrupt records alone */
629 if (wait->count > WAIT_NAME_MAX)
632 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
633 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
634 wait->count = WAIT_NAME_MAX;
635 yield = count_bad = 1;
637 for (int i = 1; i <= wait->count; i++)
639 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
640 if (count_bad && name[0] == 0) break;
641 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
642 Ustrspn(name, "0123456789"
643 "abcdefghijklmnopqrstuvwxyz"
644 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
647 "**** Data for %s corrupted: bad character in message id\n",
649 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
650 fprintf(stderr, "%02x ", name[j]);
651 fprintf(stderr, "\n");
656 t += MESSAGE_ID_LENGTH;
662 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
667 callout = (dbdata_callout_cache *)value;
669 /* New-style address record */
671 if (length == sizeof(dbdata_callout_cache_address))
673 printf("%s %s callout=%s\n",
674 print_time(((dbdata_generic *)value)->time_stamp),
676 print_cache(callout->result));
679 /* New-style domain record */
681 else if (length == sizeof(dbdata_callout_cache))
683 printf("%s %s callout=%s postmaster=%s",
684 print_time(((dbdata_generic *)value)->time_stamp),
686 print_cache(callout->result),
687 print_cache(callout->postmaster_result));
688 if (callout->postmaster_result != ccache_unknown)
689 printf(" (%s)", print_time(callout->postmaster_stamp));
690 printf(" random=%s", print_cache(callout->random_result));
691 if (callout->random_result != ccache_unknown)
692 printf(" (%s)", print_time(callout->random_stamp));
699 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
701 ratelimit = (dbdata_ratelimit *)value;
702 rate_unique = (dbdata_ratelimit_unique *)value;
703 printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
704 print_time(ratelimit->time_stamp),
705 ratelimit->time_usec, ratelimit->rate,
706 print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
711 ratelimit = (dbdata_ratelimit *)value;
712 printf("%s.%06d rate: %10.3f key: %s\n",
713 print_time(ratelimit->time_stamp),
714 ratelimit->time_usec, ratelimit->rate,
720 session = (dbdata_tls_session *)value;
721 printf(" %s %.*s\n", keybuffer, length, session->session);
725 store_reset(reset_point);
732 #endif /* EXIM_DUMPDB */
738 /*************************************************
739 * The exim_fixdb main program *
740 *************************************************/
742 /* In order not to hold the database lock any longer than is necessary, each
743 operation on the database uses a separate open/close call. This is expensive,
744 but then using this utility is not expected to be very common. Its main use is
745 to provide a way of patching up hints databases in order to run tests.
750 This causes the data from the given record to be displayed, or "not found"
751 to be output. Note that in the retry database, destination names are
752 preceded by R: or T: for router or transport retry info.
755 This causes the given record to be deleted or "not found" to be output.
757 (3) <record name> <field number> <value>
758 This sets the given value into the given field, identified by a number
759 which is output by the display command. Not all types of record can
763 This exits from exim_fixdb.
765 If the record name is omitted from (2) or (3), the previously used record name
769 int main(int argc, char **cargv)
772 uschar **argv = USS cargv;
778 name[0] = 0; /* No name set */
780 /* Sort out the database type, verify what we are working on and then process
783 dbdata_type = check_args(argc, argv, US"fixdb", US"");
784 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
786 for(; (reset_point = store_mark()); store_reset(reset_point))
793 dbdata_callout_cache *callout;
794 dbdata_ratelimit *ratelimit;
795 dbdata_ratelimit_unique *rate_unique;
796 dbdata_tls_session *session;
799 uschar field[256], value[256];
802 if (Ufgets(buffer, 256, stdin) == NULL) break;
804 buffer[Ustrlen(buffer)-1] = 0;
805 field[0] = value[0] = 0;
807 /* If the buffer contains just one digit, or just consists of "d", use the
808 previous name for an update. */
810 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
811 || Ustrcmp(buffer, "d") == 0)
815 printf("No previous record name is set\n");
818 (void)sscanf(CS buffer, "%s %s", field, value);
823 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
826 /* Handle an update request */
831 spool_directory = argv[1];
833 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
836 if (Ustrcmp(field, "d") == 0)
838 if (value[0] != 0) printf("unexpected value after \"d\"\n");
839 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
840 "not found" : "deleted");
845 else if (isdigit((uschar)field[0]))
847 int fieldno = Uatoi(field);
850 printf("value missing\n");
856 record = dbfn_read_with_length(dbm, name, &oldlength);
857 if (record == NULL) printf("not found\n"); else
860 /*int length = 0; Stops compiler warning */
865 retry = (dbdata_retry *)record;
866 /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
870 case 0: retry->basic_errno = Uatoi(value);
872 case 1: retry->more_errno = Uatoi(value);
874 case 2: if ((tt = read_time(value)) > 0) retry->first_failed = tt;
875 else printf("bad time value\n");
877 case 3: if ((tt = read_time(value)) > 0) retry->last_try = tt;
878 else printf("bad time value\n");
880 case 4: if ((tt = read_time(value)) > 0) retry->next_try = tt;
881 else printf("bad time value\n");
883 case 5: if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
884 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
885 else printf("\"yes\" or \"no\" expected=n");
887 default: printf("unknown field number\n");
894 printf("Can't change contents of wait database record\n");
898 printf("Can't change contents of misc database record\n");
902 callout = (dbdata_callout_cache *)record;
903 /* length = sizeof(dbdata_callout_cache); */
906 case 0: callout->result = Uatoi(value);
908 case 1: callout->postmaster_result = Uatoi(value);
910 case 2: callout->random_result = Uatoi(value);
912 default: printf("unknown field number\n");
919 ratelimit = (dbdata_ratelimit *)record;
922 case 0: if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
923 else printf("bad time value\n");
925 case 1: ratelimit->time_usec = Uatoi(value);
927 case 2: ratelimit->rate = Ustrtod(value, NULL);
929 case 3: if (Ustrstr(name, "/unique/") != NULL
930 && oldlength >= sizeof(dbdata_ratelimit_unique))
932 rate_unique = (dbdata_ratelimit_unique *)record;
933 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
934 else printf("bad time value\n");
937 /* else fall through */
939 case 5: if (Ustrstr(name, "/unique/") != NULL
940 && oldlength >= sizeof(dbdata_ratelimit_unique))
948 md5_end(&md5info, value, Ustrlen(value), md5sum);
949 hash = md5sum[0] << 0 | md5sum[1] << 8
950 | md5sum[2] << 16 | md5sum[3] << 24;
951 hinc = md5sum[4] << 0 | md5sum[5] << 8
952 | md5sum[6] << 16 | md5sum[7] << 24;
953 rate_unique = (dbdata_ratelimit_unique *)record;
955 for (unsigned n = 0; n < 8; n++, hash += hinc)
957 int bit = 1 << (hash % 8);
958 int byte = (hash / 8) % rate_unique->bloom_size;
959 if ((rate_unique->bloom[byte] & bit) == 0)
962 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
966 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
969 /* else fall through */
970 default: printf("unknown field number\n");
977 printf("Can't change contents of tls database record\n");
981 dbfn_write(dbm, name, record, oldlength);
988 printf("field number or d expected\n");
993 if (!verify) continue;
996 /* The "name" q causes an exit */
998 else if (Ustrcmp(name, "q") == 0) return 0;
1000 /* Handle a read request, or verify after an update. */
1002 spool_directory = argv[1];
1003 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
1006 if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
1008 printf("record %s not found\n", name);
1014 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
1018 retry = (dbdata_retry *)record;
1019 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
1020 printf("1 extra data: %d\n", retry->more_errno);
1021 printf("2 first failed: %s\n", print_time(retry->first_failed));
1022 printf("3 last try: %s\n", print_time(retry->last_try));
1023 printf("4 next try: %s\n", print_time(retry->next_try));
1024 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
1028 wait = (dbdata_wait *)record;
1030 printf("Sequence: %d\n", wait->sequence);
1031 if (wait->count > WAIT_NAME_MAX)
1033 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
1034 wait->count, WAIT_NAME_MAX);
1035 wait->count = WAIT_NAME_MAX;
1038 for (int i = 1; i <= wait->count; i++)
1040 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1041 value[MESSAGE_ID_LENGTH] = 0;
1042 if (count_bad && value[0] == 0) break;
1043 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1044 Ustrspn(value, "0123456789"
1045 "abcdefghijklmnopqrstuvwxyz"
1046 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1048 printf("\n**** Data corrupted: bad character in message id ****\n");
1049 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
1050 printf("%02x ", value[j]);
1054 printf("%s ", value);
1055 t += MESSAGE_ID_LENGTH;
1064 callout = (dbdata_callout_cache *)record;
1065 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1067 if (oldlength > sizeof(dbdata_callout_cache_address))
1069 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1070 callout->postmaster_result);
1071 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1072 callout->random_result);
1076 case type_ratelimit:
1077 ratelimit = (dbdata_ratelimit *)record;
1078 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1079 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1080 printf("2 sender rate: % .3f\n", ratelimit->rate);
1081 if (Ustrstr(name, "/unique/") != NULL
1082 && oldlength >= sizeof(dbdata_ratelimit_unique))
1084 rate_unique = (dbdata_ratelimit_unique *)record;
1085 printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1086 printf("4 test filter membership\n");
1087 printf("5 add element to filter\n");
1092 session = (dbdata_tls_session *)value;
1093 printf("0 time stamp: %s\n", print_time(session->time_stamp));
1094 printf("1 session: .%s\n", session->session);
1099 /* The database is closed after each request */
1108 #endif /* EXIM_FIXDB */
1113 /*************************************************
1114 * The exim_tidydb main program *
1115 *************************************************/
1118 /* Utility program to tidy the contents of an exim database file. There is one
1121 -t <time> expiry time for old records - default 30 days
1123 For backwards compatibility, an -f option is recognized and ignored. (It used
1124 to request a "full" tidy. This version always does the whole job.) */
1127 typedef struct key_item {
1128 struct key_item *next;
1133 int main(int argc, char **cargv)
1135 struct stat statbuf;
1136 int maxkeep = 30 * 24 * 60 * 60;
1137 int dbdata_type, i, oldest, path_len;
1138 key_item *keychain = NULL;
1142 EXIM_CURSOR *cursor;
1143 uschar **argv = USS cargv;
1149 /* Scan the options */
1151 for (i = 1; i < argc; i++)
1153 if (argv[i][0] != '-') break;
1154 if (Ustrcmp(argv[i], "-f") == 0) continue;
1155 if (Ustrcmp(argv[i], "-t") == 0)
1163 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1164 (void)sscanf(CS s, "%d%n", &value, &count);
1168 case 'w': value *= 7;
1169 case 'd': value *= 24;
1170 case 'h': value *= 60;
1171 case 'm': value *= 60;
1174 default: usage(US"tidydb", US" [-t <time>]");
1179 else usage(US"tidydb", US" [-t <time>]");
1182 /* Adjust argument values and process arguments */
1187 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1189 /* Compute the oldest keep time, verify what we are doing, and open the
1192 oldest = time(NULL) - maxkeep;
1193 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1195 spool_directory = argv[1];
1196 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
1199 /* Prepare for building file names */
1201 sprintf(CS buffer, "%s/input/", argv[1]);
1202 path_len = Ustrlen(buffer);
1205 /* It appears, by experiment, that it is a bad idea to make changes
1206 to the file while scanning it. Pity the man page doesn't warn you about that.
1207 Therefore, we scan and build a list of all the keys. Then we use that to
1208 read the records and possibly update them. */
1210 for (key = dbfn_scan(dbm, TRUE, &cursor);
1212 key = dbfn_scan(dbm, FALSE, &cursor))
1214 key_item *k = store_get(sizeof(key_item) + Ustrlen(key), is_tainted(key));
1217 Ustrcpy(k->key, key);
1220 /* Now scan the collected keys and operate on the records, resetting
1221 the store each time round. */
1223 for (; keychain && (reset_point = store_mark()); store_reset(reset_point))
1225 dbdata_generic *value;
1227 key = keychain->key;
1228 keychain = keychain->next;
1229 value = dbfn_read_with_length(dbm, key, NULL);
1231 /* A continuation record may have been deleted or renamed already, so
1232 non-existence is not serious. */
1234 if (!value) continue;
1236 /* Delete if too old */
1238 if (value->time_stamp < oldest)
1240 printf("deleted %s (too old)\n", key);
1241 dbfn_delete(dbm, key);
1245 /* Do database-specific tidying for wait databases, and message-
1246 specific tidying for the retry database. */
1248 if (dbdata_type == type_wait)
1250 dbdata_wait *wait = (dbdata_wait *)value;
1251 BOOL update = FALSE;
1253 /* Leave corrupt records alone */
1255 if (wait->time_stamp > time(NULL))
1257 printf("**** Data for '%s' corrupted\n time in future: %s\n",
1258 key, print_time(((dbdata_generic *)value)->time_stamp));
1261 if (wait->count > WAIT_NAME_MAX)
1263 printf("**** Data for '%s' corrupted\n count=%d=0x%x max=%d\n",
1264 key, wait->count, wait->count, WAIT_NAME_MAX);
1267 if (wait->sequence > WAIT_CONT_MAX)
1269 printf("**** Data for '%s' corrupted\n sequence=%d=0x%x max=%d\n",
1270 key, wait->sequence, wait->sequence, WAIT_CONT_MAX);
1274 /* Record over 1 year old; just remove it */
1276 if (wait->time_stamp < time(NULL) - 365*24*60*60)
1278 dbfn_delete(dbm, key);
1279 printf("deleted %s (too old)\n", key);
1283 /* Loop for renamed continuation records. For each message id,
1284 check to see if the message exists, and if not, remove its entry
1285 from the record. Because of the possibility of split input directories,
1286 we must look in both possible places for a -D file. */
1290 int length = wait->count * MESSAGE_ID_LENGTH;
1292 for (int offset = length - MESSAGE_ID_LENGTH;
1293 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1295 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1296 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1298 if (Ustat(buffer, &statbuf) != 0)
1300 buffer[path_len] = wait->text[offset+5];
1301 buffer[path_len+1] = '/';
1302 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1303 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1305 if (Ustat(buffer, &statbuf) != 0)
1307 int left = length - offset - MESSAGE_ID_LENGTH;
1308 if (left > 0) Ustrncpy(wait->text + offset,
1309 wait->text + offset + MESSAGE_ID_LENGTH, left);
1311 length -= MESSAGE_ID_LENGTH;
1317 /* If record is empty and the main record, either delete it or rename
1318 the next continuation, repeating if that is also empty. */
1320 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1322 while (wait->count == 0 && wait->sequence > 0)
1325 dbdata_generic *newvalue;
1326 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1327 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1328 if (newvalue != NULL)
1331 wait = (dbdata_wait *)newvalue;
1332 dbfn_delete(dbm, newkey);
1333 printf("renamed %s\n", newkey);
1336 else wait->sequence--;
1339 /* If we have ended up with an empty main record, delete it
1340 and break the loop. Otherwise the new record will be scanned. */
1342 if (wait->count == 0 && wait->sequence == 0)
1344 dbfn_delete(dbm, key);
1345 printf("deleted %s (empty)\n", key);
1351 /* If not an empty main record, break the loop */
1356 /* Re-write the record if required */
1360 printf("updated %s\n", key);
1361 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1362 wait->count * MESSAGE_ID_LENGTH);
1366 /* If a retry record's key ends with a message-id, check that that message
1367 still exists; if not, remove this record. */
1369 else if (dbdata_type == type_retry)
1372 int len = Ustrlen(key);
1374 if (len < MESSAGE_ID_LENGTH + 1) continue;
1375 id = key + len - MESSAGE_ID_LENGTH - 1;
1376 if (*id++ != ':') continue;
1378 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1379 if (i == 6 || i == 13)
1380 { if (id[i] != '-') break; }
1382 { if (!isalnum(id[i])) break; }
1383 if (i < MESSAGE_ID_LENGTH) continue;
1385 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1386 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1388 if (Ustat(buffer, &statbuf) != 0)
1390 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1391 if (Ustat(buffer, &statbuf) != 0)
1393 dbfn_delete(dbm, key);
1394 printf("deleted %s (no message)\n", key);
1401 printf("Tidying complete\n");
1405 #endif /* EXIM_TIDYDB */
1407 /* End of exim_dbutil.c */