Basic cutthrough delivery.
[exim.git] / src / src / smtp_in.c
index ca198af2932ac975bdea4240f69b8276c1237f7c..38162cd4cc42319af2427d6a6707f65680d878e3 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/smtp_in.c,v 1.59 2007/07/04 10:37:03 ph10 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2007 */
+/* Copyright (c) University of Cambridge 1995 - 2012 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for handling an incoming SMTP call. */
@@ -31,15 +29,21 @@ including that header, and restore its value afterwards. */
 
 int allow_severity = LOG_INFO;
 int deny_severity  = LOG_NOTICE;
+uschar *tcp_wrappers_name;
 #endif
 
 
 /* Size of buffer for reading SMTP commands. We used to use 512, as defined
 by RFC 821. However, RFC 1869 specifies that this must be increased for SMTP
 commands that accept arguments, and this in particular applies to AUTH, where
-the data can be quite long. */
+the data can be quite long.  More recently this value was 2048 in Exim; 
+however, RFC 4954 (circa 2007) recommends 12288 bytes to handle AUTH.  Clients
+such as Thunderbird will send an AUTH with an initial-response for GSSAPI. 
+The maximum size of a Kerberos ticket under Windows 2003 is 12000 bytes, and 
+we need room to handle large base64-encoded AUTHs for GSSAPI.
+*/
 
-#define smtp_cmd_buffer_size  2048
+#define smtp_cmd_buffer_size  16384
 
 /* Size of buffer for reading SMTP incoming packets */
 
