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    *
 *************************************************/
 
 /*************************************************
 *     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"
 
 /* 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
 
 #endif
 
+#ifdef EXPERIMENTAL_DMARC
+# include "dmarc.h"
+#endif /* EXPERIMENTAL_DMARC */
+
 /*************************************************
 *                Local static variables          *
 *************************************************/
 /*************************************************
 *                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
 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;
 }
 
     0, NULL) == OK;
 }
 
@@ -144,17 +142,16 @@ appearance of "syslog" in it. */
 else
   {
   int sep = ':';              /* Not variable - outside scripts use */
 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":";
   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 */
     {
 
   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));
 
 
 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.
 
 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
 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 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);
   {
   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. */
 
   }
 
 /* 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) */
   }
 
 /* Exit from the program (non-BSMTP cases) */
@@ -362,7 +375,7 @@ else
             LOG_MAIN, "timed out while reading local message");
   }
 
             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);
 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);
 {
 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
 /* 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;
 }
 
 
 
 
 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        *
 *************************************************/
 /*************************************************
 *        Remove a recipient from the list        *
 *************************************************/
@@ -572,7 +619,7 @@ if (!dot_ends)
   {
   register int last_ch = '\n';
 
   {
   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')
     {
     if (ch == 0) body_zerocount++;
     if (last_ch == '\r' && ch != '\n')
@@ -614,7 +661,7 @@ if (!dot_ends)
 
 ch_state = 1;
 
 
 ch_state = 1;
 
-while ((ch = (RECEIVE_GETC)()) != EOF)
+while ((ch = (receive_getc)()) != EOF)
   {
   if (ch == 0) body_zerocount++;
   switch (ch_state)
   {
   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; }
 
     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;
 
     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;
 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)
   {
   if (ch == 0) body_zerocount++;
   switch (ch_state)
@@ -749,6 +798,9 @@ while ((ch = (RECEIVE_GETC)()) != EOF)
       {
       ch_state = 0;
       body_linecount++;
       {
       ch_state = 0;
       body_linecount++;
+      if (linelength > max_received_linelength)
+        max_received_linelength = linelength;
+      linelength = -1;
       }
     else if (ch == '\r')
       {
       }
     else if (ch == '\r')
       {
@@ -759,6 +811,9 @@ while ((ch = (RECEIVE_GETC)()) != EOF)
 
     case 2:                             /* After (unwritten) CR */
     body_linecount++;
 
     case 2:                             /* After (unwritten) CR */
     body_linecount++;
+    if (linelength > max_received_linelength)
+      max_received_linelength = linelength;
+    linelength = -1;
     if (ch == '\n')
       {
       ch_state = 0;
     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;
       {
       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;
       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;
     message_size++;
     body_linecount++;
     if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
+    (void) cutthrough_put_nl();
     if (ch == '\r')
       {
       ch_state = 2;
     if (ch == '\r')
       {
       ch_state = 2;
@@ -800,11 +857,19 @@ while ((ch = (RECEIVE_GETC)()) != EOF)
   next. */
 
   message_size++;
   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 (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,
   }
 
 /* Fall through here if EOF encountered. This indicates some kind of error,
@@ -921,11 +986,46 @@ Returns:     nothing
 */
 
 static void
 */
 
 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;
 
 {
 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);
 
 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 (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);
     {
     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;
 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);
 
 
 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 */
 
 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();
 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 */
   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];
 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);
 
   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;
       }
       break;
       }
-    } while (1);
+    }
   closedir(tempdir);
 
   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:
     }
   }
 
 END_MIME_ACL:
-add_acl_headers(US"MIME");
+add_acl_headers(ACL_WHERE_MIME, US"MIME");
 if (rc == DISCARD)
   {
   recipients_count = 0;
 if (rc == DISCARD)
   {
   recipients_count = 0;
@@ -1176,9 +1276,13 @@ else if (rc != OK)
   {
   Uunlink(spool_name);
   unspool_mbox();
   {
   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_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 */
   }
   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 */
 
 
 #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                *
 *************************************************/
 /*************************************************
 *                 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 =
 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.
 
 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)
 {
 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)?
 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"";
 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;
 
 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;
 
 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. */
 
 /* Variables for use when building the Received: header. */
 
-uschar *received;
 uschar *timestamp;
 int tslen;
 
 uschar *timestamp;
 int tslen;
 
@@ -1346,6 +1502,12 @@ might take a fair bit of real time. */
 
 search_tidyup();
 
 
 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. */
 /* 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;
 
 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
 #endif
 
 /* Remember the time of reception. Exim uses time+pid for uniqueness of message
@@ -1434,7 +1600,7 @@ next->text. */
 
 for (;;)
   {
 
 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. */
 
   /* 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;
   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;
     }
 
     goto EOL;
     }
 
@@ -1513,13 +1679,13 @@ for (;;)
 
   if (ptr == 0 && ch == '.' && (smtp_input || dot_ends))
     {
 
   if (ptr == 0 && ch == '.' && (smtp_input || dot_ends))
     {
-    ch = (RECEIVE_GETC)();
+    ch = (receive_getc)();
     if (ch == '\r')
       {
     if (ch == '\r')
       {
-      ch = (RECEIVE_GETC)();
+      ch = (receive_getc)();
       if (ch != '\n')
         {
       if (ch != '\n')
         {
-        RECEIVE_UNGETC(ch);
+        receive_ungetc(ch);
         ch = '\r';              /* Revert to CR */
         }
       }
         ch = '\r';              /* Revert to CR */
         }
       }
@@ -1547,7 +1713,7 @@ for (;;)
 
   if (ch == '\r')
     {
 
   if (ch == '\r')
     {
-    ch = (RECEIVE_GETC)();
+    ch = (receive_getc)();
     if (ch == '\n')
       {
       if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = TRUE;
     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. */
 
     /* 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 = ' ';
     next->text[ptr++] = '\n';
     message_size++;
     ch = ' ';
@@ -1642,14 +1808,14 @@ for (;;)
 
   if (ch != EOF)
     {
 
   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 */
       }
     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 */
     }
 
     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:
     /* 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. */
     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)
         {
       from_header = h;
       if (!smtp_input)
         {
+        int len;
         uschar *s = Ustrchr(h->text, ':') + 1;
         while (isspace(*s)) s++;
         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,
           {
           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;
         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);
 
         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
         /* 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 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++)
   {
   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;
     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. */
 
 /* 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
 /* 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))
 
 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 */
 
 
 search_tidyup();    /* Free any cached resources */
 
@@ -2629,6 +2818,34 @@ if (filter_test != FTEST_NONE)
   return message_ended == END_DOT;
   }
 
   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
 /* 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. */
 
 /* 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
 (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;
   {
   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 */
   }
 
   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 */
   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;
     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 */
   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: "
     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 */
 
   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)
     {
 
   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. */
 
 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
 
 /* 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)
     {
 
   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
 
 #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 */
 
         !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);
     /* 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);
       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);
         }
       else if (rc != OK)
         {
         Uunlink(spool_name);
+       cancel_cutthrough_connection("data acl not ok");
 #ifdef WITH_CONTENT_SCAN
         unspool_mbox();
 #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 */
 #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();
         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. */
 #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 */
           }
         }
           /* 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
 
 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
 /* 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:
     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:
     /* Fall through */
 
     case LOCAL_SCAN_REJECT:
