Testsuite: output changes resulting
[exim.git] / src / src / exim_dbutil.c
index 17da205b4e2f500de7995b3de247402d879a07af..46219a8dbd493115845cd93c82ccae07ad6e97e7 100644 (file)
@@ -20,6 +20,7 @@ argument is the name of the database file. The available names are:
   misc:       miscellaneous hints data
   wait-<t>:   message waiting information; <t> is a transport name
   callout:    callout verification cache
+  tls:       TLS session resumption cache
 
 There are a number of common subroutines, followed by three main programs,
 whose inclusion is controlled by -D on the compilation command. */
@@ -35,6 +36,7 @@ whose inclusion is controlled by -D on the compilation command. */
 #define type_misc      3
 #define type_callout   4
 #define type_ratelimit 5
+#define type_tls       6
 
 
 /* This is used by our cut-down dbfn_open(). */
@@ -42,6 +44,19 @@ whose inclusion is controlled by -D on the compilation command. */
 uschar *spool_directory;
 
 
+/******************************************************************************/
+      /* dummies needed by Solaris build */
+gstring *
+string_vformat_trc(gstring * g, const uschar * func, unsigned line,
+  unsigned size_limit, unsigned flags, const char *format, va_list ap)
+{ return NULL; }
+uschar *
+string_sprintf_trc(const char * fmt, const uschar * func, unsigned line, ...)
+{ return NULL; }
+BOOL split_spool_directory;
+uschar * queue_name;
+/******************************************************************************/
+
 
 /*************************************************
 *         Berkeley DB error callback             *
@@ -91,7 +106,7 @@ static void
 usage(uschar *name, uschar *options)
 {
 printf("Usage: exim_%s%s  <spool-directory> <database-name>\n", name, options);
-printf("  <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit\n");
+printf("  <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit | tls\n");
 exit(1);
 }
 
@@ -114,6 +129,7 @@ if (argc == 3)
   if (Ustrncmp(argv[2], "wait-", 5) == 0) return type_wait;
   if (Ustrcmp(argv[2], "callout") == 0) return type_callout;
   if (Ustrcmp(argv[2], "ratelimit") == 0) return type_ratelimit;
+  if (Ustrcmp(argv[2], "tls") == 0) return type_tls;
   }
 usage(name, options);
 return -1;              /* Never obeyed */
