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