]> git.netwichtig.de Git - user/henk/code/exim.git/blobdiff - src/src/smtp_in.c
CHUNKING: fix non-pipelined synch checks. Bug 2004
[user/henk/code/exim.git] / src / src / smtp_in.c
index 8994c6edb8d2b242861987c97be2f6c4a54aca20..e8692ce56057021887f22b10192a938139070995 100644 (file)
@@ -82,19 +82,23 @@ enum {
 
   MAIL_CMD, RCPT_CMD, RSET_CMD,
 
+  /* This is a dummy to identify the non-sync commands when not pipelining */
+
+  NON_SYNC_CMD_NON_PIPELINING,
+
   /* RFC3030 section 2: "After all MAIL and RCPT responses are collected and
   processed the message is sent using a series of BDAT commands"
   implies that BDAT should be synchronized.  However, we see Google, at least,
   sending MAIL,RCPT,BDAT-LAST in a single packet, clearly not waiting for
-  processing of the RPCT response(s).  We shall do the same, and not require
-  synch for BDAT. */
+  processing of the RCPT response(s).  We shall do the same, and not require
+  synch for BDAT.  Worse, as the chunk may (very likely will) follow the
+  command-header in the same packet we cannot do the usual "is there any
+  follow-on data after the commmand line" even for non-pipeline mode.
+  So we'll need an explicit check after reading the expected chunk amount
+  when non-pipe, before sennding the ACK. */
 
   BDAT_CMD,
 
-  /* This is a dummy to identify the non-sync commands when not pipelining */
-
-  NON_SYNC_CMD_NON_PIPELINING,
-
   /* I have been unable to find a statement about the use of pipelining
   with AUTH, so to be on the safe side it is here, though I kind of feel
   it should be up there with the synchronized commands. */
@@ -306,6 +310,97 @@ static int synprot_error(int type, int code, uschar *data, uschar *errmess);
 static void smtp_quit_handler(uschar **, uschar **);
 static void smtp_rset_handler(void);
 
+/*************************************************
+*          Recheck synchronization               *
+*************************************************/
+
+/* Synchronization checks can never be perfect because a packet may be on its
+way but not arrived when the check is done. Such checks can in any case only be
+done when TLS is not in use. Normally, the checks happen when commands are
+read: Exim ensures that there is no more input in the input buffer. In normal
+cases, the response to the command will be fast, and there is no further check.
+
+However, for some commands an ACL is run, and that can include delays. In those
+cases, it is useful to do another check on the input just before sending the
+response. This also applies at the start of a connection. This function does
+that check by means of the select() function, as long as the facility is not
+disabled or inappropriate. A failure of select() is ignored.
+
+When there is unwanted input, we read it so that it appears in the log of the
+error.
+
+Arguments: none
+Returns:   TRUE if all is well; FALSE if there is input pending
+*/
+
+static BOOL
+check_sync(void)
+{
+int fd, rc;
+fd_set fds;
+struct timeval tzero;
+
+if (!smtp_enforce_sync || sender_host_address == NULL ||
+    sender_host_notsocket || tls_in.active >= 0)
+  return TRUE;
+
+fd = fileno(smtp_in);
+FD_ZERO(&fds);
+FD_SET(fd, &fds);
+tzero.tv_sec = 0;
+tzero.tv_usec = 0;
+rc = select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &tzero);
+
+if (rc <= 0) return TRUE;     /* Not ready to read */
+rc = smtp_getc();
+if (rc < 0) return TRUE;      /* End of file or error */
+
+smtp_ungetc(rc);
+rc = smtp_inend - smtp_inptr;
+if (rc > 150) rc = 150;
+smtp_inptr[rc] = 0;
+return FALSE;
+}
+
+
+
+/*************************************************
+*          Log incomplete transactions           *
+*************************************************/
+
+/* This function is called after a transaction has been aborted by RSET, QUIT,
+connection drops or other errors. It logs the envelope information received
+so far in order to preserve address verification attempts.
+
+Argument:   string to indicate what aborted the transaction
+Returns:    nothing
+*/
+
+static void
+incomplete_transaction_log(uschar *what)
+{
+if (sender_address == NULL ||                 /* No transaction in progress */
+    !LOGGING(smtp_incomplete_transaction))
+  return;
+
+/* Build list of recipients for logging */
+
+if (recipients_count > 0)
+  {
+  int i;
+  raw_recipients = store_get(recipients_count * sizeof(uschar *));
+  for (i = 0; i < recipients_count; i++)
+    raw_recipients[i] = recipients_list[i].address;
+  raw_recipients_count = recipients_count;
+  }
+
+log_write(L_smtp_incomplete_transaction, LOG_MAIN|LOG_SENDER|LOG_RECIPIENTS,
+  "%s incomplete transaction (%s)", host_and_ident(TRUE), what);
+}
+
+
+
+
 /*************************************************
 *          SMTP version of getc()                *
 *************************************************/