@@ -190,7 +206,6 @@ return (value == ccache_accept)? US"accept" :
 static time_t
 read_time(uschar *s)
 {
-uschar *t = s;
 int field = 0;
 int value;
 time_t now = time(NULL);
@@ -199,7 +214,7 @@ struct tm *tm = localtime(&now);
 tm->tm_sec = 0;
 tm->tm_isdst = -1;
 
-for (t = s + Ustrlen(s) - 1; t >= s; t--)
+for (uschar * t = s + Ustrlen(s) - 1; t >= s; t--)
   {
   if (*t == ':') continue;
   if (!isdigit((uschar)*t)) return -1;
@@ -241,6 +256,7 @@ Arguments:
   flags    O_RDONLY or O_RDWR
   dbblock  Points to an open_db block to be filled in.
   lof      Unused.
+  panic           Unused
 
 Returns:   NULL if the open failed, or the locking failed.
            On success, dbblock is returned. This contains the dbm pointer and
@@ -248,7 +264,7 @@ Returns:   NULL if the open failed, or the locking failed.
 */
 
 open_db *
-dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof)
+dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof, BOOL panic)
 {
 int rc;
 struct flock lock_data;
@@ -260,8 +276,9 @@ ensures that Exim has exclusive use of the database before it even tries to
 open it. If there is a database, there should be a lock file in existence. */
 
 #ifdef COMPILE_UTILITY
-asprintf(CSS &dirname, "%s/db", spool_directory);
-asprintf(CSS &filename, "%s/%s.lockfile", dirname, name);
+if (  asprintf(CSS &dirname, "%s/db", spool_directory) < 0
+   || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
+  return NULL;
 #else
 dirname = string_sprintf("%s/db", spool_directory);
 filename = string_sprintf("%s/%s.lockfile", dirname, name);
@@ -283,9 +300,9 @@ lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
 
 sigalrm_seen = FALSE;
 os_non_restarting_signal(SIGALRM, sigalrm_handler);
-alarm(EXIMDB_LOCK_TIMEOUT);
+ALARM(EXIMDB_LOCK_TIMEOUT);
 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
-alarm(0);
+ALARM_CLR(0);
 
 if (sigalrm_seen) errno = ETIMEDOUT;
 if (rc < 0)
@@ -302,7 +319,7 @@ if (rc < 0)
 exclusive access to the database, so we can go ahead and open it. */
 
 #ifdef COMPILE_UTILITY
-asprintf(CSS &filename, "%s/%s", dirname, name);
+if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
 #else
 filename = string_sprintf("%s/%s", dirname, name);
 #endif
@@ -372,7 +389,7 @@ dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
 void *yield;
 EXIM_DATUM key_datum, result_datum;
 int klen = Ustrlen(key) + 1;
-uschar * key_copy = store_get(klen);
+uschar * key_copy = store_get(klen, is_tainted(key));
 
 memcpy(key_copy, key, klen);
 
@@ -383,7 +400,10 @@ EXIM_DATUM_SIZE(key_datum) = klen;
 
 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
 
-yield = store_get(EXIM_DATUM_SIZE(result_datum));
+/* Assume for now that anything stored could have been tainted. Properly
+we should store the taint status along with the data. */
+
+yield = store_get(EXIM_DATUM_SIZE(result_datum), TRUE);
 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
 if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
 
@@ -416,7 +436,7 @@ dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
 EXIM_DATUM key_datum, value_datum;
 dbdata_generic *gptr = (dbdata_generic *)ptr;
 int klen = Ustrlen(key) + 1;
-uschar * key_copy = store_get(klen);
+uschar * key_copy = store_get(klen, is_tainted(key));
 
 memcpy(key_copy, key, klen);
 gptr->time_stamp = time(NULL);
@@ -448,7 +468,7 @@ int
 dbfn_delete(open_db *dbblock, const uschar *key)
 {
 int klen = Ustrlen(key) + 1;
-uschar * key_copy = store_get(klen);
+uschar * key_copy = store_get(klen, is_tainted(key));
 
 memcpy(key_copy, key, klen);
 EXIM_DATUM key_datum;
@@ -519,21 +539,20 @@ open_db dbblock;
 open_db *dbm;
 EXIM_CURSOR *cursor;
 uschar **argv = USS cargv;
-uschar *key;
 uschar keybuffer[1024];
 
 /* Check the arguments, and open the database */
 
 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
 spool_directory = argv[1];
-if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE)))
+if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
   exit(1);
 
 /* Scan the file, formatting the information for each entry. Note
 that data is returned in a malloc'ed block, in order that it be
 correctly aligned. */
 
