Callouts: record succeeding random local-part tests. Bug 177
[exim.git] / src / src / verify.c
index e1f81dd5ba29af189dccc0048fb38e1c1c1748d2..95876d1cd2a7f6352fbf8aa44e029b0e4155b326 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions concerned with verifying things. The original code for callout
@@ -39,7 +39,7 @@ static tree_node *dnsbl_cache = NULL;
 #define MT_NOT 1
 #define MT_ALL 2
 
-static uschar cutthrough_response(char, uschar **, int);
+static uschar cutthrough_response(int, char, uschar **, int);
 
 
 
@@ -68,9 +68,7 @@ int length, expire;
 time_t now;
 dbdata_callout_cache *cache_record;
 
-cache_record = dbfn_read_with_length(dbm_file, key, &length);
-
-if (cache_record == NULL)
+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);
   return NULL;
@@ -198,6 +196,7 @@ else
       case ccache_accept:
        HDEBUG(D_verify)
          debug_printf("callout cache: domain accepts random addresses\n");
+       *failure_ptr = US"random";
        dbfn_close(dbm_file);
        return TRUE;     /* Default yield is OK */
 
@@ -388,30 +387,33 @@ if (addr->transport == cutthrough.addr.transport)
       deliver_domain = addr->domain;
       transport_name = addr->transport->name;
 
-      host_af = (Ustrchr(host->address, ':') == NULL)? AF_INET:AF_INET6;
+      host_af = Ustrchr(host->address, ':') ? AF_INET6 : AF_INET;
 
-      if (!smtp_get_interface(tf->interface, host_af, addr, &interface,
-             US"callout") ||
-         !smtp_get_port(tf->port, addr, &port, US"callout"))
+      if (  !smtp_get_interface(tf->interface, host_af, addr, &interface,
+             US"callout")
+        || !smtp_get_port(tf->port, addr, &port, US"callout")
+        )
        log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address,
          addr->message);
 
+      smtp_port_for_connect(host, port);
+
       if (  (  interface == cutthrough.interface
            || (  interface
               && cutthrough.interface
               && Ustrcmp(interface, cutthrough.interface) == 0
            )  )
-        && port == cutthrough.host.port
+        && host->port == cutthrough.host.port
         )
        {
        uschar * resp = NULL;
 
        /* Match!  Send the RCPT TO, set done from the response */
        done =
-         smtp_write_command(&ctblock, FALSE, "RCPT TO:<%.1000s>\r\n",
+         smtp_write_command(&ctblock, SCMD_FLUSH, "RCPT TO:<%.1000s>\r\n",
            transport_rcpt_address(addr,
               addr->transport->rcpt_include_affixes)) >= 0 &&
-         cutthrough_response('2', &resp, CUTTHROUGH_DATA_TIMEOUT) == '2';
+         cutthrough_response(cutthrough.fd, '2', &resp, CUTTHROUGH_DATA_TIMEOUT) == '2';
 
        /* This would go horribly wrong if a callout fail was ignored by ACL.
        We punt by abandoning cutthrough on a reject, like the
@@ -429,7 +431,7 @@ if (addr->transport == cutthrough.addr.transport)
          }
        else
          {
-         cancel_cutthrough_connection("recipient rejected");
+         cancel_cutthrough_connection(TRUE, US"recipient rejected");
          if (!resp || errno == ETIMEDOUT)
            {
            HDEBUG(D_verify) debug_printf("SMTP timeout\n");
@@ -440,9 +442,8 @@ if (addr->transport == cutthrough.addr.transport)
              Ustrcpy(resp, US"connection dropped");
 
            addr->message =
-             string_sprintf("response to \"%s\" from %s [%s] was: %s",
-               big_buffer, host->name, host->address,
-               string_printing(resp));
+             string_sprintf("response to \"%s\" was: %s",
+               big_buffer, string_printing(resp));
 
            addr->user_message =
              string_sprintf("Callout verification failed:\n%s", resp);
@@ -460,7 +461,7 @@ if (addr->transport == cutthrough.addr.transport)
       break;   /* host_list */
       }
 if (!done)
-  cancel_cutthrough_connection("incompatible connection");
+  cancel_cutthrough_connection(TRUE, US"incompatible connection");
 return done;
 }
 
@@ -491,6 +492,7 @@ Arguments:
                       vopt_callout_random => do the "random" thing
                       vopt_callout_recipsender => use real sender for recipient
                       vopt_callout_recippmaster => use postmaster for recipient
+                     vopt_callout_hold         => lazy close connection
   se_mailfrom         MAIL FROM address for sender verify; NULL => ""
   pm_mailfrom         if non-NULL, do the postmaster check with this sender
 
@@ -557,7 +559,10 @@ else
 if (cached_callout_lookup(addr, address_key, from_address,
       &options, &pm_mailfrom, &yield, failure_ptr,
       &new_domain_record, &old_domain_cache_result))
