DMARC: expand main-config options. Bug 3102
[exim.git] / src / src / receive.c
index 974756f06402f5856a87be0c3e70a04f942fb3bc..60051e36be3049bc6cb52a7079d582ba1c5222ff 100644 (file)
@@ -2,8 +2,10 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
+/* Copyright (c) The Exim Maintainers 2020 - 2023 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 
 /* Code for receiving a message and setting up spool files. */
 
 
 /* Code for receiving a message and setting up spool files. */
 
@@ -14,9 +16,9 @@
 extern int dcc_ok;
 #endif
 
 extern int dcc_ok;
 #endif
 
-#ifdef EXPERIMENTAL_DMARC
-# include "dmarc.h"
-#endif /* EXPERIMENTAL_DMARC */
+#ifdef SUPPORT_DMARC
+# include "miscmods/dmarc.h"
+#endif
 
 /*************************************************
 *                Local static variables          *
 
 /*************************************************
 *                Local static variables          *
@@ -43,42 +45,71 @@ receive_getc initially. They just call the standard functions, passing stdin as
 the file. (When SMTP input is occurring, different functions are used by
 changing the pointer variables.) */
 
 the file. (When SMTP input is occurring, different functions are used by
 changing the pointer variables.) */
 
-int
-stdin_getc(unsigned lim)
-{
-int c = getc(stdin);
+uschar stdin_buf[4096];
+uschar * stdin_inptr = stdin_buf;
+uschar * stdin_inend = stdin_buf;
 
 
-if (had_data_timeout)
-  {
-  fprintf(stderr, "exim: timed out while reading - message abandoned\n");
-  log_write(L_lost_incoming_connection,
-            LOG_MAIN, "timed out while reading local message");
-  receive_bomb_out(US"data-timeout", NULL);   /* Does not return */
-  }
-if (had_data_sigint)
+static BOOL
+stdin_refill(void)
+{
+size_t rc = fread(stdin_buf, 1, sizeof(stdin_buf), stdin);
+if (rc <= 0)
   {
   {
-  if (filter_test == FTEST_NONE)
+  if (had_data_timeout)
+    {
+    fprintf(stderr, "exim: timed out while reading - message abandoned\n");
+    log_write(L_lost_incoming_connection,
+             LOG_MAIN, "timed out while reading local message");
+    receive_bomb_out(US"data-timeout", NULL);   /* Does not return */
+    }
+  if (had_data_sigint)
     {
     {
-    fprintf(stderr, "\nexim: %s received - message abandoned\n",
-      had_data_sigint == SIGTERM ? "SIGTERM" : "SIGINT");
-    log_write(0, LOG_MAIN, "%s received while reading local message",
-      had_data_sigint == SIGTERM ? "SIGTERM" : "SIGINT");
+    if (filter_test == FTEST_NONE)
+      {
+      fprintf(stderr, "\nexim: %s received - message abandoned\n",
+       had_data_sigint == SIGTERM ? "SIGTERM" : "SIGINT");
+      log_write(0, LOG_MAIN, "%s received while reading local message",
+       had_data_sigint == SIGTERM ? "SIGTERM" : "SIGINT");
+      }
+    receive_bomb_out(US"signal-exit", NULL);    /* Does not return */
     }
     }
-  receive_bomb_out(US"signal-exit", NULL);    /* Does not return */
+  return FALSE;
   }
   }
-return c;
+stdin_inend = stdin_buf + rc;
+stdin_inptr = stdin_buf;
+return TRUE;
+}
+
+int
+stdin_getc(unsigned lim)
+{
+if (stdin_inptr >= stdin_inend)
+  if (!stdin_refill())
+      return EOF;
+return *stdin_inptr++;
+}
+
+
+BOOL
+stdin_hasc(void)
+{
+return stdin_inptr < stdin_inend;
 }
 
 int
 stdin_ungetc(int c)
 {
 }
 
 int
 stdin_ungetc(int c)
 {
-return ungetc(c, stdin);
+if (stdin_inptr <= stdin_buf)
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "buffer underflow in stdin_ungetc");
+
+*--stdin_inptr = c;
+return c;
 }
 
 int
 stdin_feof(void)
 {
 }
 
 int
 stdin_feof(void)
 {
-return feof(stdin);
+return stdin_hasc() ? FALSE : feof(stdin);
 }
 
 int
 }
 
 int
@@ -106,10 +137,10 @@ Returns:    TRUE for a trusted caller
 */
 
 BOOL
 */
 
 BOOL