@@ -3187,7 +3581,7 @@ else
     break;
 
     case LOCAL_SCAN_TEMPREJECT_NOLOGHDR:
     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:
     /* Fall through */
 
     case LOCAL_SCAN_TEMPREJECT:
@@ -3237,6 +3631,7 @@ the message to be abandoned. */
 signal(SIGTERM, SIG_IGN);
 signal(SIGINT, SIG_IGN);
 
 signal(SIGTERM, SIG_IGN);
 signal(SIGINT, SIG_IGN);
 
+
 /* Ensure the first time flag is set in the newly-received message. */
 
 deliver_firsttime = TRUE;
 /* 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
 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=",
   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
 
 #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" A=", sender_host_authenticated);
   if (authenticated_id != NULL)
+    {
     s = string_append(s, &size, &sptr, 2, US":", authenticated_id);
     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);
 
 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.
 /* 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 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;
   {
   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
 
 /* 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)
 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)
     {
 
   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 */
       {
       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. */
 
 /* 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 */
 
 
 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? */
 
 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 */
 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);
       }
       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
     }
 
   /* For batched SMTP, generate an error message on failure, and do
@@ -3654,9 +4137,9 @@ starting. */
 
 if (blackholed_by != NULL)
   {
 
 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;
   log_write(0, LOG_MAIN, "=> blackhole %s%s", detail, blackhole_log_msg);
   log_write(0, LOG_MAIN, "Completed");
   message_id[0] = 0;