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