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