Quota checking during reception. Bug 163
[exim.git] / src / src / verify.c
index c7c769a164746d22eb372e1bdca4c6d57519af19..3a40cea268d5d5a6f511af29ffe5d830263ac8a8 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
@@ -14,7 +15,7 @@ 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 */
 
 #define CUTTHROUGH_CMD_TIMEOUT  30     /* timeout for cutthrough-routing calls */
 #define CUTTHROUGH_DATA_TIMEOUT 60     /* timeout for cutthrough-routing calls */
-static smtp_outblock ctblock;
+static smtp_context ctctx;
 uschar ctbuffer[8192];
 
 
 uschar ctbuffer[8192];
 
 
@@ -70,7 +71,7 @@ dbdata_callout_cache *cache_record;
 
 if (!(cache_record = dbfn_read_with_length(dbm_file, key, &length)))
   {
 
 if (!(cache_record = dbfn_read_with_length(dbm_file, key, &length)))
   {
-  HDEBUG(D_verify) debug_printf("callout cache: no %s record found for %s\n", type, key);
+  HDEBUG(D_verify) debug_printf_indent("callout cache: no %s record found for %s\n", type, key);
   return NULL;
   }
 
   return NULL;
   }
 
@@ -84,7 +85,7 @@ now = time(NULL);
 
 if (now - cache_record->time_stamp > expire)
   {
 
 if (now - cache_record->time_stamp > expire)
   {
-  HDEBUG(D_verify) debug_printf("callout cache: %s record expired for %s\n", type, key);
+  HDEBUG(D_verify) debug_printf_indent("callout cache: %s record expired for %s\n", type, key);
   return NULL;
   }
 
   return NULL;
   }
 
@@ -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;
@@ -111,7 +112,7 @@ if (type[0] == 'd' && cache_record->result != ccache_reject)
     cache_record->random_result = ccache_unknown;
   }
 
     cache_record->random_result = ccache_unknown;
   }
 
-HDEBUG(D_verify) debug_printf("callout cache: found %s record for %s\n", type, key);
+HDEBUG(D_verify) debug_printf_indent("callout cache: found %s record for %s\n", type, key);
 return cache_record;
 }
 
 return cache_record;
 }
 
