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