+  {
+  cancel_cutthrough_connection(TRUE, US"cache-hit");
   goto END_CALLOUT;
+  }
 
 if (!addr->transport)
   {
@@ -624,7 +629,6 @@ coding means skipping this whole loop and doing the append separately.  */
     int port = 25;
     uschar *interface = NULL;  /* Outgoing interface to use; NULL => any */
     smtp_context sx;
-    uschar responsebuffer[4096];
 
     if (!host->address)
       {
@@ -643,7 +647,7 @@ coding means skipping this whole loop and doing the append separately.  */
 
     /* Set IPv4 or IPv6 */
 
-    host_af = Ustrchr(host->address, ':') == NULL ? AF_INET : AF_INET6;
+    host_af = Ustrchr(host->address, ':') ? AF_INET6 : AF_INET;
 
     /* Expand and interpret the interface and port strings. The latter will not
     be used if there is a host-specific port (e.g. from a manualroute router).
@@ -671,6 +675,7 @@ coding means skipping this whole loop and doing the append separately.  */
     sx.interface = interface;
     sx.helo_data = tf->helo_data;
     sx.tblock = addr->transport;
+    sx.verify = TRUE;
 
 tls_retry_connection:
     /* Set the address state so that errors are recorded in it */
@@ -683,21 +688,23 @@ tls_retry_connection:
     SMTP command to send.  If we tried TLS but it failed, try again without
     if permitted */
 
-    if (  (yield = smtp_setup_conn(&sx, FALSE, TRUE)) == DEFER
+    yield = smtp_setup_conn(&sx, FALSE);
+#ifdef SUPPORT_TLS
+    if (  yield == DEFER
        && addr->basic_errno == ERRNO_TLSFAILURE
        && ob->tls_tempfail_tryclear
        && verify_check_given_host(&ob->hosts_require_tls, host) != OK
        )
       {
-      log_write(0, LOG_MAIN, "TLS session failure:"
-       " callout unencrypted to %s [%s] (not in hosts_require_tls)",
-       host->name, host->address);
-      yield = smtp_setup_conn(&sx, TRUE, TRUE);
+      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);
       }
+#endif
     if (yield != OK)
       {
-      if (addr->message) addr->message = string_sprintf("%s [%s] %s",
-                                     host->name, host->address, addr->message);
       errno = addr->basic_errno;
       transport_name = NULL;
       deliver_host = deliver_host_address = NULL;
@@ -723,68 +730,21 @@ tls_retry_connection:
     addr->authenticator = client_authenticator;
     addr->auth_id = client_authenticated_id;
 
-    /* Build a mail-AUTH string (re-using responsebuffer for convenience */
-
-    done =
-      !smtp_mail_auth_str(responsebuffer, sizeof(responsebuffer), addr, ob);
-
-    if (done)
-      {
-      addr->auth_sndr = client_authenticated_sender;
-
-      /* Send the MAIL command */
-
-      done =
-         (smtp_write_command(&sx.outblock, FALSE,
-#ifdef SUPPORT_I18N
-           addr->prop.utf8_msg && !addr->prop.utf8_downcvt
-           ? "MAIL FROM:<%s>%s%s SMTPUTF8\r\n"
-           :
-#endif
-             "MAIL FROM:<%s>%s%s\r\n",
-           from_address,
-           responsebuffer,
-           options & vopt_is_recipient && sx.peer_offered & PEER_OFFERED_SIZE
-           ? string_sprintf(" SIZE=%d", message_size + ob->size_addition)
-           : US""
-           ) >= 0)
-
-       && smtp_read_response(&sx.inblock, responsebuffer, sizeof(responsebuffer),
-           '2', callout);
-      }
-
-    deliver_host = deliver_host_address = NULL;
-    deliver_domain = save_deliver_domain;
-
-    /* If the host does not accept MAIL FROM:<>, arrange to cache this
-    information, but again, don't record anything for an I/O error or a defer. Do
-    not cache rejections of MAIL when a non-empty sender has been used, because
-    that blocks the whole domain for all senders. */
-
-    if (!done)
-      {
-      *failure_ptr = US"mail";     /* At or before MAIL */
-      if (errno == 0 && responsebuffer[0] == '5')
-        {
-        setflag(addr, af_verify_nsfail);
-        if (from_address[0] == 0)
-          new_domain_record.result = ccache_reject_mfnull;
-        }
-      }
+    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;
 
-    /* Otherwise, proceed to check a "random" address (if required), then the
-    given address, and the postmaster address (if required). Between each check,
-    issue RSET, because some servers accept only one recipient after MAIL
-    FROM:<>.
+    new_domain_record.result = old_domain_cache_result == ccache_reject_mfnull
+      ? ccache_reject_mfnull : ccache_accept;
 
-    Before doing this, set the result in the domain cache record to "accept",
-    unless its previous value was ccache_reject_mfnull. In that case, the domain
-    rejects MAIL FROM:<> and we want to continue to remember that. When that is
-    the case, we have got here only in the case of a recipient verification with
-    a non-null sender. */
+    /* Do the random local part check first. Temporarily replace the recipient
+    with the "random" value */
 
-    else
+    if (random_local_part)
       {
+      uschar * main_address = addr->address;
       const uschar * rcpt_domain = addr->domain;
 
 #ifdef SUPPORT_I18N
@@ -802,179 +762,201 @@ tls_retry_connection:
        }
 #endif
 
-      new_domain_record.result = old_domain_cache_result == ccache_reject_mfnull
-       ? ccache_reject_mfnull : ccache_accept;
-
-      /* Do the random local part check first */
-
-      if (random_local_part)
-        {
-        uschar randombuffer[1024];
-        BOOL random_ok =
-          smtp_write_command(&sx.outblock, FALSE,
-            "RCPT TO:<%.1000s@%.1000s>\r\n", random_local_part,
-            rcpt_domain) >= 0 &&
-          smtp_read_response(&sx.inblock, randombuffer,
-            sizeof(randombuffer), '2', callout);
-
-        /* Remember when we last did a random test */
-
-        new_domain_record.random_stamp = time(NULL);
-
-        /* If accepted, we aren't going to do any further tests below. */
-
-        if (random_ok)
-          new_domain_record.random_result = ccache_accept;
-
-        /* Otherwise, cache a real negative response, and get back to the right
-        state to send RCPT. Unless there's some problem such as a dropped
-        connection, we expect to succeed, because the commands succeeded above.
-       However, some servers drop the connection after responding to  an
-       invalid recipient, so on (any) error we drop and remake the connection.
-       */
-
-        else if (errno == 0)
-          {
-         /* This would be ok for 1st rcpt a cutthrough, but no way to
-         handle a subsequent.  So refuse to support any */
-         cancel_cutthrough_connection("random-recipient");
+      /* This would be ok for 1st rcpt of a cutthrough (the case handled here;
+      subsequents are done in cutthrough_multi()), but no way to
+      handle a subsequent because of the RSET vaporising the MAIL FROM.
+      So refuse to support any.  Most cutthrough use will not involve
+      random_local_part, so no loss. */
+      cancel_cutthrough_connection(TRUE, US"random-recipient");
+
+      addr->address = string_sprintf("%s@%.1000s",
+                                   random_local_part, rcpt_domain);
+      done = FALSE;
+
+      /* If accepted, we aren't going to do any further tests below.
+      Otherwise, cache a real negative response, and get back to the right
+      state to send RCPT. Unless there's some problem such as a dropped
+      connection, we expect to succeed, because the commands succeeded above.
+      However, some servers drop the connection after responding to an
+      invalid recipient, so on (any) error we drop and remake the connection.
+      XXX We don't care about that for postmaster_full.  Should we?
+
+      XXX could we add another flag to the context, and have the common
+      code emit the RSET too?  Even pipelined after the RCPT...
+      Then the main-verify call could use it if there's to be a subsequent
+      postmaster-verify.
+      The sync_responses() would need to be taught about it and we'd
+      need another return code filtering out to here.
+
+      Avoid using a SIZE option on the MAIL for all random-rcpt checks.
+      */
 
-          if (randombuffer[0] == '5')
-            new_domain_record.random_result = ccache_reject;
+      sx.avoid_option = OPTION_SIZE;
 
-          done =
-            smtp_write_command(&sx.outblock, FALSE, "RSET\r\n") >= 0 &&
-            smtp_read_response(&sx.inblock, responsebuffer, sizeof(responsebuffer),
-              '2', callout) &&
+      /* Remember when we last did a random test */
+      new_domain_record.random_stamp = time(NULL);
 
-            smtp_write_command(&sx.outblock, FALSE,
-#ifdef SUPPORT_I18N
-             addr->prop.utf8_msg && !addr->prop.utf8_downcvt
-             ? "MAIL FROM:<%s> SMTPUTF8\r\n"
-             :
-#endif
-               "MAIL FROM:<%s>\r\n",
-              from_address) >= 0 &&
-            smtp_read_response(&sx.inblock, responsebuffer, sizeof(responsebuffer),
-              '2', callout);
+      if (smtp_write_mail_and_rcpt_cmds(&sx, &yield) == 0)
+       switch(addr->transport_return)
+         {
+         case PENDING_OK:      /* random was accepted, unfortunately */
+           new_domain_record.random_result = ccache_accept;
+           yield = OK;         /* Only usable verify result we can return */
+           done = TRUE;
+           *failure_ptr = US"random";
+           goto no_conn;
+         case FAIL:            /* rejected: the preferred result */
+           new_domain_record.random_result = ccache_reject;
+           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 =
+             smtp_write_command(&sx.outblock, SCMD_FLUSH, "RSET\r\n") >= 0 &&
+             smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer),
+               '2', callout)))
+             break;
 
-         if (!done)
-           {
            HDEBUG(D_acl|D_v)
-             debug_printf("problem after random/rset/mfrom; reopen conn\n");
+             debug_printf_indent("problem after random/rset/mfrom; reopen conn\n");
            random_local_part = NULL;
 #ifdef SUPPORT_TLS
-           tls_close(FALSE, TRUE);
+           tls_close(FALSE, TLS_SHUTDOWN_NOWAIT);
 #endif
-           HDEBUG(D_transport|D_acl|D_v) debug_printf("  SMTP(close)>>\n");
+           HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
            (void)close(sx.inblock.sock);
+           sx.inblock.sock = sx.outblock.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;
+           sx.first_addr = sx.sync_addr = addr;
+           sx.ok = FALSE;
+           sx.send_rset = TRUE;
+           sx.completed_addr = FALSE;
            goto tls_retry_connection;
-           }
-          }
-        else done = FALSE;    /* Some timeout/connection problem */
-        }                     /* Random check */
+         case DEFER:           /* 4xx response to random */
+           break;              /* Just to be clear. ccache_unknown, !done. */
+         }
 
