DNS: avoid overflow in cache TTL for negative entries. Bug 1395
[exim.git] / src / src / verify.c
index c2ee4789288620bcee27469cc42497453cd9cfd2..435570bc87203cd3435e246f1641b106f7000a2f 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions concerned with verifying things. The original code for callout
@@ -14,7 +14,6 @@ caching was contributed by Kevin Fleming (but I hacked it around a bit). */
 
 #define CUTTHROUGH_CMD_TIMEOUT  30     /* timeout for cutthrough-routing calls */
 #define CUTTHROUGH_DATA_TIMEOUT 60     /* timeout for cutthrough-routing calls */
-address_item cutthrough_addr;
 static smtp_outblock ctblock;
 uschar ctbuffer[8192];
 
@@ -22,6 +21,7 @@ uschar ctbuffer[8192];
 /* Structure for caching DNSBL lookups */
 
 typedef struct dnsbl_cache_block {
+  time_t expiry;
   dns_address *rhs;
   uschar *text;
   int rc;
@@ -39,6 +39,7 @@ static tree_node *dnsbl_cache = NULL;
 #define MT_NOT 1
 #define MT_ALL 2
 
+static uschar cutthrough_response(char, uschar **);
 
 
 /*************************************************
@@ -58,7 +59,7 @@ Returns:            the cache record if a non-expired one exists, else NULL
 */
 
 static dbdata_callout_cache *
-get_callout_cache_record(open_db *dbm_file, uschar *key, uschar *type,
+get_callout_cache_record(open_db *dbm_file, const uschar *key, uschar *type,
   int positive_expire, int negative_expire)
 {
 BOOL negative;
@@ -70,7 +71,7 @@ cache_record = dbfn_read_with_length(dbm_file, key, &length);
 
 if (cache_record == NULL)
   {
-  HDEBUG(D_verify) debug_printf("callout cache: no %s record found\n", type);
+  HDEBUG(D_verify) debug_printf("callout cache: no %s record found for %s\n", type, key);
   return NULL;
   }
 
@@ -84,7 +85,7 @@ now = time(NULL);
 
 if (now - cache_record->time_stamp > expire)
   {
-  HDEBUG(D_verify) debug_printf("callout cache: %s record expired\n", type);
+  HDEBUG(D_verify) debug_printf("callout cache: %s record expired for %s\n", type, key);
   return NULL;
   }
 
@@ -111,7 +112,7 @@ if (type[0] == 'd' && cache_record->result != ccache_reject)
     cache_record->random_result = ccache_unknown;
   }
 
-HDEBUG(D_verify) debug_printf("callout cache: found %s record\n", type);
+HDEBUG(D_verify) debug_printf("callout cache: found %s record for %s\n", type, key);
 return cache_record;
 }
 
@@ -164,7 +165,7 @@ BOOL done = FALSE;
 uschar *address_key;
 uschar *from_address;
 uschar *random_local_part = NULL;
-uschar *save_deliver_domain = deliver_domain;
+const uschar *save_deliver_domain = deliver_domain;
 uschar **failure_ptr = is_recipient?
   &recipient_verify_failure : &sender_verify_failure;
 open_db dbblock;
@@ -173,6 +174,9 @@ dbdata_callout_cache new_domain_record;
 dbdata_callout_cache_address new_address_record;
 host_item *host;
 time_t callout_start_time;
+#ifdef EXPERIMENTAL_INTERNATIONAL
+BOOL utf8_offered = FALSE;
+#endif
 
 new_domain_record.result = ccache_unknown;
 new_domain_record.postmaster_result = ccache_unknown;
@@ -189,12 +193,12 @@ from_address = US"";
 
 if (is_recipient)
   {
-  if ((options & vopt_callout_recipsender) != 0)
+  if (options & vopt_callout_recipsender)
     {
     address_key = string_sprintf("%s/<%s>", addr->address, sender_address);
     from_address = sender_address;
     }
-  else if ((options & vopt_callout_recippmaster) != 0)
+  else if (options & vopt_callout_recippmaster)
     {
     address_key = string_sprintf("%s/<postmaster@%s>", addr->address,
       qualify_domain_sender);
@@ -388,12 +392,9 @@ else
   log the fact, but carry on without randomming. */
 
   if (callout_random && callout_random_local_part != NULL)
-    {
-    random_local_part = expand_string(callout_random_local_part);
-    if (random_local_part == NULL)
+    if (!(random_local_part = expand_string(callout_random_local_part)))
       log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
         "callout_random_local_part: %s", expand_string_message);
-    }
 
   /* Default the connect and overall callout timeouts if not set, and record the
   time we are starting so that we can enforce it. */
@@ -410,6 +411,113 @@ else
 
   if (smtp_out != NULL && !disable_callout_flush) mac_smtp_fflush();
 
+/* cutthrough-multi: if a nonfirst rcpt has the same routing as the first,
+and we are holding a cutthrough conn open, we can just append the rcpt to
+that conn for verification purposes (and later delivery also).  Simplest
+coding means skipping this whole loop and doing the append separately.
+
+We will need to remember it has been appended so that rcpt-acl tail code
+can do it there for the non-rcpt-verify case.  For this we keep an addresscount.
+*/
+
+  /* Can we re-use an open cutthrough connection? */
+  if (  cutthrough.fd >= 0
+     && (options & (vopt_callout_recipsender | vopt_callout_recippmaster))
+       == vopt_callout_recipsender
+     && !random_local_part
+     && !pm_mailfrom
+     )
+    {
+    if (addr->transport == cutthrough.addr.transport)
+      for (host = host_list; host; host = host->next)
+       if (Ustrcmp(host->address, cutthrough.host.address) == 0)
+         {
+         int host_af;
+         uschar *interface = NULL;  /* Outgoing interface to use; NULL => any */
+         int port = 25;
+
+         deliver_host = host->name;
+         deliver_host_address = host->address;
+         deliver_host_port = host->port;
+         deliver_domain = addr->domain;
+         transport_name = addr->transport->name;
+
+         host_af = (Ustrchr(host->address, ':') == NULL)? AF_INET:AF_INET6;
+
+         if (!smtp_get_interface(tf->interface, host_af, addr, &interface,
+                 US"callout") ||
+             !smtp_get_port(tf->port, addr, &port, US"callout"))
+           log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address,
+             addr->message);
+
+         if (  (  interface == cutthrough.interface
+               || (  interface
+                  && cutthrough.interface
+                  && Ustrcmp(interface, cutthrough.interface) == 0
+               )  )
+            && port == cutthrough.host.port
+            )
+           {
+           uschar * resp;
+
+           /* Match!  Send the RCPT TO, append the addr, set done */
+           done =
+             smtp_write_command(&ctblock, FALSE, "RCPT TO:<%.1000s>\r\n",
+               transport_rcpt_address(addr,
+                 (addr->transport == NULL)? FALSE :
+                  addr->transport->rcpt_include_affixes)) >= 0 &&
+             cutthrough_response('2', &resp) == '2';
+
+           /* This would go horribly wrong if a callout fail was ignored by ACL.
+           We punt by abandoning cutthrough on a reject, like the
+           first-rcpt does. */
+
+           if (done)
+             {
+             address_item * na = store_get(sizeof(address_item));
+             *na = cutthrough.addr;
+             cutthrough.addr = *addr;
+             cutthrough.addr.host_used = &cutthrough.host;
+             cutthrough.addr.next = na;
+
+             cutthrough.nrcpt++;
+             }
+           else
+             {
+             cancel_cutthrough_connection("recipient rejected");
+             if (errno == ETIMEDOUT)
+               {
+               HDEBUG(D_verify) debug_printf("SMTP timeout\n");
+               }
+             else if (errno == 0)
+               {
+               if (*resp == 0)
+                 Ustrcpy(resp, US"connection dropped");
+
+               addr->message =
+                 string_sprintf("response to \"%s\" from %s [%s] was: %s",
+                   big_buffer, host->name, host->address,
+                   string_printing(resp));
+
+               addr->user_message =
+                 string_sprintf("Callout verification failed:\n%s", resp);
+
+               /* Hard rejection ends the process */
+
+               if (resp[0] == '5')   /* Address rejected */
+                 {
+                 yield = FAIL;
+                 done = TRUE;
+                 }
+               }
+             }
+           }
+         break;
+         }
+    if (!done)
+      cancel_cutthrough_connection("incompatible connection");
+    }
+
   /* Now make connections to the hosts and do real callouts. The list of hosts
   is passed in as an argument. */
 
@@ -426,6 +534,11 @@ else
     BOOL esmtp;
     BOOL suppress_tls = FALSE;
     uschar *interface = NULL;  /* Outgoing interface to use; NULL => any */
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
+    BOOL dane = FALSE;
+    BOOL dane_required;
+    dns_answer tlsa_dnsa;
+#endif
     uschar inbuffer[4096];
     uschar outbuffer[1024];
     uschar responsebuffer[4096];
@@ -462,11 +575,14 @@ else
 
     deliver_host = host->name;
     deliver_host_address = host->address;
+    deliver_host_port = host->port;
     deliver_domain = addr->domain;
+    transport_name = addr->transport->name;
 
-    if (!smtp_get_interface(tf->interface, host_af, addr, NULL, &interface,
-            US"callout") ||
-        !smtp_get_port(tf->port, addr, &port, US"callout"))
+    if (  !smtp_get_interface(tf->interface, host_af, addr, &interface,
+            US"callout")
+       || !smtp_get_port(tf->port, addr, &port, US"callout")
+       )
       log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address,
         addr->message);
 
