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