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