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