@@ -48,7 +52,7 @@ the data can be quite long. */
 /* Structure for SMTP command list */
 
 typedef struct {
-  char *name;
+  const char *name;
   int len;
   short int cmd;
   short int has_arg;
@@ -123,6 +127,7 @@ static BOOL pipelining_advertised;
 static BOOL rcpt_smtp_response_same;
 static BOOL rcpt_in_progress;
 static int  nonmail_command_count;
+static BOOL smtp_exit_function_called = 0;
 static int  synprot_error_count;
 static int  unknown_command_count;
 static int  sync_cmd_limit;
@@ -202,6 +207,24 @@ static uschar *protocols[] = {
 #define pauthed  2  /* added to pextend */
 #define pnlocal  6  /* offset to remove "local" */
 
+/* Sanity check and validate optional args to MAIL FROM: envelope */
+enum {
+  ENV_MAIL_OPT_SIZE, ENV_MAIL_OPT_BODY, ENV_MAIL_OPT_AUTH,
+  ENV_MAIL_OPT_PRDR, ENV_MAIL_OPT_NULL
+  };
+typedef struct {
+  uschar *   name;  /* option requested during MAIL cmd */
+  int       value;  /* enum type */
+  BOOL need_value;  /* TRUE requires value (name=value pair format)
+                       FALSE is a singleton */
+  } env_mail_type_t;
+static env_mail_type_t env_mail_type_list[] = {
+    { US"SIZE",   ENV_MAIL_OPT_SIZE,   TRUE  },
+    { US"BODY",   ENV_MAIL_OPT_BODY,   TRUE  },
+    { US"AUTH",   ENV_MAIL_OPT_AUTH,   TRUE  },
+    { US"NULL",   ENV_MAIL_OPT_NULL,   FALSE }  /* Placeholder for ending */
+  };
+
 /* When reading SMTP from a remote host, we have to use our own versions of the
 C input-reading functions, in order to be able to flush the SMTP output only
 when about to read more data from the socket. This is the only way to get
@@ -263,6 +286,9 @@ if (smtp_inptr >= smtp_inend)
     else smtp_had_eof = 1;
     return EOF;
     }
+#ifndef DISABLE_DKIM
+  dkim_exim_verify_feed(smtp_inbuffer, rc);
+#endif
   smtp_inend = smtp_inbuffer + rc;
   smtp_inptr = smtp_inbuffer;
   }
@@ -371,30 +397,44 @@ Returns:      nothing
 */
 
 void
-smtp_printf(char *format, ...)
+smtp_printf(const char *format, ...)
 {
 va_list ap;
 
+va_start(ap, format);
+smtp_vprintf(format, ap);
+va_end(ap);
+}
+
+/* This is split off so that verify.c:respond_printf() can, in effect, call
+smtp_printf(), bearing in mind that in C a vararg function can't directly
+call another vararg function, only a function which accepts a va_list. */
+
+void
+smtp_vprintf(const char *format, va_list ap)
+{
+BOOL yield;
+
+yield = string_vformat(big_buffer, big_buffer_size, format, ap);
+
 DEBUG(D_receive)
   {
-  uschar *cr, *end;
-  va_start(ap, format);
-  (void) string_vformat(big_buffer, big_buffer_size, format, ap);
-  va_end(ap);
-  end = big_buffer + Ustrlen(big_buffer);
-  while ((cr = Ustrchr(big_buffer, '\r')) != NULL)   /* lose CRs */
-    memmove(cr, cr + 1, (end--) - cr);
-  debug_printf("SMTP>> %s", big_buffer);
+  void *reset_point = store_get(0);
+  uschar *msg_copy, *cr, *end;
+  msg_copy = string_copy(big_buffer);
+  end = msg_copy + Ustrlen(msg_copy);
+  while ((cr = Ustrchr(msg_copy, '\r')) != NULL)   /* lose CRs */
+  memmove(cr, cr + 1, (end--) - cr);
+  debug_printf("SMTP>> %s", msg_copy);
+  store_reset(reset_point);
   }
 
-va_start(ap, format);
-if (!string_vformat(big_buffer, big_buffer_size, format, ap))
+if (!yield)
   {
   log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_printf()");
   smtp_closedown(US"Unexpected error");
   exim_exit(EXIT_FAILURE);
   }
-va_end(ap);
 
 /* If this is the first output for a (non-batch) RCPT command, see if all RCPTs
 have had the same. Note: this code is also present in smtp_respond(). It would
@@ -470,9 +510,8 @@ log_write(L_lost_incoming_connection,
           host_and_ident(FALSE));
 if (smtp_batched_input)
   moan_smtp_batch(NULL, "421 SMTP command timeout");  /* Does not return */
-smtp_printf("421 %s: SMTP command timeout - closing connection\r\n",
-  smtp_active_hostname);
-mac_smtp_fflush();
+smtp_notquit_exit(US"command-timeout", US"421",
+  US"%s: SMTP command timeout - closing connection", smtp_active_hostname);
 exim_exit(EXIT_FAILURE);
 }
 
@@ -495,8 +534,8 @@ sig = sig;    /* Keep picky compilers happy */
 log_write(0, LOG_MAIN, "%s closed after SIGTERM", smtp_get_connection_info());
 if (smtp_batched_input)
   moan_smtp_batch(NULL, "421 SIGTERM received");  /* Does not return */
-smtp_printf("421 %s: Service not available - closing connection\r\n",
-  smtp_active_hostname);
+smtp_notquit_exit(US"signal-exit", US"421",
+  US"%s: Service not available - closing connection", smtp_active_hostname);
 exim_exit(EXIT_FAILURE);
 }
 
@@ -702,7 +741,9 @@ phase, sends the reply string, and gives an error to all subsequent commands
 except QUIT. The existence of an SMTP call is detected by the non-NULLness of
 smtp_in.
 
-Argument:   SMTP reply string to send, excluding the code
+Arguments:
+  message   SMTP reply string to send, excluding the code
+
 Returns:    nothing
 */
 
@@ -816,7 +857,11 @@ if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
   s = string_append(s, &size, &ptr, 2, US" CV=",
     tls_certificate_verified? "yes":"no");
 if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_peerdn != NULL)
-  s = string_append(s, &size, &ptr, 3, US" DN=\"", tls_peerdn, US"\"");
+  s = string_append(s, &size, &ptr, 3, US" DN=\"",
+    string_printing(tls_peerdn), US"\"");
+if ((log_extra_selector & LX_tls_sni) != 0 && tls_sni != NULL)
+  s = string_append(s, &size, &ptr, 3, US" SNI=\"",
+    string_printing(tls_sni), US"\"");
 #endif
 
 sep = (smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE)?
@@ -959,7 +1004,8 @@ if (*v != '=') return FALSE;
 n = v;
 while(isalpha(n[-1])) n--;
 