@@ -492,26 +608,57 @@ else
     outblock.cmd_count = 0;
     outblock.authenticating = FALSE;
 
-    /* Reset the parameters of a TLS session */
-    tls_out.cipher = tls_out.peerdn = NULL;
-
     /* Connect to the host; on failure, just loop for the next one, but we
     set the error for the last one. Use the callout_connect timeout. */
 
     tls_retry_connection:
 
+    /* Reset the parameters of a TLS session */
+    tls_out.cipher = tls_out.peerdn = tls_out.peercert = NULL;
+
     inblock.sock = outblock.sock =
-      smtp_connect(host, host_af, port, interface, callout_connect, TRUE, NULL);
-    /* reconsider DSCP here */
+      smtp_connect(host, host_af, port, interface, callout_connect,
+                 addr->transport);
     if (inblock.sock < 0)
       {
       addr->message = string_sprintf("could not connect to %s [%s]: %s",
           host->name, host->address, strerror(errno));
+      transport_name = NULL;
       deliver_host = deliver_host_address = NULL;
       deliver_domain = save_deliver_domain;
       continue;
       }
 
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
+      {
+      int rc;
+
+      tls_out.dane_verified = FALSE;
+      tls_out.tlsa_usage = 0;
+
+      dane_required =
+       verify_check_given_host(&ob->hosts_require_dane, host) == OK;
+
+      if (host->dnssec == DS_YES)
+       {
+       if(  (  dane_required
+            || verify_check_given_host(&ob->hosts_try_dane, host) == OK
+            )
+         && (rc = tlsa_lookup(host, &tlsa_dnsa, dane_required, &dane)) != OK
+         )
+         return rc;
+       }
+      else if (dane_required)
+       {
+       log_write(0, LOG_MAIN, "DANE error: %s lookup not DNSSEC", host->name);
+       return FAIL;
+       }
+
+      if (dane)
+       ob->tls_tempfail_tryclear = FALSE;
+      }
+#endif  /*DANE*/
+
     /* Expand the helo_data string to find the host name to use. */
 
     if (tf->helo_data != NULL)
@@ -533,28 +680,42 @@ else
     /* Unless ssl-on-connect, wait for the initial greeting */
     smtps_redo_greeting:
 
-    #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
     if (!smtps || (smtps && tls_out.active >= 0))
-    #endif
+#endif
+      {
       if (!(done= smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout)))
         goto RESPONSE_FAILED;
 
+#ifdef EXPERIMENTAL_EVENT
+      lookup_dnssec_authenticated = host->dnssec==DS_YES ? US"yes"
+       : host->dnssec==DS_NO ? US"no" : NULL;
+      if (event_raise(addr->transport->event_action,
+                           US"smtp:connect", responsebuffer))
+       {
+       lookup_dnssec_authenticated = NULL;
+       /* Logging?  Debug? */
+       goto RESPONSE_FAILED;
+       }
+      lookup_dnssec_authenticated = NULL;
+#endif
+      }
+
     /* Not worth checking greeting line for ESMTP support */
-    if (!(esmtp = verify_check_this_host(&(ob->hosts_avoid_esmtp), NULL,
-      host->name, host->address, NULL) != OK))
+    if (!(esmtp = verify_check_given_host(&ob->hosts_avoid_esmtp, host) != OK))
       DEBUG(D_transport)
         debug_printf("not sending EHLO (host matches hosts_avoid_esmtp)\n");
 
     tls_redo_helo:
 
-    #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
     if (smtps  &&  tls_out.active < 0) /* ssl-on-connect, first pass */
       {
       tls_offered = TRUE;
       ob->tls_tempfail_tryclear = FALSE;
       }
-      else                             /* all other cases */
-    #endif
+    else                               /* all other cases */
+#endif
 
       { esmtp_retry:
 
@@ -568,26 +729,26 @@ else
          done= FALSE;
          goto RESPONSE_FAILED;
          }
-        #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
         tls_offered = FALSE;
-        #endif
+#endif
         esmtp = FALSE;
         goto esmtp_retry;                      /* fallback to HELO */
         }
 
       /* Set tls_offered if the response to EHLO specifies support for STARTTLS. */
