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