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