-if (n[-1] != ' ') return FALSE;
+/* RFC says SP, but TAB seen in wild and other major MTAs accept it */
+if (!isspace(n[-1])) return FALSE;
 
 n[-1] = 0;
 *name = n;
@@ -990,6 +1036,7 @@ store_reset(reset_point);
 recipients_list = NULL;
 rcpt_count = rcpt_defer_count = rcpt_fail_count =
   raw_recipients_count = recipients_count = recipients_list_max = 0;
+cancel_cutthrough_connection();
 message_linecount = 0;
 message_size = -1;
 acl_added_headers = NULL;
@@ -1004,7 +1051,7 @@ fake_response = OK;                                  /* Can be set by ACL */
 no_mbox_unspool = FALSE;                             /* Can be set by ACL */
 #endif
 submission_mode = FALSE;                             /* Can be set by ACL */
-suppress_local_fixups = FALSE;                       /* Can be set by ACL */
+suppress_local_fixups = suppress_local_fixups_default; /* Can be set by ACL */
 active_local_from_check = local_from_check;          /* Can be set by ACL */
 active_local_sender_retain = local_sender_retain;    /* Can be set by ACL */
 sender_address = NULL;
@@ -1019,8 +1066,10 @@ authenticated_sender = NULL;
 bmi_run = 0;
 bmi_verdicts = NULL;
 #endif
-#ifdef EXPERIMENTAL_DOMAINKEYS
-dk_do_verify = 0;
+#ifndef DISABLE_DKIM
+dkim_signers = NULL;
+dkim_disable_verify = FALSE;
+dkim_collect_input = FALSE;
 #endif
 #ifdef EXPERIMENTAL_SPF
 spf_header_comment = NULL;
@@ -1344,6 +1393,7 @@ auth_advertised = FALSE;
 pipelining_advertised = FALSE;
 pipelining_enable = TRUE;
 sync_cmd_limit = NON_SYNC_CMD_NON_PIPELINING;
+smtp_exit_function_called = FALSE;    /* For avoiding loop in not-quit exit */
 
 memset(sender_host_cache, 0, sizeof(sender_host_cache));
 
@@ -1368,6 +1418,7 @@ smtp_cmd_buffer = (uschar *)malloc(2*smtp_cmd_buffer_size + 2);
 if (smtp_cmd_buffer == NULL)
   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;
 
 /* For batched input, the protocol setting can be overridden from the
@@ -1642,8 +1693,7 @@ if (!sender_host_unknown)
 
   #ifdef SUPPORT_TLS
   if (tls_on_connect &&
-      tls_server_start(tls_require_ciphers,
-        gnutls_require_mac, gnutls_require_kx, gnutls_require_proto) != OK)
+      tls_server_start(tls_require_ciphers) != OK)
     return FALSE;
   #endif
 
@@ -1668,7 +1718,14 @@ if (!sender_host_unknown)
 
   #ifdef USE_TCP_WRAPPERS
   errno = 0;
-  if (!hosts_ctl("exim",
+  tcp_wrappers_name = expand_string(tcp_wrappers_daemon_name);
+  if (tcp_wrappers_name == NULL)
+    {
+    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Expansion of \"%s\" "
+      "(tcp_wrappers_name) failed: %s", string_printing(tcp_wrappers_name),
+        expand_string_message);
+    }
+  if (!hosts_ctl(tcp_wrappers_name,
          (sender_host_name == NULL)? STRING_UNKNOWN : CS sender_host_name,
          (sender_host_address == NULL)? STRING_UNKNOWN : CS sender_host_address,
          (sender_ident == NULL)? STRING_UNKNOWN : CS sender_ident))
@@ -2166,8 +2223,13 @@ if (where == ACL_WHERE_RCPT || where == ACL_WHERE_DATA)
 if (where == ACL_WHERE_RCPT || where == ACL_WHERE_DATA || where == ACL_WHERE_MIME)
 #endif
   {
-  sender_info = string_sprintf("F=<%s> ", (sender_address_unrewritten != NULL)?
-    sender_address_unrewritten : sender_address);
+  sender_info = string_sprintf("F=<%s>%s%s%s%s ",
+    sender_address_unrewritten ? sender_address_unrewritten : sender_address,
+    sender_host_authenticated ? US" A="                                    : US"",
+    sender_host_authenticated ? sender_host_authenticated                  : US"",
+    sender_host_authenticated && authenticated_id ? US":"                  : US"",
+    sender_host_authenticated && authenticated_id ? authenticated_id       : US""
+    );
   }
 
 /* If there's been a sender verification failure with a specific message, and
@@ -2266,12 +2328,98 @@ if (!drop) return 0;
 
 log_write(L_smtp_connection, LOG_MAIN, "%s closed by DROP in ACL",
   smtp_get_connection_info());
+
+/* Run the not-quit ACL, but without any custom messages. This should not be a
+problem, because we get here only if some other ACL has issued "drop", and
+in that case, *its* custom messages will have been used above. */
+
+smtp_notquit_exit(US"acl-drop", NULL, NULL);
 return 2;
 }
 
 
 
 
