1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
6 /* See the file NOTICE for conditions of use and distribution. */
9 /* This single source file is used to compile three utility programs for
10 maintaining Exim hints databases.
12 exim_dumpdb dumps out the contents
13 exim_fixdb patches the database (really for Exim maintenance/testing)
14 exim_tidydb removed obsolete data
16 In all cases, the first argument is the name of the spool directory. The second
17 argument is the name of the database file. The available names are:
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
25 There are a number of common subroutines, followed by three main programs,
26 whose inclusion is controlled by -D on the compilation command. */
32 /* Identifiers for the different database types. */
37 #define type_callout 4
38 #define type_ratelimit 5
42 /* This is used by our cut-down dbfn_open(). */
44 uschar
*spool_directory
;
47 /******************************************************************************/
48 /* dummies needed by Solaris build */
52 readconf_printtime(int t
)
55 string_vformat_trc(gstring
* g
, const uschar
* func
, unsigned line
,
56 unsigned size_limit
, unsigned flags
, const char *format
, va_list ap
)
59 string_sprintf_trc(const char * fmt
, const uschar
* func
, unsigned line
, ...)
62 struct global_flags f
;
63 unsigned int log_selector
[1];
65 BOOL split_spool_directory
;
66 /******************************************************************************/
69 /*************************************************
70 * Berkeley DB error callback *
71 *************************************************/
73 /* For Berkeley DB >= 2, we can define a function to be called in case of DB
74 errors. This should help with debugging strange DB problems, e.g. getting "File
75 exists" when you try to open a db file. The API changed at release 4.3. */
77 #if defined(USE_DB) && defined(DB_VERSION_STRING)
79 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
80 dbfn_bdb_error_callback(const DB_ENV
*dbenv
, const char *pfx
, const char *msg
)
84 dbfn_bdb_error_callback(const char *pfx
, char *msg
)
88 printf("Berkeley DB error: %s\n", msg
);
94 /*************************************************
96 *************************************************/
98 SIGNAL_BOOL sigalrm_seen
;
101 sigalrm_handler(int sig
)
103 sig
= sig
; /* Keep picky compilers happy */
109 /*************************************************
110 * Output usage message and exit *
111 *************************************************/
114 usage(uschar
*name
, uschar
*options
)
116 printf("Usage: exim_%s%s <spool-directory> <database-name>\n", name
, options
);
117 printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit | tls\n");
123 /*************************************************
124 * Sort out the command arguments *
125 *************************************************/
127 /* This function checks that there are exactly 2 arguments, and checks the
128 second of them to be sure it is a known database name. */
131 check_args(int argc
, uschar
**argv
, uschar
*name
, uschar
*options
)
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
;
139 if (Ustrcmp(argv
[2], "ratelimit") == 0) return type_ratelimit
;
140 if (Ustrcmp(argv
[2], "tls") == 0) return type_tls
;
142 usage(name
, options
);
143 return -1; /* Never obeyed */
148 /*************************************************
149 * Handle attempts to write the log *
150 *************************************************/
152 /* The message gets written to stderr when log_write() is called from a
153 utility. The message always gets '\n' added on the end of it. These calls come
154 from modules such as store.c when things go drastically wrong (e.g. malloc()
155 failing). In normal use they won't get obeyed.
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
167 log_write(unsigned int selector
, int flags
, const char *format
, ...)
170 va_start(ap
, format
);
171 vfprintf(stderr
, format
, ap
);
172 fprintf(stderr
, "\n");
174 selector
= selector
; /* Keep picky compilers happy */
180 /*************************************************
181 * Format a time value for printing *
182 *************************************************/
184 static uschar time_buffer
[sizeof("09-xxx-1999 hh:mm:ss ")];
189 struct tm
*tmstr
= localtime(&t
);
190 Ustrftime(time_buffer
, sizeof(time_buffer
), "%d-%b-%Y %H:%M:%S", tmstr
);
196 /*************************************************
197 * Format a cache value for printing *
198 *************************************************/
201 print_cache(int value
)
203 return (value
== ccache_accept
)? US
"accept" :
204 (value
== ccache_reject
)? US
"reject" :
210 /*************************************************
212 *************************************************/
219 time_t now
= time(NULL
);
220 struct tm
*tm
= localtime(&now
);
225 for (uschar
* t
= s
+ Ustrlen(s
) - 1; t
>= s
; t
--)
227 if (*t
== ':') continue;
228 if (!isdigit((uschar
)*t
)) return -1;
233 if (!isdigit((uschar
)*t
)) return -1;
234 value
= value
+ (*t
- '0')*10;
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;
250 #endif /* EXIM_FIXDB */
254 /*************************************************
255 * Open and lock a database file *
256 *************************************************/
258 /* This is a cut-down version from the function in dbfn.h that Exim itself
259 uses. We assume the database exists, and therefore give up if we cannot open
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.
269 Returns: 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.
275 dbfn_open(uschar
*name
, int flags
, open_db
*dbblock
, BOOL lof
, BOOL panic
)
278 struct flock lock_data
;
279 BOOL read_only
= flags
== O_RDONLY
;
280 uschar
* dirname
, * filename
;
282 /* The first thing to do is to open a separate file on which to lock. This
283 ensures that Exim has exclusive use of the database before it even tries to
284 open it. If there is a database, there should be a lock file in existence. */
286 #ifdef COMPILE_UTILITY
287 if ( asprintf(CSS
&dirname
, "%s/db", spool_directory
) < 0
288 || asprintf(CSS
&filename
, "%s/%s.lockfile", dirname
, name
) < 0)
291 dirname
= string_sprintf("%s/db", spool_directory
);
292 filename
= string_sprintf("%s/%s.lockfile", dirname
, name
);
295 dbblock
->lockfd
= Uopen(filename
, flags
, 0);
296 if (dbblock
->lockfd
< 0)
298 printf("** Failed to open database lock file %s: %s\n", filename
,
303 /* Now we must get a lock on the opened lock file; do this with a blocking
304 lock that times out. */
306 lock_data
.l_type
= read_only
? F_RDLCK
: F_WRLCK
;
307 lock_data
.l_whence
= lock_data
.l_start
= lock_data
.l_len
= 0;
309 sigalrm_seen
= FALSE
;
310 os_non_restarting_signal(SIGALRM
, sigalrm_handler
);
311 ALARM(EXIMDB_LOCK_TIMEOUT
);
312 rc
= fcntl(dbblock
->lockfd
, F_SETLKW
, &lock_data
);
315 if (sigalrm_seen
) errno
= ETIMEDOUT
;
318 printf("** Failed to get %s lock for %s: %s",
319 flags
& O_WRONLY
? "write" : "read",
321 errno
== ETIMEDOUT
? "timed out" : strerror(errno
));
322 (void)close(dbblock
->lockfd
);
326 /* At this point we have an opened and locked separate lock file, that is,
327 exclusive access to the database, so we can go ahead and open it. */
329 #ifdef COMPILE_UTILITY
330 if (asprintf(CSS
&filename
, "%s/%s", dirname
, name
) < 0) return NULL
;
332 filename
= string_sprintf("%s/%s", dirname
, name
);
334 EXIM_DBOPEN(filename
, dirname
, flags
, 0, &(dbblock
->dbptr
));
338 printf("** Failed to open DBM file %s for %s:\n %s%s\n", filename
,
339 read_only
? "reading" : "writing", strerror(errno
),
341 " (or Berkeley DB error while opening)"
346 (void)close(dbblock
->lockfd
);
356 /*************************************************
357 * Unlock and close a database file *
358 *************************************************/
360 /* Closing a file automatically unlocks it, so after closing the database, just
363 Argument: a pointer to an open database block
368 dbfn_close(open_db
*dbblock
)
370 EXIM_DBCLOSE(dbblock
->dbptr
);
371 (void)close(dbblock
->lockfd
);
377 /*************************************************
378 * Read from database file *
379 *************************************************/
381 /* Passing back the pointer unchanged is useless, because there is no guarantee
382 of alignment. Since all the records used by Exim need to be properly aligned to
383 pick out the timestamps, etc., do the copying centrally here.
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)
390 Returns: a pointer to the retrieved record, or
391 NULL if the record is not found
395 dbfn_read_with_length(open_db
*dbblock
, const uschar
*key
, int *length
)
398 EXIM_DATUM key_datum
, result_datum
;
399 int klen
= Ustrlen(key
) + 1;
400 uschar
* key_copy
= store_get(klen
, is_tainted(key
));
402 memcpy(key_copy
, key
, klen
);
404 EXIM_DATUM_INIT(key_datum
); /* Some DBM libraries require the datum */
405 EXIM_DATUM_INIT(result_datum
); /* to be cleared before use. */
406 EXIM_DATUM_DATA(key_datum
) = CS key_copy
;
407 EXIM_DATUM_SIZE(key_datum
) = klen
;
409 if (!EXIM_DBGET(dbblock
->dbptr
, key_datum
, result_datum
)) return NULL
;
411 /* Assume for now that anything stored could have been tainted. Properly
412 we should store the taint status along with the data. */
414 yield
= store_get(EXIM_DATUM_SIZE(result_datum
), TRUE
);
415 memcpy(yield
, EXIM_DATUM_DATA(result_datum
), EXIM_DATUM_SIZE(result_datum
));
416 if (length
!= NULL
) *length
= EXIM_DATUM_SIZE(result_datum
);
418 EXIM_DATUM_FREE(result_datum
); /* Some DBM libs require freeing */
424 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
426 /*************************************************
427 * Write to database file *
428 *************************************************/
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
437 Returns: the yield of the underlying dbm or db "write" function. If this
438 is dbm, the value is zero for OK.
442 dbfn_write(open_db
*dbblock
, const uschar
*key
, void *ptr
, int length
)
444 EXIM_DATUM key_datum
, value_datum
;
445 dbdata_generic
*gptr
= (dbdata_generic
*)ptr
;
446 int klen
= Ustrlen(key
) + 1;
447 uschar
* key_copy
= store_get(klen
, is_tainted(key
));
449 memcpy(key_copy
, key
, klen
);
450 gptr
->time_stamp
= time(NULL
);
452 EXIM_DATUM_INIT(key_datum
); /* Some DBM libraries require the datum */
453 EXIM_DATUM_INIT(value_datum
); /* to be cleared before use. */
454 EXIM_DATUM_DATA(key_datum
) = CS key_copy
;
455 EXIM_DATUM_SIZE(key_datum
) = klen
;
456 EXIM_DATUM_DATA(value_datum
) = CS ptr
;
457 EXIM_DATUM_SIZE(value_datum
) = length
;
458 return EXIM_DBPUT(dbblock
->dbptr
, key_datum
, value_datum
);
463 /*************************************************
464 * Delete record from database file *
465 *************************************************/
469 dbblock a pointer to an open database block
470 key the key of the record to be deleted
472 Returns: the yield of the underlying dbm or db "delete" function.
476 dbfn_delete(open_db
*dbblock
, const uschar
*key
)
478 int klen
= Ustrlen(key
) + 1;
479 uschar
* key_copy
= store_get(klen
, is_tainted(key
));
481 memcpy(key_copy
, key
, klen
);
482 EXIM_DATUM key_datum
;
483 EXIM_DATUM_INIT(key_datum
); /* Some DBM libraries require clearing */
484 EXIM_DATUM_DATA(key_datum
) = CS key_copy
;
485 EXIM_DATUM_SIZE(key_datum
) = klen
;
486 return EXIM_DBDEL(dbblock
->dbptr
, key_datum
);
489 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
493 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
494 /*************************************************
495 * Scan the keys of a database file *
496 *************************************************/
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
506 Returns: the next record from the file, or
507 NULL if there are no more
511 dbfn_scan(open_db
*dbblock
, BOOL start
, EXIM_CURSOR
**cursor
)
513 EXIM_DATUM key_datum
, value_datum
;
515 value_datum
= value_datum
; /* dummy; not all db libraries use this */
517 /* Some dbm require an initialization */
519 if (start
) EXIM_DBCREATE_CURSOR(dbblock
->dbptr
, cursor
);
521 EXIM_DATUM_INIT(key_datum
); /* Some DBM libraries require the datum */
522 EXIM_DATUM_INIT(value_datum
); /* to be cleared before use. */
524 yield
= (EXIM_DBSCAN(dbblock
->dbptr
, key_datum
, value_datum
, start
, *cursor
))?
525 US
EXIM_DATUM_DATA(key_datum
) : NULL
;
527 /* Some dbm require a termination */
529 if (!yield
) EXIM_DBDELETE_CURSOR(*cursor
);
532 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
537 /*************************************************
538 * The exim_dumpdb main program *
539 *************************************************/
542 main(int argc
, char **cargv
)
549 uschar
**argv
= USS cargv
;
550 uschar keybuffer
[1024];
552 /* Check the arguments, and open the database */
554 dbdata_type
= check_args(argc
, argv
, US
"dumpdb", US
"");
555 spool_directory
= argv
[1];
556 if (!(dbm
= dbfn_open(argv
[2], O_RDONLY
, &dbblock
, FALSE
, TRUE
)))
559 /* Scan the file, formatting the information for each entry. Note
560 that data is returned in a malloc'ed block, in order that it be
561 correctly aligned. */
563 for (uschar
* key
= dbfn_scan(dbm
, TRUE
, &cursor
);
565 key
= dbfn_scan(dbm
, FALSE
, &cursor
))
569 dbdata_callout_cache
*callout
;
570 dbdata_ratelimit
*ratelimit
;
571 dbdata_ratelimit_unique
*rate_unique
;
572 dbdata_tls_session
*session
;
576 uschar name
[MESSAGE_ID_LENGTH
+ 1];
578 rmark reset_point
= store_mark();
580 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
581 which might change. */
583 if (Ustrlen(key
) > sizeof(keybuffer
) - 1)
585 printf("**** Overlong key encountered: %s\n", key
);
588 Ustrcpy(keybuffer
, key
);
590 if (!(value
= dbfn_read_with_length(dbm
, keybuffer
, &length
)))
591 fprintf(stderr
, "**** Entry \"%s\" was in the key scan, but the record "
592 "was not found in the file - something is wrong!\n",
596 /* Note: don't use print_time more than once in one statement, since
597 it uses a single buffer. */
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
)? "*" : "");
612 wait
= (dbdata_wait
*)value
;
613 printf("%s ", keybuffer
);
615 name
[MESSAGE_ID_LENGTH
] = 0;
617 if (wait
->count
> WAIT_NAME_MAX
)
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;
625 for (int i
= 1; i
<= wait
->count
; i
++)
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
)
635 "**** Data for %s corrupted: bad character in message id\n",
637 for (int j
= 0; j
< MESSAGE_ID_LENGTH
; j
++)
638 fprintf(stderr
, "%02x ", name
[j
]);
639 fprintf(stderr
, "\n");
644 t
+= MESSAGE_ID_LENGTH
;
650 printf("%s %s\n", print_time(((dbdata_generic
*)value
)->time_stamp
),
655 callout
= (dbdata_callout_cache
*)value
;
657 /* New-style address record */
659 if (length
== sizeof(dbdata_callout_cache_address
))
661 printf("%s %s callout=%s\n",
662 print_time(((dbdata_generic
*)value
)->time_stamp
),
664 print_cache(callout
->result
));
667 /* New-style domain record */
669 else if (length
== sizeof(dbdata_callout_cache
))
671 printf("%s %s callout=%s postmaster=%s",
672 print_time(((dbdata_generic
*)value
)->time_stamp
),
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
));
687 if (Ustrstr(key
, "/unique/") != NULL
&& length
>= sizeof(*rate_unique
))
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
,
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
,
708 session
= (dbdata_tls_session
*)value
;
709 printf(" %s %.*s\n", keybuffer
, length
, session
->session
);
713 store_reset(reset_point
);
720 #endif /* EXIM_DUMPDB */
726 /*************************************************
727 * The exim_fixdb main program *
728 *************************************************/
730 /* In order not to hold the database lock any longer than is necessary, each
731 operation on the database uses a separate open/close call. This is expensive,
732 but then using this utility is not expected to be very common. Its main use is
733 to provide a way of patching up hints databases in order to run tests.
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.
743 This causes the given record to be deleted or "not found" to be output.
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
751 This exits from exim_fixdb.
753 If the record name is omitted from (2) or (3), the previously used record name
757 int main(int argc
, char **cargv
)
760 uschar
**argv
= USS cargv
;
765 name
[0] = 0; /* No name set */
767 /* Sort out the database type, verify what we are working on and then process
770 dbdata_type
= check_args(argc
, argv
, US
"fixdb", US
"");
771 printf("Modifying Exim hints database %s/db/%s\n", argv
[1], argv
[2]);
773 for(; (reset_point
= store_mark()); store_reset(reset_point
))
780 dbdata_callout_cache
*callout
;
781 dbdata_ratelimit
*ratelimit
;
782 dbdata_ratelimit_unique
*rate_unique
;
783 dbdata_tls_session
*session
;
786 uschar field
[256], value
[256];
789 if (Ufgets(buffer
, 256, stdin
) == NULL
) break;
791 buffer
[Ustrlen(buffer
)-1] = 0;
792 field
[0] = value
[0] = 0;
794 /* If the buffer contains just one digit, or just consists of "d", use the
795 previous name for an update. */
797 if ((isdigit((uschar
)buffer
[0]) && (buffer
[1] == ' ' || buffer
[1] == '\0'))
798 || Ustrcmp(buffer
, "d") == 0)
802 printf("No previous record name is set\n");
805 (void)sscanf(CS buffer
, "%s %s", field
, value
);
810 (void)sscanf(CS buffer
, "%s %s %s", name
, field
, value
);
813 /* Handle an update request */
818 spool_directory
= argv
[1];
820 if (!(dbm
= dbfn_open(argv
[2], O_RDWR
, &dbblock
, FALSE
, TRUE
)))
823 if (Ustrcmp(field
, "d") == 0)
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");
832 else if (isdigit((uschar
)field
[0]))
834 int fieldno
= Uatoi(field
);
837 printf("value missing\n");
843 record
= dbfn_read_with_length(dbm
, name
, &oldlength
);
844 if (record
== NULL
) printf("not found\n"); else
847 /*int length = 0; Stops compiler warning */
852 retry
= (dbdata_retry
*)record
;
853 /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
857 case 0: retry
->basic_errno
= Uatoi(value
);
859 case 1: retry
->more_errno
= Uatoi(value
);
861 case 2: if ((tt
= read_time(value
)) > 0) retry
->first_failed
= tt
;
862 else printf("bad time value\n");
864 case 3: if ((tt
= read_time(value
)) > 0) retry
->last_try
= tt
;
865 else printf("bad time value\n");
867 case 4: if ((tt
= read_time(value
)) > 0) retry
->next_try
= tt
;
868 else printf("bad time value\n");
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");
874 default: printf("unknown field number\n");
881 printf("Can't change contents of wait database record\n");
885 printf("Can't change contents of misc database record\n");
889 callout
= (dbdata_callout_cache
*)record
;
890 /* length = sizeof(dbdata_callout_cache); */
893 case 0: callout
->result
= Uatoi(value
);
895 case 1: callout
->postmaster_result
= Uatoi(value
);
897 case 2: callout
->random_result
= Uatoi(value
);
899 default: printf("unknown field number\n");
906 ratelimit
= (dbdata_ratelimit
*)record
;
909 case 0: if ((tt
= read_time(value
)) > 0) ratelimit
->time_stamp
= tt
;
910 else printf("bad time value\n");
912 case 1: ratelimit
->time_usec
= Uatoi(value
);
914 case 2: ratelimit
->rate
= Ustrtod(value
, NULL
);
916 case 3: if (Ustrstr(name
, "/unique/") != NULL
917 && oldlength
>= sizeof(dbdata_ratelimit_unique
))
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");
924 /* else fall through */
926 case 5: if (Ustrstr(name
, "/unique/") != NULL
927 && oldlength
>= sizeof(dbdata_ratelimit_unique
))
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
;
942 for (unsigned n
= 0; n
< 8; n
++, hash
+= hinc
)
944 int bit
= 1 << (hash
% 8);
945 int byte
= (hash
/ 8) % rate_unique
->bloom_size
;
946 if ((rate_unique
->bloom
[byte
] & bit
) == 0)
949 if (fieldno
== 5) rate_unique
->bloom
[byte
] |= bit
;
953 seen
? "seen" : fieldno
== 5 ? "added" : "unseen", value
);
956 /* else fall through */
957 default: printf("unknown field number\n");
964 printf("Can't change contents of tls database record\n");
968 dbfn_write(dbm
, name
, record
, oldlength
);
975 printf("field number or d expected\n");
980 if (!verify
) continue;
983 /* The "name" q causes an exit */
985 else if (Ustrcmp(name
, "q") == 0) return 0;
987 /* Handle a read request, or verify after an update. */
989 spool_directory
= argv
[1];
990 if (!(dbm
= dbfn_open(argv
[2], O_RDONLY
, &dbblock
, FALSE
, TRUE
)))
993 if (!(record
= dbfn_read_with_length(dbm
, name
, &oldlength
)))
995 printf("record %s not found\n", name
);
1001 printf("%s\n", CS
print_time(((dbdata_generic
*)record
)->time_stamp
));
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");
1015 wait
= (dbdata_wait
*)record
;
1017 printf("Sequence: %d\n", wait
->sequence
);
1018 if (wait
->count
> WAIT_NAME_MAX
)
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
;
1025 for (int i
= 1; i
<= wait
->count
; i
++)
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
)
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
]);
1041 printf("%s ", value
);
1042 t
+= MESSAGE_ID_LENGTH
;
1051 callout
= (dbdata_callout_cache
*)record
;
1052 printf("0 callout: %s (%d)\n", print_cache(callout
->result
),
1054 if (oldlength
> sizeof(dbdata_callout_cache_address
))
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
);
1063 case type_ratelimit
:
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
))
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");
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
);
1086 /* The database is closed after each request */
1095 #endif /* EXIM_FIXDB */
1100 /*************************************************
1101 * The exim_tidydb main program *
1102 *************************************************/
1105 /* Utility program to tidy the contents of an exim database file. There is one
1108 -t <time> expiry time for old records - default 30 days
1110 For backwards compatibility, an -f option is recognized and ignored. (It used
1111 to request a "full" tidy. This version always does the whole job.) */
1114 typedef struct key_item
{
1115 struct key_item
*next
;
1120 int main(int argc
, char **cargv
)
1122 struct stat statbuf
;
1123 int maxkeep
= 30 * 24 * 60 * 60;
1124 int dbdata_type
, i
, oldest
, path_len
;
1125 key_item
*keychain
= NULL
;
1129 EXIM_CURSOR
*cursor
;
1130 uschar
**argv
= USS cargv
;
1134 /* Scan the options */
1136 for (i
= 1; i
< argc
; i
++)
1138 if (argv
[i
][0] != '-') break;
1139 if (Ustrcmp(argv
[i
], "-f") == 0) continue;
1140 if (Ustrcmp(argv
[i
], "-t") == 0)
1148 if (!isdigit(*s
)) usage(US
"tidydb", US
" [-t <time>]");
1149 (void)sscanf(CS s
, "%d%n", &value
, &count
);
1153 case 'w': value
*= 7;
1154 case 'd': value
*= 24;
1155 case 'h': value
*= 60;
1156 case 'm': value
*= 60;
1159 default: usage(US
"tidydb", US
" [-t <time>]");
1164 else usage(US
"tidydb", US
" [-t <time>]");
1167 /* Adjust argument values and process arguments */
1172 dbdata_type
= check_args(argc
, argv
, US
"tidydb", US
" [-t <time>]");
1174 /* Compute the oldest keep time, verify what we are doing, and open the
1177 oldest
= time(NULL
) - maxkeep
;
1178 printf("Tidying Exim hints database %s/db/%s\n", argv
[1], argv
[2]);
1180 spool_directory
= argv
[1];
1181 if (!(dbm
= dbfn_open(argv
[2], O_RDWR
, &dbblock
, FALSE
, TRUE
)))
1184 /* Prepare for building file names */
1186 sprintf(CS buffer
, "%s/input/", argv
[1]);
1187 path_len
= Ustrlen(buffer
);
1190 /* It appears, by experiment, that it is a bad idea to make changes
1191 to the file while scanning it. Pity the man page doesn't warn you about that.
1192 Therefore, we scan and build a list of all the keys. Then we use that to
1193 read the records and possibly update them. */
1195 for (key
= dbfn_scan(dbm
, TRUE
, &cursor
);
1197 key
= dbfn_scan(dbm
, FALSE
, &cursor
))
1199 key_item
*k
= store_get(sizeof(key_item
) + Ustrlen(key
), is_tainted(key
));
1202 Ustrcpy(k
->key
, key
);
1205 /* Now scan the collected keys and operate on the records, resetting
1206 the store each time round. */
1208 for (; keychain
&& (reset_point
= store_mark()); store_reset(reset_point
))
1210 dbdata_generic
*value
;
1212 key
= keychain
->key
;
1213 keychain
= keychain
->next
;
1214 value
= dbfn_read_with_length(dbm
, key
, NULL
);
1216 /* A continuation record may have been deleted or renamed already, so
1217 non-existence is not serious. */
1219 if (value
== NULL
) continue;
1221 /* Delete if too old */
1223 if (value
->time_stamp
< oldest
)
1225 printf("deleted %s (too old)\n", key
);
1226 dbfn_delete(dbm
, key
);
1230 /* Do database-specific tidying for wait databases, and message-
1231 specific tidying for the retry database. */
1233 if (dbdata_type
== type_wait
)
1235 dbdata_wait
*wait
= (dbdata_wait
*)value
;
1236 BOOL update
= FALSE
;
1238 /* Leave corrupt records alone */
1240 if (wait
->count
> WAIT_NAME_MAX
)
1242 printf("**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
1243 key
, wait
->count
, wait
->count
, WAIT_NAME_MAX
);
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. */
1254 int length
= wait
->count
* MESSAGE_ID_LENGTH
;
1256 for (int offset
= length
- MESSAGE_ID_LENGTH
;
1257 offset
>= 0; offset
-= MESSAGE_ID_LENGTH
)
1259 Ustrncpy(buffer
+path_len
, wait
->text
+ offset
, MESSAGE_ID_LENGTH
);
1260 sprintf(CS(buffer
+path_len
+ MESSAGE_ID_LENGTH
), "-D");
1262 if (Ustat(buffer
, &statbuf
) != 0)
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");
1269 if (Ustat(buffer
, &statbuf
) != 0)
1271 int left
= length
- offset
- MESSAGE_ID_LENGTH
;
1272 if (left
> 0) Ustrncpy(wait
->text
+ offset
,
1273 wait
->text
+ offset
+ MESSAGE_ID_LENGTH
, left
);
1275 length
-= MESSAGE_ID_LENGTH
;
1281 /* If record is empty and the main record, either delete it or rename
1282 the next continuation, repeating if that is also empty. */
1284 if (wait
->count
== 0 && Ustrchr(key
, ':') == NULL
)
1286 while (wait
->count
== 0 && wait
->sequence
> 0)
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
)
1295 wait
= (dbdata_wait
*)newvalue
;
1296 dbfn_delete(dbm
, newkey
);
1297 printf("renamed %s\n", newkey
);
1300 else wait
->sequence
--;
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. */
1306 if (wait
->count
== 0 && wait
->sequence
== 0)
1308 dbfn_delete(dbm
, key
);
1309 printf("deleted %s (empty)\n", key
);
1315 /* If not an empty main record, break the loop */
1320 /* Re-write the record if required */
1324 printf("updated %s\n", key
);
1325 dbfn_write(dbm
, key
, wait
, sizeof(dbdata_wait
) +
1326 wait
->count
* MESSAGE_ID_LENGTH
);
1330 /* If a retry record's key ends with a message-id, check that that message
1331 still exists; if not, remove this record. */
1333 else if (dbdata_type
== type_retry
)
1336 int len
= Ustrlen(key
);
1338 if (len
< MESSAGE_ID_LENGTH
+ 1) continue;
1339 id
= key
+ len
- MESSAGE_ID_LENGTH
- 1;
1340 if (*id
++ != ':') continue;
1342 for (i
= 0; i
< MESSAGE_ID_LENGTH
; i
++)
1343 if (i
== 6 || i
== 13)
1344 { if (id
[i
] != '-') break; }
1346 { if (!isalnum(id
[i
])) break; }
1347 if (i
< MESSAGE_ID_LENGTH
) continue;
1349 Ustrncpy(buffer
+ path_len
, id
, MESSAGE_ID_LENGTH
);
1350 sprintf(CS(buffer
+ path_len
+ MESSAGE_ID_LENGTH
), "-D");
1352 if (Ustat(buffer
, &statbuf
) != 0)
1354 sprintf(CS(buffer
+ path_len
), "%c/%s-D", id
[5], id
);
1355 if (Ustat(buffer
, &statbuf
) != 0)
1357 dbfn_delete(dbm
, key
);
1358 printf("deleted %s (no message)\n", key
);
1365 printf("Tidying complete\n");
1369 #endif /* EXIM_TIDYDB */
1371 /* End of exim_dbutil.c */