wip - see failed-summary.log.list_match_value. Pretty much ok.
[exim.git] / src / src / verify.c
index 43caac56247de0aa7efdc872f66ce9650e2fffe8..fba1f6e9e0a96d80c447a9201bddc00ad563cc38 100644 (file)
@@ -3,6 +3,7 @@
 *************************************************/
 
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 *************************************************/
 
 /* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions concerned with verifying things. The original code for callout
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions concerned with verifying things. The original code for callout
@@ -98,7 +99,7 @@ if (type[0] == 'd' && cache_record->result != ccache_reject)
   {
   if (length == sizeof(dbdata_callout_cache_obs))
     {
   {
   if (length == sizeof(dbdata_callout_cache_obs))
     {
-    dbdata_callout_cache *new = store_get(sizeof(dbdata_callout_cache));
+    dbdata_callout_cache *new = store_get(sizeof(dbdata_callout_cache), FALSE);
     memcpy(new, cache_record, length);
     new->postmaster_stamp = new->random_stamp = new->time_stamp;
     cache_record = new;
     memcpy(new, cache_record, length);
     new->postmaster_stamp = new->random_stamp = new->time_stamp;
     cache_record = new;
@@ -140,7 +141,7 @@ if (options & vopt_callout_no_cache)
   {
   HDEBUG(D_verify) debug_printf("callout cache: disabled by no_cache\n");
   }
   {
   HDEBUG(D_verify) debug_printf("callout cache: disabled by no_cache\n");
   }
-else if (!(dbm_file = dbfn_open(US"callout", O_RDWR, &dbblock, FALSE)))
+else if (!(dbm_file = dbfn_open(US"callout", O_RDWR, &dbblock, FALSE, TRUE)))
   {
   HDEBUG(D_verify) debug_printf("callout cache: not available\n");
   }
   {
   HDEBUG(D_verify) debug_printf("callout cache: not available\n");
   }
@@ -313,7 +314,7 @@ implying some kind of I/O error. We don't want to write the cache in that case.
 Otherwise the value is ccache_accept, ccache_reject, or ccache_reject_mfnull. */
 
 if (dom_rec->result != ccache_unknown)
 Otherwise the value is ccache_accept, ccache_reject, or ccache_reject_mfnull. */
 
 if (dom_rec->result != ccache_unknown)
-  if (!(dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE)))
+  if (!(dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE, TRUE)))
     {
     HDEBUG(D_verify) debug_printf("callout cache: not available\n");
     }
     {
     HDEBUG(D_verify) debug_printf("callout cache: not available\n");
     }
