Fix DKIM verify operation in -bh test mode. Bug 2017
[exim.git] / src / src / transports / smtp.c
index 677ce4ee105854ada74a841ff4df8d5c48a5f032..8708271701c8ac7b6f5df62da9a36ef9959d2120 100644 (file)
@@ -124,6 +124,8 @@ optionlist smtp_transport_options[] = {
   { "hosts_try_dane",       opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_try_dane) },
 #endif
+  { "hosts_try_fastopen",   opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, hosts_try_fastopen) },
 #ifndef DISABLE_PRDR
   { "hosts_try_prdr",       opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_try_prdr) },
@@ -209,6 +211,7 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   NULL,                /* hosts_try_dane */
   NULL,                /* hosts_require_dane */
 #endif
+  NULL,                /* hosts_try_fastopen */
 #ifndef DISABLE_PRDR
   US"*",               /* hosts_try_prdr */
 #endif
@@ -282,10 +285,11 @@ static uschar *rf_names[] = { US"NEVER", US"SUCCESS", US"FAILURE", US"DELAY" };
 
 /* Local statics */
 
-static uschar *smtp_command;   /* Points to last cmd for error messages */
-static uschar *mail_command;   /* Points to MAIL cmd for error messages */
-static BOOL    update_waiting; /* TRUE to update the "wait" database */
-static BOOL    pipelining_active; /* current transaction is in pipe mode */
+static uschar *smtp_command;           /* Points to last cmd for error messages */
+static uschar *mail_command;           /* Points to MAIL cmd for error messages */
+static uschar *data_command = US"";    /* Points to DATA cmd for error messages */
+static BOOL    update_waiting;         /* TRUE to update the "wait" database */
+static BOOL    pipelining_active;      /* current transaction is in pipe mode */
 
 
 /*************************************************
@@ -504,7 +508,7 @@ Arguments:
   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
+  message        where to put an error message
   pass_message   set TRUE if message is an SMTP response
 
 Returns:         TRUE if an SMTP "QUIT" command should be sent, else FALSE
@@ -631,11 +635,11 @@ Returns:   nothing
 static void
 write_logs(address_item *addr, host_item *host)
 {
-uschar * message = string_sprintf("H=%s [%s]", host->name, host->address);
+uschar * message = LOGGING(outgoing_port)
+  ? string_sprintf("H=%s [%s]:%d", host->name, host->address,
+                   host->port == PORT_NONE ? 25 : host->port)
+  : string_sprintf("H=%s [%s]", host->name, host->address);
 
-if (LOGGING(outgoing_port))
-  message = string_sprintf("%s:%d", message,
-             host->port == PORT_NONE ? 25 : host->port);
 if (addr->message)
   {
   message = string_sprintf("%s: %s", message, addr->message);
@@ -1387,10 +1391,14 @@ uschar * buffer = tctx->buffer;
 /* Write SMTP chunk header command */
 
 if (chunk_size > 0)
+  {
   if((cmd_count = smtp_write_command(tctx->outblock, FALSE, "BDAT %u%s\r\n",
                              chunk_size,
                              flags & tc_chunk_last ? " LAST" : "")
      ) < 0) return ERROR;
+  if (flags & tc_chunk_last)
+    data_command = string_copy(big_buffer);  /* Save for later error message */
+  }
 
 prev_cmd_count = cmd_count += tctx->cmd_count;
 