-      /* If the host is accepting all local parts, as determined by the "random"
-      check, we don't need to waste time doing any further checking. */
+      /* 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;
+      }
+    else
+      done = TRUE;
 
-      if (new_domain_record.random_result != ccache_accept && done)
-        {
-        /* Get the rcpt_include_affixes flag from the transport if there is one,
-        but assume FALSE if there is not. */
+    /* Main verify.  For rcpt-verify use SIZE if we know it and we're not cacheing;
+    for sndr-verify never use it. */
 
-       uschar * rcpt = transport_rcpt_address(addr,
-              addr->transport ? addr->transport->rcpt_include_affixes : FALSE);
+    if (done)
+      {
+      if (!(options & vopt_is_recipient  &&  options & vopt_callout_no_cache))
+       sx.avoid_option = OPTION_SIZE;
 
-#ifdef SUPPORT_I18N
-       /*XXX should the conversion be moved into transport_rcpt_address() ? */
-       if (  testflag(addr, af_utf8_downcvt)
-          && !(rcpt = string_address_utf8_to_alabel(rcpt, NULL))
-          )
-         {
-         errno = ERRNO_EXPANDFAIL;
-         *failure_ptr = US"recipient";
-         done = FALSE;
-         }
-       else
-#endif
+      done = FALSE;
+      switch(smtp_write_mail_and_rcpt_cmds(&sx, &yield))
+       {
+       case 0:  switch(addr->transport_return) /* ok so far */
+                   {
+                   case PENDING_OK:  done = TRUE;
+                                     new_address_record.result = ccache_accept;
+                                     break;
+                   case FAIL:      done = TRUE;
+                                     yield = FAIL;
+                                     *failure_ptr = US"recipient";
+                                     new_address_record.result = ccache_reject;
+                                     break;
+                   default:        break;
+                   }
+                 break;
+
+       case -1:                                /* MAIL response error */
+                 *failure_ptr = US"mail";
+                 if (errno == 0 && sx.buffer[0] == '5')
+                   {
+                   setflag(addr, af_verify_nsfail);
+                   if (from_address[0] == 0)
+                     new_domain_record.result = ccache_reject_mfnull;
+                   }
+                 break;
+                                               /* non-MAIL read i/o error */
+                                               /* non-MAIL response timeout */
+                                               /* internal error; channel still usable */
+       default:  break;                        /* transmit failed */
+       }
+      }
 
-        done =
-          smtp_write_command(&sx.outblock, FALSE, "RCPT TO:<%.1000s>\r\n",
-            rcpt) >= 0 &&
-          smtp_read_response(&sx.inblock, responsebuffer, sizeof(responsebuffer),
-            '2', callout);
+    addr->auth_sndr = client_authenticated_sender;
 
-        if (done)
-          new_address_record.result = ccache_accept;
-        else if (errno == 0 && responsebuffer[0] == '5')
-          {
-          *failure_ptr = US"recipient";
-          new_address_record.result = ccache_reject;
-          }
+    deliver_host = deliver_host_address = NULL;
+    deliver_domain = save_deliver_domain;
 
-        /* Do postmaster check if requested; if a full check is required, we
-        check for RCPT TO:<postmaster> (no domain) in accordance with RFC 821. */
+    /* Do postmaster check if requested; if a full check is required, we
+    check for RCPT TO:<postmaster> (no domain) in accordance with RFC 821. */
 
-        if (done && pm_mailfrom)
-          {
-          /* Could possibly shift before main verify, just above, and be ok
-         for cutthrough.  But no way to handle a subsequent rcpt, so just
-         refuse any */
-       cancel_cutthrough_connection("postmaster verify");
-       HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of postmaster verify\n");
-
-          done =
-            smtp_write_command(&sx.outblock, FALSE, "RSET\r\n") >= 0 &&
-            smtp_read_response(&sx.inblock, responsebuffer,
-              sizeof(responsebuffer), '2', callout) &&
-
-            smtp_write_command(&sx.outblock, FALSE,
-              "MAIL FROM:<%s>\r\n", pm_mailfrom) >= 0 &&
-            smtp_read_response(&sx.inblock, responsebuffer,
-              sizeof(responsebuffer), '2', callout) &&
-
-            /* First try using the current domain */
-
-            ((
-            smtp_write_command(&sx.outblock, FALSE,
-              "RCPT TO:<postmaster@%.1000s>\r\n", rcpt_domain) >= 0 &&
-            smtp_read_response(&sx.inblock, responsebuffer,
-              sizeof(responsebuffer), '2', callout)
-            )
-
-            ||
-
-            /* If that doesn't work, and a full check is requested,
-            try without the domain. */
-
-            (
-            (options & vopt_callout_fullpm) != 0 &&
-            smtp_write_command(&sx.outblock, FALSE,
-              "RCPT TO:<postmaster>\r\n") >= 0 &&
-            smtp_read_response(&sx.inblock, responsebuffer,
-              sizeof(responsebuffer), '2', callout)
-            ));
-
-          /* Sort out the cache record */
-
-          new_domain_record.postmaster_stamp = time(NULL);
-
-          if (done)
-            new_domain_record.postmaster_result = ccache_accept;
-          else if (errno == 0 && responsebuffer[0] == '5')
-            {
-            *failure_ptr = US"postmaster";
-            setflag(addr, af_verify_pmfail);
-            new_domain_record.postmaster_result = ccache_reject;
-            }
-          }
-        }           /* Random not accepted */
-      }             /* MAIL FROM: accepted */
+    if (done && pm_mailfrom)
+      {
+      /* Could possibly shift before main verify, just above, and be ok
+      for cutthrough.  But no way to handle a subsequent rcpt, so just
+      refuse any */
+      cancel_cutthrough_connection(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);
+
+      if (done)
+       {
+       uschar * main_address = addr->address;
+
+       /*XXX oops, affixes */
+       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;
+
+       if(  smtp_write_mail_and_rcpt_cmds(&sx, &yield) == 0
+         && addr->transport_return == PENDING_OK
+         )
+         done = TRUE;
+       else
+         done = (options & vopt_callout_fullpm) != 0
+             && smtp_write_command(&sx.outblock, SCMD_FLUSH,
+                           "RCPT TO:<postmaster>\r\n") >= 0
+             && smtp_read_response(&sx.inblock, sx.buffer,
+                           sizeof(sx.buffer), '2', callout);
+
+       /* Sort out the cache record */
+
+       new_domain_record.postmaster_stamp = time(NULL);
+
+       if (done)
+         new_domain_record.postmaster_result = ccache_accept;
+       else if (errno == 0 && sx.buffer[0] == '5')
+         {
+         *failure_ptr = US"postmaster";
+         setflag(addr, af_verify_pmfail);
+         new_domain_record.postmaster_result = ccache_reject;
+         }
 
+       addr->address = main_address;
+       }
+      }
     /* For any failure of the main check, other than a negative response, we just
     close the connection and carry on. We can identify a negative response by the
     fact that errno is zero. For I/O errors it will be non-zero
@@ -986,7 +968,7 @@ tls_retry_connection:
     is not to be widely broadcast. */
 
 no_conn:
-    if (!done) switch(errno)
+    switch(errno)
       {
       case ETIMEDOUT:
        HDEBUG(D_verify) debug_printf("SMTP timeout\n");
@@ -999,8 +981,7 @@ no_conn:
        extern int acl_where;   /* src/acl.c */
        errno = 0;
        addr->message = string_sprintf(
-           "response to \"EHLO\" from %s [%s] did not include SMTPUTF8",
-           host->name, host->address);
+           "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";
@@ -1014,21 +995,25 @@ no_conn:
        break;
 
       case 0:
-       if (*responsebuffer == 0) Ustrcpy(responsebuffer, US"connection dropped");
+       if (*sx.buffer == 0) Ustrcpy(sx.buffer, US"connection dropped");
 
-       addr->message =
-         string_sprintf("response to \"%s\" from %s [%s] was: %s",
-           big_buffer, host->name, host->address,
-           string_printing(responsebuffer));
+       /*XXX test here is ugly; seem to have a split of responsibility for
+       building this message.  Need to rationalise.  Where is it done
+       before here, and when not?
+       Not == 5xx resp to MAIL on main-verify
+       */
+       if (!addr->message) addr->message =
+         string_sprintf("response to \"%s\" was: %s",
+                         big_buffer, string_printing(sx.buffer));
 
        addr->user_message = options & vopt_is_recipient
-         ? string_sprintf("Callout verification failed:\n%s", responsebuffer)
+         ? string_sprintf("Callout verification failed:\n%s", sx.buffer)
          : string_sprintf("Called:   %s\nSent:     %s\nResponse: %s",
-           host->address, big_buffer, responsebuffer);
+           host->address, big_buffer, sx.buffer);
 
        /* Hard rejection ends the process */
 
-       if (responsebuffer[0] == '5')   /* Address rejected */
+       if (sx.buffer[0] == '5')   /* Address rejected */
          {
          yield = FAIL;
          done = TRUE;
@@ -1040,8 +1025,33 @@ no_conn:
 
     /* Cutthrough - on a successful connect and recipient-verify with
     use-sender and we are 1st rcpt and have no cutthrough conn so far
-    here is where we want to leave the conn open */
-    if (  cutthrough.delivery
+    here is where we want to leave the conn open.  Ditto for a lazy-close
+    verify. */
+
+    if (cutthrough.delivery)
+      {
+      if (addr->transport->filter_command)
+        {
+        cutthrough.delivery= FALSE;
+        HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of transport filter\n");
+        }
+#ifndef DISABLE_DKIM
+      if (ob->dkim.dkim_domain)
+        {
+        cutthrough.delivery= FALSE;
+        HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of DKIM signing\n");
+        }
+#endif
+#ifdef EXPERIMENTAL_ARC
+      if (ob->arc_sign)
+        {
+        cutthrough.delivery= FALSE;
+        HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of ARC signing\n");
+        }
+#endif
+      }
+
+    if (  (cutthrough.delivery || options & vopt_callout_hold)
        && rcpt_count == 1
        && done
        && yield == OK
@@ -1053,14 +1063,29 @@ no_conn:
        && !sx.lmtp
        )
       {
-      HDEBUG(D_acl|D_v) debug_printf("holding verify callout open for cutthrough delivery\n");
-
-      cutthrough.fd = sx.outblock.sock;        /* We assume no buffer in use in the outblock */
-      cutthrough.nrcpt = 1;
-      cutthrough.interface = interface;
-      cutthrough.host = *host;
-      cutthrough.addr = *addr;         /* Save the address_item for later logging */
-      cutthrough.addr.next =     NULL;
+      HDEBUG(D_acl|D_v) debug_printf_indent("holding verify callout open for %s\n",
+       cutthrough.delivery
+       ? "cutthrough delivery" : "potential further verifies and delivery");
+
+      cutthrough.callout_hold_only = !cutthrough.delivery;
+      cutthrough.is_tls =      tls_out.active >= 0;
+      cutthrough.fd =  sx.outblock.sock;       /* We assume no buffer in use in the outblock */
+      cutthrough.nrcpt =       1;
+      cutthrough.transport =   addr->transport->name;
+      cutthrough.interface =   interface;
+      cutthrough.snd_port =    sending_port;
+      cutthrough.peer_options =        smtp_peer_options;
+      cutthrough.host =                *host;
+       {
+       int oldpool = store_pool;
+       store_pool = POOL_PERM;
+       cutthrough.snd_ip = string_copy(sending_ip_address);
+       cutthrough.host.name = string_copy(host->name);
+       cutthrough.host.address = string_copy(host->address);
+       store_pool = oldpool;
+       }
+      cutthrough.addr =                *addr;          /* Save the address_item for later logging */
+      cutthrough.addr.next =   NULL;
       cutthrough.addr.host_used = &cutthrough.host;
       if (addr->parent)
         *(cutthrough.addr.parent = store_get(sizeof(address_item))) =
@@ -1073,24 +1098,24 @@ no_conn:
       }
     else
       {
-      /* Ensure no cutthrough on multiple address verifies */
+      /* Ensure no cutthrough on multiple verifies that were incompatible */
       if (options & vopt_callout_recipsender)
-        cancel_cutthrough_connection("not usable for cutthrough");
+        cancel_cutthrough_connection(TRUE, US"not usable for cutthrough");
       if (sx.send_quit)
        {
-       (void) smtp_write_command(&sx.outblock, FALSE, "QUIT\r\n");
+       (void) smtp_write_command(&sx.outblock, SCMD_FLUSH, "QUIT\r\n");
 
        /* Wait a short time for response, and discard it */
-       smtp_read_response(&sx.inblock, responsebuffer, sizeof(responsebuffer),
+       smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer),
          '2', 1);
        }
 
       if (sx.inblock.sock >= 0)
        {
 #ifdef SUPPORT_TLS
-       tls_close(FALSE, TRUE);
+       tls_close(FALSE, TLS_SHUTDOWN_NOWAIT);
 #endif
-       HDEBUG(D_transport|D_acl|D_v) debug_printf("  SMTP(close)>>\n");
+       HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
        (void)close(sx.inblock.sock);
        sx.inblock.sock = sx.outblock.sock = -1;
 #ifndef DISABLE_EVENT
@@ -1099,6 +1124,9 @@ no_conn:
        }
       }
 
+    if (!done || yield != OK)
+      addr->message = string_sprintf("%s [%s] : %s", host->name, host->address,
+                                   addr->message);
     }    /* Loop through all hosts, while !done */
   }
 