-for (key = dbfn_scan(dbm, TRUE, &cursor);
+for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
      key;
      key = dbfn_scan(dbm, FALSE, &cursor))
   {
@@ -542,11 +561,13 @@ for (key = dbfn_scan(dbm, TRUE, &cursor);
   dbdata_callout_cache *callout;
   dbdata_ratelimit *ratelimit;
   dbdata_ratelimit_unique *rate_unique;
+  dbdata_tls_session *session;
   int count_bad = 0;
-  int i, length;
+  int length;
   uschar *t;
   uschar name[MESSAGE_ID_LENGTH + 1];
   void *value;
+  rmark reset_point = store_mark();
 
   /* Keep a copy of the key separate, as in some DBM's the pointer is into data
   which might change. */
@@ -570,114 +591,118 @@ for (key = dbfn_scan(dbm, TRUE, &cursor);
     switch(dbdata_type)
       {
       case type_retry:
-      retry = (dbdata_retry *)value;
-      printf("  %s %d %d %s\n%s  ", keybuffer, retry->basic_errno,
-        retry->more_errno, retry->text,
-        print_time(retry->first_failed));
-      printf("%s  ", print_time(retry->last_try));
-      printf("%s %s\n", print_time(retry->next_try),
-        (retry->expired)? "*" : "");
-      break;
+       retry = (dbdata_retry *)value;
+       printf("  %s %d %d %s\n%s  ", keybuffer, retry->basic_errno,
+         retry->more_errno, retry->text,
+         print_time(retry->first_failed));
+       printf("%s  ", print_time(retry->last_try));
+       printf("%s %s\n", print_time(retry->next_try),
+         (retry->expired)? "*" : "");
+       break;
 
       case type_wait:
-      wait = (dbdata_wait *)value;
-      printf("%s ", keybuffer);
-      t = wait->text;
-      name[MESSAGE_ID_LENGTH] = 0;
-
-      if (wait->count > WAIT_NAME_MAX)
-        {
-        fprintf(stderr,
-          "**** Data for %s corrupted\n  count=%d=0x%x max=%d\n",
-          CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
-        wait->count = WAIT_NAME_MAX;
-        yield = count_bad = 1;
-        }
-      for (i = 1; i <= wait->count; i++)
-        {
-        Ustrncpy(name, t, MESSAGE_ID_LENGTH);
-        if (count_bad && name[0] == 0) break;
-        if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
-            Ustrspn(name, "0123456789"
-                          "abcdefghijklmnopqrstuvwxyz"
-                          "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
-          {
-          int j;
-          fprintf(stderr,
-            "**** Data for %s corrupted: bad character in message id\n",
-            CS keybuffer);
-          for (j = 0; j < MESSAGE_ID_LENGTH; j++)
-            fprintf(stderr, "%02x ", name[j]);
-          fprintf(stderr, "\n");
-          yield = 1;
-          break;
-          }
-        printf("%s ", name);
-        t += MESSAGE_ID_LENGTH;
-        }
-      printf("\n");
-      break;
+       wait = (dbdata_wait *)value;
+       printf("%s ", keybuffer);
+       t = wait->text;
+       name[MESSAGE_ID_LENGTH] = 0;
+
+       if (wait->count > WAIT_NAME_MAX)
+         {
+         fprintf(stderr,
+           "**** Data for %s corrupted\n  count=%d=0x%x max=%d\n",
+           CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
+         wait->count = WAIT_NAME_MAX;
+         yield = count_bad = 1;
+         }
+       for (int i = 1; i <= wait->count; i++)
+         {
+         Ustrncpy(name, t, MESSAGE_ID_LENGTH);
+         if (count_bad && name[0] == 0) break;
+         if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
+             Ustrspn(name, "0123456789"
+                           "abcdefghijklmnopqrstuvwxyz"
+                           "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
+           {
+           fprintf(stderr,
+             "**** Data for %s corrupted: bad character in message id\n",
+             CS keybuffer);
+           for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
+             fprintf(stderr, "%02x ", name[j]);
+           fprintf(stderr, "\n");
+           yield = 1;
+           break;
+           }
+         printf("%s ", name);
+         t += MESSAGE_ID_LENGTH;
+         }
+       printf("\n");
+       break;
 
       case type_misc:
-      printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
-        keybuffer);
-      break;
+       printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
+         keybuffer);
+       break;
 
       case type_callout:
-      callout = (dbdata_callout_cache *)value;
-
-      /* New-style address record */
-
-      if (length == sizeof(dbdata_callout_cache_address))
-        {
-        printf("%s %s callout=%s\n",
-          print_time(((dbdata_generic *)value)->time_stamp),
-          keybuffer,
-          print_cache(callout->result));
-        }
-
-      /* New-style domain record */
-
-      else if (length == sizeof(dbdata_callout_cache))
-        {
-        printf("%s %s callout=%s postmaster=%s",
-          print_time(((dbdata_generic *)value)->time_stamp),
-          keybuffer,
-          print_cache(callout->result),
-          print_cache(callout->postmaster_result));
-        if (callout->postmaster_result != ccache_unknown)
-          printf(" (%s)", print_time(callout->postmaster_stamp));
-        printf(" random=%s", print_cache(callout->random_result));
-        if (callout->random_result != ccache_unknown)
-          printf(" (%s)", print_time(callout->random_stamp));
-        printf("\n");
-        }
-
-      break;
+       callout = (dbdata_callout_cache *)value;
+
+       /* New-style address record */
+
+       if (length == sizeof(dbdata_callout_cache_address))
+         {
+         printf("%s %s callout=%s\n",
+           print_time(((dbdata_generic *)value)->time_stamp),
+           keybuffer,
+           print_cache(callout->result));
+         }
+
+       /* New-style domain record */
+
+       else if (length == sizeof(dbdata_callout_cache))
+         {
+         printf("%s %s callout=%s postmaster=%s",
+           print_time(((dbdata_generic *)value)->time_stamp),
+           keybuffer,
+           print_cache(callout->result),
+           print_cache(callout->postmaster_result));
+         if (callout->postmaster_result != ccache_unknown)
+           printf(" (%s)", print_time(callout->postmaster_stamp));
+         printf(" random=%s", print_cache(callout->random_result));
+         if (callout->random_result != ccache_unknown)
+           printf(" (%s)", print_time(callout->random_stamp));
+         printf("\n");
+         }
+
+       break;
 
       case type_ratelimit:
-      if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
-        {
-        ratelimit = (dbdata_ratelimit *)value;
-        rate_unique = (dbdata_ratelimit_unique *)value;
-        printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
-          print_time(ratelimit->time_stamp),
-          ratelimit->time_usec, ratelimit->rate,
-          print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
-          keybuffer);
-        }
-      else
-        {
-        ratelimit = (dbdata_ratelimit *)value;
-        printf("%s.%06d rate: %10.3f key: %s\n",
-          print_time(ratelimit->time_stamp),
-          ratelimit->time_usec, ratelimit->rate,
-          keybuffer);
-        }
-      break;
+       if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
+         {
+         ratelimit = (dbdata_ratelimit *)value;
+         rate_unique = (dbdata_ratelimit_unique *)value;
+         printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
+           print_time(ratelimit->time_stamp),
+           ratelimit->time_usec, ratelimit->rate,
+           print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
+           keybuffer);
+         }
+       else
+         {
+         ratelimit = (dbdata_ratelimit *)value;
+         printf("%s.%06d rate: %10.3f key: %s\n",
+           print_time(ratelimit->time_stamp),
+           ratelimit->time_usec, ratelimit->rate,
+           keybuffer);
+         }
+       break;
+
+      case type_tls:
+       session = (dbdata_tls_session *)value;
+       printf("  %s %.*s\n", keybuffer, length, session->session);
+       break;
       }
-    store_reset(value);
     }
+  store_reset(reset_point);
   }
 
 dbfn_close(dbm);
@@ -727,7 +752,7 @@ int dbdata_type;
 uschar **argv = USS cargv;
 uschar buffer[256];
 uschar name[256];
-void *reset_point = store_get(0);
+rmark reset_point;
 
 name[0] = 0;  /* No name set */
 
@@ -737,7 +762,7 @@ user requests */
 dbdata_type = check_args(argc, argv, US"fixdb", US"");
 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
 
-for(;;)
+for(; (reset_point = store_mark()); store_reset(reset_point))
   {
   open_db dbblock;
   open_db *dbm;
@@ -747,12 +772,11 @@ for(;;)
   dbdata_callout_cache *callout;
   dbdata_ratelimit *ratelimit;
   dbdata_ratelimit_unique *rate_unique;
-  int i, oldlength;
+  dbdata_tls_session *session;
+  int oldlength;
   uschar *t;
   uschar field[256], value[256];
 
-  store_reset(reset_point);
-
   printf("> ");
   if (Ufgets(buffer, 256, stdin) == NULL) break;
 
@@ -785,7 +809,7 @@ for(;;)
     int verify = 1;
     spool_directory = argv[1];
 
-    if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE)))
+    if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
       continue;
 
     if (Ustrcmp(field, "d") == 0)
