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