+/*************************************************
+*     Handle SMTP exit when QUIT is not given    *
+*************************************************/
+
+/* This function provides a logging/statistics hook for when an SMTP connection
+is dropped on the floor or the other end goes away. It's a global function
+because it's called from receive.c as well as this module. As well as running
+the NOTQUIT ACL, if there is one, this function also outputs a final SMTP
+response, either with a custom message from the ACL, or using a default. There
+is one case, however, when no message is output - after "drop". In that case,
+the ACL that obeyed "drop" has already supplied the custom message, and NULL is
+passed to this function.
+
+In case things go wrong while processing this function, causing an error that
+may re-enter this funtion, there is a recursion check.
+
+Arguments:
+  reason          What $smtp_notquit_reason will be set to in the ACL;
+                    if NULL, the ACL is not run
+  code            The error code to return as part of the response
+  defaultrespond  The default message if there's no user_msg
+
+Returns:          Nothing
+*/
+
+void
+smtp_notquit_exit(uschar *reason, uschar *code, uschar *defaultrespond, ...)
+{
+int rc;
+uschar *user_msg = NULL;
+uschar *log_msg = NULL;
+
+/* Check for recursive acll */
+
+if (smtp_exit_function_called)
+  {
+  log_write(0, LOG_PANIC, "smtp_notquit_exit() called more than once (%s)",
+    reason);
+  return;
+  }
+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)
+  {
+  smtp_notquit_reason = reason;
+  rc = acl_check(ACL_WHERE_NOTQUIT, NULL, acl_smtp_notquit, &user_msg,
+    &log_msg);
+  if (rc == ERROR)
+    log_write(0, LOG_MAIN|LOG_PANIC, "ACL for not-QUIT returned ERROR: %s",
+      log_msg);
+  }
+
+/* Write an SMTP response if we are expected to give one. As the default
+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 (user_msg == NULL)
+    {
+    uschar buffer[128];
+    va_list ap;
+    va_start(ap, defaultrespond);
+    if (!string_vformat(buffer, sizeof(buffer), CS defaultrespond, ap))
+      log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_notquit_exit()");
+    smtp_printf("%s %s\r\n", code, buffer);
+    va_end(ap);
+    }
+  else
+    smtp_respond(code, 3, TRUE, user_msg);
+  mac_smtp_fflush();
+  }
+}
+
+
+
+
 /*************************************************
 *             Verify HELO argument               *
 *************************************************/
@@ -3079,6 +3227,7 @@ while (done <= 0)
     HAD(SCH_MAIL);
     smtp_mailcmd_count++;              /* Count for limit and ratelimit */
     was_rej_mail = TRUE;               /* Reset if accepted */