@@ -817,147 +841,120 @@ for(;;)
           switch(dbdata_type)
             {
             case type_retry:
-            retry = (dbdata_retry *)record;
-            /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
-
-            switch(fieldno)
-              {
-              case 0:
-              retry->basic_errno = Uatoi(value);
-              break;
-
-              case 1:
-              retry->more_errno = Uatoi(value);
-              break;
-
-              case 2:
-              if ((tt = read_time(value)) > 0) retry->first_failed = tt;
-                else printf("bad time value\n");
-              break;
-
-              case 3:
-              if ((tt = read_time(value)) > 0) retry->last_try = tt;
-                else printf("bad time value\n");
-              break;
-
-              case 4:
-              if ((tt = read_time(value)) > 0) retry->next_try = tt;
-                else printf("bad time value\n");
-              break;
-
-              case 5:
-              if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
-              else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
-              else printf("\"yes\" or \"no\" expected=n");
-              break;
-
-              default:
-              printf("unknown field number\n");
-              verify = 0;
-              break;
-              }
-            break;
+             retry = (dbdata_retry *)record;
+             /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
+
+             switch(fieldno)
+               {
+               case 0: retry->basic_errno = Uatoi(value);
+                       break;
+               case 1: retry->more_errno = Uatoi(value);
+                       break;
+               case 2: if ((tt = read_time(value)) > 0) retry->first_failed = tt;
+                       else printf("bad time value\n");
+                       break;
+               case 3: if ((tt = read_time(value)) > 0) retry->last_try = tt;
+                       else printf("bad time value\n");
+                       break;
+               case 4: if ((tt = read_time(value)) > 0) retry->next_try = tt;
+                       else printf("bad time value\n");
+                       break;
+               case 5: if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
+                       else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
+                       else printf("\"yes\" or \"no\" expected=n");
+                       break;
+               default: printf("unknown field number\n");
+                        verify = 0;
+                        break;
+               }
+             break;
 
             case type_wait:
-            printf("Can't change contents of wait database record\n");
-            break;
+             printf("Can't change contents of wait database record\n");
+             break;
 
             case type_misc:
-            printf("Can't change contents of misc database record\n");
-            break;
+             printf("Can't change contents of misc database record\n");
+             break;
 
             case type_callout:
-            callout = (dbdata_callout_cache *)record;
-            /* length = sizeof(dbdata_callout_cache); */
-            switch(fieldno)
-              {
-              case 0:
-              callout->result = Uatoi(value);
-              break;
-
-              case 1:
-              callout->postmaster_result = Uatoi(value);
-              break;
-
-              case 2:
-              callout->random_result = Uatoi(value);
-              break;
-
-              default:
-              printf("unknown field number\n");
-              verify = 0;
-              break;
-              }
-            break;
+             callout = (dbdata_callout_cache *)record;
+             /* length = sizeof(dbdata_callout_cache); */
+             switch(fieldno)
+               {
+               case 0: callout->result = Uatoi(value);
+                       break;
+               case 1: callout->postmaster_result = Uatoi(value);
+                       break;
+               case 2: callout->random_result = Uatoi(value);
+                       break;
+               default: printf("unknown field number\n");
+                        verify = 0;
+                        break;
+               }
+               break;
 
             case type_ratelimit:
-            ratelimit = (dbdata_ratelimit *)record;
-            switch(fieldno)
-              {
-              case 0:
-              if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
-                else printf("bad time value\n");
-              break;
-
-              case 1:
-              ratelimit->time_usec = Uatoi(value);
-              break;
-
-              case 2:
-              ratelimit->rate = Ustrtod(value, NULL);
-              break;
-
-              case 3:
-              if (Ustrstr(name, "/unique/") != NULL
-                && oldlength >= sizeof(dbdata_ratelimit_unique))
-                {
-                rate_unique = (dbdata_ratelimit_unique *)record;
-                if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
-                  else printf("bad time value\n");
-                break;
-                }
-              /* else fall through */
-
-              case 4:
-              case 5:
-              if (Ustrstr(name, "/unique/") != NULL
-                && oldlength >= sizeof(dbdata_ratelimit_unique))
-                {
-                /* see acl.c */
-                BOOL seen;
-                unsigned n, hash, hinc;
-                uschar md5sum[16];
-                md5 md5info;
-                md5_start(&md5info);
-                md5_end(&md5info, value, Ustrlen(value), md5sum);
-                hash = md5sum[0] <<  0 | md5sum[1] <<  8
-                     | md5sum[2] << 16 | md5sum[3] << 24;
-                hinc = md5sum[4] <<  0 | md5sum[5] <<  8
-                     | md5sum[6] << 16 | md5sum[7] << 24;
-                rate_unique = (dbdata_ratelimit_unique *)record;
-                seen = TRUE;
-                for (n = 0; n < 8; n++, hash += hinc)
-                  {
-                  int bit = 1 << (hash % 8);
-                  int byte = (hash / 8) % rate_unique->bloom_size;
-                  if ((rate_unique->bloom[byte] & bit) == 0)
-                    {
-                    seen = FALSE;
-                    if (fieldno == 5) rate_unique->bloom[byte] |= bit;
-                    }
-                  }
-                printf("%s %s\n",
-                  seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
-                break;
-                }
-              /* else fall through */
-
-              default:
-              printf("unknown field number\n");
-              verify = 0;
-              break;
-              }
-            break;
+             ratelimit = (dbdata_ratelimit *)record;
+             switch(fieldno)
+               {
+               case 0: if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
+                       else printf("bad time value\n");
+                       break;
+               case 1: ratelimit->time_usec = Uatoi(value);
+                       break;
+               case 2: ratelimit->rate = Ustrtod(value, NULL);
+                       break;
+               case 3: if (Ustrstr(name, "/unique/") != NULL
+                           && oldlength >= sizeof(dbdata_ratelimit_unique))
+                         {
+                         rate_unique = (dbdata_ratelimit_unique *)record;
+                         if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
+                           else printf("bad time value\n");
+                         break;
+                         }
+                       /* else fall through */
+               case 4:
+               case 5: if (Ustrstr(name, "/unique/") != NULL
+                           && oldlength >= sizeof(dbdata_ratelimit_unique))
+                         {
+                         /* see acl.c */
+                         BOOL seen;
+                         unsigned hash, hinc;
+                         uschar md5sum[16];
+                         md5 md5info;
+                         md5_start(&md5info);
+                         md5_end(&md5info, value, Ustrlen(value), md5sum);
+                         hash = md5sum[0] <<  0 | md5sum[1] <<  8
+                              | md5sum[2] << 16 | md5sum[3] << 24;
+                         hinc = md5sum[4] <<  0 | md5sum[5] <<  8
+                              | md5sum[6] << 16 | md5sum[7] << 24;
+                         rate_unique = (dbdata_ratelimit_unique *)record;
+                         seen = TRUE;
+                         for (unsigned n = 0; n < 8; n++, hash += hinc)
+                           {
+                           int bit = 1 << (hash % 8);
+                           int byte = (hash / 8) % rate_unique->bloom_size;
+                           if ((rate_unique->bloom[byte] & bit) == 0)
+                             {
+                             seen = FALSE;
+                             if (fieldno == 5) rate_unique->bloom[byte] |= bit;
+                             }
+                           }
+                         printf("%s %s\n",
+                           seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
+                         break;
+                         }
+                       /* else fall through */
+               default: printf("unknown field number\n");
+                        verify = 0;
+                        break;
+               }
+             break;
+
+            case type_tls:
+             printf("Can't change contents of tls database record\n");
+             break;
             }
 
           dbfn_write(dbm, name, record, oldlength);
@@ -982,7 +979,7 @@ for(;;)
   /* Handle a read request, or verify after an update. */
 
   spool_directory = argv[1];
-  if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE)))
+  if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
     continue;
 
   if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
