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