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