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