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