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