Add new errors mail_4xx, data_4xx, lost_connection, tls_required.
[exim.git] / src / src / transports / smtp.c
index be8b15029696e73d34ce1200ecc8aedb0ec65f2c..e223bb183e418530dd399cb1d838330219dde102 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/transports/smtp.c,v 1.7 2005/03/08 15:32:02 tom Exp $ */
+/* $Cambridge: exim/src/src/transports/smtp.c,v 1.25 2006/03/09 15:10:16 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* Copyright (c) University of Cambridge 1995 - 2006 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -25,6 +25,8 @@ optionlist smtp_transport_options[] = {
       (void *)offsetof(smtp_transport_options_block, allow_localhost) },
   { "authenticated_sender", opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, authenticated_sender) },
+  { "authenticated_sender_force", opt_bool,
+      (void *)offsetof(smtp_transport_options_block, authenticated_sender_force) },
   { "command_timeout",      opt_time,
       (void *)offsetof(smtp_transport_options_block, command_timeout) },
   { "connect_timeout",      opt_time,
@@ -93,6 +95,8 @@ optionlist smtp_transport_options[] = {
       (void *)offsetof(smtp_transport_options_block, interface) },
   { "keepalive",            opt_bool,
       (void *)offsetof(smtp_transport_options_block, keepalive) },
+  { "lmtp_ignore_quota",    opt_bool,
+      (void *)offsetof(smtp_transport_options_block, lmtp_ignore_quota) },
   { "max_rcpt",             opt_int | opt_public,
       (void *)offsetof(transport_instance, max_addresses) },
   { "multi_domain",         opt_bool | opt_public,
@@ -156,6 +160,7 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   5,                   /* hosts_max_try */
   50,                  /* hosts_max_try_hardlimit */
   FALSE,               /* allow_localhost */
+  FALSE,               /* authenticated_sender_force */
   FALSE,               /* gethostbyname */
   TRUE,                /* dns_qualify_single */
   FALSE,               /* dns_search_parents */
@@ -163,6 +168,7 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   FALSE,               /* hosts_override */
   FALSE,               /* hosts_randomize */
   TRUE,                /* keepalive */
+  FALSE,               /* lmtp_ignore_quota */
   TRUE                 /* retry_include_ip_address */
   #ifdef SUPPORT_TLS
  ,NULL,                /* tls_certificate */
@@ -204,6 +210,8 @@ Arguments:
   tblock    pointer to the transport instance block
   addrlist  list of addresses about to be transported
   tf        if not NULL, pointer to block in which to return options
+  uid       the uid that will be set (not used)
+  gid       the gid that will be set (not used)
   errmsg    place for error message (not used)
 
 Returns:  OK always (FAIL, DEFER not used)
@@ -211,12 +219,14 @@ Returns:  OK always (FAIL, DEFER not used)
 
 static int
 smtp_transport_setup(transport_instance *tblock, address_item *addrlist,
-  transport_feedback *tf, uschar **errmsg)
+  transport_feedback *tf, uid_t uid, gid_t gid, uschar **errmsg)
 {
 smtp_transport_options_block *ob =
   (smtp_transport_options_block *)(tblock->options_block);
 
 errmsg = errmsg;    /* Keep picky compilers happy */
+uid = uid;
+gid = gid;
 
 /* Pass back options if required. This interface is getting very messy. */
 
@@ -313,10 +323,11 @@ host_build_hostlist(&(ob->fallback_hostlist), ob->fallback_hosts, FALSE);
 status means that an address is not currently being processed.
 
 Arguments:
-  addrlist     points to a chain of addresses
-  errno_value  to put in each address's errno field
-  msg          to put in each address's message field
-  rc           to put in each address's transport_return field
+  addrlist       points to a chain of addresses
+  errno_value    to put in each address's errno field
+  msg            to put in each address's message field
+  rc             to put in each address's transport_return field
+  pass_message   if TRUE, set the "pass message" flag in the address
 
 If errno_value has the special value ERRNO_CONNECTTIMEOUT, ETIMEDOUT is put in
 the errno field, and RTEF_CTOUT is ORed into the more_errno field, to indicate
@@ -325,8 +336,9 @@ this particular type of timeout.
 Returns:       nothing
 */
 
-static
-void set_errno(address_item *addrlist, int errno_value, uschar *msg, int rc)
+static void
+set_errno(address_item *addrlist, int errno_value, uschar *msg, int rc,
+  BOOL pass_message)
 {
 address_item *addr;
 int orvalue = 0;
@@ -340,7 +352,11 @@ for (addr = addrlist; addr != NULL; addr = addr->next)
   if (addr->transport_return < PENDING) continue;
   addr->basic_errno = errno_value;
   addr->more_errno |= orvalue;
-  if (msg != NULL) addr->message = msg;
+  if (msg != NULL)
+    {
+    addr->message = msg;
+    if (pass_message) setflag(addr, af_pass_message);
+    }
   addr->transport_return = rc;
   }
 }