@@ -1525,6 +1533,7 @@ struct lflags {
   BOOL send_quit:1;
   BOOL setting_up:1;
   BOOL esmtp:1;
+  BOOL esmtp_sent:1;
   BOOL pending_MAIL:1;
 #ifndef DISABLE_PRDR
   BOOL prdr_active:1;
@@ -1572,10 +1581,7 @@ lflags.send_rset = TRUE;
 lflags.send_quit = TRUE;
 lflags.setting_up = TRUE;
 lflags.esmtp = TRUE;
-lflags.pending_MAIL;
-#ifndef DISABLE_PRDR
-lflags.prdr_active;
-#endif
+lflags.esmtp_sent = FALSE;
 #ifdef SUPPORT_I18N
 lflags.utf8_needed = FALSE;
 #endif
@@ -1587,6 +1593,7 @@ lflags.dane_required = verify_check_given_host(&ob->hosts_require_dane, host) ==
 
 *message_defer = FALSE;
 smtp_command = US"initial connection";
+buffer[0] = '\0';
 if (max_rcpt == 0) max_rcpt = 999999;
 
 /* Set up the buffer for reading SMTP response packets. */
@@ -1707,7 +1714,12 @@ if (continue_hostname == NULL)
 
   if (!lflags.smtps)
     {
-    BOOL good_response = smtp_read_response(&inblock, buffer, sizeof(buffer),
+    BOOL good_response;
+
+#ifdef TCP_QUICKACK
+    (void) setsockopt(inblock.sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
+#endif
+    good_response = smtp_read_response(&inblock, buffer, sizeof(buffer),
       '2', ob->command_timeout);
 #ifdef EXPERIMENTAL_DSN_INFO
     smtp_greeting = string_copy(buffer);
@@ -1734,7 +1746,7 @@ if (continue_hostname == NULL)
     /* Now check if the helo_data expansion went well, and sign off cleanly if
     it didn't. */
 
-    if (helo_data == NULL)
+    if (!helo_data)
       {
       uschar *message = string_sprintf("failed to expand helo_data: %s",
         expand_string_message);
@@ -1800,6 +1812,7 @@ goto SEND_QUIT;
     if (smtp_write_command(&outblock, FALSE, "%s %s\r\n",
          lflags.lmtp ? "LHLO" : "EHLO", helo_data) < 0)
       goto SEND_FAILED;
+    lflags.esmtp_sent = TRUE;
     if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
            ob->command_timeout))
       {
@@ -1823,15 +1836,37 @@ goto SEND_QUIT;
   if (!lflags.esmtp)
     {
     BOOL good_response;
+    int n = sizeof(buffer);
+    uschar * rsp = buffer;
+
+    if (lflags.esmtp_sent && (n = Ustrlen(buffer)) < sizeof(buffer)/2)
+      { rsp = buffer + n + 1; n = sizeof(buffer) - n; }
 
     if (smtp_write_command(&outblock, FALSE, "HELO %s\r\n", helo_data) < 0)
       goto SEND_FAILED;
-    good_response = smtp_read_response(&inblock, buffer, sizeof(buffer),
+    good_response = smtp_read_response(&inblock, rsp, n,
       '2', ob->command_timeout);
 #ifdef EXPERIMENTAL_DSN_INFO
-    helo_response = string_copy(buffer);
+    helo_response = string_copy(rsp);
 #endif
-    if (!good_response) goto RESPONSE_FAILED;
+    if (!good_response)
+      {
+      /* Handle special logging for a closed connection after HELO
+      when had previously sent EHLO */
+
+      if (rsp != buffer && rsp[0] == 0 && (errno == 0 || errno == ECONNRESET))
+       {
+       message = NULL;
+       lflags.send_quit = FALSE;
+       save_errno = ERRNO_SMTPCLOSED;
+       message = string_sprintf("Remote host closed connection "
+             "in response to %s (EHLO response was: %s)",
+             smtp_command, buffer);
+       goto FAILED;
+       }
+      Ustrncpy(buffer, rsp, sizeof(buffer)/2);
+      goto RESPONSE_FAILED;
+      }
     }
 
   peer_offered = smtp_peer_options = 0;
@@ -1858,7 +1893,7 @@ set from the command line if they were set in the process that passed the
 connection on. */
 
 /*XXX continue case needs to propagate DSN_INFO, prob. in deliver.c
-as the contine goes via transport_pass_socket() and doublefork and exec.
+as the continue goes via transport_pass_socket() and doublefork and exec.
 It does not wait.  Unclear how we keep separate host's responses
 separate - we could match up by host ip+port as a bodge. */
 
@@ -1924,12 +1959,9 @@ if (  smtp_peer_options & PEER_OFFERED_TLS
     if (rc != OK)
       {
 # ifdef EXPERIMENTAL_DANE
-      if (rc == DEFER && lflags.dane)
-       {
-       log_write(0, LOG_MAIN,
+      if (lflags.dane) log_write(0, LOG_MAIN,
          "DANE attempt failed; no TLS connection to %s [%s]",
          host->name, host->address);
-       }
 # endif
 
       save_errno = ERRNO_TLSFAILURE;
@@ -2147,10 +2179,10 @@ set it up. This cannot be done until the identify of the host is known. */
 if (tblock->filter_command != NULL)
   {
   BOOL rc;
-  uschar buffer[64];
-  sprintf(CS buffer, "%.50s transport", tblock->name);
+  uschar fbuf[64];
+  sprintf(CS fbuf, "%.50s transport", tblock->name);
   rc = transport_set_up_command(&transport_filter_argv, tblock->filter_command,
-    TRUE, DEFER, addrlist, buffer, NULL);
+    TRUE, DEFER, addrlist, fbuf, NULL);
   transport_filter_timeout = tblock->filter_timeout;
 
   /* On failure, copy the error to all addresses, abandon the SMTP call, and
@@ -2397,17 +2429,14 @@ for (addr = first_addr;
   rcpt_addr = transport_rcpt_address(addr, tblock->rcpt_include_affixes);
 
 #ifdef SUPPORT_I18N
-  {
-  uschar * dummy_errstr;
   if (  testflag(addrlist, af_utf8_downcvt)
-     && (rcpt_addr = string_address_utf8_to_alabel(rcpt_addr, &dummy_errstr),
-        dummy_errstr
-     )  )
+     && !(rcpt_addr = string_address_utf8_to_alabel(rcpt_addr, NULL))
+     )
     {
+    /*XXX could we use a per-address errstr here? Not fail the whole send? */
     errno = ERRNO_EXPANDFAIL;
     goto SEND_FAILED;
     }
-  }
 #endif
 
   count = smtp_write_command(&outblock, no_flush, "RCPT TO:<%s>%s%s\r\n",
@@ -2485,6 +2514,7 @@ if (  !(peer_offered & PEER_OFFERED_CHUNKING)
     default: goto RESPONSE_FAILED;       /* I/O error, or any MAIL/DATA error */
     }
   pipelining_active = FALSE;
+  data_command = string_copy(big_buffer);  /* Save for later error message */
   }
 
 /* If there were no good recipients (but otherwise there have been no
@@ -2708,7 +2738,7 @@ else
 #else
            "LMTP error after %s: %s",
 #endif
-            big_buffer, string_printing(buffer));
+            data_command, string_printing(buffer));
           setflag(addr, af_pass_message);   /* Allow message to go to user */
           if (buffer[0] == '5')
             addr->transport_return = FAIL;
@@ -3079,7 +3109,7 @@ if (completed_address && lflags.ok && lflags.send_quit)
         }
 #endif
 
-      /* If the socket is successfully passed, we musn't send QUIT (or
+      /* If the socket is successfully passed, we mustn't send QUIT (or
       indeed anything!) from here. */
 
 /*XXX DSN_INFO: assume likely to do new HELO; but for greet we'll want to
@@ -3138,6 +3168,13 @@ specified in the transports, and therefore not visible at top level, in which
 case continue_more won't get set. */
 
 HDEBUG(D_transport|D_acl|D_v) debug_printf("  SMTP(close)>>\n");
+if (lflags.send_quit)
+  {
+  shutdown(outblock.sock, SHUT_WR);
+  if (fcntl(inblock.sock, F_SETFL, O_NONBLOCK) == 0)
+    for (rc = 16; read(inblock.sock, inbuffer, sizeof(inbuffer)) > 0 && rc > 0;)
+      rc--;                            /* drain socket */
+  }
 (void)close(inblock.sock);
 
 #ifndef DISABLE_EVENT