Copyright updates:
[exim.git] / src / src / exim_dbutil.c
CommitLineData
059ec3d9
PH
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
4
f9ba5e22 5/* Copyright (c) University of Cambridge 1995 - 2018 */
1e1ddfac 6/* Copyright (c) The Exim Maintainers 2020 */
059ec3d9
PH
7/* See the file NOTICE for conditions of use and distribution. */
8
9
10/* This single source file is used to compile three utility programs for
11maintaining Exim hints databases.
12
13 exim_dumpdb dumps out the contents
14 exim_fixdb patches the database (really for Exim maintenance/testing)
15 exim_tidydb removed obsolete data
16
17In all cases, the first argument is the name of the spool directory. The second
18argument is the name of the database file. The available names are:
19
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
b10c87b3 24 tls: TLS session resumption cache
059ec3d9
PH
25
26There are a number of common subroutines, followed by three main programs,
27whose inclusion is controlled by -D on the compilation command. */
28
29
c99ce5c9 30#include "exim.h"
059ec3d9
PH
31
32
33/* Identifiers for the different database types. */
34
870f6ba8
TF
35#define type_retry 1
36#define type_wait 2
37#define type_misc 3
38#define type_callout 4
39#define type_ratelimit 5
b10c87b3 40#define type_tls 6
059ec3d9
PH
41
42
c99ce5c9
TF
43/* This is used by our cut-down dbfn_open(). */
44
45uschar *spool_directory;
46
059ec3d9 47
65766f1b
JH
48/******************************************************************************/
49 /* dummies needed by Solaris build */
fa2a928f
JH
50void
51millisleep(int msec)
52{}
53uschar *
54readconf_printtime(int t)
55{ return NULL; }
65766f1b
JH
56gstring *
57string_vformat_trc(gstring * g, const uschar * func, unsigned line,
58 unsigned size_limit, unsigned flags, const char *format, va_list ap)
59{ return NULL; }
f3a6b42d
JH
60uschar *
61string_sprintf_trc(const char * fmt, const uschar * func, unsigned line, ...)
62{ return NULL; }
e66a8a1a
JH
63BOOL
64string_format_trc(uschar * buf, int len, const uschar * func, unsigned line,
65 const char * fmt, ...)
66{ return FALSE; }
fa2a928f
JH
67
68struct global_flags f;
69unsigned int log_selector[1];
70uschar * queue_name;
71BOOL split_spool_directory;
65766f1b
JH
72/******************************************************************************/
73
059ec3d9
PH
74
75/*************************************************
76* Berkeley DB error callback *
77*************************************************/
78
79/* For Berkeley DB >= 2, we can define a function to be called in case of DB
80errors. This should help with debugging strange DB problems, e.g. getting "File
1f922db1 81exists" when you try to open a db file. The API changed at release 4.3. */
059ec3d9
PH
82
83#if defined(USE_DB) && defined(DB_VERSION_STRING)
84void
1f922db1
PH
85#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
86dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
87{
88dbenv = dbenv;
89#else
059ec3d9
PH
90dbfn_bdb_error_callback(const char *pfx, char *msg)
91{
1f922db1 92#endif
059ec3d9
PH
93pfx = pfx;
94printf("Berkeley DB error: %s\n", msg);
95}
96#endif
97
98
99
100/*************************************************
101* SIGALRM handler *
102*************************************************/
103
c99ce5c9 104SIGNAL_BOOL sigalrm_seen;
059ec3d9
PH
105
106void
107sigalrm_handler(int sig)
108{
109sig = sig; /* Keep picky compilers happy */
110sigalrm_seen = 1;
111}
112
113
114
115/*************************************************
116* Output usage message and exit *
117*************************************************/
118
119static void
120usage(uschar *name, uschar *options)
121{
122printf("Usage: exim_%s%s <spool-directory> <database-name>\n", name, options);
b10c87b3 123printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit | tls\n");
059ec3d9
PH
124exit(1);
125}
126
127
128
129/*************************************************
130* Sort out the command arguments *
131*************************************************/
132
133/* This function checks that there are exactly 2 arguments, and checks the
134second of them to be sure it is a known database name. */
135
136static int
137check_args(int argc, uschar **argv, uschar *name, uschar *options)
138{
139if (argc == 3)
140 {
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;
870f6ba8 145 if (Ustrcmp(argv[2], "ratelimit") == 0) return type_ratelimit;
b10c87b3 146 if (Ustrcmp(argv[2], "tls") == 0) return type_tls;
059ec3d9
PH
147 }
148usage(name, options);
149return -1; /* Never obeyed */
150}
151
152
153
154/*************************************************
155* Handle attempts to write the log *
156*************************************************/
157
158/* The message gets written to stderr when log_write() is called from a
159utility. The message always gets '\n' added on the end of it. These calls come
160from modules such as store.c when things go drastically wrong (e.g. malloc()
161failing). In normal use they won't get obeyed.
162
163Arguments:
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
168
169Returns: nothing
170*/
171
172void
c99ce5c9 173log_write(unsigned int selector, int flags, const char *format, ...)
059ec3d9
PH
174{
175va_list ap;
176va_start(ap, format);
177vfprintf(stderr, format, ap);
178fprintf(stderr, "\n");
179va_end(ap);
180selector = selector; /* Keep picky compilers happy */
181flags = flags;
182}
183
184
185
186/*************************************************
187* Format a time value for printing *
188*************************************************/
189
190static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss ")];
191
192uschar *
193print_time(time_t t)
194{
195struct tm *tmstr = localtime(&t);
196Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
197return time_buffer;
198}
199
200
201
202/*************************************************
203* Format a cache value for printing *
204*************************************************/
205
206uschar *
207print_cache(int value)
208{
209return (value == ccache_accept)? US"accept" :
210 (value == ccache_reject)? US"reject" :
211 US"unknown";
212}
213
214
215#ifdef EXIM_FIXDB
216/*************************************************
217* Read time value *
218*************************************************/
219
220static time_t
221read_time(uschar *s)
222{
059ec3d9
PH
223int field = 0;
224int value;
225time_t now = time(NULL);
226struct tm *tm = localtime(&now);
227
228tm->tm_sec = 0;
229tm->tm_isdst = -1;
230
d7978c0f 231for (uschar * t = s + Ustrlen(s) - 1; t >= s; t--)
059ec3d9
PH
232 {
233 if (*t == ':') continue;
234 if (!isdigit((uschar)*t)) return -1;
235
236 value = *t - '0';
237 if (--t >= s)
238 {
239 if (!isdigit((uschar)*t)) return -1;
240 value = value + (*t - '0')*10;
241 }
242
243 switch (field++)
244 {
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;
250 default: return -1;
251 }
252 }
253
254return mktime(tm);
255}
256#endif /* EXIM_FIXDB */
257
258
259
260/*************************************************
261* Open and lock a database file *
262*************************************************/
263
264/* This is a cut-down version from the function in dbfn.h that Exim itself
265uses. We assume the database exists, and therefore give up if we cannot open
266the lock file.
267
268Arguments:
059ec3d9
PH
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.
c99ce5c9 272 lof Unused.
b10c87b3 273 panic Unused
059ec3d9
PH
274
275Returns: 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.
278*/
279
c99ce5c9 280open_db *
b10c87b3 281dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof, BOOL panic)
059ec3d9
PH
282{
283int rc;
284struct flock lock_data;
285BOOL read_only = flags == O_RDONLY;
ebda598a 286uschar * dirname, * filename;
059ec3d9
PH
287
288/* The first thing to do is to open a separate file on which to lock. This
289ensures that Exim has exclusive use of the database before it even tries to
290open it. If there is a database, there should be a lock file in existence. */
291
ebda598a 292#ifdef COMPILE_UTILITY
5d520e57
JH
293if ( asprintf(CSS &dirname, "%s/db", spool_directory) < 0
294 || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
295 return NULL;
ebda598a
JH
296#else
297dirname = string_sprintf("%s/db", spool_directory);
298filename = string_sprintf("%s/%s.lockfile", dirname, name);
299#endif
059ec3d9 300
cfb9cf20 301dbblock->lockfd = Uopen(filename, flags, 0);
059ec3d9
PH
302if (dbblock->lockfd < 0)
303 {
cfb9cf20 304 printf("** Failed to open database lock file %s: %s\n", filename,
059ec3d9
PH
305 strerror(errno));
306 return NULL;
307 }
308
309/* Now we must get a lock on the opened lock file; do this with a blocking
310lock that times out. */
311
ebda598a 312lock_data.l_type = read_only ? F_RDLCK : F_WRLCK;
059ec3d9
PH
313lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
314
315sigalrm_seen = FALSE;
316os_non_restarting_signal(SIGALRM, sigalrm_handler);
c2a1bba0 317ALARM(EXIMDB_LOCK_TIMEOUT);
059ec3d9 318rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
c2a1bba0 319ALARM_CLR(0);
059ec3d9
PH
320
321if (sigalrm_seen) errno = ETIMEDOUT;
322if (rc < 0)
323 {
324 printf("** Failed to get %s lock for %s: %s",
d4ff61d1 325 flags & O_WRONLY ? "write" : "read",
cfb9cf20 326 filename,
d4ff61d1 327 errno == ETIMEDOUT ? "timed out" : strerror(errno));
f1e894f3 328 (void)close(dbblock->lockfd);
059ec3d9
PH
329 return NULL;
330 }
331
332/* At this point we have an opened and locked separate lock file, that is,
333exclusive access to the database, so we can go ahead and open it. */
334
ebda598a 335#ifdef COMPILE_UTILITY
5d520e57 336if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
ebda598a
JH
337#else
338filename = string_sprintf("%s/%s", dirname, name);
339#endif
6e0fddef 340EXIM_DBOPEN(filename, dirname, flags, 0, &dbblock->dbptr);
059ec3d9 341
13c7874e 342if (!dbblock->dbptr)
059ec3d9 343 {
cfb9cf20 344 printf("** Failed to open DBM file %s for %s:\n %s%s\n", filename,
059ec3d9
PH
345 read_only? "reading" : "writing", strerror(errno),
346 #ifdef USE_DB
347 " (or Berkeley DB error while opening)"
348 #else
349 ""
350 #endif
351 );
f1e894f3 352 (void)close(dbblock->lockfd);
059ec3d9
PH
353 return NULL;
354 }
355
356return dbblock;
357}
358
359
360
361
362/*************************************************
363* Unlock and close a database file *
364*************************************************/
365
366/* Closing a file automatically unlocks it, so after closing the database, just
367close the lock file.
368
369Argument: a pointer to an open database block
370Returns: nothing
371*/
372
c99ce5c9 373void
059ec3d9
PH
374dbfn_close(open_db *dbblock)
375{
376EXIM_DBCLOSE(dbblock->dbptr);
f1e894f3 377(void)close(dbblock->lockfd);
059ec3d9
PH
378}
379
380
381
382
383/*************************************************
384* Read from database file *
385*************************************************/
386
387/* Passing back the pointer unchanged is useless, because there is no guarantee
388of alignment. Since all the records used by Exim need to be properly aligned to
389pick out the timestamps, etc., do the copying centrally here.
390
391Arguments:
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)
395
396Returns: a pointer to the retrieved record, or
397 NULL if the record is not found
398*/
399
c99ce5c9 400void *
55414b25 401dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
059ec3d9
PH
402{
403void *yield;
404EXIM_DATUM key_datum, result_datum;
55414b25 405int klen = Ustrlen(key) + 1;
f3ebb786 406uschar * key_copy = store_get(klen, is_tainted(key));
55414b25
JH
407
408memcpy(key_copy, key, klen);
059ec3d9
PH
409
410EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
411EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */
55414b25
JH
412EXIM_DATUM_DATA(key_datum) = CS key_copy;
413EXIM_DATUM_SIZE(key_datum) = klen;
059ec3d9
PH
414
415if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
416
f3ebb786
JH
417/* Assume for now that anything stored could have been tainted. Properly
418we should store the taint status along with the data. */
419
420yield = store_get(EXIM_DATUM_SIZE(result_datum), TRUE);
059ec3d9
PH
421memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
422if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
423
424EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */
425return yield;
426}
427
428
429
430#if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
431
432/*************************************************
433* Write to database file *
434*************************************************/
435
436/*
437Arguments:
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
442
443Returns: the yield of the underlying dbm or db "write" function. If this
444 is dbm, the value is zero for OK.
445*/
446
c99ce5c9 447int
55414b25 448dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
059ec3d9
PH
449{
450EXIM_DATUM key_datum, value_datum;
451dbdata_generic *gptr = (dbdata_generic *)ptr;
55414b25 452int klen = Ustrlen(key) + 1;
f3ebb786 453uschar * key_copy = store_get(klen, is_tainted(key));
55414b25
JH
454
455memcpy(key_copy, key, klen);
059ec3d9
PH
456gptr->time_stamp = time(NULL);
457
458EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
459EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
55414b25
JH
460EXIM_DATUM_DATA(key_datum) = CS key_copy;
461EXIM_DATUM_SIZE(key_datum) = klen;
059ec3d9
PH
462EXIM_DATUM_DATA(value_datum) = CS ptr;
463EXIM_DATUM_SIZE(value_datum) = length;
464return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
465}
466
467
468
469/*************************************************
470* Delete record from database file *
471*************************************************/
472
473/*
474Arguments:
475 dbblock a pointer to an open database block
476 key the key of the record to be deleted
477
478Returns: the yield of the underlying dbm or db "delete" function.
479*/
480
c99ce5c9 481int
55414b25 482dbfn_delete(open_db *dbblock, const uschar *key)
059ec3d9 483{
55414b25 484int klen = Ustrlen(key) + 1;
f3ebb786 485uschar * key_copy = store_get(klen, is_tainted(key));
55414b25
JH
486
487memcpy(key_copy, key, klen);
059ec3d9
PH
488EXIM_DATUM key_datum;
489EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
55414b25
JH
490EXIM_DATUM_DATA(key_datum) = CS key_copy;
491EXIM_DATUM_SIZE(key_datum) = klen;
059ec3d9
PH
492return EXIM_DBDEL(dbblock->dbptr, key_datum);
493}
494
495#endif /* EXIM_TIDYDB || EXIM_FIXDB */
496
497
498
499#if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
500/*************************************************
501* Scan the keys of a database file *
502*************************************************/
503
504/*
505Arguments:
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
511
512Returns: the next record from the file, or
513 NULL if there are no more
514*/
515
c99ce5c9 516uschar *
059ec3d9
PH
517dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
518{
519EXIM_DATUM key_datum, value_datum;
520uschar *yield;
521value_datum = value_datum; /* dummy; not all db libraries use this */
522
523/* Some dbm require an initialization */
524
525if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
526
527EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
528EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
529
530yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
531 US EXIM_DATUM_DATA(key_datum) : NULL;
532
533/* Some dbm require a termination */
534
535if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
536return yield;
537}
538#endif /* EXIM_DUMPDB || EXIM_TIDYDB */
539
540
541
542#ifdef EXIM_DUMPDB
543/*************************************************
544* The exim_dumpdb main program *
545*************************************************/
546
547int
548main(int argc, char **cargv)
549{
550int dbdata_type = 0;
551int yield = 0;
552open_db dbblock;
553open_db *dbm;
554EXIM_CURSOR *cursor;
555uschar **argv = USS cargv;
059ec3d9
PH
556uschar keybuffer[1024];
557
558/* Check the arguments, and open the database */
559
560dbdata_type = check_args(argc, argv, US"dumpdb", US"");
c99ce5c9 561spool_directory = argv[1];
b10c87b3 562if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
0a6c178c 563 exit(1);
059ec3d9
PH
564
565/* Scan the file, formatting the information for each entry. Note
566that data is returned in a malloc'ed block, in order that it be
567correctly aligned. */
568
d7978c0f 569for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
0a6c178c
JH
570 key;
571 key = dbfn_scan(dbm, FALSE, &cursor))
059ec3d9
PH
572 {
573 dbdata_retry *retry;
574 dbdata_wait *wait;
575 dbdata_callout_cache *callout;
870f6ba8 576 dbdata_ratelimit *ratelimit;
c99ce5c9 577 dbdata_ratelimit_unique *rate_unique;
b10c87b3 578 dbdata_tls_session *session;
059ec3d9 579 int count_bad = 0;
d7978c0f 580 int length;
059ec3d9
PH
581 uschar *t;
582 uschar name[MESSAGE_ID_LENGTH + 1];
583 void *value;
f3ebb786 584 rmark reset_point = store_mark();
059ec3d9
PH
585
586 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
587 which might change. */
588
589 if (Ustrlen(key) > sizeof(keybuffer) - 1)
590 {
591 printf("**** Overlong key encountered: %s\n", key);
592 return 1;
593 }
594 Ustrcpy(keybuffer, key);
059ec3d9 595
0a6c178c 596 if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
059ec3d9
PH
597 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
598 "was not found in the file - something is wrong!\n",
599 CS keybuffer);
600 else
601 {
602 /* Note: don't use print_time more than once in one statement, since
603 it uses a single buffer. */
604
605 switch(dbdata_type)
606 {
607 case type_retry:
d7978c0f
JH
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)? "*" : "");
615 break;
059ec3d9
PH
616
617 case type_wait:
d7978c0f
JH
618 wait = (dbdata_wait *)value;
619 printf("%s ", keybuffer);
620 t = wait->text;
621 name[MESSAGE_ID_LENGTH] = 0;
622
623 if (wait->count > WAIT_NAME_MAX)
624 {
625 fprintf(stderr,
626 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
627 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
628 wait->count = WAIT_NAME_MAX;
629 yield = count_bad = 1;
630 }
631 for (int i = 1; i <= wait->count; i++)
632 {
633 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
634 if (count_bad && name[0] == 0) break;
635 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
636 Ustrspn(name, "0123456789"
637 "abcdefghijklmnopqrstuvwxyz"
638 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
639 {
640 fprintf(stderr,
641 "**** Data for %s corrupted: bad character in message id\n",
642 CS keybuffer);
643 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
644 fprintf(stderr, "%02x ", name[j]);
645 fprintf(stderr, "\n");
646 yield = 1;
647 break;
648 }
649 printf("%s ", name);
650 t += MESSAGE_ID_LENGTH;
651 }
652 printf("\n");
653 break;
059ec3d9
PH
654
655 case type_misc:
d7978c0f
JH
656 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
657 keybuffer);
658 break;
059ec3d9
PH
659
660 case type_callout:
d7978c0f
JH
661 callout = (dbdata_callout_cache *)value;
662
663 /* New-style address record */
664
665 if (length == sizeof(dbdata_callout_cache_address))
666 {
667 printf("%s %s callout=%s\n",
668 print_time(((dbdata_generic *)value)->time_stamp),
669 keybuffer,
670 print_cache(callout->result));
671 }
672
673 /* New-style domain record */
674
675 else if (length == sizeof(dbdata_callout_cache))
676 {
677 printf("%s %s callout=%s postmaster=%s",
678 print_time(((dbdata_generic *)value)->time_stamp),
679 keybuffer,
680 print_cache(callout->result),
681 print_cache(callout->postmaster_result));
682 if (callout->postmaster_result != ccache_unknown)
683 printf(" (%s)", print_time(callout->postmaster_stamp));
684 printf(" random=%s", print_cache(callout->random_result));
685 if (callout->random_result != ccache_unknown)
686 printf(" (%s)", print_time(callout->random_stamp));
687 printf("\n");
688 }
689
690 break;
870f6ba8
TF
691
692 case type_ratelimit:
d7978c0f
JH
693 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
694 {
695 ratelimit = (dbdata_ratelimit *)value;
696 rate_unique = (dbdata_ratelimit_unique *)value;
697 printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
698 print_time(ratelimit->time_stamp),
699 ratelimit->time_usec, ratelimit->rate,
700 print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
701 keybuffer);
702 }
703 else
704 {
705 ratelimit = (dbdata_ratelimit *)value;
706 printf("%s.%06d rate: %10.3f key: %s\n",
707 print_time(ratelimit->time_stamp),
708 ratelimit->time_usec, ratelimit->rate,
709 keybuffer);
710 }
711 break;
b10c87b3
JH
712
713 case type_tls:
714 session = (dbdata_tls_session *)value;
715 printf(" %s %.*s\n", keybuffer, length, session->session);
716 break;
059ec3d9 717 }
059ec3d9 718 }
f3ebb786 719 store_reset(reset_point);
059ec3d9
PH
720 }
721
722dbfn_close(dbm);
723return yield;
724}
725
726#endif /* EXIM_DUMPDB */
727
728
729
730
731#ifdef EXIM_FIXDB
732/*************************************************
733* The exim_fixdb main program *
734*************************************************/
735
736/* In order not to hold the database lock any longer than is necessary, each
737operation on the database uses a separate open/close call. This is expensive,
738but then using this utility is not expected to be very common. Its main use is
739to provide a way of patching up hints databases in order to run tests.
740
741Syntax of commands:
742
743(1) <record name>
744 This causes the data from the given record to be displayed, or "not found"
745 to be output. Note that in the retry database, destination names are
746 preceded by R: or T: for router or transport retry info.
747
748(2) <record name> d
749 This causes the given record to be deleted or "not found" to be output.
750
751(3) <record name> <field number> <value>
752 This sets the given value into the given field, identified by a number
753 which is output by the display command. Not all types of record can
754 be changed.
755
756(4) q
757 This exits from exim_fixdb.
758
759If the record name is omitted from (2) or (3), the previously used record name
760is re-used. */
761
762
763int main(int argc, char **cargv)
764{
765int dbdata_type;
766uschar **argv = USS cargv;
767uschar buffer[256];
768uschar name[256];
f3ebb786 769rmark reset_point;
059ec3d9
PH
770
771name[0] = 0; /* No name set */
772
773/* Sort out the database type, verify what we are working on and then process
774user requests */
775
776dbdata_type = check_args(argc, argv, US"fixdb", US"");
777printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
778
f3ebb786 779for(; (reset_point = store_mark()); store_reset(reset_point))
059ec3d9
PH
780 {
781 open_db dbblock;
782 open_db *dbm;
783 void *record;
784 dbdata_retry *retry;
785 dbdata_wait *wait;
786 dbdata_callout_cache *callout;
870f6ba8 787 dbdata_ratelimit *ratelimit;
c99ce5c9 788 dbdata_ratelimit_unique *rate_unique;
b10c87b3 789 dbdata_tls_session *session;
d7978c0f 790 int oldlength;
059ec3d9
PH
791 uschar *t;
792 uschar field[256], value[256];
793
059ec3d9
PH
794 printf("> ");
795 if (Ufgets(buffer, 256, stdin) == NULL) break;
796
797 buffer[Ustrlen(buffer)-1] = 0;
798 field[0] = value[0] = 0;
799
800 /* If the buffer contains just one digit, or just consists of "d", use the
801 previous name for an update. */
802
2695e54e 803 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
2e88a017 804 || Ustrcmp(buffer, "d") == 0)
059ec3d9
PH
805 {
806 if (name[0] == 0)
807 {
808 printf("No previous record name is set\n");
809 continue;
810 }
ff790e47 811 (void)sscanf(CS buffer, "%s %s", field, value);
059ec3d9
PH
812 }
813 else
814 {
815 name[0] = 0;
ff790e47 816 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
059ec3d9
PH
817 }
818
819 /* Handle an update request */
820
821 if (field[0] != 0)
822 {
823 int verify = 1;
c99ce5c9 824 spool_directory = argv[1];
0a6c178c 825
b10c87b3 826 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
0a6c178c 827 continue;
059ec3d9
PH
828
829 if (Ustrcmp(field, "d") == 0)
830 {
831 if (value[0] != 0) printf("unexpected value after \"d\"\n");
832 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
833 "not found" : "deleted");
834 dbfn_close(dbm);
835 continue;
836 }
837
838 else if (isdigit((uschar)field[0]))
839 {
840 int fieldno = Uatoi(field);
841 if (value[0] == 0)
842 {
843 printf("value missing\n");
844 dbfn_close(dbm);
845 continue;
846 }
847 else
848 {
849 record = dbfn_read_with_length(dbm, name, &oldlength);
850 if (record == NULL) printf("not found\n"); else
851 {
852 time_t tt;
4dc2379a 853 /*int length = 0; Stops compiler warning */
059ec3d9
PH
854
855 switch(dbdata_type)
856 {
857 case type_retry:
d7978c0f
JH
858 retry = (dbdata_retry *)record;
859 /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
860
861 switch(fieldno)
862 {
863 case 0: retry->basic_errno = Uatoi(value);
864 break;
865 case 1: retry->more_errno = Uatoi(value);
866 break;
867 case 2: if ((tt = read_time(value)) > 0) retry->first_failed = tt;
868 else printf("bad time value\n");
869 break;
870 case 3: if ((tt = read_time(value)) > 0) retry->last_try = tt;
871 else printf("bad time value\n");
872 break;
873 case 4: if ((tt = read_time(value)) > 0) retry->next_try = tt;
874 else printf("bad time value\n");
875 break;
876 case 5: if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
877 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
878 else printf("\"yes\" or \"no\" expected=n");
879 break;
880 default: printf("unknown field number\n");
881 verify = 0;
882 break;
883 }
884 break;
059ec3d9
PH
885
886 case type_wait:
d7978c0f
JH
887 printf("Can't change contents of wait database record\n");
888 break;
059ec3d9
PH
889
890 case type_misc:
d7978c0f
JH
891 printf("Can't change contents of misc database record\n");
892 break;
059ec3d9
PH
893
894 case type_callout:
d7978c0f
JH
895 callout = (dbdata_callout_cache *)record;
896 /* length = sizeof(dbdata_callout_cache); */
897 switch(fieldno)
898 {
899 case 0: callout->result = Uatoi(value);
900 break;
901 case 1: callout->postmaster_result = Uatoi(value);
902 break;
903 case 2: callout->random_result = Uatoi(value);
904 break;
905 default: printf("unknown field number\n");
906 verify = 0;
907 break;
908 }
909 break;
870f6ba8
TF
910
911 case type_ratelimit:
d7978c0f
JH
912 ratelimit = (dbdata_ratelimit *)record;
913 switch(fieldno)
914 {
915 case 0: if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
916 else printf("bad time value\n");
917 break;
918 case 1: ratelimit->time_usec = Uatoi(value);
919 break;
920 case 2: ratelimit->rate = Ustrtod(value, NULL);
921 break;
922 case 3: if (Ustrstr(name, "/unique/") != NULL
923 && oldlength >= sizeof(dbdata_ratelimit_unique))
924 {
925 rate_unique = (dbdata_ratelimit_unique *)record;
926 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
927 else printf("bad time value\n");
928 break;
929 }
930 /* else fall through */
931 case 4:
932 case 5: if (Ustrstr(name, "/unique/") != NULL
933 && oldlength >= sizeof(dbdata_ratelimit_unique))
934 {
935 /* see acl.c */
936 BOOL seen;
937 unsigned hash, hinc;
938 uschar md5sum[16];
939 md5 md5info;
940 md5_start(&md5info);
941 md5_end(&md5info, value, Ustrlen(value), md5sum);
942 hash = md5sum[0] << 0 | md5sum[1] << 8
943 | md5sum[2] << 16 | md5sum[3] << 24;
944 hinc = md5sum[4] << 0 | md5sum[5] << 8
945 | md5sum[6] << 16 | md5sum[7] << 24;
946 rate_unique = (dbdata_ratelimit_unique *)record;
947 seen = TRUE;
948 for (unsigned n = 0; n < 8; n++, hash += hinc)
949 {
950 int bit = 1 << (hash % 8);
951 int byte = (hash / 8) % rate_unique->bloom_size;
952 if ((rate_unique->bloom[byte] & bit) == 0)
953 {
954 seen = FALSE;
955 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
956 }
957 }
958 printf("%s %s\n",
959 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
960 break;
961 }
962 /* else fall through */
963 default: printf("unknown field number\n");
964 verify = 0;
965 break;
966 }
967 break;
b10c87b3
JH
968
969 case type_tls:
970 printf("Can't change contents of tls database record\n");
971 break;
059ec3d9
PH
972 }
973
c99ce5c9 974 dbfn_write(dbm, name, record, oldlength);
059ec3d9
PH
975 }
976 }
977 }
978
979 else
980 {
981 printf("field number or d expected\n");
982 verify = 0;
983 }
984
985 dbfn_close(dbm);
986 if (!verify) continue;
987 }
988
989 /* The "name" q causes an exit */
990
991 else if (Ustrcmp(name, "q") == 0) return 0;
992
993 /* Handle a read request, or verify after an update. */
994
c99ce5c9 995 spool_directory = argv[1];
b10c87b3 996 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
0a6c178c 997 continue;
059ec3d9 998
0a6c178c 999 if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
059ec3d9
PH
1000 {
1001 printf("record %s not found\n", name);
1002 name[0] = 0;
1003 }
1004 else
1005 {
1006 int count_bad = 0;
1007 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
1008 switch(dbdata_type)
1009 {
1010 case type_retry:
d7978c0f
JH
1011 retry = (dbdata_retry *)record;
1012 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
1013 printf("1 extra data: %d\n", retry->more_errno);
1014 printf("2 first failed: %s\n", print_time(retry->first_failed));
1015 printf("3 last try: %s\n", print_time(retry->last_try));
1016 printf("4 next try: %s\n", print_time(retry->next_try));
1017 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
1018 break;
059ec3d9
PH
1019
1020 case type_wait:
d7978c0f
JH
1021 wait = (dbdata_wait *)record;
1022 t = wait->text;
1023 printf("Sequence: %d\n", wait->sequence);
1024 if (wait->count > WAIT_NAME_MAX)
1025 {
1026 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
1027 wait->count, WAIT_NAME_MAX);
1028 wait->count = WAIT_NAME_MAX;
1029 count_bad = 1;
1030 }
1031 for (int i = 1; i <= wait->count; i++)
1032 {
1033 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1034 value[MESSAGE_ID_LENGTH] = 0;
1035 if (count_bad && value[0] == 0) break;
1036 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1037 Ustrspn(value, "0123456789"
1038 "abcdefghijklmnopqrstuvwxyz"
1039 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1040 {
1041 printf("\n**** Data corrupted: bad character in message id ****\n");
1042 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
1043 printf("%02x ", value[j]);
1044 printf("\n");
1045 break;
1046 }
1047 printf("%s ", value);
1048 t += MESSAGE_ID_LENGTH;
1049 }
1050 printf("\n");
1051 break;
059ec3d9
PH
1052
1053 case type_misc:
d7978c0f 1054 break;
059ec3d9
PH
1055
1056 case type_callout:
d7978c0f
JH
1057 callout = (dbdata_callout_cache *)record;
1058 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1059 callout->result);
1060 if (oldlength > sizeof(dbdata_callout_cache_address))
1061 {
1062 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1063 callout->postmaster_result);
1064 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1065 callout->random_result);
1066 }
1067 break;
870f6ba8
TF
1068
1069 case type_ratelimit:
d7978c0f
JH
1070 ratelimit = (dbdata_ratelimit *)record;
1071 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1072 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1073 printf("2 sender rate: % .3f\n", ratelimit->rate);
1074 if (Ustrstr(name, "/unique/") != NULL
1075 && oldlength >= sizeof(dbdata_ratelimit_unique))
1076 {
1077 rate_unique = (dbdata_ratelimit_unique *)record;
1078 printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1079 printf("4 test filter membership\n");
1080 printf("5 add element to filter\n");
1081 }
1082 break;
b10c87b3
JH
1083
1084 case type_tls:
1085 session = (dbdata_tls_session *)value;
1086 printf("0 time stamp: %s\n", print_time(session->time_stamp));
1087 printf("1 session: .%s\n", session->session);
1088 break;
059ec3d9
PH
1089 }
1090 }
1091
1092 /* The database is closed after each request */
1093
1094 dbfn_close(dbm);
1095 }
1096
1097printf("\n");
1098return 0;
1099}
1100
1101#endif /* EXIM_FIXDB */
1102
1103
1104
1105#ifdef EXIM_TIDYDB
1106/*************************************************
1107* The exim_tidydb main program *
1108*************************************************/
1109
1110
1111/* Utility program to tidy the contents of an exim database file. There is one
1112option:
1113
1114 -t <time> expiry time for old records - default 30 days
1115
1116For backwards compatibility, an -f option is recognized and ignored. (It used
1117to request a "full" tidy. This version always does the whole job.) */
1118
1119
1120typedef struct key_item {
1121 struct key_item *next;
1122 uschar key[1];
1123} key_item;
1124
1125
1126int main(int argc, char **cargv)
1127{
1128struct stat statbuf;
1129int maxkeep = 30 * 24 * 60 * 60;
1130int dbdata_type, i, oldest, path_len;
1131key_item *keychain = NULL;
f3ebb786 1132rmark reset_point;
059ec3d9
PH
1133open_db dbblock;
1134open_db *dbm;
1135EXIM_CURSOR *cursor;
1136uschar **argv = USS cargv;
1137uschar buffer[256];
1138uschar *key;
1139
1140/* Scan the options */
1141
1142for (i = 1; i < argc; i++)
1143 {
1144 if (argv[i][0] != '-') break;
1145 if (Ustrcmp(argv[i], "-f") == 0) continue;
1146 if (Ustrcmp(argv[i], "-t") == 0)
1147 {
1148 uschar *s;
1149 s = argv[++i];
1150 maxkeep = 0;
1151 while (*s != 0)
1152 {
1153 int value, count;
1154 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1155 (void)sscanf(CS s, "%d%n", &value, &count);
1156 s += count;
1157 switch (*s)
1158 {
1159 case 'w': value *= 7;
1160 case 'd': value *= 24;
1161 case 'h': value *= 60;
1162 case 'm': value *= 60;
1163 case 's': s++;
1164 break;
1165 default: usage(US"tidydb", US" [-t <time>]");
1166 }
1167 maxkeep += value;
1168 }
1169 }
1170 else usage(US"tidydb", US" [-t <time>]");
1171 }
1172
1173/* Adjust argument values and process arguments */
1174
1175argc -= --i;
1176argv += i;
1177
1178dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1179
1180/* Compute the oldest keep time, verify what we are doing, and open the
1181database */
1182
1183oldest = time(NULL) - maxkeep;
1184printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1185
c99ce5c9 1186spool_directory = argv[1];
b10c87b3 1187if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
0a6c178c 1188 exit(1);
059ec3d9
PH
1189
1190/* Prepare for building file names */
1191
1192sprintf(CS buffer, "%s/input/", argv[1]);
1193path_len = Ustrlen(buffer);
1194
1195
1196/* It appears, by experiment, that it is a bad idea to make changes
1197to the file while scanning it. Pity the man page doesn't warn you about that.
1198Therefore, we scan and build a list of all the keys. Then we use that to
1199read the records and possibly update them. */
1200
0a6c178c
JH
1201for (key = dbfn_scan(dbm, TRUE, &cursor);
1202 key;
1203 key = dbfn_scan(dbm, FALSE, &cursor))
059ec3d9 1204 {
f3ebb786 1205 key_item *k = store_get(sizeof(key_item) + Ustrlen(key), is_tainted(key));
059ec3d9
PH
1206 k->next = keychain;
1207 keychain = k;
1208 Ustrcpy(k->key, key);
059ec3d9
PH
1209 }
1210
1211/* Now scan the collected keys and operate on the records, resetting
1212the store each time round. */
1213
f3ebb786 1214for (; keychain && (reset_point = store_mark()); store_reset(reset_point))
059ec3d9
PH
1215 {
1216 dbdata_generic *value;
1217
059ec3d9
PH
1218 key = keychain->key;
1219 keychain = keychain->next;
1220 value = dbfn_read_with_length(dbm, key, NULL);
1221
1222 /* A continuation record may have been deleted or renamed already, so
1223 non-existence is not serious. */
1224
1225 if (value == NULL) continue;
1226
1227 /* Delete if too old */
1228
1229 if (value->time_stamp < oldest)
1230 {
1231 printf("deleted %s (too old)\n", key);
1232 dbfn_delete(dbm, key);
1233 continue;
1234 }
1235
1236 /* Do database-specific tidying for wait databases, and message-
1237 specific tidying for the retry database. */
1238
1239 if (dbdata_type == type_wait)
1240 {
1241 dbdata_wait *wait = (dbdata_wait *)value;
1242 BOOL update = FALSE;
1243
1244 /* Leave corrupt records alone */
1245
1246 if (wait->count > WAIT_NAME_MAX)
1247 {
1248 printf("**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
1249 key, wait->count, wait->count, WAIT_NAME_MAX);
1250 continue;
1251 }
1252
1253 /* Loop for renamed continuation records. For each message id,
1254 check to see if the message exists, and if not, remove its entry
1255 from the record. Because of the possibility of split input directories,
1256 we must look in both possible places for a -D file. */
1257
1258 for (;;)
1259 {
059ec3d9
PH
1260 int length = wait->count * MESSAGE_ID_LENGTH;
1261
d7978c0f 1262 for (int offset = length - MESSAGE_ID_LENGTH;
059ec3d9
PH
1263 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1264 {
1265 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1266 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1267
1268 if (Ustat(buffer, &statbuf) != 0)
1269 {
1270 buffer[path_len] = wait->text[offset+5];
1271 buffer[path_len+1] = '/';
1272 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1273 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1274
1275 if (Ustat(buffer, &statbuf) != 0)
1276 {
1277 int left = length - offset - MESSAGE_ID_LENGTH;
1278 if (left > 0) Ustrncpy(wait->text + offset,
1279 wait->text + offset + MESSAGE_ID_LENGTH, left);
1280 wait->count--;
1281 length -= MESSAGE_ID_LENGTH;
1282 update = TRUE;
1283 }
1284 }
1285 }
1286
1287 /* If record is empty and the main record, either delete it or rename
1288 the next continuation, repeating if that is also empty. */
1289
1290 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1291 {
1292 while (wait->count == 0 && wait->sequence > 0)
1293 {
1294 uschar newkey[256];
1295 dbdata_generic *newvalue;
1296 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1297 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1298 if (newvalue != NULL)
1299 {
1300 value = newvalue;
1301 wait = (dbdata_wait *)newvalue;
1302 dbfn_delete(dbm, newkey);
1303 printf("renamed %s\n", newkey);
1304 update = TRUE;
1305 }
1306 else wait->sequence--;
1307 }
1308
1309 /* If we have ended up with an empty main record, delete it
1310 and break the loop. Otherwise the new record will be scanned. */
1311
1312 if (wait->count == 0 && wait->sequence == 0)
1313 {
1314 dbfn_delete(dbm, key);
1315 printf("deleted %s (empty)\n", key);
1316 update = FALSE;
1317 break;
1318 }
1319 }
1320
1321 /* If not an empty main record, break the loop */
1322
1323 else break;
1324 }
1325
1326 /* Re-write the record if required */
1327
1328 if (update)
1329 {
1330 printf("updated %s\n", key);
1331 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1332 wait->count * MESSAGE_ID_LENGTH);
1333 }
1334 }
1335
1336 /* If a retry record's key ends with a message-id, check that that message
1337 still exists; if not, remove this record. */
1338
1339 else if (dbdata_type == type_retry)
1340 {
1341 uschar *id;
1342 int len = Ustrlen(key);
1343
1344 if (len < MESSAGE_ID_LENGTH + 1) continue;
1345 id = key + len - MESSAGE_ID_LENGTH - 1;
1346 if (*id++ != ':') continue;
1347
1348 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
059ec3d9
PH
1349 if (i == 6 || i == 13)
1350 { if (id[i] != '-') break; }
1351 else
1352 { if (!isalnum(id[i])) break; }
059ec3d9
PH
1353 if (i < MESSAGE_ID_LENGTH) continue;
1354
1355 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1356 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1357
1358 if (Ustat(buffer, &statbuf) != 0)
1359 {
0ec020ea
PH
1360 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1361 if (Ustat(buffer, &statbuf) != 0)
1362 {
1363 dbfn_delete(dbm, key);
1364 printf("deleted %s (no message)\n", key);
1365 }
059ec3d9
PH
1366 }
1367 }
1368 }
1369
1370dbfn_close(dbm);
1371printf("Tidying complete\n");
1372return 0;
1373}
1374
1375#endif /* EXIM_TIDYDB */
1376
1377/* End of exim_dbutil.c */