@@ -358,18 +374,19 @@ the yield variable. If no response was actually read, a suitable digit is
 chosen.
 
 Arguments:
-  host         the current host, to get its name for messages
-  errno_value  pointer to the errno value
-  more_errno   from the top address for use with ERRNO_FILTER_FAIL
-  buffer       the SMTP response buffer
-  yield        where to put a one-digit SMTP response code
-  message      where to put an errror message
-
-Returns:       TRUE if an SMTP "QUIT" command should be sent, else FALSE
+  host           the current host, to get its name for messages
+  errno_value    pointer to the errno value
+  more_errno     from the top address for use with ERRNO_FILTER_FAIL
+  buffer         the SMTP response buffer
+  yield          where to put a one-digit SMTP response code
+  message        where to put an errror message
+  pass_message   set TRUE if message is an SMTP response
+
+Returns:         TRUE if an SMTP "QUIT" command should be sent, else FALSE
 */
 
 static BOOL check_response(host_item *host, int *errno_value, int more_errno,
-  uschar *buffer, int *yield, uschar **message)
+  uschar *buffer, int *yield, uschar **message, BOOL *pass_message)
 {
 uschar *pl = US"";
 
@@ -444,18 +461,20 @@ if (*errno_value == ERRNO_WRITEINCOMPLETE)
 if (buffer[0] != 0)
   {
   uschar *s = string_printing(buffer);
-  *message = US string_sprintf("SMTP error from remote mailer after %s%s: "
+  *message = US string_sprintf("SMTP error from remote mail server after %s%s: "
     "host %s [%s]: %s", pl, smtp_command, host->name, host->address, s);
+  *pass_message = TRUE;
   *yield = buffer[0];
   return TRUE;
   }
 
 /* No data was read. If there is no errno, this must be the EOF (i.e.
-connection closed) case, which causes deferral. Otherwise, put the host's
-identity in the message, leaving the errno value to be interpreted as well. In
-all cases, we have to assume the connection is now dead. */
+connection closed) case, which causes deferral. An explicit connection reset
+error has the same effect. Otherwise, put the host's identity in the message,
+leaving the errno value to be interpreted as well. In all cases, we have to
+assume the connection is now dead. */
 
-if (*errno_value == 0)
+if (*errno_value == 0 || *errno_value == ECONNRESET)
   {
   *errno_value = ERRNO_SMTPCLOSED;
   *message = US string_sprintf("Remote host %s [%s] closed connection "
@@ -494,14 +513,14 @@ if (addr->message != NULL)
   }
 else
   {
-  log_write(0, LOG_MAIN, "%s [%s]: %s",
-    host->name,
-    host->address,
-    strerror(addr->basic_errno));
-  deliver_msglog("%s %s [%s]: %s\n",
-    tod_stamp(tod_log),
-    host->name,
-    host->address,
+  uschar *msg =
+    ((log_extra_selector & LX_outgoing_port) != 0)?
+    string_sprintf("%s [%s]:%d", host->name, host->address,
+      (host->port == PORT_NONE)? 25 : host->port)
+    :
+    string_sprintf("%s [%s]", host->name, host->address);
+  log_write(0, LOG_MAIN, "%s %s", msg, strerror(addr->basic_errno));
+  deliver_msglog("%s %s %s\n", tod_stamp(tod_log), msg,
     strerror(addr->basic_errno));
   }
 }
@@ -579,6 +598,12 @@ if (pending_MAIL)
     if (errno == 0 && buffer[0] != 0)
       {
       uschar flushbuffer[4096];
+      int save_errno = 0;
+      if (buffer[0] == '4')
+        {
+        save_errno = ERRNO_MAIL4XX;
+        addr->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+        }
       while (count-- > 0)
         {
         if (!smtp_read_response(inblock, flushbuffer, sizeof(flushbuffer),
@@ -586,6 +611,7 @@ if (pending_MAIL)
             && (errno != 0 || flushbuffer[0] == 0))
           break;
         }
+      errno = save_errno;
       }
     return -3;
     }
@@ -623,7 +649,7 @@ while (count-- > 0)
     uschar *message = string_sprintf("SMTP timeout while connected to %s [%s] "
       "after RCPT TO:<%s>", host->name, host->address,
       transport_rcpt_address(addr, include_affixes));
-    set_errno(addrlist, save_errno, message, DEFER);
+    set_errno(addrlist, save_errno, message, DEFER, FALSE);
     retry_add_item(addr, addr->address_retry_key, 0);
     host->update_waiting = FALSE;
     return -1;
@@ -646,9 +672,10 @@ while (count-- > 0)
   else
     {
     addr->message =
-      string_sprintf("SMTP error from remote mailer after RCPT TO:<%s>: "
+      string_sprintf("SMTP error from remote mail server after RCPT TO:<%s>: "
         "host %s [%s]: %s", transport_rcpt_address(addr, include_affixes),
         host->name, host->address, string_printing(buffer));
+    setflag(addr, af_pass_message);
     deliver_msglog("%s %s\n", tod_stamp(tod_log), addr->message);
 
     /* The response was 5xx */
@@ -663,11 +690,9 @@ while (count-- > 0)
 
     else
       {
-      int bincode = (buffer[1] - '0')*10 + buffer[2] - '0';
-
       addr->transport_return = DEFER;
       addr->basic_errno = ERRNO_RCPT4XX;
-      addr->more_errno |= bincode << 8;
+      addr->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
 
       /* Log temporary errors if there are more hosts to be tried. */
 
@@ -699,8 +724,17 @@ if (pending_DATA != 0 &&
   {
   int code;
   uschar *msg;
-  if (pending_DATA > 0 || (yield & 1) != 0) return -3;
-  (void)check_response(host, &errno, 0, buffer, &code, &msg);
+  BOOL pass_message;
+  if (pending_DATA > 0 || (yield & 1) != 0)
+    {
+    if (errno == 0 && buffer[0] == '4')
+      {
+      errno = ERRNO_DATA4XX;
+      addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+      }
+    return -3;
+    }
+  (void)check_response(host, &errno, 0, buffer, &code, &msg, &pass_message);
   DEBUG(D_transport) debug_printf("%s\nerror for DATA ignored: pipelining "
     "is in use and there were no good recipients\n", msg);
   }
@@ -736,7 +770,7 @@ Arguments:
                   failed by one of them.
   host            host to deliver to
   host_af         AF_INET or AF_INET6
-  port            TCP/IP port to use, in host byte order
+  port            default TCP/IP port to use, in host byte order
   interface       interface to bind to, or NULL
   tblock          transport instance block
   copy_host       TRUE if host set in addr->host_used must be copied, because
@@ -782,9 +816,11 @@ BOOL setting_up = TRUE;
 BOOL completed_address = FALSE;
 BOOL esmtp = TRUE;
 BOOL pending_MAIL;
+BOOL pass_message = FALSE;
 smtp_inblock inblock;
 smtp_outblock outblock;
 int max_rcpt = tblock->max_addresses;
+uschar *igquotstr = US"";
 uschar *local_authenticated_sender = authenticated_sender;
 uschar *helo_data;
 uschar *message = NULL;
@@ -822,7 +858,7 @@ if (helo_data == NULL)
   {
   uschar *message = string_sprintf("failed to expand helo_data: %s",
     expand_string_message);
-  set_errno(addrlist, 0, message, DEFER);
+  set_errno(addrlist, 0, message, DEFER, FALSE);
   return ERROR;
   }
 
@@ -842,7 +878,7 @@ if (ob->authenticated_sender != NULL)
       {
       uschar *message = string_sprintf("failed to expand "
         "authenticated_sender: %s", expand_string_message);
-      set_errno(addrlist, 0, message, DEFER);
+      set_errno(addrlist, 0, message, DEFER, FALSE);
       return ERROR;
       }
     }
@@ -861,7 +897,7 @@ if (continue_hostname == NULL)
   if (inblock.sock < 0)
     {
     set_errno(addrlist, (errno == ETIMEDOUT)? ERRNO_CONNECTTIMEOUT : errno,
-      NULL, DEFER);
+      NULL, DEFER, FALSE);
     return DEFER;
     }
 
@@ -936,6 +972,13 @@ goto SEND_QUIT;
       ob->command_timeout)) goto RESPONSE_FAILED;
     }
 
+  /* Set IGNOREQUOTA if the response to LHLO specifies support and the
+  lmtp_ignore_quota option was set. */
+
+  igquotstr = (lmtp && ob->lmtp_ignore_quota &&
+    pcre_exec(regex_IGNOREQUOTA, NULL, CS buffer, Ustrlen(CS buffer), 0,
+      PCRE_EOPT, NULL, 0) >= 0)? US" IGNOREQUOTA" : US"";
+
   /* Set tls_offered if the response to EHLO specifies support for STARTTLS. */
 
   #ifdef SUPPORT_TLS
@@ -1020,8 +1063,11 @@ if (tls_offered && !suppress_tls &&
 
     for (addr = addrlist; addr != NULL; addr = addr->next)
       {
-      addr->cipher = tls_cipher;
-      addr->peerdn = tls_peerdn;
+      if (addr->transport_return == PENDING_DEFER)
+        {
+        addr->cipher = tls_cipher;
+        addr->peerdn = tls_peerdn;
+        }
       }
     }
   }
