Update all copyright messages to cover 1995 - 2009. Remove tab from exim_checkaccess.src
[exim.git] / src / src / receive.c
index e2d6a1a1d19e2beda2b638f2af057a126e5dbae0..fcaa237a3d5cb15d399c3fb5e3349d39d51c1066 100644 (file)
@@ -1,22 +1,18 @@
-/* $Cambridge: exim/src/src/receive.c,v 1.22 2005/08/01 14:41:25 ph10 Exp $ */
+/* $Cambridge: exim/src/src/receive.c,v 1.53 2009/11/16 19:50:37 nm4 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* Copyright (c) University of Cambridge 1995 - 2009 */
 /* 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
 
 /*************************************************
@@ -178,7 +174,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,12 +279,14 @@ 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)
 {
 /* 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
 {
 /* 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
@@ -306,20 +304,16 @@ if (spool_name[0] != 0)
 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);
   else if (data_fd >= 0) (void)close(data_fd);
 
-/* 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 (smtp_input)
   {
-  if (!smtp_batched_input)
-    {
-    smtp_printf("421 %s %s - closing connection.\r\n", smtp_active_hostname,
-      msg);
-    mac_smtp_fflush();
-    }
-
-  /* Control does not return from moan_smtp_batch(). */
-
-  else moan_smtp_batch(NULL, "421 %s - message abandoned", msg);
+  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);
   }
 
 /* Exit from the program (non-BSMTP cases) */
   }
 
 /* Exit from the program (non-BSMTP cases) */
@@ -362,7 +356,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 +378,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 +400,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 +438,7 @@ else
     }
   }
 
     }
   }
 
-receive_bomb_out(msg);    /* Does not return */
+receive_bomb_out(US"signal-exit", msg);    /* Does not return */
 }
 
 
 }
 
 