@@ -325,6 +420,7 @@ smtp_getc(void)
 if (smtp_inptr >= smtp_inend)
   {
   int rc, save_errno;
+  if (!smtp_out) return EOF;
   fflush(smtp_out);
   if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
   rc = read(fileno(smtp_in), smtp_inbuffer, in_buffer_size);
@@ -352,15 +448,15 @@ if (smtp_inptr >= smtp_inend)
 return *smtp_inptr++;
 }
 
-#ifndef DISABLE_DKIM
 void
 smtp_get_cache(void)
 {
+#ifndef DISABLE_DKIM
 int n = smtp_inend - smtp_inptr;
 if (n > 0)
   dkim_exim_verify_feed(smtp_inptr, n);
-}
 #endif
+}
 
 
 /* Get a byte from the smtp input, in CHUNKING mode.  Handle ack of the
@@ -393,13 +489,29 @@ for(;;)
   receive_getc = lwr_receive_getc;
   receive_ungetc = lwr_receive_ungetc;
 
+  /* Unless PIPELINING was offered, there should be no next command
+  until after we ack that chunk */
+
+  if (!pipelining_advertised && !check_sync())
+    {
+    incomplete_transaction_log(US"sync failure");
+    log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error "
+      "(next input sent too soon: pipelining was not advertised): "
+      "rejected \"%s\" %s next input=\"%s\"",
+      smtp_cmd_buffer, host_and_ident(TRUE),
+      string_printing(smtp_inptr));
+      (void) synprot_error(L_smtp_protocol_error, 554, NULL,
+       US"SMTP synchronization error");
+    goto repeat_until_rset;
+    }
+
   /* If not the last, ack the received chunk.  The last response is delayed
   until after the data ACL decides on it */
 
   if (chunking_state == CHUNKING_LAST)
     {
 #ifndef DISABLE_DKIM
-    dkim_exim_verify_feed(".\r\n", 3); /* for consistency with .-term MAIL */
+    dkim_exim_verify_feed(NULL, 0);    /* notify EOD */
 #endif
     return EOD;
     }
@@ -476,6 +588,20 @@ next_cmd:
   }
 }
 
+static void
+bdat_flush_data(void)
+{
+while (chunking_data_left-- > 0)
+  if (lwr_receive_getc() < 0)
+    break;
+
+receive_getc = lwr_receive_getc;
+receive_ungetc = lwr_receive_ungetc;
+
+if (chunking_state != CHUNKING_LAST)
+  chunking_state = CHUNKING_OFFERED;
+}
+
 
 
 
@@ -746,10 +872,10 @@ Arguments: fd     - File descriptor for input
 Returns:   none
 */
 static void
-restore_socket_timeout(int fd, int get_ok, struct timeval tvtmp, socklen_t vslen)
+restore_socket_timeout(int fd, int get_ok, struct timeval tvtmp, socklen_t vslen)
 {
 if (get_ok == 0)
-  setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tvtmp, vslen);
+  (void) setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS tvtmp, vslen);
 }
 
 /*************************************************
@@ -790,7 +916,7 @@ so exit with an error if do not find the exact required pieces. This
 includes an incorrect number of spaces separating args.
 
 Arguments: none
-Returns:   int
+Returns:   Boolean success
 */
 
 static BOOL
@@ -834,27 +960,23 @@ char tmpip6[INET6_ADDRSTRLEN];
 struct sockaddr_in6 tmpaddr6;
 
 int get_ok = 0;
-int size, ret, fd;
+int size, ret;
+int fd = fileno(smtp_in);
 const char v2sig[12] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
 uschar *iptype;  /* To display debug info */
 struct timeval tv;
-socklen_t vslen = 0;
 struct timeval tvtmp;
-
-vslen = sizeof(struct timeval);
-
-fd = fileno(smtp_in);
+socklen_t vslen = sizeof(struct timeval);
 
 /* Save current socket timeout values */
-get_ok = getsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tvtmp,
-                    &vslen);
+get_ok = getsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS &tvtmp, &vslen);
 
 /* Proxy Protocol host must send header within a short time
 (default 3 seconds) or it's considered invalid */
 tv.tv_sec  = PROXY_NEGOTIATION_TIMEOUT_SEC;
 tv.tv_usec = PROXY_NEGOTIATION_TIMEOUT_USEC;
-setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv,
-           sizeof(struct timeval));
+if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS &tv, sizeof(tv)) < 0)
+  return FALSE;
 
 do
   {
@@ -865,22 +987,17 @@ do
   while (ret == -1 && errno == EINTR);
 
 if (ret == -1)
-  {
-  restore_socket_timeout(fd, get_ok, tvtmp, vslen);
-  return (errno == EAGAIN) ? 0 : ERRNO_PROXYFAIL;
-  }
+  goto proxyfail;
 
-if (ret >= 16 &&
-    memcmp(&hdr.v2, v2sig, 12) == 0)
+if (ret >= 16 && memcmp(&hdr.v2, v2sig, 12) == 0)
   {
-  uint8_t ver, cmd;
+  uint8_t ver = (hdr.v2.ver_cmd & 0xf0) >> 4;
+  uint8_t cmd = (hdr.v2.ver_cmd & 0x0f);
 
   /* May 2014: haproxy combined the version and command into one byte to
      allow two full bytes for the length field in order to proxy SSL
      connections.  SSL Proxy is not supported in this version of Exim, but
      must still seperate values here. */
-  ver = (hdr.v2.ver_cmd & 0xf0) >> 4;
-  cmd = (hdr.v2.ver_cmd & 0x0f);
 
   if (ver != 0x02)
     {
@@ -889,7 +1006,7 @@ if (ret >= 16 &&
     }
   DEBUG(D_receive) debug_printf("Detected PROXYv2 header\n");
   /* The v2 header will always be 16 bytes per the spec. */
-  size = 16 + hdr.v2.len;
+  size = 16 + ntohs(hdr.v2.len);
   if (ret < size)
     {
     DEBUG(D_receive) debug_printf("Truncated or too large PROXYv2 header (%d/%d)\n",
@@ -904,11 +1021,11 @@ if (ret >= 16 &&
         case 0x11:  /* TCPv4 address type */
           iptype = US"IPv4";
           tmpaddr.sin_addr.s_addr = hdr.v2.addr.ip4.src_addr;
-          inet_ntop(AF_INET, &(tmpaddr.sin_addr), (char *)&tmpip, sizeof(tmpip));
-          if (!string_is_ip_address(US tmpip,NULL))
+          inet_ntop(AF_INET, &tmpaddr.sin_addr, CS &tmpip, sizeof(tmpip));
+          if (!string_is_ip_address(US tmpip, NULL))
             {
             DEBUG(D_receive) debug_printf("Invalid %s source IP\n", iptype);
-            return ERRNO_PROXYFAIL;
+            goto proxyfail;
             }
           proxy_local_address = sender_host_address;
           sender_host_address = string_copy(US tmpip);
@@ -917,11 +1034,11 @@ if (ret >= 16 &&
           sender_host_port    = tmpport;
           /* Save dest ip/port */
           tmpaddr.sin_addr.s_addr = hdr.v2.addr.ip4.dst_addr;
-          inet_ntop(AF_INET, &(tmpaddr.sin_addr), (char *)&tmpip, sizeof(tmpip));
-          if (!string_is_ip_address(US tmpip,NULL))
+          inet_ntop(AF_INET, &tmpaddr.sin_addr, CS &tmpip, sizeof(tmpip));
+          if (!string_is_ip_address(US tmpip, NULL))
             {
             DEBUG(D_receive) debug_printf("Invalid %s dest port\n", iptype);
-            return ERRNO_PROXYFAIL;
+            goto proxyfail;
             }
           proxy_external_address = string_copy(US tmpip);
           tmpport              = ntohs(hdr.v2.addr.ip4.dst_port);
@@ -930,11 +1047,11 @@ if (ret >= 16 &&
         case 0x21:  /* TCPv6 address type */
           iptype = US"IPv6";
           memmove(tmpaddr6.sin6_addr.s6_addr, hdr.v2.addr.ip6.src_addr, 16);
-          inet_ntop(AF_INET6, &(tmpaddr6.sin6_addr), (char *)&tmpip6, sizeof(tmpip6));
-          if (!string_is_ip_address(US tmpip6,NULL))
+          inet_ntop(AF_INET6, &tmpaddr6.sin6_addr, CS &tmpip6, sizeof(tmpip6));
+          if (!string_is_ip_address(US tmpip6, NULL))
             {
             DEBUG(D_receive) debug_printf("Invalid %s source IP\n", iptype);
-            return ERRNO_PROXYFAIL;
+            goto proxyfail;
             }
           proxy_local_address = sender_host_address;
           sender_host_address = string_copy(US tmpip6);
@@ -943,11 +1060,11 @@ if (ret >= 16 &&
           sender_host_port    = tmpport;
           /* Save dest ip/port */
           memmove(tmpaddr6.sin6_addr.s6_addr, hdr.v2.addr.ip6.dst_addr, 16);
-          inet_ntop(AF_INET6, &(tmpaddr6.sin6_addr), (char *)&tmpip6, sizeof(tmpip6));
-          if (!string_is_ip_address(US tmpip6,NULL))
+          inet_ntop(AF_INET6, &tmpaddr6.sin6_addr, CS &tmpip6, sizeof(tmpip6));
+          if (!string_is_ip_address(US tmpip6, NULL))
             {
             DEBUG(D_receive) debug_printf("Invalid %s dest port\n", iptype);
-            return ERRNO_PROXYFAIL;
+            goto proxyfail;
             }
           proxy_external_address = string_copy(US tmpip6);
           tmpport              = ntohs(hdr.v2.addr.ip6.dst_port);
@@ -970,8 +1087,7 @@ if (ret >= 16 &&
       goto proxyfail;
     }
   }
-else if (ret >= 8 &&
-         memcmp(hdr.v1.line, "PROXY", 5) == 0)
+else if (ret >= 8 && memcmp(hdr.v1.line, "PROXY", 5) == 0)
   {
   uschar *p = string_copy(hdr.v1.line);
   uschar *end = memchr(p, '\r', ret - 1);
@@ -1024,7 +1140,7 @@ else if (ret >= 8 &&
     goto proxyfail;
     }
   *sp = '\0';
-  if(!string_is_ip_address(p,NULL))
+  if(!string_is_ip_address(p, NULL))
     {
     DEBUG(D_receive)
       debug_printf("Proxied src arg is not an %s address\n", iptype);
@@ -1040,7 +1156,7 @@ else if (ret >= 8 &&
     goto proxyfail;
     }
   *sp = '\0';
-  if(!string_is_ip_address(p,NULL))
+  if(!string_is_ip_address(p, NULL))
     {
     DEBUG(D_receive)
       debug_printf("Proxy dest arg is not an %s address\n", iptype);
@@ -1054,7 +1170,7 @@ else if (ret >= 8 &&
     goto proxyfail;
     }
   *sp = '\0';
-  tmp_port = strtol(CCS p,&endc,10);
+  tmp_port = strtol(CCS p, &endc, 10);
   if (*endc || tmp_port == 0)
     {
     DEBUG(D_receive)
@@ -1069,7 +1185,7 @@ else if (ret >= 8 &&
     DEBUG(D_receive) debug_printf("Did not find proxy dest port\n");
     goto proxyfail;
     }
-  tmp_port = strtol(CCS p,&endc,10);
+  tmp_port = strtol(CCS p, &endc, 10);
   if (*endc || tmp_port == 0)
     {
     DEBUG(D_receive)
@@ -1088,13 +1204,13 @@ else
   }
 
 proxyfail:
-restore_socket_timeout(fd, get_ok, tvtmp, vslen);
+restore_socket_timeout(fd, get_ok, &tvtmp, vslen);
 /* Don't flush any potential buffer contents. Any input should cause a
    synchronization failure */
 return FALSE;
 
 done:
-restore_socket_timeout(fd, get_ok, tvtmp, vslen);
+restore_socket_timeout(fd, get_ok, &tvtmp, vslen);
 DEBUG(D_receive)
   debug_printf("Valid %s sender from Proxy Protocol header\n", iptype);
 return proxy_session;
@@ -1251,60 +1367,6 @@ return OTHER_CMD;
 
 
 
-/*************************************************
-*          Recheck synchronization               *
-*************************************************/
-
-/* Synchronization checks can never be perfect because a packet may be on its
-way but not arrived when the check is done. Such checks can in any case only be
-done when TLS is not in use. Normally, the checks happen when commands are
-read: Exim ensures that there is no more input in the input buffer. In normal
-cases, the response to the command will be fast, and there is no further check.
-
-However, for some commands an ACL is run, and that can include delays. In those
-cases, it is useful to do another check on the input just before sending the
-response. This also applies at the start of a connection. This function does
-that check by means of the select() function, as long as the facility is not
-disabled or inappropriate. A failure of select() is ignored.
-
-When there is unwanted input, we read it so that it appears in the log of the
-error.
-
-Arguments: none
-Returns:   TRUE if all is well; FALSE if there is input pending
-*/
-
-static BOOL
-check_sync(void)
-{
-int fd, rc;
-fd_set fds;
-struct timeval tzero;
-
-if (!smtp_enforce_sync || sender_host_address == NULL ||
-    sender_host_notsocket || tls_in.active >= 0)
-  return TRUE;
-
-fd = fileno(smtp_in);
-FD_ZERO(&fds);
-FD_SET(fd, &fds);
-tzero.tv_sec = 0;
-tzero.tv_usec = 0;
-rc = select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &tzero);
-
-if (rc <= 0) return TRUE;     /* Not ready to read */
-rc = smtp_getc();
-if (rc < 0) return TRUE;      /* End of file or error */
-
-smtp_ungetc(rc);
-rc = smtp_inend - smtp_inptr;
-if (rc > 150) rc = 150;
-smtp_inptr[rc] = 0;
-return FALSE;
-}
-
-
-
 /*************************************************
 *          Forced closedown of call              *
 *************************************************/
@@ -1329,26 +1391,23 @@ if (smtp_in == NULL || smtp_batched_input) return;
 receive_swallow_smtp();
 smtp_printf("421 %s\r\n", message);
 
-for (;;)
+for (;;) switch(smtp_read_command(FALSE))
   {
-  switch(smtp_read_command(FALSE))
-    {
-    case EOF_CMD:
-    return;
+  case EOF_CMD:
+  return;
 
-    case QUIT_CMD:
-    smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
-    mac_smtp_fflush();
-    return;
+  case QUIT_CMD:
+  smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
+  mac_smtp_fflush();
+  return;
 
-    case RSET_CMD:
-    smtp_printf("250 Reset OK\r\n");
-    break;
+  case RSET_CMD:
+  smtp_printf("250 Reset OK\r\n");
+  break;
 
-    default:
-    smtp_printf("421 %s\r\n", message);
-    break;
-    }
+  default:
+  smtp_printf("421 %s\r\n", message);
+  break;
   }
 }
 
@@ -1371,8 +1430,8 @@ Returns:     a string describing the connection
 uschar *
 smtp_get_connection_info(void)
 {
-uschar *hostname = (sender_fullhost == NULL)?
-  sender_host_address : sender_fullhost;
+const uschar * hostname = sender_fullhost
+  ? sender_fullhost : sender_host_address;
 
 if (host_checking)
   return string_sprintf("SMTP connection from %s", hostname);
@@ -2035,10 +2094,10 @@ acl_var_c = NULL;
 
 /* Allow for trailing 0 in the command and data buffers. */
 
-smtp_cmd_buffer = (uschar *)malloc(2*smtp_cmd_buffer_size + 2);
-if (smtp_cmd_buffer == NULL)
+if (!(smtp_cmd_buffer = US malloc(2*smtp_cmd_buffer_size + 2)))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE,
     "malloc() failed for SMTP command buffer");
+
 smtp_cmd_buffer[0] = 0;
 smtp_data_buffer = smtp_cmd_buffer + smtp_cmd_buffer_size + 1;
 
@@ -2047,7 +2106,7 @@ command line by a trusted caller. */
 
 if (smtp_batched_input)
   {
-  if (received_protocol == NULL) received_protocol = US"local-bsmtp";
+  if (!received_protocol) received_protocol = US"local-bsmtp";
   }
 
 /* For non-batched SMTP input, the protocol setting is forced here. It will be
@@ -2060,9 +2119,9 @@ else
 /* Set up the buffer for inputting using direct read() calls, and arrange to
 call the local functions instead of the standard C ones. */
 
-smtp_inbuffer = (uschar *)malloc(in_buffer_size);
-if (smtp_inbuffer == NULL)
+if (!(smtp_inbuffer = (uschar *)malloc(in_buffer_size)))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "malloc() failed for SMTP input buffer");
+
 receive_getc = smtp_getc;
 receive_get_cache = smtp_get_cache;
 receive_ungetc = smtp_ungetc;
@@ -2461,8 +2520,7 @@ if (smtp_batched_input) return TRUE;
 proxy_session = FALSE;
 proxy_session_failed = FALSE;
 if (check_proxy_protocol_host())
-  {
-  if (setup_proxy_protocol_host() == FALSE)
+  if (!setup_proxy_protocol_host())
     {
     proxy_session_failed = TRUE;
     DEBUG(D_receive)
@@ -2474,20 +2532,18 @@ if (check_proxy_protocol_host())
     (void)host_name_lookup();
     host_build_sender_fullhost();
     }
-  }
 #endif
 
 /* Run the ACL if it exists */
 
 user_msg = NULL;
-if (acl_smtp_connect != NULL)
+if (acl_smtp_connect)
   {
   int rc;
-  rc = acl_check(ACL_WHERE_CONNECT, NULL, acl_smtp_connect, &user_msg,
-    &log_msg);
-  if (rc != OK)
+  if ((rc = acl_check(ACL_WHERE_CONNECT, NULL, acl_smtp_connect, &user_msg,
+                     &log_msg)) != OK)
     {
-    (void)smtp_handle_acl_fail(ACL_WHERE_CONNECT, rc, user_msg, log_msg);
+    (void) smtp_handle_acl_fail(ACL_WHERE_CONNECT, rc, user_msg, log_msg);
     return FALSE;
     }
   }
@@ -2635,43 +2691,6 @@ return yield;
 
 
 
-/*************************************************
-*          Log incomplete transactions           *
-*************************************************/
-
-/* This function is called after a transaction has been aborted by RSET, QUIT,
-connection drops or other errors. It logs the envelope information received
-so far in order to preserve address verification attempts.
-
-Argument:   string to indicate what aborted the transaction
-Returns:    nothing
-*/
-
-static void
-incomplete_transaction_log(uschar *what)
-{
-if (sender_address == NULL ||                 /* No transaction in progress */
-    !LOGGING(smtp_incomplete_transaction))
-  return;
-
-/* Build list of recipients for logging */
-
-if (recipients_count > 0)
-  {
-  int i;
-  raw_recipients = store_get(recipients_count * sizeof(uschar *));
-  for (i = 0; i < recipients_count; i++)
-    raw_recipients[i] = recipients_list[i].address;
-  raw_recipients_count = recipients_count;
-  }
-
-log_write(L_smtp_incomplete_transaction, LOG_MAIN|LOG_SENDER|LOG_RECIPIENTS,
-  "%s incomplete transaction (%s)", host_and_ident(TRUE), what);
-}
-
-
-
-
 /*************************************************
 *    Send SMTP response, possibly multiline      *
 *************************************************/
@@ -2853,16 +2872,16 @@ uschar *lognl;
 uschar *sender_info = US"";
 uschar *what =
 #ifdef WITH_CONTENT_SCAN
-  (where == ACL_WHERE_MIME)? US"during MIME ACL checks" :
+  where == ACL_WHERE_MIME ? US"during MIME ACL checks" :
 #endif
-  (where == ACL_WHERE_PREDATA)? US"DATA" :
-  (where == ACL_WHERE_DATA)? US"after DATA" :
+  where == ACL_WHERE_PREDATA ? US"DATA" :
+  where == ACL_WHERE_DATA ? US"after DATA" :
 #ifndef DISABLE_PRDR
-  (where == ACL_WHERE_PRDR)? US"after DATA PRDR" :
+  where == ACL_WHERE_PRDR ? US"after DATA PRDR" :
 #endif
-  (smtp_cmd_data == NULL)?
-    string_sprintf("%s in \"connect\" ACL", acl_wherenames[where]) :
-    string_sprintf("%s %s", acl_wherenames[where], smtp_cmd_data);
+  smtp_cmd_data ?
+    string_sprintf("%s %s", acl_wherenames[where], smtp_cmd_data) :
+    string_sprintf("%s in \"connect\" ACL", acl_wherenames[where]);
 
 if (drop) rc = FAIL;
 
@@ -2939,16 +2958,16 @@ if (sender_verified_failed != NULL &&
 
 /* Sort out text for logging */
 
-log_msg = (log_msg == NULL)? US"" : string_sprintf(": %s", log_msg);
-lognl = Ustrchr(log_msg, '\n');
-if (lognl != NULL) *lognl = 0;
+log_msg = log_msg ? string_sprintf(": %s", log_msg) : US"";
+if ((lognl = Ustrchr(log_msg, '\n'))) *lognl = 0;
 
 /* Send permanent failure response to the command, but the code used isn't
 always a 5xx one - see comments at the start of this function. If the original
 rc was FAIL_DROP we drop the connection and yield 2. */
 
-if (rc == FAIL) smtp_respond(smtp_code, codelen, TRUE, (user_msg == NULL)?
-  US"Administrative prohibition" : user_msg);
+if (rc == FAIL)
+  smtp_respond(smtp_code, codelen, TRUE,
+    user_msg ? user_msg : US"Administrative prohibition");
 
 /* Send temporary failure response to the command. Don't give any details,
 unless acl_temp_details is set. This is TRUE for a callout defer, a "defer"
@@ -2959,21 +2978,19 @@ interactions between temp_details and return_error_details. One day it should
 be re-implemented in a tidier fashion. */
 
 else
-  {
-  if (acl_temp_details && user_msg != NULL)
+  if (acl_temp_details && user_msg)
     {
-    if (smtp_return_error_details &&
-        sender_verified_failed != NULL &&
-        sender_verified_failed->message != NULL)
-      {
+    if (  smtp_return_error_details
+       && sender_verified_failed
+       && sender_verified_failed->message
+       )
       smtp_respond(smtp_code, codelen, FALSE, sender_verified_failed->message);
-      }
+
     smtp_respond(smtp_code, codelen, TRUE, user_msg);
     }
   else
     smtp_respond(smtp_code, codelen, TRUE,
       US"Temporary local problem - please try later");
-  }
 
 /* Log the incident to the logs that are specified by log_reject_target
 (default main, reject). This can be empty to suppress logging of rejections. If
@@ -2988,7 +3005,8 @@ if (log_reject_target != 0)
 #else
   uschar * tls = US"";
 #endif
-  log_write(0, log_reject_target, "%s%s%s %s%srejected %s%s",
+  log_write(where == ACL_WHERE_CONNECT ? L_connection_reject : 0,
+    log_reject_target, "%s%s%s %s%srejected %s%s",
     LOGGING(dnssec) && sender_host_dnssec ? US" DS" : US"",
     host_and_ident(TRUE),
     tls,
@@ -3057,12 +3075,11 @@ smtp_exit_function_called = TRUE;
 
 /* Call the not-QUIT ACL, if there is one, unless no reason is given. */
 
-if (acl_smtp_notquit != NULL && reason != NULL)
+if (acl_smtp_notquit && reason)
   {
   smtp_notquit_reason = reason;
-  rc = acl_check(ACL_WHERE_NOTQUIT, NULL, acl_smtp_notquit, &user_msg,
-    &log_msg);
-  if (rc == ERROR)
+  if ((rc = acl_check(ACL_WHERE_NOTQUIT, NULL, acl_smtp_notquit, &user_msg,
+                     &log_msg)) == ERROR)
     log_write(0, LOG_MAIN|LOG_PANIC, "ACL for not-QUIT returned ERROR: %s",
       log_msg);
   }
@@ -3072,9 +3089,11 @@ responses are all internal, they should always fit in the buffer, but code a
 warning, just in case. Note that string_vformat() still leaves a complete
 string, even if it is incomplete. */
 
-if (code != NULL && defaultrespond != NULL)
+if (code && defaultrespond)
   {
-  if (user_msg == NULL)
+  if (user_msg)
+    smtp_respond(code, 3, TRUE, user_msg);
+  else
     {
     uschar buffer[128];
     va_list ap;
@@ -3084,8 +3103,6 @@ if (code != NULL && defaultrespond != NULL)
     smtp_printf("%s %s\r\n", code, buffer);
     va_end(ap);
     }
-  else
-    smtp_respond(code, 3, TRUE, user_msg);
   mac_smtp_fflush();
   }
 }
@@ -3391,7 +3408,7 @@ smtp_quit_handler(uschar ** user_msgp, uschar ** log_msgp)
 {
 HAD(SCH_QUIT);
 incomplete_transaction_log(US"QUIT");
-if (acl_smtp_quit != NULL)
+if (acl_smtp_quit)
   {
   int rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, user_msgp, log_msgp);
   if (rc == ERROR)
@@ -3512,7 +3529,7 @@ while (done <= 0)
   uschar *orcpt = NULL;
   int flags;
 
-#if defined(SUPPORT_TLS) && defined(AUTH_TLS)
+#ifdef AUTH_TLS
   /* Check once per STARTTLS or SSL-on-connect for a TLS AUTH */
   if (  tls_in.active >= 0
      && tls_in.peercert
@@ -3544,6 +3561,12 @@ while (done <= 0)
     }
 #endif
 
+#ifdef TCP_QUICKACK
+  if (smtp_in)         /* Avoid pure-ACKs while in cmd pingpong phase */
+    (void) setsockopt(fileno(smtp_in), IPPROTO_TCP, TCP_QUICKACK,
+           US &off, sizeof(off));
+#endif
+
   switch(smtp_read_command(TRUE))
     {
     /* The AUTH command is not permitted to occur inside a transaction, and may
@@ -3922,7 +3945,7 @@ while (done <= 0)
       them in either case in the AUTH command. */
 
       if (  auths
-#if defined(SUPPORT_TLS) && defined(AUTH_TLS)
+#ifdef AUTH_TLS
         && !sender_host_authenticated
 #endif
          && verify_check_host(&auth_advertise_hosts) == OK
@@ -4647,20 +4670,22 @@ while (done <= 0)
     there may be a delay in this, re-check for a synchronization error
     afterwards, unless pipelining was advertised. */
 
-    if (recipients_discarded) rc = DISCARD; else
-      {
-      rc = acl_check(ACL_WHERE_RCPT, recipient, acl_smtp_rcpt, &user_msg,
-        &log_msg);
-      if (rc == OK && !pipelining_advertised && !check_sync())
+    if (recipients_discarded)
+      rc = DISCARD;
+    else
+      if (  (rc = acl_check(ACL_WHERE_RCPT, recipient, acl_smtp_rcpt, &user_msg,
+                   &log_msg)) == OK
+        && !pipelining_advertised && !check_sync())
         goto SYNC_FAILURE;
-      }
 
     /* The ACL was happy */
 
     if (rc == OK)
       {
-      if (user_msg == NULL) smtp_printf("250 Accepted\r\n");
-        else smtp_user_msg(US"250", user_msg);
+      if (user_msg)
+        smtp_user_msg(US"250", user_msg);
+      else
+        smtp_printf("250 Accepted\r\n");
       receive_add_recipient(recipient, -1);
 
       /* Set the dsn flags in the recipients_list */
@@ -4676,8 +4701,10 @@ while (done <= 0)
 
     else if (rc == DISCARD)
       {
-      if (user_msg == NULL) smtp_printf("250 Accepted\r\n");
-        else smtp_user_msg(US"250", user_msg);
+      if (user_msg)
+        smtp_user_msg(US"250", user_msg);
+      else
+        smtp_printf("250 Accepted\r\n");
       rcpt_fail_count++;
       discarded = TRUE;
       log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> RCPT %s: "
@@ -4776,6 +4803,9 @@ while (done <= 0)
          smtp_connection_had[smtp_ch_index-1] == SCH_DATA
          ? US"valid RCPT command must precede DATA"
          : US"valid RCPT command must precede BDAT");
+
+      if (chunking_state > CHUNKING_OFFERED)
+       bdat_flush_data();
       break;
       }
 
@@ -4788,9 +4818,7 @@ while (done <= 0)
       }
 
     if (chunking_state > CHUNKING_OFFERED)
-      {                                /* No predata ACL or go-ahead output for BDAT */
-      rc = OK;
-      }
+      rc = OK;                 /* No predata ACL or go-ahead output for BDAT */
     else
       {
       /* If there is an ACL, re-check the synchronization afterwards, since the
@@ -4823,6 +4851,11 @@ while (done <= 0)
          "354 Enter message, ending with \".\" on a line by itself\r\n");
       }
 
+#ifdef TCP_QUICKACK
+    if (smtp_in)       /* all ACKs needed to ramp window up for bulk data */
+      (void) setsockopt(fileno(smtp_in), IPPROTO_TCP, TCP_QUICKACK,
+             US &on, sizeof(on));
+#endif
     done = 3;
     message_ended = END_NOTENDED;   /* Indicate in middle of data */
 
@@ -5011,45 +5044,39 @@ while (done <= 0)
     set, but we must still reject all incoming commands. */
 
     DEBUG(D_tls) debug_printf("TLS failed to start\n");
-    while (done <= 0)
+    while (done <= 0) switch(smtp_read_command(FALSE))
       {
-      switch(smtp_read_command(FALSE))
-        {
-        case EOF_CMD:
-        log_write(L_smtp_connection, LOG_MAIN, "%s closed by EOF",
-          smtp_get_connection_info());
-        smtp_notquit_exit(US"tls-failed", NULL, NULL);
-        done = 2;
-        break;
-
-        /* It is perhaps arguable as to which exit ACL should be called here,
-        but as it is probably a situation that almost never arises, it
-        probably doesn't matter. We choose to call the real QUIT ACL, which in
-        some sense is perhaps "right". */
+      case EOF_CMD:
+       log_write(L_smtp_connection, LOG_MAIN, "%s closed by EOF",
+         smtp_get_connection_info());
+       smtp_notquit_exit(US"tls-failed", NULL, NULL);
+       done = 2;
+       break;
 
-        case QUIT_CMD:
-        user_msg = NULL;
-        if (acl_smtp_quit != NULL)
-          {
-          rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, &user_msg,
-            &log_msg);
-          if (rc == ERROR)
-            log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s",
-              log_msg);
-          }
-        if (user_msg == NULL)
-          smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
-        else
-          smtp_respond(US"221", 3, TRUE, user_msg);
-        log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
-          smtp_get_connection_info());
-        done = 2;
-        break;
+      /* It is perhaps arguable as to which exit ACL should be called here,
+      but as it is probably a situation that almost never arises, it
+      probably doesn't matter. We choose to call the real QUIT ACL, which in
+      some sense is perhaps "right". */
+
+      case QUIT_CMD:
+       user_msg = NULL;
+       if (  acl_smtp_quit
+          && ((rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, &user_msg,
+                             &log_msg)) == ERROR))
+           log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s",
+             log_msg);
+       if (user_msg)
+         smtp_respond(US"221", 3, TRUE, user_msg);
+       else
+         smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
+       log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
+         smtp_get_connection_info());
+       done = 2;
+       break;
 
-        default:
-        smtp_printf("554 Security failure\r\n");
-        break;
-        }
+      default:
+       smtp_printf("554 Security failure\r\n");
+       break;
       }
     tls_close(TRUE, TRUE);
     break;