@@ -1067,6 +1113,13 @@ if (continue_hostname == NULL
   int require_auth;
   uschar *fail_reason = US"server did not advertise AUTH support";
 
+  /* Set for IGNOREQUOTA if the response to LHLO specifies support and the
+  lmtp_ignore_quota option was set. */
+
+  igquotstr = (lmtp && ob->lmtp_ignore_quota &&
+    pcre_exec(regex_IGNOREQUOTA, NULL, CS buffer, Ustrlen(CS buffer), 0,
+      PCRE_EOPT, NULL, 0) >= 0)? US" IGNOREQUOTA" : US"";
+
   /* If the response to EHLO specified support for the SIZE parameter, note
   this, provided size_addition is non-negative. */
 
@@ -1174,16 +1227,21 @@ if (continue_hostname == NULL
             /* Failure by some other means. In effect, the authenticator
             decided it wasn't prepared to handle this case. Typically this
             is the result of "fail" in an expansion string. Do we need to
-            log anything here? */
+            log anything here? Feb 2006: a message is now put in the buffer
+            if logging is required. */
 
             case CANCELLED:
+            if (*buffer != 0)
+              log_write(0, LOG_MAIN, "%s authenticator cancelled "
+                "authentication H=%s [%s] %s", au->name, host->name,
+                host->address, buffer);
             break;
 
             /* Internal problem, message in buffer. */
 
             case ERROR:
             yield = ERROR;
-            set_errno(addrlist, 0, string_copy(buffer), DEFER);
+            set_errno(addrlist, 0, string_copy(buffer), DEFER, FALSE);
             goto SEND_QUIT;
             }
 
@@ -1199,7 +1257,8 @@ if (continue_hostname == NULL
     {
     yield = DEFER;
     set_errno(addrlist, ERRNO_AUTHFAIL,
-      string_sprintf("authentication required but %s", fail_reason), DEFER);
+      string_sprintf("authentication required but %s", fail_reason), DEFER,
+      FALSE);
     goto SEND_QUIT;
     }
   }