@@ -564,6 +560,7 @@ read_message_data(FILE *fout)
 {
 int ch_state;
 register int ch;
 {
 int ch_state;
 register int ch;
+register int linelength = 0;
 
 /* Handle the case when only EOF terminates the message */
 
 
 /* Handle the case when only EOF terminates the message */
 
@@ -571,11 +568,14 @@ 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')
       {
+      if (linelength > max_received_linelength)
+        max_received_linelength = linelength;
+      linelength = 0;
       if (fputc('\n', fout) == EOF) return END_WERROR;
       message_size++;
       body_linecount++;
       if (fputc('\n', fout) == EOF) return END_WERROR;
       message_size++;
       body_linecount++;
@@ -583,12 +583,21 @@ if (!dot_ends)
     if (ch == '\r') continue;
 
     if (fputc(ch, fout) == EOF) return END_WERROR;
     if (ch == '\r') continue;
 
     if (fputc(ch, fout) == EOF) return END_WERROR;
-    if (ch == '\n') body_linecount++;
+    if (ch == '\n')
+      {
+      if (linelength > max_received_linelength)
+        max_received_linelength = linelength;
+      linelength = 0;
+      body_linecount++;
+      }
+    else linelength++;
     if (++message_size > thismessage_size_limit) return END_SIZE;
     }
 
   if (last_ch != '\n')
     {
     if (++message_size > thismessage_size_limit) return END_SIZE;
     }
 
   if (last_ch != '\n')
     {
+    if (linelength > max_received_linelength)
+      max_received_linelength = linelength;
     if (fputc('\n', fout) == EOF) return END_WERROR;
     message_size++;
     body_linecount++;
     if (fputc('\n', fout) == EOF) return END_WERROR;
     message_size++;
     body_linecount++;
@@ -601,32 +610,44 @@ 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)
     {
     case 0:                         /* Normal state (previous char written) */
     if (ch == '\n')
   {
   if (ch == 0) body_zerocount++;
   switch (ch_state)
     {
     case 0:                         /* Normal state (previous char written) */
     if (ch == '\n')
-      { body_linecount++; ch_state = 1; }
+      {
+      body_linecount++;
+      if (linelength > max_received_linelength)
+        max_received_linelength = linelength;
+      linelength = -1;
+      ch_state = 1;
+      }
     else if (ch == '\r')
       { ch_state = 2; continue; }
     break;
 
     case 1:                         /* After written "\n" */
     if (ch == '.') { ch_state = 3; continue; }
     else if (ch == '\r')
       { ch_state = 2; continue; }
     break;
 
     case 1:                         /* After written "\n" */
     if (ch == '.') { ch_state = 3; continue; }
-    if (ch != '\n') ch_state = 0;
+    if (ch != '\n') ch_state = 0; else linelength = -1;
     break;
 
     case 2:
     body_linecount++;               /* After unwritten "\r" */
     break;
 
     case 2:
     body_linecount++;               /* After unwritten "\r" */
+    if (linelength > max_received_linelength)
+      max_received_linelength = linelength;
     if (ch == '\n')
     if (ch == '\n')
-      { ch_state = 1; }
+      {
+      ch_state = 1;
+      linelength = -1;
+      }
     else
       {
       if (message_size++, fputc('\n', fout) == EOF) return END_WERROR;
       if (ch == '\r') continue;
       ch_state = 0;
     else
       {
       if (message_size++, fputc('\n', fout) == EOF) return END_WERROR;
       if (ch == '\r') continue;
       ch_state = 0;
+      linelength = 0;
       }
     break;
 
       }
     break;
 
@@ -634,6 +655,7 @@ while ((ch = (RECEIVE_GETC)()) != EOF)
     if (ch == '\n') return END_DOT;
     if (ch == '\r') { ch_state = 4; continue; }
     message_size++;
     if (ch == '\n') return END_DOT;
     if (ch == '\r') { ch_state = 4; continue; }
     message_size++;
+    linelength++;
     if (fputc('.', fout) == EOF) return END_WERROR;
     ch_state = 0;
     break;
     if (fputc('.', fout) == EOF) return END_WERROR;
     ch_state = 0;
     break;
@@ -648,6 +670,7 @@ while ((ch = (RECEIVE_GETC)()) != EOF)
     break;
     }
 
     break;
     }
 
+  linelength++;
   if (fputc(ch, fout) == EOF) return END_WERROR;
   if (++message_size > thismessage_size_limit) return END_SIZE;
   }
   if (fputc(ch, fout) == EOF) return END_WERROR;
   if (++message_size > thismessage_size_limit) return END_SIZE;
   }
@@ -701,8 +724,9 @@ read_message_data_smtp(FILE *fout)
 {
 int ch_state = 0;
 register int ch;
 {
 int ch_state = 0;
 register 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)
@@ -722,6 +746,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')
       {
@@ -732,6 +759,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;
@@ -773,6 +803,7 @@ while ((ch = (RECEIVE_GETC)()) != EOF)
   next. */
 
   message_size++;
   next. */
 
   message_size++;
+  linelength++;
   if (fout != NULL)
     {
     if (fputc(ch, fout) == EOF) return END_WERROR;
   if (fout != NULL)
     {
     if (fputc(ch, fout) == EOF) return END_WERROR;
@@ -875,12 +906,12 @@ exim_exit(error_rc);
 *          Add header lines set up by ACL        *
 *************************************************/
 
 *          Add header lines set up by ACL        *
 *************************************************/
 
-/* This function is called to add the header lines that were set up by "warn"
-statements in an ACL onto the list of headers in memory. It is done in two
-stages like this, because when the ACL for RCPT is running, the other headers
-have not yet been received. This function is called twice; once just before
-running the DATA ACL, and once after. This is so that header lines added by
-MAIL or RCPT are visible to the DATA ACL.
+/* This function is called to add the header lines that were set up by
+statements in an ACL to the list of headers in memory. It is done in two stages
+like this, because when the ACL for RCPT is running, the other headers have not
+yet been received. This function is called twice; once just before running the
+DATA ACL, and once after. This is so that header lines added by MAIL or RCPT
+are visible to the DATA ACL.
 
 Originally these header lines were added at the end. Now there is support for
 three different places: top, bottom, and after the Received: header(s). There
 
 Originally these header lines were added at the end. Now there is support for
 three different places: top, bottom, and after the Received: header(s). There
@@ -899,10 +930,10 @@ add_acl_headers(uschar *acl_name)
 header_line *h, *next;
 header_line *last_received = NULL;
 
 header_line *h, *next;
 header_line *last_received = NULL;
 
-if (acl_warn_headers == NULL) return;
+if (acl_added_headers == NULL) return;
 DEBUG(D_receive|D_acl) debug_printf(">>Headers added by %s ACL:\n", acl_name);
 
 DEBUG(D_receive|D_acl) debug_printf(">>Headers added by %s ACL:\n", acl_name);
 
-for (h = acl_warn_headers; h != NULL; h = next)
+for (h = acl_added_headers; h != NULL; h = next)
   {
   next = h->next;
 
   {
   next = h->next;
 
@@ -964,7 +995,7 @@ for (h = acl_warn_headers; h != NULL; h = next)
   DEBUG(D_receive|D_acl) debug_printf("  %s", header_last->text);
   }
 
   DEBUG(D_receive|D_acl) debug_printf("  %s", header_last->text);
   }
 
-acl_warn_headers = NULL;
+acl_added_headers = NULL;
 DEBUG(D_receive|D_acl) debug_printf(">>\n");
 }
 
 DEBUG(D_receive|D_acl) debug_printf(">>\n");
 }
 
@@ -1036,24 +1067,27 @@ 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);
 
 /* check if it is a MIME message */
 my_headerlist = header_list;
 
 memset(CS rfc822_file_path,0,2048);
 
 /* check if it is a MIME message */
 my_headerlist = header_list;
-while (my_headerlist != NULL) {
+while (my_headerlist != NULL)
+  {
   /* skip deleted headers */
   /* skip deleted headers */
-  if (my_headerlist->type == '*') {
+  if (my_headerlist->type == '*')
+    {
     my_headerlist = my_headerlist->next;
     continue;
     my_headerlist = my_headerlist->next;
     continue;
-  };
-  if (strncmpic(my_headerlist->text, US"Content-Type:", 13) == 0) {
+    }
+  if (strncmpic(my_headerlist->text, US"Content-Type:", 13) == 0)
+    {
     DEBUG(D_receive) debug_printf("Found Content-Type: header - executing acl_smtp_mime.\n");
     goto DO_MIME_ACL;
     DEBUG(D_receive) debug_printf("Found Content-Type: header - executing acl_smtp_mime.\n");
     goto DO_MIME_ACL;
-  };
+    }
   my_headerlist = my_headerlist->next;
   my_headerlist = my_headerlist->next;
-};
+  }
 
 DEBUG(D_receive) debug_printf("No Content-Type: header - presumably not a MIME message.\n");
 return TRUE;
 
 DEBUG(D_receive) debug_printf("No Content-Type: header - presumably not a MIME message.\n");
 return TRUE;
@@ -1067,7 +1101,7 @@ if (mbox_file == NULL) {
          "acl_smtp_mime: error while creating mbox spool file, message temporarily rejected.");
   Uunlink(spool_name);
   unspool_mbox();
          "acl_smtp_mime: error while creating mbox spool file, message temporarily rejected.");
   Uunlink(spool_name);
   unspool_mbox();
-  smtp_respond(451, TRUE, US"temporary local problem");
+  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 */
   return FALSE;                 /* Indicate skip to end of receive function */
   message_id[0] = 0;            /* Indicate no message accepted */
   *smtp_reply_ptr = US"";       /* Indicate reply already sent */
   return FALSE;                 /* Indicate skip to end of receive function */
@@ -1080,18 +1114,21 @@ mime_part_count = -1;
 rc = mime_acl_check(acl, mbox_file, NULL, &user_msg, &log_msg);
 (void)fclose(mbox_file);
 
 rc = mime_acl_check(acl, mbox_file, NULL, &user_msg, &log_msg);
 (void)fclose(mbox_file);
 
-if (Ustrlen(rfc822_file_path) > 0) {
+if (Ustrlen(rfc822_file_path) > 0)
+  {
   mime_part_count = mime_part_count_buffer;
 
   mime_part_count = mime_part_count_buffer;
 
-  if (unlink(CS rfc822_file_path) == -1) {
+  if (unlink(CS rfc822_file_path) == -1)
+    {
     log_write(0, LOG_PANIC,
          "acl_smtp_mime: can't unlink RFC822 spool file, skipping.");
       goto END_MIME_ACL;
     log_write(0, LOG_PANIC,
          "acl_smtp_mime: can't unlink RFC822 spool file, skipping.");
       goto END_MIME_ACL;
-  };
-};
+    }
+  }
 
 /* check if we must check any message/rfc822 attachments */
 
 /* check if we must check any message/rfc822 attachments */
-if (rc == OK) {
+if (rc == OK)
+  {
   uschar temp_path[1024];
   int n;
   struct dirent *entry;
   uschar temp_path[1024];
   int n;
   struct dirent *entry;
@@ -1100,33 +1137,37 @@ if (rc == OK) {
   (void)string_format(temp_path, 1024, "%s/scan/%s", spool_directory,
     message_id);
 
   (void)string_format(temp_path, 1024, "%s/scan/%s", spool_directory,
     message_id);
 
- tempdir = opendir(CS temp_path);
- n = 0;
- do {
-   entry = readdir(tempdir);
-   if (entry == NULL) break;
-    if (strncmpic(US entry->d_name,US"__rfc822_",9) == 0) {
+  tempdir = opendir(CS temp_path);
+  n = 0;
+  do
+    {
+    entry = readdir(tempdir);
+    if (entry == NULL) 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);
       (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);
-     break;
-    };
- } while (1);
- closedir(tempdir);
+      debug_printf("RFC822 attachment detected: running MIME ACL for '%s'\n", rfc822_file_path);
+      break;
+      }
   } while (1);
 closedir(tempdir);
 
 
-  if (entry != NULL) {
+  if (entry != NULL)
+    {
     mbox_file = Ufopen(rfc822_file_path,"rb");
     mbox_file = Ufopen(rfc822_file_path,"rb");
-    if (mbox_file == NULL) {
+    if (mbox_file == NULL)
+      {
       log_write(0, LOG_PANIC,
          "acl_smtp_mime: can't open RFC822 spool file, skipping.");
       unlink(CS rfc822_file_path);
       goto END_MIME_ACL;
       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;
-  };
-};
+    }
+  }
 
 END_MIME_ACL:
 add_acl_headers(US"MIME");
 
 END_MIME_ACL:
 add_acl_headers(US"MIME");
@@ -1144,7 +1185,7 @@ else if (rc != OK)
   *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 */
   *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 */
-  };
+  }
 
 return TRUE;
 }
 
 return TRUE;
 }
@@ -1160,9 +1201,10 @@ return TRUE;
 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
 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
-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.
+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
+handled using SMTP conventions about termination and lines starting with dots.
+For non-SMTP messages, dot_ends is true for dot-terminated messages.
 
 If a message was successfully read, message_id[0] will be non-zero.
 
 
 If a message was successfully read, message_id[0] will be non-zero.
 
@@ -1243,7 +1285,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)?
@@ -1252,6 +1295,7 @@ int  header_size = 256;
 int  start, end, domain, size, sptr;
 int  id_resolution;
 int  had_zero = 0;
 int  start, end, domain, size, sptr;
 int  id_resolution;
 int  had_zero = 0;