@@ -1164,7 +1192,7 @@ int rc;
 get rewritten. */
 
 addr2 = *addr;
-HDEBUG(D_acl) debug_printf("----------- %s cutthrough setup ------------\n",
+HDEBUG(D_acl) debug_printf_indent("----------- %s cutthrough setup ------------\n",
   rcpt_count > 1 ? "more" : "start");
 rc = verify_address(&addr2, NULL,
        vopt_is_recipient | vopt_callout_recipsender | vopt_callout_no_cache,
@@ -1172,7 +1200,7 @@ rc = verify_address(&addr2, NULL,
        NULL, NULL, NULL);
 addr->message = addr2.message;
 addr->user_message = addr2.user_message;
-HDEBUG(D_acl) debug_printf("----------- end cutthrough setup ------------\n");
+HDEBUG(D_acl) debug_printf_indent("----------- end cutthrough setup ------------\n");
 return rc;
 }
 
@@ -1187,7 +1215,7 @@ if(cutthrough.fd < 0)
 
 if(
 #ifdef SUPPORT_TLS
-   (tls_out.active == cutthrough.fd) ? tls_write(FALSE, ctblock.buffer, n) :
+   tls_out.active == cutthrough.fd ? tls_write(FALSE, ctblock.buffer, n, FALSE) :
 #endif
    send(cutthrough.fd, ctblock.buffer, n, 0) > 0
   )
@@ -1197,7 +1225,7 @@ if(
   return TRUE;
 }
 
-HDEBUG(D_transport|D_acl) debug_printf("cutthrough_send failed: %s\n", strerror(errno));
+HDEBUG(D_transport|D_acl) debug_printf_indent("cutthrough_send failed: %s\n", strerror(errno));
 return FALSE;
 }
 
@@ -1218,20 +1246,27 @@ return TRUE;
 }
 
 /* Buffered output of counted data block.   Return boolean success */
-BOOL
+static BOOL
 cutthrough_puts(uschar * cp, int n)
 {
 if (cutthrough.fd < 0)       return TRUE;
 if (_cutthrough_puts(cp, n)) return TRUE;
-cancel_cutthrough_connection("transmit failed");
+cancel_cutthrough_connection(TRUE, US"transmit failed");
 return FALSE;
 }
 
+void
+cutthrough_data_puts(uschar * cp, int n)
+{
+if (cutthrough.delivery) (void) cutthrough_puts(cp, n);
+return;
+}
+
 
 static BOOL
 _cutthrough_flush_send(void)
 {
-int n= ctblock.ptr-ctblock.buffer;
+int n = ctblock.ptr - ctblock.buffer;
 
 if(n>0)
   if(!cutthrough_send(n))
@@ -1245,21 +1280,28 @@ BOOL
 cutthrough_flush_send(void)
 {
 if (_cutthrough_flush_send()) return TRUE;
-cancel_cutthrough_connection("transmit failed");
+cancel_cutthrough_connection(TRUE, US"transmit failed");
 return FALSE;
 }
 
 
-BOOL
+static BOOL
 cutthrough_put_nl(void)
 {
 return cutthrough_puts(US"\r\n", 2);
 }
 
 
+void
+cutthrough_data_put_nl(void)
+{
+cutthrough_data_puts(US"\r\n", 2);
+}
+
+
 /* Get and check response from cutthrough target */
 static uschar
-cutthrough_response(char expect, uschar ** copy, int timeout)
+cutthrough_response(int fd, char expect, uschar ** copy, int timeout)
 {
 smtp_inblock inblock;
 uschar inbuffer[4096];
@@ -1269,12 +1311,12 @@ inblock.buffer = inbuffer;
 inblock.buffersize = sizeof(inbuffer);
 inblock.ptr = inbuffer;
 inblock.ptrend = inbuffer;
-inblock.sock = cutthrough.fd;
+inblock.sock = fd;
 /* this relies on (inblock.sock == tls_out.active) */
 if(!smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), expect, timeout))
-  cancel_cutthrough_connection("target timeout on read");
+  cancel_cutthrough_connection(TRUE, US"target timeout on read");
 