@@ -1225,7 +1284,8 @@ if (tblock->filter_command != NULL)
 
   if (!rc)
     {
-    set_errno(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER);
+    set_errno(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER,
+      FALSE);
     yield = ERROR;
     goto SEND_QUIT;
     }
@@ -1266,7 +1326,8 @@ if (smtp_use_size)
 
 /* Add the authenticated sender address if present */
 
-if (smtp_authenticated && local_authenticated_sender != NULL)
+if ((smtp_authenticated || ob->authenticated_sender_force) &&
+    local_authenticated_sender != NULL)
   {
   string_format(p, sizeof(buffer) - (p-buffer), " AUTH=%s",
     auth_xtextencode(local_authenticated_sender,
@@ -1292,7 +1353,15 @@ switch(rc)
 
   case +1:                /* Block was sent */
   if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
-    ob->command_timeout)) goto RESPONSE_FAILED;
+       ob->command_timeout))
+    {
+    if (errno == 0 && buffer[0] == '4')
+      {
+      errno = ERRNO_MAIL4XX;
+      addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+      }
+    goto RESPONSE_FAILED;
+    }
   pending_MAIL = FALSE;
   break;
   }
@@ -1327,8 +1396,8 @@ for (addr = first_addr;
   yield as OK, because this error can often mean that there is a problem with
   just one address, so we don't want to delay the host. */
 
-  count = smtp_write_command(&outblock, no_flush, "RCPT TO:<%s>\r\n",
-    transport_rcpt_address(addr, tblock->rcpt_include_affixes));
+  count = smtp_write_command(&outblock, no_flush, "RCPT TO:<%s>%s\r\n",
+    transport_rcpt_address(addr, tblock->rcpt_include_affixes), igquotstr);
   if (count < 0) goto SEND_FAILED;
   if (count > 0)
     {
@@ -1365,7 +1434,8 @@ if (mua_wrapper)
     }
   if (badaddr != NULL)
     {
-    set_errno(addrlist, 0, badaddr->message, FAIL);
+    set_errno(addrlist, 0, badaddr->message, FAIL,
+      testflag(badaddr, af_pass_message));
     ok = FALSE;
     }
   }