+int  prevlines_length = 0;
 
 register int ptr = 0;
 
 
 register int ptr = 0;
 
@@ -1275,20 +1319,16 @@ uschar *queued_by = NULL;
 uschar *errmsg, *s;
 struct stat statbuf;
 
 uschar *errmsg, *s;
 struct stat statbuf;
 
-/* Final message to give to SMTP caller */
+/* Final message to give to SMTP caller, and messages from ACLs */
 
 uschar *smtp_reply = NULL;
 
 uschar *smtp_reply = NULL;
+uschar *user_msg, *log_msg;
 
 /* Working header pointers */
 
 header_line *h, *next;
 
 
 /* Working header pointers */
 
 header_line *h, *next;
 
-/* Flags for noting the existence of certain headers */
-
-/**** No longer check for these (Nov 2003)
-BOOL to_or_cc_header_exists = FALSE;
-BOOL bcc_header_exists = FALSE;
-****/
+/* Flags for noting the existence of certain headers (only one left) */
 
 BOOL date_header_exists = FALSE;
 
 
 BOOL date_header_exists = FALSE;
 
@@ -1336,18 +1376,18 @@ data_fd = -1;
 spool_name[0] = 0;
 message_size = 0;
 warning_count = 0;
 spool_name[0] = 0;
 message_size = 0;
 warning_count = 0;
-received_count = 1;     /* For the one we will add */
+received_count = 1;            /* For the one we will add */
 
 if (thismessage_size_limit <= 0) thismessage_size_limit = INT_MAX;
 
 /* While reading the message, the following counts are computed. */
 
 
 if (thismessage_size_limit <= 0) thismessage_size_limit = INT_MAX;
 
 /* While reading the message, the following counts are computed. */
 
-message_linecount = body_linecount = body_zerocount = 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
 
 /* 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
@@ -1398,7 +1438,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. */
@@ -1462,7 +1502,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;
     }
 
@@ -1477,13 +1517,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 */
         }
       }
@@ -1511,7 +1551,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;
@@ -1521,7 +1561,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 = ' ';
@@ -1578,6 +1618,12 @@ for (;;)
   receive_linecount++;
   message_linecount++;
 
   receive_linecount++;
   message_linecount++;
 
+  /* Keep track of maximum line length */
+
+  if (ptr - prevlines_length > max_received_linelength)
+    max_received_linelength = ptr - prevlines_length;
+  prevlines_length = ptr + 1;
+
   /* Now put in the terminating newline. There is always space for
   at least two more characters. */
 
   /* Now put in the terminating newline. There is always space for
   at least two more characters. */
 
