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