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