@@ -138,11 +139,11 @@ stage, unless caching has been disabled. */
 
 if (options & vopt_callout_no_cache)
   {
 
 if (options & vopt_callout_no_cache)
   {
-  HDEBUG(D_verify) debug_printf("callout cache: disabled by no_cache\n");
+  HDEBUG(D_verify) debug_printf_indent("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_indent("callout cache: not available\n");
   }
 else
   {
   }
 else
   {
@@ -172,9 +173,8 @@ else
     if (  cache_record->result == ccache_reject
        || *from_address == 0 && cache_record->result == ccache_reject_mfnull)
       {
     if (  cache_record->result == ccache_reject
        || *from_address == 0 && cache_record->result == ccache_reject_mfnull)
       {
-      setflag(addr, af_verify_nsfail);
       HDEBUG(D_verify)
       HDEBUG(D_verify)
-       debug_printf("callout cache: domain gave initial rejection, or "
+       debug_printf_indent("callout cache: domain gave initial rejection, or "
          "does not accept HELO or MAIL FROM:<>\n");
       setflag(addr, af_verify_nsfail);
       addr->user_message = US"(result of an earlier callout reused).";
          "does not accept HELO or MAIL FROM:<>\n");
       setflag(addr, af_verify_nsfail);
       addr->user_message = US"(result of an earlier callout reused).";
@@ -195,14 +195,14 @@ else
       {
       case ccache_accept:
        HDEBUG(D_verify)
       {
       case ccache_accept:
        HDEBUG(D_verify)
-         debug_printf("callout cache: domain accepts random addresses\n");
+         debug_printf_indent("callout cache: domain accepts random addresses\n");
        *failure_ptr = US"random";
        dbfn_close(dbm_file);
        return TRUE;     /* Default yield is OK */
 
       case ccache_reject:
        HDEBUG(D_verify)
        *failure_ptr = US"random";
        dbfn_close(dbm_file);
        return TRUE;     /* Default yield is OK */
 
       case ccache_reject:
        HDEBUG(D_verify)
-         debug_printf("callout cache: domain rejects random addresses\n");
+         debug_printf_indent("callout cache: domain rejects random addresses\n");
        *opt_ptr = options & ~vopt_callout_random;
        new_domain_record->random_result = ccache_reject;
        new_domain_record->random_stamp = cache_record->random_stamp;
        *opt_ptr = options & ~vopt_callout_random;
        new_domain_record->random_result = ccache_reject;
        new_domain_record->random_stamp = cache_record->random_stamp;
@@ -210,7 +210,7 @@ else
 
       default:
        HDEBUG(D_verify)
 
       default:
        HDEBUG(D_verify)
-         debug_printf("callout cache: need to check random address handling "
+         debug_printf_indent("callout cache: need to check random address handling "
            "(not cached or cache expired)\n");
        dbfn_close(dbm_file);
        return FALSE;
            "(not cached or cache expired)\n");
        dbfn_close(dbm_file);
        return FALSE;
@@ -227,7 +227,7 @@ else
        {
        setflag(addr, af_verify_pmfail);
        HDEBUG(D_verify)
        {
        setflag(addr, af_verify_pmfail);
        HDEBUG(D_verify)
-         debug_printf("callout cache: domain does not accept "
+         debug_printf_indent("callout cache: domain does not accept "
            "RCPT TO:<postmaster@domain>\n");
        *yield = FAIL;
        *failure_ptr = US"postmaster";
            "RCPT TO:<postmaster@domain>\n");
        *yield = FAIL;
        *failure_ptr = US"postmaster";
@@ -239,7 +239,7 @@ else
       if (cache_record->postmaster_result == ccache_unknown)
        {
        HDEBUG(D_verify)
       if (cache_record->postmaster_result == ccache_unknown)
        {
        HDEBUG(D_verify)
-         debug_printf("callout cache: need to check RCPT "
+         debug_printf_indent("callout cache: need to check RCPT "
            "TO:<postmaster@domain> (not cached or cache expired)\n");
        dbfn_close(dbm_file);
        return FALSE;
            "TO:<postmaster@domain> (not cached or cache expired)\n");
        dbfn_close(dbm_file);
        return FALSE;
@@ -250,7 +250,7 @@ else
       that the value in the cache record is preserved (with its old timestamp).
       */
 
       that the value in the cache record is preserved (with its old timestamp).
       */
 
-      HDEBUG(D_verify) debug_printf("callout cache: domain accepts RCPT "
+      HDEBUG(D_verify) debug_printf_indent("callout cache: domain accepts RCPT "
        "TO:<postmaster@domain>\n");
       *pm_ptr = NULL;
       new_domain_record->postmaster_result = ccache_accept;
        "TO:<postmaster@domain>\n");
       *pm_ptr = NULL;
       new_domain_record->postmaster_result = ccache_accept;
@@ -274,12 +274,12 @@ else
   if (cache_address_record->result == ccache_accept)
     {
     HDEBUG(D_verify)
   if (cache_address_record->result == ccache_accept)
     {
     HDEBUG(D_verify)
-      debug_printf("callout cache: address record is positive\n");
+      debug_printf_indent("callout cache: address record is positive\n");
     }
   else
     {
     HDEBUG(D_verify)
     }
   else
     {
     HDEBUG(D_verify)
-      debug_printf("callout cache: address record is negative\n");
+      debug_printf_indent("callout cache: address record is negative\n");
     addr->user_message = US"Previous (cached) callout verification failure";
     *failure_ptr = US"recipient";
     *yield = FAIL;
     addr->user_message = US"Previous (cached) callout verification failure";
     *failure_ptr = US"recipient";
     *yield = FAIL;
@@ -314,15 +314,15 @@ 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_indent("callout cache: not available\n");
     }
   else
     {
     (void)dbfn_write(dbm_file, domain, dom_rec,
       (int)sizeof(dbdata_callout_cache));
     }
   else
     {
     (void)dbfn_write(dbm_file, domain, dom_rec,
       (int)sizeof(dbdata_callout_cache));
-    HDEBUG(D_verify) debug_printf("wrote callout cache domain record for %s:\n"
+    HDEBUG(D_verify) debug_printf_indent("wrote callout cache domain record for %s:\n"
       "  result=%d postmaster=%d random=%d\n",
       domain,
       dom_rec->result,
       "  result=%d postmaster=%d random=%d\n",
       domain,
       dom_rec->result,
@@ -336,16 +336,16 @@ 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)
     {
   if (!dbm_file)
     {
-    HDEBUG(D_verify) debug_printf("no callout cache available\n");
+    HDEBUG(D_verify) debug_printf_indent("no callout cache available\n");
     }
   else
     {
     (void)dbfn_write(dbm_file, address_key, addr_rec,
       (int)sizeof(dbdata_callout_cache_address));
     }
   else
     {
     (void)dbfn_write(dbm_file, address_key, addr_rec,
       (int)sizeof(dbdata_callout_cache_address));
-    HDEBUG(D_verify) debug_printf("wrote %s callout cache address record for %s\n",
+    HDEBUG(D_verify) debug_printf_indent("wrote %s callout cache address record for %s\n",
       addr_rec->result == ccache_accept ? "positive" : "negative",
       address_key);
     }
       addr_rec->result == ccache_accept ? "positive" : "negative",
       address_key);
     }
@@ -371,10 +371,9 @@ cutthrough_multi(address_item * addr, host_item * host_list,
   transport_feedback * tf, int * yield)
 {
 BOOL done = FALSE;
   transport_feedback * tf, int * yield)
 {
 BOOL done = FALSE;
-host_item * host;
 
 if (addr->transport == cutthrough.addr.transport)
 
 if (addr->transport == cutthrough.addr.transport)
-  for (host = host_list; host; host = host->next)
+  for (host_item * host = host_list; host; host = host->next)
     if (Ustrcmp(host->address, cutthrough.host.address) == 0)
       {
       int host_af;
     if (Ustrcmp(host->address, cutthrough.host.address) == 0)
       {
       int host_af;
@@ -410,7 +409,7 @@ if (addr->transport == cutthrough.addr.transport)
 
        /* Match!  Send the RCPT TO, set done from the response */
        done =
 
        /* Match!  Send the RCPT TO, set done from the response */
        done =
-            smtp_write_command(&ctblock, SCMD_FLUSH, "RCPT TO:<%.1000s>\r\n",
+            smtp_write_command(&ctctx, SCMD_FLUSH, "RCPT TO:<%.1000s>\r\n",
              transport_rcpt_address(addr,
                 addr->transport->rcpt_include_affixes)) >= 0
          && cutthrough_response(&cutthrough.cctx, '2', &resp,
              transport_rcpt_address(addr,
                 addr->transport->rcpt_include_affixes)) >= 0
          && cutthrough_response(&cutthrough.cctx, '2', &resp,
@@ -422,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;
@@ -569,6 +568,7 @@ if (!addr->transport)
   {
   HDEBUG(D_verify) debug_printf("cannot callout via null transport\n");
   }
   {
   HDEBUG(D_verify) debug_printf("cannot callout via null transport\n");
   }
+
 else if (Ustrcmp(addr->transport->driver_name, "smtp") != 0)
   log_write(0, LOG_MAIN|LOG_PANIC|LOG_CONFIG_FOR, "callout transport '%s': %s is non-smtp",
     addr->transport->name, addr->transport->driver_name);
 else if (Ustrcmp(addr->transport->driver_name, "smtp") != 0)
   log_write(0, LOG_MAIN|LOG_PANIC|LOG_CONFIG_FOR, "callout transport '%s': %s is non-smtp",
     addr->transport->name, addr->transport->driver_name);
@@ -576,7 +576,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;
-  host_item * host;
+  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,
@@ -589,6 +589,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. */
 
@@ -602,7 +606,7 @@ else
   and cause the client to time out. So in this case we forgo the PIPELINING
   optimization. */
 
   and cause the client to time out. So in this case we forgo the PIPELINING
   optimization. */
 
-  if (smtp_out && !disable_callout_flush) mac_smtp_fflush();
+  if (smtp_out && !f.disable_callout_flush) mac_smtp_fflush();
 
   clearflag(addr, af_verify_pmfail);  /* postmaster callout flag */
   clearflag(addr, af_verify_nsfail);  /* null sender callout flag */
 
   clearflag(addr, af_verify_pmfail);  /* postmaster callout flag */
   clearflag(addr, af_verify_nsfail);  /* null sender callout flag */
@@ -624,12 +628,11 @@ coding means skipping this whole loop and doing the append separately.  */
   /* If we did not use a cached connection, make connections to the hosts
   and do real callouts. The list of hosts is passed in as an argument. */
 
   /* If we did not use a cached connection, make connections to the hosts
   and do real callouts. The list of hosts is passed in as an argument. */
 
-  for (host = host_list; host && !done; host = host->next)
+  for (host_item * host = host_list; host && !done; host = host->next)
     {
     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)
       {
@@ -669,14 +672,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.host = host;
-    sx.host_af = host_af,
-    sx.port = port;
-    sx.interface = interface;
-    sx.helo_data = tf->helo_data;
-    sx.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 */
@@ -689,19 +695,19 @@ 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
-       && verify_check_given_host(&ob->hosts_require_tls, host) != OK
+       && verify_check_given_host(CUSS &ob->hosts_require_tls, host) != OK
        )
       {
       log_write(0, LOG_MAIN,
        "%s: callout unencrypted to %s [%s] (not in hosts_require_tls)",
        addr->message, host->name, host->address);
       addr->transport_return = PENDING_DEFER;
        )
       {
       log_write(0, LOG_MAIN,
        "%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)
@@ -731,11 +737,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;
@@ -792,12 +798,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 */
@@ -808,37 +814,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.outblock, SCMD_FLUSH, "RSET\r\n") >= 0 &&
-             smtp_read_response(&sx.inblock, 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. */
@@ -847,10 +852,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;
@@ -861,10 +866,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 */
                    {
@@ -882,7 +887,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)
@@ -912,9 +917,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.outblock, SCMD_FLUSH, "RSET\r\n") >= 0
-          && smtp_read_response(&sx.inblock, 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)
        {
@@ -924,23 +928,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.outblock, SCMD_FLUSH,
+             && smtp_write_command(sx, SCMD_FLUSH,
                            "RCPT TO:<postmaster>\r\n") >= 0
                            "RCPT TO:<postmaster>\r\n") >= 0
-             && smtp_read_response(&sx.inblock, 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 */
 
@@ -948,7 +952,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);
@@ -973,7 +977,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
@@ -981,8 +985,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";
@@ -992,11 +995,11 @@ no_conn:
        break;
 #endif
       case ECONNREFUSED:
        break;
 #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
@@ -1005,16 +1008,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;
@@ -1061,7 +1084,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",
@@ -1071,7 +1094,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;
@@ -1086,44 +1109,44 @@ no_conn:
        cutthrough.host.address = string_copy(host->address);
        store_pool = oldpool;
        }
        cutthrough.host.address = string_copy(host->address);
        store_pool = oldpool;
        }
-      cutthrough.addr =                *addr;          /* Save the address_item for later logging */
+
+      /* Save the address_item and parent chain for later logging */
+      cutthrough.addr =                *addr;
       cutthrough.addr.next =   NULL;
       cutthrough.addr.host_used = &cutthrough.host;
       cutthrough.addr.next =   NULL;
       cutthrough.addr.host_used = &cutthrough.host;
-      if (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.cctx = &cutthrough.cctx;
+      for (address_item * caddr = &cutthrough.addr, * parent = addr->parent;
+          parent;
+          caddr = caddr->parent, parent = parent->parent)
+        *(caddr->parent = store_get(sizeof(address_item), FALSE)) = *parent;
+
+      ctctx.outblock.buffer = ctbuffer;
+      ctctx.outblock.buffersize = sizeof(ctbuffer);
+      ctctx.outblock.ptr = ctbuffer;
+      /* ctctx.outblock.cmd_count = 0; ctctx.outblock.authenticating = FALSE; */
+      ctctx.outblock.cctx = &cutthrough.cctx;
       }
     else
       {
       /* Ensure no cutthrough on multiple verifies that were incompatible */
       if (options & vopt_callout_recipsender)
         cancel_cutthrough_connection(TRUE, US"not usable for cutthrough");
       }
     else
       {
       /* 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.outblock, SCMD_FLUSH, "QUIT\r\n");
+      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);
 
 
-       /* Wait a short time for response, and discard it */
-       smtp_read_response(&sx.inblock, 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
@@ -1178,7 +1201,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;
 }
 
@@ -1188,7 +1211,7 @@ return yield;
    one was requested and a recipient-verify wasn't subsequently done.
 */
 int
    one was requested and a recipient-verify wasn't subsequently done.
 */
 int
-open_cutthrough_connection( address_item * addr )
+open_cutthrough_connection(address_item * addr)
 {
 address_item addr2;
 int rc;
 {
 address_item addr2;
 int rc;
@@ -1220,16 +1243,16 @@ if(cutthrough.cctx.sock < 0)
   return TRUE;
 
 if(
   return TRUE;
 
 if(
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
    cutthrough.is_tls
    cutthrough.is_tls
-   ? tls_write(cutthrough.cctx.tls_ctx, ctblock.buffer, n, FALSE)
+   ? tls_write(cutthrough.cctx.tls_ctx, ctctx.outblock.buffer, n, FALSE)
    :
 #endif
    :
 #endif
-     send(cutthrough.cctx.sock, ctblock.buffer, n, 0) > 0
+     send(cutthrough.cctx.sock, ctctx.outblock.buffer, n, 0) > 0
   )
 {
   transport_count += n;
   )
 {
   transport_count += n;
-  ctblock.ptr= ctblock.buffer;
+  ctctx.outblock.ptr= ctctx.outblock.buffer;
   return TRUE;
 }
 
   return TRUE;
 }
 
@@ -1244,11 +1267,11 @@ _cutthrough_puts(uschar * cp, int n)
 {
 while(n--)
  {
 {
 while(n--)
  {
- if(ctblock.ptr >= ctblock.buffer+ctblock.buffersize)
-   if(!cutthrough_send(ctblock.buffersize))
+ if(ctctx.outblock.ptr >= ctctx.outblock.buffer+ctctx.outblock.buffersize)
+   if(!cutthrough_send(ctctx.outblock.buffersize))
      return FALSE;
 
      return FALSE;
 
- *ctblock.ptr++ = *cp++;
+ *ctctx.outblock.ptr++ = *cp++;
  }
 return TRUE;
 }
  }
 return TRUE;
 }
@@ -1274,7 +1297,7 @@ return;
 static BOOL
 _cutthrough_flush_send(void)
 {
 static BOOL
 _cutthrough_flush_send(void)
 {
-int n = ctblock.ptr - ctblock.buffer;
+int n = ctctx.outblock.ptr - ctctx.outblock.buffer;
 
 if(n>0)
   if(!cutthrough_send(n))
 
 if(n>0)
   if(!cutthrough_send(n))
@@ -1311,16 +1334,16 @@ cutthrough_data_puts(US"\r\n", 2);
 static uschar
 cutthrough_response(client_conn_ctx * cctx, char expect, uschar ** copy, int timeout)
 {
 static uschar
 cutthrough_response(client_conn_ctx * cctx, char expect, uschar ** copy, int timeout)
 {
-smtp_inblock inblock;
+smtp_context sx = {0};
 uschar inbuffer[4096];
 uschar responsebuffer[4096];
 
 uschar inbuffer[4096];
 uschar responsebuffer[4096];
 
-inblock.buffer = inbuffer;
-inblock.buffersize = sizeof(inbuffer);
-inblock.ptr = inbuffer;
-inblock.ptrend = inbuffer;
-inblock.cctx = cctx;
-if(!smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), expect, timeout))
+sx.inblock.buffer = inbuffer;
+sx.inblock.buffersize = sizeof(inbuffer);
+sx.inblock.ptr = inbuffer;
+sx.inblock.ptrend = inbuffer;
+sx.inblock.cctx = cctx;
+if(!smtp_read_response(&sx, responsebuffer, sizeof(responsebuffer), expect, timeout))
   cancel_cutthrough_connection(TRUE, US"target timeout on read");
 
 if(copy)
   cancel_cutthrough_connection(TRUE, US"target timeout on read");
 
 if(copy)
@@ -1411,7 +1434,7 @@ if(fd >= 0)
      conn before the final dot.
   */
   client_conn_ctx tmp_ctx = cutthrough.cctx;
      conn before the final dot.
   */
   client_conn_ctx tmp_ctx = cutthrough.cctx;
-  ctblock.ptr = ctbuffer;
+  ctctx.outblock.ptr = ctbuffer;
   HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP>> QUIT\n");
   _cutthrough_puts(US"QUIT\r\n", 6);   /* avoid recursion */
   _cutthrough_flush_send();
   HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP>> QUIT\n");
   _cutthrough_puts(US"QUIT\r\n", 6);   /* avoid recursion */
   _cutthrough_flush_send();
@@ -1421,7 +1444,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);
@@ -1433,7 +1456,7 @@ if(fd >= 0)
   (void)close(fd);
   HDEBUG(D_acl) debug_printf_indent("----------- cutthrough shutdown (%s) ------------\n", why);
   }
   (void)close(fd);
   HDEBUG(D_acl) debug_printf_indent("----------- cutthrough shutdown (%s) ------------\n", why);
   }
-ctblock.ptr = ctbuffer;
+ctctx.outblock.ptr = ctbuffer;
 }
 
 void
 }
 
 void
@@ -1467,7 +1490,6 @@ uschar *
 cutthrough_finaldot(void)
 {
 uschar res;
 cutthrough_finaldot(void)
 {
 uschar res;
-address_item * addr;
 HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP>> .\n");
 
 /* Assume data finshed with new-line */
 HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP>> .\n");
 
 /* Assume data finshed with new-line */
@@ -1479,7 +1501,7 @@ if(  !cutthrough_puts(US".", 1)
 
 res = cutthrough_response(&cutthrough.cctx, '2', &cutthrough.addr.message,
        CUTTHROUGH_DATA_TIMEOUT);
 
 res = cutthrough_response(&cutthrough.cctx, '2', &cutthrough.addr.message,
        CUTTHROUGH_DATA_TIMEOUT);
-for (addr = &cutthrough.addr; addr; addr = addr->next)
+for (address_item * addr = &cutthrough.addr; addr; addr = addr->next)
   {
   addr->message = cutthrough.addr.message;
   switch(res)
   {
   addr->message = cutthrough.addr.message;
   switch(res)
@@ -1535,6 +1557,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;
@@ -1589,7 +1613,7 @@ address testing (-bt), which is indicated by address_test_mode being set.
 Arguments:
   vaddr            contains the address to verify; the next field in this block
                      must be NULL
 Arguments:
   vaddr            contains the address to verify; the next field in this block
                      must be NULL
-  f                if not NULL, write the result to this file
+  fp               if not NULL, write the result to this file
   options          various option bits:
                      vopt_fake_sender => this sender verify is not for the real
                        sender (it was verify=sender=xxxx or an address from a
   options          various option bits:
                      vopt_fake_sender => this sender verify is not for the real
                        sender (it was verify=sender=xxxx or an address from a
@@ -1630,19 +1654,19 @@ Returns:           OK      address verified
 */
 
 int
 */
 
 int
-verify_address(address_item *vaddr, FILE *f, int options, int callout,
-  int callout_overall, int callout_connect, uschar *se_mailfrom,
+verify_address(address_item * vaddr, FILE * fp, int options, int callout,
+  int callout_overall, int callout_connect, uschar * se_mailfrom,
   uschar *pm_mailfrom, BOOL *routed)
 {
 BOOL allok = TRUE;
   uschar *pm_mailfrom, BOOL *routed)
 {
 BOOL allok = TRUE;
-BOOL full_info = (f == NULL)? FALSE : (debug_selector != 0);
+BOOL full_info = fp ? debug_selector != 0 : FALSE;
 BOOL expn         = (options & vopt_expn) != 0;
 BOOL success_on_redirect = (options & vopt_success_on_redirect) != 0;
 int i;
 int yield = OK;
 BOOL expn         = (options & vopt_expn) != 0;
 BOOL success_on_redirect = (options & vopt_success_on_redirect) != 0;
 int i;
 int yield = OK;
-int verify_type = expn? v_expn :
-     address_test_mode? v_none :
-          options & vopt_is_recipient? v_recipient : v_sender;
+int verify_type = expn ? v_expn :
+   f.address_test_mode ? v_none :
+          options & vopt_is_recipient ? v_recipient : v_sender;
 address_item *addr_list;
 address_item *addr_new = NULL;
 address_item *addr_remote = NULL;
 address_item *addr_list;
 address_item *addr_new = NULL;
 address_item *addr_remote = NULL;
@@ -1676,8 +1700,8 @@ if (parse_find_at(address) == NULL)
   {
   if (!(options & vopt_qualify))
     {
   {
   if (!(options & vopt_qualify))
     {
-    if (f)
-      respond_printf(f, "%sA domain is required for \"%s\"%s\n",
+    if (fp)
+      respond_printf(fp, "%sA domain is required for \"%s\"%s\n",
         ko_prefix, address, cr);
     *failure_ptr = US"qualify";
     return FAIL;
         ko_prefix, address, cr);
     *failure_ptr = US"qualify";
     return FAIL;
@@ -1688,7 +1712,7 @@ if (parse_find_at(address) == NULL)
 DEBUG(D_verify)
   {
   debug_printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
 DEBUG(D_verify)
   {
   debug_printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
-  debug_printf("%s %s\n", address_test_mode? "Testing" : "Verifying", address);
+  debug_printf("%s %s\n", f.address_test_mode? "Testing" : "Verifying", address);
   }
 
 /* Rewrite and report on it. Clear the domain and local part caches - these
   }
 
 /* Rewrite and report on it. Clear the domain and local part caches - these
@@ -1701,9 +1725,9 @@ if (global_rewrite_rules)
     global_rewrite_rules, rewrite_existflags);
   if (address != old)
     {
     global_rewrite_rules, rewrite_existflags);
   if (address != old)
     {
-    for (i = 0; i < (MAX_NAMED_LIST * 2)/32; i++) vaddr->localpart_cache[i] = 0;
-    for (i = 0; i < (MAX_NAMED_LIST * 2)/32; i++) vaddr->domain_cache[i] = 0;
-    if (f && !expn) fprintf(f, "Address rewritten as: %s\n", address);
+    for (int i = 0; i < (MAX_NAMED_LIST * 2)/32; i++) vaddr->localpart_cache[i] = 0;
+    for (int i = 0; i < (MAX_NAMED_LIST * 2)/32; i++) vaddr->domain_cache[i] = 0;
+    if (fp && !expn) fprintf(fp, "Address rewritten as: %s\n", address);
     }
   }
 
     }
   }
 
@@ -1769,29 +1793,29 @@ while (addr_new)
   if (testflag(addr, af_pfr))
     {
     allok = FALSE;
   if (testflag(addr, af_pfr))
     {
     allok = FALSE;
-    if (f)
+    if (fp)
       {
       BOOL allow;
 
       if (addr->address[0] == '>')
         {
         allow = testflag(addr, af_allow_reply);
       {
       BOOL allow;
 
       if (addr->address[0] == '>')
         {
         allow = testflag(addr, af_allow_reply);
-        fprintf(f, "%s -> mail %s", addr->parent->address, addr->address + 1);
+        fprintf(fp, "%s -> mail %s", addr->parent->address, addr->address + 1);
         }
       else
         {
         allow = addr->address[0] == '|'
           ? testflag(addr, af_allow_pipe) : testflag(addr, af_allow_file);
         }
       else
         {
         allow = addr->address[0] == '|'
           ? testflag(addr, af_allow_pipe) : testflag(addr, af_allow_file);
-        fprintf(f, "%s -> %s", addr->parent->address, addr->address);
+        fprintf(fp, "%s -> %s", addr->parent->address, addr->address);
         }
 
       if (addr->basic_errno == ERRNO_BADTRANSPORT)
         }
 
       if (addr->basic_errno == ERRNO_BADTRANSPORT)
-        fprintf(f, "\n*** Error in setting up pipe, file, or autoreply:\n"
+        fprintf(fp, "\n*** Error in setting up pipe, file, or autoreply:\n"
           "%s\n", addr->message);
       else if (allow)
           "%s\n", addr->message);
       else if (allow)
-        fprintf(f, "\n  transport = %s\n", addr->transport->name);
+        fprintf(fp, "\n  transport = %s\n", addr->transport->name);
       else
       else
-        fprintf(f, " *** forbidden ***\n");
+        fprintf(fp, " *** forbidden ***\n");
       }
     continue;
     }
       }
     continue;
     }
@@ -1823,6 +1847,8 @@ while (addr_new)
 
   if (rc == OK)
     {
 
   if (rc == OK)
     {
+    BOOL local_verify = FALSE;
+
     if (routed) *routed = TRUE;
     if (callout > 0)
       {
     if (routed) *routed = TRUE;
     if (callout > 0)
       {
@@ -1849,73 +1875,76 @@ while (addr_new)
       transport's options, so as to mimic what would happen if we were really
       sending a message to this address. */
 
       transport's options, so as to mimic what would happen if we were really
       sending a message to this address. */
 
-      if ((tp = addr->transport) && !tp->info->local)
-        {
-        (void)(tp->setup)(tp, addr, &tf, 0, 0, NULL);
+      if ((tp = addr->transport))
+       if (!tp->info->local)
+         {
+         (void)(tp->setup)(tp, addr, &tf, 0, 0, NULL);
 
 
-        /* If the transport has hosts and the router does not, or if the
-        transport is configured to override the router's hosts, we must build a
-        host list of the transport's hosts, and find the IP addresses */
+         /* If the transport has hosts and the router does not, or if the
+         transport is configured to override the router's hosts, we must build a
+         host list of the transport's hosts, and find the IP addresses */
 
 
-        if (tf.hosts && (!host_list || tf.hosts_override))
-          {
-          uschar *s;
-          const uschar *save_deliver_domain = deliver_domain;
-          uschar *save_deliver_localpart = deliver_localpart;
-
-          host_list = NULL;    /* Ignore the router's hosts */
-
-          deliver_domain = addr->domain;
-          deliver_localpart = addr->local_part;
-          s = expand_string(tf.hosts);
-          deliver_domain = save_deliver_domain;
-          deliver_localpart = save_deliver_localpart;
-
-          if (!s)
-            {
-            log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand list of hosts "
-              "\"%s\" in %s transport for callout: %s", tf.hosts,
-              tp->name, expand_string_message);
-            }
-          else
-            {
-            int flags;
-            host_item *host, *nexthost;
-            host_build_hostlist(&host_list, s, tf.hosts_randomize);
-
-            /* Just ignore failures to find a host address. If we don't manage
-            to find any addresses, the callout will defer. Note that more than
-            one address may be found for a single host, which will result in
-            additional host items being inserted into the chain. Hence we must
-            save the next host first. */
-
-            flags = HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
-            if (tf.qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE;
-            if (tf.search_parents) flags |= HOST_FIND_SEARCH_PARENTS;
-
-            for (host = host_list; host; host = nexthost)
-              {
-              nexthost = host->next;
-              if (tf.gethostbyname ||
-                  string_is_ip_address(host->name, NULL) != 0)
-                (void)host_find_byname(host, NULL, flags, NULL, TRUE);
-              else
+         if (tf.hosts && (!host_list || tf.hosts_override))
+           {
+           uschar *s;
+           const uschar *save_deliver_domain = deliver_domain;
+           uschar *save_deliver_localpart = deliver_localpart;
+
+           host_list = NULL;    /* Ignore the router's hosts */
+
+           deliver_domain = addr->domain;
+           deliver_localpart = addr->local_part;
+           s = expand_string(tf.hosts);
+           deliver_domain = save_deliver_domain;
+           deliver_localpart = save_deliver_localpart;
+
+           if (!s)
+             {
+             log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand list of hosts "
+               "\"%s\" in %s transport for callout: %s", tf.hosts,
+               tp->name, expand_string_message);
+             }
+           else
+             {
+             int flags;
+             host_build_hostlist(&host_list, s, tf.hosts_randomize);
+
+             /* Just ignore failures to find a host address. If we don't manage
+             to find any addresses, the callout will defer. Note that more than
+             one address may be found for a single host, which will result in
+             additional host items being inserted into the chain. Hence we must
+             save the next host first. */
+
+             flags = HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
+             if (tf.qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE;
+             if (tf.search_parents) flags |= HOST_FIND_SEARCH_PARENTS;
+
+             for (host_item * host = host_list, * nexthost; host; host = nexthost)
                {
                {
-               dnssec_domains * dnssec_domains = NULL;
-               if (Ustrcmp(tp->driver_name, "smtp") == 0)
+               nexthost = host->next;
+               if (tf.gethostbyname ||
+                   string_is_ip_address(host->name, NULL) != 0)
+                 (void)host_find_byname(host, NULL, flags, NULL, TRUE);
+               else
                  {
                  {
-                 smtp_transport_options_block * ob =
-                     (smtp_transport_options_block *) tp->options_block;
-                 dnssec_domains = &ob->dnssec;
-                 }
+                 const dnssec_domains * dsp = NULL;
+                 if (Ustrcmp(tp->driver_name, "smtp") == 0)
+                   {
+                   smtp_transport_options_block * ob =
+                       (smtp_transport_options_block *) tp->options_block;
+                   dsp = &ob->dnssec;
+                   }
 
 
-                (void) host_find_bydns(host, NULL, flags, NULL, NULL, NULL,
-                 dnssec_domains, NULL, NULL);
+                 (void) host_find_bydns(host, NULL, flags, NULL, NULL, NULL,
+                   dsp, NULL, NULL);
+                 }
                }
                }
-              }
-            }
-          }
-        }
+             }
+           }
+         }
+       else if (  options & vopt_quota
+               && Ustrcmp(tp->driver_name, "appendfile") == 0)
+         local_verify = TRUE;
 
       /* Can only do a callout if we have at least one host! If the callout
       fails, it will have set ${sender,recipient}_verify_failure. */
 
       /* Can only do a callout if we have at least one host! If the callout
       fails, it will have set ${sender,recipient}_verify_failure. */
@@ -1923,7 +1952,7 @@ while (addr_new)
       if (host_list)
         {
         HDEBUG(D_verify) debug_printf("Attempting full verification using callout\n");
       if (host_list)
         {
         HDEBUG(D_verify) debug_printf("Attempting full verification using callout\n");
-        if (host_checking && !host_checking_callout)
+        if (host_checking && !f.host_checking_callout)
           {
           HDEBUG(D_verify)
             debug_printf("... callout omitted by default when host testing\n"
           {
           HDEBUG(D_verify)
             debug_printf("... callout omitted by default when host testing\n"
@@ -1931,18 +1960,27 @@ 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);
+#ifndef DISABLE_TLS
+         deliver_set_expansions(NULL);
+#endif
           }
         }
           }
         }
+      else if (local_verify)
+       {
+        HDEBUG(D_verify) debug_printf("Attempting quota verification\n");
+
+       deliver_set_expansions(addr);
+       deliver_local(addr, TRUE);
+       rc = addr->transport_return;
+       }
       else
       else
-        {
         HDEBUG(D_verify) debug_printf("Cannot do callout: neither router nor "
         HDEBUG(D_verify) debug_printf("Cannot do callout: neither router nor "
-          "transport provided a host list\n");
-        }
+          "transport provided a host list, or transport is not smtp\n");
       }
     }
 
       }
     }
 
@@ -1961,29 +1999,29 @@ while (addr_new)
   if (rc == FAIL)
     {
     allok = FALSE;
   if (rc == FAIL)
     {
     allok = FALSE;
-    if (f)
+    if (fp)
       {
       address_item *p = addr->parent;
 
       {
       address_item *p = addr->parent;
 
-      respond_printf(f, "%s%s %s", ko_prefix,
+      respond_printf(fp, "%s%s %s", ko_prefix,
         full_info ? addr->address : address,
         full_info ? addr->address : address,
-        address_test_mode ? "is undeliverable" : "failed to verify");
-      if (!expn && admin_user)
+        f.address_test_mode ? "is undeliverable" : "failed to verify");
+      if (!expn && f.admin_user)
         {
         if (addr->basic_errno > 0)
         {
         if (addr->basic_errno > 0)
-          respond_printf(f, ": %s", strerror(addr->basic_errno));
+          respond_printf(fp, ": %s", strerror(addr->basic_errno));
         if (addr->message)
         if (addr->message)
-          respond_printf(f, ": %s", addr->message);
+          respond_printf(fp, ": %s", addr->message);
         }
 
       /* Show parents iff doing full info */
 
       if (full_info) while (p)
         {
         }
 
       /* Show parents iff doing full info */
 
       if (full_info) while (p)
         {
-        respond_printf(f, "%s\n    <-- %s", cr, p->address);
+        respond_printf(fp, "%s\n    <-- %s", cr, p->address);
         p = p->parent;
         }
         p = p->parent;
         }
-      respond_printf(f, "%s\n", cr);
+      respond_printf(fp, "%s\n", cr);
       }
     cancel_cutthrough_connection(TRUE, US"routing hard fail");
 
       }
     cancel_cutthrough_connection(TRUE, US"routing hard fail");
 
@@ -2000,29 +2038,29 @@ while (addr_new)
   else if (rc == DEFER)
     {
     allok = FALSE;
   else if (rc == DEFER)
     {
     allok = FALSE;
-    if (f)
+    if (fp)
       {
       address_item *p = addr->parent;
       {
       address_item *p = addr->parent;
-      respond_printf(f, "%s%s cannot be resolved at this time", ko_prefix,
+      respond_printf(fp, "%s%s cannot be resolved at this time", ko_prefix,
         full_info? addr->address : address);
         full_info? addr->address : address);
-      if (!expn && admin_user)
+      if (!expn && f.admin_user)
         {
         if (addr->basic_errno > 0)
         {
         if (addr->basic_errno > 0)
-          respond_printf(f, ": %s", strerror(addr->basic_errno));
+          respond_printf(fp, ": %s", strerror(addr->basic_errno));
         if (addr->message)
         if (addr->message)
-          respond_printf(f, ": %s", addr->message);
+          respond_printf(fp, ": %s", addr->message);
         else if (addr->basic_errno <= 0)
         else if (addr->basic_errno <= 0)
-          respond_printf(f, ": unknown error");
+          respond_printf(fp, ": unknown error");
         }
 
       /* Show parents iff doing full info */
 
       if (full_info) while (p)
         {
         }
 
       /* Show parents iff doing full info */
 
       if (full_info) while (p)
         {
-        respond_printf(f, "%s\n    <-- %s", cr, p->address);
+        respond_printf(fp, "%s\n    <-- %s", cr, p->address);
         p = p->parent;
         }
         p = p->parent;
         }
-      respond_printf(f, "%s\n", cr);
+      respond_printf(fp, "%s\n", cr);
       }
     cancel_cutthrough_connection(TRUE, US"routing soft fail");
 
       }
     cancel_cutthrough_connection(TRUE, US"routing soft fail");
 
@@ -2043,16 +2081,16 @@ while (addr_new)
 
     if (!addr_new)
       if (!addr_local && !addr_remote)
 
     if (!addr_new)
       if (!addr_local && !addr_remote)
-        respond_printf(f, "250 mail to <%s> is discarded\r\n", address);
+        respond_printf(fp, "250 mail to <%s> is discarded\r\n", address);
       else
       else
-        respond_printf(f, "250 <%s>\r\n", address);
+        respond_printf(fp, "250 <%s>\r\n", address);
 
     else do
       {
       address_item *addr2 = addr_new;
       addr_new = addr2->next;
       if (!addr_new) ok_prefix = US"250 ";
 
     else do
       {
       address_item *addr2 = addr_new;
       addr_new = addr2->next;
       if (!addr_new) ok_prefix = US"250 ";
-      respond_printf(f, "%s<%s>\r\n", ok_prefix, addr2->address);
+      respond_printf(fp, "%s<%s>\r\n", ok_prefix, addr2->address);
       } while (addr_new);
     yield = OK;
     goto out;
       } while (addr_new);
     yield = OK;
     goto out;
@@ -2086,13 +2124,15 @@ while (addr_new)
          )  )
        )
       {
          )  )
        )
       {
-      if (f) fprintf(f, "%s %s\n",
-        address, address_test_mode ? "is deliverable" : "verified");
+      if (fp) fprintf(fp, "%s %s\n",
+        address, f.address_test_mode ? "is deliverable" : "verified");
 
       /* If we have carried on to verify a child address, we want the value
       of $address_data to be that of the child */
 
       vaddr->prop.address_data = addr->prop.address_data;
 
       /* If we have carried on to verify a child address, we want the value
       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 */
 
@@ -2106,7 +2146,7 @@ while (addr_new)
   }     /* Loop for generated addresses */
 
 /* Display the full results of the successful routing, including any generated
   }     /* Loop for generated addresses */
 
 /* Display the full results of the successful routing, including any generated
-addresses. Control gets here only when full_info is set, which requires f not
+addresses. Control gets here only when full_info is set, which requires fp not
 to be NULL, and this occurs only when a top-level verify is called with the
 debugging switch on.
 
 to be NULL, and this occurs only when a top-level verify is called with the
 debugging switch on.
 
@@ -2116,7 +2156,7 @@ discarded, usually because of the use of :blackhole: in an alias file. */
 
 if (allok && !addr_local && !addr_remote)
   {
 
 if (allok && !addr_local && !addr_remote)
   {
-  fprintf(f, "mail to %s is discarded\n", address);
+  fprintf(fp, "mail to %s is discarded\n", address);
   goto out;
   }
 
   goto out;
   }
 
@@ -2124,15 +2164,14 @@ for (addr_list = addr_local, i = 0; i < 2; addr_list = addr_remote, i++)
   while (addr_list)
     {
     address_item *addr = addr_list;
   while (addr_list)
     {
     address_item *addr = addr_list;
-    address_item *p = addr->parent;
     transport_instance * tp = addr->transport;
 
     addr_list = addr->next;
 
     transport_instance * tp = addr->transport;
 
     addr_list = addr->next;
 
-    fprintf(f, "%s", CS addr->address);
-#ifdef EXPERIMENTAL_SRS
+    fprintf(fp, "%s", CS addr->address);
+#ifdef EXPERIMENTAL_SRS_ALT
     if(addr->prop.srs_sender)
     if(addr->prop.srs_sender)
-      fprintf(f, "    [srs = %s]", addr->prop.srs_sender);
+      fprintf(fp, "    [srs = %s]", addr->prop.srs_sender);
 #endif
 
     /* If the address is a duplicate, show something about it. */
 #endif
 
     /* If the address is a duplicate, show something about it. */
@@ -2141,19 +2180,19 @@ for (addr_list = addr_local, i = 0; i < 2; addr_list = addr_remote, i++)
       {
       tree_node *tnode;
       if ((tnode = tree_search(tree_duplicates, addr->unique)))
       {
       tree_node *tnode;
       if ((tnode = tree_search(tree_duplicates, addr->unique)))
-        fprintf(f, "   [duplicate, would not be delivered]");
+        fprintf(fp, "   [duplicate, would not be delivered]");
       else tree_add_duplicate(addr->unique, addr);
       }
 
     /* Now show its parents */
 
       else tree_add_duplicate(addr->unique, addr);
       }
 
     /* Now show its parents */
 
-    for (p = addr->parent; p; p = p->parent)
-      fprintf(f, "\n    <-- %s", p->address);
-    fprintf(f, "\n  ");
+    for (address_item * p = addr->parent; p; p = p->parent)
+      fprintf(fp, "\n    <-- %s", p->address);
+    fprintf(fp, "\n  ");
 
     /* Show router, and transport */
 
 
     /* Show router, and transport */
 
-    fprintf(f, "router = %s, transport = %s\n",
+    fprintf(fp, "router = %s, transport = %s\n",
       addr->router->name, tp ? tp->name : US"unset");
 
     /* Show any hosts that are set up by a router unless the transport
       addr->router->name, tp ? tp->name : US"unset");
 
     /* Show any hosts that are set up by a router unless the transport
@@ -2161,32 +2200,31 @@ for (addr_list = addr_local, i = 0; i < 2; addr_list = addr_remote, i++)
 
     if (addr->host_list && tp && !tp->overrides_hosts)
       {
 
     if (addr->host_list && tp && !tp->overrides_hosts)
       {
-      host_item *h;
       int maxlen = 0;
       int maxaddlen = 0;
       int maxlen = 0;
       int maxaddlen = 0;
-      for (h = addr->host_list; h; h = h->next)
+      for (host_item * h = addr->host_list; h; h = h->next)
         {                              /* get max lengths of host names, addrs */
         int len = Ustrlen(h->name);
         if (len > maxlen) maxlen = len;
         len = h->address ? Ustrlen(h->address) : 7;
         if (len > maxaddlen) maxaddlen = len;
         }
         {                              /* get max lengths of host names, addrs */
         int len = Ustrlen(h->name);
         if (len > maxlen) maxlen = len;
         len = h->address ? Ustrlen(h->address) : 7;
         if (len > maxaddlen) maxaddlen = len;
         }
-      for (h = addr->host_list; h; h = h->next)
+      for (host_item * h = addr->host_list; h; h = h->next)
        {
        {
-       fprintf(f, "  host %-*s ", maxlen, h->name);
+       fprintf(fp, "  host %-*s ", maxlen, h->name);
 
        if (h->address)
 
        if (h->address)
-         fprintf(f, "[%s%-*c", h->address, maxaddlen+1 - Ustrlen(h->address), ']');
+         fprintf(fp, "[%s%-*c", h->address, maxaddlen+1 - Ustrlen(h->address), ']');
        else if (tp->info->local)
        else if (tp->info->local)
-         fprintf(f, " %-*s ", maxaddlen, "");  /* Omit [unknown] for local */
+         fprintf(fp, " %-*s ", maxaddlen, "");  /* Omit [unknown] for local */
        else
        else
-         fprintf(f, "[%s%-*c", "unknown", maxaddlen+1 - 7, ']');
+         fprintf(fp, "[%s%-*c", "unknown", maxaddlen+1 - 7, ']');
 
 
-        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  &&  h->dnssec == DS_YES) fputs(" AD", f);
-        if (h->status == hstatus_unusable) fputs(" ** unusable **", f);
-       fputc('\n', f);
+        if (h->mx >= 0) fprintf(fp, " MX=%d", h->mx);
+        if (h->port != PORT_NONE) fprintf(fp, " port=%d", h->port);
+        if (f.running_in_test_harness  &&  h->dnssec == DS_YES) fputs(" AD", fp);
+        if (h->status == hstatus_unusable) fputs(" ** unusable **", fp);
+       fputc('\n', fp);
         }
       }
     }
         }
       }
     }
@@ -2196,7 +2234,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;
 }
@@ -2221,11 +2259,10 @@ Returns:     OK
 int
 verify_check_headers(uschar **msgptr)
 {
 int
 verify_check_headers(uschar **msgptr)
 {
-header_line *h;
 uschar *colon, *s;
 int yield = OK;
 
 uschar *colon, *s;
 int yield = OK;
 
-for (h = header_list; h && yield == OK; h = h->next)
+for (header_line * h = header_list; h && yield == OK; h = h->next)
   {
   if (h->type != htype_from &&
       h->type != htype_reply_to &&
   {
   if (h->type != htype_from &&
       h->type != htype_reply_to &&
@@ -2237,12 +2274,12 @@ for (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. */
 
-  parse_allow_group = TRUE;
+  f.parse_allow_group = TRUE;
 
   while (*s)
     {
 
   while (*s)
     {
@@ -2265,13 +2302,13 @@ for (h = header_list; h && yield == OK; h = h->next)
       {
       if (h->type == htype_from || h->type == htype_sender)
         {
       {
       if (h->type == htype_from || h->type == htype_sender)
         {
-        if (!allow_unqualified_sender) recipient = NULL;
+        if (!f.allow_unqualified_sender) recipient = NULL;
         }
       else
         {
         }
       else
         {
-        if (!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
@@ -2316,11 +2353,11 @@ for (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 */
 
     }   /* Next address */
 
-  parse_allow_group = FALSE;
-  parse_found_group = FALSE;
+  f.parse_allow_group = FALSE;
+  f.parse_found_group = FALSE;
   }     /* Next header unless yield has been set FALSE */
 
 return yield;
   }     /* Next header unless yield has been set FALSE */
 
 return yield;
@@ -2344,17 +2381,16 @@ Returns:     OK
 int
 verify_check_header_names_ascii(uschar **msgptr)
 {
 int
 verify_check_header_names_ascii(uschar **msgptr)
 {
-header_line *h;
-uschar *colon, *s;
+uschar *colon;
 
 
-for (h = header_list; h; h = h->next)
+for (header_line * h = header_list; h; h = h->next)
   {
   colon = Ustrchr(h->text, ':');
   {
   colon = Ustrchr(h->text, ':');
-  for(s = h->text; s < colon; s++)
+  for(uschar * s = h->text; s < colon; s++)
     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;
       }
   }
@@ -2374,22 +2410,20 @@ 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)
 {
 {
-int i;
-for (i = 0; i < recipients_count; i++)
+for (int i = 0; i < recipients_count; i++)
   {
   {
-  header_line *h;
   BOOL found = FALSE;
   uschar *address = recipients_list[i].address;
 
   BOOL found = FALSE;
   uschar *address = recipients_list[i].address;
 
-  for (h = header_list; !found && h != NULL; h = h->next)
+  for (header_line * h = header_list; !found && h; h = h->next)
     {
     uschar *colon, *s;
 
     {
     uschar *colon, *s;
 
@@ -2397,17 +2431,17 @@ for (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. */
 
-    parse_allow_group = TRUE;
+    f.parse_allow_group = TRUE;
 
 
-    while (*s != 0)
+    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,26 +2453,27 @@ for (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 */
 
       }   /* Next address */
 
-    parse_allow_group = FALSE;
-    parse_found_group = FALSE;
+    f.parse_allow_group = FALSE;
+    f.parse_found_group = FALSE;
     }     /* Next header (if found is false) */
 
   if (!found) return FAIL;
     }     /* Next header (if found is false) */
 
   if (!found) return FAIL;
@@ -2466,10 +2501,9 @@ Returns:     pointer to an address item, or NULL
 address_item *
 verify_checked_sender(uschar *sender)
 {
 address_item *
 verify_checked_sender(uschar *sender)
 {
-address_item *addr;
-for (addr = sender_verified_list; addr != NULL; addr = addr->next)
-  if (Ustrcmp(sender, addr->address) == 0) break;
-return addr;
+for (address_item * addr = sender_verified_list; addr; addr = addr->next)
+  if (Ustrcmp(sender, addr->address) == 0) return addr;
+return NULL;
 }
 
 
 }
 
 
@@ -2523,12 +2557,9 @@ verify_check_header_address(uschar **user_msgptr, uschar **log_msgptr,
 static int header_types[] = { htype_sender, htype_reply_to, htype_from };
 BOOL done = FALSE;
 int yield = FAIL;
 static int header_types[] = { htype_sender, htype_reply_to, htype_from };
 BOOL done = FALSE;
 int yield = FAIL;
-int i;
 
 
-for (i = 0; i < 3 && !done; i++)
-  {
-  header_line *h;
-  for (h = header_list; h != NULL && !done; h = h->next)
+for (int i = 0; i < 3 && !done; i++)
+  for (header_line * h = header_list; h != NULL && !done; h = h->next)
     {
     int terminator, new_ok;
     uschar *s, *ss, *endname;
     {
     int terminator, new_ok;
     uschar *s, *ss, *endname;
@@ -2539,7 +2570,7 @@ for (i = 0; i < 3 && !done; i++)
     /* Scan the addresses in the header, enabling group syntax. Note that we
     have to reset this after the header has been scanned. */
 
     /* Scan the addresses in the header, enabling group syntax. Note that we
     have to reset this after the header has been scanned. */
 
-    parse_allow_group = TRUE;
+    f.parse_allow_group = TRUE;
 
     while (*s != 0)
       {
 
     while (*s != 0)
       {
@@ -2591,7 +2622,7 @@ for (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;
@@ -2602,7 +2633,7 @@ for (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--;
@@ -2657,10 +2688,10 @@ for (i = 0; i < 3 && !done; i++)
       s = ss;
       }     /* Next address */
 
       s = ss;
       }     /* Next address */
 
-    parse_allow_group = FALSE;
-    parse_found_group = FALSE;
+    f.parse_allow_group = FALSE;
+    f.parse_found_group = FALSE;
     }       /* Next header, unless done */
     }       /* Next header, unless done */
-  }         /* Next header type unless done */
+           /* Next header type unless done */
 
 if (yield == FAIL && *log_msgptr == NULL)
   *log_msgptr = US"there is no valid sender in any header line";
 
 if (yield == FAIL && *log_msgptr == NULL)
   *log_msgptr = US"there is no valid sender in any header line";
@@ -2733,6 +2764,7 @@ qlen = snprintf(CS buffer, sizeof(buffer), "%d , %d\r\n",
 early_data.data = buffer;
 early_data.len = qlen;
 
 early_data.data = buffer;
 early_data.len = qlen;
 
+/*XXX we trust that the query is idempotent */
 if (ip_connect(ident_conn_ctx.sock, host_af, sender_host_address, port,
                rfc1413_query_timeout, &early_data) < 0)
   {
 if (ip_connect(ident_conn_ctx.sock, host_af, sender_host_address, port,
                rfc1413_query_timeout, &early_data) < 0)
   {
@@ -2757,7 +2789,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
@@ -2872,7 +2904,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 "*". */
@@ -2891,7 +2923,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;
@@ -2899,12 +2930,10 @@ if (*ss == '@')
     }
   else if (Ustrcmp(ss, "@[]") == 0)
     {
     }
   else if (Ustrcmp(ss, "@[]") == 0)
     {
-    ip_address_item *ip;
-    for (ip = host_find_interfaces(); ip != NULL; ip = ip->next)
+    for (ip_address_item * ip = host_find_interfaces(); ip; ip = ip->next)
       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. */
@@ -2923,24 +2952,33 @@ only by digits and dots (a slash at the start indicates a file name and of
 course slashes may be present in lookups, but not preceded only by digits and
 dots). */
 
 course slashes may be present in lookups, but not preceded only by digits and
 dots). */
 
-for (t = ss; isdigit(*t) || *t == '.'; t++);
+for (t = ss; isdigit(*t) || *t == '.'; ) t++;
 if (*t == 0 || (*t == '/' && t != ss))
   {
   *error = US"malformed IPv4 address or address mask";
   return ERROR;
   }
 
 if (*t == 0 || (*t == '/' && t != ss))
   {
   *error = US"malformed IPv4 address or address mask";
   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
@@ -2950,14 +2988,15 @@ 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;
 
 /* Do the IP address lookup if that is indeed what we have */
 
 
 /* Do the IP address lookup if that is indeed what we have */
 
@@ -2972,7 +3011,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);
@@ -3015,9 +3054,9 @@ 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);
-  if (valueptr != NULL) *valueptr = result;
-  return (result != NULL)? OK : search_find_defer? DEFER: FAIL;
+  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;
   }
 
 /* The pattern is not an IP address or network reference of any kind. That is,
   }
 
 /* The pattern is not an IP address or network reference of any kind. That is,
@@ -3059,11 +3098,8 @@ if (*t == 0)
   rc = host_find_byname(&h, NULL, HOST_FIND_QUALIFY_SINGLE, NULL, FALSE);
   if (rc == HOST_FOUND || rc == HOST_FOUND_LOCAL)
     {
   rc = host_find_byname(&h, NULL, HOST_FIND_QUALIFY_SINGLE, NULL, FALSE);
   if (rc == HOST_FOUND || rc == HOST_FOUND_LOCAL)
     {
-    host_item *hh;
-    for (hh = &h; hh != NULL; hh = hh->next)
-      {
+    for (host_item * hh = &h; hh; hh = hh->next)
       if (host_is_in_net(hh->address, cb->host_address, 0)) return OK;
       if (host_is_in_net(hh->address, cb->host_address, 0)) return OK;
-      }
     return FAIL;
     }
   if (rc == HOST_FIND_AGAIN) return DEFER;
     return FAIL;
     }
   if (rc == HOST_FIND_AGAIN) return DEFER;
@@ -3076,7 +3112,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);
 
@@ -3086,13 +3122,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 */
@@ -3232,9 +3269,9 @@ return rc;
 *      Check the given host item matches a list  *
 *************************************************/
 int
 *      Check the given host item matches a list  *
 *************************************************/
 int
-verify_check_given_host(uschar **listptr, host_item *host)
+verify_check_given_host(const uschar **listptr, const host_item *host)
 {
 {
-return verify_check_this_host(CUSS listptr, NULL, host->name, host->address, NULL);
+return verify_check_this_host(listptr, NULL, host->name, host->address, NULL);
 }
 
 /*************************************************
 }
 
 /*************************************************
@@ -3257,7 +3294,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);
 }
 
 
 }
 
 
@@ -3292,9 +3329,8 @@ always 1. */
 
 if (host_aton(address, bin) == 1)
   {
 
 if (host_aton(address, bin) == 1)
   {
-  int i;
   int x = bin[0];
   int x = bin[0];
-  for (i = 0; i < 4; i++)
+  for (int i = 0; i < 4; i++)
     {
     sprintf(CS bptr, "%d.", x & 255);
     while (*bptr) bptr++;
     {
     sprintf(CS bptr, "%d.", x & 255);
     while (*bptr) bptr++;
@@ -3308,19 +3344,16 @@ unknown. This is just a guess. */
 
 #if HAVE_IPV6
 else
 
 #if HAVE_IPV6
 else
-  {
-  int i, j;
-  for (j = 3; j >= 0; j--)
+  for (int j = 3; j >= 0; j--)
     {
     int x = bin[j];
     {
     int x = bin[j];
-    for (i = 0; i < 8; i++)
+    for (int i = 0; i < 8; i++)
       {
       sprintf(CS bptr, "%x.", x & 15);
       while (*bptr) bptr++;
       x >>= 4;
       }
     }
       {
       sprintf(CS bptr, "%x.", x & 15);
       while (*bptr) bptr++;
       x >>= 4;
       }
     }
-  }
 #endif
 
 /* Remove trailing period -- this is needed so that both arbitrary
 #endif
 
 /* Remove trailing period -- this is needed so that both arbitrary
@@ -3367,16 +3400,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);
@@ -3392,7 +3427,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
@@ -3400,7 +3435,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;
 
@@ -3411,16 +3446,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;
@@ -3435,36 +3470,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_record *rr;
-    dns_address **addrp = &(cb->rhs);
-    for (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. */
+
+      time_t soa_negttl = dns_expire_from_soa(dnsa, T_A);
+      cb->expiry = soa_negttl ? soa_negttl : time(NULL) + ttl;
+      break;
+      }
 
 
-    if (!cb->rhs) cb->rc = DNS_NODATA;
+    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
@@ -3475,7 +3532,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
@@ -3496,7 +3553,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;
 
@@ -3504,8 +3560,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;
        }
 
@@ -3527,9 +3583,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;
           }
         }
@@ -3594,22 +3650,18 @@ 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)
-      {
-      dns_record *rr;
-      for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
-           rr;
-           rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
-        if (rr->type == T_TXT) break;
-      if (rr)
-        {
-        int len = (rr->data)[0];
-        if (len > 511) len = 127;
-        store_pool = POOL_PERM;
-        cb->text = string_sprintf("%.*s", len, CUS (rr->data+1));
-        store_pool = old_pool;
-        }
-      }
+    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 (len > 511) len = 127;
+         store_pool = POOL_PERM;
+         cb->text = string_sprintf("%.*s", len, CUS (rr->data+1));
+         store_pool = old_pool;
+         break;
+         }
     }
 
   dnslist_value = addlist;
     }
 
   dnslist_value = addlist;
@@ -3703,8 +3755,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 *s;
-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 */
@@ -3717,7 +3767,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;
@@ -3727,7 +3777,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 */
 
@@ -3781,8 +3831,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;
@@ -3794,32 +3843,28 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
   actually causing an error here, because that would no doubt hold up incoming
   mail. Instead, I'll just log it. */
 
   actually causing an error here, because that would no doubt hold up incoming
   mail. Instead, I'll just log it. */
 
-  for (s = domain; *s != 0; s++)
-    {
+  for (uschar * s = domain; *s; s++)
     if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_')
       {
       log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains "
         "strange characters - is this right?", domain);
       break;
       }
     if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_')
       {
       log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains "
         "strange characters - is this right?", domain);
       break;
       }
-    }
 
   /* Check the alternate domain if present */
 
 
   /* Check the alternate domain if present */
 
-  if (domain_txt != domain) for (s = domain_txt; *s != 0; s++)
-    {
+  if (domain_txt != domain) for (uschar * s = domain_txt; *s; s++)
     if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_')
       {
       log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains "
         "strange characters - is this right?", domain_txt);
       break;
       }
     if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_')
       {
       log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains "
         "strange characters - is this right?", domain_txt);
       break;
       }
-    }
 
   /* If there is no key string, construct the query by adding the domain name
   onto the inverted host address, and perform a single DNS lookup. */
 
 
   /* If there is no key string, construct the query by adding the domain name
   onto the inverted host address, and perform a single DNS lookup. */
 
-  if (key == NULL)
+  if (!key)
     {
     if (where == ACL_WHERE_NOTSMTP_START || where == ACL_WHERE_NOTSMTP)
       {
     {
     if (where == ACL_WHERE_NOTSMTP_START || where == ACL_WHERE_NOTSMTP)
       {
@@ -3828,7 +3873,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);
@@ -3850,11 +3895,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;
 
@@ -3866,7 +3909,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);
@@ -3890,6 +3932,246 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
 return FAIL;
 }
 
 return FAIL;
 }
 
+
+
+/****************************************************
+  Verify a local user account for quota sufficiency
+****************************************************/
+
+/* The real work, done via a re-exec for privs, calls
+down to the transport for the quota check.
+
+Route and transport (in recipient-verify mode) the
+given recipient. 
+
+A routing result indicating any transport type other than appendfile
+results in a fail.
+
+Return, on stdout, a result string containing:
+- highlevel result code (OK, DEFER, FAIL)
+- errno
+- where string
+- message string
+*/
+
+void
+verify_quota(uschar * address)
+{
+address_item vaddr = {.address = address};
+BOOL routed;
+uschar * msg = US"\0";
+int rc, len = 1;
+
+if ((rc = verify_address(&vaddr, NULL, vopt_is_recipient | vopt_quota,
+    1, 0, 0, NULL, NULL, &routed)) != OK)
+  {
+  uschar * where = recipient_verify_failure;
+  msg = acl_verify_message ? acl_verify_message : vaddr.message;
+  if (!msg) msg = US"";
+  if (rc == DEFER && vaddr.basic_errno == ERRNO_EXIMQUOTA)
+    {
+    rc = FAIL;                                 /* DEFER -> FAIL */
+    where = US"quota";
+    vaddr.basic_errno = 0;
+    }
+  else if (!where) where = US"";
+
+  len = 5 + Ustrlen(msg) + 1 + Ustrlen(where);
+  msg = string_sprintf("%c%c%c%c%c%s%c%s", (uschar)rc,
+    (vaddr.basic_errno >> 24) && 0xff, (vaddr.basic_errno >> 16) && 0xff,
+    (vaddr.basic_errno >> 8) && 0xff, vaddr.basic_errno && 0xff,
+    where, '\0', msg);
+  }
+
+DEBUG(D_verify) debug_printf_indent("verify_quota: len %d\n", len);
+write(1, msg, len);
+return;
+}
+
+
+/******************************************************************************/
+
+/* Quota cache lookup.  We use the callout hints db also for the quota cache.
+Return TRUE if a nonexpired record was found, having filled in the yield
+argument.
+*/
+
+static BOOL
+cached_quota_lookup(const uschar * rcpt, int * yield,
+  int pos_cache, int neg_cache)
+{
+open_db dbblock, *dbm_file = NULL;
+dbdata_callout_cache_address * cache_address_record;
+
+if (!pos_cache && !neg_cache)
+  return FALSE;
+if (!(dbm_file = dbfn_open(US"callout", O_RDWR, &dbblock, FALSE, TRUE)))
+  {
+  HDEBUG(D_verify) debug_printf_indent("quota cache: not available\n");
+  return FALSE;
+  }
+if (!(cache_address_record = (dbdata_callout_cache_address *)
+    get_callout_cache_record(dbm_file, rcpt, US"address",
+      pos_cache, neg_cache)))
+  {
+  dbfn_close(dbm_file);
+  return FALSE;
+  }
+if (cache_address_record->result == ccache_accept)
+  *yield = OK;
+dbfn_close(dbm_file);
+return TRUE;
+}
+
+/* Quota cache write */
+
+static void
+cache_quota_write(const uschar * rcpt, int yield, int pos_cache, int neg_cache)
+{
+open_db dbblock, *dbm_file = NULL;
+dbdata_callout_cache_address cache_address_record;
+
+if (!pos_cache && !neg_cache)
+  return;
+if (!(dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE, TRUE)))
+  {
+  HDEBUG(D_verify) debug_printf_indent("quota cache: not available\n");
+  return;
+  }
+
+cache_address_record.result = yield == OK ? ccache_accept : ccache_reject;
+
+(void)dbfn_write(dbm_file, rcpt, &cache_address_record,
+       (int)sizeof(dbdata_callout_cache_address));
+HDEBUG(D_verify) debug_printf_indent("wrote %s quota cache record for %s\n",
+      yield == OK ? "positive" : "negative", rcpt);
+
+dbfn_close(dbm_file);
+return;
+}
+
+
+/* To evaluate a local user's quota, starting in ACL, we need to
+fork & exec to regain privileges, to that we can change to the user's
+identity for access to their files.
+
+Arguments:
+ rcpt          Recipient account
+ pos_cache     Number of seconds to cache a positive result (delivery
+               to be accepted).  Zero to disable caching.
+ neg_cache     Number of seconds to cache a negative result.  Zero to disable.
+ msg           Pointer to result string pointer
+
+Return:                OK/DEFER/FAIL code
+*/
+
+int
+verify_quota_call(const uschar * rcpt, int pos_cache, int neg_cache,
+  uschar ** msg)
+{
+int pfd[2], pid, save_errno, yield = FAIL;
+void (*oldsignal)(int);
+const uschar * where = US"socketpair";
+
+*msg = NULL;
+
+if (cached_quota_lookup(rcpt, &yield, pos_cache, neg_cache))
+  {
+  HDEBUG(D_verify) debug_printf_indent("quota cache: address record is %d\n",
+    yield == OK ? "positive" : "negative");
+  if (yield != OK)
+    {
+    recipient_verify_failure = US"quota";
+    acl_verify_message = *msg =
+      US"Previous (cached) quota verification failure";
+    }
+  return yield;
+  }
+
+if (pipe(pfd) != 0)
+  goto fail;
+
+where = US"fork";
+oldsignal = signal(SIGCHLD, SIG_DFL);
+if ((pid = exim_fork(US"quota-verify")) < 0)
+  {
+  save_errno = errno;
+  close(pfd[pipe_write]);
+  close(pfd[pipe_read]);
+  errno = save_errno;
+  goto fail;
+  }
+
+if (pid == 0)          /* child */
+  {
+  close(pfd[pipe_read]);
+  force_fd(pfd[pipe_write], 1);                /* stdout to pipe */
+  close(pfd[pipe_write]);
+  dup2(1, 0);
+  if (debug_fd > 0) force_fd(debug_fd, 2);
+
+  child_exec_exim(CEE_EXEC_EXIT, FALSE, NULL, FALSE, 3,
+    US"-MCq", string_sprintf("%d", message_size), rcpt);
+  /*NOTREACHED*/
+  }
+
+save_errno = errno;
+close(pfd[pipe_write]);
+
+if (pid < 0)
+  {
+  DEBUG(D_verify) debug_printf_indent(" fork: %s\n", strerror(save_errno));
+  }
+else
+  {
+  uschar buf[128];
+  int n = read(pfd[pipe_read], buf, sizeof(buf));
+  int status;
+
+  waitpid(pid, &status, 0);
+  if (status == 0)
+    {
+    uschar * s;
+
+    if (n > 0) yield = buf[0];
+    if (n > 4)
+      save_errno = (buf[1] << 24) | (buf[2] << 16) | (buf[3] << 8) | buf[4];
+    if ((recipient_verify_failure = n > 5
+       ? string_copyn_taint(buf+5, n-5, FALSE) : NULL))
+      {
+      int m;
+      s = buf + 5 + Ustrlen(recipient_verify_failure) + 1;
+      m = n - (s - buf);
+      acl_verify_message = *msg =
+       m > 0 ? string_copyn_taint(s, m, FALSE) : NULL;
+      }
+
+    DEBUG(D_verify) debug_printf_indent("verify call response:"
+      " len %d yield %s errno '%s' where '%s' msg '%s'\n",
+      n, rc_names[yield], strerror(save_errno), recipient_verify_failure, *msg);
+
+    if (  yield == OK
+       || save_errno == 0 && Ustrcmp(recipient_verify_failure, "quota") == 0)
+      cache_quota_write(rcpt, yield, pos_cache, neg_cache);
+    else DEBUG(D_verify)
+      debug_printf_indent("result not cacheable\n");
+    }
+  else
+    {
+    DEBUG(D_verify)
+      debug_printf_indent("verify call response: waitpid status 0x%04x\n", status);
+    }
+  }
+
+close(pfd[pipe_read]);
+errno = save_errno;
+
+fail:
+
+return yield;
+}
+
+
 /* vi: aw ai sw=2
 */
 /* End of verify.c */
 /* vi: aw ai sw=2
 */
 /* End of verify.c */