+    env_mail_type_t * mail_args;       /* Sanity check & validate args */
 
     if (helo_required && !helo_seen)
       {
@@ -3127,113 +3276,137 @@ while (done <= 0)
       {
       uschar *name, *value, *end;
       unsigned long int size;
+      BOOL arg_error = FALSE;
 
       if (!extract_option(&name, &value)) break;
 
-      /* Handle SIZE= by reading the value. We don't do the check till later,
-      in order to be able to log the sender address on failure. */
-
-      if (strcmpic(name, US"SIZE") == 0 &&
-          ((size = Ustrtoul(value, &end, 10)), *end == 0))
+      for (mail_args = env_mail_type_list;
+           (char *)mail_args < (char *)env_mail_type_list + sizeof(env_mail_type_list);
+           mail_args++
+          )
         {
-        if ((size == ULONG_MAX && errno == ERANGE) || size > INT_MAX)
-          size = INT_MAX;
-        message_size = (int)size;
+        if (strcmpic(name, mail_args->name) == 0)
+          break;
         }
+      if (mail_args->need_value && strcmpic(value, US"") == 0)
+        break;
+      /* This doesn't seem right to use
+        if ((char *)mail_args >= (char *)env_mail_type_list + sizeof(env_mail_type_list))
+        goto BAD_MAIL_ARGS;
+      */
 
-      /* If this session was initiated with EHLO and accept_8bitmime is set,
-      Exim will have indicated that it supports the BODY=8BITMIME option. In
-      fact, it does not support this according to the RFCs, in that it does not
-      take any special action for forwarding messages containing 8-bit
-      characters. That is why accept_8bitmime is not the default setting, but
-      some sites want the action that is provided. We recognize both "8BITMIME"
-      and "7BIT" as body types, but take no action. */
-
-      else if (accept_8bitmime && strcmpic(name, US"BODY") == 0 &&
-          (strcmpic(value, US"8BITMIME") == 0 ||
-           strcmpic(value, US"7BIT") == 0)) {}
-
-      /* Handle the AUTH extension. If the value given is not "<>" and either
-      the ACL says "yes" or there is no ACL but the sending host is
-      authenticated, we set it up as the authenticated sender. However, if the
-      authenticator set a condition to be tested, we ignore AUTH on MAIL unless
-      the condition is met. The value of AUTH is an xtext, which means that +,
-      = and cntrl chars are coded in hex; however "<>" is unaffected by this
-      coding. */
-
-      else if (strcmpic(name, US"AUTH") == 0)
+      switch(mail_args->value)
         {
-        if (Ustrcmp(value, "<>") != 0)
-          {
-          int rc;
-          uschar *ignore_msg;
-
-          if (auth_xtextdecode(value, &authenticated_sender) < 0)
-            {
-            /* Put back terminator overrides for error message */
-            name[-1] = ' ';
-            value[-1] = '=';
-            done = synprot_error(L_smtp_syntax_error, 501, NULL,
-              US"invalid data for AUTH");
-            goto COMMAND_LOOP;
-            }
-
-          if (acl_smtp_mailauth == NULL)
+        /* Handle SIZE= by reading the value. We don't do the check till later,
+        in order to be able to log the sender address on failure. */
+        case ENV_MAIL_OPT_SIZE:
+          /* if (strcmpic(name, US"SIZE") == 0 && */
+          if (((size = Ustrtoul(value, &end, 10)), *end == 0))
             {
-            ignore_msg = US"client not authenticated";
-            rc = (sender_host_authenticated != NULL)? OK : FAIL;
+            if ((size == ULONG_MAX && errno == ERANGE) || size > INT_MAX)
+              size = INT_MAX;
+            message_size = (int)size;
             }
           else
-            {
-            ignore_msg = US"rejected by ACL";
-            rc = acl_check(ACL_WHERE_MAILAUTH, NULL, acl_smtp_mailauth,
-              &user_msg, &log_msg);
-            }
+            arg_error = TRUE;
+          break;
 
-          switch (rc)
-            {
-            case OK:
-            if (authenticated_by == NULL ||
-                authenticated_by->mail_auth_condition == NULL ||
-                expand_check_condition(authenticated_by->mail_auth_condition,
-                    authenticated_by->name, US"authenticator"))
-              break;     /* Accept the AUTH */
-
-            ignore_msg = US"server_mail_auth_condition failed";
-            if (authenticated_id != NULL)
-              ignore_msg = string_sprintf("%s: authenticated ID=\"%s\"",
-                ignore_msg, authenticated_id);
-
-            /* Fall through */
-
-            case FAIL:
-            authenticated_sender = NULL;
-            log_write(0, LOG_MAIN, "ignoring AUTH=%s from %s (%s)",
-              value, host_and_ident(TRUE), ignore_msg);
+        /* If this session was initiated with EHLO and accept_8bitmime is set,
+        Exim will have indicated that it supports the BODY=8BITMIME option. In
+        fact, it does not support this according to the RFCs, in that it does not
+        take any special action for forwarding messages containing 8-bit
+        characters. That is why accept_8bitmime is not the default setting, but
+        some sites want the action that is provided. We recognize both "8BITMIME"
+        and "7BIT" as body types, but take no action. */
+        case ENV_MAIL_OPT_BODY:
+          if (accept_8bitmime &&
+              (strcmpic(value, US"8BITMIME") == 0 ||
+               strcmpic(value, US"7BIT") == 0) )
             break;
+          arg_error = TRUE;
+          break;
 
-            /* Should only get DEFER or ERROR here. Put back terminator
-            overrides for error message */
+        /* Handle the AUTH extension. If the value given is not "<>" and either
+        the ACL says "yes" or there is no ACL but the sending host is
+        authenticated, we set it up as the authenticated sender. However, if the
+        authenticator set a condition to be tested, we ignore AUTH on MAIL unless
+        the condition is met. The value of AUTH is an xtext, which means that +,
+        = and cntrl chars are coded in hex; however "<>" is unaffected by this
+        coding. */
+        case ENV_MAIL_OPT_AUTH:
+          if (Ustrcmp(value, "<>") != 0)
+            {
+            int rc;
+            uschar *ignore_msg;
 
-            default:
-            name[-1] = ' ';
-            value[-1] = '=';
-            (void)smtp_handle_acl_fail(ACL_WHERE_MAILAUTH, rc, user_msg,
-              log_msg);
-            goto COMMAND_LOOP;
+            if (auth_xtextdecode(value, &authenticated_sender) < 0)
+              {
+              /* Put back terminator overrides for error message */
+              name[-1] = ' ';
+              value[-1] = '=';
+              done = synprot_error(L_smtp_syntax_error, 501, NULL,
+                US"invalid data for AUTH");
+              goto COMMAND_LOOP;
+              }
+            if (acl_smtp_mailauth == NULL)
+              {
+              ignore_msg = US"client not authenticated";
+              rc = (sender_host_authenticated != NULL)? OK : FAIL;
+              }
+            else
+              {
+              ignore_msg = US"rejected by ACL";
+              rc = acl_check(ACL_WHERE_MAILAUTH, NULL, acl_smtp_mailauth,
+                &user_msg, &log_msg);
+              }
+  
+            switch (rc)
+              {
+              case OK:
+              if (authenticated_by == NULL ||
+                  authenticated_by->mail_auth_condition == NULL ||
+                  expand_check_condition(authenticated_by->mail_auth_condition,
+                      authenticated_by->name, US"authenticator"))
+                break;     /* Accept the AUTH */
+  
+              ignore_msg = US"server_mail_auth_condition failed";
+              if (authenticated_id != NULL)
+                ignore_msg = string_sprintf("%s: authenticated ID=\"%s\"",
+                  ignore_msg, authenticated_id);
+  
+              /* Fall through */
+  
+              case FAIL:
+              authenticated_sender = NULL;
+              log_write(0, LOG_MAIN, "ignoring AUTH=%s from %s (%s)",
+                value, host_and_ident(TRUE), ignore_msg);
+              break;
+  
+              /* Should only get DEFER or ERROR here. Put back terminator
+              overrides for error message */
+  
+              default:
+              name[-1] = ' ';
+              value[-1] = '=';
+              (void)smtp_handle_acl_fail(ACL_WHERE_MAILAUTH, rc, user_msg,
+                log_msg);
+              goto COMMAND_LOOP;
+              }
             }
-          }
-        }
-
-      /* Unknown option. Stick back the terminator characters and break
-      the loop. An error for a malformed address will occur. */
+            break;
+        /* Unknown option. Stick back the terminator characters and break
+        the loop. An error for a malformed address will occur. */
+        default:
 
-      else
-        {
-        name[-1] = ' ';
-        value[-1] = '=';
-        break;
+          /* BAD_MAIL_ARGS: */
+          name[-1] = ' ';
+          value[-1] = '=';
+          break;
         }
+      /* Break out of for loop if switch() had bad argument or
+         when start of the email address is reached */
+      if (arg_error) break;
       }
 
     /* If we have passed the threshold for rate limiting, apply the current
@@ -3727,6 +3900,32 @@ while (done <= 0)
     toomany = FALSE;
     cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = FALSE;
 
+    /* There's an attack where more data is read in past the STARTTLS command
+    before TLS is negotiated, then assumed to be part of the secure session
+    when used afterwards; we use segregated input buffers, so are not
+    vulnerable, but we want to note when it happens and, for sheer paranoia,
+    ensure that the buffer is "wiped".
+    Pipelining sync checks will normally have protected us too, unless disabled
+    by configuration. */
+
+    if (receive_smtp_buffered())
+      {
+      DEBUG(D_any)
+        debug_printf("Non-empty input buffer after STARTTLS; naive attack?");
+      if (tls_active < 0)
+        smtp_inend = smtp_inptr = smtp_inbuffer;
+      /* and if TLS is already active, tls_server_start() should fail */
+      }
+
+    /* There is nothing we value in the input buffer and if TLS is succesfully
+    negotiated, we won't use this buffer again; if TLS fails, we'll just read
+    fresh content into it.  The buffer contains arbitrary content from an
+    untrusted remote source; eg: NOOP <shellcode>\r\nSTARTTLS\r\n
+    It seems safest to just wipe away the content rather than leave it as a
+    target to jump to. */
+
+    memset(smtp_inbuffer, 0, in_buffer_size);
+
     /* Attempt to start up a TLS session, and if successful, discard all
     knowledge that was obtained previously. At least, that's what the RFC says,
     and that's what happens by default. However, in order to work round YAEB,
@@ -3735,8 +3934,7 @@ while (done <= 0)
     We must allow for an extra EHLO command and an extra AUTH command after
     STARTTLS that don't add to the nonmail command count. */
 
-    if ((rc = tls_server_start(tls_require_ciphers, gnutls_require_mac,
-           gnutls_require_kx, gnutls_require_proto)) == OK)
+    if ((rc = tls_server_start(tls_require_ciphers)) == OK)
       {
       if (!tls_remember_esmtp)
         helo_seen = esmtp = auth_advertised = pipelining_advertised = FALSE;
@@ -3786,11 +3984,29 @@ while (done <= 0)
         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 situtation 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:
-        smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
+        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;
@@ -3813,15 +4029,13 @@ while (done <= 0)
     case QUIT_CMD:
     HAD(SCH_QUIT);
     incomplete_transaction_log(US"QUIT");
-
     if (acl_smtp_quit != NULL)
       {
-      rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit,&user_msg,&log_msg);
+      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
@@ -3882,7 +4096,8 @@ while (done <= 0)
 
     case EOF_CMD:
     incomplete_transaction_log(US"connection lost");
-    smtp_printf("421 %s lost input connection\r\n", smtp_active_hostname);
+    smtp_notquit_exit(US"connection-lost", US"421",
+      US"%s lost input connection", smtp_active_hostname);
 
     /* Don't log by default unless in the middle of a message, as some mailers
     just drop the call rather than sending QUIT, and it clutters up the logs.
@@ -4088,7 +4303,8 @@ while (done <= 0)
       pipelining_advertised? "" : " not",
       smtp_cmd_buffer, host_and_ident(TRUE),
       string_printing(smtp_inptr));
-    smtp_printf("554 SMTP synchronization error\r\n");
+    smtp_notquit_exit(US"synchronization-error", US"554",
+      US"SMTP synchronization error");
     done = 1;   /* Pretend eof - drops connection */
     break;
 
@@ -4100,7 +4316,7 @@ while (done <= 0)
     log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
       "nonmail commands (last was \"%.*s\")",  host_and_ident(FALSE),
       s - smtp_cmd_buffer, smtp_cmd_buffer);
-    smtp_printf("554 Too many nonmail commands\r\n");
+    smtp_notquit_exit(US"bad-commands", US"554", US"Too many nonmail commands");
     done = 1;   /* Pretend eof - drops connection */
     break;
 
@@ -4113,7 +4329,8 @@ while (done <= 0)
         string_printing(smtp_cmd_buffer), host_and_ident(TRUE),
         US"unrecognized command");
       incomplete_transaction_log(US"unrecognized command");
-      smtp_printf("500 Too many unrecognized commands\r\n");
+      smtp_notquit_exit(US"bad-commands", US"500",
+        US"Too many unrecognized commands");
       done = 2;
       log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
         "unrecognized commands (last was \"%s\")", host_and_ident(FALSE),