-if(copy != NULL)
+if(copy)
   {
   uschar * cp;
   *copy = cp = string_copy(responsebuffer);
@@ -1292,21 +1334,21 @@ return responsebuffer[0];
 BOOL
 cutthrough_predata(void)
 {
-if(cutthrough.fd < 0)
+if(cutthrough.fd < 0 || cutthrough.callout_hold_only)
   return FALSE;
 
-HDEBUG(D_transport|D_acl|D_v) debug_printf("  SMTP>> DATA\n");
+HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP>> DATA\n");
 cutthrough_puts(US"DATA\r\n", 6);
 cutthrough_flush_send();
 
 /* Assume nothing buffered.  If it was it gets ignored. */
-return cutthrough_response('3', NULL, CUTTHROUGH_DATA_TIMEOUT) == '3';
+return cutthrough_response(cutthrough.fd, '3', NULL, CUTTHROUGH_DATA_TIMEOUT) == '3';
 }
 
 
-/* fd and tctx args only to match write_chunk() */
+/* tctx arg only to match write_chunk() */
 static BOOL
-cutthrough_write_chunk(int fd, transport_ctx * tctx, uschar * s, int len)
+cutthrough_write_chunk(transport_ctx * tctx, uschar * s, int len)
 {
 uschar * s2;
 while(s && (s2 = Ustrchr(s, '\n')))
@@ -1327,61 +1369,76 @@ cutthrough_headers_send(void)
 {
 transport_ctx tctx;
 
-if(cutthrough.fd < 0)
+if(cutthrough.fd < 0 || cutthrough.callout_hold_only)
   return FALSE;
 
 /* We share a routine with the mainline transport to handle header add/remove/rewrites,
    but having a separate buffered-output function (for now)
 */
-HDEBUG(D_acl) debug_printf("----------- start cutthrough headers send -----------\n");
+HDEBUG(D_acl) debug_printf_indent("----------- start cutthrough headers send -----------\n");
 
+tctx.u.fd = cutthrough.fd;
 tctx.tblock = cutthrough.addr.transport;
 tctx.addr = &cutthrough.addr;
 tctx.check_string = US".";
 tctx.escape_string = US"..";
+/*XXX check under spool_files_wireformat.  Might be irrelevant */
 tctx.options = topt_use_crlf;
 
-if (!transport_headers_send(cutthrough.fd, &tctx, &cutthrough_write_chunk))
+if (!transport_headers_send(&tctx, &cutthrough_write_chunk))
   return FALSE;
 
-HDEBUG(D_acl) debug_printf("----------- done cutthrough headers send ------------\n");
+HDEBUG(D_acl) debug_printf_indent("----------- done cutthrough headers send ------------\n");
 return TRUE;
 }
 
 
 static void
-close_cutthrough_connection(const char * why)
+close_cutthrough_connection(const uschar * why)
 {
-if(cutthrough.fd >= 0)
+int fd = cutthrough.fd;
+if(fd >= 0)
   {
   /* We could be sending this after a bunch of data, but that is ok as
      the only way to cancel the transfer in dataphase is to drop the tcp
      conn before the final dot.
   */
   ctblock.ptr = ctbuffer;
-  HDEBUG(D_transport|D_acl|D_v) debug_printf("  SMTP>> QUIT\n");
+  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();
+  cutthrough.fd = -1;                  /* avoid recursion via read timeout */
+  cutthrough.nrcpt = 0;                        /* permit re-cutthrough on subsequent message */
 
   /* Wait a short time for response, and discard it */
-  cutthrough_response('2', NULL, 1);
-
-  #ifdef SUPPORT_TLS
-  tls_close(FALSE, TRUE);
-  #endif
-  HDEBUG(D_transport|D_acl|D_v) debug_printf("  SMTP(close)>>\n");
-  (void)close(cutthrough.fd);
-  cutthrough.fd = -1;
-  HDEBUG(D_acl) debug_printf("----------- cutthrough shutdown (%s) ------------\n", why);
+  cutthrough_response(fd, '2', NULL, 1);
+
+#ifdef SUPPORT_TLS
+  tls_close(FALSE, TLS_SHUTDOWN_NOWAIT);
+#endif
+  HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
+  (void)close(fd);
+  HDEBUG(D_acl) debug_printf_indent("----------- cutthrough shutdown (%s) ------------\n", why);
   }
 ctblock.ptr = ctbuffer;
 }
 
 void
-cancel_cutthrough_connection(const char * why)
+cancel_cutthrough_connection(BOOL close_noncutthrough_verifies, const uschar * why)
 {
-close_cutthrough_connection(why);
-cutthrough.delivery = FALSE;
+if (cutthrough.delivery || close_noncutthrough_verifies)
+  close_cutthrough_connection(why);
+cutthrough.delivery = cutthrough.callout_hold_only = FALSE;
+}
+
+
+void
+release_cutthrough_connection(const uschar * why)
+{
+if (cutthrough.fd < 0) return;
+HDEBUG(D_acl) debug_printf_indent("release cutthrough conn: %s\n", why);
+cutthrough.fd = -1;
+cutthrough.delivery = cutthrough.callout_hold_only = FALSE;
 }
 
 
@@ -1397,7 +1454,7 @@ cutthrough_finaldot(void)
 {
 uschar res;
 address_item * addr;
-HDEBUG(D_transport|D_acl|D_v) debug_printf("  SMTP>> .\n");
+HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP>> .\n");
 
 /* Assume data finshed with new-line */
 if(  !cutthrough_puts(US".", 1)
@@ -1406,7 +1463,7 @@ if(  !cutthrough_puts(US".", 1)
   )
   return cutthrough.addr.message;
 
-res = cutthrough_response('2', &cutthrough.addr.message, CUTTHROUGH_DATA_TIMEOUT);
+res = cutthrough_response(cutthrough.fd, '2', &cutthrough.addr.message, CUTTHROUGH_DATA_TIMEOUT);
 for (addr = &cutthrough.addr; addr; addr = addr->next)
   {
   addr->message = cutthrough.addr.message;
@@ -1414,7 +1471,7 @@ for (addr = &cutthrough.addr; addr; addr = addr->next)
     {
     case '2':
       delivery_log(LOG_MAIN, addr, (int)'>', NULL);
-      close_cutthrough_connection("delivered");
+      close_cutthrough_connection(US"delivered");
       break;
 
     case '4':
@@ -1499,7 +1556,7 @@ va_list ap;
 
 va_start(ap, format);
 if (smtp_out && (f == smtp_out))
-  smtp_vprintf(format, ap);
+  smtp_vprintf(format, FALSE, ap);
 else
   vfprintf(f, format, ap);
 va_end(ap);
@@ -1602,9 +1659,9 @@ else ko_prefix = cr = US"";
 
 if (parse_find_at(address) == NULL)
   {
-  if ((options & vopt_qualify) == 0)
+  if (!(options & vopt_qualify))
     {
-    if (f != NULL)
+    if (f)
       respond_printf(f, "%sA domain is required for \"%s\"%s\n",
         ko_prefix, address, cr);
     *failure_ptr = US"qualify";
@@ -1622,7 +1679,7 @@ DEBUG(D_verify)
 /* Rewrite and report on it. Clear the domain and local part caches - these
 may have been set by domains and local part tests during an ACL. */
 
-if (global_rewrite_rules != NULL)
+if (global_rewrite_rules)
   {
   uschar *old = address;
   address = rewrite_address(address, options & vopt_is_recipient, FALSE,
@@ -1631,21 +1688,21 @@ if (global_rewrite_rules != NULL)
     {
     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 != NULL && !expn) fprintf(f, "Address rewritten as: %s\n", address);
+    if (f && !expn) fprintf(f, "Address rewritten as: %s\n", address);
     }
   }
 
 /* If this is the real sender address, we must update sender_address at
 this point, because it may be referred to in the routers. */
 
-if ((options & (vopt_fake_sender|vopt_is_recipient)) == 0)
+if (!(options & (vopt_fake_sender|vopt_is_recipient)))
   sender_address = address;
 
 /* If the address was rewritten to <> no verification can be done, and we have
 to return OK. This rewriting is permitted only for sender addresses; for other
 addresses, such rewriting fails. */
 
-if (address[0] == 0) return OK;
+if (!address[0]) return OK;
 
 /* Flip the legacy TLS-related variables over to the outbound set in case
 they're used in the context of a transport used by verification. Reset them
@@ -1697,7 +1754,7 @@ while (addr_new)
   if (testflag(addr, af_pfr))
     {
     allok = FALSE;
-    if (f != NULL)
+    if (f)
       {
       BOOL allow;
 
@@ -1708,8 +1765,8 @@ while (addr_new)
         }
       else
         {
-        allow = (addr->address[0] == '|')?
-          testflag(addr, af_allow_pipe) : testflag(addr, af_allow_file);
+        allow = addr->address[0] == '|'
+          testflag(addr, af_allow_pipe) : testflag(addr, af_allow_file);
         fprintf(f, "%s -> %s", addr->parent->address, addr->address);
         }
 
@@ -1761,16 +1818,16 @@ while (addr_new)
       transport. */
 
       transport_feedback tf = {
-        NULL,                       /* interface (=> any) */
-        US"smtp",                   /* port */
-        US"smtp",                   /* protocol */
-        NULL,                       /* hosts */
-        US"$smtp_active_hostname",  /* helo_data */
-        FALSE,                      /* hosts_override */
-        FALSE,                      /* hosts_randomize */
-        FALSE,                      /* gethostbyname */
-        TRUE,                       /* qualify_single */
-        FALSE                       /* search_parents */
+        .interface =           NULL,                       /* interface (=> any) */
+        .port =                        US"smtp",
+        .protocol =            US"smtp",
+        .hosts =               NULL,
+        .helo_data =           US"$smtp_active_hostname",
+        .hosts_override =      FALSE,
+        .hosts_randomize =     FALSE,
+        .gethostbyname =       FALSE,
+        .qualify_single =      TRUE,
+        .search_parents =      FALSE
         };
 
       /* If verification yielded a remote transport, we want to use that
@@ -1817,7 +1874,7 @@ while (addr_new)
             additional host items being inserted into the chain. Hence we must
             save the next host first. */
 
-            flags = HOST_FIND_BY_A;
+            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;
 
@@ -1837,7 +1894,7 @@ while (addr_new)
                  dnssec_domains = &ob->dnssec;
                  }
 
-                (void)host_find_bydns(host, NULL, flags, NULL, NULL, NULL,
+                (void) host_find_bydns(host, NULL, flags, NULL, NULL, NULL,
                  dnssec_domains, NULL, NULL);
                }
               }
@@ -1913,7 +1970,7 @@ while (addr_new)
         }
       respond_printf(f, "%s\n", cr);
       }
-    cancel_cutthrough_connection("routing hard fail");
+    cancel_cutthrough_connection(TRUE, US"routing hard fail");
 
     if (!full_info)
       {
@@ -1952,7 +2009,7 @@ while (addr_new)
         }
       respond_printf(f, "%s\n", cr);
       }
-    cancel_cutthrough_connection("routing soft fail");
+    cancel_cutthrough_connection(TRUE, US"routing soft fail");
 
     if (!full_info)
       {
@@ -2025,7 +2082,7 @@ while (addr_new)
       /* If stopped because more than one new address, cannot cutthrough */
 
       if (addr_new && addr_new->next)
-       cancel_cutthrough_connection("multiple addresses from routing");
+       cancel_cutthrough_connection(TRUE, US"multiple addresses from routing");
 
       yield = OK;
       goto out;
@@ -2137,7 +2194,7 @@ return yield;
 *************************************************/
 
 /* This function checks those header lines that contain addresses, and verifies
-that all the addresses therein are syntactially correct.
+that all the addresses therein are 5322-syntactially correct.
 
 Arguments:
   msgptr     where to put an error message
@@ -2153,7 +2210,7 @@ header_line *h;
 uschar *colon, *s;
 int yield = OK;
 
-for (h = header_list; h != NULL && yield == OK; h = h->next)
+for (h = header_list; h && yield == OK; h = h->next)
   {
   if (h->type != htype_from &&
       h->type != htype_reply_to &&
@@ -2172,7 +2229,7 @@ for (h = header_list; h != NULL && yield == OK; h = h->next)
 
   parse_allow_group = TRUE;
 
-  while (*s != 0)
+  while (*s)
     {
     uschar *ss = parse_find_address_end(s, FALSE);
     uschar *recipient, *errmess;
@@ -2189,7 +2246,7 @@ for (h = header_list; h != NULL && yield == OK; h = h->next)
     /* Permit an unqualified address only if the message is local, or if the
     sending host is configured to be permitted to send them. */
 
-    if (recipient != NULL && domain == 0)
+    if (recipient && !domain)
       {
       if (h->type == htype_from || h->type == htype_sender)
         {
@@ -2205,7 +2262,7 @@ for (h = header_list; h != NULL && yield == OK; h = h->next)
     /* It's an error if no address could be extracted, except for the special
     case of an empty address. */
 
-    if (recipient == NULL && Ustrcmp(errmess, "empty address") != 0)
+    if (!recipient && Ustrcmp(errmess, "empty address") != 0)
       {
       uschar *verb = US"is";
       uschar *t = ss;
@@ -2235,7 +2292,7 @@ for (h = header_list; h != NULL && yield == OK; h = h->next)
       /* deconst cast ok as we're passing a non-const to string_printing() */
       *msgptr = US string_printing(
         string_sprintf("%s: failing address in \"%.*s:\" header %s: %.*s",
-          errmess, tt - h->text, h->text, verb, len, s));
+          errmess, (int)(tt - h->text), h->text, verb, len, s));
 
       yield = FAIL;
       break;          /* Out of address loop */
@@ -2243,7 +2300,7 @@ for (h = header_list; h != NULL && yield == OK; h = h->next)
 
     /* Advance to the next address */
 
-    s = ss + (terminator? 1:0);
+    s = ss + (terminator ? 1 : 0);
     while (isspace(*s)) s++;
     }   /* Next address */
 
@@ -2275,18 +2332,16 @@ verify_check_header_names_ascii(uschar **msgptr)
 header_line *h;
 uschar *colon, *s;
 
-for (h = header_list; h != NULL; h = h->next)
+for (h = header_list; h; h = h->next)
   {
-   colon = Ustrchr(h->text, ':');
-   for(s = h->text; s < colon; s++)
-     {
-        if ((*s < 33) || (*s > 126))
-        {
-                *msgptr = string_sprintf("Invalid character in header \"%.*s\" found",
-                                         colon - h->text, h->text);
-                return FAIL;
-        }
-     }
+  colon = Ustrchr(h->text, ':');
+  for(s = h->text; s < colon; s++)
+    if ((*s < 33) || (*s > 126))
+      {
+      *msgptr = string_sprintf("Invalid character in header \"%.*s\" found",
+                            colon - h->text, h->text);
+      return FAIL;
+      }
   }
 return OK;
 }
@@ -2538,7 +2593,7 @@ for (i = 0; i < 3 && !done; i++)
           while (ss > s && isspace(ss[-1])) ss--;
           *log_msgptr = string_sprintf("syntax error in '%.*s' header when "
             "scanning for sender: %s in \"%.*s\"",
-            endname - h->text, h->text, *log_msgptr, ss - s, s);
+            (int)(endname - h->text), h->text, *log_msgptr, (int)(ss - s), s);
           yield = FAIL;
           done = TRUE;
           break;
@@ -2566,11 +2621,9 @@ for (i = 0; i < 3 && !done; i++)
         {
         *verrno = vaddr->basic_errno;
         if (smtp_return_error_details)
-          {
           *user_msgptr = string_sprintf("Rejected after DATA: "
             "could not verify \"%.*s\" header address\n%s: %s",
-            endname - h->text, h->text, vaddr->address, vaddr->message);
-          }
+            (int)(endname - h->text), h->text, vaddr->address, vaddr->message);
         }
 
       /* Success or defer */
@@ -2631,6 +2684,7 @@ verify_get_ident(int port)
 int sock, host_af, qlen;
 int received_sender_port, received_interface_port, n;
 uschar *p;
+blob early_data;
 uschar buffer[2048];
 
 /* Default is no ident. Check whether we want to do an ident check for this
@@ -2656,8 +2710,15 @@ if (ip_bind(sock, host_af, interface_address, 0) < 0)
   goto END_OFF;
   }
 
+/* Construct and send the query. */
+
+qlen = snprintf(CS buffer, sizeof(buffer), "%d , %d\r\n",
+  sender_host_port, interface_port);
+early_data.data = buffer;
+early_data.len = qlen;
+
 if (ip_connect(sock, host_af, sender_host_address, port,
-               rfc1413_query_timeout, TRUE) < 0)
+               rfc1413_query_timeout, &early_data) < 0)
   {
   if (errno == ETIMEDOUT && LOGGING(ident_timeout))
     log_write(0, LOG_MAIN, "ident connection to %s timed out",
@@ -2668,16 +2729,6 @@ if (ip_connect(sock, host_af, sender_host_address, port,
   goto END_OFF;
   }
 
-/* Construct and send the query. */
-
-sprintf(CS buffer, "%d , %d\r\n", sender_host_port, interface_port);
-qlen = Ustrlen(buffer);
-if (send(sock, buffer, qlen, 0) < 0)
-  {
-  DEBUG(D_ident) debug_printf("ident send failed: %s\n", strerror(errno));
-  goto END_OFF;
-  }
-
 /* Read a response line. We put it into the rest of the buffer, using several
 recv() calls if necessary. */
 
@@ -3050,7 +3101,7 @@ if (isquery)
 /* Not a query-style lookup; must ensure the host name is present, and then we
 do a check on the name and all its aliases. */
 
-if (sender_host_name == NULL)
+if (!sender_host_name)
   {
   HDEBUG(D_host_lookup)
     debug_printf("sender host name required, to match against %s\n", ss);
@@ -3065,8 +3116,7 @@ if (sender_host_name == NULL)
 
 /* Match on the sender host name, using the general matching function */
 
-switch(match_check_string(sender_host_name, ss, -1, TRUE, TRUE, TRUE,
-       valueptr))
+switch(match_check_string(sender_host_name, ss, -1, TRUE, TRUE, TRUE, valueptr))
   {
   case OK:    return OK;
   case DEFER: return DEFER;
@@ -3075,14 +3125,12 @@ switch(match_check_string(sender_host_name, ss, -1, TRUE, TRUE, TRUE,
 /* If there are aliases, try matching on them. */
 
 aliases = sender_host_aliases;
-while (*aliases != NULL)
-  {
+while (*aliases)
   switch(match_check_string(*aliases++, ss, -1, TRUE, TRUE, TRUE, valueptr))
     {
     case OK:    return OK;
     case DEFER: return DEFER;
     }
-  }
 return FAIL;
 }
 
@@ -3128,18 +3176,16 @@ verify_check_this_host(const uschar **listptr, unsigned int *cache_bits,
 int rc;
 unsigned int *local_cache_bits = cache_bits;
 const uschar *save_host_address = deliver_host_address;
-check_host_block cb;
-cb.host_name = host_name;
-cb.host_address = host_address;
+check_host_block cb = { .host_name = host_name, .host_address = host_address };
 
-if (valueptr != NULL) *valueptr = NULL;
+if (valueptr) *valueptr = NULL;
 
 /* If the host address starts off ::ffff: it is an IPv6 address in
 IPv4-compatible mode. Find the IPv4 part for checking against IPv4
 addresses. */
 
-cb.host_ipv4 = (Ustrncmp(host_address, "::ffff:", 7) == 0)?
-  host_address + 7 : host_address;
+cb.host_ipv4 = Ustrncmp(host_address, "::ffff:", 7) == 0
+  host_address + 7 : host_address;
 
 /* During the running of the check, put the IP address into $host_address. In
 the case of calls from the smtp transport, it will already be there. However,
@@ -3383,25 +3429,23 @@ else
     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 != NULL) da = da->next;
-          addrp = &(da->next);
+          while (da->next) da = da->next;
+          addrp = &da->next;
          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 == NULL) cb->rc = DNS_NODATA;
+    if (!cb->rhs) cb->rc = DNS_NODATA;
     }
 
   cb->expiry = time(NULL)+ttl;
@@ -3423,7 +3467,7 @@ if (cb->rc == DNS_SUCCEED)
   records. For A6 records (currently not expected to be used) there may be
   multiple addresses from a single record. */
 
-  for (da = cb->rhs->next; da != NULL; da = da->next)
+  for (da = cb->rhs->next; da; da = da->next)
     addlist = string_sprintf("%s, %s", addlist, da->address);
 
   HDEBUG(D_dnsbl) debug_printf("DNS lookup for %s succeeded (yielding %s)\n",
@@ -3432,9 +3476,9 @@ if (cb->rc == DNS_SUCCEED)
   /* Address list check; this can be either for equality, or via a bitmask.
   In the latter case, all the bits must match. */
 
-  if (iplist != NULL)
+  if (iplist)
     {
-    for (da = cb->rhs; da != NULL; da = da->next)
+    for (da = cb->rhs; da; da = da->next)
       {
       int ipsep = ',';
       uschar ip[46];
@@ -3444,12 +3488,11 @@ if (cb->rc == DNS_SUCCEED)
       /* Handle exact matching */
 
       if (!bitmask)
-        {
-        while ((res = string_nextinlist(&ptr, &ipsep, ip, sizeof(ip))) != NULL)
-          {
-          if (Ustrcmp(CS da->address, ip) == 0) break;
-          }
-        }
+       {
+        while ((res = string_nextinlist(&ptr, &ipsep, ip, sizeof(ip))))
+          if (Ustrcmp(CS da->address, ip) == 0)
+           break;
+       }
 
       /* Handle bitmask matching */
 
@@ -3469,7 +3512,7 @@ if (cb->rc == DNS_SUCCEED)
 
         /* Scan the returned addresses, skipping any that are IPv6 */
 
-        while ((res = string_nextinlist(&ptr, &ipsep, ip, sizeof(ip))) != NULL)
+        while ((res = string_nextinlist(&ptr, &ipsep, ip, sizeof(ip))))
           {
           if (host_aton(ip, address) != 1) continue;
           if ((address[0] & mask) == address[0]) break;
@@ -3502,17 +3545,13 @@ if (cb->rc == DNS_SUCCEED)
         switch(match_type)
           {
           case 0:
-          res = US"was no match";
-          break;
+           res = US"was no match"; break;
           case MT_NOT:
-          res = US"was an exclude match";
-          break;
+           res = US"was an exclude match"; break;
           case MT_ALL:
-          res = US"was an IP address that did not match";
-          break;
+           res = US"was an IP address that did not match"; break;
           case MT_NOT|MT_ALL:
-          res = US"were no IP addresses that did not match";
-          break;
+           res = US"were no IP addresses that did not match"; break;
           }
         debug_printf("=> but we are not accepting this block class because\n");
         debug_printf("=> there %s for %s%c%s\n",
@@ -3544,15 +3583,15 @@ if (cb->rc == DNS_SUCCEED)
       {
       dns_record *rr;
       for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
-           rr != NULL;
+           rr;
            rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
         if (rr->type == T_TXT) break;
-      if (rr != NULL)
+      if (rr)
         {
         int len = (rr->data)[0];
         if (len > 511) len = 127;
         store_pool = POOL_PERM;
-        cb->text = string_sprintf("%.*s", len, (const uschar *)(rr->data+1));
+        cb->text = string_sprintf("%.*s", len, CUS (rr->data+1));
         store_pool = old_pool;
         }
       }