-      #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
       if (esmtp && !suppress_tls &&  tls_out.active < 0)
-        {
-          if (regex_STARTTLS == NULL) regex_STARTTLS =
-           regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
+       {
+       if (regex_STARTTLS == NULL) regex_STARTTLS =
+         regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
 
-          tls_offered = pcre_exec(regex_STARTTLS, NULL, CS responsebuffer,
-                       Ustrlen(responsebuffer), 0, PCRE_EOPT, NULL, 0) >= 0;
+       tls_offered = pcre_exec(regex_STARTTLS, NULL, CS responsebuffer,
+                     Ustrlen(responsebuffer), 0, PCRE_EOPT, NULL, 0) >= 0;
        }
       else
         tls_offered = FALSE;
-      #endif
+#endif
       }
 
     /* If TLS is available on this connection attempt to
@@ -598,12 +759,10 @@ else
     the client not be required to use TLS. If the response is bad, copy the buffer
     for error analysis. */
 
-    #ifdef SUPPORT_TLS
-    if (tls_offered &&
-       verify_check_this_host(&(ob->hosts_avoid_tls), NULL, host->name,
-         host->address, NULL) != OK &&
-       verify_check_this_host(&(ob->hosts_verify_avoid_tls), NULL, host->name,
-         host->address, NULL) != OK
+#ifdef SUPPORT_TLS
+    if (  tls_offered
+       && verify_check_given_host(&ob->hosts_avoid_tls, host) != OK
+       && verify_check_given_host(&ob->hosts_verify_avoid_tls, host) != OK
        )
       {
       uschar buffer2[4096];
@@ -623,11 +782,11 @@ else
         {
         if (errno != 0 || buffer2[0] == 0 ||
                (buffer2[0] == '4' && !ob->tls_tempfail_tryclear))
-       {
-       Ustrncpy(responsebuffer, buffer2, sizeof(responsebuffer));
-       done= FALSE;
-       goto RESPONSE_FAILED;
-       }
+         {
+         Ustrncpy(responsebuffer, buffer2, sizeof(responsebuffer));
+         done= FALSE;
+         goto RESPONSE_FAILED;
+         }
         }
 
        /* STARTTLS accepted or ssl-on-connect: try to negotiate a TLS session. */
@@ -636,37 +795,59 @@ else
        int oldtimeout = ob->command_timeout;
        int rc;
 
+       tls_negotiate:
        ob->command_timeout = callout;
-        rc = tls_client_start(inblock.sock, host, addr, ob);
+        rc = tls_client_start(inblock.sock, host, addr, addr->transport
+# ifdef EXPERIMENTAL_DANE
+                           , dane ? &tlsa_dnsa : NULL
+# endif
+                           );
        ob->command_timeout = oldtimeout;
 
-        /* TLS negotiation failed; give an error.  Try in clear on a new connection,
-           if the options permit it for this host. */
+        /* TLS negotiation failed; give an error.  Try in clear on a new
+       connection, if the options permit it for this host. */
         if (rc != OK)
           {
-       if (  rc == DEFER
-          && ob->tls_tempfail_tryclear
-          && !smtps
-          && verify_check_this_host(&(ob->hosts_require_tls), NULL,
-            host->name, host->address, NULL) != OK
-#ifdef EXPERIMENTAL_DANE
-          && verify_check_this_host(&(ob->hosts_require_dane), NULL,
-            host->name, host->address, NULL) != OK
-#endif
-          )
-         {
-            (void)close(inblock.sock);
-         log_write(0, LOG_MAIN, "TLS session failure: delivering unencrypted "
-           "to %s [%s] (not in hosts_require_tls)", host->name, host->address);
-         suppress_tls = TRUE;
-         goto tls_retry_connection;
-         }
-       /*save_errno = ERRNO_TLSFAILURE;*/
-       /*message = US"failure while setting up TLS session";*/
-       send_quit = FALSE;
-       done= FALSE;
-       goto TLS_FAILED;
-       }
+         if (rc == DEFER)
+           {
+           (void)close(inblock.sock);
+# ifdef EXPERIMENTAL_EVENT
+           (void) event_raise(addr->transport->event_action,
+                                   US"tcp:close", NULL);
+# endif
+# ifdef EXPERIMENTAL_DANE
+           if (dane)
+             {
+             if (!dane_required)
+               {
+               log_write(0, LOG_MAIN, "DANE attempt failed;"
+                 " trying CA-root TLS to %s [%s] (not in hosts_require_dane)",
+                 host->name, host->address);
+               dane = FALSE;
+               goto tls_negotiate;
+               }
+             }
+           else
+# endif
+             if (  ob->tls_tempfail_tryclear
+                && !smtps
+                && verify_check_given_host(&ob->hosts_require_tls, host) != OK
+                )
+             {
+             log_write(0, LOG_MAIN, "TLS session failure:"
+               " delivering unencrypted to %s [%s] (not in hosts_require_tls)",
+               host->name, host->address);
+             suppress_tls = TRUE;
+             goto tls_retry_connection;
+             }
+           }
+
+         /*save_errno = ERRNO_TLSFAILURE;*/
+         /*message = US"failure while setting up TLS session";*/
+         send_quit = FALSE;
+         done= FALSE;
+         goto TLS_FAILED;
+         }
 
         /* TLS session is set up.  Copy info for logging. */
         addr->cipher = tls_out.cipher;
@@ -674,7 +855,7 @@ else
 
         /* For SMTPS we need to wait for the initial OK response, then do HELO. */
         if (smtps)
-        goto smtps_redo_greeting;
+         goto smtps_redo_greeting;
 
         /* For STARTTLS we need to redo EHLO */
         goto tls_redo_helo;
@@ -683,23 +864,24 @@ else
 
     /* If the host is required to use a secure channel, ensure that we have one. */
     if (tls_out.active < 0)
-      if (  verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name,
-             host->address, NULL) == OK
-#ifdef EXPERIMENTAL_DANE
-        || verify_check_this_host(&(ob->hosts_require_dane), NULL, host->name,
-             host->address, NULL) == OK
-#endif
+      if (
+# ifdef EXPERIMENTAL_DANE
+        dane ||
+# endif
+         verify_check_given_host(&ob->hosts_require_tls, host) == OK
         )
         {
         /*save_errno = ERRNO_TLSREQUIRED;*/
-        log_write(0, LOG_MAIN, "a TLS session is required for %s [%s], but %s",
+        log_write(0, LOG_MAIN,
+         "H=%s [%s]: a TLS session is required for this host, but %s",
           host->name, host->address,
-       tls_offered? "an attempt to start TLS failed" : "the server did not offer TLS support");
+         tls_offered ? "an attempt to start TLS failed"
+                     : "the server did not offer TLS support");
         done= FALSE;
         goto TLS_FAILED;
         }
 
-    #endif /*SUPPORT_TLS*/
+#endif /*SUPPORT_TLS*/
 
     done = TRUE; /* so far so good; have response to HELO */
 
@@ -707,20 +889,20 @@ else
 
     /* For now, transport_filter by cutthrough-delivery is not supported */
     /* Need proper integration with the proper transport mechanism. */
-    if (cutthrough_delivery)
+    if (cutthrough.delivery)
       {
       if (addr->transport->filter_command)
         {
-        cutthrough_delivery= FALSE;
+        cutthrough.delivery = FALSE;
         HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of transport filter\n");
         }
-      #ifndef DISABLE_DKIM
+#ifndef DISABLE_DKIM
       if (ob->dkim_domain)
         {
-        cutthrough_delivery= FALSE;
+        cutthrough.delivery = FALSE;
         HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of DKIM signing\n");
         }
-      #endif
+#endif
       }
 
     SEND_FAILED:
@@ -742,6 +924,40 @@ else
         }
       }
 