@@ -1472,8 +1542,16 @@ if (!ok) ok = TRUE; else
   /* For SMTP, we now read a single response that applies to the whole message.
   If it is OK, then all the addresses have been delivered. */
 
-  if (!lmtp) ok = smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
-    ob->final_timeout);
+  if (!lmtp)
+    {
+    ok = smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
+      ob->final_timeout);
+    if (!ok && errno == 0 && buffer[0] == '4')
+      {
+      errno = ERRNO_DATA4XX;
+      addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+      }
+    }
 
   /* For LMTP, we get back a response for every RCPT command that we sent;
   some may be accepted and some rejected. For those that get a response, their
@@ -1525,7 +1603,8 @@ if (!ok) ok = TRUE; else
 
       /* LMTP - if the response fails badly (e.g. timeout), use it for all the
       remaining addresses. Otherwise, it's a return code for just the one
-      address. */
+      address. For temporary errors, add a retry item for the address so that
+      it doesn't get tried again too soon. */
 
       if (lmtp)
         {
@@ -1535,18 +1614,26 @@ if (!ok) ok = TRUE; else
           if (errno != 0 || buffer[0] == 0) goto RESPONSE_FAILED;
           addr->message = string_sprintf("LMTP error after %s: %s",
             big_buffer, string_printing(buffer));
-          addr->transport_return = (buffer[0] == '5')? FAIL : DEFER;
+          setflag(addr, af_pass_message);   /* Allow message to go to user */
+          if (buffer[0] == '5')
+            addr->transport_return = FAIL;
+          else
+            {
+            errno = ERRNO_DATA4XX;
+            addr->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+            addr->transport_return = DEFER;
+            retry_add_item(addr, addr->address_retry_key, 0);
+            }
           continue;
           }
         completed_address = TRUE;   /* NOW we can set this flag */
         }
 
       /* SMTP, or success return from LMTP for this address. Pass back the
-      actual port used. */
+      actual host that was used. */
 
       addr->transport_return = OK;
       addr->more_errno = delivery_time;
-      thost->port = port;
       addr->host_used = thost;
       addr->special_action = flag;
       addr->message = conf;
@@ -1594,7 +1681,7 @@ if (!ok)
   save_errno = errno;
   message = NULL;
   send_quit = check_response(host, &save_errno, addrlist->more_errno,
-    buffer, &code, &message);
+    buffer, &code, &message, &pass_message);
   goto FAILED;
 
   SEND_FAILED:
