TLS: downgrade fail of cert-based authentication from log message to debug messsage
[users/jgh/exim.git] / src / src / receive.c
index ee95cc98120aea90b184c9a9bc76a52bb6ca2748..b430ee26197754e50e90611dc1e4e5c8e97875ad 100644 (file)
@@ -1,24 +1,22 @@
-/* $Cambridge: exim/src/src/receive.c,v 1.38 2007/06/22 14:38:58 ph10 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2007 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Code for receiving a message and setting up spool files. */
 
 #include "exim.h"
 
-#ifdef EXPERIMENTAL_DOMAINKEYS
-#define RECEIVE_GETC dk_receive_getc
-#define RECEIVE_UNGETC dk_receive_ungetc
-#else
-#define RECEIVE_GETC receive_getc
-#define RECEIVE_UNGETC receive_ungetc
+#ifdef EXPERIMENTAL_DCC
+extern int dcc_ok;
 #endif
 
+#ifdef EXPERIMENTAL_DMARC
+# include "dmarc.h"
+#endif /* EXPERIMENTAL_DMARC */
+
 /*************************************************
 *                Local static variables          *
 *************************************************/
@@ -89,7 +87,7 @@ if (newsender == NULL || untrusted_set_sender == NULL) return FALSE;
 qnewsender = (Ustrchr(newsender, '@') != NULL)?
   newsender : string_sprintf("%s@%s", newsender, qualify_domain_sender);
 return
-  match_address_list(qnewsender, TRUE, TRUE, &untrusted_set_sender, NULL, -1,
+  match_address_list(qnewsender, TRUE, TRUE, CUSS &untrusted_set_sender, NULL, -1,
     0, NULL) == OK;
 }
 
@@ -144,17 +142,16 @@ appearance of "syslog" in it. */
 else
   {
   int sep = ':';              /* Not variable - outside scripts use */
-  uschar *p = log_file_path;
+  const uschar *p = log_file_path;
   name = US"log";
 
   /* An empty log_file_path means "use the default". This is the same as an
   empty item in a list. */
 
   if (*p == 0) p = US":";
-  while ((path = string_nextinlist(&p, &sep, buffer, sizeof(buffer))) != NULL)
-    {
-    if (Ustrcmp(path, "syslog") != 0) break;
-    }
+  while ((path = string_nextinlist(&p, &sep, buffer, sizeof(buffer))))
+    if (Ustrcmp(path, "syslog") != 0)
+      break;
 
   if (path == NULL)  /* No log files */
     {
@@ -178,7 +175,7 @@ else
     }
   }
 
-/* We now have the patch; do the business */
+/* We now have the path; do the business */
 
 memset(&statbuf, 0, sizeof(statbuf));
 
@@ -283,43 +280,59 @@ that case is done by setting a flag to cause the log functions to call this
 function if there is an ultimate disaster. That is why it is globally
 accessible.
 
-Arguments:   SMTP response to give if in an SMTP session
+Arguments:
+  reason     text reason to pass to the not-quit ACL
+  msg        default SMTP response to give if in an SMTP session
 Returns:     it doesn't
 */
 
 void
-receive_bomb_out(uschar *msg)
+receive_bomb_out(uschar *reason, uschar *msg)
 {
+  static BOOL already_bombing_out;
+/* The smtp_notquit_exit() below can call ACLs which can trigger recursive
+timeouts, if someone has something slow in their quit ACL.  Since the only
+things we should be doing are to close down cleanly ASAP, on the second
+pass we also close down stuff that might be opened again, before bypassing
+the ACL call and exiting. */
+
 /* If spool_name is set, it contains the name of the data file that is being
 written. Unlink it before closing so that it cannot be picked up by a delivery
 process. Ensure that any header file is also removed. */
 
-if (spool_name[0] != 0)
+if (spool_name[0] != '\0')
   {
   Uunlink(spool_name);
   spool_name[Ustrlen(spool_name) - 1] = 'H';
   Uunlink(spool_name);
+  spool_name[0] = '\0';
   }
 
 /* Now close the file if it is open, either as a fd or a stream. */
 
-if (data_file != NULL) (void)fclose(data_file);
-  else if (data_fd >= 0) (void)close(data_fd);
+if (data_file != NULL)
+  {
+  (void)fclose(data_file);
+  data_file = NULL;
+} else if (data_fd >= 0) {
+  (void)close(data_fd);
+  data_fd = -1;
+  }
 
-/* Attempt to close down an SMTP connection tidily. */
+/* Attempt to close down an SMTP connection tidily. For non-batched SMTP, call
+smtp_notquit_exit(), which runs the NOTQUIT ACL, if present, and handles the
+SMTP response. */
 
-if (smtp_input)
+if (!already_bombing_out)
   {
-  if (!smtp_batched_input)
+  already_bombing_out = TRUE;
+  if (smtp_input)
     {
-    smtp_printf("421 %s %s - closing connection.\r\n", smtp_active_hostname,
-      msg);
-    mac_smtp_fflush();
+    if (smtp_batched_input)
+      moan_smtp_batch(NULL, "421 %s - message abandoned", msg);  /* No return */
+    smtp_notquit_exit(reason, US"421", US"%s %s - closing connection.",
+      smtp_active_hostname, msg);
     }
-
-  /* Control does not return from moan_smtp_batch(). */
-
-  else moan_smtp_batch(NULL, "421 %s - message abandoned", msg);
   }
 
 /* Exit from the program (non-BSMTP cases) */
@@ -362,7 +375,7 @@ else
             LOG_MAIN, "timed out while reading local message");
   }
 
-receive_bomb_out(msg);   /* Does not return */
+receive_bomb_out(US"data-timeout", msg);   /* Does not return */
 }
 
 
@@ -384,7 +397,8 @@ local_scan_timeout_handler(int sig)
 sig = sig;    /* Keep picky compilers happy */
 log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() function timed out - "
   "message temporarily rejected (size %d)", message_size);
-receive_bomb_out(US"local verification problem");   /* Does not return */
+/* Does not return */
+receive_bomb_out(US"local-scan-timeout", US"local verification problem");
 }
 
 
@@ -405,7 +419,8 @@ local_scan_crash_handler(int sig)
 {
 log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() function crashed with "
   "signal %d - message temporarily rejected (size %d)", sig, message_size);
-receive_bomb_out(US"local verification problem");   /* Does not return */
+/* Does not return */
+receive_bomb_out(US"local-scan-error", US"local verification problem");
 }
 
 
@@ -442,7 +457,7 @@ else
     }
   }
 
-receive_bomb_out(msg);    /* Does not return */
+receive_bomb_out(US"signal-exit", msg);    /* Does not return */
 }
 
 
@@ -481,12 +496,44 @@ recipients_list[recipients_count].bmi_optin = bmi_current_optin;
 /* reset optin string pointer for next recipient */
 bmi_current_optin = NULL;
 #endif
+recipients_list[recipients_count].orcpt = NULL;
+recipients_list[recipients_count].dsn_flags = 0;
 recipients_list[recipients_count++].errors_to = NULL;
 }
 
 
 
 