@@ -335,7 +336,7 @@ is disabled. */
 if (done  &&  addr_rec->result != ccache_unknown)
   {
   if (!dbm_file)
 if (done  &&  addr_rec->result != ccache_unknown)
   {
   if (!dbm_file)
-    dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE);
+    dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE, TRUE);
   if (!dbm_file)
     {
     HDEBUG(D_verify) debug_printf("no callout cache available\n");
   if (!dbm_file)
     {
     HDEBUG(D_verify) debug_printf("no callout cache available\n");
@@ -420,7 +421,7 @@ if (addr->transport == cutthrough.addr.transport)
 
        if (done)
          {
 
        if (done)
          {
-         address_item * na = store_get(sizeof(address_item));
+         address_item * na = store_get(sizeof(address_item), FALSE);
          *na = cutthrough.addr;
          cutthrough.addr = *addr;
          cutthrough.addr.host_used = &cutthrough.host;
          *na = cutthrough.addr;
          cutthrough.addr = *addr;
          cutthrough.addr.host_used = &cutthrough.host;
@@ -574,6 +575,7 @@ else
   {
   smtp_transport_options_block *ob =
     (smtp_transport_options_block *)addr->transport->options_block;
   {
   smtp_transport_options_block *ob =
     (smtp_transport_options_block *)addr->transport->options_block;
+  smtp_context * sx = NULL;
 
   /* The information wasn't available in the cache, so we have to do a real
   callout and save the result in the cache for next time, unless no_cache is set,
 
   /* The information wasn't available in the cache, so we have to do a real
   callout and save the result in the cache for next time, unless no_cache is set,
@@ -586,6 +588,10 @@ else
       log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
         "callout_random_local_part: %s", expand_string_message);
 
       log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
         "callout_random_local_part: %s", expand_string_message);
 
+  /* Compile regex' used by client-side smtp */
+
+  smtp_deliver_init();
+
   /* Default the connect and overall callout timeouts if not set, and record the
   time we are starting so that we can enforce it. */
 
   /* Default the connect and overall callout timeouts if not set, and record the
   time we are starting so that we can enforce it. */
 
@@ -625,8 +631,7 @@ coding means skipping this whole loop and doing the append separately.  */
     {
     int host_af;
     int port = 25;
     {
     int host_af;
     int port = 25;
-    uschar *interface = NULL;  /* Outgoing interface to use; NULL => any */
-    smtp_context sx;
+    uschar * interface = NULL;  /* Outgoing interface to use; NULL => any */
 
     if (!host->address)
       {
 
     if (!host->address)
       {
@@ -666,14 +671,17 @@ coding means skipping this whole loop and doing the append separately.  */
       log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address,
         addr->message);
 
       log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address,
         addr->message);
 
-    sx.addrlist = addr;
-    sx.conn_args.host = host;
-    sx.conn_args.host_af = host_af,
-    sx.port = port;
-    sx.conn_args.interface = interface;
-    sx.helo_data = tf->helo_data;
-    sx.conn_args.tblock = addr->transport;
-    sx.verify = TRUE;
+    if (!sx) sx = store_get(sizeof(*sx), TRUE);        /* tainted buffers */
+    memset(sx, 0, sizeof(*sx));
+
+    sx->addrlist = addr;
+    sx->conn_args.host = host;
+    sx->conn_args.host_af = host_af,
+    sx->port = port;
+    sx->conn_args.interface = interface;
+    sx->helo_data = tf->helo_data;
+    sx->conn_args.tblock = addr->transport;
+    sx->verify = TRUE;
 
 tls_retry_connection:
     /* Set the address state so that errors are recorded in it */
 
 tls_retry_connection:
     /* Set the address state so that errors are recorded in it */
@@ -686,8 +694,8 @@ tls_retry_connection:
     SMTP command to send.  If we tried TLS but it failed, try again without
     if permitted */
 
     SMTP command to send.  If we tried TLS but it failed, try again without
     if permitted */
 
-    yield = smtp_setup_conn(&sx, FALSE);
-#ifdef SUPPORT_TLS
+    yield = smtp_setup_conn(sx, FALSE);
+#ifndef DISABLE_TLS
     if (  yield == DEFER
        && addr->basic_errno == ERRNO_TLSFAILURE
        && ob->tls_tempfail_tryclear
     if (  yield == DEFER
        && addr->basic_errno == ERRNO_TLSFAILURE
        && ob->tls_tempfail_tryclear
@@ -698,7 +706,7 @@ tls_retry_connection:
        "%s: callout unencrypted to %s [%s] (not in hosts_require_tls)",
        addr->message, host->name, host->address);
       addr->transport_return = PENDING_DEFER;
        "%s: callout unencrypted to %s [%s] (not in hosts_require_tls)",
        addr->message, host->name, host->address);
       addr->transport_return = PENDING_DEFER;
-      yield = smtp_setup_conn(&sx, TRUE);
+      yield = smtp_setup_conn(sx, TRUE);
       }
 #endif
     if (yield != OK)
       }
 #endif
     if (yield != OK)
@@ -728,11 +736,11 @@ tls_retry_connection:
     addr->authenticator = client_authenticator;
     addr->auth_id = client_authenticated_id;
 
     addr->authenticator = client_authenticator;
     addr->auth_id = client_authenticated_id;
 
-    sx.from_addr = from_address;
-    sx.first_addr = sx.sync_addr = addr;
-    sx.ok = FALSE;                     /*XXX these 3 last might not be needed for verify? */
-    sx.send_rset = TRUE;
-    sx.completed_addr = FALSE;
+    sx->from_addr = from_address;
+    sx->first_addr = sx->sync_addr = addr;
+    sx->ok = FALSE;                    /*XXX these 3 last might not be needed for verify? */
+    sx->send_rset = TRUE;
+    sx->completed_addr = FALSE;
 
     new_domain_record.result = old_domain_cache_result == ccache_reject_mfnull
       ? ccache_reject_mfnull : ccache_accept;
 
     new_domain_record.result = old_domain_cache_result == ccache_reject_mfnull
       ? ccache_reject_mfnull : ccache_accept;
@@ -789,12 +797,12 @@ tls_retry_connection:
       Avoid using a SIZE option on the MAIL for all random-rcpt checks.
       */
 
       Avoid using a SIZE option on the MAIL for all random-rcpt checks.
       */
 
-      sx.avoid_option = OPTION_SIZE;
+      sx->avoid_option = OPTION_SIZE;
 
       /* Remember when we last did a random test */
       new_domain_record.random_stamp = time(NULL);
 
 
       /* Remember when we last did a random test */
       new_domain_record.random_stamp = time(NULL);
 
-      if (smtp_write_mail_and_rcpt_cmds(&sx, &yield) == 0)
+      if (smtp_write_mail_and_rcpt_cmds(sx, &yield) == 0)
        switch(addr->transport_return)
          {
          case PENDING_OK:      /* random was accepted, unfortunately */
        switch(addr->transport_return)
          {
          case PENDING_OK:      /* random was accepted, unfortunately */
@@ -805,36 +813,36 @@ tls_retry_connection:
            goto no_conn;
          case FAIL:            /* rejected: the preferred result */
            new_domain_record.random_result = ccache_reject;
            goto no_conn;
          case FAIL:            /* rejected: the preferred result */
            new_domain_record.random_result = ccache_reject;
-           sx.avoid_option = 0;
+           sx->avoid_option = 0;
 
            /* Between each check, issue RSET, because some servers accept only
            one recipient after MAIL FROM:<>.
            XXX We don't care about that for postmaster_full.  Should we? */
 
            if ((done =
 
            /* Between each check, issue RSET, because some servers accept only
            one recipient after MAIL FROM:<>.
            XXX We don't care about that for postmaster_full.  Should we? */
 
            if ((done =
-             smtp_write_command(&sx, SCMD_FLUSH, "RSET\r\n") >= 0 &&
-             smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '2', callout)))
+             smtp_write_command(sx, SCMD_FLUSH, "RSET\r\n") >= 0 &&
+             smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2', callout)))
              break;
 
            HDEBUG(D_acl|D_v)
              debug_printf_indent("problem after random/rset/mfrom; reopen conn\n");
            random_local_part = NULL;
              break;
 
            HDEBUG(D_acl|D_v)
              debug_printf_indent("problem after random/rset/mfrom; reopen conn\n");
            random_local_part = NULL;
-#ifdef SUPPORT_TLS
-           tls_close(sx.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+#ifndef DISABLE_TLS
+           tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
 #endif
            HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
 #endif
            HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
-           (void)close(sx.cctx.sock);
-           sx.cctx.sock = -1;
+           (void)close(sx->cctx.sock);
+           sx->cctx.sock = -1;
 #ifndef DISABLE_EVENT
            (void) event_raise(addr->transport->event_action,
                              US"tcp:close", NULL);
 #endif
            addr->address = main_address;
            addr->transport_return = PENDING_DEFER;
 #ifndef DISABLE_EVENT
            (void) event_raise(addr->transport->event_action,
                              US"tcp:close", NULL);
 #endif
            addr->address = main_address;
            addr->transport_return = PENDING_DEFER;
-           sx.first_addr = sx.sync_addr = addr;
-           sx.ok = FALSE;
-           sx.send_rset = TRUE;
-           sx.completed_addr = FALSE;
+           sx->first_addr = sx->sync_addr = addr;
+           sx->ok = FALSE;
+           sx->send_rset = TRUE;
+           sx->completed_addr = FALSE;
            goto tls_retry_connection;
          case DEFER:           /* 4xx response to random */
            break;              /* Just to be clear. ccache_unknown, !done. */
            goto tls_retry_connection;
          case DEFER:           /* 4xx response to random */
            break;              /* Just to be clear. ccache_unknown, !done. */
@@ -843,10 +851,10 @@ tls_retry_connection:
       /* Re-setup for main verify, or for the error message when failing */
       addr->address = main_address;
       addr->transport_return = PENDING_DEFER;
       /* Re-setup for main verify, or for the error message when failing */
       addr->address = main_address;
       addr->transport_return = PENDING_DEFER;
-      sx.first_addr = sx.sync_addr = addr;
-      sx.ok = FALSE;
-      sx.send_rset = TRUE;
-      sx.completed_addr = FALSE;
+      sx->first_addr = sx->sync_addr = addr;
+      sx->ok = FALSE;
+      sx->send_rset = TRUE;
+      sx->completed_addr = FALSE;
       }
     else
       done = TRUE;
       }
     else
       done = TRUE;
@@ -857,10 +865,10 @@ tls_retry_connection:
     if (done)
       {
       if (!(options & vopt_is_recipient  &&  options & vopt_callout_no_cache))
     if (done)
       {
       if (!(options & vopt_is_recipient  &&  options & vopt_callout_no_cache))
-       sx.avoid_option = OPTION_SIZE;
+       sx->avoid_option = OPTION_SIZE;
 
       done = FALSE;
 
       done = FALSE;
-      switch(smtp_write_mail_and_rcpt_cmds(&sx, &yield))
+      switch(smtp_write_mail_and_rcpt_cmds(sx, &yield))
        {
        case 0:  switch(addr->transport_return) /* ok so far */
                    {
        {
        case 0:  switch(addr->transport_return) /* ok so far */
                    {
@@ -878,7 +886,7 @@ tls_retry_connection:
 
        case -1:                                /* MAIL response error */
                  *failure_ptr = US"mail";
 
        case -1:                                /* MAIL response error */
                  *failure_ptr = US"mail";
-                 if (errno == 0 && sx.buffer[0] == '5')
+                 if (errno == 0 && sx->buffer[0] == '5')
                    {
                    setflag(addr, af_verify_nsfail);
                    if (from_address[0] == 0)
                    {
                    setflag(addr, af_verify_nsfail);
                    if (from_address[0] == 0)
@@ -908,8 +916,8 @@ tls_retry_connection:
       cancel_cutthrough_connection(TRUE, US"postmaster verify");
       HDEBUG(D_acl|D_v) debug_printf_indent("Cutthrough cancelled by presence of postmaster verify\n");
 
       cancel_cutthrough_connection(TRUE, US"postmaster verify");
       HDEBUG(D_acl|D_v) debug_printf_indent("Cutthrough cancelled by presence of postmaster verify\n");
 
-      done = smtp_write_command(&sx, SCMD_FLUSH, "RSET\r\n") >= 0
-          && smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '2', callout);
+      done = smtp_write_command(sx, SCMD_FLUSH, "RSET\r\n") >= 0
+          && smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2', callout);
 
       if (done)
        {
 
       if (done)
        {
@@ -919,23 +927,23 @@ tls_retry_connection:
        addr->address = string_sprintf("postmaster@%.1000s", addr->domain);
        addr->transport_return = PENDING_DEFER;
 
        addr->address = string_sprintf("postmaster@%.1000s", addr->domain);
        addr->transport_return = PENDING_DEFER;
 
-       sx.from_addr = pm_mailfrom;
-       sx.first_addr = sx.sync_addr = addr;
-       sx.ok = FALSE;
-       sx.send_rset = TRUE;
-       sx.completed_addr = FALSE;
-       sx.avoid_option = OPTION_SIZE;
+       sx->from_addr = pm_mailfrom;
+       sx->first_addr = sx->sync_addr = addr;
+       sx->ok = FALSE;
+       sx->send_rset = TRUE;
+       sx->completed_addr = FALSE;
+       sx->avoid_option = OPTION_SIZE;
 
 
-       if(  smtp_write_mail_and_rcpt_cmds(&sx, &yield) == 0
+       if(  smtp_write_mail_and_rcpt_cmds(sx, &yield) == 0
          && addr->transport_return == PENDING_OK
          )
          done = TRUE;
        else
          done = (options & vopt_callout_fullpm) != 0
          && addr->transport_return == PENDING_OK
          )
          done = TRUE;
        else
          done = (options & vopt_callout_fullpm) != 0
-             && smtp_write_command(&sx, SCMD_FLUSH,
+             && smtp_write_command(sx, SCMD_FLUSH,
                            "RCPT TO:<postmaster>\r\n") >= 0
                            "RCPT TO:<postmaster>\r\n") >= 0
-             && smtp_read_response(&sx, sx.buffer,
-                           sizeof(sx.buffer), '2', callout);
+             && smtp_read_response(sx, sx->buffer,
+                           sizeof(sx->buffer), '2', callout);
 
        /* Sort out the cache record */
 
 
        /* Sort out the cache record */
 
@@ -943,7 +951,7 @@ tls_retry_connection:
 
        if (done)
          new_domain_record.postmaster_result = ccache_accept;
 
        if (done)
          new_domain_record.postmaster_result = ccache_accept;
-       else if (errno == 0 && sx.buffer[0] == '5')
+       else if (errno == 0 && sx->buffer[0] == '5')
          {
          *failure_ptr = US"postmaster";
          setflag(addr, af_verify_pmfail);
          {
          *failure_ptr = US"postmaster";
          setflag(addr, af_verify_pmfail);
@@ -968,7 +976,7 @@ no_conn:
       {
       case ETIMEDOUT:
        HDEBUG(D_verify) debug_printf("SMTP timeout\n");
       {
       case ETIMEDOUT:
        HDEBUG(D_verify) debug_printf("SMTP timeout\n");
-       sx.send_quit = FALSE;
+       sx->send_quit = FALSE;
        break;
 
 #ifdef SUPPORT_I18N
        break;
 
 #ifdef SUPPORT_I18N
@@ -976,8 +984,7 @@ no_conn:
        {
        extern int acl_where;   /* src/acl.c */
        errno = 0;
        {
        extern int acl_where;   /* src/acl.c */
        errno = 0;
-       addr->message = string_sprintf(
-           "response to \"EHLO\" did not include SMTPUTF8");
+       addr->message = US"response to \"EHLO\" did not include SMTPUTF8";
        addr->user_message = acl_where == ACL_WHERE_RCPT
          ? US"533 no support for internationalised mailbox name"
          : US"550 mailbox unavailable";
        addr->user_message = acl_where == ACL_WHERE_RCPT
          ? US"533 no support for internationalised mailbox name"
          : US"550 mailbox unavailable";
@@ -985,20 +992,13 @@ no_conn:
        done = TRUE;
        }
        break;
        done = TRUE;
        }
        break;
-#endif
-#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
-      case ERRNO_REQUIRETLS:
-        addr->user_message = US"530 5.7.4 REQUIRETLS support required";
-       yield = FAIL;
-       done = TRUE;
-       break;
 #endif
       case ECONNREFUSED:
 #endif
       case ECONNREFUSED:
-       sx.send_quit = FALSE;
+       sx->send_quit = FALSE;
        break;
 
       case 0:
        break;
 
       case 0:
-       if (*sx.buffer == 0) Ustrcpy(sx.buffer, US"connection dropped");
+       if (*sx->buffer == 0) Ustrcpy(sx->buffer, US"connection dropped");
 
        /*XXX test here is ugly; seem to have a split of responsibility for
        building this message.  Need to rationalise.  Where is it done
 
        /*XXX test here is ugly; seem to have a split of responsibility for
        building this message.  Need to rationalise.  Where is it done
@@ -1007,16 +1007,36 @@ no_conn:
        */
        if (!addr->message) addr->message =
          string_sprintf("response to \"%s\" was: %s",
        */
        if (!addr->message) addr->message =
          string_sprintf("response to \"%s\" was: %s",
-                         big_buffer, string_printing(sx.buffer));
+                         big_buffer, string_printing(sx->buffer));
 
 
+       /* RFC 5321 section 4.2: the text portion of the response may have only
+       HT, SP, Printable US-ASCII.  Deal with awkward chars by cutting the
+       received message off before passing it onward.  Newlines are ok; they
+       just become a multiline response (but wrapped in the error code we
+       produce). */
+
+       for (uschar * s = sx->buffer;
+            *s && s < sx->buffer + sizeof(sx->buffer);
+            s++)
+         {
+         uschar c = *s;
+         if (c != '\t' && c != '\n' && (c < ' ' || c > '~'))
+           {
+           if (s - sx->buffer < sizeof(sx->buffer) - 12)
+             memcpy(s, "(truncated)", 12);
+           else
+             *s = '\0';
+           break;
+           }
+         }
        addr->user_message = options & vopt_is_recipient
        addr->user_message = options & vopt_is_recipient
-         ? string_sprintf("Callout verification failed:\n%s", sx.buffer)
+         ? string_sprintf("Callout verification failed:\n%s", sx->buffer)
          : string_sprintf("Called:   %s\nSent:     %s\nResponse: %s",
          : string_sprintf("Called:   %s\nSent:     %s\nResponse: %s",
-           host->address, big_buffer, sx.buffer);
+           host->address, big_buffer, sx->buffer);
 
        /* Hard rejection ends the process */
 
 
        /* Hard rejection ends the process */
 
-       if (sx.buffer[0] == '5')   /* Address rejected */
+       if (sx->buffer[0] == '5')   /* Address rejected */
          {
          yield = FAIL;
          done = TRUE;
          {
          yield = FAIL;
          done = TRUE;
@@ -1063,7 +1083,7 @@ no_conn:
        && !random_local_part
        && !pm_mailfrom
        && cutthrough.cctx.sock < 0
        && !random_local_part
        && !pm_mailfrom
        && cutthrough.cctx.sock < 0
-       && !sx.lmtp
+       && !sx->lmtp
        )
       {
       HDEBUG(D_acl|D_v) debug_printf_indent("holding verify callout open for %s\n",
        )
       {
       HDEBUG(D_acl|D_v) debug_printf_indent("holding verify callout open for %s\n",
@@ -1073,7 +1093,7 @@ no_conn:
       cutthrough.callout_hold_only = !cutthrough.delivery;
       cutthrough.is_tls =      tls_out.active.sock >= 0;
       /* We assume no buffer in use in the outblock */
       cutthrough.callout_hold_only = !cutthrough.delivery;
       cutthrough.is_tls =      tls_out.active.sock >= 0;
       /* We assume no buffer in use in the outblock */
-      cutthrough.cctx =                sx.cctx;
+      cutthrough.cctx =                sx->cctx;
       cutthrough.nrcpt =       1;
       cutthrough.transport =   addr->transport->name;
       cutthrough.interface =   interface;
       cutthrough.nrcpt =       1;
       cutthrough.transport =   addr->transport->name;
       cutthrough.interface =   interface;
@@ -1096,7 +1116,7 @@ no_conn:
       for (address_item * caddr = &cutthrough.addr, * parent = addr->parent;
           parent;
           caddr = caddr->parent, parent = parent->parent)
       for (address_item * caddr = &cutthrough.addr, * parent = addr->parent;
           parent;
           caddr = caddr->parent, parent = parent->parent)
-        *(caddr->parent = store_get(sizeof(address_item))) = *parent;
+        *(caddr->parent = store_get(sizeof(address_item), FALSE)) = *parent;
 
       ctctx.outblock.buffer = ctbuffer;
       ctctx.outblock.buffersize = sizeof(ctbuffer);
 
       ctctx.outblock.buffer = ctbuffer;
       ctctx.outblock.buffersize = sizeof(ctbuffer);
@@ -1109,26 +1129,23 @@ no_conn:
       /* Ensure no cutthrough on multiple verifies that were incompatible */
       if (options & vopt_callout_recipsender)
         cancel_cutthrough_connection(TRUE, US"not usable for cutthrough");
       /* Ensure no cutthrough on multiple verifies that were incompatible */
       if (options & vopt_callout_recipsender)
         cancel_cutthrough_connection(TRUE, US"not usable for cutthrough");
-      if (sx.send_quit)
-       {
-       (void) smtp_write_command(&sx, SCMD_FLUSH, "QUIT\r\n");
-
-       /* Wait a short time for response, and discard it */
-       smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '2', 1);
-       }
+      if (sx->send_quit)
+       if (smtp_write_command(sx, SCMD_FLUSH, "QUIT\r\n") != -1)
+         /* Wait a short time for response, and discard it */
+         smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2', 1);
 
 
-      if (sx.cctx.sock >= 0)
+      if (sx->cctx.sock >= 0)
        {
        {
-#ifdef SUPPORT_TLS
-       if (sx.cctx.tls_ctx)
+#ifndef DISABLE_TLS
+       if (sx->cctx.tls_ctx)
          {
          {
-         tls_close(sx.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
-         sx.cctx.tls_ctx = NULL;
+         tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+         sx->cctx.tls_ctx = NULL;
          }
 #endif
        HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
          }
 #endif
        HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
-       (void)close(sx.cctx.sock);
-       sx.cctx.sock = -1;
+       (void)close(sx->cctx.sock);
+       sx->cctx.sock = -1;
 #ifndef DISABLE_EVENT
        (void) event_raise(addr->transport->event_action, US"tcp:close", NULL);
 #endif
 #ifndef DISABLE_EVENT
        (void) event_raise(addr->transport->event_action, US"tcp:close", NULL);
 #endif
@@ -1183,7 +1200,7 @@ if (!done)
 /* Come here from within the cache-reading code on fast-track exit. */
 
 END_CALLOUT:
 /* Come here from within the cache-reading code on fast-track exit. */
 
 END_CALLOUT:
-tls_modify_variables(&tls_in);
+tls_modify_variables(&tls_in); /* return variables to inbound values */
 return yield;
 }
 
 return yield;
 }
 
@@ -1225,7 +1242,7 @@ if(cutthrough.cctx.sock < 0)
   return TRUE;
 
 if(
   return TRUE;
 
 if(
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
    cutthrough.is_tls
    ? tls_write(cutthrough.cctx.tls_ctx, ctctx.outblock.buffer, n, FALSE)
    :
    cutthrough.is_tls
    ? tls_write(cutthrough.cctx.tls_ctx, ctctx.outblock.buffer, n, FALSE)
    :
@@ -1426,7 +1443,7 @@ if(fd >= 0)
   /* Wait a short time for response, and discard it */
   cutthrough_response(&tmp_ctx, '2', NULL, 1);
 
   /* Wait a short time for response, and discard it */
   cutthrough_response(&tmp_ctx, '2', NULL, 1);
 
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
   if (cutthrough.is_tls)
     {
     tls_close(cutthrough.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
   if (cutthrough.is_tls)
     {
     tls_close(cutthrough.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
@@ -1539,6 +1556,8 @@ if (addr != vaddr)
   vaddr->basic_errno = addr->basic_errno;
   vaddr->more_errno = addr->more_errno;
   vaddr->prop.address_data = addr->prop.address_data;
   vaddr->basic_errno = addr->basic_errno;
   vaddr->more_errno = addr->more_errno;
   vaddr->prop.address_data = addr->prop.address_data;
+  vaddr->prop.variables = NULL;
+  tree_dup((tree_node **)&vaddr->prop.variables, addr->prop.variables);
   copyflag(vaddr, addr, af_pass_message);
   }
 return yield;
   copyflag(vaddr, addr, af_pass_message);
   }
 return yield;
@@ -1934,12 +1953,12 @@ while (addr_new)
           }
         else
           {
           }
         else
           {
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
          deliver_set_expansions(addr);
 #endif
           rc = do_callout(addr, host_list, &tf, callout, callout_overall,
             callout_connect, options, se_mailfrom, pm_mailfrom);
          deliver_set_expansions(addr);
 #endif
           rc = do_callout(addr, host_list, &tf, callout, callout_overall,
             callout_connect, options, se_mailfrom, pm_mailfrom);
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
          deliver_set_expansions(NULL);
 #endif
           }
          deliver_set_expansions(NULL);
 #endif
           }
@@ -2099,6 +2118,8 @@ while (addr_new)
       of $address_data to be that of the child */
 
       vaddr->prop.address_data = addr->prop.address_data;
       of $address_data to be that of the child */
 
       vaddr->prop.address_data = addr->prop.address_data;
+      vaddr->prop.variables = NULL;
+      tree_dup((tree_node **)&vaddr->prop.variables, addr->prop.variables);
 
       /* If stopped because more than one new address, cannot cutthrough */
 
 
       /* If stopped because more than one new address, cannot cutthrough */
 
@@ -2200,7 +2221,7 @@ the -bv or -bt case). */
 
 out:
 verify_mode = NULL;
 
 out:
 verify_mode = NULL;
-tls_modify_variables(&tls_in);
+tls_modify_variables(&tls_in); /* return variables to inbound values */
 
 return yield;
 }
 
 return yield;
 }
@@ -2240,7 +2261,7 @@ for (header_line * h = header_list; h && yield == OK; h = h->next)
 
   colon = Ustrchr(h->text, ':');
   s = colon + 1;
 
   colon = Ustrchr(h->text, ':');
   s = colon + 1;
-  while (isspace(*s)) s++;
+  Uskip_whitespace(&s);
 
   /* Loop for multiple addresses in the header, enabling group syntax. Note
   that we have to reset this after the header has been scanned. */
 
   /* Loop for multiple addresses in the header, enabling group syntax. Note
   that we have to reset this after the header has been scanned. */
@@ -2274,7 +2295,7 @@ for (header_line * h = header_list; h && yield == OK; h = h->next)
         {
         if (!f.allow_unqualified_recipient) recipient = NULL;
         }
         {
         if (!f.allow_unqualified_recipient) recipient = NULL;
         }
-      if (recipient == NULL) errmess = US"unqualified address not permitted";
+      if (!recipient) errmess = US"unqualified address not permitted";
       }
 
     /* It's an error if no address could be extracted, except for the special
       }
 
     /* It's an error if no address could be extracted, except for the special
@@ -2319,7 +2340,7 @@ for (header_line * h = header_list; h && yield == OK; h = h->next)
     /* Advance to the next address */
 
     s = ss + (terminator ? 1 : 0);
     /* Advance to the next address */
 
     s = ss + (terminator ? 1 : 0);
-    while (isspace(*s)) s++;
+    Uskip_whitespace(&s);
     }   /* Next address */
 
   f.parse_allow_group = FALSE;
     }   /* Next address */
 
   f.parse_allow_group = FALSE;
@@ -2356,7 +2377,7 @@ for (header_line * h = header_list; h; h = h->next)
     if ((*s < 33) || (*s > 126))
       {
       *msgptr = string_sprintf("Invalid character in header \"%.*s\" found",
     if ((*s < 33) || (*s > 126))
       {
       *msgptr = string_sprintf("Invalid character in header \"%.*s\" found",
-                            colon - h->text, h->text);
+                            (int)(colon - h->text), h->text);
       return FAIL;
       }
   }
       return FAIL;
       }
   }
@@ -2376,13 +2397,13 @@ The original proposed patch did the former, but I have chosen to do the latter,
 because (a) it requires no memory and (b) will use fewer resources when there
 are many addresses in To: and/or Cc: and only one or two envelope recipients.
 
 because (a) it requires no memory and (b) will use fewer resources when there
 are many addresses in To: and/or Cc: and only one or two envelope recipients.
 
-Arguments:   none
+Arguments:   case_sensitive   true if case sensitive matching should be used
 Returns:     OK    if there are no blind recipients
              FAIL  if there is at least one blind recipient
 */
 
 int
 Returns:     OK    if there are no blind recipients
              FAIL  if there is at least one blind recipient
 */
 
 int
-verify_check_notblind(void)
+verify_check_notblind(BOOL case_sensitive)
 {
 for (int i = 0; i < recipients_count; i++)
   {
 {
 for (int i = 0; i < recipients_count; i++)
   {
@@ -2397,7 +2418,7 @@ for (int i = 0; i < recipients_count; i++)
 
     colon = Ustrchr(h->text, ':');
     s = colon + 1;
 
     colon = Ustrchr(h->text, ':');
     s = colon + 1;
-    while (isspace(*s)) s++;
+    Uskip_whitespace(&s);
 
     /* Loop for multiple addresses in the header, enabling group syntax. Note
     that we have to reset this after the header has been scanned. */
 
     /* Loop for multiple addresses in the header, enabling group syntax. Note
     that we have to reset this after the header has been scanned. */
@@ -2406,8 +2427,8 @@ for (int i = 0; i < recipients_count; i++)
 
     while (*s)
       {
 
     while (*s)
       {
-      uschar *ss = parse_find_address_end(s, FALSE);
-      uschar *recipient,*errmess;
+      uschar * ss = parse_find_address_end(s, FALSE);
+      uschar * recipient, * errmess;
       int terminator = *ss;
       int start, end, domain;
 
       int terminator = *ss;
       int start, end, domain;
 
@@ -2419,22 +2440,23 @@ for (int i = 0; i < recipients_count; i++)
       *ss = terminator;
 
       /* If we found a valid recipient that has a domain, compare it with the
       *ss = terminator;
 
       /* If we found a valid recipient that has a domain, compare it with the
-      envelope recipient. Local parts are compared case-sensitively, domains
-      case-insensitively. By comparing from the start with length "domain", we
-      include the "@" at the end, which ensures that we are comparing the whole
-      local part of each address. */
-
-      if (recipient != NULL && domain != 0)
-        {
-        found = Ustrncmp(recipient, address, domain) == 0 &&
-                strcmpic(recipient + domain, address + domain) == 0;
-        if (found) break;
-        }
+      envelope recipient. Local parts are compared with case-sensitivity
+      according to the routine arg, domains case-insensitively.
+      By comparing from the start with length "domain", we include the "@" at
+      the end, which ensures that we are comparing the whole local part of each
+      address. */
+
+      if (recipient && domain != 0)
+        if ((found = (case_sensitive
+               ? Ustrncmp(recipient, address, domain) == 0
+               : strncmpic(recipient, address, domain) == 0)
+             && strcmpic(recipient + domain, address + domain) == 0))
+         break;
 
       /* Advance to the next address */
 
 
       /* Advance to the next address */
 
-      s = ss + (terminator? 1:0);
-      while (isspace(*s)) s++;
+      s = ss + (terminator ? 1:0);
+      Uskip_whitespace(&s);
       }   /* Next address */
 
     f.parse_allow_group = FALSE;
       }   /* Next address */
 
     f.parse_allow_group = FALSE;
@@ -2587,7 +2609,7 @@ for (int i = 0; i < 3 && !done; i++)
         /* If we found an empty address, just carry on with the next one, but
         kill the message. */
 
         /* If we found an empty address, just carry on with the next one, but
         kill the message. */
 
-        if (address == NULL && Ustrcmp(*log_msgptr, "empty address") == 0)
+        if (!address && Ustrcmp(*log_msgptr, "empty address") == 0)
           {
           *log_msgptr = NULL;
           s = ss;
           {
           *log_msgptr = NULL;
           s = ss;
@@ -2598,7 +2620,7 @@ for (int i = 0; i < 3 && !done; i++)
         function, and ensure that the failing address gets added to the error
         message. */
 
         function, and ensure that the failing address gets added to the error
         message. */
 
-        if (address == NULL)
+        if (!address)
           {
           new_ok = FAIL;
           while (ss > s && isspace(ss[-1])) ss--;
           {
           new_ok = FAIL;
           while (ss > s && isspace(ss[-1])) ss--;
@@ -2754,7 +2776,7 @@ for (;;)
   int size = sizeof(buffer) - (p - buffer);
 
   if (size <= 0) goto END_OFF;   /* Buffer filled without seeing \n. */
   int size = sizeof(buffer) - (p - buffer);
 
   if (size <= 0) goto END_OFF;   /* Buffer filled without seeing \n. */
-  count = ip_recv(&ident_conn_ctx, p, size, rfc1413_query_timeout);
+  count = ip_recv(&ident_conn_ctx, p, size, time(NULL) + rfc1413_query_timeout);
   if (count <= 0) goto END_OFF;  /* Read error or EOF */
 
   /* Scan what we just read, to see if we have reached the terminating \r\n. Be
   if (count <= 0) goto END_OFF;  /* Read error or EOF */
 
   /* Scan what we just read, to see if we have reached the terminating \r\n. Be
@@ -2869,7 +2891,7 @@ BOOL iplookup = FALSE;
 BOOL isquery = FALSE;
 BOOL isiponly = cb->host_name != NULL && cb->host_name[0] == 0;
 const uschar *t;
 BOOL isquery = FALSE;
 BOOL isiponly = cb->host_name != NULL && cb->host_name[0] == 0;
 const uschar *t;
-uschar *semicolon;
+uschar * semicolon, * endname, * opts;
 uschar **aliases;
 
 /* Optimize for the special case when the pattern is "*". */
 uschar **aliases;
 
 /* Optimize for the special case when the pattern is "*". */
@@ -2888,7 +2910,6 @@ provided that host name matching is permitted; if it's "@[]" match against the
 local host's IP addresses. */
 
 if (*ss == '@')
 local host's IP addresses. */
 
 if (*ss == '@')
-  {
   if (ss[1] == 0)
     {
     if (isiponly) return ERROR;
   if (ss[1] == 0)
     {
     if (isiponly) return ERROR;
@@ -2900,7 +2921,6 @@ if (*ss == '@')
       if (Ustrcmp(ip->address, cb->host_address) == 0) return OK;
     return FAIL;
     }
       if (Ustrcmp(ip->address, cb->host_address) == 0) return OK;
     return FAIL;
     }
-  }
 
 /* If the pattern is an IP address, optionally followed by a bitmask count, do
 a (possibly masked) comparison with the current IP address. */
 
 /* If the pattern is an IP address, optionally followed by a bitmask count, do
 a (possibly masked) comparison with the current IP address. */
@@ -2926,17 +2946,26 @@ if (*t == 0 || (*t == '/' && t != ss))
   return ERROR;
   }
 
   return ERROR;
   }
 
-/* See if there is a semicolon in the pattern */
+/* See if there is a semicolon in the pattern, separating a searchtype
+prefix.  If there is one then check for comma-sep options. */
 
 
-semicolon = Ustrchr(ss, ';');
+if ((semicolon = Ustrchr(ss, ';')))
+  if ((opts = Ustrchr(ss, ',')) && opts < semicolon)
+    {
+    endname = opts++;
+    opts = string_copyn(opts, semicolon - opts);
+    }
+  else
+    {
+    endname = semicolon;
+    opts = NULL;
+    }
 
 /* If we are doing an IP address only match, then all lookups must be IP
 address lookups, even if there is no "net-". */
 
 if (isiponly)
 
 /* If we are doing an IP address only match, then all lookups must be IP
 address lookups, even if there is no "net-". */
 
 if (isiponly)
-  {
   iplookup = semicolon != NULL;
   iplookup = semicolon != NULL;
-  }
 
 /* Otherwise, if the item is of the form net[n]-lookup;<file|query> then it is
 a lookup on a masked IP network, in textual form. We obey this code even if we
 
 /* Otherwise, if the item is of the form net[n]-lookup;<file|query> then it is
 a lookup on a masked IP network, in textual form. We obey this code even if we
@@ -2946,12 +2975,12 @@ key is implicit. For query-style lookups the key is specified in the query.
 From release 4.30, the use of net- for query style is no longer needed, but we
 retain it for backward compatibility. */
 
 From release 4.30, the use of net- for query style is no longer needed, but we
 retain it for backward compatibility. */
 
-if (Ustrncmp(ss, "net", 3) == 0 && semicolon != NULL)
+if (Ustrncmp(ss, "net", 3) == 0 && semicolon)
   {
   mlen = 0;
   for (t = ss + 3; isdigit(*t); t++) mlen = mlen * 10 + *t - '0';
   if (mlen == 0 && t == ss+3) mlen = -1;  /* No mask supplied */
   {
   mlen = 0;
   for (t = ss + 3; isdigit(*t); t++) mlen = mlen * 10 + *t - '0';
   if (mlen == 0 && t == ss+3) mlen = -1;  /* No mask supplied */
-  iplookup = (*t++ == '-');
+  iplookup = *t++ == '-';
   }
 else
   t = ss;
   }
 else
   t = ss;
@@ -2969,7 +2998,7 @@ if (iplookup)
 
   /* Find the search type */
 
 
   /* Find the search type */
 
-  search_type = search_findtype(t, semicolon - t);
+  search_type = search_findtype(t, endname - t);
 
   if (search_type < 0) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s",
     search_error_message);
 
   if (search_type < 0) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s",
     search_error_message);
@@ -3012,7 +3041,7 @@ if (iplookup)
   if (!(handle = search_open(filename, search_type, 0, NULL, NULL)))
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", search_error_message);
 
   if (!(handle = search_open(filename, search_type, 0, NULL, NULL)))
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", search_error_message);
 
-  result = search_find(handle, filename, key, -1, NULL, 0, 0, NULL);
+  result = search_find(handle, filename, key, -1, NULL, 0, 0, NULL, opts);
   if (valueptr) *valueptr = result;
   return result ? OK : f.search_find_defer ? DEFER: FAIL;
   }
   if (valueptr) *valueptr = result;
   return result ? OK : f.search_find_defer ? DEFER: FAIL;
   }
@@ -3070,7 +3099,7 @@ using the general string matching function. When this function is called for
 outgoing hosts, the name is always given explicitly. If it is NULL, it means we
 must use sender_host_name and its aliases, looking them up if necessary. */
 
 outgoing hosts, the name is always given explicitly. If it is NULL, it means we
 must use sender_host_name and its aliases, looking them up if necessary. */
 
-if (cb->host_name != NULL)   /* Explicit host name given */
+if (cb->host_name)   /* Explicit host name given */
   return match_check_string(cb->host_name, ss, -1, TRUE, TRUE, TRUE,
     valueptr);
 
   return match_check_string(cb->host_name, ss, -1, TRUE, TRUE, TRUE,
     valueptr);
 
@@ -3080,13 +3109,14 @@ query does not contain $sender_host_name. From release 4.23, a reference to
 $sender_host_name causes it to be looked up, so we don't need to do the lookup
 on spec. */
 
 $sender_host_name causes it to be looked up, so we don't need to do the lookup
 on spec. */
 
-if ((semicolon = Ustrchr(ss, ';')) != NULL)
+if ((semicolon = Ustrchr(ss, ';')))
   {
   {
-  const uschar *affix;
+  const uschar * affix, * opts;
   int partial, affixlen, starflags, id;
 
   *semicolon = 0;
   int partial, affixlen, starflags, id;
 
   *semicolon = 0;
-  id = search_findtype_partial(ss, &partial, &affix, &affixlen, &starflags);
+  id = search_findtype_partial(ss, &partial, &affix, &affixlen, &starflags,
+         &opts);
   *semicolon=';';
 
   if (id < 0)                           /* Unknown lookup type */
   *semicolon=';';
 
   if (id < 0)                           /* Unknown lookup type */
@@ -3251,7 +3281,7 @@ int
 verify_check_host(uschar **listptr)
 {
 return verify_check_this_host(CUSS listptr, sender_host_cache, NULL,
 verify_check_host(uschar **listptr)
 {
 return verify_check_this_host(CUSS listptr, sender_host_cache, NULL,
-  (sender_host_address == NULL)? US"" : sender_host_address, NULL);
+  sender_host_address ? sender_host_address : US"", NULL);
 }
 
 
 }
 
 
@@ -3357,16 +3387,18 @@ one_check_dnsbl(uschar *domain, uschar *domain_txt, uschar *keydomain,
   uschar *prepend, uschar *iplist, BOOL bitmask, int match_type,
   int defer_return)
 {
   uschar *prepend, uschar *iplist, BOOL bitmask, int match_type,
   int defer_return)
 {
-dns_answer dnsa;
+dns_answer * dnsa = store_get_dns_answer();
 dns_scan dnss;
 tree_node *t;
 dnsbl_cache_block *cb;
 int old_pool = store_pool;
 dns_scan dnss;
 tree_node *t;
 dnsbl_cache_block *cb;
 int old_pool = store_pool;
-uschar query[256];         /* DNS domain max length */
+uschar * query;
+int qlen;
 
 /* Construct the specific query domainname */
 
 
 /* Construct the specific query domainname */
 
-if (!string_format(query, sizeof(query), "%s.%s", prepend, domain))
+query = string_sprintf("%s.%s", prepend, domain);
+if ((qlen = Ustrlen(query)) >= 256)
   {
   log_write(0, LOG_MAIN|LOG_PANIC, "dnslist query is too long "
     "(ignored): %s...", query);
   {
   log_write(0, LOG_MAIN|LOG_PANIC, "dnslist query is too long "
     "(ignored): %s...", query);
@@ -3382,7 +3414,7 @@ if (  (t = tree_search(dnsbl_cache, query))
 /* Previous lookup was cached */
 
   {
 /* Previous lookup was cached */
 
   {
-  HDEBUG(D_dnsbl) debug_printf("using result of previous DNS lookup\n");
+  HDEBUG(D_dnsbl) debug_printf("dnslists: using result of previous lookup\n");
   }
 
 /* If not cached from a previous lookup, we must do a DNS lookup, and
   }
 
 /* If not cached from a previous lookup, we must do a DNS lookup, and
@@ -3390,7 +3422,7 @@ cache the result in permanent memory. */
 
 else
   {
 
 else
   {
-  uint ttl = 3600;
+  uint ttl = 3600;     /* max TTL for positive cache entries */
 
   store_pool = POOL_PERM;
 
 
   store_pool = POOL_PERM;
 
@@ -3401,16 +3433,16 @@ else
 
   else
     {  /* Set up a tree entry to cache the lookup */
 
   else
     {  /* Set up a tree entry to cache the lookup */
-    t = store_get(sizeof(tree_node) + Ustrlen(query));
+    t = store_get(sizeof(tree_node) + qlen + 1 + 1, is_tainted(query));
     Ustrcpy(t->name, query);
     Ustrcpy(t->name, query);
-    t->data.ptr = cb = store_get(sizeof(dnsbl_cache_block));
+    t->data.ptr = cb = store_get(sizeof(dnsbl_cache_block), FALSE);
     (void)tree_insertnode(&dnsbl_cache, t);
     }
 
   /* Do the DNS lookup . */
 
   HDEBUG(D_dnsbl) debug_printf("new DNS lookup for %s\n", query);
     (void)tree_insertnode(&dnsbl_cache, t);
     }
 
   /* Do the DNS lookup . */
 
   HDEBUG(D_dnsbl) debug_printf("new DNS lookup for %s\n", query);
-  cb->rc = dns_basic_lookup(&dnsa, query, T_A);
+  cb->rc = dns_basic_lookup(dnsa, query, T_A);
   cb->text_set = FALSE;
   cb->text = NULL;
   cb->rhs = NULL;
   cb->text_set = FALSE;
   cb->text = NULL;
   cb->rhs = NULL;
@@ -3425,34 +3457,58 @@ else
   addresses generated in that way as well.
 
   Mark the cache entry with the "now" plus the minimum of the address TTLs,
   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. */
+  or the RFC 2308 negative-cache value from the SOA if none were found. */
 
 
-  if (cb->rc == DNS_SUCCEED)
+  switch (cb->rc)
     {
     {
-    dns_address ** addrp = &(cb->rhs);
-    for (dns_record * rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS); 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)
-          {
-          *addrp = da;
-          while (da->next) da = da->next;
-          addrp = &da->next;
+    case DNS_SUCCEED:
+      {
+      dns_address ** addrp = &cb->rhs;
+      dns_address * da;
+      for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
+          rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
+       if (rr->type == T_A && (da = dns_address_from_rr(dnsa, rr)))
+         {
+         *addrp = da;
+         while (da->next) da = da->next;
+         addrp = &da->next;
          if (ttl > rr->ttl) ttl = rr->ttl;
          if (ttl > rr->ttl) ttl = rr->ttl;
-          }
-        }
+         }
 
 
-    /* If we didn't find any A records, change the return code. This can
-    happen when there is a CNAME record but there are no A records for what
-    it points to. */
+      if (cb->rhs)
+       {
+       cb->expiry = time(NULL) + ttl;
+       break;
+       }
+
+      /* If we didn't find any A records, change the return code. This can
+      happen when there is a CNAME record but there are no A records for what
+      it points to. */
+
+      cb->rc = DNS_NODATA;
+      }
+      /*FALLTHROUGH*/
+
+    case DNS_NOMATCH:
+    case DNS_NODATA:
+      {
+      /* Although there already is a neg-cache layer maintained by
+      dns_basic_lookup(), we have a dnslist cache entry allocated and
+      tree-inserted. So we may as well use it. */
 
 
-    if (!cb->rhs) cb->rc = DNS_NODATA;
+      time_t soa_negttl = dns_expire_from_soa(dnsa, T_A);
+      cb->expiry = soa_negttl ? soa_negttl : time(NULL) + ttl;
+      break;
+      }
+
+    default:
+      cb->expiry = time(NULL) + ttl;
+      break;
     }
 
     }
 
-  cb->expiry = time(NULL)+ttl;
   store_pool = old_pool;
   store_pool = old_pool;
+  HDEBUG(D_dnsbl) debug_printf("dnslists: wrote cache entry, ttl=%d\n",
+    (int)(cb->expiry - time(NULL)));
   }
 
 /* We now have the result of the DNS lookup, either newly done, or cached
   }
 
 /* We now have the result of the DNS lookup, either newly done, or cached
@@ -3463,7 +3519,7 @@ list (introduced by "&"), or a negative bitmask list (introduced by "!&").*/
 
 if (cb->rc == DNS_SUCCEED)
   {
 
 if (cb->rc == DNS_SUCCEED)
   {
-  dns_address *da = NULL;
+  dns_address * da = NULL;
   uschar *addlist = cb->rhs->address;
 
   /* For A and AAAA records, there may be multiple addresses from multiple
   uschar *addlist = cb->rhs->address;
 
   /* For A and AAAA records, there may be multiple addresses from multiple
@@ -3484,7 +3540,6 @@ if (cb->rc == DNS_SUCCEED)
     for (da = cb->rhs; da; da = da->next)
       {
       int ipsep = ',';
     for (da = cb->rhs; da; da = da->next)
       {
       int ipsep = ',';
-      uschar ip[46];
       const uschar *ptr = iplist;
       uschar *res;
 
       const uschar *ptr = iplist;
       uschar *res;
 
@@ -3492,8 +3547,8 @@ if (cb->rc == DNS_SUCCEED)
 
       if (!bitmask)
        {
 
       if (!bitmask)
        {
-        while ((res = string_nextinlist(&ptr, &ipsep, ip, sizeof(ip))))
-          if (Ustrcmp(CS da->address, ip) == 0)
+        while ((res = string_nextinlist(&ptr, &ipsep, NULL, 0)))
+          if (Ustrcmp(CS da->address, res) == 0)
            break;
        }
 
            break;
        }
 
@@ -3515,9 +3570,9 @@ if (cb->rc == DNS_SUCCEED)
 
         /* Scan the returned addresses, skipping any that are IPv6 */
 
 
         /* Scan the returned addresses, skipping any that are IPv6 */
 
-        while ((res = string_nextinlist(&ptr, &ipsep, ip, sizeof(ip))))
+        while ((res = string_nextinlist(&ptr, &ipsep, NULL, 0)))
           {
           {
-          if (host_aton(ip, address) != 1) continue;
+          if (host_aton(res, address) != 1) continue;
           if ((address[0] & mask) == address[0]) break;
           }
         }
           if ((address[0] & mask) == address[0]) break;
           }
         }
@@ -3582,9 +3637,9 @@ if (cb->rc == DNS_SUCCEED)
   if (!cb->text_set)
     {
     cb->text_set = TRUE;
   if (!cb->text_set)
     {
     cb->text_set = TRUE;
-    if (dns_basic_lookup(&dnsa, query, T_TXT) == DNS_SUCCEED)
-      for (dns_record * rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS); rr;
-           rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
+    if (dns_basic_lookup(dnsa, query, T_TXT) == DNS_SUCCEED)
+      for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
+           rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
         if (rr->type == T_TXT)
          {
          int len = (rr->data)[0];
         if (rr->type == T_TXT)
          {
          int len = (rr->data)[0];
@@ -3687,7 +3742,6 @@ int sep = 0;
 int defer_return = FAIL;
 const uschar *list = *listptr;
 uschar *domain;
 int defer_return = FAIL;
 const uschar *list = *listptr;
 uschar *domain;
-uschar buffer[1024];
 uschar revadd[128];        /* Long enough for IPv6 address */
 
 /* Indicate that the inverted IP address is not yet set up */
 uschar revadd[128];        /* Long enough for IPv6 address */
 
 /* Indicate that the inverted IP address is not yet set up */
@@ -3700,7 +3754,7 @@ dns_init(FALSE, FALSE, FALSE);    /*XXX dnssec? */
 
 /* Loop through all the domains supplied, until something matches */
 
 
 /* Loop through all the domains supplied, until something matches */
 
-while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
+while ((domain = string_nextinlist(&list, &sep, NULL, 0)))
   {
   int rc;
   BOOL bitmask = FALSE;
   {
   int rc;
   BOOL bitmask = FALSE;
@@ -3710,7 +3764,7 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
   uschar *iplist;
   uschar *key;
 
   uschar *iplist;
   uschar *key;
 
-  HDEBUG(D_dnsbl) debug_printf("DNS list check: %s\n", domain);
+  HDEBUG(D_dnsbl) debug_printf("dnslists check: %s\n", domain);
 
   /* Deal with special values that change the behaviour on defer */
 
 
   /* Deal with special values that change the behaviour on defer */
 
@@ -3764,8 +3818,7 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
   set domain_txt == domain. */
 
   domain_txt = domain;
   set domain_txt == domain. */
 
   domain_txt = domain;
-  comma = Ustrchr(domain, ',');
-  if (comma != NULL)
+  if ((comma = Ustrchr(domain, ',')))
     {
     *comma++ = 0;
     domain = comma;
     {
     *comma++ = 0;
     domain = comma;
@@ -3807,7 +3860,7 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
          acl_wherenames[where]);
       return ERROR;
       }
          acl_wherenames[where]);
       return ERROR;
       }
-    if (sender_host_address == NULL) return FAIL;    /* can never match */
+    if (!sender_host_address) return FAIL;    /* can never match */
     if (revadd[0] == 0) invert_address(revadd, sender_host_address);
     rc = one_check_dnsbl(domain, domain_txt, sender_host_address, revadd,
       iplist, bitmask, match_type, defer_return);
     if (revadd[0] == 0) invert_address(revadd, sender_host_address);
     rc = one_check_dnsbl(domain, domain_txt, sender_host_address, revadd,
       iplist, bitmask, match_type, defer_return);
@@ -3829,11 +3882,9 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
     int keysep = 0;
     BOOL defer = FALSE;
     uschar *keydomain;
     int keysep = 0;
     BOOL defer = FALSE;
     uschar *keydomain;
-    uschar keybuffer[256];
     uschar keyrevadd[128];
 
     uschar keyrevadd[128];
 
-    while ((keydomain = string_nextinlist(CUSS &key, &keysep, keybuffer,
-            sizeof(keybuffer))) != NULL)
+    while ((keydomain = string_nextinlist(CUSS &key, &keysep, NULL, 0)))
       {
       uschar *prepend = keydomain;
 
       {
       uschar *prepend = keydomain;
 
@@ -3845,7 +3896,6 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
 
       rc = one_check_dnsbl(domain, domain_txt, keydomain, prepend, iplist,
         bitmask, match_type, defer_return);
 
       rc = one_check_dnsbl(domain, domain_txt, keydomain, prepend, iplist,
         bitmask, match_type, defer_return);
-
       if (rc == OK)
         {
         dnslist_domain = string_copy(domain_txt);
       if (rc == OK)
         {
         dnslist_domain = string_copy(domain_txt);