@@ -1629,69 +1716,92 @@ if (!ok)
     {
     if (code == '5')
       {
-      set_errno(addrlist, save_errno, message, FAIL);
+      set_errno(addrlist, save_errno, message, FAIL, pass_message);
       }
     else
       {
-      set_errno(addrlist, save_errno, message, DEFER);
+      set_errno(addrlist, save_errno, message, DEFER, pass_message);
       yield = DEFER;
       }
     }
 
-  /* If there was an I/O error or timeout or other transportation error,
-  indicated by errno being non-zero, defer all addresses and yield DEFER,
-  except for the case of failed add_headers expansion, or a transport filter
-  failure, when the yield should be ERROR, to stop it trying other hosts.
-
-  However, handle timeouts after MAIL FROM or "." and loss of connection after
+  /* We want to handle timeouts after MAIL or "." and loss of connection after
   "." specially. They can indicate a problem with the sender address or with
-  the contents of the message rather than a real error on the connection.
-  Therefore, treat these cases in the same way as a 4xx response.
-
-  The following condition tests for NOT these special cases. */
+  the contents of the message rather than a real error on the connection. These
+  cases are treated in the same way as a 4xx response. This next bit of code
+  does the classification. */
 
-  else if (save_errno != 0 &&
-           (save_errno != ETIMEDOUT ||
-             (Ustrncmp(smtp_command,"MAIL",4) != 0 &&
-              Ustrncmp(smtp_command,"end ",4) != 0)) &&
-           (save_errno != ERRNO_SMTPCLOSED ||
-              Ustrncmp(smtp_command,"end ",4) != 0))
+  else
     {
-    yield = (save_errno == ERRNO_CHHEADER_FAIL ||
-             save_errno == ERRNO_FILTER_FAIL)? ERROR : DEFER;
-    set_errno(addrlist, save_errno, message, DEFER);
-    }
+    BOOL message_error;
+
+    switch(save_errno)
+      {
+      case 0:
+      case ERRNO_MAIL4XX:
+      case ERRNO_DATA4XX:
+      message_error = TRUE;
+      break;
 
-  /* Otherwise we have a message-specific error response from the remote
-  host. This is one of
-    (a) negative response or timeout after "mail from"
-    (b) negative response after "data"
-    (c) negative response or timeout or dropped connection after "."
-  It won't be a negative response or timeout after "rcpt to", as that is dealt
-  with separately above. The action in all cases is to set an appropriate
-  error code for all the addresses, but to leave yield set to OK because
-  the host itself has not failed. [It might in practice have failed for a
-  timeout after MAIL FROM, or "." but if so, we'll discover that at the next
-  delivery attempt.] For a temporary error, set the message_defer flag, and
-  write to the logs for information if this is not the last host. The error for
-  the last host will be logged as part of the address's log line. */
+      case ETIMEDOUT:
+      message_error = Ustrncmp(smtp_command,"MAIL",4) == 0 ||
+                      Ustrncmp(smtp_command,"end ",4) == 0;
+      break;
 
-  else
-    {
-    if (mua_wrapper) code = '5';  /* Force hard failure in wrapper mode */
+      case ERRNO_SMTPCLOSED:
+      message_error = Ustrncmp(smtp_command,"end ",4) == 0;
+      break;
+
+      default:
+      message_error = FALSE;
+      break;
+      }
+
+    /* Handle the cases that are treated as message errors. These are:
+
+      (a) negative response or timeout after MAIL
+      (b) negative response after DATA
+      (c) negative response or timeout or dropped connection after "."
+
+    It won't be a negative response or timeout after RCPT, as that is dealt
+    with separately above. The action in all cases is to set an appropriate
+    error code for all the addresses, but to leave yield set to OK because the
+    host itself has not failed. Of course, it might in practice have failed
+    when we've had a timeout, but if so, we'll discover that at the next
+    delivery attempt. For a temporary error, set the message_defer flag, and
+    write to the logs for information if this is not the last host. The error
+    for the last host will be logged as part of the address's log line. */
+
+    if (message_error)
+      {
+      if (mua_wrapper) code = '5';  /* Force hard failure in wrapper mode */
+      set_errno(addrlist, save_errno, message, (code == '5')? FAIL : DEFER,
+        pass_message);
+
+      /* If there's an errno, the message contains just the identity of
+      the host. */
 
-    set_errno(addrlist, save_errno, message, (code == '5')? FAIL : DEFER);
+      if (code != '5')     /* Anything other than 5 is treated as temporary */
+        {
+        if (save_errno > 0)
+          message = US string_sprintf("%s: %s", message, strerror(save_errno));
+        if (host->next != NULL) log_write(0, LOG_MAIN, "%s", message);
+        deliver_msglog("%s %s\n", tod_stamp(tod_log), message);
+        *message_defer = TRUE;
+        }
+      }
 
-    /* If there's an errno, the message contains just the identity of
-    the host. */
+    /* Otherwise, we have an I/O error or a timeout other than after MAIL or
+    ".", or some other transportation error. We defer all addresses and yield
+    DEFER, except for the case of failed add_headers expansion, or a transport
+    filter failure, when the yield should be ERROR, to stop it trying other
+    hosts. */
 
-    if (code != '5')     /* Anything other than 5 is treated as temporary */
+    else
       {
-      if (save_errno > 0)
-        message = US string_sprintf("%s: %s", message, strerror(save_errno));
-      if (host->next != NULL) log_write(0, LOG_MAIN, "%s", message);
-      deliver_msglog("%s %s\n", tod_stamp(tod_log), message);
-      *message_defer = TRUE;
+      yield = (save_errno == ERRNO_CHHEADER_FAIL ||
+               save_errno == ERRNO_FILTER_FAIL)? ERROR : DEFER;
+      set_errno(addrlist, save_errno, message, DEFER, pass_message);
       }
     }
   }