+#ifdef EXPERIMENTAL_INTERNATIONAL
+    else if (  addr->prop.utf8_msg
+           && !addr->prop.utf8_downcvt
+           && !(  esmtp
+               && (  regex_UTF8
+                  || ( (regex_UTF8 = regex_must_compile(
+                         US"\\n250[\\s\\-]SMTPUTF8(\\s|\\n|$)", FALSE, TRUE)),
+                     TRUE
+                  )  )
+               && (  (utf8_offered = pcre_exec(regex_UTF8, NULL,
+                           CS responsebuffer, Ustrlen(responsebuffer),
+                           0, PCRE_EOPT, NULL, 0) >= 0)
+                  || addr->prop.utf8_downcvt_maybe
+           )   )  )
+      {
+      HDEBUG(D_acl|D_v) debug_printf("utf8 required but not offered\n");
+      errno = ERRNO_UTF8_FWD;
+      setflag(addr, af_verify_nsfail);
+      done = FALSE;
+      }
+    else if (  addr->prop.utf8_msg
+           && (addr->prop.utf8_downcvt || !utf8_offered)
+           && (setflag(addr, af_utf8_downcvt),
+               from_address = string_address_utf8_to_alabel(from_address,
+                                     &addr->message),
+               addr->message
+           )  )
+      {
+      errno = ERRNO_EXPANDFAIL;
+      setflag(addr, af_verify_nsfail);
+      done = FALSE;
+      }
+#endif
+
     /* If we haven't authenticated, but are required to, give up. */
     /* Try to AUTH */
 
@@ -759,7 +975,13 @@ else
       ( (addr->auth_sndr = client_authenticated_sender),
 
     /* Send the MAIL command */
-        (smtp_write_command(&outblock, FALSE, "MAIL FROM:<%s>%s\r\n",
+        (smtp_write_command(&outblock, FALSE,
+#ifdef EXPERIMENTAL_INTERNATIONAL
+         addr->prop.utf8_msg && !addr->prop.utf8_downcvt
+         ? "MAIL FROM:<%s>%s SMTPUTF8\r\n"
+         :
+#endif
+           "MAIL FROM:<%s>%s\r\n",
           from_address, responsebuffer) >= 0)
       )  &&
 
@@ -798,6 +1020,23 @@ else
 
     else
       {
+      const uschar * rcpt_domain = addr->domain;
+
+#ifdef EXPERIMENTAL_INTERNATIONAL
+      uschar * errstr = NULL;
+      if (  testflag(addr, af_utf8_downcvt)
+        && (rcpt_domain = string_domain_utf8_to_alabel(rcpt_domain,
+                                   &errstr), errstr)
+        )
+       {
+       addr->message = errstr;
+       errno = ERRNO_EXPANDFAIL;
+       setflag(addr, af_verify_nsfail);
+       done = FALSE;
+       rcpt_domain = US"";  /*XXX errorhandling! */
+       }
+#endif
+
       new_domain_record.result =
         (old_domain_cache_result == ccache_reject_mfnull)?
           ccache_reject_mfnull: ccache_accept;
@@ -810,7 +1049,7 @@ else
         BOOL random_ok =
           smtp_write_command(&outblock, FALSE,
             "RCPT TO:<%.1000s@%.1000s>\r\n", random_local_part,
-            addr->domain) >= 0 &&
+            rcpt_domain) >= 0 &&
           smtp_read_response(&inblock, randombuffer,
             sizeof(randombuffer), '2', callout);
 
@@ -821,16 +1060,21 @@ else
         /* If accepted, we aren't going to do any further tests below. */
 
         if (random_ok)
-          {
           new_domain_record.random_result = ccache_accept;
-          }
 
         /* Otherwise, cache a real negative response, and get back to the right
         state to send RCPT. Unless there's some problem such as a dropped
-        connection, we expect to succeed, because the commands succeeded above. */
+        connection, we expect to succeed, because the commands succeeded above.
+       However, some servers drop the connection after responding to  an
+       invalid recipient, so on (any) error we drop and remake the connection.
+       */
 
         else if (errno == 0)
           {
+         /* This would be ok for 1st rcpt a cutthrough, but no way to
+         handle a subsequent.  So refuse to support any */
+         cancel_cutthrough_connection("random-recipient");
+
           if (randombuffer[0] == '5')
             new_domain_record.random_result = ccache_reject;
 
@@ -839,10 +1083,32 @@ else
             smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
               '2', callout) &&
 
-            smtp_write_command(&outblock, FALSE, "MAIL FROM:<%s>\r\n",
+            smtp_write_command(&outblock, FALSE,
+#ifdef EXPERIMENTAL_INTERNATIONAL
+             addr->prop.utf8_msg && !addr->prop.utf8_downcvt
+             ? "MAIL FROM:<%s> SMTPUTF8\r\n"
+             :
+#endif
+               "MAIL FROM:<%s>\r\n",
               from_address) >= 0 &&
             smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
               '2', callout);
+
+         if (!done)
+           {
+           HDEBUG(D_acl|D_v)
+             debug_printf("problem after random/rset/mfrom; reopen conn\n");
+           random_local_part = NULL;
+#ifdef SUPPORT_TLS
+           tls_close(FALSE, TRUE);
+#endif
+           (void)close(inblock.sock);
+#ifdef EXPERIMENTAL_EVENT
+           (void) event_raise(addr->transport->event_action,
+                             US"tcp:close", NULL);
+#endif
+           goto tls_retry_connection;
+           }
           }
         else done = FALSE;    /* Some timeout/connection problem */
         }                     /* Random check */
@@ -855,11 +1121,27 @@ else
         /* Get the rcpt_include_affixes flag from the transport if there is one,
         but assume FALSE if there is not. */
 
