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