@@ -1744,6 +1854,7 @@ if (completed_address && ok && send_quit)
         ))
     {
     uschar *msg;
+    BOOL pass_message;
 
     if (send_rset)
       {
@@ -1757,7 +1868,8 @@ if (completed_address && ok && send_quit)
                   ob->command_timeout)))
         {
         int code;
-        send_quit = check_response(host, &errno, 0, buffer, &code, &msg);
+        send_quit = check_response(host, &errno, 0, buffer, &code, &msg,
+          &pass_message);
         if (!send_quit)
           {
           DEBUG(D_transport) debug_printf("%s\n", msg);
@@ -1803,7 +1915,7 @@ if (completed_address && ok && send_quit)
 
     /* If RSET failed and there are addresses left, they get deferred. */
 
-    else set_errno(first_addr, errno, msg, DEFER);
+    else set_errno(first_addr, errno, msg, DEFER, FALSE);
     }
   }
 
@@ -1846,7 +1958,7 @@ writing RSET might have failed, or there may be other addresses whose hosts are
 specified in the transports, and therefore not visible at top level, in which
 case continue_more won't get set. */
 
-close(inblock.sock);
+(void)close(inblock.sock);
 continue_transport = NULL;
 continue_hostname = NULL;
 return yield;
@@ -1899,7 +2011,7 @@ outblock.authenticating = FALSE;
 (void)smtp_write_command(&outblock, FALSE, "QUIT\r\n");
 (void)smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
   ob->command_timeout);
-close(inblock.sock);
+(void)close(inblock.sock);
 }
 
 
@@ -2092,12 +2204,9 @@ else if (ob->hosts_randomize && hostlist->mx == MX_NONE && !continuing)
   }
 
 
-/* Sort out the port.  Set up a string for adding to the retry key if the port
-number is not the standard SMTP port. */
+/* Sort out the default port.  */
 
 if (!smtp_get_port(ob->port, addrlist, &port, tid)) return FALSE;
-pistring = string_sprintf(":%d", port);
-if (Ustrcmp(pistring, ":25") == 0) pistring = US"";
 
 
 /* For each host-plus-IP-address on the list:
@@ -2173,7 +2282,8 @@ for (cutoff_retry = 0; expired &&
     uschar *serialize_key = NULL;
 
     /* Default next host is next host. :-) But this can vary if the
-    hosts_max_try limit is hit (see below). */
+    hosts_max_try limit is hit (see below). It may also be reset if a host
+    address is looked up here (in case the host was multihomed). */
 
     nexthost = host->next;
 