-receive_check_set_sender(uschar *newsender)
+receive_check_set_sender(const uschar * newsender)
 {
 {
-uschar *qnewsender;
-if (trusted_caller) return TRUE;
+const uschar * qnewsender;
+if (f.trusted_caller) return TRUE;
 if (!newsender || !untrusted_set_sender) return FALSE;
 qnewsender = Ustrchr(newsender, '@')
   ? newsender : string_sprintf("%s@%s", newsender, qualify_domain_sender);
 if (!newsender || !untrusted_set_sender) return FALSE;
 qnewsender = Ustrchr(newsender, '@')
   ? newsender : string_sprintf("%s@%s", newsender, qualify_domain_sender);
@@ -144,7 +175,7 @@ Returns:        available on-root space, in kilobytes
 All values are -1 if the STATFS functions are not available.
 */
 
 All values are -1 if the STATFS functions are not available.
 */
 
-int
+int_eximarith_t
 receive_statvfs(BOOL isspool, int *inodeptr)
 {
 #ifdef HAVE_STATFS
 receive_statvfs(BOOL isspool, int *inodeptr)
 {
 #ifdef HAVE_STATFS
@@ -175,6 +206,7 @@ else
   empty item in a list. */
 
   if (*p == 0) p = US":";
   empty item in a list. */
 
   if (*p == 0) p = US":";
+  /* should never be a tainted list */
   while ((path = string_nextinlist(&p, &sep, buffer, sizeof(buffer))))
     if (Ustrcmp(path, "syslog") != 0)
       break;
   while ((path = string_nextinlist(&p, &sep, buffer, sizeof(buffer))))
     if (Ustrcmp(path, "syslog") != 0)
       break;
@@ -216,14 +248,14 @@ if (STATVFS(CS path, &statbuf) != 0)
     log_write(0, LOG_MAIN|LOG_PANIC, "cannot accept message: failed to stat "
       "%s directory %s: %s", name, path, strerror(errno));
     smtp_closedown(US"spool or log directory problem");
     log_write(0, LOG_MAIN|LOG_PANIC, "cannot accept message: failed to stat "
       "%s directory %s: %s", name, path, strerror(errno));
     smtp_closedown(US"spool or log directory problem");
-    exim_exit(EXIT_FAILURE, NULL);
+    exim_exit(EXIT_FAILURE);
     }
 
 *inodeptr = (statbuf.F_FILES > 0)? statbuf.F_FAVAIL : -1;
 
 /* Disks are getting huge. Take care with computing the size in kilobytes. */
 
     }
 
 *inodeptr = (statbuf.F_FILES > 0)? statbuf.F_FAVAIL : -1;
 
 /* Disks are getting huge. Take care with computing the size in kilobytes. */
 
-return (int)(((double)statbuf.F_BAVAIL * (double)statbuf.F_FRSIZE)/1024.0);
+return (int_eximarith_t)(((double)statbuf.F_BAVAIL * (double)statbuf.F_FRSIZE)/1024.0);
 
 #else
 /* Unable to find partition sizes in this environment. */
 
 #else
 /* Unable to find partition sizes in this environment. */
@@ -258,40 +290,40 @@ Returns:       FALSE if there isn't enough space, or if the information cannot
 BOOL
 receive_check_fs(int msg_size)
 {
 BOOL
 receive_check_fs(int msg_size)
 {
-int space, inodes;
+int inodes;
 
 if (check_spool_space > 0 || msg_size > 0 || check_spool_inodes > 0)
   {
 
 if (check_spool_space > 0 || msg_size > 0 || check_spool_inodes > 0)
   {
-  space = receive_statvfs(TRUE, &inodes);
+  int_eximarith_t space = receive_statvfs(TRUE, &inodes);
 
   DEBUG(D_receive)
 
   DEBUG(D_receive)
-    debug_printf("spool directory space = %dK inodes = %d "
-      "check_space = %dK inodes = %d msg_size = %d\n",
+    debug_printf("spool directory space = " PR_EXIM_ARITH "K inodes = %d "
+      "check_space = " PR_EXIM_ARITH "K inodes = %d msg_size = %d\n",
       space, inodes, check_spool_space, check_spool_inodes, msg_size);
 
       space, inodes, check_spool_space, check_spool_inodes, msg_size);
 
-  if ((space >= 0 && space < check_spool_space) ||
-      (inodes >= 0 && inodes < check_spool_inodes))
+  if (  space >= 0 && space + msg_size / 1024 < check_spool_space
+     || inodes >= 0 && inodes < check_spool_inodes)
     {
     {
-    log_write(0, LOG_MAIN, "spool directory space check failed: space=%d "
-      "inodes=%d", space, inodes);
+    log_write(0, LOG_MAIN, "spool directory space check failed: space="
+      PR_EXIM_ARITH " inodes=%d", space, inodes);
     return FALSE;
     }
   }
 
 if (check_log_space > 0 || check_log_inodes > 0)
   {
     return FALSE;
     }
   }
 
 if (check_log_space > 0 || check_log_inodes > 0)
   {
-  space = receive_statvfs(FALSE, &inodes);
+  int_eximarith_t space = receive_statvfs(FALSE, &inodes);
 
   DEBUG(D_receive)
 
   DEBUG(D_receive)
-    debug_printf("log directory space = %dK inodes = %d "
-      "check_space = %dK inodes = %d\n",
+    debug_printf("log directory space = " PR_EXIM_ARITH "K inodes = %d "
+      "check_space = " PR_EXIM_ARITH "K inodes = %d\n",
       space, inodes, check_log_space, check_log_inodes);
 
       space, inodes, check_log_space, check_log_inodes);
 
-  if ((space >= 0 && space < check_log_space) ||
-      (inodes >= 0 && inodes < check_log_inodes))
+  if (  space >= 0 && space < check_log_space
+     || inodes >= 0 && inodes < check_log_inodes)
     {
     {
-    log_write(0, LOG_MAIN, "log directory space check failed: space=%d "
-      "inodes=%d", space, inodes);
+    log_write(0, LOG_MAIN, "log directory space check failed: space=" PR_EXIM_ARITH
+      " inodes=%d", space, inodes);
     return FALSE;
     }
   }
     return FALSE;
     }
   }
@@ -371,7 +403,7 @@ if (!already_bombing_out)
 
 /* Exit from the program (non-BSMTP cases) */
 
 
 /* Exit from the program (non-BSMTP cases) */
 
-exim_exit(EXIT_FAILURE, NULL);
+exim_exit(EXIT_FAILURE);
 }
 
 
 }
 
 
@@ -481,15 +513,22 @@ Returns:      nothing
 */
 
 void
 */
 
 void
-receive_add_recipient(uschar *recipient, int pno)
+receive_add_recipient(const uschar * recipient, int pno)
 {
 if (recipients_count >= recipients_list_max)
   {
   recipient_item *oldlist = recipients_list;
   int oldmax = recipients_list_max;
 {
 if (recipients_count >= recipients_list_max)
   {
   recipient_item *oldlist = recipients_list;
   int oldmax = recipients_list_max;
-  recipients_list_max = recipients_list_max? 2*recipients_list_max : 50;
-  recipients_list = store_get(recipients_list_max * sizeof(recipient_item));
-  if (oldlist != NULL)
+
+  const int safe_recipients_limit = INT_MAX / 2 / sizeof(recipient_item);
+  if (recipients_list_max < 0 || recipients_list_max >= safe_recipients_limit)
+    {
+    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Too many recipients: %d", recipients_list_max);
+    }
+
+  recipients_list_max = recipients_list_max ? 2*recipients_list_max : 50;
+  recipients_list = store_get(recipients_list_max * sizeof(recipient_item), GET_UNTAINTED);
+  if (oldlist)
     memcpy(recipients_list, oldlist, oldmax * sizeof(recipient_item));
   }
 
     memcpy(recipients_list, oldlist, oldmax * sizeof(recipient_item));
   }
 
@@ -530,7 +569,7 @@ smtp_user_msg(uschar *code, uschar *user_msg)
 {
 int len = 3;
 smtp_message_code(&code, &len, &user_msg, NULL, TRUE);
 {
 int len = 3;
 smtp_message_code(&code, &len, &user_msg, NULL, TRUE);
-smtp_respond(code, len, TRUE, user_msg);
+smtp_respond(code, len, SR_FINAL, user_msg);
 }
 #endif
 
 }
 #endif
 
@@ -551,13 +590,11 @@ Returns:      TRUE if it did remove something; FALSE otherwise
 */
 
 BOOL
 */
 
 BOOL
-receive_remove_recipient(uschar *recipient)
+receive_remove_recipient(const uschar * recipient)
 {
 {
-int count;
 DEBUG(D_receive) debug_printf("receive_remove_recipient(\"%s\") called\n",
   recipient);
 DEBUG(D_receive) debug_printf("receive_remove_recipient(\"%s\") called\n",
   recipient);
-for (count = 0; count < recipients_count; count++)
-  {
+for (int count = 0; count < recipients_count; count++)
   if (Ustrcmp(recipients_list[count].address, recipient) == 0)
     {
     if ((--recipients_count - count) > 0)
   if (Ustrcmp(recipients_list[count].address, recipient) == 0)
     {
     if ((--recipients_count - count) > 0)
@@ -565,7 +602,6 @@ for (count = 0; count < recipients_count; count++)
         (recipients_count - count)*sizeof(recipient_item));
     return TRUE;
     }
         (recipients_count - count)*sizeof(recipient_item));
     return TRUE;
     }
-  }
 return FALSE;
 }
 
 return FALSE;
 }
 
@@ -573,6 +609,26 @@ return FALSE;
 
 
 
 
 
 
+/* Pause for a while waiting for input.  If none received in that time,
+close the logfile, if we had one open; then if we wait for a long-running
+datasource (months, in one use-case) log rotation will not leave us holding
+the file copy. */
+
+static void
+log_close_chk(void)
+{
+if (!receive_timeout && !receive_hasc())
+  {
+  struct timeval t;
+  timesince(&t, &received_time);
+  if (t.tv_sec > 30*60)
+    mainlog_close();
+  else
+    if (poll_one_fd(0, POLLIN, (30*60 - t.tv_sec) * 1000) == 0)
+      mainlog_close();
+  }
+}
+
 /*************************************************
 *     Read data portion of a non-SMTP message    *
 *************************************************/
 /*************************************************
 *     Read data portion of a non-SMTP message    *
 *************************************************/
@@ -619,11 +675,13 @@ register int linelength = 0;
 
 /* Handle the case when only EOF terminates the message */
 
 
 /* Handle the case when only EOF terminates the message */
 
-if (!dot_ends)
+if (!f.dot_ends)
   {
   {
-  register int last_ch = '\n';
+  int last_ch = '\n';
 
 
-  for (; (ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF; last_ch = ch)
+  for ( ;
+       log_close_chk(), (ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != 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')
@@ -665,7 +723,7 @@ if (!dot_ends)
 
 ch_state = 1;
 
 
 ch_state = 1;
 
-while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF)
+while (log_close_chk(), (ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF)
   {
   if (ch == 0) body_zerocount++;
   switch (ch_state)
   {
   if (ch == 0) body_zerocount++;
   switch (ch_state)
@@ -770,100 +828,114 @@ July 2003: Bare CRs cause trouble. We now treat them as line terminators as
 well, so that there are no CRs in spooled messages. However, the message
 terminating dot is not recognized between two bare CRs.
 
 well, so that there are no CRs in spooled messages. However, the message
 terminating dot is not recognized between two bare CRs.
 
+Dec 2023: getting a site to send a body including an "LF . LF" sequence
+followed by SMTP commands is a possible "smtp smuggling" attack.  If
+the first (header) line for the message has a proper CRLF then enforce
+that for the body: convert bare LF to a space.
+
 Arguments:
 Arguments:
-  fout      a FILE to which to write the message; NULL if skipping
+  fout         a FILE to which to write the message; NULL if skipping
+  strict_crlf  require full CRLF sequence as a line ending
 
 Returns:    One of the END_xxx values indicating why it stopped reading
 */
 
 static int
 
 Returns:    One of the END_xxx values indicating why it stopped reading
 */
 
 static int
-read_message_data_smtp(FILE *fout)
+read_message_data_smtp(FILE * fout, BOOL strict_crlf)
 {
 {
-int ch_state = 0;
-int ch;
-int linelength = 0;
+enum { s_linestart, s_normal, s_had_cr, s_had_nl_dot, s_had_dot_cr } ch_state =
+             s_linestart;
+int linelength = 0, ch;
 
 while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF)
   {
   if (ch == 0) body_zerocount++;
   switch (ch_state)
     {
 
 while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF)
   {
   if (ch == 0) body_zerocount++;
   switch (ch_state)
     {
-    case 0:                             /* After LF or CRLF */
-    if (ch == '.')
-      {
-      ch_state = 3;
-      continue;                         /* Don't ever write . after LF */
-      }
-    ch_state = 1;
+    case s_linestart:                  /* After LF or CRLF */
+      if (ch == '.')
+       {
+       ch_state = s_had_nl_dot;
+       continue;                       /* Don't ever write . after LF */
+       }
+      ch_state = s_normal;
 
 
-    /* Else fall through to handle as normal uschar. */
+      /* Else fall through to handle as normal uschar. */
 
 
-    case 1:                             /* Normal state */
-    if (ch == '\n')
-      {
-      ch_state = 0;
-      body_linecount++;
+    case s_normal:                     /* Normal state */
+      if (ch == '\r')
+       {
+       ch_state = s_had_cr;
+       continue;                       /* Don't write the CR */
+       }
+      if (ch == '\n')                  /* Bare LF at end of line */
+       if (strict_crlf)
+         ch = ' ';                     /* replace LF with space */
+       else
+         {                             /* treat as line ending */
+         ch_state = s_linestart;
+         body_linecount++;
+         if (linelength > max_received_linelength)
+           max_received_linelength = linelength;
+         linelength = -1;
+         }
+      break;
+
+    case s_had_cr:                     /* After (unwritten) CR */
+      body_linecount++;                        /* Any char ends line */
       if (linelength > max_received_linelength)
       if (linelength > max_received_linelength)
-        max_received_linelength = linelength;
+       max_received_linelength = linelength;
       linelength = -1;
       linelength = -1;
-      }
-    else if (ch == '\r')
-      {
-      ch_state = 2;
-      continue;
-      }
-    break;
+      if (ch == '\n')                  /* proper CRLF */
+       ch_state = s_linestart;
+      else
+       {
+       message_size++;         /* convert the dropped CR to a stored NL */
+       if (fout && fputc('\n', fout) == EOF) return END_WERROR;
+       cutthrough_data_put_nl();
+       if (ch == '\r')                 /* CR; do not write */
+         continue;
+       ch_state = s_normal;            /* not LF or CR; process as standard */
+       }
+      break;
 
 
-    case 2:                             /* After (unwritten) CR */
-    body_linecount++;
-    if (linelength > max_received_linelength)
-      max_received_linelength = linelength;
-    linelength = -1;
-    if (ch == '\n')
-      {
-      ch_state = 0;
-      }
-    else
-      {
-      message_size++;
-      if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
-      cutthrough_data_put_nl();
-      if (ch != '\r') ch_state = 1; else continue;
-      }
-    break;
+    case s_had_nl_dot:                 /* After [CR] LF . */
+      if (ch == '\n')                  /* [CR] LF . LF */
+       if (strict_crlf)
+         ch = ' ';                     /* replace LF with space */
+       else
+         return END_DOT;
+      else if (ch == '\r')             /* [CR] LF . CR */
+       {
+       ch_state = s_had_dot_cr;
+       continue;                       /* Don't write the CR */
+       }
+      /* The dot was removed on reaching s_had_nl_dot. For a doubled dot, here,
+      reinstate it to cutthrough. The current ch, dot or not, is passed both to
+      cutthrough and to file below. */
+      else if (ch == '.')
+       {
+       uschar c = ch;
+       cutthrough_data_puts(&c, 1);
+       }
+      ch_state = s_normal;
+      break;
 
 
-    case 3:                             /* After [CR] LF . */
-    if (ch == '\n')
-      return END_DOT;
-    if (ch == '\r')
-      {
-      ch_state = 4;
-      continue;
-      }
-    /* The dot was removed at state 3. For a doubled dot, here, reinstate
-    it to cutthrough. The current ch, dot or not, is passed both to cutthrough
-    and to file below. */
-    if (ch == '.')
-      {
-      uschar c= ch;
-      cutthrough_data_puts(&c, 1);
-      }
-    ch_state = 1;
-    break;
+    case s_had_dot_cr:                 /* After [CR] LF . CR */
+      if (ch == '\n')
+       return END_DOT;                 /* Preferred termination */
 
 
-    case 4:                             /* After [CR] LF . CR */
-    if (ch == '\n') return END_DOT;
-    message_size++;
-    body_linecount++;
-    if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
-    cutthrough_data_put_nl();
-    if (ch == '\r')
-      {
-      ch_state = 2;
-      continue;
-      }
-    ch_state = 1;
-    break;
+      message_size++;          /* convert the dropped CR to a stored NL */
+      body_linecount++;
+      if (fout && fputc('\n', fout) == EOF) return END_WERROR;
+      cutthrough_data_put_nl();
+      if (ch == '\r')
+       {
+       ch_state = s_had_cr;
+       continue;                       /* CR; do not write */
+       }
+      ch_state = s_normal;
+      break;
     }
 
   /* Add the character to the spool file, unless skipping; then loop for the
     }
 
   /* Add the character to the spool file, unless skipping; then loop for the
@@ -909,7 +981,7 @@ Returns:    One of the END_xxx values indicating why it stopped reading
 */
 
 static int
 */
 
 static int
-read_message_bdat_smtp(FILE *fout)
+read_message_bdat_smtp(FILE * fout)
 {
 int linelength = 0, ch;
 enum CH_STATE ch_state = LF_SEEN;
 {
 int linelength = 0, ch;
 enum CH_STATE ch_state = LF_SEEN;
@@ -1015,7 +1087,7 @@ for(;;)
 }
 
 static int
 }
 
 static int
-read_message_bdat_smtp_wire(FILE *fout)
+read_message_bdat_smtp_wire(FILE * fout)
 {
 int ch;
 
 {
 int ch;
 
@@ -1023,7 +1095,7 @@ int ch;
 
 DEBUG(D_receive) debug_printf("CHUNKING: %s\n",
        fout ? "writing spoolfile in wire format" : "flushing input");
 
 DEBUG(D_receive) debug_printf("CHUNKING: %s\n",
        fout ? "writing spoolfile in wire format" : "flushing input");
-spool_file_wireformat = TRUE;
+f.spool_file_wireformat = TRUE;
 
 for (;;)
   {
 
 for (;;)
   {
@@ -1079,7 +1151,7 @@ receive_swallow_smtp(void)
 {
 if (message_ended >= END_NOTENDED)
   message_ended = chunking_state <= CHUNKING_OFFERED
 {
 if (message_ended >= END_NOTENDED)
   message_ended = chunking_state <= CHUNKING_OFFERED
-     ? read_message_data_smtp(NULL)
+     ? read_message_data_smtp(NULL, FALSE)
      : read_message_bdat_smtp_wire(NULL);
 }
 
      : read_message_bdat_smtp_wire(NULL);
 }
 
@@ -1097,7 +1169,7 @@ Returns:   the SMTP response
 */
 
 static uschar *
 */
 
 static uschar *
-handle_lost_connection(uschar *s)
+handle_lost_connection(uschar * s)
 {
 log_write(L_lost_incoming_connection | L_smtp_connection, LOG_MAIN,
   "%s lost while reading message data%s", smtp_get_connection_info(), s);
 {
 log_write(L_lost_incoming_connection | L_smtp_connection, LOG_MAIN,
   "%s lost while reading message data%s", smtp_get_connection_info(), s);
@@ -1131,6 +1203,8 @@ static void
 give_local_error(int errcode, uschar *text1, uschar *text2, int error_rc,
   FILE *f, header_line *hptr)
 {
 give_local_error(int errcode, uschar *text1, uschar *text2, int error_rc,
   FILE *f, header_line *hptr)
 {
+DEBUG(D_all) debug_printf("%s%s\n", text2, text1);
+
 if (error_handling == ERRORS_SENDER)
   {
   error_block eblock;
 if (error_handling == ERRORS_SENDER)
   {
   error_block eblock;
@@ -1143,7 +1217,7 @@ if (error_handling == ERRORS_SENDER)
 else
   fprintf(stderr, "exim: %s%s\n", text2, text1);  /* Sic */
 (void)fclose(f);
 else
   fprintf(stderr, "exim: %s%s\n", text2, text1);  /* Sic */
 (void)fclose(f);
-exim_exit(error_rc, US"");
+exim_exit(error_rc);
 }
 
 
 }
 
 
@@ -1171,10 +1245,9 @@ Returns:     nothing
 */
 
 static void
 */
 
 static void
-add_acl_headers(int where, uschar *acl_name)
+add_acl_headers(int where, uschar * acl_name)
 {
 {
-header_line *h, *next;
-header_line *last_received = NULL;
+header_line * last_received = NULL;
 
 switch(where)
   {
 
 switch(where)
   {
@@ -1194,18 +1267,24 @@ if (acl_removed_headers)
   {
   DEBUG(D_receive|D_acl) debug_printf_indent(">>Headers removed by %s ACL:\n", acl_name);
 
   {
   DEBUG(D_receive|D_acl) debug_printf_indent(">>Headers removed by %s ACL:\n", acl_name);
 
-  for (h = header_list; h; h = h->next) if (h->type != htype_old)
+  for (header_line * h = header_list; h; h = h->next) if (h->type != htype_old)
     {
     {
-    const uschar * list = acl_removed_headers;
+    const uschar * list = acl_removed_headers, * s;
     int sep = ':';         /* This is specified as a colon-separated list */
     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))
+    /* If a list element has a leading '^' then it is an RE for
+    the whole header, else just a header name. */
+    while ((s = string_nextinlist(&list, &sep, NULL, 0)))
+      if (  (  *s == '^'
+           && regex_match(
+               regex_must_compile(s, MCS_CACHEABLE, FALSE),
+               h->text, h->slen, NULL)
+            )
+        || header_testname(h, s, Ustrlen(s), FALSE)
+        )
        {
        h->type = htype_old;
        {
        h->type = htype_old;
-        DEBUG(D_receive|D_acl) debug_printf_indent("  %s", h->text);
+       DEBUG(D_receive|D_acl) debug_printf_indent("  %s", h->text);
        }
     }
   acl_removed_headers = NULL;
        }
     }
   acl_removed_headers = NULL;
@@ -1215,7 +1294,7 @@ if (acl_removed_headers)
 if (!acl_added_headers) return;
 DEBUG(D_receive|D_acl) debug_printf_indent(">>Headers added by %s ACL:\n", acl_name);
 
 if (!acl_added_headers) return;
 DEBUG(D_receive|D_acl) debug_printf_indent(">>Headers added by %s ACL:\n", acl_name);
 
-for (h = acl_added_headers; h; h = next)
+for (header_line * h = acl_added_headers, * next; h; h = next)
   {
   next = h->next;
 
   {
   next = h->next;
 
@@ -1305,21 +1384,32 @@ if (sender_fullhost)
   if (LOGGING(dnssec) && sender_host_dnssec)   /*XXX sender_helo_dnssec? */
     g = string_catn(g, US" DS", 3);
   g = string_append(g, 2, US" H=", sender_fullhost);
   if (LOGGING(dnssec) && sender_host_dnssec)   /*XXX sender_helo_dnssec? */
     g = string_catn(g, US" DS", 3);
   g = string_append(g, 2, US" H=", sender_fullhost);
-  if (LOGGING(incoming_interface) && interface_address != NULL)
-    {
-    g = string_cat(g,
-      string_sprintf(" I=[%s]:%d", interface_address, interface_port));
-    }
+  if (LOGGING(incoming_interface) && interface_address)
+    g = string_fmt_append(g, " I=[%s]:%d", interface_address, interface_port);
   }
   }
-if (tcp_in_fastopen && !tcp_in_fastopen_logged)
+if (f.tcp_in_fastopen && !f.tcp_in_fastopen_logged)
   {
   {
-  g = string_catn(g, US" TFO", 4);
-  tcp_in_fastopen_logged = TRUE;
+  g = string_catn(g, US" TFO*", f.tcp_in_fastopen_data ? 5 : 4);
+  f.tcp_in_fastopen_logged = TRUE;
   }
 if (sender_ident)
   g = string_append(g, 2, US" U=", sender_ident);
   }
 if (sender_ident)
   g = string_append(g, 2, US" U=", sender_ident);
+if (LOGGING(connection_id))
+  g = string_fmt_append(g, " Ci=%s", connection_id);
 if (received_protocol)
   g = string_append(g, 2, US" P=", received_protocol);
 if (received_protocol)
   g = string_append(g, 2, US" P=", received_protocol);
+if (LOGGING(pipelining) && f.smtp_in_pipelining_advertised)
+  {
+  g = string_catn(g, US" L", 2);
+#ifndef DISABLE_PIPE_CONNECT
+  if (f.smtp_in_early_pipe_used)
+    g = string_catn(g, US"*", 1);
+  else if (f.smtp_in_early_pipe_advertised)
+    g = string_catn(g, US".", 1);
+#endif
+  if (!f.smtp_in_pipelining_used)
+    g = string_catn(g, US"-", 1);
+  }
 return g;
 }
 
 return g;
 }
 
@@ -1350,7 +1440,6 @@ run_mime_acl(uschar *acl, BOOL *smtp_yield_ptr, uschar **smtp_reply_ptr,
 FILE *mbox_file;
 uschar * rfc822_file_path = NULL;
 unsigned long mbox_size;
 FILE *mbox_file;
 uschar * rfc822_file_path = NULL;
 unsigned long mbox_size;
-header_line *my_headerlist;
 uschar *user_msg, *log_msg;
 int mime_part_count_buffer = -1;
 uschar * mbox_filename;
 uschar *user_msg, *log_msg;
 int mime_part_count_buffer = -1;
 uschar * mbox_filename;
@@ -1358,7 +1447,8 @@ int rc = OK;
 
 /* check if it is a MIME message */
 
 
 /* check if it is a MIME message */
 
-for (my_headerlist = header_list; my_headerlist; my_headerlist = my_headerlist->next)
+for (header_line * my_headerlist = header_list; my_headerlist;
+     my_headerlist = my_headerlist->next)
   if (  my_headerlist->type != '*'                     /* skip deleted headers */
      && strncmpic(my_headerlist->text, US"Content-Type:", 13) == 0
      )
   if (  my_headerlist->type != '*'                     /* skip deleted headers */
      && strncmpic(my_headerlist->text, US"Content-Type:", 13) == 0
      )
@@ -1382,7 +1472,7 @@ if (!(mbox_file = spool_mbox(&mbox_size, NULL, &mbox_filename)))
 #ifdef EXPERIMENTAL_DCC
   dcc_ok = 0;
 #endif
 #ifdef EXPERIMENTAL_DCC
   dcc_ok = 0;
 #endif
-  smtp_respond(US"451", 3, TRUE, US"temporary local problem");
+  smtp_respond(US"451", 3, SR_FINAL, 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 */
@@ -1416,7 +1506,7 @@ if (rc == OK)
   struct dirent * entry;
   DIR * tempdir;
 
   struct dirent * entry;
   DIR * tempdir;
 
-  for (tempdir = opendir(CS scandir); entry = readdir(tempdir); )
+  for (tempdir = exim_opendir(scandir); entry = readdir(tempdir); )
     if (strncmpic(US entry->d_name, US"__rfc822_", 9) == 0)
       {
       rfc822_file_path = string_sprintf("%s/%s", scandir, entry->d_name);
     if (strncmpic(US entry->d_name, US"__rfc822_", 9) == 0)
       {
       rfc822_file_path = string_sprintf("%s/%s", scandir, entry->d_name);
@@ -1478,12 +1568,12 @@ return TRUE;
 void
 received_header_gen(void)
 {
 void
 received_header_gen(void)
 {
-uschar *received;
-uschar *timestamp;
-header_line *received_header= header_list;
+uschar * received;
+uschar * timestamp = expand_string(US"${tod_full}");
+header_line * received_header= header_list;
 
 
-timestamp = expand_string(US"${tod_full}");
 if (recipients_count == 1) received_for = recipients_list[0].address;
 if (recipients_count == 1) received_for = recipients_list[0].address;
+GET_OPTION("received_header_text");
 received = expand_string(received_header_text);
 received_for = NULL;
 
 received = expand_string(received_header_text);
 received_for = NULL;
 
@@ -1501,14 +1591,14 @@ 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. */
 
 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)
+if (!received[0])
   {
   received_header->text = string_sprintf("Received: ; %s\n", timestamp);
   received_header->type = htype_old;
   }
 else
   {
   {
   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->text = string_sprintf("%s;\n\t%s\n", received, timestamp);
   received_header->type = htype_received;
   }
 
   received_header->type = htype_received;
   }
 
@@ -1613,17 +1703,17 @@ not. */
 BOOL
 receive_msg(BOOL extract_recip)
 {
 BOOL
 receive_msg(BOOL extract_recip)
 {
-int  i;
 int  rc = FAIL;
 int  msg_size = 0;
 int  process_info_len = Ustrlen(process_info);
 int  error_rc = error_handling == ERRORS_SENDER
        ? errors_sender_rc : EXIT_FAILURE;
 int  header_size = 256;
 int  rc = FAIL;
 int  msg_size = 0;
 int  process_info_len = Ustrlen(process_info);
 int  error_rc = error_handling == ERRORS_SENDER
        ? errors_sender_rc : EXIT_FAILURE;
 int  header_size = 256;
-int  start, end, domain;
-int  id_resolution;
 int  had_zero = 0;
 int  prevlines_length = 0;
 int  had_zero = 0;
 int  prevlines_length = 0;
+const int id_resolution = BASE_62 == 62 && !host_number_string ? 1
+  : BASE_62 != 62 && host_number_string ? 4
+  : 2;
 
 int ptr = 0;
 
 
 int ptr = 0;
 
@@ -1646,6 +1736,7 @@ uschar *frozen_by = NULL;
 uschar *queued_by = NULL;
 
 uschar *errmsg;
 uschar *queued_by = NULL;
 
 uschar *errmsg;
+rmark rcvd_log_reset_point;
 gstring * g;
 struct stat statbuf;
 
 gstring * g;
 struct stat statbuf;
 
@@ -1656,7 +1747,8 @@ uschar *user_msg, *log_msg;
 
 /* Working header pointers */
 
 
 /* Working header pointers */
 
-header_line *h, *next;
+rmark reset_point;
+header_line * next;
 
 /* Flags for noting the existence of certain headers (only one left) */
 
 
 /* Flags for noting the existence of certain headers (only one left) */
 
@@ -1664,20 +1756,22 @@ BOOL date_header_exists = FALSE;
 
 /* Pointers to receive the addresses of headers whose contents we need. */
 
 
 /* Pointers to receive the addresses of headers whose contents we need. */
 
-header_line *from_header = NULL;
-header_line *subject_header = NULL;
-header_line *msgid_header = NULL;
-header_line *received_header;
-
-#ifdef EXPERIMENTAL_DMARC
-int dmarc_up = 0;
-#endif /* EXPERIMENTAL_DMARC */
+header_line * from_header = NULL;
+#ifdef SUPPORT_DMARC
+header_line * dmarc_from_header = NULL;
+#endif
+header_line * subject_header = NULL, * msgid_header = NULL, * received_header;
+BOOL msgid_header_newly_created = FALSE;
 
 /* Variables for use when building the Received: header. */
 
 
 /* Variables for use when building the Received: header. */
 
-uschar *timestamp;
+uschar * timestamp;
 int tslen;
 
 int tslen;
 
+/* Time of creation of message_id */
+
+static struct timeval message_id_tv = { 0, 0 };
+
 
 /* Release any open files that might have been cached while preparing to
 accept the message - e.g. by verifying addresses - because reading a message
 
 /* Release any open files that might have been cached while preparing to
 accept the message - e.g. by verifying addresses - because reading a message
@@ -1695,16 +1789,18 @@ if (extract_recip || !smtp_input)
 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. */
 
 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. */
 
-received_header = header_list = header_last = store_get(sizeof(header_line));
+received_header = header_list = header_last = store_get(sizeof(header_line), GET_UNTAINTED);
 header_list->next = NULL;
 header_list->type = htype_old;
 header_list->text = NULL;
 header_list->slen = 0;
 
 header_list->next = NULL;
 header_list->type = htype_old;
 header_list->text = NULL;
 header_list->slen = 0;
 
-/* Control block for the next header to be read. */
+/* Control block for the next header to be read.
+The data comes from the message, so is tainted. */
 
 
-next = store_get(sizeof(header_line));
-next->text = store_get(header_size);
+reset_point = store_mark();
+next = store_get(sizeof(header_line), GET_UNTAINTED);
+next->text = store_get(header_size, GET_TAINTED);
 
 /* Initialize message id to be null (indicating no message read), and the
 header names list to be the normal list. Indicate there is no data file open
 
 /* Initialize message id to be null (indicating no message read), and the
 header names list to be the normal list. Indicate there is no data file open
@@ -1725,29 +1821,50 @@ 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;
 
-#ifndef DISABLE_DKIM
-/* Call into DKIM to set up the context.  In CHUNKING mode
-we clear the dot-stuffing flag */
-if (smtp_input && !smtp_batched_input && !dkim_disable_verify)
-  dkim_exim_verify_init(chunking_state <= CHUNKING_OFFERED);
+#ifdef WITH_CONTENT_SCAN
+/* reset non-per-part mime variables */
+mime_is_coverletter    = 0;
+mime_is_rfc822         = 0;
+mime_part_count        = -1;
 #endif
 
 #endif
 
-#ifdef EXPERIMENTAL_DMARC
-/* initialize libopendmarc */
-dmarc_up = dmarc_init();
-#endif
+if (misc_mod_msg_init() != OK)
+  goto TIDYUP;
+
+/* In SMTP sessions we may receive several messages in one connection. Before
+each subsequent one, we wait for the clock to tick at the level of message-id
+granularity.
+This is so that the combination of time+pid is unique, even on systems where the
+pid can be re-used within our time interval. We can't shorten the interval
+without re-designing the message-id. See comments above where the message id is
+created. This is Something For The Future.
+Do this wait any time we have previously created a message-id, even if we
+rejected the message.  This gives unique IDs for logging done by ACLs.
+The initial timestamp must have been obtained via exim_gettime() to avoid
+issues on Linux with suspend/resume. */
+
+if (message_id_tv.tv_sec)
+  {
+  message_id_tv.tv_usec = (message_id_tv.tv_usec/id_resolution) * id_resolution;
+  exim_wait_tick(&message_id_tv, id_resolution);
+  }
 
 /* Remember the time of reception. Exim uses time+pid for uniqueness of message
 ids, and fractions of a second are required. See the comments that precede the
 
 /* Remember the time of reception. Exim uses time+pid for uniqueness of message
 ids, and fractions of a second are required. See the comments that precede the
-message id creation below. */
+message id creation below.
+We use a routine that if possible uses a monotonic clock, and can be used again
+after reception for the tick-wait even under the Linux non-Posix behaviour. */
 
 
-(void)gettimeofday(&message_id_tv, NULL);
+else
+  exim_gettime(&message_id_tv);
 
 /* For other uses of the received time we can operate with granularity of one
 second, and for that we use the global variable received_time. This is for
 
 /* For other uses of the received time we can operate with granularity of one
 second, and for that we use the global variable received_time. This is for
-things like ultimate message timeouts. */
+things like ultimate message timeouts.
+For this we do not care about the Linux suspend/resume problem, so rather than
+use exim_gettime() everywhere we use a plain gettimeofday() here. */
 
 
-received_time = message_id_tv;
+gettimeofday(&received_time, NULL);
 
 /* If SMTP input, set the special handler for timeouts. The alarm() calls
 happen in the smtp_getc() function when it refills its buffer. */
 
 /* If SMTP input, set the special handler for timeouts. The alarm() calls
 happen in the smtp_getc() function when it refills its buffer. */
@@ -1762,7 +1879,7 @@ single timeout for the whole message. */
 else if (receive_timeout > 0)
   {
   os_non_restarting_signal(SIGALRM, data_timeout_handler);
 else if (receive_timeout > 0)
   {
   os_non_restarting_signal(SIGALRM, data_timeout_handler);
-  alarm(receive_timeout);
+  ALARM(receive_timeout);
   }
 
 /* SIGTERM and SIGINT are caught always. */
   }
 
 /* SIGTERM and SIGINT are caught always. */
@@ -1793,12 +1910,15 @@ for (;;)
   /* 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. */
 
-  if (ch == EOF && smtp_input /* && !smtp_batched_input */)
-    {
-    smtp_reply = handle_lost_connection(US" (header)");
-    smtp_yield = FALSE;
-    goto TIDYUP;                       /* Skip to end of function */
-    }
+  if (smtp_input /* && !smtp_batched_input */)
+    if (ch == EOF)
+      {
+      smtp_reply = handle_lost_connection(US" (header)");
+      smtp_yield = FALSE;
+      goto TIDYUP;                       /* Skip to end of function */
+      }
+    else if (ch == ERR)
+      goto TIDYUP;
 
   /* See if we are at the current header's size limit - there must be at least
   four bytes left. This allows for the new character plus a zero, plus two for
 
   /* See if we are at the current header's size limit - there must be at least
   four bytes left. This allows for the new character plus a zero, plus two for
@@ -1817,8 +1937,11 @@ for (;;)
   if (ptr >= header_size - 4)
     {
     int oldsize = header_size;
   if (ptr >= header_size - 4)
     {
     int oldsize = header_size;
-    /* header_size += 256; */
+
+    if (header_size >= INT_MAX/2)
+      goto OVERSIZE;
     header_size *= 2;
     header_size *= 2;
+
     if (!store_extend(next->text, oldsize, header_size))
       next->text = store_newblock(next->text, header_size, ptr);
     }
     if (!store_extend(next->text, oldsize, header_size))
       next->text = store_newblock(next->text, header_size, ptr);
     }
@@ -1835,7 +1958,7 @@ for (;;)
   those from data files use just LF. Treat LF in local SMTP input as a
   terminator too. Treat EOF as a line terminator always. */
 
   those from data files use just LF. Treat LF in local SMTP input as a
   terminator too. Treat EOF as a line terminator always. */
 
-  if (ch == EOF) goto EOL;
+  if (ch < 0) goto EOL;
 
   /* FUDGE: There are sites out there that don't send CRs before their LFs, and
   other MTAs accept this. We are therefore forced into this "liberalisation"
 
   /* FUDGE: There are sites out there that don't send CRs before their LFs, and
   other MTAs accept this. We are therefore forced into this "liberalisation"
@@ -1846,8 +1969,10 @@ for (;;)
 
   if (ch == '\n')
     {
 
   if (ch == '\n')
     {
-    if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = FALSE;
-      else if (first_line_ended_crlf) receive_ungetc(' ');
+    if (first_line_ended_crlf == TRUE_UNSET)
+      first_line_ended_crlf = FALSE;
+    else if (first_line_ended_crlf)
+      receive_ungetc(' ');
     goto EOL;
     }
 
     goto EOL;
     }
 
@@ -1856,26 +1981,32 @@ for (;;)
   This implements the dot-doubling rule, though header lines starting with
   dots aren't exactly common. They are legal in RFC 822, though. If the
   following is CRLF or LF, this is the line that that terminates the
   This implements the dot-doubling rule, though header lines starting with
   dots aren't exactly common. They are legal in RFC 822, though. If the
   following is CRLF or LF, this is the line that that terminates the
+
   entire message. We set message_ended to indicate this has happened (to
   prevent further reading), and break out of the loop, having freed the
   empty header, and set next = NULL to indicate no data line. */
 
   entire message. We set message_ended to indicate this has happened (to
   prevent further reading), and break out of the loop, having freed the
   empty header, and set next = NULL to indicate no data line. */
 
-  if (ptr == 0 && ch == '.' && dot_ends)
+  if (f.dot_ends && ptr == 0 && ch == '.')
     {
     {
+    /* leading dot while in headers-read mode */
     ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
     ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
-    if (ch == '\r')
+    if (ch == '\n' && first_line_ended_crlf == TRUE /* and not TRUE_UNSET */ )
+               /* dot, LF  but we are in CRLF mode.  Attack? */
+      ch = ' ';        /* replace the LF with a space */
+
+    else if (ch == '\r')
       {
       ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
       if (ch != '\n')
         {
       {
       ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
       if (ch != '\n')
         {
-        receive_ungetc(ch);
+       if (ch >= 0) receive_ungetc(ch);
         ch = '\r';              /* Revert to CR */
         }
       }
     if (ch == '\n')
       {
       message_ended = END_DOT;
         ch = '\r';              /* Revert to CR */
         }
       }
     if (ch == '\n')
       {
       message_ended = END_DOT;
-      store_reset(next);
+      reset_point = store_reset(reset_point);
       next = NULL;
       break;                    /* End character-reading loop */
       }
       next = NULL;
       break;                    /* End character-reading loop */
       }
@@ -1899,14 +2030,15 @@ for (;;)
     ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
     if (ch == '\n')
       {
     ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
     if (ch == '\n')
       {
-      if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = TRUE;
+      if (first_line_ended_crlf == TRUE_UNSET)
+       first_line_ended_crlf = TRUE;
       goto EOL;
       }
 
     /* Otherwise, put back the character after CR, and turn the bare CR
     into LF SP. */
 
       goto EOL;
       }
 
     /* Otherwise, put back the character after CR, and turn the bare CR
     into LF SP. */
 
-    ch = (receive_ungetc)(ch);
+    if (ch >= 0) (receive_ungetc)(ch);
     next->text[ptr++] = '\n';
     message_size++;
     ch = ' ';
     next->text[ptr++] = '\n';
     message_size++;
     ch = ' ';
@@ -1924,6 +2056,7 @@ for (;;)
 
   if (message_size >= header_maxsize)
     {
 
   if (message_size >= header_maxsize)
     {
+OVERSIZE:
     next->text[ptr] = 0;
     next->slen = ptr;
     next->type = htype_other;
     next->text[ptr] = 0;
     next->slen = ptr;
     next->type = htype_other;
@@ -1933,7 +2066,7 @@ for (;;)
 
     log_write(0, LOG_MAIN, "ridiculously long message header received from "
       "%s (more than %d characters): message abandoned",
 
     log_write(0, LOG_MAIN, "ridiculously long message header received from "
       "%s (more than %d characters): message abandoned",
-      sender_host_unknown? sender_ident : sender_fullhost, header_maxsize);
+      f.sender_host_unknown ? sender_ident : sender_fullhost, header_maxsize);
 
     if (smtp_input)
       {
 
     if (smtp_input)
       {
@@ -1980,7 +2113,7 @@ for (;;)
 
   if (ptr == 1)
     {
 
   if (ptr == 1)
     {
-    store_reset(next);
+    reset_point = store_reset(reset_point);
     next = NULL;
     break;
     }
     next = NULL;
     break;
     }
@@ -1989,17 +2122,19 @@ for (;;)
   whitespace character. If it is, we have a continuation of this header line.
   There is always space for at least one character at this point. */
 
   whitespace character. If it is, we have a continuation of this header line.
   There is always space for at least one character at this point. */
 
-  if (ch != EOF)
+  if (ch >= 0)
     {
     int nextch = (receive_getc)(GETC_BUFFER_UNLIMITED);
     if (nextch == ' ' || nextch == '\t')
       {
       next->text[ptr++] = nextch;
     {
     int nextch = (receive_getc)(GETC_BUFFER_UNLIMITED);
     if (nextch == ' ' || nextch == '\t')
       {
       next->text[ptr++] = nextch;
-      message_size++;
+      if (++message_size >= header_maxsize)
+       goto OVERSIZE;
       continue;                      /* Iterate the loop */
       }
       continue;                      /* Iterate the loop */
       }
-    else if (nextch != EOF) (receive_ungetc)(nextch);   /* For next time */
-    else ch = EOF;                   /* Cause main loop to exit at end */
+    else if (nextch >= 0)      /* not EOF, ERR etc */
+      (receive_ungetc)(nextch);   /* For next time */
+    else ch = nextch;                   /* Cause main loop to exit at end */
     }
 
   /* We have got to the real line end. Terminate the string and release store
     }
 
   /* We have got to the real line end. Terminate the string and release store
@@ -2008,7 +2143,7 @@ for (;;)
 
   next->text[ptr] = 0;
   next->slen = ptr;
 
   next->text[ptr] = 0;
   next->slen = ptr;
-  store_reset(next->text + ptr + 1);
+  store_release_above(next->text + ptr + 1);
 
   /* Check the running total size against the overall message size limit. We
   don't expect to fail here, but if the overall limit is set less than MESSAGE_
 
   /* Check the running total size against the overall message size limit. We
   don't expect to fail here, but if the overall limit is set less than MESSAGE_
@@ -2057,10 +2192,11 @@ for (;;)
      && regex_match_and_setup(regex_From, next->text, 0, -1)
      )
     {
      && regex_match_and_setup(regex_From, next->text, 0, -1)
      )
     {
-    if (!sender_address_forced)
+    if (!f.sender_address_forced)
       {
       {
-      uschar *uucp_sender = expand_string(uucp_from_sender);
-      if (!uucp_sender)
+      uschar * uucp_sender;
+      GET_OPTION("uucp_from_sender");
+      if (!(uucp_sender = expand_string(uucp_from_sender)))
         log_write(0, LOG_MAIN|LOG_PANIC,
           "expansion of \"%s\" failed after matching "
           "\"From \" line: %s", uucp_from_sender, expand_string_message);
         log_write(0, LOG_MAIN|LOG_PANIC,
           "expansion of \"%s\" failed after matching "
           "\"From \" line: %s", uucp_from_sender, expand_string_message);
@@ -2073,17 +2209,18 @@ for (;;)
         if (newsender)
           {
           if (domain == 0 && newsender[0] != 0)
         if (newsender)
           {
           if (domain == 0 && newsender[0] != 0)
-            newsender = rewrite_address_qualify(newsender, FALSE);
+           /* deconst ok as newsender was not const */
+            newsender = US rewrite_address_qualify(newsender, FALSE);
 
           if (filter_test != FTEST_NONE || receive_check_set_sender(newsender))
             {
             sender_address = newsender;
 
 
           if (filter_test != FTEST_NONE || receive_check_set_sender(newsender))
             {
             sender_address = newsender;
 
-            if (trusted_caller || filter_test != FTEST_NONE)
+            if (f.trusted_caller || filter_test != FTEST_NONE)
               {
               authenticated_sender = NULL;
               originator_name = US"";
               {
               authenticated_sender = NULL;
               originator_name = US"";
-              sender_local = FALSE;
+              f.sender_local = FALSE;
               }
 
             if (filter_test != FTEST_NONE)
               }
 
             if (filter_test != FTEST_NONE)
@@ -2100,15 +2237,14 @@ for (;;)
 
   else
     {
 
   else
     {
-    uschar *p = next->text;
+    uschar * p = next->text;
 
     /* If not a valid header line, break from the header reading loop, leaving
     next != NULL, indicating that it holds the first line of the body. */
 
     if (isspace(*p)) break;
     while (mac_isgraph(*p) && *p != ':') p++;
 
     /* If not a valid header line, break from the header reading loop, leaving
     next != NULL, indicating that it holds the first line of the body. */
 
     if (isspace(*p)) break;
     while (mac_isgraph(*p) && *p != ':') p++;
-    while (isspace(*p)) p++;
-    if (*p != ':')
+    if (Uskip_whitespace(&p) != ':')
       {
       body_zerocount = had_zero;
       break;
       {
       body_zerocount = had_zero;
       break;
@@ -2118,7 +2254,8 @@ for (;;)
     the line, stomp on them here. */
 
     if (had_zero > 0)
     the line, stomp on them here. */
 
     if (had_zero > 0)
-      for (p = next->text; p < next->text + ptr; p++) if (*p == 0) *p = '?';
+      for (uschar * p = next->text; p < next->text + ptr; p++) if (*p == 0)
+               *p = '?';
 
     /* It is perfectly legal to have an empty continuation line
     at the end of a header, but it is confusing to humans
 
     /* It is perfectly legal to have an empty continuation line
     at the end of a header, but it is confusing to humans
@@ -2154,7 +2291,7 @@ for (;;)
       {
       log_write(0, LOG_MAIN, "overlong message header line received from "
         "%s (more than %d characters): message abandoned",
       {
       log_write(0, LOG_MAIN, "overlong message header line received from "
         "%s (more than %d characters): message abandoned",
-        sender_host_unknown? sender_ident : sender_fullhost,
+        f.sender_host_unknown ? sender_ident : sender_fullhost,
         header_line_maxsize);
 
       if (smtp_input)
         header_line_maxsize);
 
       if (smtp_input)
@@ -2190,22 +2327,29 @@ for (;;)
       sender_address,
       sender_fullhost ? " H=" : "", sender_fullhost ? sender_fullhost : US"",
       sender_ident ? " U=" : "",    sender_ident ? sender_ident : US"");
       sender_address,
       sender_fullhost ? " H=" : "", sender_fullhost ? sender_fullhost : US"",
       sender_ident ? " U=" : "",    sender_ident ? sender_ident : US"");
-    smtp_printf("552 Message header not CRLF terminated\r\n", FALSE);
+    smtp_printf("552 Message header not CRLF terminated\r\n", SP_NO_MORE);
     bdat_flush_data();
     smtp_reply = US"";
     goto TIDYUP;                             /* Skip to end of function */
     }
 
   /* The line has been handled. If we have hit EOF, break out of the loop,
     bdat_flush_data();
     smtp_reply = US"";
     goto TIDYUP;                             /* Skip to end of function */
     }
 
   /* The line has been handled. If we have hit EOF, break out of the loop,
-  indicating no pending data line. */
+  indicating no pending data line and no more data for the message */
 
 
-  if (ch == EOF) { next = NULL; break; }
+  if (ch < 0)
+    {
+    next = NULL;
+    if (ch == EOF)     message_ended = END_DOT;
+    else if (ch == ERR) message_ended = END_PROTOCOL;
+    break;
+    }
 
   /* Set up for the next header */
 
 
   /* Set up for the next header */
 
+  reset_point = store_mark();
   header_size = 256;
   header_size = 256;
-  next = store_get(sizeof(header_line));
-  next->text = store_get(header_size);
+  next = store_get(sizeof(header_line), GET_UNTAINTED);
+  next->text = store_get(header_size, GET_TAINTED);
   ptr = 0;
   had_zero = 0;
   prevlines_length = 0;
   ptr = 0;
   had_zero = 0;
   prevlines_length = 0;
@@ -2220,22 +2364,31 @@ normal case). */
 DEBUG(D_receive)
   {
   debug_printf(">>Headers received:\n");
 DEBUG(D_receive)
   {
   debug_printf(">>Headers received:\n");
-  for (h = header_list->next; h; h = h->next)
-    debug_printf("%s", h->text);
+  acl_level++;
+  for (header_line * h = header_list->next; h; h = h->next)
+    debug_printf_indent("%s", h->text);
   debug_printf("\n");
   debug_printf("\n");
+  acl_level--;
   }
 
 /* End of file on any SMTP connection is an error. If an incoming SMTP call
 is dropped immediately after valid headers, the next thing we will see is EOF.
 We must test for this specially, as further down the reading of the data is
   }
 
 /* End of file on any SMTP connection is an error. If an incoming SMTP call
 is dropped immediately after valid headers, the next thing we will see is EOF.
 We must test for this specially, as further down the reading of the data is
-skipped if already at EOF. */
+skipped if already at EOF.
+In CHUNKING mode, a protocol error makes us give up on the message. */
 
 
-if (smtp_input && (receive_feof)())
-  {
-  smtp_reply = handle_lost_connection(US" (after header)");
-  smtp_yield = FALSE;
-  goto TIDYUP;                       /* Skip to end of function */
-  }
+if (smtp_input)
+  if ((receive_feof)())
+    {
+    smtp_reply = handle_lost_connection(US" (after header)");
+    smtp_yield = FALSE;
+    goto TIDYUP;                       /* Skip to end of function */
+    }
+  else if (message_ended == END_PROTOCOL)
+    {
+    smtp_reply = US"";                 /* no reply needed */
+    goto TIDYUP;
+    }
 
 /* If this is a filter test run and no headers were read, output a warning
 in case there is a mistake in the test message. */
 
 /* If this is a filter test run and no headers were read, output a warning
 in case there is a mistake in the test message. */
@@ -2247,7 +2400,7 @@ if (filter_test != FTEST_NONE && header_list->next == NULL)
 /* Scan the headers to identify them. Some are merely marked for later
 processing; some are dealt with here. */
 
 /* Scan the headers to identify them. Some are merely marked for later
 processing; some are dealt with here. */
 
-for (h = header_list->next; h; h = h->next)
+for (header_line * h = header_list->next; h; h = h->next)
   {
   BOOL is_resent = strncmpic(h->text, US"resent-", 7) == 0;
   if (is_resent) contains_resent_headers = TRUE;
   {
   BOOL is_resent = strncmpic(h->text, US"resent-", 7) == 0;
   if (is_resent) contains_resent_headers = TRUE;
@@ -2288,19 +2441,23 @@ for (h = header_list->next; h; h = h->next)
 
     case htype_from:
       h->type = htype_from;
 
     case htype_from:
       h->type = htype_from;
+#ifdef SUPPORT_DMARC
+      if (!is_resent) dmarc_from_header = h;
+#endif
       if (!resents_exist || is_resent)
        {
        from_header = h;
        if (!smtp_input)
          {
          int len;
       if (!resents_exist || is_resent)
        {
        from_header = h;
        if (!smtp_input)
          {
          int len;
-         uschar *s = Ustrchr(h->text, ':') + 1;
-         while (isspace(*s)) s++;
+         uschar * s = Ustrchr(h->text, ':') + 1;
+
+         Uskip_whitespace(&s);
          len = h->slen - (s - h->text) - 1;
          if (Ustrlen(originator_login) == len &&
              strncmpic(s, originator_login, len) == 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";
+           uschar * name = is_resent ? US"Resent-From" : US"From";
            header_add(htype_from, "%s: %s <%s@%s>\n", name, originator_name,
              originator_login, qualify_domain_sender);
            from_header = header_last;
            header_add(htype_from, "%s: %s <%s@%s>\n", name, originator_name,
              originator_login, qualify_domain_sender);
            from_header = header_last;
@@ -2355,15 +2512,13 @@ for (h = header_list->next; h; h = h->next)
 
       if (filter_test != FTEST_NONE)
        {
 
       if (filter_test != FTEST_NONE)
        {
-       uschar *start = h->text + 12;
-       uschar *end = start + Ustrlen(start);
-       while (isspace(*start)) start++;
+       uschar * start = h->text + 12;
+       uschar * end = start + Ustrlen(start);
+
+       Uskip_whitespace(&start);
        while (end > start && isspace(end[-1])) end--;
        if (*start == '<' && end[-1] == '>')
        while (end > start && isspace(end[-1])) end--;
        if (*start == '<' && end[-1] == '>')
-         {
-         start++;
-         end--;
-         }
+         { start++; end--; }
        return_path = string_copyn(start, end - start);
        printf("Return-path taken from \"Return-path:\" header line\n");
        }
        return_path = string_copyn(start, end - start);
        printf("Return-path taken from \"Return-path:\" header line\n");
        }
@@ -2381,9 +2536,9 @@ for (h = header_list->next; h; h = h->next)
     set.) */
 
     case htype_sender:
     set.) */
 
     case htype_sender:
-      h->type =    !active_local_sender_retain
-               && (  sender_local && !trusted_caller && !suppress_local_fixups
-                  || submission_mode
+      h->type =    !f.active_local_sender_retain
+               && (  f.sender_local && !f.trusted_caller && !f.suppress_local_fixups
+                  || f.submission_mode
                   )
                && (!resents_exist || is_resent)
        ? htype_old : htype_sender;
                   )
                && (!resents_exist || is_resent)
        ? htype_old : htype_sender;
@@ -2451,7 +2606,7 @@ if (extract_recip)
     {
     while (recipients_count-- > 0)
       {
     {
     while (recipients_count-- > 0)
       {
-      uschar *s = rewrite_address(recipients_list[recipients_count].address,
+      const uschar * s = rewrite_address(recipients_list[recipients_count].address,
         TRUE, TRUE, global_rewrite_rules, rewrite_existflags);
       tree_add_nonrecipient(s);
       }
         TRUE, TRUE, global_rewrite_rules, rewrite_existflags);
       tree_add_nonrecipient(s);
       }
@@ -2461,25 +2616,25 @@ if (extract_recip)
 
   /* Now scan the headers */
 
 
   /* Now scan the headers */
 
-  for (h = header_list->next; h; h = h->next)
+  for (header_line * h = header_list->next; h; h = h->next)
     {
     if ((h->type == htype_to || h->type == htype_cc || h->type == htype_bcc) &&
         (!contains_resent_headers || strncmpic(h->text, US"resent-", 7) == 0))
       {
     {
     if ((h->type == htype_to || h->type == htype_cc || h->type == htype_bcc) &&
         (!contains_resent_headers || strncmpic(h->text, US"resent-", 7) == 0))
       {
-      uschar *s = Ustrchr(h->text, ':') + 1;
-      while (isspace(*s)) s++;
+      uschar * s = Ustrchr(h->text, ':') + 1;
+      Uskip_whitespace(&s);
 
 
-      parse_allow_group = TRUE;          /* Allow address group syntax */
+      f.parse_allow_group = TRUE;          /* Allow address group syntax */
 
 
-      while (*s != 0)
+      while (*s)
         {
         uschar *ss = parse_find_address_end(s, FALSE);
         {
         uschar *ss = parse_find_address_end(s, FALSE);
-        uschar *recipient, *errmess, *p, *pp;
+        uschar *recipient, *errmess, *pp;
         int start, end, domain;
 
         /* Check on maximum */
 
         int start, end, domain;
 
         /* Check on maximum */
 
-        if (recipients_max > 0 && ++rcount > recipients_max)
+        if (recipients_max_expanded > 0 && ++rcount > recipients_max_expanded)
           give_local_error(ERRMESS_TOOMANYRECIP, US"too many recipients",
             US"message rejected: ", error_rc, stdin, NULL);
           /* Does not return */
           give_local_error(ERRMESS_TOOMANYRECIP, US"too many recipients",
             US"message rejected: ", error_rc, stdin, NULL);
           /* Does not return */
@@ -2489,8 +2644,8 @@ if (extract_recip)
         white space that follows the newline must not be removed - it is part
         of the header. */
 
         white space that follows the newline must not be removed - it is part
         of the header. */
 
-        pp = recipient = store_get(ss - s + 1);
-        for (p = s; p < ss; p++) if (*p != '\n') *pp++ = *p;
+        pp = recipient = store_get(ss - s + 1, s);
+        for (uschar * p = s; p < ss; p++) if (*p != '\n') *pp++ = *p;
         *pp = 0;
 
 #ifdef SUPPORT_I18N
         *pp = 0;
 
 #ifdef SUPPORT_I18N
@@ -2502,11 +2657,12 @@ if (extract_recip)
           &domain, FALSE);
 
 #ifdef SUPPORT_I18N
           &domain, FALSE);
 
 #ifdef SUPPORT_I18N
-       if (string_is_utf8(recipient))
-         message_smtputf8 = TRUE;
-       else
-         allow_utf8_domains = b;
+        if (recipient)
+          if (string_is_utf8(recipient)) message_smtputf8 = TRUE;
+          else allow_utf8_domains = b;
        }
        }
+#else
+        ;
 #endif
 
         /* Keep a list of all the bad addresses so we can send a single
 #endif
 
         /* Keep a list of all the bad addresses so we can send a single
@@ -2517,10 +2673,10 @@ if (extract_recip)
 
         If there are no recipients at all, an error will occur later. */
 
 
         If there are no recipients at all, an error will occur later. */
 
-        if (recipient == NULL && Ustrcmp(errmess, "empty address") != 0)
+        if (!recipient && Ustrcmp(errmess, "empty address") != 0)
           {
           int len = Ustrlen(s);
           {
           int len = Ustrlen(s);
-          error_block *b = store_get(sizeof(error_block));
+          error_block * b = store_get(sizeof(error_block), GET_UNTAINTED);
           while (len > 0 && isspace(s[len-1])) len--;
           b->next = NULL;
           b->text1 = string_printing(string_copyn(s, len));
           while (len > 0 && isspace(s[len-1])) len--;
           b->next = NULL;
           b->text1 = string_printing(string_copyn(s, len));
@@ -2535,7 +2691,7 @@ if (extract_recip)
         that this has happened, in order to give a better error if there are
         no recipients left. */
 
         that this has happened, in order to give a better error if there are
         no recipients left. */
 
-        else if (recipient != NULL)
+        else if (recipient)
           {
           if (tree_search(tree_nonrecipients, recipient) == NULL)
             receive_add_recipient(recipient, -1);
           {
           if (tree_search(tree_nonrecipients, recipient) == NULL)
             receive_add_recipient(recipient, -1);
@@ -2545,12 +2701,12 @@ if (extract_recip)
 
         /* Move on past this address */
 
 
         /* Move on past this address */
 
-        s = ss + (*ss? 1:0);
-        while (isspace(*s)) s++;
+        s = ss + (*ss ? 1 : 0);
+        Uskip_whitespace(&s);
         }    /* Next address */
 
         }    /* Next address */
 
-      parse_allow_group = FALSE;      /* Reset group syntax flags */
-      parse_found_group = FALSE;
+      f.parse_allow_group = FALSE;      /* Reset group syntax flags */
+      f.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
 
       /* 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
@@ -2563,41 +2719,37 @@ if (extract_recip)
   }
 
 /* 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
-lifetime of Exim. This description was rewritten for Exim 4.14 (February 2003).
-Retaining all the history in the comment has become too unwieldy - read
-previous release sources if you want it.
-
-The message ID has 3 parts: tttttt-pppppp-ss. Each part is a number in base 62.
-The first part is the current time, in seconds. The second part is the current
-pid. Both are large enough to hold 32-bit numbers in base 62. The third part
-can hold a number in the range 0-3843. It used to be a computed sequence
-number, but is now the fractional component of the current time in units of
-1/2000 of a second (i.e. a value in the range 0-1999). After a message has been
-received, Exim ensures that the timer has ticked at the appropriate level
-before proceeding, to avoid duplication if the pid happened to be re-used
-within the same time period. It seems likely that most messages will take at
-least half a millisecond to be received, so no delay will normally be
-necessary. At least for some time...
+lifetime of Exim, and is changing for Exim 4.97.
+The previous change was in about 2003.
+
+Detail for the pre-4.97 version is here in [square-brackets].
 
 
-There is a modification when localhost_number is set. Formerly this was allowed
-to be as large as 255. Now it is restricted to the range 0-16, and the final
-component of the message id becomes (localhost_number * 200) + fractional time
-in units of 1/200 of a second (i.e. a value in the range 0-3399).
+The message ID has 3 parts: tttttt-ppppppppppp-ssss  (6, 11, 4 - total 23 with
+the dashes).  Each part is a number in base 62.
+[ tttttt-pppppp-ss  6, 6, 2 => 16 ]
 
 
-Some not-really-Unix operating systems use case-insensitive file names (Darwin,
-Cygwin). For these, we have to use base 36 instead of base 62. Luckily, this
-still allows the tttttt field to hold a large enough number to last for some
-more decades, and the final two-digit field can hold numbers up to 1295, which
-is enough for milliseconds (instead of 1/2000 of a second).
+The first part is the current time, in seconds.  Six chars is enough until
+year 3700 with case-sensitive filesystes, but will run out in 2038 on
+case-insensitive ones (Cygwin, Darwin - where we have to use base-36.
+Both of those are in the "unsupported" bucket, so ignore for now).
 
 
-However, the pppppp field cannot hold a 32-bit pid, but it can hold a 31-bit
-pid, so it is probably safe because pids have to be positive. The
-localhost_number is restricted to 0-10 for these hosts, and when it is set, the
-final field becomes (localhost_number * 100) + fractional time in centiseconds.
+The second part is the current pid, and supports 64b [31b] PIDs.
 
 
-Note that string_base62() returns its data in a static storage block, so it
-must be copied before calling string_base62() again. It always returns exactly
-6 characters.
+The third part holds sub-second time, plus (when localhost_number is set)
+the host number multiplied by a number large enough to keep it away from
+the time portion. Host numbers are restricted to the range 0-16.
+The time resolution is variously 1, 2 or 4 microseconds [0.5 or 1 ms]
+depending on the use of localhost_nubmer and of case-insensitive filesystems.
+
+After a message has been received, Exim ensures that the timer has ticked at the
+appropriate level before proceeding, to avoid duplication if the pid happened to
+be re-used within the same time period. It seems likely that most messages will
+take at least half a millisecond to be received, so no delay will normally be
+necessary. At least for some time...
+
+Note that string_base62_XX() returns its data in a static storage block, so it
+must be copied before calling string_base62_XXX) again. It always returns exactly
+11 (_64) or 6 (_32) characters.
 
 There doesn't seem to be anything in the RFC which requires a message id to
 start with a letter, but Smail was changed to ensure this. The external form of
 
 There doesn't seem to be anything in the RFC which requires a message id to
 start with a letter, but Smail was changed to ensure this. The external form of
@@ -2610,35 +2762,35 @@ checking that a string is in this format must be updated in a corresponding
 way. It appears in the initializing code in exim.c. The macro MESSAGE_ID_LENGTH
 must also be changed to reflect the correct string length. The queue-sort code
 needs to know the layout. Then, of course, other programs that rely on the
 way. It appears in the initializing code in exim.c. The macro MESSAGE_ID_LENGTH
 must also be changed to reflect the correct string length. The queue-sort code
 needs to know the layout. Then, of course, other programs that rely on the
-message id format will need updating too. */
+message id format will need updating too (inc. at least exim_msgdate). */
 
 
-Ustrncpy(message_id, string_base62((long int)(message_id_tv.tv_sec)), 6);
-message_id[6] = '-';
-Ustrncpy(message_id + 7, string_base62((long int)getpid()), 6);
+Ustrncpy(message_id, string_base62_32((long int)(message_id_tv.tv_sec)), MESSAGE_ID_TIME_LEN);
+message_id[MESSAGE_ID_TIME_LEN] = '-';
+Ustrncpy(message_id + MESSAGE_ID_TIME_LEN + 1,
+       string_base62_64((long int)getpid()),
+       MESSAGE_ID_PID_LEN
+       );
 
 /* Deal with the case where the host number is set. The value of the number was
 
 /* Deal with the case where the host number is set. The value of the number was
-checked when it was read, to ensure it isn't too big. The timing granularity is
-left in id_resolution so that an appropriate wait can be done after receiving
-the message, if necessary (we hope it won't be). */
+checked when it was read, to ensure it isn't too big. */
 
 if (host_number_string)
 
 if (host_number_string)
-  {
-  id_resolution = (BASE_62 == 62)? 5000 : 10000;
-  sprintf(CS(message_id + MESSAGE_ID_LENGTH - 3), "-%2s",
-    string_base62((long int)(
-      host_number * (1000000/id_resolution) +
-        message_id_tv.tv_usec/id_resolution)) + 4);
-  }
+  sprintf(CS(message_id + MESSAGE_ID_TIME_LEN + 1 + MESSAGE_ID_PID_LEN),
+       "-%" str(MESSAGE_ID_SUBTIME_LEN) "s",
+       string_base62_32((long int)(
+         host_number * (1000000/id_resolution)
+         + message_id_tv.tv_usec/id_resolution))
+       + (6 - MESSAGE_ID_SUBTIME_LEN)
+        );
 
 /* Host number not set: final field is just the fractional time at an
 appropriate resolution. */
 
 else
 
 /* Host number not set: final field is just the fractional time at an
 appropriate resolution. */
 
 else
-  {
-  id_resolution = (BASE_62 == 62)? 500 : 1000;
-  sprintf(CS(message_id + MESSAGE_ID_LENGTH - 3), "-%2s",
-    string_base62((long int)(message_id_tv.tv_usec/id_resolution)) + 4);
-  }
+  sprintf(CS(message_id + MESSAGE_ID_TIME_LEN + 1 + MESSAGE_ID_PID_LEN),
+    "-%" str(MESSAGE_ID_SUBTIME_LEN) "s",
+       string_base62_32((long int)(message_id_tv.tv_usec/id_resolution))
+       + (6 - MESSAGE_ID_SUBTIME_LEN));
 
 /* Add the current message id onto the current process info string if
 it will fit. */
 
 /* Add the current message id onto the current process info string if
 it will fit. */
@@ -2650,7 +2802,7 @@ it will fit. */
 to be the least significant base-62 digit of the time of arrival. Otherwise
 ensure that it is an empty string. */
 
 to be the least significant base-62 digit of the time of arrival. Otherwise
 ensure that it is an empty string. */
 
-message_subdir[0] = split_spool_directory ? message_id[5] : 0;
+set_subdir_str(message_subdir, message_id, 0);
 
 /* Now that we have the message-id, if there is no message-id: header, generate
 one, but only for local (without suppress_local_fixups) or submission mode
 
 /* Now that we have the message-id, if there is no message-id: header, generate
 one, but only for local (without suppress_local_fixups) or submission mode
@@ -2658,20 +2810,21 @@ messages. This can be user-configured if required, but we had better flatten
 any illegal characters therein. */
 
 if (  !msgid_header
 any illegal characters therein. */
 
 if (  !msgid_header
-   && ((!sender_host_address && !suppress_local_fixups) || submission_mode))
+   && ((!sender_host_address && !f.suppress_local_fixups) || f.submission_mode))
   {
   {
-  uschar *p;
   uschar *id_text = US"";
   uschar *id_domain = primary_hostname;
   uschar *id_text = US"";
   uschar *id_domain = primary_hostname;
+  header_line * h;
 
   /* Permit only letters, digits, dots, and hyphens in the domain */
 
 
   /* Permit only letters, digits, dots, and hyphens in the domain */
 
+  GET_OPTION("message_id_header_domain");
   if (message_id_domain)
     {
     uschar *new_id_domain = expand_string(message_id_domain);
     if (!new_id_domain)
       {
   if (message_id_domain)
     {
     uschar *new_id_domain = expand_string(message_id_domain);
     if (!new_id_domain)
       {
-      if (!expand_string_forcedfail)
+      if (!f.expand_string_forcedfail)
         log_write(0, LOG_MAIN|LOG_PANIC,
           "expansion of \"%s\" (message_id_header_domain) "
           "failed: %s", message_id_domain, expand_string_message);
         log_write(0, LOG_MAIN|LOG_PANIC,
           "expansion of \"%s\" (message_id_header_domain) "
           "failed: %s", message_id_domain, expand_string_message);
@@ -2679,7 +2832,7 @@ if (  !msgid_header
     else if (*new_id_domain)
       {
       id_domain = new_id_domain;
     else if (*new_id_domain)
       {
       id_domain = new_id_domain;
-      for (p = id_domain; *p; p++)
+      for (uschar * p = id_domain; *p; p++)
         if (!isalnum(*p) && *p != '.') *p = '-';  /* No need to test '-' ! */
       }
     }
         if (!isalnum(*p) && *p != '.') *p = '-';  /* No need to test '-' ! */
       }
     }
@@ -2687,12 +2840,13 @@ if (  !msgid_header
   /* Permit all characters except controls and RFC 2822 specials in the
   additional text part. */
 
   /* Permit all characters except controls and RFC 2822 specials in the
   additional text part. */
 
+  GET_OPTION("message_id_header_text");
   if (message_id_text)
     {
     uschar *new_id_text = expand_string(message_id_text);
     if (!new_id_text)
       {
   if (message_id_text)
     {
     uschar *new_id_text = expand_string(message_id_text);
     if (!new_id_text)
       {
-      if (!expand_string_forcedfail)
+      if (!f.expand_string_forcedfail)
         log_write(0, LOG_MAIN|LOG_PANIC,
           "expansion of \"%s\" (message_id_header_text) "
           "failed: %s", message_id_text, expand_string_message);
         log_write(0, LOG_MAIN|LOG_PANIC,
           "expansion of \"%s\" (message_id_header_text) "
           "failed: %s", message_id_text, expand_string_message);
@@ -2700,17 +2854,25 @@ if (  !msgid_header
     else if (*new_id_text)
       {
       id_text = new_id_text;
     else if (*new_id_text)
       {
       id_text = new_id_text;
-      for (p = id_text; *p; p++) if (mac_iscntrl_or_special(*p)) *p = '-';
+      for (uschar * p = id_text; *p; p++) if (mac_iscntrl_or_special(*p)) *p = '-';
       }
     }
 
       }
     }
 
-  /* 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. */
+  /* 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_at_position(!resents_exist, NULL, FALSE, htype_id,
+  h = header_add_at_position_internal(!resents_exist, NULL, FALSE, htype_id,
     "%sMessage-Id: <%s%s%s@%s>\n", resent_prefix, message_id_external,
     "%sMessage-Id: <%s%s%s@%s>\n", resent_prefix, message_id_external,
-    (*id_text == 0)? "" : ".", id_text, id_domain);
+    *id_text == 0 ? "" : ".", id_text, id_domain);
+
+  /* Arrange for newly-created Message-Id to be logged */
+
+  if (!resents_exist)
+    {
+    msgid_header_newly_created = TRUE;
+    msgid_header = h;
+    }
   }
 
 /* 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
@@ -2719,8 +2881,8 @@ function may mess with the real recipients. */
 
 if (LOGGING(received_recipients))
   {
 
 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 *), GET_UNTAINTED);
+  for (int i = 0; i < recipients_count; i++)
     raw_recipients[i] = string_copy(recipients_list[i].address);
   raw_recipients_count = recipients_count;
   }
     raw_recipients[i] = string_copy(recipients_list[i].address);
   raw_recipients_count = recipients_count;
   }
@@ -2729,10 +2891,13 @@ if (LOGGING(received_recipients))
 recipients will get here only if the conditions were right (allow_unqualified_
 recipient is TRUE). */
 
 recipients will get here only if the conditions were right (allow_unqualified_
 recipient is TRUE). */
 
-for (i = 0; i < recipients_count; i++)
-  recipients_list[i].address =
-    rewrite_address(recipients_list[i].address, TRUE, TRUE,
+DEBUG(D_rewrite)
+  { debug_printf_indent("qualify & rewrite recipients list\n"); acl_level++; }
+for (int i = 0; i < recipients_count; i++)
+  recipients_list[i].address = /* deconst ok as src was not cont */
+    US rewrite_address(recipients_list[i].address, TRUE, TRUE,
       global_rewrite_rules, rewrite_existflags);
       global_rewrite_rules, rewrite_existflags);
+DEBUG(D_rewrite) acl_level--;
 
 /* If there is no From: header, generate one for local (without
 suppress_local_fixups) or submission_mode messages. If there is no sender
 
 /* If there is no From: header, generate one for local (without
 suppress_local_fixups) or submission_mode messages. If there is no sender
@@ -2744,9 +2909,9 @@ 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
 From:) but we still want to ensure a valid Sender: if it is required. */
 
 if (  !from_header
-   && ((!sender_host_address && !suppress_local_fixups) || submission_mode))
+   && ((!sender_host_address && !f.suppress_local_fixups) || f.submission_mode))
   {
   {
-  uschar *oname = US"";
+  const 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
 
   /* 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
@@ -2755,8 +2920,8 @@ if (  !from_header
 
   if (!sender_host_address)
     {
 
   if (!sender_host_address)
     {
-    if (!trusted_caller || sender_name_forced ||
-         (!smtp_input && !sender_address_forced))
+    if (!f.trusted_caller || f.sender_name_forced ||
+         (!smtp_input && !f.sender_address_forced))
       oname = originator_name;
     }
 
       oname = originator_name;
     }
 
@@ -2775,12 +2940,12 @@ if (  !from_header
       resent_prefix, oname, *oname ? " <" : "");
     fromend = *oname ? US">" : US"";
 
       resent_prefix, oname, *oname ? " <" : "");
     fromend = *oname ? US">" : US"";
 
-    if (sender_local || local_error_message)
+    if (f.sender_local || f.local_error_message)
       header_add(htype_from, "%s%s@%s%s\n", fromstart,
         local_part_quote(originator_login), qualify_domain_sender,
         fromend);
 
       header_add(htype_from, "%s%s@%s%s\n", fromstart,
         local_part_quote(originator_login), qualify_domain_sender,
         fromend);
 
-    else if (submission_mode && authenticated_id)
+    else if (f.submission_mode && authenticated_id)
       {
       if (!submission_domain)
         header_add(htype_from, "%s%s@%s%s\n", fromstart,
       {
       if (!submission_domain)
         header_add(htype_from, "%s%s@%s%s\n", fromstart,
@@ -2827,9 +2992,9 @@ 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
 parse_extract_address fails, and a Sender: header is inserted, as required. */
 
 if (  from_header
-   && (  active_local_from_check
-      && (  sender_local && !trusted_caller && !suppress_local_fixups
-        || submission_mode && authenticated_id
+   && (  f.active_local_from_check
+      && (  f.sender_local && !f.trusted_caller && !f.suppress_local_fixups
+        || f.submission_mode && authenticated_id
    )  )  )
   {
   BOOL make_sender = TRUE;
    )  )  )
   {
   BOOL make_sender = TRUE;
@@ -2840,7 +3005,7 @@ if (  from_header
       &start, &end, &domain, FALSE);
   uschar *generated_sender_address;
 
       &start, &end, &domain, FALSE);
   uschar *generated_sender_address;
 
-  generated_sender_address = submission_mode
+  generated_sender_address = f.submission_mode
     ? !submission_domain
     ? string_sprintf("%s@%s",
        local_part_quote(authenticated_id), qualify_domain_sender)
     ? !submission_domain
     ? string_sprintf("%s@%s",
        local_part_quote(authenticated_id), qualify_domain_sender)
@@ -2860,9 +3025,8 @@ if (  from_header
     uschar *at = domain ? from_address + domain - 1 : NULL;
 
     if (at) *at = 0;
     uschar *at = domain ? from_address + domain - 1 : NULL;
 
     if (at) *at = 0;
-    from_address += route_check_prefix(from_address, local_from_prefix);
-    slen = route_check_suffix(from_address, local_from_suffix);
-    if (slen > 0)
+    from_address += route_check_prefix(from_address, local_from_prefix, NULL);
+    if ((slen = route_check_suffix(from_address, local_from_suffix, NULL)) > 0)
       {
       memmove(from_address+slen, from_address, Ustrlen(from_address)-slen);
       from_address += slen;
       {
       memmove(from_address+slen, from_address, Ustrlen(from_address)-slen);
       from_address += slen;
@@ -2878,19 +3042,19 @@ if (  from_header
   appropriate rewriting rules. */
 
   if (make_sender)
   appropriate rewriting rules. */
 
   if (make_sender)
-    if (submission_mode && !submission_name)
+    if (f.submission_mode && !submission_name)
       header_add(htype_sender, "%sSender: %s\n", resent_prefix,
         generated_sender_address);
     else
       header_add(htype_sender, "%sSender: %s <%s>\n",
         resent_prefix,
       header_add(htype_sender, "%sSender: %s\n", resent_prefix,
         generated_sender_address);
     else
       header_add(htype_sender, "%sSender: %s <%s>\n",
         resent_prefix,
-        submission_mode? submission_name : originator_name,
+        f.submission_mode ? submission_name : originator_name,
         generated_sender_address);
 
   /* Ensure that a non-null envelope sender address corresponds to the
   submission mode sender address. */
 
         generated_sender_address);
 
   /* Ensure that a non-null envelope sender address corresponds to the
   submission mode sender address. */
 
-  if (submission_mode && *sender_address)
+  if (f.submission_mode && *sender_address)
     {
     if (!sender_address_unrewritten)
       sender_address_unrewritten = sender_address;
     {
     if (!sender_address_unrewritten)
       sender_address_unrewritten = sender_address;
@@ -2905,13 +3069,17 @@ if (  from_header
 /* 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. */
 
+DEBUG(D_rewrite)
+  { debug_printf("rewrite rules on sender address\n"); acl_level++; }
 if (global_rewrite_rules && !sender_address_unrewritten && *sender_address)
   {
 if (global_rewrite_rules && !sender_address_unrewritten && *sender_address)
   {
-  sender_address = rewrite_address(sender_address, FALSE, TRUE,
+  /* deconst ok as src was not const */
+  sender_address = US rewrite_address(sender_address, FALSE, TRUE,
     global_rewrite_rules, rewrite_existflags);
   DEBUG(D_receive|D_rewrite)
     debug_printf("rewritten sender = %s\n", sender_address);
   }
     global_rewrite_rules, rewrite_existflags);
   DEBUG(D_receive|D_rewrite)
     debug_printf("rewritten sender = %s\n", sender_address);
   }
+DEBUG(D_rewrite) acl_level--;
 
 
 /* The headers must be run through rewrite_header(), because it ensures that
 
 
 /* The headers must be run through rewrite_header(), because it ensures that
@@ -2928,12 +3096,13 @@ We start at the second header, skipping our own Received:. This rewriting is
 documented as happening *after* recipient addresses are taken from the headers
 by the -t command line option. An added Sender: gets rewritten here. */
 
 documented as happening *after* recipient addresses are taken from the headers
 by the -t command line option. An added Sender: gets rewritten here. */
 
-for (h = header_list->next; h; h = h->next)
-  {
-  header_line *newh = rewrite_header(h, NULL, NULL, global_rewrite_rules,
-    rewrite_existflags, TRUE);
-  if (newh) h = newh;
-  }
+DEBUG(D_rewrite)
+  { debug_printf("qualify and rewrite headers\n"); acl_level++; }
+for (header_line * h = header_list->next, * newh; h; h = h->next)
+  if ((newh = rewrite_header(h, NULL, NULL, global_rewrite_rules,
+                             rewrite_existflags, TRUE)))
+    h = newh;
+DEBUG(D_rewrite) acl_level--;
 
 
 /* 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",
@@ -2955,7 +3124,7 @@ As per Message-Id, we prepend if resending, else append.
 */
 
 if (  !date_header_exists
 */
 
 if (  !date_header_exists
-   && ((!sender_host_address && !suppress_local_fixups) || submission_mode))
+   && ((!sender_host_address && !f.suppress_local_fixups) || f.submission_mode))
   header_add_at_position(!resents_exist, NULL, FALSE, 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));
 
@@ -2967,9 +3136,11 @@ new Received:) has not yet been set. */
 DEBUG(D_receive)
   {
   debug_printf(">>Headers after rewriting and local additions:\n");
 DEBUG(D_receive)
   {
   debug_printf(">>Headers after rewriting and local additions:\n");
-  for (h = header_list->next; h; h = h->next)
-    debug_printf("%c %s", h->type, h->text);
+  acl_level++;
+  for (header_line * h = header_list->next; h; h = h->next)
+    debug_printf_indent("%c %s", h->type, h->text);
   debug_printf("\n");
   debug_printf("\n");
+  acl_level--;
   }
 
 /* The headers are now complete in store. If we are running in filter
   }
 
 /* The headers are now complete in store. If we are running in filter
@@ -3006,9 +3177,8 @@ if (cutthrough.cctx.sock >= 0 && cutthrough.delivery)
       sender_address,
       sender_fullhost ? "H=" : "", sender_fullhost ? sender_fullhost : US"",
       sender_ident ? "U=" : "", sender_ident ? sender_ident : US"");
       sender_address,
       sender_fullhost ? "H=" : "", sender_fullhost ? sender_fullhost : US"",
       sender_ident ? "U=" : "", sender_ident ? sender_ident : US"");
-    message_id[0] = 0;                       /* Indicate no message accepted */
     smtp_reply = US"550 Too many \"Received\" headers - suspected mail loop";
     smtp_reply = US"550 Too many \"Received\" headers - suspected mail loop";
-    goto TIDYUP;                             /* Skip to end of function */
+    goto NOT_ACCEPTED;                         /* Skip to end of function */
     }
   received_header_gen();
   add_acl_headers(ACL_WHERE_RCPT, US"MAIL or RCPT");
     }
   received_header_gen();
   add_acl_headers(ACL_WHERE_RCPT, US"MAIL or RCPT");
@@ -3017,7 +3187,7 @@ if (cutthrough.cctx.sock >= 0 && cutthrough.delivery)
 
 
 /* Open a new spool file for the data portion of the message. We need
 
 
 /* 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
+to access it both via a file descriptor and a stdio stream. Try to make the
 directory if it isn't there. */
 
 spool_name = spool_fname(US"input", message_subdir, message_id, US"-D");
 directory if it isn't there. */
 
 spool_name = spool_fname(US"input", message_subdir, message_id, US"-D");
@@ -3040,7 +3210,7 @@ if ((data_fd = Uopen(spool_name, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE)) < 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. */
 
-if (fchown(data_fd, exim_uid, exim_gid))
+if (0 != exim_fchown(data_fd, exim_uid, exim_gid, spool_name))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE,
     "Failed setting ownership on spool file %s: %s",
     spool_name, strerror(errno));
   log_write(0, LOG_MAIN|LOG_PANIC_DIE,
     "Failed setting ownership on spool file %s: %s",
     spool_name, strerror(errno));
@@ -3055,7 +3225,7 @@ spool_data_file = fdopen(data_fd, "w+");
 lock_data.l_type = F_WRLCK;
 lock_data.l_whence = SEEK_SET;
 lock_data.l_start = 0;
 lock_data.l_type = F_WRLCK;
 lock_data.l_whence = SEEK_SET;
 lock_data.l_start = 0;
-lock_data.l_len = SPOOL_DATA_START_OFFSET;
+lock_data.l_len = spool_data_start_offset(message_id);
 
 if (fcntl(data_fd, F_SETLK, &lock_data) < 0)
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Cannot lock %s (%d): %s", spool_name,
 
 if (fcntl(data_fd, F_SETLK, &lock_data) < 0)
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Cannot lock %s (%d): %s", spool_name,
@@ -3086,7 +3256,7 @@ if (!ferror(spool_data_file) && !(receive_feof)() && message_ended != END_DOT)
   if (smtp_input)
     {
     message_ended = chunking_state <= CHUNKING_OFFERED
   if (smtp_input)
     {
     message_ended = chunking_state <= CHUNKING_OFFERED
-      ? read_message_data_smtp(spool_data_file)
+      ? read_message_data_smtp(spool_data_file, first_line_ended_crlf)
       : spool_wireformat
       ? read_message_bdat_smtp_wire(spool_data_file)
       : read_message_bdat_smtp(spool_data_file);
       : spool_wireformat
       ? read_message_bdat_smtp_wire(spool_data_file)
       : read_message_bdat_smtp(spool_data_file);
@@ -3105,12 +3275,11 @@ if (!ferror(spool_data_file) && !(receive_feof)() && message_ended != END_DOT)
     case END_EOF:
       if (smtp_input)
        {
     case END_EOF:
       if (smtp_input)
        {
-       Uunlink(spool_name);                 /* Lose data file when closed */
+       Uunlink(spool_name);            /* Lose data file when closed */
        cancel_cutthrough_connection(TRUE, US"sender closed connection");
        cancel_cutthrough_connection(TRUE, US"sender closed connection");
-       message_id[0] = 0;                   /* Indicate no message accepted */
        smtp_reply = handle_lost_connection(US"");
        smtp_yield = FALSE;
        smtp_reply = handle_lost_connection(US"");
        smtp_yield = FALSE;
-       goto TIDYUP;                         /* Skip to end of function */
+       goto NOT_ACCEPTED;                              /* Skip to end of function */
        }
       break;
 
        }
       break;
 
@@ -3135,12 +3304,11 @@ if (!ferror(spool_data_file) && !(receive_feof)() && message_ended != END_DOT)
       if (smtp_input)
        {
        smtp_reply = US"552 Message size exceeds maximum permitted";
       if (smtp_input)
        {
        smtp_reply = US"552 Message size exceeds maximum permitted";
-       message_id[0] = 0;               /* Indicate no message accepted */
-       goto TIDYUP;                     /* Skip to end of function */
+       goto NOT_ACCEPTED;                      /* Skip to end of function */
        }
       else
        {
        }
       else
        {
-       fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+       fseek(spool_data_file, (long int)spool_data_start_offset(message_id), SEEK_SET);
        give_local_error(ERRMESS_TOOBIG,
          string_sprintf("message too big (max=%d)", thismessage_size_limit),
          US"message rejected: ", error_rc, spool_data_file, header_list);
        give_local_error(ERRMESS_TOOBIG,
          string_sprintf("message too big (max=%d)", thismessage_size_limit),
          US"message rejected: ", error_rc, spool_data_file, header_list);
@@ -3154,8 +3322,7 @@ if (!ferror(spool_data_file) && !(receive_feof)() && message_ended != END_DOT)
       Uunlink(spool_name);             /* Lose the data file when closed */
       cancel_cutthrough_connection(TRUE, US"sender protocol error");
       smtp_reply = US"";               /* Response already sent */
       Uunlink(spool_name);             /* Lose the data file when closed */
       cancel_cutthrough_connection(TRUE, US"sender protocol error");
       smtp_reply = US"";               /* Response already sent */
-      message_id[0] = 0;               /* Indicate no message accepted */
-      goto TIDYUP;                     /* Skip to end of function */
+      goto NOT_ACCEPTED;                       /* Skip to end of function */
     }
   }
 
     }
   }
 
@@ -3196,13 +3363,12 @@ if (fflush(spool_data_file) == EOF || ferror(spool_data_file) ||
       smtp_reply = US"451 Error while writing spool file";
       receive_swallow_smtp();
       }
       smtp_reply = US"451 Error while writing spool file";
       receive_swallow_smtp();
       }
-    message_id[0] = 0;               /* Indicate no message accepted */
-    goto TIDYUP;                     /* Skip to end of function */
+    goto NOT_ACCEPTED;                 /* Skip to end of function */
     }
 
   else
     {
     }
 
   else
     {
-    fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+    fseek(spool_data_file, (long int)spool_data_start_offset(message_id), SEEK_SET);
     give_local_error(ERRMESS_IOERR, msg, US"", error_rc, spool_data_file,
       header_list);
     /* Does not return */
     give_local_error(ERRMESS_IOERR, msg, US"", error_rc, spool_data_file,
       header_list);
     /* Does not return */
@@ -3213,7 +3379,7 @@ if (fflush(spool_data_file) == EOF || ferror(spool_data_file) ||
 /* No I/O errors were encountered while writing the data file. */
 
 DEBUG(D_receive) debug_printf("Data file written for message %s\n", message_id);
 /* No I/O errors were encountered while writing the data file. */
 
 DEBUG(D_receive) debug_printf("Data file written for message %s\n", message_id);
-if (LOGGING(receive_time)) timesince(&received_time_taken, &received_time);
+gettimeofday(&received_time_complete, NULL);
 
 
 /* If there were any bad addresses extracted by -t, or there were no recipients
 
 
 /* If there were any bad addresses extracted by -t, or there were no recipients
@@ -3234,17 +3400,16 @@ if (extract_recip && (bad_addresses || recipients_count == 0))
     if (recipients_count == 0) debug_printf("*** No recipients\n");
     if (bad_addresses)
       {
     if (recipients_count == 0) debug_printf("*** No recipients\n");
     if (bad_addresses)
       {
-      error_block *eblock = bad_addresses;
       debug_printf("*** Bad address(es)\n");
       debug_printf("*** Bad address(es)\n");
-      while (eblock != NULL)
-        {
+      for (error_block * eblock = bad_addresses; eblock; eblock = eblock->next)
         debug_printf("  %s: %s\n", eblock->text1, eblock->text2);
         debug_printf("  %s: %s\n", eblock->text1, eblock->text2);
-        eblock = eblock->next;
-        }
       }
     }
 
       }
     }
 
-  fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+  log_write(0, LOG_MAIN|LOG_PANIC, "%s found in headers",
+    bad_addresses ? "bad addresses" : "no recipients");
+
+  fseek(spool_data_file, (long int)spool_data_start_offset(message_id), SEEK_SET);
 
   /* If configured to send errors to the sender, but this fails, force
   a failure error code. We use a special one for no recipients so that it
 
   /* If configured to send errors to the sender, but this fails, force
   a failure error code. We use a special one for no recipients so that it
@@ -3255,11 +3420,12 @@ if (extract_recip && (bad_addresses || recipients_count == 0))
   if (error_handling == ERRORS_SENDER)
     {
     if (!moan_to_sender(
   if (error_handling == ERRORS_SENDER)
     {
     if (!moan_to_sender(
-          (bad_addresses == NULL)?
-            (extracted_ignored? ERRMESS_IGADDRESS : ERRMESS_NOADDRESS) :
-          (recipients_list == NULL)? ERRMESS_BADNOADDRESS : ERRMESS_BADADDRESS,
-          bad_addresses, header_list, spool_data_file, FALSE))
-      error_rc = (bad_addresses == NULL)? EXIT_NORECIPIENTS : EXIT_FAILURE;
+          bad_addresses
+         ? recipients_list ? ERRMESS_BADADDRESS : ERRMESS_BADNOADDRESS
+         : extracted_ignored ? ERRMESS_IGADDRESS : ERRMESS_NOADDRESS,
+          bad_addresses, header_list, spool_data_file, FALSE
+       )              )
+      error_rc = bad_addresses ? EXIT_FAILURE : EXIT_NORECIPIENTS;
     }
   else
     {
     }
   else
     {
@@ -3282,7 +3448,7 @@ if (extract_recip && (bad_addresses || recipients_count == 0))
     {
     Uunlink(spool_name);
     (void)fclose(spool_data_file);
     {
     Uunlink(spool_name);
     (void)fclose(spool_data_file);
-    exim_exit(error_rc, US"receiving");
+    exim_exit(error_rc);
     }
   }
 
     }
   }
 
@@ -3310,7 +3476,7 @@ if (!received_header->text)       /* Non-cutthrough case */
   /* Set the value of message_body_size for the DATA ACL and for local_scan() */
 
   message_body_size = (fstat(data_fd, &statbuf) == 0)?
   /* 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;
+    statbuf.st_size - spool_data_start_offset(message_id) : -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. */
@@ -3319,7 +3485,7 @@ if (!received_header->text)       /* Non-cutthrough case */
   }
 else
   message_body_size = (fstat(data_fd, &statbuf) == 0)?
   }
 else
   message_body_size = (fstat(data_fd, &statbuf) == 0)?
-    statbuf.st_size - SPOOL_DATA_START_OFFSET : -1;
+    statbuf.st_size - spool_data_start_offset(message_id) : -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
@@ -3331,10 +3497,10 @@ $message_body_end can be extracted if needed. Allow $recipients in expansions.
 deliver_datafile = data_fd;
 user_msg = NULL;
 
 deliver_datafile = data_fd;
 user_msg = NULL;
 
-enable_dollar_recipients = TRUE;
+f.enable_dollar_recipients = TRUE;
 
 if (recipients_count == 0)
 
 if (recipients_count == 0)
-  blackholed_by = recipients_discarded ? US"MAIL ACL" : US"RCPT ACL";
+  blackholed_by = f.recipients_discarded ? US"MAIL ACL" : US"RCPT ACL";
 
 else
   {
 
 else
   {
@@ -3344,179 +3510,142 @@ else
     {
 
 #ifndef DISABLE_DKIM
     {
 
 #ifndef DISABLE_DKIM
-    if (!dkim_disable_verify)
+    if (!f.dkim_disable_verify)
       {
       {
-      /* Finish verification */
-      dkim_exim_verify_finish();
+      misc_module_info * mi = misc_mod_findonly(US"dkim");
+      if (mi)
+       {
+       typedef void (*vfin_fn_t)(void);
+       typedef int  (*vacl_fn_t)(uschar **, uschar**);
+       typedef void (*vlog_fn_t)(void);
 
 
-      /* Check if we must run the DKIM ACL */
-      if (acl_smtp_dkim && dkim_verify_signers && *dkim_verify_signers)
-        {
-        uschar * dkim_verify_signers_expanded =
-          expand_string(dkim_verify_signers);
-       gstring * results = NULL;
-       int signer_sep = 0;
-       const uschar * ptr;
-       uschar * item;
-       gstring * seen_items = NULL;
-       int old_pool = store_pool;
-
-       store_pool = POOL_PERM;   /* Allow created variables to live to data ACL */
-
-        if (!(ptr = dkim_verify_signers_expanded))
-          log_write(0, LOG_MAIN|LOG_PANIC,
-            "expansion of dkim_verify_signers option failed: %s",
-            expand_string_message);
-
-       /* Default to OK when no items are present */
-       rc = OK;
-       while ((item = string_nextinlist(&ptr, &signer_sep, NULL, 0)))
-         {
-         /* Prevent running ACL for an empty item */
-         if (!item || !*item) continue;
+       /* Finish off the body hashes, calculate sigs and do compares */
 
 
-         /* 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;
-           const uschar * seen_items_list = string_from_gstring(seen_items);
-           int seen_sep = ':';
-           BOOL seen_this_item = FALSE;
-
-           while ((seen_item = string_nextinlist(&seen_items_list, &seen_sep,
-                                                 NULL, 0)))
-             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;
-             }
+       (((vfin_fn_t *) mi->functions)[DKIM_VERIFY_FINISH]) ();
 
 
-           seen_items = string_catn(seen_items, US":", 1);
-           }
-         seen_items = string_cat(seen_items, item);
+       /* Check if we must run the DKIM ACL */
+
+       GET_OPTION("acl_smtp_dkim");
+       if (acl_smtp_dkim)
+         {
+         rc = (((vacl_fn_t *) mi->functions)[DKIM_ACL_ENTRY])
+                                                   (&user_msg, &log_msg);
+         add_acl_headers(ACL_WHERE_DKIM, US"DKIM");
 
 
-         rc = dkim_exim_acl_run(item, &results, &user_msg, &log_msg);
          if (rc != OK)
            {
          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(TRUE, US"dkim acl not ok");
            cancel_cutthrough_connection(TRUE, US"dkim acl not ok");
-           break;
+
+           if (rc != DISCARD)
+             {
+             Uunlink(spool_name);
+             if (smtp_handle_acl_fail(ACL_WHERE_DKIM, rc, user_msg, log_msg) != 0)
+               smtp_yield = FALSE;     /* No more msgs after dropped conn */
+             smtp_reply = US"";        /* Indicate reply already sent */
+             goto NOT_ACCEPTED;        /* Skip to end of function */
+             }
+           recipients_count = 0;
+           blackholed_by = US"DKIM ACL";
+           if (log_msg)
+             blackhole_log_msg = string_sprintf(": %s", log_msg);
            }
          }
            }
          }
-       dkim_verify_status = string_from_gstring(results);
-       store_pool = old_pool;
-       add_acl_headers(ACL_WHERE_DKIM, US"DKIM");
-       if (rc == DISCARD)
-         {
-         recipients_count = 0;
-         blackholed_by = US"DKIM ACL";
-         if (log_msg)
-           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 messages 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 */
-         }
-        }
-      else
-       dkim_exim_verify_log_all();
+       else    /* No ACL; just log */
+         (((vlog_fn_t *) mi->functions)[DKIM_VERIFY_LOG_ALL]) ();
+       }
       }
 #endif /* DISABLE_DKIM */
 
 #ifdef WITH_CONTENT_SCAN
       }
 #endif /* DISABLE_DKIM */
 
 #ifdef WITH_CONTENT_SCAN
-    if (  recipients_count > 0
-       && acl_smtp_mime
-       && !run_mime_acl(acl_smtp_mime, &smtp_yield, &smtp_reply, &blackholed_by)
-       )
-      goto TIDYUP;
+    if (recipients_count > 0)
+      {
+      GET_OPTION("acl_smtp_mime");
+      if (acl_smtp_mime
+        && !run_mime_acl(acl_smtp_mime, &smtp_yield, &smtp_reply, &blackholed_by)
+        )
+       goto TIDYUP;
+      }
 #endif /* WITH_CONTENT_SCAN */
 
 #endif /* WITH_CONTENT_SCAN */
 
-#ifdef EXPERIMENTAL_DMARC
-    dmarc_up = dmarc_store_data(from_header);
-#endif /* EXPERIMENTAL_DMARC */
+#ifdef SUPPORT_DMARC
+    {
+    misc_module_info * mi = misc_mod_findonly(US"dmarc");
+    if (mi)
+      {
+      typedef int (*fn_t)(header_line *);
+      (((fn_t *) mi->functions)[DMARC_STORE_DATA]) (dmarc_from_header);
+      }
+    }
+#endif
 
 #ifndef DISABLE_PRDR
 
 #ifndef DISABLE_PRDR
-    if (prdr_requested && recipients_count > 1 && acl_smtp_data_prdr)
+    if (prdr_requested && recipients_count > 1)
       {
       {
-      unsigned int c;
-      int all_pass = OK;
-      int all_fail = FAIL;
+      GET_OPTION("acl_smtp_data_prdr");
+      if (acl_smtp_data_prdr)
+       {
+       int all_pass = OK;
+       int all_fail = FAIL;
 
 
-      smtp_printf("353 PRDR content analysis beginning\r\n", TRUE);
-      /* 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
+       smtp_printf("353 PRDR content analysis beginning\r\n", SP_MORE);
+       /* Loop through recipients, responses must be in same order received */
+       for (unsigned int c = 0; recipients_count > c; c++)
          {
          {
+         const 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)
          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);
+           {
+           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;
+         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)
+         goto NOT_ACCEPTED;
        }
        }
+      else
+       prdr_requested = FALSE;
       }
     else
       prdr_requested = FALSE;
       }
     else
       prdr_requested = FALSE;
@@ -3525,7 +3654,8 @@ else
     /* Check the recipients count again, as the MIME ACL might have changed
     them. */
 
     /* Check the recipients count again, as the MIME ACL might have changed
     them. */
 
-    if (acl_smtp_data != NULL && recipients_count > 0)
+    GET_OPTION("acl_smtp_data");
+    if (acl_smtp_data && recipients_count > 0)
       {
       rc = acl_check(ACL_WHERE_DATA, NULL, acl_smtp_data, &user_msg, &log_msg);
       add_acl_headers(ACL_WHERE_DATA, US"DATA");
       {
       rc = acl_check(ACL_WHERE_DATA, NULL, acl_smtp_data, &user_msg, &log_msg);
       add_acl_headers(ACL_WHERE_DATA, US"DATA");
@@ -3550,8 +3680,7 @@ else
         if (smtp_handle_acl_fail(ACL_WHERE_DATA, rc, user_msg, log_msg) != 0)
           smtp_yield = FALSE;    /* No more messages after dropped connection */
         smtp_reply = US"";       /* Indicate reply already sent */
         if (smtp_handle_acl_fail(ACL_WHERE_DATA, rc, user_msg, log_msg) != 0)
           smtp_yield = FALSE;    /* No more messages 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 */
+        goto NOT_ACCEPTED;                     /* Skip to end of function */
         }
       }
     }
         }
       }
     }
@@ -3563,6 +3692,7 @@ else
     {
 
 #ifdef WITH_CONTENT_SCAN
     {
 
 #ifdef WITH_CONTENT_SCAN
+    GET_OPTION("acl_not_smtp_mime");
     if (  acl_not_smtp_mime
        && !run_mime_acl(acl_not_smtp_mime, &smtp_yield, &smtp_reply,
           &blackholed_by)
     if (  acl_not_smtp_mime
        && !run_mime_acl(acl_not_smtp_mime, &smtp_yield, &smtp_reply,
           &blackholed_by)
@@ -3570,10 +3700,11 @@ else
       goto TIDYUP;
 #endif /* WITH_CONTENT_SCAN */
 
       goto TIDYUP;
 #endif /* WITH_CONTENT_SCAN */
 
+    GET_OPTION("acl_not_smtp");
     if (acl_not_smtp)
       {
     if (acl_not_smtp)
       {
-      uschar *user_msg, *log_msg;
-      authentication_local = TRUE;
+      uschar * user_msg, * log_msg;
+      f.authentication_local = TRUE;
       rc = acl_check(ACL_WHERE_NOTSMTP, NULL, acl_not_smtp, &user_msg, &log_msg);
       if (rc == DISCARD)
         {
       rc = acl_check(ACL_WHERE_NOTSMTP, NULL, acl_not_smtp, &user_msg, &log_msg);
       if (rc == DISCARD)
         {
@@ -3604,7 +3735,7 @@ else
           /* Does not return */
         else
           {
           /* Does not return */
         else
           {
-          fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+          fseek(spool_data_file, (long int)spool_data_start_offset(message_id), SEEK_SET);
           give_local_error(ERRMESS_LOCAL_ACL, user_msg,
             US"message rejected by non-SMTP ACL: ", error_rc, spool_data_file,
               header_list);
           give_local_error(ERRMESS_LOCAL_ACL, user_msg,
             US"message rejected by non-SMTP ACL: ", error_rc, spool_data_file,
               header_list);
@@ -3617,8 +3748,8 @@ else
 
   /* The applicable ACLs have been run */
 
 
   /* The applicable ACLs have been run */
 
-  if (deliver_freeze) frozen_by = US"ACL";     /* for later logging */
-  if (queue_only_policy) queued_by = US"ACL";
+  if (f.deliver_freeze) frozen_by = US"ACL";     /* for later logging */
+  if (f.queue_only_policy) queued_by = US"ACL";
   }
 
 #ifdef WITH_CONTENT_SCAN
   }
 
 #ifdef WITH_CONTENT_SCAN
@@ -3636,7 +3767,7 @@ 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 recipients have been discarded. */
 
 supply their own checking code. The local_scan() function is run even when all
 the recipients have been discarded. */
 
-lseek(data_fd, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+lseek(data_fd, (long int)spool_data_start_offset(message_id), SEEK_SET);
 
 /* Arrange to catch crashes in local_scan(), so that the -D file gets
 deleted, and the incident gets logged. */
 
 /* Arrange to catch crashes in local_scan(), so that the -D file gets
 deleted, and the incident gets logged. */
@@ -3655,12 +3786,12 @@ if (sigsetjmp(local_scan_env, 1) == 0)
 
   had_local_scan_timeout = 0;
   os_non_restarting_signal(SIGALRM, local_scan_timeout_handler);
 
   had_local_scan_timeout = 0;
   os_non_restarting_signal(SIGALRM, local_scan_timeout_handler);
-  if (local_scan_timeout > 0) alarm(local_scan_timeout);
+  if (local_scan_timeout > 0) ALARM(local_scan_timeout);
   rc = local_scan(data_fd, &local_scan_data);
   rc = local_scan(data_fd, &local_scan_data);
-  alarm(0);
+  ALARM_CLR(0);
   os_non_restarting_signal(SIGALRM, sigalrm_handler);
 
   os_non_restarting_signal(SIGALRM, sigalrm_handler);
 
-  enable_dollar_recipients = FALSE;
+  f.enable_dollar_recipients = FALSE;
 
   store_pool = POOL_MAIN;   /* In case changed */
   DEBUG(D_receive) debug_printf("local_scan() returned %d %s\n", rc,
 
   store_pool = POOL_MAIN;   /* In case changed */
   DEBUG(D_receive) debug_printf("local_scan() returned %d %s\n", rc,
@@ -3702,9 +3833,9 @@ if (local_scan_data)
 
 if (rc == LOCAL_SCAN_ACCEPT_FREEZE)
   {
 
 if (rc == LOCAL_SCAN_ACCEPT_FREEZE)
   {
-  if (!deliver_freeze)         /* ACL might have already frozen */
+  if (!f.deliver_freeze)         /* ACL might have already frozen */
     {
     {
-    deliver_freeze = TRUE;
+    f.deliver_freeze = TRUE;
     deliver_frozen_at = time(NULL);
     frozen_by = US"local_scan()";
     }
     deliver_frozen_at = time(NULL);
     frozen_by = US"local_scan()";
     }
@@ -3712,9 +3843,9 @@ if (rc == LOCAL_SCAN_ACCEPT_FREEZE)
   }
 else if (rc == LOCAL_SCAN_ACCEPT_QUEUE)
   {
   }
 else if (rc == LOCAL_SCAN_ACCEPT_QUEUE)
   {
-  if (!queue_only_policy)      /* ACL might have already queued */
+  if (!f.queue_only_policy)      /* ACL might have already queued */
     {
     {
-    queue_only_policy = TRUE;
+    f.queue_only_policy = TRUE;
     queued_by = US"local_scan()";
     }
   rc = LOCAL_SCAN_ACCEPT;
     queued_by = US"local_scan()";
     }
   rc = LOCAL_SCAN_ACCEPT;
@@ -3726,18 +3857,15 @@ the spool file gets corrupted. Ensure that all recipients are qualified. */
 if (rc == LOCAL_SCAN_ACCEPT)
   {
   if (local_scan_data)
 if (rc == LOCAL_SCAN_ACCEPT)
   {
   if (local_scan_data)
+    for (uschar * s = local_scan_data; *s; s++) if (*s == '\n') *s = ' ';
+  for (recipient_item * r = recipients_list;
+       r < recipients_list + recipients_count; r++)
     {
     {
-    uschar *s;
-    for (s = local_scan_data; *s != 0; s++) if (*s == '\n') *s = ' ';
-    }
-  for (i = 0; i < recipients_count; i++)
-    {
-    recipient_item *r = recipients_list + i;
     r->address = rewrite_address_qualify(r->address, TRUE);
     r->address = rewrite_address_qualify(r->address, TRUE);
-    if (r->errors_to != NULL)
+    if (r->errors_to)
       r->errors_to = rewrite_address_qualify(r->errors_to, TRUE);
     }
       r->errors_to = rewrite_address_qualify(r->errors_to, TRUE);
     }
-  if (recipients_count == 0 && blackholed_by == NULL)
+  if (recipients_count == 0 && !blackholed_by)
     blackholed_by = US"local_scan";
   }
 
     blackholed_by = US"local_scan";
   }
 
@@ -3781,29 +3909,25 @@ else
       break;
     }
 
       break;
     }
 
-  g = string_append(NULL, 2, US"F=",
-    sender_address[0] == 0 ? US"<>" : sender_address);
+  g = string_append(NULL, 2, US"F=", *sender_address ? sender_address : US"<>");
   g = add_host_info_for_log(g);
 
   g = add_host_info_for_log(g);
 
-  log_write(0, LOG_MAIN|LOG_REJECT, "%s %srejected by local_scan(): %.256s",
-    string_from_gstring(g), istemp, string_printing(errmsg));
+  log_write(0, LOG_MAIN|LOG_REJECT, "%Y %srejected by local_scan(): %.256s",
+    g, istemp, string_printing(errmsg));
 
   if (smtp_input)
 
   if (smtp_input)
-    {
     if (!smtp_batched_input)
       {
     if (!smtp_batched_input)
       {
-      smtp_respond(smtp_code, 3, TRUE, errmsg);
-      message_id[0] = 0;            /* Indicate no message accepted */
+      smtp_respond(smtp_code, 3, SR_FINAL, errmsg);
       smtp_reply = US"";            /* Indicate reply already sent */
       smtp_reply = US"";            /* Indicate reply already sent */
-      goto TIDYUP;                  /* Skip to end of function */
+      goto NOT_ACCEPTED;                       /* Skip to end of function */
       }
     else
       moan_smtp_batch(NULL, "%s %s", smtp_code, errmsg);
       /* Does not return */
       }
     else
       moan_smtp_batch(NULL, "%s %s", smtp_code, errmsg);
       /* Does not return */
-    }
   else
     {
   else
     {
-    fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+    fseek(spool_data_file, (long int)spool_data_start_offset(message_id), SEEK_SET);
     give_local_error(ERRMESS_LOCAL_SCAN, errmsg,
       US"message rejected by local scan code: ", error_rc, spool_data_file,
         header_list);
     give_local_error(ERRMESS_LOCAL_SCAN, errmsg,
       US"message rejected by local scan code: ", error_rc, spool_data_file,
         header_list);
@@ -3818,15 +3942,28 @@ signal(SIGTERM, SIG_IGN);
 signal(SIGINT, SIG_IGN);
 #endif /* HAVE_LOCAL_SCAN */
 
 signal(SIGINT, SIG_IGN);
 #endif /* HAVE_LOCAL_SCAN */
 
+/* If we are faking a reject or defer, avoid sennding a DSN for the
+actually-accepted message */
+
+if (fake_response != OK)
+  for (recipient_item * r = recipients_list;
+       r < recipients_list + recipients_count; r++)
+    {
+    DEBUG(D_receive) if (r->dsn_flags & (rf_notify_success | rf_notify_delay))
+      debug_printf("DSN: clearing flags due to fake-response for message\n");
+    r->dsn_flags = r->dsn_flags & ~(rf_notify_success | rf_notify_delay)
+                   | rf_notify_never;
+    }
+
 
 /* Ensure the first time flag is set in the newly-received message. */
 
 
 /* Ensure the first time flag is set in the newly-received message. */
 
-deliver_firsttime = TRUE;
+f.deliver_firsttime = TRUE;
 
 #ifdef EXPERIMENTAL_BRIGHTMAIL
 if (bmi_run == 1)
   { /* rewind data file */
 
 #ifdef EXPERIMENTAL_BRIGHTMAIL
 if (bmi_run == 1)
   { /* rewind data file */
-  lseek(data_fd, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+  lseek(data_fd, (long int)spool_data_start_offset(message_id), SEEK_SET);
   bmi_verdicts = bmi_process_message(header_list, data_fd);
   }
 #endif
   bmi_verdicts = bmi_process_message(header_list, data_fd);
   }
 #endif
@@ -3845,8 +3982,8 @@ memcpy(received_header->text + received_header->slen - tslen - 1,
 
 if (mua_wrapper)
   {
 
 if (mua_wrapper)
   {
-  deliver_freeze = FALSE;
-  queue_only_policy = FALSE;
+  f.deliver_freeze = FALSE;
+  f.queue_only_policy = FALSE;
   }
 
 /* Keep the data file open until we have written the header file, in order to
   }
 
 /* Keep the data file open until we have written the header file, in order to
@@ -3856,10 +3993,9 @@ file fails, we have failed to accept this message. */
 
 if (host_checking || blackholed_by)
   {
 
 if (host_checking || blackholed_by)
   {
-  header_line *h;
   Uunlink(spool_name);
   msg_size = 0;                                  /* Compute size for log line */
   Uunlink(spool_name);
   msg_size = 0;                                  /* Compute size for log line */
-  for (h = header_list; h; h = h->next)
+  for (header_line * h = header_list; h; h = h->next)
     if (h->type != '*') msg_size += h->slen;
   }
 
     if (h->type != '*') msg_size += h->slen;
   }
 
@@ -3874,12 +4010,11 @@ else
     if (smtp_input)
       {
       smtp_reply = US"451 Error in writing spool file";
     if (smtp_input)
       {
       smtp_reply = US"451 Error in writing spool file";
-      message_id[0] = 0;          /* Indicate no message accepted */
-      goto TIDYUP;
+      goto NOT_ACCEPTED;
       }
     else
       {
       }
     else
       {
-      fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+      fseek(spool_data_file, (long int)spool_data_start_offset(message_id), SEEK_SET);
       give_local_error(ERRMESS_IOERR, errmsg, US"", error_rc, spool_data_file,
         header_list);
       /* Does not return */
       give_local_error(ERRMESS_IOERR, errmsg, US"", error_rc, spool_data_file,
         header_list);
       /* Does not return */
@@ -3891,22 +4026,15 @@ else
 
 receive_messagecount++;
 
 
 receive_messagecount++;
 
-/* In SMTP sessions we may receive several in one connection. After each one,
-we wait for the clock to tick at the level of message-id granularity. This is
-so that the combination of time+pid is unique, even on systems where the pid
-can be re-used within our time interval. We can't shorten the interval without
-re-designing the message-id. See comments above where the message id is
-created. This is Something For The Future. */
-
-message_id_tv.tv_usec = (message_id_tv.tv_usec/id_resolution) * id_resolution;
-exim_wait_tick(&message_id_tv, id_resolution);
-
-/* Add data size to written header size. We do not count the initial file name
-that is in the file, but we do add one extra for the notional blank line that
-precedes the data. This total differs from message_size in that it include the
-added Received: header and any other headers that got created locally. */
-
-if (fflush(spool_data_file))
+if (  fflush(spool_data_file)
+#if _POSIX_C_SOURCE >= 199309L || _XOPEN_SOURCE >= 500
+# ifdef ENABLE_DISABLE_FSYNC
+   || !disable_fsync && fdatasync(data_fd)
+# else
+   || fdatasync(data_fd)
+# endif
+#endif
+   )
   {
   errmsg = string_sprintf("Spool write error: %s", strerror(errno));
   log_write(0, LOG_MAIN, "%s\n", errmsg);
   {
   errmsg = string_sprintf("Spool write error: %s", strerror(errno));
   log_write(0, LOG_MAIN, "%s\n", errmsg);
@@ -3915,20 +4043,24 @@ if (fflush(spool_data_file))
   if (smtp_input)
     {
     smtp_reply = US"451 Error in writing spool file";
   if (smtp_input)
     {
     smtp_reply = US"451 Error in writing spool file";
-    message_id[0] = 0;          /* Indicate no message accepted */
-    goto TIDYUP;
+    goto NOT_ACCEPTED;
     }
   else
     {
     }
   else
     {
-    fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+    fseek(spool_data_file, (long int)spool_data_start_offset(message_id), SEEK_SET);
     give_local_error(ERRMESS_IOERR, errmsg, US"", error_rc, spool_data_file,
       header_list);
     /* Does not return */
     }
   }
     give_local_error(ERRMESS_IOERR, errmsg, US"", error_rc, spool_data_file,
       header_list);
     /* Does not return */
     }
   }
-fstat(data_fd, &statbuf);
 
 
-msg_size += statbuf.st_size - SPOOL_DATA_START_OFFSET + 1;
+/* Add data size to written header size. We do not count the initial file name
+that is in the file, but we do add one extra for the notional blank line that
+precedes the data. This total differs from message_size in that it include the
+added Received: header and any other headers that got created locally. */
+
+fstat(data_fd, &statbuf);
+msg_size += statbuf.st_size - spool_data_start_offset(message_id) + 1;
 
 /* Generate a "message received" log entry. We do this by building up a dynamic
 string as required.  We log the arrival of a new message while the
 
 /* Generate a "message received" log entry. We do this by building up a dynamic
 string as required.  We log the arrival of a new message while the
@@ -3937,25 +4069,32 @@ it first! Include any message id that is in the message - since the syntax of a
 message id is actually an addr-spec, we can use the parse routine to canonicalize
 it. */
 
 message id is actually an addr-spec, we can use the parse routine to canonicalize
 it. */
 
+rcvd_log_reset_point = store_mark();
 g = string_get(256);
 
 g = string_append(g, 2,
   fake_response == FAIL ? US"(= " : US"<= ",
 g = string_get(256);
 
 g = string_append(g, 2,
   fake_response == FAIL ? US"(= " : US"<= ",
-  sender_address[0] == 0 ? US"<>" : sender_address);
+  *sender_address ? sender_address : US"<>");
 if (message_reference)
   g = string_append(g, 2, US" R=", message_reference);
 
 g = add_host_info_for_log(g);
 
 if (message_reference)
   g = string_append(g, 2, US" R=", message_reference);
 
 g = add_host_info_for_log(g);
 
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
 if (LOGGING(tls_cipher) && tls_in.cipher)
 if (LOGGING(tls_cipher) && tls_in.cipher)
+  {
   g = string_append(g, 2, US" X=", tls_in.cipher);
   g = string_append(g, 2, US" X=", tls_in.cipher);
+# ifndef DISABLE_TLS_RESUME
+  if (LOGGING(tls_resumption) && tls_in.resumption & RESUME_USED)
+    g = string_catn(g, US"*", 1);
+# endif
+  }
 if (LOGGING(tls_certificate_verified) && tls_in.cipher)
   g = string_append(g, 2, US" CV=", tls_in.certificate_verified ? "yes":"no");
 if (LOGGING(tls_peerdn) && tls_in.peerdn)
   g = string_append(g, 3, US" DN=\"", string_printing(tls_in.peerdn), US"\"");
 if (LOGGING(tls_sni) && tls_in.sni)
 if (LOGGING(tls_certificate_verified) && tls_in.cipher)
   g = string_append(g, 2, US" CV=", tls_in.certificate_verified ? "yes":"no");
 if (LOGGING(tls_peerdn) && tls_in.peerdn)
   g = string_append(g, 3, US" DN=\"", string_printing(tls_in.peerdn), US"\"");
 if (LOGGING(tls_sni) && tls_in.sni)
-  g = string_append(g, 3, US" SNI=\"", string_printing(tls_in.sni), US"\"");
+  g = string_append(g, 2, US" SNI=", string_printing2(tls_in.sni, SP_TAB|SP_SPACE));
 #endif
 
 if (sender_host_authenticated)
 #endif
 
 if (sender_host_authenticated)
@@ -3982,30 +4121,40 @@ if (proxy_session && LOGGING(proxy))
 if (chunking_state > CHUNKING_OFFERED)
   g = string_catn(g, US" K", 2);
 
 if (chunking_state > CHUNKING_OFFERED)
   g = string_catn(g, US" K", 2);
 
-sprintf(CS big_buffer, "%d", msg_size);
-g = string_append(g, 2, US" S=", big_buffer);
+g = string_fmt_append(g, " S=%d", msg_size);
 
 /* log 8BITMIME mode announced in MAIL_FROM
    0 ... no BODY= used
    7 ... 7BIT
    8 ... 8BITMIME */
 if (LOGGING(8bitmime))
 
 /* 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);
-  g = string_append(g, 2, US" M8S=", big_buffer);
-  }
+  g = string_fmt_append(g, " M8S=%d", body_8bitmime);
 
 #ifndef DISABLE_DKIM
 
 #ifndef DISABLE_DKIM
-if (LOGGING(dkim) && dkim_verify_overall)
-  g = string_append(g, 2, US" DKIM=", dkim_verify_overall);
+if (LOGGING(dkim))
+  {
+  misc_module_info * mi = misc_mod_findonly(US"dkim");
+  typedef gstring * (*fn_t)(gstring *);
+  if (mi)
+    g = (((fn_t *) mi->functions)[DKIM_VDOM_FIRSTPASS]) (g);
+
 # ifdef EXPERIMENTAL_ARC
 # ifdef EXPERIMENTAL_ARC
-if (LOGGING(dkim) && arc_state && Ustrcmp(arc_state, "pass") == 0)
-  g = string_catn(g, US" ARC", 4);
+   {
+    mi = misc_mod_findonly(US"arc");
+    typedef BOOL (*fn_t)(void);
+    if (mi && (((fn_t *) mi->functions)[ARC_STATE_IS_PASS]) ())
+      g = string_catn(g, US" ARC", 4);
+   }
 # endif
 # endif
+  }
 #endif
 
 if (LOGGING(receive_time))
 #endif
 
 if (LOGGING(receive_time))
-  g = string_append(g, 2, US" RT=", string_timediff(&received_time_taken));
+  {
+  struct timeval diff = received_time_complete;
+  timediff(&diff, &received_time);
+  g = string_append(g, 2, US" RT=", string_timediff(&diff));
+  }
 
 if (*queue_name)
   g = string_append(g, 2, US" Q=", queue_name);
 
 if (*queue_name)
   g = string_append(g, 2, US" Q=", queue_name);
@@ -4015,16 +4164,22 @@ 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.
 Also, allow for domain literals in the message id. */
 
 Therefore, make sure we use a printing-characters only version for the log.
 Also, allow for domain literals in the message id. */
 
-if (msgid_header)
+if (  LOGGING(msg_id) && msgid_header
+   && (LOGGING(msg_id_created) || !msgid_header_newly_created)
+   )
   {
   {
-  uschar *old_id;
+  uschar * old_id;
   BOOL save_allow_domain_literals = allow_domain_literals;
   allow_domain_literals = TRUE;
   BOOL save_allow_domain_literals = allow_domain_literals;
   allow_domain_literals = TRUE;
+  int start, end, domain;
+
   old_id = parse_extract_address(Ustrchr(msgid_header->text, ':') + 1,
     &errmsg, &start, &end, &domain, FALSE);
   allow_domain_literals = save_allow_domain_literals;
   old_id = parse_extract_address(Ustrchr(msgid_header->text, ':') + 1,
     &errmsg, &start, &end, &domain, FALSE);
   allow_domain_literals = save_allow_domain_literals;
-  if (old_id != NULL)
-    g = string_append(g, 2, US" id=", string_printing(old_id));
+  if (old_id)
+    g = string_append(g, 2,
+      msgid_header_newly_created ? US" id*=" : US" id=",
+      string_printing(old_id));
   }
 
 /* If subject logging is turned on, create suitable printing-character
   }
 
 /* If subject logging is turned on, create suitable printing-character
@@ -4032,7 +4187,6 @@ text. By expanding $h_subject: we make use of the MIME decoding. */
 
 if (LOGGING(subject) && subject_header)
   {
 
 if (LOGGING(subject) && subject_header)
   {
-  int i;
   uschar *p = big_buffer;
   uschar *ss = expand_string(US"$h_subject:");
 
   uschar *p = big_buffer;
   uschar *ss = expand_string(US"$h_subject:");
 
@@ -4040,7 +4194,7 @@ if (LOGGING(subject) && subject_header)
   a C-like string, and turn any non-printers into escape sequences. */
 
   *p++ = '\"';
   a C-like string, and turn any non-printers into escape sequences. */
 
   *p++ = '\"';
-  if (*ss != 0) for (i = 0; i < 100 && ss[i] != 0; i++)
+  if (*ss != 0) for (int i = 0; i < 100 && ss[i] != 0; i++)
     {
     if (ss[i] == '\"' || ss[i] == '\\') *p++ = '\\';
     *p++ = ss[i];
     {
     if (ss[i] == '\"' || ss[i] == '\\') *p++ = '\\';
     *p++ = ss[i];
@@ -4064,7 +4218,7 @@ if (message_logs && !blackholed_by)
   {
   int fd;
   uschar * m_name = spool_fname(US"msglog", message_subdir, message_id, US"");
   {
   int fd;
   uschar * m_name = spool_fname(US"msglog", message_subdir, message_id, US"");
-  
+
   if (  (fd = Uopen(m_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE)) < 0
      && errno == ENOENT
      )
   if (  (fd = Uopen(m_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE)) < 0
      && errno == ENOENT
      )
@@ -4089,11 +4243,12 @@ if (message_logs && !blackholed_by)
       }
     else
       {
       }
     else
       {
-      uschar *now = tod_stamp(tod_log);
+      uschar * now = tod_stamp(tod_log);
+      /* Drop the initial "<= " */
       fprintf(message_log, "%s Received from %s\n", now, g->s+3);
       fprintf(message_log, "%s Received from %s\n", now, g->s+3);
-      if (deliver_freeze) fprintf(message_log, "%s frozen by %s\n", now,
+      if (f.deliver_freeze) fprintf(message_log, "%s frozen by %s\n", now,
         frozen_by);
         frozen_by);
-      if (queue_only_policy) fprintf(message_log,
+      if (f.queue_only_policy) fprintf(message_log,
         "%s no immediate delivery: queued%s%s by %s\n", now,
         *queue_name ? " in " : "", *queue_name ? CS queue_name : "",
        queued_by);
         "%s no immediate delivery: queued%s%s by %s\n", now,
         *queue_name ? " in " : "", *queue_name ? CS queue_name : "",
        queued_by);
@@ -4106,12 +4261,12 @@ if (message_logs && !blackholed_by)
 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. */
 
 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;
+f.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
 
 /* 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
+receive_hasc(). 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.
 
 input to be sent following the final dot, so the presence of following input is
 not an error.
 
@@ -4126,20 +4281,14 @@ 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. */
 
 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 && !sender_host_notsocket &&
-    !receive_smtp_buffered())
+if (  smtp_input && sender_host_address && !f.sender_host_notsocket
+   && !receive_hasc())
   {
   {
-  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)
+  if (poll_one_fd(fileno(smtp_in), POLLIN, 0) != 0)
     {
     int c = (receive_getc)(GETC_BUFFER_UNLIMITED);
     {
     int c = (receive_getc)(GETC_BUFFER_UNLIMITED);
-    if (c != EOF) (receive_ungetc)(c); else
+    if (c != EOF) (receive_ungetc)(c);
+    else
       {
       smtp_notquit_exit(US"connection-lost", NULL, NULL);
       smtp_reply = US"";    /* No attempt to send a response */
       {
       smtp_notquit_exit(US"connection-lost", NULL, NULL);
       smtp_reply = US"";    /* No attempt to send a response */
@@ -4147,10 +4296,10 @@ if (smtp_input && sender_host_address && !sender_host_notsocket &&
 
       /* Re-use the log line workspace */
 
 
       /* Re-use the log line workspace */
 
-      g->ptr = 0;
+      gstring_reset(g);
       g = string_cat(g, US"SMTP connection lost after final dot");
       g = add_host_info_for_log(g);
       g = string_cat(g, US"SMTP connection lost after final dot");
       g = add_host_info_for_log(g);
-      log_write(0, LOG_MAIN, "%s", string_from_gstring(g));
+      log_write(0, LOG_MAIN, "%Y", g);
 
       /* Delete the files for this aborted message. */
 
 
       /* Delete the files for this aborted message. */
 
@@ -4191,7 +4340,7 @@ if(cutthrough.cctx.sock >= 0 && cutthrough.delivery)
 
     case '4':  /* Temp-reject. Keep spoolfiles and accept, unless defer-pass mode.
                ... for which, pass back the exact error */
 
     case '4':  /* Temp-reject. Keep spoolfiles and accept, unless defer-pass mode.
                ... for which, pass back the exact error */
-      if (cutthrough.defer_pass) smtp_reply = string_copy_malloc(msg);
+      if (cutthrough.defer_pass) smtp_reply = string_copy_perm(msg, TRUE);
       cutthrough_done = TMP_REJ;               /* Avoid the usual immediate delivery attempt */
       break;                                   /* message_id needed for SMTP accept below */
 
       cutthrough_done = TMP_REJ;               /* Avoid the usual immediate delivery attempt */
       break;                                   /* message_id needed for SMTP accept below */
 
@@ -4201,7 +4350,7 @@ if(cutthrough.cctx.sock >= 0 && cutthrough.delivery)
       break;                                   /* message_id needed for SMTP accept below */
 
     case '5':  /* Perm-reject.  Do the same to the source.  Dump any spoolfiles */
       break;                                   /* message_id needed for SMTP accept below */
 
     case '5':  /* Perm-reject.  Do the same to the source.  Dump any spoolfiles */
-      smtp_reply = string_copy_malloc(msg);            /* Pass on the exact error */
+      smtp_reply = string_copy_perm(msg, TRUE);                /* Pass on the exact error */
       cutthrough_done = PERM_REJ;
       break;
     }
       cutthrough_done = PERM_REJ;
       break;
     }
@@ -4214,25 +4363,26 @@ if(!smtp_reply)
 #endif
   {
   log_write(0, LOG_MAIN |
 #endif
   {
   log_write(0, LOG_MAIN |
-    (LOGGING(received_recipients) ? LOG_RECIPIENTS : 0) |
-    (LOGGING(received_sender) ? LOG_SENDER : 0),
-    "%s", g->s);
+               (LOGGING(received_recipients) ? LOG_RECIPIENTS : 0) |
+               (LOGGING(received_sender) ? LOG_SENDER : 0),
+           "%Y", g);
 
   /* Log any control actions taken by an ACL or local_scan(). */
 
 
   /* 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,
+  if (f.deliver_freeze) log_write(0, LOG_MAIN, "frozen by %s", frozen_by);
+  if (f.queue_only_policy) log_write(L_delay_delivery, LOG_MAIN,
     "no immediate delivery: queued%s%s by %s",
     "no immediate delivery: queued%s%s by %s",
-    *queue_name ? " in " : "", *queue_name ? CS queue_name : "",       
+    *queue_name ? " in " : "", *queue_name ? CS queue_name : "",
     queued_by);
   }
     queued_by);
   }
-receive_call_bombout = FALSE;
+f.receive_call_bombout = FALSE;
 
 
-store_reset(g);   /* The store for the main log message can be reused */
+/* The store for the main log message can be reused */
+rcvd_log_reset_point = store_reset(rcvd_log_reset_point);
 
 /* If the message is frozen, and freeze_tell is set, do the telling. */
 
 
 /* If the message is frozen, and freeze_tell is set, do the telling. */
 
-if (deliver_freeze && freeze_tell && freeze_tell[0])
+if (f.deliver_freeze && freeze_tell && freeze_tell[0])
   moan_tell_someone(freeze_tell, NULL, US"Message frozen on arrival",
     "Message %s was frozen on arrival by %s.\nThe sender is <%s>.\n",
     message_id, frozen_by, sender_address);
   moan_tell_someone(freeze_tell, NULL, US"Message frozen on arrival",
     "Message %s was frozen on arrival by %s.\nThe sender is <%s>.\n",
     message_id, frozen_by, sender_address);
@@ -4253,18 +4403,49 @@ a queue-runner could grab it in the window.
 
 A fflush() was done earlier in the expectation that any write errors on the
 data file will be flushed(!) out thereby. Nevertheless, it is theoretically
 
 A fflush() was done earlier in the expectation that any write errors on the
 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?  We can at least log it; if it is observed on some platform
-then we can think about properly declaring the message not-received. */
+possible for fclose() to fail - and this has been seen on obscure filesystems
+(probably one that delayed the actual media write as long as possible)
+but what to do? What has happened to the lock if this happens?
+It's a mess because we already logged the acceptance.
+We can at least log the issue, try to remove spoolfiles and respond with
+a temp-reject.  We do not want to close before logging acceptance because
+we want to hold the lock until we know that logging worked.
+Could we make this less likely by doing an fdatasync() just after the fflush()?
+That seems like a good thing on data-security grounds, but how much will it hit
+performance? */
+
 
 
+goto TIDYUP;
+
+NOT_ACCEPTED:
+message_id[0] = 0;                             /* Indicate no message accepted */
 
 TIDYUP:
 process_info[process_info_len] = 0;                    /* Remove message id */
 if (spool_data_file && cutthrough_done == NOT_TRIED)
   {
   if (fclose(spool_data_file))                         /* Frees the lock */
 
 TIDYUP:
 process_info[process_info_len] = 0;                    /* Remove message id */
 if (spool_data_file && cutthrough_done == NOT_TRIED)
   {
   if (fclose(spool_data_file))                         /* Frees the lock */
-    log_write(0, LOG_MAIN|LOG_PANIC,
-      "spoolfile error on close: %s", strerror(errno));
+    {
+    log_msg = string_sprintf("spoolfile error on close: %s", strerror(errno));
+    log_write(0, LOG_MAIN|LOG_PANIC |
+                 (LOGGING(received_recipients) ? LOG_RECIPIENTS : 0) |
+                 (LOGGING(received_sender) ? LOG_SENDER : 0),
+             "%s", log_msg);
+    log_write(0, LOG_MAIN |
+                 (LOGGING(received_recipients) ? LOG_RECIPIENTS : 0) |
+                 (LOGGING(received_sender) ? LOG_SENDER : 0),
+             "rescind the above message-accept");
+
+    Uunlink(spool_name);
+    Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H"));
+    Uunlink(spool_fname(US"msglog", message_subdir, message_id, US""));
+
+    /* Claim a data ACL temp-reject, just to get reject logging and response */
+    if (smtp_input) smtp_handle_acl_fail(ACL_WHERE_DATA, rc, NULL, log_msg);
+    smtp_reply = US"";         /* Indicate reply already sent */
+
+    message_id[0] = 0;         /* no message accepted */
+    }
   spool_data_file = NULL;
   }
 
   spool_data_file = NULL;
   }
 
@@ -4293,7 +4474,7 @@ if (smtp_input)
       {
       if (fake_response != OK)
         smtp_respond(fake_response == DEFER ? US"450" : US"550",
       {
       if (fake_response != OK)
         smtp_respond(fake_response == DEFER ? US"450" : US"550",
-         3, TRUE, fake_response_text);
+         3, SR_FINAL, fake_response_text);
 
       /* An OK response is required; use "message" text if present. */
 
 
       /* An OK response is required; use "message" text if present. */
 
@@ -4302,19 +4483,24 @@ if (smtp_input)
         uschar *code = US"250";
         int len = 3;
         smtp_message_code(&code, &len, &user_msg, NULL, TRUE);
         uschar *code = US"250";
         int len = 3;
         smtp_message_code(&code, &len, &user_msg, NULL, TRUE);
-        smtp_respond(code, len, TRUE, user_msg);
+        smtp_respond(code, len, SR_FINAL, user_msg);
         }
 
       /* Default OK response */
 
       else if (chunking_state > CHUNKING_OFFERED)
        {
         }
 
       /* Default OK response */
 
       else if (chunking_state > CHUNKING_OFFERED)
        {
-        smtp_printf("250- %u byte chunk, total %d\r\n250 OK id=%s\r\n", FALSE,
+       /* If there is more input waiting, no need to flush (probably the client
+       pipelined QUIT after data).  We check only the in-process buffer, not
+       the socket. */
+
+        smtp_printf("250- %u byte chunk, total %d\r\n250 OK id=%s\r\n",
+           receive_hasc(),
            chunking_datasize, message_size+message_linecount, message_id);
        chunking_state = CHUNKING_OFFERED;
        }
       else
            chunking_datasize, message_size+message_linecount, message_id);
        chunking_state = CHUNKING_OFFERED;
        }
       else
-        smtp_printf("250 OK id=%s\r\n", FALSE, message_id);
+        smtp_printf("250 OK id=%s\r\n", receive_hasc(), message_id);
 
       if (host_checking)
         fprintf(stdout,
 
       if (host_checking)
         fprintf(stdout,
@@ -4325,10 +4511,10 @@ if (smtp_input)
 
     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 ? US"450" : US"550", 3, TRUE,
-          fake_response_text);
+        smtp_respond(fake_response == DEFER ? US"450" : US"550",
+                     3, SR_FINAL, fake_response_text);
       else
       else
-        smtp_printf("%.1024s\r\n", FALSE, smtp_reply);
+        smtp_printf("%.1024s\r\n", SP_NO_MORE, smtp_reply);
 
     switch (cutthrough_done)
       {
 
     switch (cutthrough_done)
       {
@@ -4402,3 +4588,5 @@ return yield;  /* TRUE if more messages (SMTP only) */
 }
 
 /* End of receive.c */
 }
 
 /* End of receive.c */
+/* vi: se aw ai sw=2
+*/