Use C99 initialisations for iterators
[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 int field = 0;
194 int value;
195 time_t now = time(NULL);
196 struct tm *tm = localtime(&now);
197
198 tm->tm_sec = 0;
199 tm->tm_isdst = -1;
200
201 for (uschar * t = s + Ustrlen(s) - 1; t >= s; t--)
202 {
203 if (*t == ':') continue;
204 if (!isdigit((uschar)*t)) return -1;
205
206 value = *t - '0';
207 if (--t >= s)
208 {
209 if (!isdigit((uschar)*t)) return -1;
210 value = value + (*t - '0')*10;
211 }
212
213 switch (field++)
214 {
215 case 0: tm->tm_min = value; break;
216 case 1: tm->tm_hour = value; break;
217 case 2: tm->tm_mday = value; break;
218 case 3: tm->tm_mon = value - 1; break;
219 case 4: tm->tm_year = (value < 90)? value + 100 : value; break;
220 default: return -1;
221 }
222 }
223
224 return mktime(tm);
225 }
226 #endif /* EXIM_FIXDB */
227
228
229
230 /*************************************************
231 * Open and lock a database file *
232 *************************************************/
233
234 /* This is a cut-down version from the function in dbfn.h that Exim itself
235 uses. We assume the database exists, and therefore give up if we cannot open
236 the lock file.
237
238 Arguments:
239 name The single-component name of one of Exim's database files.
240 flags O_RDONLY or O_RDWR
241 dbblock Points to an open_db block to be filled in.
242 lof Unused.
243
244 Returns: NULL if the open failed, or the locking failed.
245 On success, dbblock is returned. This contains the dbm pointer and
246 the fd of the locked lock file.
247 */
248
249 open_db *
250 dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof)
251 {
252 int rc;
253 struct flock lock_data;
254 BOOL read_only = flags == O_RDONLY;
255 uschar * dirname, * filename;
256
257 /* The first thing to do is to open a separate file on which to lock. This
258 ensures that Exim has exclusive use of the database before it even tries to
259 open it. If there is a database, there should be a lock file in existence. */
260
261 #ifdef COMPILE_UTILITY
262 if ( asprintf(CSS &dirname, "%s/db", spool_directory) < 0
263 || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
264 return NULL;
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_CLR(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 if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
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 keybuffer[1024];
523
524 /* Check the arguments, and open the database */
525
526 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
527 spool_directory = argv[1];
528 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE)))
529 exit(1);
530
531 /* Scan the file, formatting the information for each entry. Note
532 that data is returned in a malloc'ed block, in order that it be
533 correctly aligned. */
534
535 for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
536 key;
537 key = dbfn_scan(dbm, FALSE, &cursor))
538 {
539 dbdata_retry *retry;
540 dbdata_wait *wait;
541 dbdata_callout_cache *callout;
542 dbdata_ratelimit *ratelimit;
543 dbdata_ratelimit_unique *rate_unique;
544 int count_bad = 0;
545 int length;
546 uschar *t;
547 uschar name[MESSAGE_ID_LENGTH + 1];
548 void *value;
549
550 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
551 which might change. */
552
553 if (Ustrlen(key) > sizeof(keybuffer) - 1)
554 {
555 printf("**** Overlong key encountered: %s\n", key);
556 return 1;
557 }
558 Ustrcpy(keybuffer, key);
559
560 if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
561 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
562 "was not found in the file - something is wrong!\n",
563 CS keybuffer);
564 else
565 {
566 /* Note: don't use print_time more than once in one statement, since
567 it uses a single buffer. */
568
569 switch(dbdata_type)
570 {
571 case type_retry:
572 retry = (dbdata_retry *)value;
573 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
574 retry->more_errno, retry->text,
575 print_time(retry->first_failed));
576 printf("%s ", print_time(retry->last_try));
577 printf("%s %s\n", print_time(retry->next_try),
578 (retry->expired)? "*" : "");
579 break;
580
581 case type_wait:
582 wait = (dbdata_wait *)value;
583 printf("%s ", keybuffer);
584 t = wait->text;
585 name[MESSAGE_ID_LENGTH] = 0;
586
587 if (wait->count > WAIT_NAME_MAX)
588 {
589 fprintf(stderr,
590 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
591 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
592 wait->count = WAIT_NAME_MAX;
593 yield = count_bad = 1;
594 }
595 for (int i = 1; i <= wait->count; i++)
596 {
597 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
598 if (count_bad && name[0] == 0) break;
599 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
600 Ustrspn(name, "0123456789"
601 "abcdefghijklmnopqrstuvwxyz"
602 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
603 {
604 fprintf(stderr,
605 "**** Data for %s corrupted: bad character in message id\n",
606 CS keybuffer);
607 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
608 fprintf(stderr, "%02x ", name[j]);
609 fprintf(stderr, "\n");
610 yield = 1;
611 break;
612 }
613 printf("%s ", name);
614 t += MESSAGE_ID_LENGTH;
615 }
616 printf("\n");
617 break;
618
619 case type_misc:
620 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
621 keybuffer);
622 break;
623
624 case type_callout:
625 callout = (dbdata_callout_cache *)value;
626
627 /* New-style address record */
628
629 if (length == sizeof(dbdata_callout_cache_address))
630 {
631 printf("%s %s callout=%s\n",
632 print_time(((dbdata_generic *)value)->time_stamp),
633 keybuffer,
634 print_cache(callout->result));
635 }
636
637 /* New-style domain record */
638
639 else if (length == sizeof(dbdata_callout_cache))
640 {
641 printf("%s %s callout=%s postmaster=%s",
642 print_time(((dbdata_generic *)value)->time_stamp),
643 keybuffer,
644 print_cache(callout->result),
645 print_cache(callout->postmaster_result));
646 if (callout->postmaster_result != ccache_unknown)
647 printf(" (%s)", print_time(callout->postmaster_stamp));
648 printf(" random=%s", print_cache(callout->random_result));
649 if (callout->random_result != ccache_unknown)
650 printf(" (%s)", print_time(callout->random_stamp));
651 printf("\n");
652 }
653
654 break;
655
656 case type_ratelimit:
657 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
658 {
659 ratelimit = (dbdata_ratelimit *)value;
660 rate_unique = (dbdata_ratelimit_unique *)value;
661 printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
662 print_time(ratelimit->time_stamp),
663 ratelimit->time_usec, ratelimit->rate,
664 print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
665 keybuffer);
666 }
667 else
668 {
669 ratelimit = (dbdata_ratelimit *)value;
670 printf("%s.%06d rate: %10.3f key: %s\n",
671 print_time(ratelimit->time_stamp),
672 ratelimit->time_usec, ratelimit->rate,
673 keybuffer);
674 }
675 break;
676 }
677 store_reset(value);
678 }
679 }
680
681 dbfn_close(dbm);
682 return yield;
683 }
684
685 #endif /* EXIM_DUMPDB */
686
687
688
689
690 #ifdef EXIM_FIXDB
691 /*************************************************
692 * The exim_fixdb main program *
693 *************************************************/
694
695 /* In order not to hold the database lock any longer than is necessary, each
696 operation on the database uses a separate open/close call. This is expensive,
697 but then using this utility is not expected to be very common. Its main use is
698 to provide a way of patching up hints databases in order to run tests.
699
700 Syntax of commands:
701
702 (1) <record name>
703 This causes the data from the given record to be displayed, or "not found"
704 to be output. Note that in the retry database, destination names are
705 preceded by R: or T: for router or transport retry info.
706
707 (2) <record name> d
708 This causes the given record to be deleted or "not found" to be output.
709
710 (3) <record name> <field number> <value>
711 This sets the given value into the given field, identified by a number
712 which is output by the display command. Not all types of record can
713 be changed.
714
715 (4) q
716 This exits from exim_fixdb.
717
718 If the record name is omitted from (2) or (3), the previously used record name
719 is re-used. */
720
721
722 int main(int argc, char **cargv)
723 {
724 int dbdata_type;
725 uschar **argv = USS cargv;
726 uschar buffer[256];
727 uschar name[256];
728 void *reset_point = store_get(0);
729
730 name[0] = 0; /* No name set */
731
732 /* Sort out the database type, verify what we are working on and then process
733 user requests */
734
735 dbdata_type = check_args(argc, argv, US"fixdb", US"");
736 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
737
738 for(;;)
739 {
740 open_db dbblock;
741 open_db *dbm;
742 void *record;
743 dbdata_retry *retry;
744 dbdata_wait *wait;
745 dbdata_callout_cache *callout;
746 dbdata_ratelimit *ratelimit;
747 dbdata_ratelimit_unique *rate_unique;
748 int oldlength;
749 uschar *t;
750 uschar field[256], value[256];
751
752 store_reset(reset_point);
753
754 printf("> ");
755 if (Ufgets(buffer, 256, stdin) == NULL) break;
756
757 buffer[Ustrlen(buffer)-1] = 0;
758 field[0] = value[0] = 0;
759
760 /* If the buffer contains just one digit, or just consists of "d", use the
761 previous name for an update. */
762
763 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
764 || Ustrcmp(buffer, "d") == 0)
765 {
766 if (name[0] == 0)
767 {
768 printf("No previous record name is set\n");
769 continue;
770 }
771 (void)sscanf(CS buffer, "%s %s", field, value);
772 }
773 else
774 {
775 name[0] = 0;
776 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
777 }
778
779 /* Handle an update request */
780
781 if (field[0] != 0)
782 {
783 int verify = 1;
784 spool_directory = argv[1];
785
786 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE)))
787 continue;
788
789 if (Ustrcmp(field, "d") == 0)
790 {
791 if (value[0] != 0) printf("unexpected value after \"d\"\n");
792 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
793 "not found" : "deleted");
794 dbfn_close(dbm);
795 continue;
796 }
797
798 else if (isdigit((uschar)field[0]))
799 {
800 int fieldno = Uatoi(field);
801 if (value[0] == 0)
802 {
803 printf("value missing\n");
804 dbfn_close(dbm);
805 continue;
806 }
807 else
808 {
809 record = dbfn_read_with_length(dbm, name, &oldlength);
810 if (record == NULL) printf("not found\n"); else
811 {
812 time_t tt;
813 /*int length = 0; Stops compiler warning */
814
815 switch(dbdata_type)
816 {
817 case type_retry:
818 retry = (dbdata_retry *)record;
819 /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
820
821 switch(fieldno)
822 {
823 case 0: retry->basic_errno = Uatoi(value);
824 break;
825 case 1: retry->more_errno = Uatoi(value);
826 break;
827 case 2: if ((tt = read_time(value)) > 0) retry->first_failed = tt;
828 else printf("bad time value\n");
829 break;
830 case 3: if ((tt = read_time(value)) > 0) retry->last_try = tt;
831 else printf("bad time value\n");
832 break;
833 case 4: if ((tt = read_time(value)) > 0) retry->next_try = tt;
834 else printf("bad time value\n");
835 break;
836 case 5: if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
837 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
838 else printf("\"yes\" or \"no\" expected=n");
839 break;
840 default: printf("unknown field number\n");
841 verify = 0;
842 break;
843 }
844 break;
845
846 case type_wait:
847 printf("Can't change contents of wait database record\n");
848 break;
849
850 case type_misc:
851 printf("Can't change contents of misc database record\n");
852 break;
853
854 case type_callout:
855 callout = (dbdata_callout_cache *)record;
856 /* length = sizeof(dbdata_callout_cache); */
857 switch(fieldno)
858 {
859 case 0: callout->result = Uatoi(value);
860 break;
861 case 1: callout->postmaster_result = Uatoi(value);
862 break;
863 case 2: callout->random_result = Uatoi(value);
864 break;
865 default: printf("unknown field number\n");
866 verify = 0;
867 break;
868 }
869 break;
870
871 case type_ratelimit:
872 ratelimit = (dbdata_ratelimit *)record;
873 switch(fieldno)
874 {
875 case 0: if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
876 else printf("bad time value\n");
877 break;
878 case 1: ratelimit->time_usec = Uatoi(value);
879 break;
880 case 2: ratelimit->rate = Ustrtod(value, NULL);
881 break;
882 case 3: if (Ustrstr(name, "/unique/") != NULL
883 && oldlength >= sizeof(dbdata_ratelimit_unique))
884 {
885 rate_unique = (dbdata_ratelimit_unique *)record;
886 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
887 else printf("bad time value\n");
888 break;
889 }
890 /* else fall through */
891 case 4:
892 case 5: if (Ustrstr(name, "/unique/") != NULL
893 && oldlength >= sizeof(dbdata_ratelimit_unique))
894 {
895 /* see acl.c */
896 BOOL seen;
897 unsigned hash, hinc;
898 uschar md5sum[16];
899 md5 md5info;
900 md5_start(&md5info);
901 md5_end(&md5info, value, Ustrlen(value), md5sum);
902 hash = md5sum[0] << 0 | md5sum[1] << 8
903 | md5sum[2] << 16 | md5sum[3] << 24;
904 hinc = md5sum[4] << 0 | md5sum[5] << 8
905 | md5sum[6] << 16 | md5sum[7] << 24;
906 rate_unique = (dbdata_ratelimit_unique *)record;
907 seen = TRUE;
908 for (unsigned n = 0; n < 8; n++, hash += hinc)
909 {
910 int bit = 1 << (hash % 8);
911 int byte = (hash / 8) % rate_unique->bloom_size;
912 if ((rate_unique->bloom[byte] & bit) == 0)
913 {
914 seen = FALSE;
915 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
916 }
917 }
918 printf("%s %s\n",
919 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
920 break;
921 }
922 /* else fall through */
923 default: printf("unknown field number\n");
924 verify = 0;
925 break;
926 }
927 break;
928 }
929
930 dbfn_write(dbm, name, record, oldlength);
931 }
932 }
933 }
934
935 else
936 {
937 printf("field number or d expected\n");
938 verify = 0;
939 }
940
941 dbfn_close(dbm);
942 if (!verify) continue;
943 }
944
945 /* The "name" q causes an exit */
946
947 else if (Ustrcmp(name, "q") == 0) return 0;
948
949 /* Handle a read request, or verify after an update. */
950
951 spool_directory = argv[1];
952 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE)))
953 continue;
954
955 if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
956 {
957 printf("record %s not found\n", name);
958 name[0] = 0;
959 }
960 else
961 {
962 int count_bad = 0;
963 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
964 switch(dbdata_type)
965 {
966 case type_retry:
967 retry = (dbdata_retry *)record;
968 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
969 printf("1 extra data: %d\n", retry->more_errno);
970 printf("2 first failed: %s\n", print_time(retry->first_failed));
971 printf("3 last try: %s\n", print_time(retry->last_try));
972 printf("4 next try: %s\n", print_time(retry->next_try));
973 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
974 break;
975
976 case type_wait:
977 wait = (dbdata_wait *)record;
978 t = wait->text;
979 printf("Sequence: %d\n", wait->sequence);
980 if (wait->count > WAIT_NAME_MAX)
981 {
982 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
983 wait->count, WAIT_NAME_MAX);
984 wait->count = WAIT_NAME_MAX;
985 count_bad = 1;
986 }
987 for (int i = 1; i <= wait->count; i++)
988 {
989 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
990 value[MESSAGE_ID_LENGTH] = 0;
991 if (count_bad && value[0] == 0) break;
992 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
993 Ustrspn(value, "0123456789"
994 "abcdefghijklmnopqrstuvwxyz"
995 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
996 {
997 printf("\n**** Data corrupted: bad character in message id ****\n");
998 for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
999 printf("%02x ", value[j]);
1000 printf("\n");
1001 break;
1002 }
1003 printf("%s ", value);
1004 t += MESSAGE_ID_LENGTH;
1005 }
1006 printf("\n");
1007 break;
1008
1009 case type_misc:
1010 break;
1011
1012 case type_callout:
1013 callout = (dbdata_callout_cache *)record;
1014 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1015 callout->result);
1016 if (oldlength > sizeof(dbdata_callout_cache_address))
1017 {
1018 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1019 callout->postmaster_result);
1020 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1021 callout->random_result);
1022 }
1023 break;
1024
1025 case type_ratelimit:
1026 ratelimit = (dbdata_ratelimit *)record;
1027 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1028 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1029 printf("2 sender rate: % .3f\n", ratelimit->rate);
1030 if (Ustrstr(name, "/unique/") != NULL
1031 && oldlength >= sizeof(dbdata_ratelimit_unique))
1032 {
1033 rate_unique = (dbdata_ratelimit_unique *)record;
1034 printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1035 printf("4 test filter membership\n");
1036 printf("5 add element to filter\n");
1037 }
1038 break;
1039 }
1040 }
1041
1042 /* The database is closed after each request */
1043
1044 dbfn_close(dbm);
1045 }
1046
1047 printf("\n");
1048 return 0;
1049 }
1050
1051 #endif /* EXIM_FIXDB */
1052
1053
1054
1055 #ifdef EXIM_TIDYDB
1056 /*************************************************
1057 * The exim_tidydb main program *
1058 *************************************************/
1059
1060
1061 /* Utility program to tidy the contents of an exim database file. There is one
1062 option:
1063
1064 -t <time> expiry time for old records - default 30 days
1065
1066 For backwards compatibility, an -f option is recognized and ignored. (It used
1067 to request a "full" tidy. This version always does the whole job.) */
1068
1069
1070 typedef struct key_item {
1071 struct key_item *next;
1072 uschar key[1];
1073 } key_item;
1074
1075
1076 int main(int argc, char **cargv)
1077 {
1078 struct stat statbuf;
1079 int maxkeep = 30 * 24 * 60 * 60;
1080 int dbdata_type, i, oldest, path_len;
1081 key_item *keychain = NULL;
1082 void *reset_point;
1083 open_db dbblock;
1084 open_db *dbm;
1085 EXIM_CURSOR *cursor;
1086 uschar **argv = USS cargv;
1087 uschar buffer[256];
1088 uschar *key;
1089
1090 /* Scan the options */
1091
1092 for (i = 1; i < argc; i++)
1093 {
1094 if (argv[i][0] != '-') break;
1095 if (Ustrcmp(argv[i], "-f") == 0) continue;
1096 if (Ustrcmp(argv[i], "-t") == 0)
1097 {
1098 uschar *s;
1099 s = argv[++i];
1100 maxkeep = 0;
1101 while (*s != 0)
1102 {
1103 int value, count;
1104 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1105 (void)sscanf(CS s, "%d%n", &value, &count);
1106 s += count;
1107 switch (*s)
1108 {
1109 case 'w': value *= 7;
1110 case 'd': value *= 24;
1111 case 'h': value *= 60;
1112 case 'm': value *= 60;
1113 case 's': s++;
1114 break;
1115 default: usage(US"tidydb", US" [-t <time>]");
1116 }
1117 maxkeep += value;
1118 }
1119 }
1120 else usage(US"tidydb", US" [-t <time>]");
1121 }
1122
1123 /* Adjust argument values and process arguments */
1124
1125 argc -= --i;
1126 argv += i;
1127
1128 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1129
1130 /* Compute the oldest keep time, verify what we are doing, and open the
1131 database */
1132
1133 oldest = time(NULL) - maxkeep;
1134 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1135
1136 spool_directory = argv[1];
1137 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE)))
1138 exit(1);
1139
1140 /* Prepare for building file names */
1141
1142 sprintf(CS buffer, "%s/input/", argv[1]);
1143 path_len = Ustrlen(buffer);
1144
1145
1146 /* It appears, by experiment, that it is a bad idea to make changes
1147 to the file while scanning it. Pity the man page doesn't warn you about that.
1148 Therefore, we scan and build a list of all the keys. Then we use that to
1149 read the records and possibly update them. */
1150
1151 for (key = dbfn_scan(dbm, TRUE, &cursor);
1152 key;
1153 key = dbfn_scan(dbm, FALSE, &cursor))
1154 {
1155 key_item *k = store_get(sizeof(key_item) + Ustrlen(key));
1156 k->next = keychain;
1157 keychain = k;
1158 Ustrcpy(k->key, key);
1159 }
1160
1161 /* Now scan the collected keys and operate on the records, resetting
1162 the store each time round. */
1163
1164 reset_point = store_get(0);
1165
1166 while (keychain)
1167 {
1168 dbdata_generic *value;
1169
1170 store_reset(reset_point);
1171 key = keychain->key;
1172 keychain = keychain->next;
1173 value = dbfn_read_with_length(dbm, key, NULL);
1174
1175 /* A continuation record may have been deleted or renamed already, so
1176 non-existence is not serious. */
1177
1178 if (value == NULL) continue;
1179
1180 /* Delete if too old */
1181
1182 if (value->time_stamp < oldest)
1183 {
1184 printf("deleted %s (too old)\n", key);
1185 dbfn_delete(dbm, key);
1186 continue;
1187 }
1188
1189 /* Do database-specific tidying for wait databases, and message-
1190 specific tidying for the retry database. */
1191
1192 if (dbdata_type == type_wait)
1193 {
1194 dbdata_wait *wait = (dbdata_wait *)value;
1195 BOOL update = FALSE;
1196
1197 /* Leave corrupt records alone */
1198
1199 if (wait->count > WAIT_NAME_MAX)
1200 {
1201 printf("**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
1202 key, wait->count, wait->count, WAIT_NAME_MAX);
1203 continue;
1204 }
1205
1206 /* Loop for renamed continuation records. For each message id,
1207 check to see if the message exists, and if not, remove its entry
1208 from the record. Because of the possibility of split input directories,
1209 we must look in both possible places for a -D file. */
1210
1211 for (;;)
1212 {
1213 int length = wait->count * MESSAGE_ID_LENGTH;
1214
1215 for (int offset = length - MESSAGE_ID_LENGTH;
1216 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1217 {
1218 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1219 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1220
1221 if (Ustat(buffer, &statbuf) != 0)
1222 {
1223 buffer[path_len] = wait->text[offset+5];
1224 buffer[path_len+1] = '/';
1225 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1226 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1227
1228 if (Ustat(buffer, &statbuf) != 0)
1229 {
1230 int left = length - offset - MESSAGE_ID_LENGTH;
1231 if (left > 0) Ustrncpy(wait->text + offset,
1232 wait->text + offset + MESSAGE_ID_LENGTH, left);
1233 wait->count--;
1234 length -= MESSAGE_ID_LENGTH;
1235 update = TRUE;
1236 }
1237 }
1238 }
1239
1240 /* If record is empty and the main record, either delete it or rename
1241 the next continuation, repeating if that is also empty. */
1242
1243 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1244 {
1245 while (wait->count == 0 && wait->sequence > 0)
1246 {
1247 uschar newkey[256];
1248 dbdata_generic *newvalue;
1249 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1250 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1251 if (newvalue != NULL)
1252 {
1253 value = newvalue;
1254 wait = (dbdata_wait *)newvalue;
1255 dbfn_delete(dbm, newkey);
1256 printf("renamed %s\n", newkey);
1257 update = TRUE;
1258 }
1259 else wait->sequence--;
1260 }
1261
1262 /* If we have ended up with an empty main record, delete it
1263 and break the loop. Otherwise the new record will be scanned. */
1264
1265 if (wait->count == 0 && wait->sequence == 0)
1266 {
1267 dbfn_delete(dbm, key);
1268 printf("deleted %s (empty)\n", key);
1269 update = FALSE;
1270 break;
1271 }
1272 }
1273
1274 /* If not an empty main record, break the loop */
1275
1276 else break;
1277 }
1278
1279 /* Re-write the record if required */
1280
1281 if (update)
1282 {
1283 printf("updated %s\n", key);
1284 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1285 wait->count * MESSAGE_ID_LENGTH);
1286 }
1287 }
1288
1289 /* If a retry record's key ends with a message-id, check that that message
1290 still exists; if not, remove this record. */
1291
1292 else if (dbdata_type == type_retry)
1293 {
1294 uschar *id;
1295 int len = Ustrlen(key);
1296
1297 if (len < MESSAGE_ID_LENGTH + 1) continue;
1298 id = key + len - MESSAGE_ID_LENGTH - 1;
1299 if (*id++ != ':') continue;
1300
1301 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1302 if (i == 6 || i == 13)
1303 { if (id[i] != '-') break; }
1304 else
1305 { if (!isalnum(id[i])) break; }
1306 if (i < MESSAGE_ID_LENGTH) continue;
1307
1308 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1309 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1310
1311 if (Ustat(buffer, &statbuf) != 0)
1312 {
1313 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1314 if (Ustat(buffer, &statbuf) != 0)
1315 {
1316 dbfn_delete(dbm, key);
1317 printf("deleted %s (no message)\n", key);
1318 }
1319 }
1320 }
1321 }
1322
1323 dbfn_close(dbm);
1324 printf("Tidying complete\n");
1325 return 0;
1326 }
1327
1328 #endif /* EXIM_TIDYDB */
1329
1330 /* End of exim_dbutil.c */