@@ -1600,14 +1646,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 */
     }
 
@@ -1806,6 +1852,7 @@ for (;;)
   next->text = store_get(header_size);
   ptr = 0;
   had_zero = 0;
   next->text = store_get(header_size);
   ptr = 0;
   had_zero = 0;
+  prevlines_length = 0;
   }      /* Continue, starting to read the next header */
 
 /* At this point, we have read all the headers into a data structure in main
   }      /* Continue, starting to read the next header */
 
 /* At this point, we have read all the headers into a data structure in main
@@ -1851,30 +1898,18 @@ for (h = header_list->next; h != NULL; h = h->next)
 
   switch (header_checkname(h, is_resent))
     {
 
   switch (header_checkname(h, is_resent))
     {
-    /* "Bcc:" gets flagged, and its existence noted, whether it's resent- or
-    not. */
-
     case htype_bcc:
     case htype_bcc:
-    h->type = htype_bcc;
-    /****
-    bcc_header_exists = TRUE;
-    ****/
+    h->type = htype_bcc;        /* Both Bcc: and Resent-Bcc: */
     break;
 
     break;
 
-    /* "Cc:" gets flagged, and the existence of a recipient header is noted,
-    whether it's resent- or not. */
-
     case htype_cc:
     case htype_cc:
-    h->type = htype_cc;
-    /****
-    to_or_cc_header_exists = TRUE;
-    ****/
+    h->type = htype_cc;         /* Both Cc: and Resent-Cc: */
     break;
 
     /* Record whether a Date: or Resent-Date: header exists, as appropriate. */
 
     case htype_date:
     break;
 
     /* 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. */
@@ -1976,18 +2011,22 @@ for (h = header_list->next; h != NULL; h = h->next)
     break;
 
     /* If there is a "Sender:" header and the message is locally originated,
     break;
 
     /* If there is a "Sender:" header and the message is locally originated,
-    and from an untrusted caller, or if we are in submission mode for a remote
-    message, mark it "old" so that it will not be transmitted with the message,
-    unless active_local_sender_retain is set. (This can only be true if
-    active_local_from_check is false.) If there are any resent- headers in the
-    message, apply this rule to Resent-Sender: instead of Sender:. Messages
-    with multiple resent- header sets cannot be tidily handled. (For this
-    reason, at least one MUA - Pine - turns old resent- headers into X-resent-
-    headers when resending, leaving just one set.) */
+    and from an untrusted caller and suppress_local_fixups is not set, or if we
+    are in submission mode for a remote message, mark it "old" so that it will
+    not be transmitted with the message, unless active_local_sender_retain is
+    set. (This can only be true if active_local_from_check is false.) If there
+    are any resent- headers in the message, apply this rule to Resent-Sender:
+    instead of Sender:. Messages with multiple resent- header sets cannot be
+    tidily handled. (For this reason, at least one MUA - Pine - turns old
+    resent- headers into X-resent- headers when resending, leaving just one
+    set.) */
 
     case htype_sender:
     h->type = ((!active_local_sender_retain &&
 
     case htype_sender:
     h->type = ((!active_local_sender_retain &&
-                ((sender_local && !trusted_caller) || submission_mode)
+                (
+                (sender_local && !trusted_caller && !suppress_local_fixups)
+                  || submission_mode
+                )
                ) &&
                (!resents_exist||is_resent))?
       htype_old : htype_sender;
                ) &&
                (!resents_exist||is_resent))?
       htype_old : htype_sender;
@@ -2063,8 +2102,6 @@ if (extract_recip)
     recipients_count = recipients_list_max = 0;
     }
 
     recipients_count = recipients_list_max = 0;
     }
 
-  parse_allow_group = TRUE;             /* Allow address group syntax */
-
   /* Now scan the headers */
 
   for (h = header_list->next; h != NULL; h = h->next)
   /* Now scan the headers */
 
   for (h = header_list->next; h != NULL; h = h->next)
@@ -2075,6 +2112,8 @@ if (extract_recip)
       uschar *s = Ustrchr(h->text, ':') + 1;
       while (isspace(*s)) s++;
 
       uschar *s = Ustrchr(h->text, ':') + 1;
       while (isspace(*s)) s++;
 
+      parse_allow_group = TRUE;          /* Allow address group syntax */
+
       while (*s != 0)
         {
         uschar *ss = parse_find_address_end(s, FALSE);
       while (*s != 0)
         {
         uschar *ss = parse_find_address_end(s, FALSE);
@@ -2139,24 +2178,19 @@ if (extract_recip)
 
         s = ss + (*ss? 1:0);
         while (isspace(*s)) s++;
 
         s = ss + (*ss? 1:0);
         while (isspace(*s)) s++;
-        }
+        }    /* Next address */
+
+      parse_allow_group = FALSE;      /* Reset group syntax flags */
+      parse_found_group = FALSE;
 
       /* If this was the bcc: header, mark it "old", which means it
       will be kept on the spool, but not transmitted as part of the
       message. */
 
 
       /* If this was the bcc: header, mark it "old", which means it
       will be kept on the spool, but not transmitted as part of the
       message. */
 
-      if (h->type == htype_bcc)
-        {
-        h->type = htype_old;
-        /****
-        bcc_header_exists = FALSE;
-        ****/
-        }
+      if (h->type == htype_bcc) h->type = htype_old;
       }   /* For appropriate header line */
     }     /* For each header line */
 
       }   /* For appropriate header line */
     }     /* For each header line */
 