+/*************************************************
+*        Send user response message              *
+*************************************************/
+
+/* This function is passed a default response code and a user message. It calls
+smtp_message_code() to check and possibly modify the response code, and then
+calls smtp_respond() to transmit the response. I put this into a function
+just to avoid a lot of repetition.
+
+Arguments:
+  code         the response code
+  user_msg     the user message
+
+Returns:       nothing
+*/
+
+#ifndef DISABLE_PRDR
+static void
+smtp_user_msg(uschar *code, uschar *user_msg)
+{
+int len = 3;
+smtp_message_code(&code, &len, &user_msg, NULL);
+smtp_respond(code, len, TRUE, user_msg);
+}
+#endif
+
+
+
+
+
 /*************************************************
 *        Remove a recipient from the list        *
 *************************************************/
@@ -572,7 +619,7 @@ if (!dot_ends)
   {
   register int last_ch = '\n';
 
-  for (; (ch = (RECEIVE_GETC)()) != EOF; last_ch = ch)
+  for (; (ch = (receive_getc)()) != EOF; last_ch = ch)
     {
     if (ch == 0) body_zerocount++;
     if (last_ch == '\r' && ch != '\n')
@@ -614,7 +661,7 @@ if (!dot_ends)
 
 ch_state = 1;
 
-while ((ch = (RECEIVE_GETC)()) != EOF)
+while ((ch = (receive_getc)()) != EOF)
   {
   if (ch == 0) body_zerocount++;
   switch (ch_state)
@@ -634,6 +681,7 @@ while ((ch = (RECEIVE_GETC)()) != EOF)
 
     case 1:                         /* After written "\n" */
     if (ch == '.') { ch_state = 3; continue; }
+    if (ch == '\r') { ch_state = 2; continue; }
     if (ch != '\n') ch_state = 0; else linelength = -1;
     break;
 
@@ -727,9 +775,10 @@ static int
 read_message_data_smtp(FILE *fout)
 {
 int ch_state = 0;
-register int ch;
+int ch;
+register int linelength = 0;
 
-while ((ch = (RECEIVE_GETC)()) != EOF)
+while ((ch = (receive_getc)()) != EOF)
   {
   if (ch == 0) body_zerocount++;
   switch (ch_state)
@@ -749,6 +798,9 @@ while ((ch = (RECEIVE_GETC)()) != EOF)
       {
       ch_state = 0;
       body_linecount++;
+      if (linelength > max_received_linelength)
+        max_received_linelength = linelength;
+      linelength = -1;
       }
     else if (ch == '\r')
       {
@@ -759,6 +811,9 @@ while ((ch = (RECEIVE_GETC)()) != EOF)
 
     case 2:                             /* After (unwritten) CR */
     body_linecount++;
+    if (linelength > max_received_linelength)
+      max_received_linelength = linelength;
+    linelength = -1;
     if (ch == '\n')
       {
       ch_state = 0;
@@ -767,6 +822,7 @@ while ((ch = (RECEIVE_GETC)()) != EOF)
       {
       message_size++;
       if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
+      (void) cutthrough_put_nl();
       if (ch != '\r') ch_state = 1; else continue;
       }
     break;
@@ -787,6 +843,7 @@ while ((ch = (RECEIVE_GETC)()) != EOF)
     message_size++;
     body_linecount++;
     if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
+    (void) cutthrough_put_nl();
     if (ch == '\r')
       {
       ch_state = 2;
@@ -800,11 +857,19 @@ while ((ch = (RECEIVE_GETC)()) != EOF)
   next. */
 
   message_size++;
+  linelength++;
   if (fout != NULL)
     {
     if (fputc(ch, fout) == EOF) return END_WERROR;
     if (message_size > thismessage_size_limit) return END_SIZE;
     }
+  if(ch == '\n')
+    (void) cutthrough_put_nl();
+  else
+    {
+    uschar c= ch;
+    (void) cutthrough_puts(&c, 1);
+    }
   }
 
 /* Fall through here if EOF encountered. This indicates some kind of error,
@@ -921,11 +986,46 @@ Returns:     nothing
 */
 
 static void
-add_acl_headers(uschar *acl_name)
+add_acl_headers(int where, uschar *acl_name)
 {
 header_line *h, *next;
 header_line *last_received = NULL;
 
+switch(where)
+  {
+  case ACL_WHERE_DKIM:
+  case ACL_WHERE_MIME:
+  case ACL_WHERE_DATA:
+    if (cutthrough.fd >= 0 && (acl_removed_headers || acl_added_headers))
+    {
+    log_write(0, LOG_MAIN|LOG_PANIC, "Header modification in data ACLs"
+                       " will not take effect on cutthrough deliveries");
+    return;
+    }
+  }
+
+if (acl_removed_headers != NULL)
+  {
+  DEBUG(D_receive|D_acl) debug_printf(">>Headers removed by %s ACL:\n", acl_name);
+
+  for (h = header_list; h != NULL; h = h->next) if (h->type != htype_old)
+    {
+    const uschar * list = acl_removed_headers;
+    int sep = ':';         /* This is specified as a colon-separated list */
+    uschar *s;
+    uschar buffer[128];
+
+    while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
+      if (header_testname(h, s, Ustrlen(s), FALSE))
+       {
+       h->type = htype_old;
+        DEBUG(D_receive|D_acl) debug_printf("  %s", h->text);
+       }
+    }
+  acl_removed_headers = NULL;
+  DEBUG(D_receive|D_acl) debug_printf(">>\n");
+  }
+
 if (acl_added_headers == NULL) return;
 DEBUG(D_receive|D_acl) debug_printf(">>Headers added by %s ACL:\n", acl_name);
 
@@ -1018,8 +1118,7 @@ add_host_info_for_log(uschar *s, int *sizeptr, int *ptrptr)
 if (sender_fullhost != NULL)
   {
   s = string_append(s, sizeptr, ptrptr, 2, US" H=", sender_fullhost);
-  if ((log_extra_selector & LX_incoming_interface) != 0 &&
-       interface_address != NULL)
+  if (LOGGING(incoming_interface) && interface_address != NULL)
     {
     uschar *ss = string_sprintf(" I=[%s]:%d", interface_address,
       interface_port);
@@ -1063,7 +1162,7 @@ unsigned long mbox_size;
 header_line *my_headerlist;
 uschar *user_msg, *log_msg;
 int mime_part_count_buffer = -1;
-int rc;
+int rc = OK;
 
 memset(CS rfc822_file_path,0,2048);
 
@@ -1090,13 +1189,16 @@ return TRUE;
 
 DO_MIME_ACL:
 /* make sure the eml mbox file is spooled up */
-mbox_file = spool_mbox(&mbox_size);
+mbox_file = spool_mbox(&mbox_size, NULL);
 if (mbox_file == NULL) {
   /* error while spooling */
   log_write(0, LOG_MAIN|LOG_PANIC,
          "acl_smtp_mime: error while creating mbox spool file, message temporarily rejected.");
   Uunlink(spool_name);
   unspool_mbox();
+#ifdef EXPERIMENTAL_DCC
+  dcc_ok = 0;
+#endif
   smtp_respond(US"451", 3, TRUE, US"temporary local problem");
   message_id[0] = 0;            /* Indicate no message accepted */
   *smtp_reply_ptr = US"";       /* Indicate reply already sent */
@@ -1126,47 +1228,45 @@ if (Ustrlen(rfc822_file_path) > 0)
 if (rc == OK)
   {
   uschar temp_path[1024];
-  int n;
-  struct dirent *entry;
-  DIR *tempdir;
+  struct dirent * entry;
+  DIR * tempdir;
 
-  (void)string_format(temp_path, 1024, "%s/scan/%s", spool_directory,
-    message_id);
+  (void) string_format(temp_path, sizeof(temp_path), "%s/scan/%s",
+    spool_directory, message_id);
 
   tempdir = opendir(CS temp_path);
-  n = 0;
-  do
+  for (;;)
     {
-    entry = readdir(tempdir);
-    if (entry == NULL) break;
-    if (strncmpic(US entry->d_name,US"__rfc822_",9) == 0)
+    if (!(entry = readdir(tempdir)))
+      break;
+    if (strncmpic(US entry->d_name, US"__rfc822_", 9) == 0)
       {
-      (void)string_format(rfc822_file_path, 2048,"%s/scan/%s/%s", spool_directory, message_id, entry->d_name);
-      debug_printf("RFC822 attachment detected: running MIME ACL for '%s'\n", rfc822_file_path);
+      (void) string_format(rfc822_file_path, sizeof(rfc822_file_path),
+       "%s/scan/%s/%s", spool_directory, message_id, entry->d_name);
+      debug_printf("RFC822 attachment detected: running MIME ACL for '%s'\n",
+       rfc822_file_path);
       break;
       }
-    } while (1);
+    }
   closedir(tempdir);
 
-  if (entry != NULL)
+  if (entry)
     {
-    mbox_file = Ufopen(rfc822_file_path,"rb");
-    if (mbox_file == NULL)
+    if ((mbox_file = Ufopen(rfc822_file_path, "rb")))
       {
-      log_write(0, LOG_PANIC,
-         "acl_smtp_mime: can't open RFC822 spool file, skipping.");
-      unlink(CS rfc822_file_path);
-      goto END_MIME_ACL;
+      /* set RFC822 expansion variable */
+      mime_is_rfc822 = 1;
+      mime_part_count_buffer = mime_part_count;
+      goto MIME_ACL_CHECK;
       }
-    /* set RFC822 expansion variable */
-    mime_is_rfc822 = 1;
-    mime_part_count_buffer = mime_part_count;
-    goto MIME_ACL_CHECK;
+    log_write(0, LOG_PANIC,
+       "acl_smtp_mime: can't open RFC822 spool file, skipping.");
+    unlink(CS rfc822_file_path);
     }
   }
 
 END_MIME_ACL:
-add_acl_headers(US"MIME");
+add_acl_headers(ACL_WHERE_MIME, US"MIME");
 if (rc == DISCARD)
   {
   recipients_count = 0;
@@ -1176,9 +1276,13 @@ else if (rc != OK)
   {
   Uunlink(spool_name);
   unspool_mbox();
-  if (smtp_handle_acl_fail(ACL_WHERE_MIME, rc, user_msg, log_msg) != 0)
+#ifdef EXPERIMENTAL_DCC
+  dcc_ok = 0;
+#endif
+  if (smtp_input && smtp_handle_acl_fail(ACL_WHERE_MIME, rc, user_msg, log_msg) != 0) {
     *smtp_yield_ptr = FALSE;    /* No more messsages after dropped connection */
-  *smtp_reply_ptr = US"";       /* Indicate reply already sent */
+    *smtp_reply_ptr = US"";     /* Indicate reply already sent */
+  }
   message_id[0] = 0;            /* Indicate no message accepted */
   return FALSE;                 /* Cause skip to end of receive function */
   }
@@ -1189,6 +1293,52 @@ return TRUE;
 #endif  /* WITH_CONTENT_SCAN */
 
 
+
+void
+received_header_gen(void)
+{
+uschar *received;
+uschar *timestamp;
+header_line *received_header= header_list;
+
+timestamp = expand_string(US"${tod_full}");
+if (recipients_count == 1) received_for = recipients_list[0].address;
+received = expand_string(received_header_text);
+received_for = NULL;
+
+if (received == NULL)
+  {
+  if(spool_name[0] != 0)
+    Uunlink(spool_name);           /* Lose the data file */
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Expansion of \"%s\" "
+    "(received_header_text) failed: %s", string_printing(received_header_text),
+      expand_string_message);
+  }
+
+/* The first element on the header chain is reserved for the Received header,
+so all we have to do is fill in the text pointer, and set the type. However, if
+the result of the expansion is an empty string, we leave the header marked as
+"old" so as to refrain from adding a Received header. */
+
+if (received[0] == 0)
+  {
+  received_header->text = string_sprintf("Received: ; %s\n", timestamp);
+  received_header->type = htype_old;
+  }
+else
+  {
+  received_header->text = string_sprintf("%s; %s\n", received, timestamp);
+  received_header->type = htype_received;
+  }
+
+received_header->slen = Ustrlen(received_header->text);
+
+DEBUG(D_receive) debug_printf(">>Generated Received: header line\n%c %s",
+  received_header->type, received_header->text);
+}
+
+
+
 /*************************************************
 *                 Receive message                *
 *************************************************/
@@ -1198,7 +1348,8 @@ Either a non-null list of recipients, or the extract flag will be true, or
 both. The flag sender_local is true for locally generated messages. The flag
 submission_mode is true if an ACL has obeyed "control = submission". The flag
 suppress_local_fixups is true if an ACL has obeyed "control =
-suppress_local_fixups". The flag smtp_input is true if the message is to be
+suppress_local_fixups" or -G was passed on the command-line.
+The flag smtp_input is true if the message is to be
 handled using SMTP conventions about termination and lines starting with dots.
 For non-SMTP messages, dot_ends is true for dot-terminated messages.
 
@@ -1281,7 +1432,8 @@ not. */
 BOOL
 receive_msg(BOOL extract_recip)
 {
-int  i, rc;
+int  i;
+int  rc = FAIL;
 int  msg_size = 0;
 int  process_info_len = Ustrlen(process_info);
 int  error_rc = (error_handling == ERRORS_SENDER)?
@@ -1304,6 +1456,7 @@ BOOL resents_exist = FALSE;
 uschar *resent_prefix = US"";
 uschar *blackholed_by = NULL;
 uschar *blackhole_log_msg = US"";
+enum {NOT_TRIED, TMP_REJ, PERM_REJ, ACCEPTED} cutthrough_done = NOT_TRIED;
 
 flock_t lock_data;
 error_block *bad_addresses = NULL;
@@ -1334,9 +1487,12 @@ header_line *subject_header = NULL;
 header_line *msgid_header = NULL;
 header_line *received_header;
 
+#ifdef EXPERIMENTAL_DMARC
+int dmarc_up = 0;
+#endif /* EXPERIMENTAL_DMARC */
+
 /* Variables for use when building the Received: header. */
 
-uschar *received;
 uschar *timestamp;
 int tslen;
 
@@ -1346,6 +1502,12 @@ might take a fair bit of real time. */
 
 search_tidyup();
 
+/* Extracting the recipient list from an input file is incompatible with
+cutthrough delivery with the no-spool option.  It shouldn't be possible
+to set up the combination, but just in case kill any ongoing connection. */
+if (extract_recip || !smtp_input)
+  cancel_cutthrough_connection("not smtp input");
+
 /* Initialize the chain of headers by setting up a place-holder for Received:
 header. Temporarily mark it as "old", i.e. not to be used. We keep header_last
 pointing to the end of the chain to make adding headers simple. */
@@ -1380,10 +1542,14 @@ if (thismessage_size_limit <= 0) thismessage_size_limit = INT_MAX;
 message_linecount = body_linecount = body_zerocount =
   max_received_linelength = 0;
 
-#ifdef EXPERIMENTAL_DOMAINKEYS
-/* Call into DK to set up the context. Check if DK is to be run are carried out
-   inside dk_exim_verify_init(). */
-dk_exim_verify_init();
+#ifndef DISABLE_DKIM
+/* Call into DKIM to set up the context. */
+if (smtp_input && !smtp_batched_input && !dkim_disable_verify) dkim_exim_verify_init();
+#endif
+
+#ifdef EXPERIMENTAL_DMARC
+/* initialize libopendmarc */
+dmarc_up = dmarc_init();
 #endif
 
 /* Remember the time of reception. Exim uses time+pid for uniqueness of message
@@ -1434,7 +1600,7 @@ next->text. */
 
 for (;;)
   {
-  int ch = (RECEIVE_GETC)();
+  int ch = (receive_getc)();
 
   /* If we hit EOF on a SMTP connection, it's an error, since incoming
   SMTP must have a correct "." terminator. */
@@ -1498,7 +1664,7 @@ for (;;)
   if (ch == '\n')
     {
     if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = FALSE;
-      else if (first_line_ended_crlf) RECEIVE_UNGETC(' ');
+      else if (first_line_ended_crlf) receive_ungetc(' ');
     goto EOL;
     }
 
@@ -1513,13 +1679,13 @@ for (;;)
 
   if (ptr == 0 && ch == '.' && (smtp_input || dot_ends))
     {
-    ch = (RECEIVE_GETC)();
+    ch = (receive_getc)();
     if (ch == '\r')
       {
-      ch = (RECEIVE_GETC)();
+      ch = (receive_getc)();
       if (ch != '\n')
         {
-        RECEIVE_UNGETC(ch);
+        receive_ungetc(ch);
         ch = '\r';              /* Revert to CR */
         }
       }
@@ -1547,7 +1713,7 @@ for (;;)
 
   if (ch == '\r')
     {
-    ch = (RECEIVE_GETC)();
+    ch = (receive_getc)();
     if (ch == '\n')
       {
       if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = TRUE;
@@ -1557,7 +1723,7 @@ for (;;)
     /* Otherwise, put back the character after CR, and turn the bare CR
     into LF SP. */
 
-    ch = (RECEIVE_UNGETC)(ch);
+    ch = (receive_ungetc)(ch);
     next->text[ptr++] = '\n';
     message_size++;
     ch = ' ';
@@ -1642,14 +1808,14 @@ for (;;)
 
   if (ch != EOF)
     {
-    int nextch = (RECEIVE_GETC)();
+    int nextch = (receive_getc)();
     if (nextch == ' ' || nextch == '\t')
       {
       next->text[ptr++] = nextch;
       message_size++;
       continue;                      /* Iterate the loop */
       }
-    else if (nextch != EOF) (RECEIVE_UNGETC)(nextch);   /* For next time */
+    else if (nextch != EOF) (receive_ungetc)(nextch);   /* For next time */
     else ch = EOF;                   /* Cause main loop to exit at end */
     }
 
@@ -1905,7 +2071,7 @@ for (h = header_list->next; h != NULL; h = h->next)
     /* Record whether a Date: or Resent-Date: header exists, as appropriate. */
 
     case htype_date:
-    date_header_exists = !resents_exist || is_resent;
+    if (!resents_exist || is_resent) date_header_exists = TRUE;
     break;
 
     /* Same comments as about Return-Path: below. */
@@ -1933,9 +2099,12 @@ for (h = header_list->next; h != NULL; h = h->next)
       from_header = h;
       if (!smtp_input)
         {
+        int len;
         uschar *s = Ustrchr(h->text, ':') + 1;
         while (isspace(*s)) s++;
-        if (strncmpic(s, originator_login, h->slen - (s - h->text) - 1) == 0)
+        len = h->slen - (s - h->text) - 1;
+        if (Ustrlen(originator_login) == len &&
+           strncmpic(s, originator_login, len) == 0)
           {
           uschar *name = is_resent? US"Resent-From" : US"From";
           header_add(htype_from, "%s: %s <%s@%s>\n", name, originator_name,
@@ -2133,9 +2302,23 @@ if (extract_recip)
         pp = recipient = store_get(ss - s + 1);
         for (p = s; p < ss; p++) if (*p != '\n') *pp++ = *p;
         *pp = 0;
+
+#ifdef EXPERIMENTAL_INTERNATIONAL
+       {
+       BOOL b = allow_utf8_domains;
+       allow_utf8_domains = TRUE;
+#endif
         recipient = parse_extract_address(recipient, &errmess, &start, &end,
           &domain, FALSE);
 
+#ifdef EXPERIMENTAL_INTERNATIONAL
+       if (string_is_utf8(recipient))
+         message_smtputf8 = TRUE;
+       else
+         allow_utf8_domains = b;
+       }
+#endif
+
         /* Keep a list of all the bad addresses so we can send a single
         error message at the end. However, an empty address is not an error;
         just ignore it. This can come from an empty group list like
@@ -2332,17 +2515,20 @@ if (msgid_header == NULL &&
       }
     }
 
-  /* Add the header line */
+  /* Add the header line
+   * Resent-* headers are prepended, per RFC 5322 3.6.6.  Non-Resent-* are
+   * appended, to preserve classical expectations of header ordering. */
 
-  header_add(htype_id, "%sMessage-Id: <%s%s%s@%s>\n", resent_prefix,
-    message_id_external, (*id_text == 0)? "" : ".", id_text, id_domain);
+  header_add_at_position(!resents_exist, NULL, FALSE, htype_id,
+    "%sMessage-Id: <%s%s%s@%s>\n", resent_prefix, message_id_external,
+    (*id_text == 0)? "" : ".", id_text, id_domain);
   }
 
 /* If we are to log recipients, keep a copy of the raw ones before any possible
 rewriting. Must copy the count, because later ACLs and the local_scan()
 function may mess with the real recipients. */
 
-if ((log_extra_selector & LX_received_recipients) != 0)
+if (LOGGING(received_recipients))
   {
   raw_recipients = store_get(recipients_count * sizeof(uschar *));
   for (i = 0; i < recipients_count; i++)
@@ -2543,13 +2729,13 @@ if (from_header != NULL &&
     if (sender_address_unrewritten == NULL)
       sender_address_unrewritten = sender_address;
     sender_address = generated_sender_address;
-    log_write(L_address_rewrite, LOG_MAIN,
-      "\"%s\" from env-from rewritten as \"%s\" by submission mode",
-      sender_address_unrewritten, generated_sender_address);
+    if (Ustrcmp(sender_address_unrewritten, generated_sender_address) != 0)
+      log_write(L_address_rewrite, LOG_MAIN,
+        "\"%s\" from env-from rewritten as \"%s\" by submission mode",
+        sender_address_unrewritten, generated_sender_address);
     }
   }
 
-
 /* If there are any rewriting rules, apply them to the sender address, unless
 it has already been rewritten as part of verification for SMTP input. */
 
@@ -2599,12 +2785,15 @@ changes in RFC 2822, this was dropped in November 2003. */
 /* If there is no date header, generate one if the message originates locally
 (i.e. not over TCP/IP) and suppress_local_fixups is not set, or if the
 submission mode flag is set. Messages without Date: are not valid, but it seems
-to be more confusing if Exim adds one to all remotely-originated messages. */
+to be more confusing if Exim adds one to all remotely-originated messages.
+As per Message-Id, we prepend if resending, else append.
+*/
 
 if (!date_header_exists &&
       ((sender_host_address == NULL && !suppress_local_fixups)
         || submission_mode))
-  header_add(htype_other, "%sDate: %s\n", resent_prefix, tod_stamp(tod_full));
+  header_add_at_position(!resents_exist, NULL, FALSE, htype_other,
+    "%sDate: %s\n", resent_prefix, tod_stamp(tod_full));
 
 search_tidyup();    /* Free any cached resources */
 
@@ -2629,6 +2818,34 @@ if (filter_test != FTEST_NONE)
   return message_ended == END_DOT;
   }
 
+/* Cutthrough delivery:
+We have to create the Received header now rather than at the end of reception,
+so the timestamp behaviour is a change to the normal case.
+XXX Ensure this gets documented XXX.
+Having created it, send the headers to the destination. */
+if (cutthrough.fd >= 0)
+  {
+  if (received_count > received_headers_max)
+    {
+    cancel_cutthrough_connection("too many headers");
+    if (smtp_input) receive_swallow_smtp();  /* Swallow incoming SMTP */
+    log_write(0, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: "
+      "Too many \"Received\" headers",
+      sender_address,
+      (sender_fullhost == NULL)? "" : " H=",
+      (sender_fullhost == NULL)? US"" : sender_fullhost,
+      (sender_ident == NULL)? "" : " U=",
+      (sender_ident == NULL)? US"" : sender_ident);
+    message_id[0] = 0;                       /* Indicate no message accepted */
+    smtp_reply = US"550 Too many \"Received\" headers - suspected mail loop";
+    goto TIDYUP;                             /* Skip to end of function */
+    }
+  received_header_gen();
+  add_acl_headers(ACL_WHERE_RCPT, US"MAIL or RCPT");
+  (void) cutthrough_headers_send();
+  }
+
+
 /* Open a new spool file for the data portion of the message. We need
 to access it both via a file descriptor and a stream. Try to make the
 directory if it isn't there. Note re use of sprintf: spool_directory
@@ -2655,7 +2872,10 @@ if (data_fd < 0)
 /* Make sure the file's group is the Exim gid, and double-check the mode
 because the group setting doesn't always get set automatically. */
 
-(void)fchown(data_fd, exim_uid, exim_gid);
+if (fchown(data_fd, exim_uid, exim_gid))
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+    "Failed setting ownership on spool file %s: %s",
+    spool_name, strerror(errno));
 (void)fchmod(data_fd, SPOOL_MODE);
 
 /* We now have data file open. Build a stream for it and lock it. We lock only
@@ -2685,7 +2905,7 @@ if (next != NULL)
   {
   uschar *s = next->text;
   int len = next->slen;
-  (void)fwrite(s, 1, len, data_file);
+  len = fwrite(s, 1, len, data_file);  len = len; /* compiler quietening */
   body_linecount++;                 /* Assumes only 1 line */
   }
 
@@ -2710,6 +2930,7 @@ if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT)
   if (smtp_input && message_ended == END_EOF)
     {
     Uunlink(spool_name);                     /* Lose data file when closed */
+    cancel_cutthrough_connection("sender closed connection");
     message_id[0] = 0;                       /* Indicate no message accepted */
     smtp_reply = handle_lost_connection(US"");
     smtp_yield = FALSE;
@@ -2722,6 +2943,7 @@ if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT)
   if (message_ended == END_SIZE)
     {
     Uunlink(spool_name);                /* Lose the data file when closed */
+    cancel_cutthrough_connection("mail too big");
     if (smtp_input) receive_swallow_smtp();  /* Swallow incoming SMTP */
 
     log_write(L_size_reject, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: "
@@ -2777,6 +2999,7 @@ if (fflush(data_file) == EOF || ferror(data_file) ||
 
   log_write(0, LOG_MAIN, "Message abandoned: %s", msg);
   Uunlink(spool_name);                /* Lose the data file */
+  cancel_cutthrough_connection("error writing spoolfile");
 
   if (smtp_input)
     {
@@ -2896,50 +3119,25 @@ for use when we generate the Received: header.
 
 Note: the checking for too many Received: headers is handled by the delivery
 code. */
+/*XXX eventually add excess Received: check for cutthrough case back when classifying them */
 
-timestamp = expand_string(US"${tod_full}");
-if (recipients_count == 1) received_for = recipients_list[0].address;
-received = expand_string(received_header_text);
-received_for = NULL;
-
-if (received == NULL)
+if (received_header->text == NULL)     /* Non-cutthrough case */
   {
-  Uunlink(spool_name);           /* Lose the data file */
-  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Expansion of \"%s\" "
-    "(received_header_text) failed: %s", string_printing(received_header_text),
-      expand_string_message);
-  }
+  received_header_gen();
 
-/* The first element on the header chain is reserved for the Received header,
-so all we have to do is fill in the text pointer, and set the type. However, if
-the result of the expansion is an empty string, we leave the header marked as
-"old" so as to refrain from adding a Received header. */
+  /* Set the value of message_body_size for the DATA ACL and for local_scan() */
 
-if (received[0] == 0)
-  {
-  received_header->text = string_sprintf("Received: ; %s\n", timestamp);
-  received_header->type = htype_old;
-  }
-else
-  {
-  received_header->text = string_sprintf("%s; %s\n", received, timestamp);
-  received_header->type = htype_received;
-  }
-
-received_header->slen = Ustrlen(received_header->text);
-
-DEBUG(D_receive) debug_printf(">>Generated Received: header line\n%c %s",
-  received_header->type, received_header->text);
-
-/* Set the value of message_body_size for the DATA ACL and for local_scan() */
-
-message_body_size = (fstat(data_fd, &statbuf) == 0)?
-  statbuf.st_size - SPOOL_DATA_START_OFFSET : -1;
+  message_body_size = (fstat(data_fd, &statbuf) == 0)?
+    statbuf.st_size - SPOOL_DATA_START_OFFSET : -1;
 
-/* If an ACL from any RCPT commands set up any warning headers to add, do so
-now, before running the DATA ACL. */
+  /* If an ACL from any RCPT commands set up any warning headers to add, do so
+  now, before running the DATA ACL. */
 
-add_acl_headers(US"MAIL or RCPT");
+  add_acl_headers(ACL_WHERE_RCPT, US"MAIL or RCPT");
+  }
+else
+  message_body_size = (fstat(data_fd, &statbuf) == 0)?
+    statbuf.st_size - SPOOL_DATA_START_OFFSET : -1;
 
 /* If an ACL is specified for checking things at this stage of reception of a
 message, run it, unless all the recipients were removed by "discard" in earlier
@@ -2964,35 +3162,223 @@ else
   if (smtp_input && !smtp_batched_input)
     {
 
-#ifdef EXPERIMENTAL_DOMAINKEYS
-    dk_exim_verify_finish();
-#endif
+#ifndef DISABLE_DKIM
+    if (!dkim_disable_verify)
+      {
+      /* Finish verification, this will log individual signature results to
+         the mainlog */
+      dkim_exim_verify_finish();
+
+      /* Check if we must run the DKIM ACL */
+      if ((acl_smtp_dkim != NULL) &&
+          (dkim_verify_signers != NULL) &&
+          (dkim_verify_signers[0] != '\0'))
+        {
+        uschar *dkim_verify_signers_expanded =
+          expand_string(dkim_verify_signers);
+        if (dkim_verify_signers_expanded == NULL)
+          {
+          log_write(0, LOG_MAIN|LOG_PANIC,
+            "expansion of dkim_verify_signers option failed: %s",
+            expand_string_message);
+          }
+        else
+          {
+          int sep = 0;
+          const uschar *ptr = dkim_verify_signers_expanded;
+          uschar *item = NULL;
+          uschar *seen_items = NULL;
+          int     seen_items_size = 0;
+          int     seen_items_offset = 0;
+          uschar itembuf[256];
+          /* Default to OK when no items are present */
+          rc = OK;
+          while ((item = string_nextinlist(&ptr, &sep,
+                                           itembuf,
+                                           sizeof(itembuf))))
+            {
+            /* Prevent running ACL for an empty item */
+            if (!item || (item[0] == '\0')) continue;
+
+            /* Only run ACL once for each domain or identity,
+           no matter how often it appears in the expanded list. */
+            if (seen_items)
+              {
+              uschar *seen_item = NULL;
+              uschar seen_item_buf[256];
+              const uschar *seen_items_list = seen_items;
+              BOOL seen_this_item = FALSE;
+
+              while ((seen_item = string_nextinlist(&seen_items_list, &sep,
+                                                    seen_item_buf,
+                                                    sizeof(seen_item_buf))))
+               if (Ustrcmp(seen_item,item) == 0)
+                 {
+                 seen_this_item = TRUE;
+                 break;
+                 }
+
+              if (seen_this_item)
+                {
+                DEBUG(D_receive)
+                  debug_printf("acl_smtp_dkim: skipping signer %s, "
+                   "already seen\n", item);
+                continue;
+                }
+
+              seen_items = string_append(seen_items, &seen_items_size,
+               &seen_items_offset, 1, ":");
+              }
+
+            seen_items = string_append(seen_items, &seen_items_size,
+             &seen_items_offset, 1, item);
+            seen_items[seen_items_offset] = '\0';
+
+            DEBUG(D_receive)
+              debug_printf("calling acl_smtp_dkim for dkim_cur_signer=%s\n",
+               item);
+
+            dkim_exim_acl_setup(item);
+            rc = acl_check(ACL_WHERE_DKIM, NULL, acl_smtp_dkim,
+                 &user_msg, &log_msg);
+
+            if (rc != OK)
+             {
+             DEBUG(D_receive)
+               debug_printf("acl_smtp_dkim: acl_check returned %d on %s, "
+                 "skipping remaining items\n", rc, item);
+             cancel_cutthrough_connection("dkim acl not ok");
+             break;
+             }
+            }
+          add_acl_headers(ACL_WHERE_DKIM, US"DKIM");
+          if (rc == DISCARD)
+            {
+            recipients_count = 0;
+            blackholed_by = US"DKIM ACL";
+            if (log_msg != NULL)
+              blackhole_log_msg = string_sprintf(": %s", log_msg);
+            }
+          else if (rc != OK)
+            {
+            Uunlink(spool_name);
+            if (smtp_handle_acl_fail(ACL_WHERE_DKIM, rc, user_msg, log_msg) != 0)
+              smtp_yield = FALSE;    /* No more messsages after dropped connection */
+            smtp_reply = US"";       /* Indicate reply already sent */
+            message_id[0] = 0;       /* Indicate no message accepted */
+            goto TIDYUP;             /* Skip to end of function */
+            }
+          }
+        }
+      }
+#endif /* DISABLE_DKIM */
 
 #ifdef WITH_CONTENT_SCAN
-    if (acl_smtp_mime != NULL &&
+    if (recipients_count > 0 &&
+        acl_smtp_mime != NULL &&
         !run_mime_acl(acl_smtp_mime, &smtp_yield, &smtp_reply, &blackholed_by))
       goto TIDYUP;
 #endif /* WITH_CONTENT_SCAN */
 
+#ifdef EXPERIMENTAL_DMARC
+    dmarc_up = dmarc_store_data(from_header);
+#endif /* EXPERIMENTAL_DMARC */
+
+#ifndef DISABLE_PRDR
+    if (prdr_requested && recipients_count > 1 && acl_smtp_data_prdr)
+      {
+      unsigned int c;
+      int all_pass = OK;
+      int all_fail = FAIL;
+
+      smtp_printf("353 PRDR content analysis beginning\r\n");
+      /* Loop through recipients, responses must be in same order received */
+      for (c = 0; recipients_count > c; c++)
+        {
+       uschar * addr= recipients_list[c].address;
+       uschar * msg= US"PRDR R=<%s> %s";
+       uschar * code;
+        DEBUG(D_receive)
+          debug_printf("PRDR processing recipient %s (%d of %d)\n",
+                       addr, c+1, recipients_count);
+        rc = acl_check(ACL_WHERE_PRDR, addr,
+                       acl_smtp_data_prdr, &user_msg, &log_msg);
+
+        /* If any recipient rejected content, indicate it in final message */
+        all_pass |= rc;
+        /* If all recipients rejected, indicate in final message */
+        all_fail &= rc;
+
+        switch (rc)
+          {
+          case OK: case DISCARD: code = US"250"; break;
+          case DEFER:            code = US"450"; break;
+          default:               code = US"550"; break;
+          }
+       if (user_msg != NULL)
+         smtp_user_msg(code, user_msg);
+       else
+         {
+         switch (rc)
+            {
+            case OK: case DISCARD:
+              msg = string_sprintf(CS msg, addr, "acceptance");        break;
+            case DEFER:
+              msg = string_sprintf(CS msg, addr, "temporary refusal"); break;
+            default:
+              msg = string_sprintf(CS msg, addr, "refusal");           break;
+            }
+          smtp_user_msg(code, msg);
+         }
+       if (log_msg)       log_write(0, LOG_MAIN, "PRDR %s %s", addr, log_msg);
+       else if (user_msg) log_write(0, LOG_MAIN, "PRDR %s %s", addr, user_msg);
+       else               log_write(0, LOG_MAIN, "%s", CS msg);
+
+       if (rc != OK) { receive_remove_recipient(addr); c--; }
+        }
+      /* Set up final message, used if data acl gives OK */
+      smtp_reply = string_sprintf("%s id=%s message %s",
+                      all_fail == FAIL ? US"550" : US"250",
+                      message_id,
+                       all_fail == FAIL
+                        ? US"rejected for all recipients"
+                        : all_pass == OK
+                          ? US"accepted"
+                          : US"accepted for some recipients");
+      if (recipients_count == 0)
+        {
+        message_id[0] = 0;       /* Indicate no message accepted */
+       goto TIDYUP;
+       }
+      }
+    else
+      prdr_requested = FALSE;
+#endif /* !DISABLE_PRDR */
+
     /* Check the recipients count again, as the MIME ACL might have changed
     them. */
 
     if (acl_smtp_data != NULL && recipients_count > 0)
       {
       rc = acl_check(ACL_WHERE_DATA, NULL, acl_smtp_data, &user_msg, &log_msg);
-      add_acl_headers(US"DATA");
+      add_acl_headers(ACL_WHERE_DATA, US"DATA");
       if (rc == DISCARD)
         {
         recipients_count = 0;
         blackholed_by = US"DATA ACL";
         if (log_msg != NULL)
           blackhole_log_msg = string_sprintf(": %s", log_msg);
+       cancel_cutthrough_connection("data acl discard");
         }
       else if (rc != OK)
         {
         Uunlink(spool_name);
+       cancel_cutthrough_connection("data acl not ok");
 #ifdef WITH_CONTENT_SCAN
         unspool_mbox();
+#endif
+#ifdef EXPERIMENTAL_DCC
+       dcc_ok = 0;
 #endif
         if (smtp_handle_acl_fail(ACL_WHERE_DATA, rc, user_msg, log_msg) != 0)
           smtp_yield = FALSE;    /* No more messsages after dropped connection */
@@ -3032,6 +3418,9 @@ else
         Uunlink(spool_name);
 #ifdef WITH_CONTENT_SCAN
         unspool_mbox();
+#endif
+#ifdef EXPERIMENTAL_DCC
+       dcc_ok = 0;
 #endif
         /* The ACL can specify where rejections are to be logged, possibly
         nowhere. The default is main and reject logs. */
@@ -3055,7 +3444,7 @@ else
           /* Does not return */
           }
         }
-      add_acl_headers(US"non-SMTP");
+      add_acl_headers(ACL_WHERE_NOTSMTP, US"non-SMTP");
       }
     }
 
@@ -3069,6 +3458,11 @@ else
 unspool_mbox();
 #endif
 
+#ifdef EXPERIMENTAL_DCC
+dcc_ok = 0;
+#endif
+
+
 /* The final check on the message is to run the scan_local() function. The
 version supplied with Exim always accepts, but this is a hook for sysadmins to
 supply their own checking code. The local_scan() function is run even when all
@@ -3178,7 +3572,7 @@ else
     goto TEMPREJECT;
 
     case LOCAL_SCAN_REJECT_NOLOGHDR:
-    log_extra_selector &= ~LX_rejected_header;
+    BIT_CLEAR(log_selector, log_selector_size, Li_rejected_header);
     /* Fall through */
 
     case LOCAL_SCAN_REJECT:
@@ -3187,7 +3581,7 @@ else
     break;
 
     case LOCAL_SCAN_TEMPREJECT_NOLOGHDR:
-    log_extra_selector &= ~LX_rejected_header;
+    BIT_CLEAR(log_selector, log_selector_size, Li_rejected_header);
     /* Fall through */
 
     case LOCAL_SCAN_TEMPREJECT:
@@ -3237,6 +3631,7 @@ the message to be abandoned. */
 signal(SIGTERM, SIG_IGN);
 signal(SIGINT, SIG_IGN);
 
+
 /* Ensure the first time flag is set in the newly-received message. */
 
 deliver_firsttime = TRUE;
@@ -3351,26 +3746,53 @@ if (message_reference != NULL)
 s = add_host_info_for_log(s, &size, &sptr);
 
 #ifdef SUPPORT_TLS
-if ((log_extra_selector & LX_tls_cipher) != 0 && tls_cipher != NULL)
-  s = string_append(s, &size, &sptr, 2, US" X=", tls_cipher);
-if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
-     tls_cipher != NULL)
+if (LOGGING(tls_cipher) && tls_in.cipher)
+  s = string_append(s, &size, &sptr, 2, US" X=", tls_in.cipher);
+if (LOGGING(tls_certificate_verified) && tls_in.cipher)
   s = string_append(s, &size, &sptr, 2, US" CV=",
-    tls_certificate_verified? "yes":"no");
-if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_peerdn != NULL)
-  s = string_append(s, &size, &sptr, 3, US" DN=\"", tls_peerdn, US"\"");
+    tls_in.certificate_verified? "yes":"no");
+if (LOGGING(tls_peerdn) && tls_in.peerdn)
+  s = string_append(s, &size, &sptr, 3, US" DN=\"",
+    string_printing(tls_in.peerdn), US"\"");
+if (LOGGING(tls_sni) && tls_in.sni)
+  s = string_append(s, &size, &sptr, 3, US" SNI=\"",
+    string_printing(tls_in.sni), US"\"");
 #endif
 
-if (sender_host_authenticated != NULL)
+if (sender_host_authenticated)
   {
   s = string_append(s, &size, &sptr, 2, US" A=", sender_host_authenticated);
   if (authenticated_id != NULL)
+    {
     s = string_append(s, &size, &sptr, 2, US":", authenticated_id);
+    if (LOGGING(smtp_mailauth) && authenticated_sender != NULL)
+      s = string_append(s, &size, &sptr, 2, US":", authenticated_sender);
+    }
   }
 
+#ifndef DISABLE_PRDR
+if (prdr_requested)
+  s = string_append(s, &size, &sptr, 1, US" PRDR");
+#endif
+
+#ifdef EXPERIMENTAL_PROXY
+if (proxy_session && LOGGING(proxy))
+  s = string_append(s, &size, &sptr, 2, US" PRX=", proxy_host_address);
+#endif
+
 sprintf(CS big_buffer, "%d", msg_size);
 s = string_append(s, &size, &sptr, 2, US" S=", big_buffer);
 
+/* log 8BITMIME mode announced in MAIL_FROM
+   0 ... no BODY= used
+   7 ... 7BIT
+   8 ... 8BITMIME */
+if (LOGGING(8bitmime))
+  {
+  sprintf(CS big_buffer, "%d", body_8bitmime);
+  s = string_append(s, &size, &sptr, 2, US" M8S=", big_buffer);
+  }
+
 /* If an addr-spec in a message-id contains a quoted string, it can contain
 any characters except " \ and CR and so in particular it can contain NL!
 Therefore, make sure we use a printing-characters only version for the log.
@@ -3391,7 +3813,7 @@ if (msgid_header != NULL)
 /* If subject logging is turned on, create suitable printing-character
 text. By expanding $h_subject: we make use of the MIME decoding. */
 
-if ((log_extra_selector & LX_subject) != 0 && subject_header != NULL)
+if (LOGGING(subject) && subject_header != NULL)
   {
   int i;
   uschar *p = big_buffer;
@@ -3418,7 +3840,7 @@ s[sptr] = 0;
 
 /* Create a message log file if message logs are being used and this message is
 not blackholed. Write the reception stuff to it. We used to leave message log
-creation until the first delivery, but this has proved confusing for somep
+creation until the first delivery, but this has proved confusing for some
 people. */
 
 if (message_logs && blackholed_by == NULL)
@@ -3502,8 +3924,8 @@ if (smtp_input && sender_host_address != NULL && !sender_host_notsocket &&
 
   if (select(fileno(smtp_in) + 1, &select_check, NULL, NULL, &tv) != 0)
     {
-    int c = (RECEIVE_GETC)();
-    if (c != EOF) (RECEIVE_UNGETC)(c); else
+    int c = (receive_getc)();
+    if (c != EOF) (receive_ungetc)(c); else
       {
       uschar *msg = US"SMTP connection lost after final dot";
       smtp_reply = US"";    /* No attempt to send a response */
@@ -3539,17 +3961,58 @@ if (smtp_input && sender_host_address != NULL && !sender_host_notsocket &&
 /* The connection has not gone away; we really are going to take responsibility
 for this message. */
 
-log_write(0, LOG_MAIN |
-  (((log_extra_selector & LX_received_recipients) != 0)? LOG_RECIPIENTS : 0) |
-  (((log_extra_selector & LX_received_sender) != 0)? LOG_SENDER : 0),
-  "%s", s);
-receive_call_bombout = FALSE;
+/* Cutthrough - had sender last-dot; assume we've sent (or bufferred) all
+   data onward by now.
 
-/* Log any control actions taken by an ACL or local_scan(). */
+   Send dot onward.  If accepted, wipe the spooled files, log as delivered and accept
+   the sender's dot (below).
+   If rejected: copy response to sender, wipe the spooled files, log approriately.
+   If temp-reject: accept to sender, keep the spooled files.
 
-if (deliver_freeze) log_write(0, LOG_MAIN, "frozen by %s", frozen_by);
-if (queue_only_policy) log_write(L_delay_delivery, LOG_MAIN,
-  "no immediate delivery: queued by %s", queued_by);
+   Having the normal spool files lets us do data-filtering, and store/forward on temp-reject.
+
+   XXX We do not handle queue-only, freezing, or blackholes.
+*/
+if(cutthrough.fd >= 0)
+  {
+  uschar * msg= cutthrough_finaldot(); /* Ask the target system to accept the messsage */
+                                       /* Logging was done in finaldot() */
+  switch(msg[0])
+    {
+    case '2':  /* Accept. Do the same to the source; dump any spoolfiles.   */
+      cutthrough_done = ACCEPTED;
+      break;                                   /* message_id needed for SMTP accept below */
+
+    default:   /* Unknown response, or error.  Treat as temp-reject.         */
+    case '4':  /* Temp-reject. Keep spoolfiles and accept. */
+      cutthrough_done = TMP_REJ;               /* Avoid the usual immediate delivery attempt */
+      break;                                   /* message_id needed for SMTP accept below */
+
+    case '5':  /* Perm-reject.  Do the same to the source.  Dump any spoolfiles */
+      smtp_reply= msg;         /* Pass on the exact error */
+      cutthrough_done = PERM_REJ;
+      break;
+    }
+  }
+
+#ifndef DISABLE_PRDR
+if(!smtp_reply || prdr_requested)
+#else
+if(!smtp_reply)
+#endif
+  {
+  log_write(0, LOG_MAIN |
+    (LOGGING(received_recipients)? LOG_RECIPIENTS : 0) |
+    (LOGGING(received_sender)? LOG_SENDER : 0),
+    "%s", s);
+
+  /* Log any control actions taken by an ACL or local_scan(). */
+
+  if (deliver_freeze) log_write(0, LOG_MAIN, "frozen by %s", frozen_by);
+  if (queue_only_policy) log_write(L_delay_delivery, LOG_MAIN,
+    "no immediate delivery: queued by %s", queued_by);
+  }
+receive_call_bombout = FALSE;
 
 store_reset(s);   /* The store for the main log message can be reused */
 
@@ -3577,6 +4040,7 @@ data file will be flushed(!) out thereby. Nevertheless, it is theoretically
 possible for fclose() to fail - but what to do? What has happened to the lock
 if this happens? */
 
+
 TIDYUP:
 process_info[process_info_len] = 0;                /* Remove message id */
 if (data_file != NULL) (void)fclose(data_file);    /* Frees the lock */
@@ -3637,6 +4101,25 @@ if (smtp_input)
       else
         smtp_printf("%.1024s\r\n", smtp_reply);
       }
+
+    switch (cutthrough_done)
+      {
+      case ACCEPTED: log_write(0, LOG_MAIN, "Completed");/* Delivery was done */
+      case PERM_REJ: {                                 /* Delete spool files */
+             sprintf(CS spool_name, "%s/input/%s/%s-D", spool_directory,
+               message_subdir, message_id);
+             Uunlink(spool_name);
+             sprintf(CS spool_name, "%s/input/%s/%s-H", spool_directory,
+               message_subdir, message_id);
+             Uunlink(spool_name);
+             sprintf(CS spool_name, "%s/msglog/%s/%s", spool_directory,
+               message_subdir, message_id);
+             Uunlink(spool_name);
+             }
+      case TMP_REJ: message_id[0] = 0;   /* Prevent a delivery from starting */
+      default:break;
+      }
+    cutthrough.delivery = FALSE;
     }
 
   /* For batched SMTP, generate an error message on failure, and do
@@ -3654,9 +4137,9 @@ starting. */
 
 if (blackholed_by != NULL)
   {
-  uschar *detail = (local_scan_data != NULL)?
-    string_printing(local_scan_data) :
-    string_sprintf("(%s discarded recipients)", blackholed_by);
+  const uschar *detail = local_scan_data
+    ? string_printing(local_scan_data)
+    string_sprintf("(%s discarded recipients)", blackholed_by);
   log_write(0, LOG_MAIN, "=> blackhole %s%s", detail, blackhole_log_msg);
   log_write(0, LOG_MAIN, "Completed");
   message_id[0] = 0;