+       uschar * rcpt = transport_rcpt_address(addr,
+              addr->transport ? addr->transport->rcpt_include_affixes : FALSE);
+
+#ifdef EXPERIMENTAL_INTERNATIONAL
+       /*XXX should the conversion be moved into transport_rcpt_address() ? */
+       uschar * dummy_errstr = NULL;
+       if (  testflag(addr, af_utf8_downcvt)
+          && (rcpt = string_address_utf8_to_alabel(rcpt, &dummy_errstr),
+              dummy_errstr
+          )  )
+       {
+       errno = ERRNO_EXPANDFAIL;
+       *failure_ptr = US"recipient";
+       done = FALSE;
+       }
+       else
+#endif
+
         done =
           smtp_write_command(&outblock, FALSE, "RCPT TO:<%.1000s>\r\n",
-            transport_rcpt_address(addr,
-              (addr->transport == NULL)? FALSE :
-               addr->transport->rcpt_include_affixes)) >= 0 &&
+            rcpt) >= 0 &&
           smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
             '2', callout);
 
@@ -876,8 +1158,10 @@ else
 
         if (done && pm_mailfrom != NULL)
           {
-          /*XXX not suitable for cutthrough - sequencing problems */
-       cutthrough_delivery= FALSE;
+          /* Could possibly shift before main verify, just above, and be ok
+         for cutthrough.  But no way to handle a subsequent rcpt, so just
+         refuse any */
+       cancel_cutthrough_connection("postmaster verify");
        HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of postmaster verify\n");
 
           done =
@@ -894,7 +1178,7 @@ else
 
             ((
             smtp_write_command(&outblock, FALSE,
-              "RCPT TO:<postmaster@%.1000s>\r\n", addr->domain) >= 0 &&
+              "RCPT TO:<postmaster@%.1000s>\r\n", rcpt_domain) >= 0 &&
             smtp_read_response(&inblock, responsebuffer,
               sizeof(responsebuffer), '2', callout)
             )
@@ -945,6 +1229,21 @@ else
         HDEBUG(D_verify) debug_printf("SMTP timeout\n");
         send_quit = FALSE;
         }