-  parse_allow_group = FALSE;      /* Reset group syntax flags */
-  parse_found_group = FALSE;
   }
 
 /* Now build the unique message id. This has changed several times over the
   }
 
 /* Now build the unique message id. This has changed several times over the
@@ -2249,11 +2283,13 @@ ensure that it is an empty string. */
 message_subdir[0] = split_spool_directory? message_id[5] : 0;
 
 /* Now that we have the message-id, if there is no message-id: header, generate
 message_subdir[0] = split_spool_directory? message_id[5] : 0;
 
 /* Now that we have the message-id, if there is no message-id: header, generate
-one, but only for local or submission mode messages. This can be
-user-configured if required, but we had better flatten any illegal characters
-therein. */
+one, but only for local (without suppress_local_fixups) or submission mode
+messages. This can be user-configured if required, but we had better flatten
+any illegal characters therein. */
 
 
-if (msgid_header == NULL && (sender_host_address == NULL || submission_mode))
+if (msgid_header == NULL &&
+      ((sender_host_address == NULL && !suppress_local_fixups)
+        || submission_mode))
   {
   uschar *p;
   uschar *id_text = US"";
   {
   uschar *p;
   uschar *id_text = US"";
@@ -2302,8 +2338,9 @@ if (msgid_header == NULL && (sender_host_address == NULL || submission_mode))
 
   /* Add the header line */
 
 
   /* Add the header line */
 
-  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(FALSE, 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
   }
 
 /* If we are to log recipients, keep a copy of the raw ones before any possible
@@ -2327,17 +2364,41 @@ for (i = 0; i < recipients_count; i++)
     rewrite_address(recipients_list[i].address, TRUE, TRUE,
       global_rewrite_rules, rewrite_existflags);
 
     rewrite_address(recipients_list[i].address, TRUE, TRUE,
       global_rewrite_rules, rewrite_existflags);
 
-/* If there is no From: header, generate one for local or submission_mode
-messages. If there is no sender address, but the sender is local or this is a
-local delivery error, use the originator login. This shouldn't happen for
-genuine bounces, but might happen for autoreplies. The addition of From: must
-be done *before* checking for the possible addition of a Sender: header,
-because untrusted_set_sender allows an untrusted user to set anything in the
-envelope (which might then get info From:) but we still want to ensure a valid
-Sender: if it is required. */
-
-if (from_header == NULL && (sender_host_address == NULL || submission_mode))
+/* If there is no From: header, generate one for local (without
+suppress_local_fixups) or submission_mode messages. If there is no sender
+address, but the sender is local or this is a local delivery error, use the
+originator login. This shouldn't happen for genuine bounces, but might happen
+for autoreplies. The addition of From: must be done *before* checking for the
+possible addition of a Sender: header, because untrusted_set_sender allows an
+untrusted user to set anything in the envelope (which might then get info
+From:) but we still want to ensure a valid Sender: if it is required. */
+
+if (from_header == NULL &&
+    ((sender_host_address == NULL && !suppress_local_fixups)
+      || submission_mode))
   {
   {
+  uschar *oname = US"";
+
+  /* Use the originator_name if this is a locally submitted message and the
+  caller is not trusted. For trusted callers, use it only if -F was used to
+  force its value or if we have a non-SMTP message for which -f was not used
+  to set the sender. */
+
+  if (sender_host_address == NULL)
+    {
+    if (!trusted_caller || sender_name_forced ||
+         (!smtp_input && !sender_address_forced))
+      oname = originator_name;
+    }
+
+  /* For non-locally submitted messages, the only time we use the originator
+  name is when it was forced by the /name= option on control=submission. */
+
+  else
+    {
+    if (submission_name != NULL) oname = submission_name;
+    }
+
   /* Envelope sender is empty */
 
   if (sender_address[0] == 0)
   /* Envelope sender is empty */
 
   if (sender_address[0] == 0)
@@ -2345,8 +2406,8 @@ if (from_header == NULL && (sender_host_address == NULL || submission_mode))
     uschar *fromstart, *fromend;
 
     fromstart = string_sprintf("%sFrom: %s%s", resent_prefix,
     uschar *fromstart, *fromend;
 
     fromstart = string_sprintf("%sFrom: %s%s", resent_prefix,
-      originator_name, (originator_name[0] == 0)? "" : " <");
-    fromend = (originator_name[0] == 0)? US"" : US">";
+      oname, (oname[0] == 0)? "" : " <");
+    fromend = (oname[0] == 0)? US"" : US">";
 
     if (sender_local || local_error_message)
       {
 
     if (sender_local || local_error_message)
       {
@@ -2384,30 +2445,30 @@ if (from_header == NULL && (sender_host_address == NULL || submission_mode))
   else
     {
     header_add(htype_from, "%sFrom: %s%s%s%s\n", resent_prefix,
   else
     {
     header_add(htype_from, "%sFrom: %s%s%s%s\n", resent_prefix,
-      originator_name,
-      (originator_name[0] == 0)? "" : " <",
+      oname,
+      (oname[0] == 0)? "" : " <",
       (sender_address_unrewritten == NULL)?
         sender_address : sender_address_unrewritten,
       (sender_address_unrewritten == NULL)?
         sender_address : sender_address_unrewritten,
-      (originator_name[0] == 0)? "" : ">");
+      (oname[0] == 0)? "" : ">");
 
     from_header = header_last;    /* To get it checked for Sender: */
     }
   }
 
 
 
     from_header = header_last;    /* To get it checked for Sender: */
     }
   }
 
 
-/* If the sender is local, or if we are in submission mode and there is an
-authenticated_id, check that an existing From: is correct, and if not, generate
-a Sender: header, unless disabled. Any previously-existing Sender: header was
-removed above. Note that sender_local, as well as being TRUE if the caller of
-exim is not trusted, is also true if a trusted caller did not supply a -f
-argument for non-smtp input. To allow trusted callers to forge From: without
-supplying -f, we have to test explicitly here. If the From: header contains
-more than one address, then the call to parse_extract_address fails, and a
-Sender: header is inserted, as required. */
+/* If the sender is local (without suppress_local_fixups), or if we are in
+submission mode and there is an authenticated_id, check that an existing From:
+is correct, and if not, generate a Sender: header, unless disabled. Any
+previously-existing Sender: header was removed above. Note that sender_local,
+as well as being TRUE if the caller of exim is not trusted, is also true if a
+trusted caller did not supply a -f argument for non-smtp input. To allow
+trusted callers to forge From: without supplying -f, we have to test explicitly
+here. If the From: header contains more than one address, then the call to
+parse_extract_address fails, and a Sender: header is inserted, as required. */
 
 if (from_header != NULL &&
      (active_local_from_check &&
 
 if (from_header != NULL &&
      (active_local_from_check &&
-       ((sender_local && !trusted_caller) ||
+       ((sender_local && !trusted_caller && !suppress_local_fixups) ||
         (submission_mode && authenticated_id != NULL))
      ))
   {
         (submission_mode && authenticated_id != NULL))
      ))
   {
@@ -2469,12 +2530,14 @@ if (from_header != NULL &&
 
   if (make_sender)
     {
 
   if (make_sender)
     {
-    if (submission_mode && originator_name[0] == 0)
+    if (submission_mode && submission_name == NULL)
       header_add(htype_sender, "%sSender: %s\n", resent_prefix,
         generated_sender_address);
     else
       header_add(htype_sender, "%sSender: %s <%s>\n",
       header_add(htype_sender, "%sSender: %s\n", resent_prefix,
         generated_sender_address);
     else
       header_add(htype_sender, "%sSender: %s <%s>\n",
-        resent_prefix, originator_name, generated_sender_address);
+        resent_prefix,
+        submission_mode? submission_name : originator_name,
+        generated_sender_address);
     }
 
   /* Ensure that a non-null envelope sender address corresponds to the
     }
 
   /* Ensure that a non-null envelope sender address corresponds to the
@@ -2485,9 +2548,10 @@ 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);
     }
   }
 
     }
   }
 
@@ -2528,31 +2592,26 @@ for (h = header_list->next; h != NULL; h = h->next)
 
 
 /* An RFC 822 (sic) message is not legal unless it has at least one of "to",
 
 
 /* An RFC 822 (sic) message is not legal unless it has at least one of "to",
-"cc", or "bcc". Note that although the minimal examples in RFC822 show just
+"cc", or "bcc". Note that although the minimal examples in RFC 822 show just
 "to" or "bcc", the full syntax spec allows "cc" as well. If any resent- header
 exists, this applies to the set of resent- headers rather than the normal set.
 
 "to" or "bcc", the full syntax spec allows "cc" as well. If any resent- header
 exists, this applies to the set of resent- headers rather than the normal set.
 
-The requirement for a recipient header has been removed in RFC 2822. Earlier
-versions of Exim added a To: header for locally submitted messages, and an
-empty Bcc: header for others or when always_bcc was set. In the light of the
-changes in RFC 2822, we now always add Bcc: just in case there are still MTAs
-out there that insist on the RFC 822 syntax.
+The requirement for a recipient header has been removed in RFC 2822. At this
+point in the code, earlier versions of Exim added a To: header for locally
+submitted messages, and an empty Bcc: header for others. In the light of the
+changes in RFC 2822, this was dropped in November 2003. */
 
 
-November 2003: While generally revising what Exim does to fix up headers, it
-seems like a good time to remove this altogether. */
-
-/******
-if (!to_or_cc_header_exists && !bcc_header_exists)
-  header_add(htype_bcc, "Bcc:\n");
-******/
 
 /* If there is no date header, generate one if the message originates locally
 
 /* If there is no date header, generate one if the message originates locally
-(i.e. not over TCP/IP) or 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. */
+(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. */
 
 
-if (!date_header_exists && (sender_host_address == NULL || submission_mode))
-  header_add(htype_other, "%sDate: %s\n", resent_prefix, tod_stamp(tod_full));
+if (!date_header_exists &&
+      ((sender_host_address == NULL && !suppress_local_fixups)
+        || submission_mode))
+  header_add_at_position(FALSE, 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 */
 
@@ -2714,7 +2773,7 @@ the input in cases of output errors, since the far end doesn't expect to see
 anything until the terminating dot line is sent. */
 
 if (fflush(data_file) == EOF || ferror(data_file) ||
 anything until the terminating dot line is sent. */
 
 if (fflush(data_file) == EOF || ferror(data_file) ||
-    fsync(fileno(data_file)) < 0 || (receive_ferror)())
+    EXIMfsync(fileno(data_file)) < 0 || (receive_ferror)())
   {
   uschar *msg_errno = US strerror(errno);
   BOOL input_error = (receive_ferror)() != 0;
   {
   uschar *msg_errno = US strerror(errno);
   BOOL input_error = (receive_ferror)() != 0;
@@ -2897,6 +2956,9 @@ $message_body_end can be extracted if needed. Allow $recipients in expansions.
 */
 
 deliver_datafile = data_fd;
 */
 
 deliver_datafile = data_fd;
+user_msg = NULL;
+
+enable_dollar_recipients = TRUE;
 
 if (recipients_count == 0)
   {
 
 if (recipients_count == 0)
   {
@@ -2904,19 +2966,104 @@ if (recipients_count == 0)
   }
 else
   {
   }
 else
   {
-  enable_dollar_recipients = TRUE;
-
   /* Handle interactive SMTP messages */
 
   if (smtp_input && !smtp_batched_input)
     {
 
   /* Handle interactive SMTP messages */
 
   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;
+          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))) != NULL)
+            {
+            /* 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 != NULL)
+              {
+              uschar *seen_items_list = seen_items;
+              if (match_isinlist(item,
+                    &seen_items_list,0,NULL,NULL,MCL_STRING,TRUE,NULL) == OK)
+                {
+                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);
+                break;
+              }
+            }
+          add_acl_headers(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 */
@@ -2926,7 +3073,6 @@ else
 
     if (acl_smtp_data != NULL && recipients_count > 0)
       {
 
     if (acl_smtp_data != NULL && recipients_count > 0)
       {
-      uschar *user_msg, *log_msg;
       rc = acl_check(ACL_WHERE_DATA, NULL, acl_smtp_data, &user_msg, &log_msg);
       add_acl_headers(US"DATA");
       if (rc == DISCARD)
       rc = acl_check(ACL_WHERE_DATA, NULL, acl_smtp_data, &user_msg, &log_msg);
       add_acl_headers(US"DATA");
       if (rc == DISCARD)
@@ -2981,8 +3127,13 @@ else
 #ifdef WITH_CONTENT_SCAN
         unspool_mbox();
 #endif
 #ifdef WITH_CONTENT_SCAN
         unspool_mbox();
 #endif
-        log_write(0, LOG_MAIN|LOG_REJECT, "F=<%s> rejected by non-SMTP ACL: %s",
-          sender_address, log_msg);
+        /* The ACL can specify where rejections are to be logged, possibly
+        nowhere. The default is main and reject logs. */
+
+        if (log_reject_target != 0)
+          log_write(0, log_reject_target, "F=<%s> rejected by non-SMTP ACL: %s",
+            sender_address, log_msg);
+
         if (user_msg == NULL) user_msg = US"local configuration problem";
         if (smtp_batched_input)
           {
         if (user_msg == NULL) user_msg = US"local configuration problem";
         if (smtp_batched_input)
           {
@@ -3006,14 +3157,17 @@ else
 
   if (deliver_freeze) frozen_by = US"ACL";     /* for later logging */
   if (queue_only_policy) queued_by = US"ACL";
 
   if (deliver_freeze) frozen_by = US"ACL";     /* for later logging */
   if (queue_only_policy) queued_by = US"ACL";
-
-  enable_dollar_recipients = FALSE;
   }
 
 #ifdef WITH_CONTENT_SCAN
 unspool_mbox();
 #endif
 
   }
 
 #ifdef WITH_CONTENT_SCAN
 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
@@ -3039,6 +3193,8 @@ rc = local_scan(data_fd, &local_scan_data);
 alarm(0);
 os_non_restarting_signal(SIGALRM, sigalrm_handler);
 
 alarm(0);
 os_non_restarting_signal(SIGALRM, sigalrm_handler);
 
+enable_dollar_recipients = FALSE;
+
 store_pool = POOL_MAIN;   /* In case changed */
 DEBUG(D_receive) debug_printf("local_scan() returned %d %s\n", rc,
   local_scan_data);
 store_pool = POOL_MAIN;   /* In case changed */
 DEBUG(D_receive) debug_printf("local_scan() returned %d %s\n", rc,
   local_scan_data);
@@ -3060,7 +3216,7 @@ if (local_scan_data != NULL)
 
 if (rc == LOCAL_SCAN_ACCEPT_FREEZE)
   {
 
 if (rc == LOCAL_SCAN_ACCEPT_FREEZE)
   {
-  if (!deliver_freeze)      /* ACL might have already frozen */
+  if (!deliver_freeze)         /* ACL might have already frozen */
     {
     deliver_freeze = TRUE;
     deliver_frozen_at = time(NULL);
     {
     deliver_freeze = TRUE;
     deliver_frozen_at = time(NULL);
@@ -3106,9 +3262,9 @@ else
   {
   uschar *istemp = US"";
   uschar *s = NULL;
   {
   uschar *istemp = US"";
   uschar *s = NULL;
+  uschar *smtp_code;
   int size = 0;
   int sptr = 0;
   int size = 0;
   int sptr = 0;
-  int code;
 
   errmsg = local_scan_data;
 
 
   errmsg = local_scan_data;
 
@@ -3125,7 +3281,7 @@ else
     /* Fall through */
 
     case LOCAL_SCAN_REJECT:
     /* Fall through */
 
     case LOCAL_SCAN_REJECT:
-    code = 550;
+    smtp_code = US"550";
     if (errmsg == NULL) errmsg =  US"Administrative prohibition";
     break;
 
     if (errmsg == NULL) errmsg =  US"Administrative prohibition";
     break;
 
@@ -3135,7 +3291,7 @@ else
 
     case LOCAL_SCAN_TEMPREJECT:
     TEMPREJECT:
 
     case LOCAL_SCAN_TEMPREJECT:
     TEMPREJECT:
-    code = 451;
+    smtp_code = US"451";
     if (errmsg == NULL) errmsg = US"Temporary local problem";
     istemp = US"temporarily ";
     break;
     if (errmsg == NULL) errmsg = US"Temporary local problem";
     istemp = US"temporarily ";
     break;
@@ -3153,14 +3309,14 @@ else
     {
     if (!smtp_batched_input)
       {
     {
     if (!smtp_batched_input)
       {
-      smtp_respond(code, TRUE, errmsg);
+      smtp_respond(smtp_code, 3, TRUE, errmsg);
       message_id[0] = 0;            /* Indicate no message accepted */
       smtp_reply = US"";            /* Indicate reply already sent */
       goto TIDYUP;                  /* Skip to end of function */
       }
     else
       {
       message_id[0] = 0;            /* Indicate no message accepted */
       smtp_reply = US"";            /* Indicate reply already sent */
       goto TIDYUP;                  /* Skip to end of function */
       }
     else
       {
-      moan_smtp_batch(NULL, "%d %s", code, errmsg);
+      moan_smtp_batch(NULL, "%s %s", smtp_code, errmsg);
       /* Does not return */
       }
     }
       /* Does not return */
       }
     }
@@ -3301,7 +3457,8 @@ if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
   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, 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"\"");
+  s = string_append(s, &size, &sptr, 3, US" DN=\"",
+    string_printing(tls_peerdn), US"\"");
 #endif
 
 if (sender_host_authenticated != NULL)
 #endif
 
 if (sender_host_authenticated != NULL)
@@ -3359,22 +3516,6 @@ not put the zero in. */
 
 s[sptr] = 0;
 
 
 s[sptr] = 0;
 
-/* While writing to the log, set a flag to cause a call to receive_bomb_out()
-if the log cannot be opened. */
-
-receive_call_bombout = TRUE;
-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;
-
-/* 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);
-
 /* 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
 /* 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
@@ -3425,6 +3566,91 @@ if (message_logs && blackholed_by == NULL)
     }
   }
 
     }
   }
 
+/* Everything has now been done for a successful message except logging its
+arrival, and outputting an SMTP response. While writing to the log, set a flag
+to cause a call to receive_bomb_out() if the log cannot be opened. */
+
+receive_call_bombout = TRUE;
+
+/* Before sending an SMTP response in a TCP/IP session, we check to see if the
+connection has gone away. This can only be done if there is no unconsumed input
+waiting in the local input buffer. We can test for this by calling
+receive_smtp_buffered(). RFC 2920 (pipelining) explicitly allows for additional
+input to be sent following the final dot, so the presence of following input is
+not an error.
+
+If the connection is still present, but there is no unread input for the
+socket, the result of a select() call will be zero. If, however, the connection
+has gone away, or if there is pending input, the result of select() will be
+non-zero. The two cases can be distinguished by trying to read the next input
+character. If we succeed, we can unread it so that it remains in the local
+buffer for handling later. If not, the connection has been lost.
+
+Of course, since TCP/IP is asynchronous, there is always a chance that the
+connection will vanish between the time of this test and the sending of the
+response, but the chance of this happening should be small. */
+
+if (smtp_input && sender_host_address != NULL && !sender_host_notsocket &&
+    !receive_smtp_buffered())
+  {
+  struct timeval tv;
+  fd_set select_check;
+  FD_ZERO(&select_check);
+  FD_SET(fileno(smtp_in), &select_check);
+  tv.tv_sec = 0;
+  tv.tv_usec = 0;
+
+  if (select(fileno(smtp_in) + 1, &select_check, NULL, NULL, &tv) != 0)
+    {
+    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 */
+      smtp_yield = FALSE;   /* Nothing more on this connection */
+
+      /* Re-use the log line workspace */
+
+      sptr = 0;
+      s = string_cat(s, &size, &sptr, msg, Ustrlen(msg));
+      s = add_host_info_for_log(s, &size, &sptr);
+      s[sptr] = 0;
+      log_write(0, LOG_MAIN, "%s", s);
+
+      /* Delete the files for this aborted message. */
+
+      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);
+
+      goto TIDYUP;
+      }
+    }
+  }
+
+/* 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;
+
+/* 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);
+
 store_reset(s);   /* The store for the main log message can be reused */
 
 /* If the message is frozen, and freeze_tell is set, do the telling. */
 store_reset(s);   /* The store for the main log message can be reused */
 
 /* If the message is frozen, and freeze_tell is set, do the telling. */
@@ -3439,9 +3665,9 @@ if (deliver_freeze && freeze_tell != NULL && freeze_tell[0] != 0)
 
 /* Either a message has been successfully received and written to the two spool
 files, or an error in writing the spool has occurred for an SMTP message, or
 
 /* Either a message has been successfully received and written to the two spool
 files, or an error in writing the spool has occurred for an SMTP message, or
-an SMTP message has been rejected because of a bad sender. (For a non-SMTP
-message we will have already given up because there's no point in carrying on!)
-In either event, we must now close (and thereby unlock) the data file. In the
+an SMTP message has been rejected for policy reasons. (For a non-SMTP message
+we will have already given up because there's no point in carrying on!) In
+either event, we must now close (and thereby unlock) the data file. In the
 successful case, this leaves the message on the spool, ready for delivery. In
 the error case, the spool file will be deleted. Then tidy up store, interact
 with an SMTP call if necessary, and return.
 successful case, this leaves the message on the spool, ready for delivery. In
 the error case, the spool file will be deleted. Then tidy up store, interact
 with an SMTP call if necessary, and return.
@@ -3470,28 +3696,44 @@ if (smtp_input)
   yield = smtp_yield;
 
   /* Handle interactive SMTP callers. After several kinds of error, smtp_reply
   yield = smtp_yield;
 
   /* Handle interactive SMTP callers. After several kinds of error, smtp_reply
-  is set to the response. However, after an ACL error or local_scan() error,
-  the response has already been sent, and smtp_reply is an empty string to
-  indicate this. */
+  is set to the response that should be sent. When it is NULL, we generate
+  default responses. After an ACL error or local_scan() error, the response has
+  already been sent, and smtp_reply is an empty string to indicate this. */
 
   if (!smtp_batched_input)
     {
     if (smtp_reply == NULL)
       {
       if (fake_response != OK)
 
   if (!smtp_batched_input)
     {
     if (smtp_reply == NULL)
       {
       if (fake_response != OK)
-        smtp_respond(fake_response == DEFER ? 450 : 550,
-                     TRUE, fake_response_text);
+        smtp_respond((fake_response == DEFER)? US"450" : US"550", 3, TRUE,
+          fake_response_text);
+
+      /* An OK response is required; use "message" text if present. */
+
+      else if (user_msg != NULL)
+        {
+        uschar *code = US"250";
+        int len = 3;
+        smtp_message_code(&code, &len, &user_msg, NULL);
+        smtp_respond(code, len, TRUE, user_msg);
+        }
+
+      /* Default OK response */
+
       else
         smtp_printf("250 OK id=%s\r\n", message_id);
       if (host_checking)
         fprintf(stdout,
           "\n**** SMTP testing: that is not a real message id!\n\n");
       }
       else
         smtp_printf("250 OK id=%s\r\n", message_id);
       if (host_checking)
         fprintf(stdout,
           "\n**** SMTP testing: that is not a real message id!\n\n");
       }
+
+    /* smtp_reply is set non-empty */
+
     else if (smtp_reply[0] != 0)
       {
       if (fake_response != OK && (smtp_reply[0] == '2'))
     else if (smtp_reply[0] != 0)
       {
       if (fake_response != OK && (smtp_reply[0] == '2'))
-        smtp_respond(fake_response == DEFER ? 450 : 550,
-                     TRUE, fake_response_text);
+        smtp_respond((fake_response == DEFER)? US"450" : US"550", 3, TRUE,
+          fake_response_text);
       else
         smtp_printf("%.1024s\r\n", smtp_reply);
       }
       else
         smtp_printf("%.1024s\r\n", smtp_reply);
       }