@@ -2200,6 +2310,8 @@ for (cutoff_retry = 0; expired &&
 
     if (host->address == NULL)
       {
+      int new_port;
+      host_item *hh;
       uschar *canonical_name;
 
       if (host->status >= hstatus_unusable)
@@ -2211,12 +2323,19 @@ for (cutoff_retry = 0; expired &&
 
       DEBUG(D_transport) debug_printf("getting address for %s\n", host->name);
 
+      /* The host name is permitted to have an attached port. Find it, and
+      strip it from the name. Just remember it for now. */
+
+      new_port = host_item_get_port(host);
+
+      /* Count hosts looked up */
+
       hosts_looked_up++;
 
       /* Find by name if so configured, or if it's an IP address. We don't
       just copy the IP address, because we need the test-for-local to happen. */
 
-      if (ob->gethostbyname || string_is_ip_address(host->name, NULL) > 0)
+      if (ob->gethostbyname || string_is_ip_address(host->name, NULL) != 0)
         rc = host_find_byname(host, NULL, &canonical_name, TRUE);
       else
         {
@@ -2227,6 +2346,11 @@ for (cutoff_retry = 0; expired &&
           &canonical_name, NULL);
         }
 
+      /* Update the host (and any additional blocks, resulting from
+      multihoming) with a host-specific port, if any. */
+
+      for (hh = host; hh != nexthost; hh = hh->next) hh->port = new_port;
+
       /* Failure to find the host at this time (usually DNS temporary failure)
       is really a kind of routing failure rather than a transport failure.
       Therefore we add a retry item of the routing kind, not to stop us trying
@@ -2283,6 +2407,11 @@ for (cutoff_retry = 0; expired &&
       continue;      /* With next host */
       }
 
+    /* Reset the default next host in case a multihomed host whose addresses
+    are not looked up till just above added to the host list. */
+
+    nexthost = host->next;
+
     /* If queue_smtp is set (-odqs or the first part of a 2-stage run), or the
     domain is in queue_smtp_domains, we don't actually want to attempt any
     deliveries. When doing a queue run, queue_smtp_domains is always unset. If
@@ -2291,8 +2420,8 @@ for (cutoff_retry = 0; expired &&
     doing a two-stage queue run, don't do this if forcing. */
 
     if ((!deliver_force || queue_2stage) && (queue_smtp ||
-        match_isinlist(addrlist->domain, &queue_smtp_domains, 0, NULL, NULL,
-          MCL_DOMAIN, TRUE, NULL) == OK))
+        match_isinlist(addrlist->domain, &queue_smtp_domains, 0,
+          &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK))
       {
       expired = FALSE;
       for (addr = addrlist; addr != NULL; addr = addr->next)
@@ -2315,6 +2444,14 @@ for (cutoff_retry = 0; expired &&
     deliver_host = host->name;
     deliver_host_address = host->address;
 
+    /* Set up a string for adding to the retry key if the port number is not
+    the standard SMTP port. A host may have its own port setting that overrides
+    the default. */
+
+    pistring = string_sprintf(":%d", (host->port == PORT_NONE)?
+      port : host->port);
+    if (Ustrcmp(pistring, ":25") == 0) pistring = US"";
+
     /* Select IPv4 or IPv6, and choose an outgoing interface. If the interface
     string changes upon expansion, we must add it to the key that is used for
     retries, because connections to the same host from a different interface
@@ -2440,7 +2577,7 @@ for (cutoff_retry = 0; expired &&
     if (dont_deliver)
       {
       host_item *host2;
-      set_errno(addrlist, 0, NULL, OK);
+      set_errno(addrlist, 0, NULL, OK, FALSE);
       for (addr = addrlist; addr != NULL; addr = addr->next)
         {
         addr->host_used = host;
@@ -2762,6 +2899,7 @@ for (addr = addrlist; addr != NULL; addr = addr->next)
       }
     else if (expired)
       {
+      setflag(addr, af_pass_message);   /* This is not a security risk */
       addr->message = (ob->delay_after_cutoff)?
         US"retry time not reached for any host after a long failure period" :
         US"all hosts have been failing for a long time and were last tried "