+#ifdef EXPERIMENTAL_INTERNATIONAL
+      else if (errno == ERRNO_UTF8_FWD)
+       {
+       extern int acl_where;   /* src/acl.c */
+       errno = 0;
+       addr->message = string_sprintf(
+           "response to \"%s\" from %s [%s] did not include SMTPUTF8",
+            big_buffer, host->name, host->address);
+        addr->user_message = acl_where == ACL_WHERE_RCPT
+         ? US"533 mailbox name not allowed"
+         : US"550 mailbox unavailable";
+       yield = FAIL;
+       done = TRUE;
+       }
+#endif
       else if (errno == 0)
         {
         if (*responsebuffer == 0) Ustrcpy(responsebuffer, US"connection dropped");
@@ -972,32 +1271,35 @@ else
 
     /* End the SMTP conversation and close the connection. */
 
-    /* Cutthrough - on a successfull connect and recipient-verify with use-sender
-    and we have no cutthrough conn so far
+    /* Cutthrough - on a successfull connect and recipient-verify with
+    use-sender and we are 1st rcpt and have no cutthrough conn so far
     here is where we want to leave the conn open */
-    if (  cutthrough_delivery
+    if (  cutthrough.delivery
+       && rcpt_count == 1
        && done
        && yield == OK
        && (options & (vopt_callout_recipsender|vopt_callout_recippmaster)) == vopt_callout_recipsender
        && !random_local_part
        && !pm_mailfrom
-       && cutthrough_fd < 0
+       && cutthrough.fd < 0
+       && !lmtp
        )
       {
-      cutthrough_fd= outblock.sock;    /* We assume no buffer in use in the outblock */
-      cutthrough_addr = *addr;         /* Save the address_item for later logging */
-      cutthrough_addr.next =     NULL;
-      cutthrough_addr.host_used = store_get(sizeof(host_item));
-      cutthrough_addr.host_used->name =    host->name;
-      cutthrough_addr.host_used->address = host->address;
-      cutthrough_addr.host_used->port =    port;
+      cutthrough.fd = outblock.sock;   /* We assume no buffer in use in the outblock */
+      cutthrough.nrcpt = 1;
+      cutthrough.interface = interface;
+      cutthrough.host = *host;
+      cutthrough.addr = *addr;         /* Save the address_item for later logging */
+      cutthrough.addr.next =     NULL;
+      cutthrough.addr.host_used = &cutthrough.host;
       if (addr->parent)
-        *(cutthrough_addr.parent = store_get(sizeof(address_item)))= *addr->parent;
+        *(cutthrough.addr.parent = store_get(sizeof(address_item))) =
+         *addr->parent;
       ctblock.buffer = ctbuffer;
       ctblock.buffersize = sizeof(ctbuffer);
       ctblock.ptr = ctbuffer;
       /* ctblock.cmd_count = 0; ctblock.authenticating = FALSE; */
-      ctblock.sock = cutthrough_fd;
+      ctblock.sock = cutthrough.fd;
       }
     else
       {
@@ -1006,10 +1308,14 @@ else
         cancel_cutthrough_connection("multiple verify calls");
       if (send_quit) (void)smtp_write_command(&outblock, FALSE, "QUIT\r\n");
 
-      #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
       tls_close(FALSE, TRUE);
-      #endif
+#endif
       (void)close(inblock.sock);
+#ifdef EXPERIMENTAL_EVENT
+      (void) event_raise(addr->transport->event_action,
+                             US"tcp:close", NULL);
+#endif
       }
 
     }    /* Loop through all hosts, while !done */
@@ -1118,7 +1424,8 @@ address_item addr2;
 get rewritten. */
 
 addr2 = *addr;
-HDEBUG(D_acl) debug_printf("----------- start cutthrough setup ------------\n");
+HDEBUG(D_acl) debug_printf("----------- %s cutthrough setup ------------\n",
+  rcpt_count > 1 ? "more" : "start");
 (void) verify_address(&addr2, NULL,
        vopt_is_recipient | vopt_callout_recipsender | vopt_callout_no_cache,
        CUTTHROUGH_CMD_TIMEOUT, -1, -1,
@@ -1133,14 +1440,14 @@ return;
 static BOOL
 cutthrough_send(int n)
 {
-if(cutthrough_fd < 0)
+if(cutthrough.fd < 0)
   return TRUE;
 
 if(
 #ifdef SUPPORT_TLS
-   (tls_out.active == cutthrough_fd) ? tls_write(FALSE, ctblock.buffer, n) :
+   (tls_out.active == cutthrough.fd) ? tls_write(FALSE, ctblock.buffer, n) :
 #endif
-   send(cutthrough_fd, ctblock.buffer, n, 0) > 0
+   send(cutthrough.fd, ctblock.buffer, n, 0) > 0
   )
 {
   transport_count += n;
@@ -1172,7 +1479,7 @@ return TRUE;
 BOOL
 cutthrough_puts(uschar * cp, int n)
 {
-if (cutthrough_fd < 0)       return TRUE;
+if (cutthrough.fd < 0)       return TRUE;
 if (_cutthrough_puts(cp, n)) return TRUE;
 cancel_cutthrough_connection("transmit failed");
 return FALSE;
@@ -1180,7 +1487,7 @@ return FALSE;
 
 
 static BOOL
-_cutthrough_flush_send( void )
+_cutthrough_flush_send(void)
 {
 int n= ctblock.ptr-ctblock.buffer;
 
@@ -1193,7 +1500,7 @@ return TRUE;
 
 /* Send out any bufferred output.  Return boolean success. */
 BOOL
-cutthrough_flush_send( void )
+cutthrough_flush_send(void)
 {
 if (_cutthrough_flush_send()) return TRUE;
 cancel_cutthrough_connection("transmit failed");
@@ -1202,7 +1509,7 @@ return FALSE;
 
 
 BOOL
-cutthrough_put_nl( void )
+cutthrough_put_nl(void)
 {
 return cutthrough_puts(US"\r\n", 2);
 }
@@ -1220,7 +1527,7 @@ inblock.buffer = inbuffer;
 inblock.buffersize = sizeof(inbuffer);
 inblock.ptr = inbuffer;
 inblock.ptrend = inbuffer;
-inblock.sock = cutthrough_fd;
+inblock.sock = cutthrough.fd;
 /* this relies on (inblock.sock == tls_out.active) */
 if(!smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), expect, CUTTHROUGH_DATA_TIMEOUT))
   cancel_cutthrough_connection("target timeout on read");
@@ -1228,7 +1535,7 @@ if(!smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), expect,
 if(copy != NULL)
   {
   uschar * cp;
-  *copy= cp= string_copy(responsebuffer);
+  *copy = cp = string_copy(responsebuffer);
   /* Trim the trailing end of line */
   cp += Ustrlen(responsebuffer);
   if(cp > *copy  &&  cp[-1] == '\n') *--cp = '\0';
@@ -1241,9 +1548,9 @@ return responsebuffer[0];
 
 /* Negotiate dataphase with the cutthrough target, returning success boolean */
 BOOL
-cutthrough_predata( void )
+cutthrough_predata(void)
 {
-if(cutthrough_fd < 0)
+if(cutthrough.fd < 0)
   return FALSE;
 
 HDEBUG(D_transport|D_acl|D_v) debug_printf("  SMTP>> DATA\n");
@@ -1274,9 +1581,9 @@ return TRUE;
 /* Expands newlines to wire format (CR,NL).           */
 /* Also sends header-terminating blank line.          */
 BOOL
-cutthrough_headers_send( void )
+cutthrough_headers_send(void)
 {
-if(cutthrough_fd < 0)
+if(cutthrough.fd < 0)
   return FALSE;
 
 /* We share a routine with the mainline transport to handle header add/remove/rewrites,
@@ -1284,10 +1591,12 @@ if(cutthrough_fd < 0)
 */
 HDEBUG(D_acl) debug_printf("----------- start cutthrough headers send -----------\n");
 
-if (!transport_headers_send(&cutthrough_addr, cutthrough_fd,
-       cutthrough_addr.transport->add_headers, cutthrough_addr.transport->remove_headers,
+if (!transport_headers_send(&cutthrough.addr, cutthrough.fd,
+       cutthrough.addr.transport->add_headers,
+       cutthrough.addr.transport->remove_headers,
        &cutthrough_write_chunk, TRUE,
-       cutthrough_addr.transport->rewrite_rules, cutthrough_addr.transport->rewrite_existflags))
+       cutthrough.addr.transport->rewrite_rules,
+       cutthrough.addr.transport->rewrite_existflags))
   return FALSE;
 
 HDEBUG(D_acl) debug_printf("----------- done cutthrough headers send ------------\n");
@@ -1296,9 +1605,9 @@ return TRUE;
 
 
 static void
-close_cutthrough_connection( const char * why )
+close_cutthrough_connection(const char * why)
 {
-if(cutthrough_fd >= 0)
+if(cutthrough.fd >= 0)
   {
   /* We could be sending this after a bunch of data, but that is ok as
      the only way to cancel the transfer in dataphase is to drop the tcp
@@ -1313,18 +1622,18 @@ if(cutthrough_fd >= 0)
   #ifdef SUPPORT_TLS
   tls_close(FALSE, TRUE);
   #endif
-  (void)close(cutthrough_fd);
-  cutthrough_fd= -1;
+  (void)close(cutthrough.fd);
+  cutthrough.fd = -1;
   HDEBUG(D_acl) debug_printf("----------- cutthrough shutdown (%s) ------------\n", why);
   }
 ctblock.ptr = ctbuffer;
 }
 
 void
-cancel_cutthrough_connection( const char * why )
+cancel_cutthrough_connection(const char * why)
 {
 close_cutthrough_connection(why);
-cutthrough_delivery= FALSE;
+cutthrough.delivery = FALSE;
 }
 
 
@@ -1336,33 +1645,45 @@ cutthrough_delivery= FALSE;
    Return smtp response-class digit.
 */
 uschar *
-cutthrough_finaldot( void )
+cutthrough_finaldot(void)
 {
+uschar res;
+address_item * addr;
 HDEBUG(D_transport|D_acl|D_v) debug_printf("  SMTP>> .\n");
 
 /* Assume data finshed with new-line */
-if(!cutthrough_puts(US".", 1) || !cutthrough_put_nl() || !cutthrough_flush_send())
-  return cutthrough_addr.message;
+if(  !cutthrough_puts(US".", 1)
+  || !cutthrough_put_nl()
+  || !cutthrough_flush_send()
+  )
+  return cutthrough.addr.message;
 
-switch(cutthrough_response('2', &cutthrough_addr.message))
+res = cutthrough_response('2', &cutthrough.addr.message);
+for (addr = &cutthrough.addr; addr; addr = addr->next)
   {
-  case '2':
-    delivery_log(LOG_MAIN, &cutthrough_addr, (int)'>', NULL);
-    close_cutthrough_connection("delivered");
-    break;
+  addr->message = cutthrough.addr.message;
+  switch(res)
+    {
+    case '2':
+      delivery_log(LOG_MAIN, addr, (int)'>', NULL);
+      close_cutthrough_connection("delivered");
+      break;
 
-  case '4':
-    delivery_log(LOG_MAIN, &cutthrough_addr, 0, US"tmp-reject from cutthrough after DATA:");
-    break;
+    case '4':
+      delivery_log(LOG_MAIN, addr, 0,
+       US"tmp-reject from cutthrough after DATA:");
+      break;
 
-  case '5':
-    delivery_log(LOG_MAIN|LOG_REJECT, &cutthrough_addr, 0, US"rejected after DATA:");
-    break;
+    case '5':
+      delivery_log(LOG_MAIN|LOG_REJECT, addr, 0,
+       US"rejected after DATA:");
+      break;
 
-  default:
-    break;
+    default:
+      break;
+    }
   }
-  return cutthrough_addr.message;
+return cutthrough.addr.message;
 }
 
 
@@ -1393,7 +1714,7 @@ if (addr != vaddr)
   vaddr->user_message = addr->user_message;
   vaddr->basic_errno = addr->basic_errno;
   vaddr->more_errno = addr->more_errno;
-  vaddr->p.address_data = addr->p.address_data;
+  vaddr->prop.address_data = addr->prop.address_data;
   copyflag(vaddr, addr, af_pass_message);
   }
 return yield;
@@ -1654,8 +1975,8 @@ while (addr_new != NULL)
 
   /* Just in case some router parameter refers to it. */
 
-  return_path = (addr->p.errors_address != NULL)?
-    addr->p.errors_address : sender_address;
+  return_path = (addr->prop.errors_address != NULL)?
+    addr->prop.errors_address : sender_address;
 
   /* Split the address into domain and local part, handling the %-hack if
   necessary, and then route it. While routing a sender address, set
@@ -1715,7 +2036,7 @@ while (addr_new != NULL)
         if (tf.hosts != NULL && (host_list == NULL || tf.hosts_override))
           {
           uschar *s;
-          uschar *save_deliver_domain = deliver_domain;
+          const uschar *save_deliver_domain = deliver_domain;
           uschar *save_deliver_localpart = deliver_localpart;
 
           host_list = NULL;    /* Ignore the router's hosts */
@@ -1735,7 +2056,6 @@ while (addr_new != NULL)
           else
             {
             int flags;
-            uschar *canonical_name;
             host_item *host, *nexthost;
             host_build_hostlist(&host_list, s, tf.hosts_randomize);
 
@@ -1754,21 +2074,20 @@ while (addr_new != NULL)
               nexthost = host->next;
               if (tf.gethostbyname ||
                   string_is_ip_address(host->name, NULL) != 0)
-                (void)host_find_byname(host, NULL, flags, &canonical_name, TRUE);
+                (void)host_find_byname(host, NULL, flags, NULL, TRUE);
               else
                {
-               uschar * d_request = NULL, * d_require = NULL;
+               dnssec_domains * dnssec_domains = NULL;
                if (Ustrcmp(addr->transport->driver_name, "smtp") == 0)
                  {
                  smtp_transport_options_block * ob =
                      (smtp_transport_options_block *)
                        addr->transport->options_block;
-                 d_request = ob->dnssec_request_domains;
-                 d_require = ob->dnssec_require_domains;
+                 dnssec_domains = &ob->dnssec;
                  }
 
                 (void)host_find_bydns(host, NULL, flags, NULL, NULL, NULL,
-                 d_request, d_require, &canonical_name, NULL);
+                 dnssec_domains, NULL, NULL);
                }
               }
             }
@@ -1792,8 +2111,10 @@ while (addr_new != NULL)
 #ifdef SUPPORT_TLS
          deliver_set_expansions(addr);
 #endif
+         verify_mode = is_recipient ? US"R" : US"S";
           rc = do_callout(addr, host_list, &tf, callout, callout_overall,
             callout_connect, options, se_mailfrom, pm_mailfrom);
+         verify_mode = NULL;
           }
         }
       else
@@ -1947,7 +2268,7 @@ while (addr_new != NULL)
       /* If we have carried on to verify a child address, we want the value
       of $address_data to be that of the child */
 
-      vaddr->p.address_data = addr->p.address_data;
+      vaddr->prop.address_data = addr->prop.address_data;
       yield = OK;
       goto out;
       }
@@ -1979,8 +2300,8 @@ for (addr_list = addr_local, i = 0; i < 2; addr_list = addr_remote, i++)
 
     fprintf(f, "%s", CS addr->address);
 #ifdef EXPERIMENTAL_SRS
-    if(addr->p.srs_sender)
-      fprintf(f, "    [srs = %s]", addr->p.srs_sender);
+    if(addr->prop.srs_sender)
+      fprintf(f, "    [srs = %s]", addr->prop.srs_sender);
 #endif
 
     /* If the address is a duplicate, show something about it. */
@@ -2043,6 +2364,12 @@ for (addr_list = addr_local, i = 0; i < 2; addr_list = addr_remote, i++)
         while (len++ < maxaddlen) fprintf(f," ");
         if (h->mx >= 0) fprintf(f, "MX=%d", h->mx);
         if (h->port != PORT_NONE) fprintf(f, " port=%d", h->port);
+        if (running_in_test_harness)
+#ifndef DISABLE_DNSSEC
+          fprintf(f, " ad=%s", h->dnssec==DS_YES ? "yes" : "no");
+#else
+          fprintf(f, " ad=no");
+#endif
         if (h->status == hstatus_unusable) fprintf(f, " ** unusable **");
         fprintf(f, "\n");
         }
@@ -2162,7 +2489,8 @@ for (h = header_list; h != NULL && yield == OK; h = h->next)
         verb = US"begins";
         }
 
-      *msgptr = string_printing(
+      /* deconst cast ok as we're passing a non-const to string_printing() */
+      *msgptr = US string_printing(
         string_sprintf("%s: failing address in \"%.*s:\" header %s: %.*s",
           errmess, tt - h->text, h->text, verb, len, s));
 
@@ -2589,7 +2917,7 @@ if (ip_bind(sock, host_af, interface_address, 0) < 0)
 if (ip_connect(sock, host_af, sender_host_address, port, rfc1413_query_timeout)
      < 0)
   {
-  if (errno == ETIMEDOUT && (log_extra_selector & LX_ident_timeout) != 0)
+  if (errno == ETIMEDOUT && LOGGING(ident_timeout))
     {
     log_write(0, LOG_MAIN, "ident connection to %s timed out",
       sender_host_address);
@@ -2683,9 +3011,9 @@ if (*p == 0) goto END_OFF;
 /* The rest of the line is the data we want. We turn it into printing
 characters when we save it, so that it cannot mess up the format of any logging
 or Received: lines into which it gets inserted. We keep a maximum of 127
-characters. */
+characters. The deconst cast is ok as we fed a nonconst to string_printing() */
 
-sender_ident = string_printing(string_copyn(p, 127));
+sender_ident = US string_printing(string_copyn(p, 127));
 DEBUG(D_ident) debug_printf("sender_ident = %s\n", sender_ident);
 
 END_OFF:
@@ -2730,7 +3058,7 @@ Returns:         OK      matched
 */
 
 int
-check_host(void *arg, uschar *ss, uschar **valueptr, uschar **error)
+check_host(void *arg, const uschar *ss, const uschar **valueptr, uschar **error)
 {
 check_host_block *cb = (check_host_block *)arg;
 int mlen = -1;
@@ -2738,7 +3066,7 @@ int maskoffset;
 BOOL iplookup = FALSE;
 BOOL isquery = FALSE;
 BOOL isiponly = cb->host_name != NULL && cb->host_name[0] == 0;
-uschar *t;
+const uschar *t;
 uschar *semicolon;
 uschar **aliases;
 
@@ -2919,6 +3247,10 @@ if (*t == 0)
   h.address = NULL;
   h.mx = MX_NONE;
 
+  /* Using byname rather than bydns here means we cannot determine dnssec
+  status.  On the other hand it is unclear how that could be either
+  propagated up or enforced. */
+
   rc = host_find_byname(&h, NULL, HOST_FIND_QUALIFY_SINGLE, NULL, FALSE);
   if (rc == HOST_FOUND || rc == HOST_FOUND_LOCAL)
     {
@@ -2951,7 +3283,7 @@ on spec. */
 
 if ((semicolon = Ustrchr(ss, ';')) != NULL)
   {
-  uschar *affix;
+  const uschar *affix;
   int partial, affixlen, starflags, id;
 
   *semicolon = 0;
@@ -3052,12 +3384,12 @@ determined from the IP address, the result is FAIL unless the item
 "+allow_unknown" was met earlier in the list, in which case OK is returned. */
 
 int
-verify_check_this_host(uschar **listptr, unsigned int *cache_bits,
-  uschar *host_name, uschar *host_address, uschar **valueptr)
+verify_check_this_host(const uschar **listptr, unsigned int *cache_bits,
+  const uschar *host_name, const uschar *host_address, const uschar **valueptr)
 {
 int rc;
 unsigned int *local_cache_bits = cache_bits;
-uschar *save_host_address = deliver_host_address;
+const uschar *save_host_address = deliver_host_address;
 check_host_block cb;
 cb.host_name = host_name;
 cb.host_address = host_address;
@@ -3096,6 +3428,15 @@ return rc;
 
 
 
+/*************************************************
+*      Check the given host item matches a list  *
+*************************************************/
+int
+verify_check_given_host(uschar **listptr, host_item *host)
+{
+return verify_check_this_host(CUSS listptr, NULL, host->name, host->address, NULL);
+}
+
 /*************************************************
 *      Check the remote host matches a list      *
 *************************************************/
@@ -3115,7 +3456,7 @@ Returns:               the yield of verify_check_this_host(),
 int
 verify_check_host(uschar **listptr)
 {
-return verify_check_this_host(listptr, sender_host_cache, NULL,
+return verify_check_this_host(CUSS listptr, sender_host_cache, NULL,
   (sender_host_address == NULL)? US"" : sender_host_address, NULL);
 }
 
@@ -3244,21 +3585,37 @@ if (!string_format(query, sizeof(query), "%s.%s", prepend, domain))
 
 /* Look for this query in the cache. */
 
-t = tree_search(dnsbl_cache, query);
+if (  (t = tree_search(dnsbl_cache, query))
+   && (cb = t->data.ptr)->expiry > time(NULL)
+   )
+
+/* Previous lookup was cached */
+
+  {
+  HDEBUG(D_dnsbl) debug_printf("using result of previous DNS lookup\n");
+  }
 
 /* If not cached from a previous lookup, we must do a DNS lookup, and
 cache the result in permanent memory. */
 
-if (t == NULL)
+else
   {
+  uint ttl = 3600;
+
   store_pool = POOL_PERM;
 
-  /* Set up a tree entry to cache the lookup */
+  if (t)
+    {
+    HDEBUG(D_dnsbl) debug_printf("cached data found but past valid time; ");
+    }
 
-  t = store_get(sizeof(tree_node) + Ustrlen(query));
-  Ustrcpy(t->name, query);
-  t->data.ptr = cb = store_get(sizeof(dnsbl_cache_block));
-  (void)tree_insertnode(&dnsbl_cache, t);
+  else
+    {  /* Set up a tree entry to cache the lookup */
+    t = store_get(sizeof(tree_node) + Ustrlen(query));
+    Ustrcpy(t->name, query);
+    t->data.ptr = cb = store_get(sizeof(dnsbl_cache_block));
+    (void)tree_insertnode(&dnsbl_cache, t);
+    }
 
   /* Do the DNS loopup . */
 
@@ -3276,24 +3633,28 @@ if (t == NULL)
 
   Quite apart from one A6 RR generating multiple addresses, there are DNS
   lists that return more than one A record, so we must handle multiple
-  addresses generated in that way as well. */
+  addresses generated in that way as well.
+
+  Mark the cache entry with the "now" plus the minimum of the address TTLs,
+  or some suitably far-future time if none were found. */
 
   if (cb->rc == DNS_SUCCEED)
     {
     dns_record *rr;
     dns_address **addrp = &(cb->rhs);
     for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
-         rr != NULL;
+         rr;
          rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
       {
       if (rr->type == T_A)
         {
         dns_address *da = dns_address_from_rr(&dnsa, rr);
-        if (da != NULL)
+        if (da)
           {
           *addrp = da;
           while (da->next != NULL) da = da->next;
           addrp = &(da->next);
+         if (ttl > rr->ttl) ttl = rr->ttl;
           }
         }
       }
@@ -3305,17 +3666,10 @@ if (t == NULL)
     if (cb->rhs == NULL) cb->rc = DNS_NODATA;
     }
 
+  cb->expiry = time(NULL)+ttl;
   store_pool = old_pool;
   }
 
-/* Previous lookup was cached */
-
-else
-  {
-  HDEBUG(D_dnsbl) debug_printf("using result of previous DNS lookup\n");
-  cb = t->data.ptr;
-  }
-
 /* We now have the result of the DNS lookup, either newly done, or cached
 from a previous call. If the lookup succeeded, check against the address
 list if there is one. This may be a positive equality list (introduced by
@@ -3346,7 +3700,7 @@ if (cb->rc == DNS_SUCCEED)
       {
       int ipsep = ',';
       uschar ip[46];
-      uschar *ptr = iplist;
+      const uschar *ptr = iplist;
       uschar *res;
 
       /* Handle exact matching */
@@ -3549,11 +3903,11 @@ Returns:    OK      successful lookup (i.e. the address is on the list), or
 */
 
 int
-verify_check_dnsbl(uschar **listptr)
+verify_check_dnsbl(const uschar **listptr)
 {
 int sep = 0;
 int defer_return = FAIL;
-uschar *list = *listptr;
+const uschar *list = *listptr;
 uschar *domain;
 uschar *s;
 uschar buffer[1024];
@@ -3699,7 +4053,7 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
     uschar keybuffer[256];
     uschar keyrevadd[128];
 
-    while ((keydomain = string_nextinlist(&key, &keysep, keybuffer,
+    while ((keydomain = string_nextinlist(CUSS &key, &keysep, keybuffer,
             sizeof(keybuffer))) != NULL)
       {
       uschar *prepend = keydomain;