@@ -997,79 +994,84 @@ for(;;)
     switch(dbdata_type)
       {
       case type_retry:
-      retry = (dbdata_retry *)record;
-      printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
-      printf("1 extra data:   %d\n", retry->more_errno);
-      printf("2 first failed: %s\n", print_time(retry->first_failed));
-      printf("3 last try:     %s\n", print_time(retry->last_try));
-      printf("4 next try:     %s\n", print_time(retry->next_try));
-      printf("5 expired:      %s\n", (retry->expired)? "yes" : "no");
-      break;
+       retry = (dbdata_retry *)record;
+       printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
+       printf("1 extra data:   %d\n", retry->more_errno);
+       printf("2 first failed: %s\n", print_time(retry->first_failed));
+       printf("3 last try:     %s\n", print_time(retry->last_try));
+       printf("4 next try:     %s\n", print_time(retry->next_try));
+       printf("5 expired:      %s\n", (retry->expired)? "yes" : "no");
+       break;
 
       case type_wait:
-      wait = (dbdata_wait *)record;
-      t = wait->text;
-      printf("Sequence: %d\n", wait->sequence);
-      if (wait->count > WAIT_NAME_MAX)
-        {
-        printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
-          wait->count, WAIT_NAME_MAX);
-        wait->count = WAIT_NAME_MAX;
-        count_bad = 1;
-        }
-      for (i = 1; i <= wait->count; i++)
-        {
-        Ustrncpy(value, t, MESSAGE_ID_LENGTH);
-        value[MESSAGE_ID_LENGTH] = 0;
-        if (count_bad && value[0] == 0) break;
-        if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
-            Ustrspn(value, "0123456789"
-                          "abcdefghijklmnopqrstuvwxyz"
-                          "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
-          {
-          int j;
-          printf("\n**** Data corrupted: bad character in message id ****\n");
-          for (j = 0; j < MESSAGE_ID_LENGTH; j++)
-            printf("%02x ", value[j]);
-          printf("\n");
-          break;
-          }
-        printf("%s ", value);
-        t += MESSAGE_ID_LENGTH;
-        }
-      printf("\n");
-      break;
+       wait = (dbdata_wait *)record;
+       t = wait->text;
+       printf("Sequence: %d\n", wait->sequence);
+       if (wait->count > WAIT_NAME_MAX)
+         {
+         printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
+           wait->count, WAIT_NAME_MAX);
+         wait->count = WAIT_NAME_MAX;
+         count_bad = 1;
+         }
+       for (int i = 1; i <= wait->count; i++)
+         {
+         Ustrncpy(value, t, MESSAGE_ID_LENGTH);
+         value[MESSAGE_ID_LENGTH] = 0;
+         if (count_bad && value[0] == 0) break;
+         if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
+             Ustrspn(value, "0123456789"
+                           "abcdefghijklmnopqrstuvwxyz"
+                           "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
+           {
+           printf("\n**** Data corrupted: bad character in message id ****\n");
+           for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
+             printf("%02x ", value[j]);
+           printf("\n");
+           break;
+           }
+         printf("%s ", value);
+         t += MESSAGE_ID_LENGTH;
+         }
+       printf("\n");
+       break;
 
       case type_misc:
-      break;
+       break;
 
       case type_callout:
-      callout = (dbdata_callout_cache *)record;
-      printf("0 callout:    %s (%d)\n", print_cache(callout->result),
-          callout->result);
-      if (oldlength > sizeof(dbdata_callout_cache_address))
-        {
-        printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
-            callout->postmaster_result);
-        printf("2 random:     %s (%d)\n", print_cache(callout->random_result),
-            callout->random_result);
-        }
-      break;
+       callout = (dbdata_callout_cache *)record;
+       printf("0 callout:    %s (%d)\n", print_cache(callout->result),
+           callout->result);
+       if (oldlength > sizeof(dbdata_callout_cache_address))
+         {
+         printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
+             callout->postmaster_result);
+         printf("2 random:     %s (%d)\n", print_cache(callout->random_result),
+             callout->random_result);
+         }
+       break;
 
       case type_ratelimit:
-      ratelimit = (dbdata_ratelimit *)record;
-      printf("0 time stamp:  %s\n", print_time(ratelimit->time_stamp));
-      printf("1 fract. time: .%06d\n", ratelimit->time_usec);
-      printf("2 sender rate: % .3f\n", ratelimit->rate);
-      if (Ustrstr(name, "/unique/") != NULL
-       && oldlength >= sizeof(dbdata_ratelimit_unique))
-       {
-       rate_unique = (dbdata_ratelimit_unique *)record;
-       printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
-       printf("4 test filter membership\n");
-       printf("5 add element to filter\n");
-       }
-      break;
+       ratelimit = (dbdata_ratelimit *)record;
+       printf("0 time stamp:  %s\n", print_time(ratelimit->time_stamp));
+       printf("1 fract. time: .%06d\n", ratelimit->time_usec);
+       printf("2 sender rate: % .3f\n", ratelimit->rate);
+       if (Ustrstr(name, "/unique/") != NULL
+        && oldlength >= sizeof(dbdata_ratelimit_unique))
+        {
+        rate_unique = (dbdata_ratelimit_unique *)record;
+        printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
+        printf("4 test filter membership\n");
+        printf("5 add element to filter\n");
+        }
+       break;
+
+      case type_tls:
+       session = (dbdata_tls_session *)value;
+       printf("0 time stamp:  %s\n", print_time(session->time_stamp));
+       printf("1 session: .%s\n", session->session);
+       break;
       }
     }
 
@@ -1113,7 +1115,7 @@ struct stat statbuf;
 int maxkeep = 30 * 24 * 60 * 60;
 int dbdata_type, i, oldest, path_len;
 key_item *keychain = NULL;
-void *reset_point;
+rmark reset_point;
 open_db dbblock;
 open_db *dbm;
 EXIM_CURSOR *cursor;
@@ -1168,7 +1170,7 @@ oldest = time(NULL) - maxkeep;
 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
 
 spool_directory = argv[1];
-if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE)))
+if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
   exit(1);
 
 /* Prepare for building file names */
@@ -1186,7 +1188,7 @@ for (key = dbfn_scan(dbm, TRUE, &cursor);
      key;
      key = dbfn_scan(dbm, FALSE, &cursor))
   {
-  key_item *k = store_get(sizeof(key_item) + Ustrlen(key));
+  key_item *k = store_get(sizeof(key_item) + Ustrlen(key), is_tainted(key));
   k->next = keychain;
   keychain = k;
   Ustrcpy(k->key, key);
@@ -1195,13 +1197,10 @@ for (key = dbfn_scan(dbm, TRUE, &cursor);
 /* Now scan the collected keys and operate on the records, resetting
 the store each time round. */
 
-reset_point = store_get(0);
-
-while (keychain)
+for (; keychain && (reset_point = store_mark()); store_reset(reset_point))
   {
   dbdata_generic *value;
 
-  store_reset(reset_point);
   key = keychain->key;
   keychain = keychain->next;
   value = dbfn_read_with_length(dbm, key, NULL);
@@ -1244,10 +1243,9 @@ while (keychain)
 
     for (;;)
       {
-      int offset;
       int length = wait->count * MESSAGE_ID_LENGTH;
 
-      for (offset = length - MESSAGE_ID_LENGTH;
+      for (int offset = length - MESSAGE_ID_LENGTH;
            offset >= 0; offset -= MESSAGE_ID_LENGTH)
         {
         Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
@@ -1334,12 +1332,10 @@ while (keychain)
     if (*id++ != ':') continue;
 
     for (i = 0; i < MESSAGE_ID_LENGTH; i++)
-      {
       if (i == 6 || i == 13)
         { if (id[i] != '-') break; }
       else
         { if (!isalnum(id[i])) break; }
-      }
     if (i < MESSAGE_ID_LENGTH) continue;
 
     Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);