Spec docs for IDNA2008 support
[exim.git] / src / src / receive.c
index 072fee9f1f27aaf4fe50541e1ee17e94d3f5ba69..67fcc8e15b73503c7e9035c8746478ab3debf97e 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2017 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Code for receiving a message and setting up spool files. */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Code for receiving a message and setting up spool files. */
@@ -14,7 +14,7 @@ extern int dcc_ok;
 #endif
 
 #ifdef EXPERIMENTAL_DMARC
 #endif
 
 #ifdef EXPERIMENTAL_DMARC
-#include "dmarc.h"
+# include "dmarc.h"
 #endif /* EXPERIMENTAL_DMARC */
 
 /*************************************************
 #endif /* EXPERIMENTAL_DMARC */
 
 /*************************************************
@@ -23,7 +23,7 @@ extern int dcc_ok;
 
 static FILE   *data_file = NULL;
 static int     data_fd = -1;
 
 static FILE   *data_file = NULL;
 static int     data_fd = -1;
-static uschar  spool_name[256];
+static uschar *spool_name = US"";
 
 
 
 
 
 
@@ -37,7 +37,7 @@ the file. (When SMTP input is occurring, different functions are used by
 changing the pointer variables.) */
 
 int
 changing the pointer variables.) */
 
 int
-stdin_getc(void)
+stdin_getc(unsigned lim)
 {
 return getc(stdin);
 }
 {
 return getc(stdin);
 }
@@ -87,7 +87,7 @@ if (newsender == NULL || untrusted_set_sender == NULL) return FALSE;
 qnewsender = (Ustrchr(newsender, '@') != NULL)?
   newsender : string_sprintf("%s@%s", newsender, qualify_domain_sender);
 return
 qnewsender = (Ustrchr(newsender, '@') != NULL)?
   newsender : string_sprintf("%s@%s", newsender, qualify_domain_sender);
 return
-  match_address_list(qnewsender, TRUE, TRUE, &untrusted_set_sender, NULL, -1,
+  match_address_list(qnewsender, TRUE, TRUE, CUSS &untrusted_set_sender, NULL, -1,
     0, NULL) == OK;
 }
 
     0, NULL) == OK;
 }
 
@@ -124,6 +124,7 @@ receive_statvfs(BOOL isspool, int *inodeptr)
 {
 #ifdef HAVE_STATFS
 struct STATVFS statbuf;
 {
 #ifdef HAVE_STATFS
 struct STATVFS statbuf;
+struct stat dummy;
 uschar *path;
 uschar *name;
 uschar buffer[1024];
 uschar *path;
 uschar *name;
 uschar buffer[1024];
@@ -142,17 +143,16 @@ appearance of "syslog" in it. */
 else
   {
   int sep = ':';              /* Not variable - outside scripts use */
 else
   {
   int sep = ':';              /* Not variable - outside scripts use */
-  uschar *p = log_file_path;
+  const uschar *p = log_file_path;
   name = US"log";
 
   /* An empty log_file_path means "use the default". This is the same as an
   empty item in a list. */
 
   if (*p == 0) p = US":";
   name = US"log";
 
   /* An empty log_file_path means "use the default". This is the same as an
   empty item in a list. */
 
   if (*p == 0) p = US":";
-  while ((path = string_nextinlist(&p, &sep, buffer, sizeof(buffer))) != NULL)
-    {
-    if (Ustrcmp(path, "syslog") != 0) break;
-    }
+  while ((path = string_nextinlist(&p, &sep, buffer, sizeof(buffer))))
+    if (Ustrcmp(path, "syslog") != 0)
+      break;
 
   if (path == NULL)  /* No log files */
     {
 
   if (path == NULL)  /* No log files */
     {
@@ -181,12 +181,18 @@ else
 memset(&statbuf, 0, sizeof(statbuf));
 
 if (STATVFS(CS path, &statbuf) != 0)
 memset(&statbuf, 0, sizeof(statbuf));
 
 if (STATVFS(CS path, &statbuf) != 0)
-  {
-  log_write(0, LOG_MAIN|LOG_PANIC, "cannot accept message: failed to stat "
-    "%s directory %s: %s", name, spool_directory, strerror(errno));
-  smtp_closedown(US"spool or log directory problem");
-  exim_exit(EXIT_FAILURE);
-  }
+  if (stat(CS path, &dummy) == -1 && errno == ENOENT)
+    {                          /* Can happen on first run after installation */
+    *inodeptr = -1;
+    return -1;
+    }
+  else
+    {
+    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);
+    }
 
 *inodeptr = (statbuf.F_FILES > 0)? statbuf.F_FAVAIL : -1;
 
 
 *inodeptr = (statbuf.F_FILES > 0)? statbuf.F_FAVAIL : -1;
 
@@ -194,9 +200,9 @@ if (STATVFS(CS path, &statbuf) != 0)
 
 return (int)(((double)statbuf.F_BAVAIL * (double)statbuf.F_FRSIZE)/1024.0);
 
 
 return (int)(((double)statbuf.F_BAVAIL * (double)statbuf.F_FRSIZE)/1024.0);
 
+#else
 /* Unable to find partition sizes in this environment. */
 
 /* Unable to find partition sizes in this environment. */
 
-#else
 *inodeptr = -1;
 return -1;
 #endif
 *inodeptr = -1;
 return -1;
 #endif
@@ -497,6 +503,8 @@ recipients_list[recipients_count].bmi_optin = bmi_current_optin;
 /* reset optin string pointer for next recipient */
 bmi_current_optin = NULL;
 #endif
 /* reset optin string pointer for next recipient */
 bmi_current_optin = NULL;
 #endif
+recipients_list[recipients_count].orcpt = NULL;
+recipients_list[recipients_count].dsn_flags = 0;
 recipients_list[recipients_count++].errors_to = NULL;
 }
 
 recipients_list[recipients_count++].errors_to = NULL;
 }
 
@@ -519,12 +527,12 @@ Arguments:
 Returns:       nothing
 */
 
 Returns:       nothing
 */
 
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
 static void
 smtp_user_msg(uschar *code, uschar *user_msg)
 {
 int len = 3;
 static void
 smtp_user_msg(uschar *code, uschar *user_msg)
 {
 int len = 3;
-smtp_message_code(&code, &len, &user_msg, NULL);
+smtp_message_code(&code, &len, &user_msg, NULL, TRUE);
 smtp_respond(code, len, TRUE, user_msg);
 }
 #endif
 smtp_respond(code, len, TRUE, user_msg);
 }
 #endif
@@ -618,7 +626,7 @@ if (!dot_ends)
   {
   register int last_ch = '\n';
 
   {
   register int last_ch = '\n';
 
-  for (; (ch = (receive_getc)()) != EOF; last_ch = ch)
+  for (; (ch = (receive_getc)(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')
@@ -660,7 +668,7 @@ if (!dot_ends)
 
 ch_state = 1;
 
 
 ch_state = 1;
 
-while ((ch = (receive_getc)()) != EOF)
+while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF)
   {
   if (ch == 0) body_zerocount++;
   switch (ch_state)
   {
   if (ch == 0) body_zerocount++;
   switch (ch_state)
@@ -680,7 +688,9 @@ while ((ch = (receive_getc)()) != EOF)
 
     case 1:                         /* After written "\n" */
     if (ch == '.') { ch_state = 3; continue; }
 
     case 1:                         /* After written "\n" */
     if (ch == '.') { ch_state = 3; continue; }
-    if (ch != '\n') ch_state = 0; else linelength = -1;
+    if (ch == '\r') { ch_state = 2; continue; }
+    if (ch == '\n') { body_linecount++; linelength = -1; }
+    else ch_state = 0;
     break;
 
     case 2:
     break;
 
     case 2:
@@ -774,9 +784,9 @@ read_message_data_smtp(FILE *fout)
 {
 int ch_state = 0;
 int ch;
 {
 int ch_state = 0;
 int ch;
-register int linelength = 0;
+int linelength = 0;
 
 
-while ((ch = (receive_getc)()) != EOF)
+while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF)
   {
   if (ch == 0) body_zerocount++;
   switch (ch_state)
   {
   if (ch == 0) body_zerocount++;
   switch (ch_state)
@@ -833,7 +843,15 @@ while ((ch = (receive_getc)()) != EOF)
       ch_state = 4;
       continue;
       }
       ch_state = 4;
       continue;
       }
-    ch_state = 1;                       /* The dot itself is removed */
+    /* 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;
+      (void) cutthrough_puts(&c, 1);
+      }
+    ch_state = 1;
     break;
 
     case 4:                             /* After [CR] LF . CR */
     break;
 
     case 4:                             /* After [CR] LF . CR */
@@ -856,7 +874,7 @@ while ((ch = (receive_getc)()) != EOF)
 
   message_size++;
   linelength++;
 
   message_size++;
   linelength++;
-  if (fout != NULL)
+  if (fout)
     {
     if (fputc(ch, fout) == EOF) return END_WERROR;
     if (message_size > thismessage_size_limit) return END_SIZE;
     {
     if (fputc(ch, fout) == EOF) return END_WERROR;
     if (message_size > thismessage_size_limit) return END_SIZE;
@@ -865,7 +883,7 @@ while ((ch = (receive_getc)()) != EOF)
     (void) cutthrough_put_nl();
   else
     {
     (void) cutthrough_put_nl();
   else
     {
-    uschar c= ch;
+    uschar c = ch;
     (void) cutthrough_puts(&c, 1);
     }
   }
     (void) cutthrough_puts(&c, 1);
     }
   }
@@ -879,6 +897,96 @@ return END_EOF;
 
 
 
 
 
 
+/* Variant of the above read_message_data_smtp() specialised for RFC 3030
+CHUNKING. Accept input lines separated by either CRLF or CR or LF and write
+LF-delimited spoolfile.  Until we have wireformat spoolfiles, we need the
+body_linecount accounting for proper re-expansion for the wire, so use
+a cut-down version of the state-machine above; we don't need to do leading-dot
+detection and unstuffing.
+
+Arguments:
+  fout      a FILE to which to write the message; NULL if skipping
+
+Returns:    One of the END_xxx values indicating why it stopped reading
+*/
+
+static int
+read_message_bdat_smtp(FILE *fout)
+{
+int ch_state = 0, linelength = 0, ch;
+
+for(;;)
+  {
+  switch ((ch = (bdat_getc)(GETC_BUFFER_UNLIMITED)))
+    {
+    case EOF:  return END_EOF;
+    case EOD:  return END_DOT;         /* normal exit */
+    case ERR:  return END_PROTOCOL;
+    case '\0':  body_zerocount++; break;
+    }
+  switch (ch_state)
+    {
+    case 0:                             /* After LF or CRLF */
+      ch_state = 1;
+      /* fall through to handle as normal uschar. */
+
+    case 1:                             /* Mid-line state */
+      if (ch == '\n')
+       {
+       ch_state = 0;
+       body_linecount++;
+       if (linelength > max_received_linelength)
+         max_received_linelength = linelength;
+       linelength = -1;
+       }
+      else if (ch == '\r')
+       {
+       ch_state = 2;
+       continue;                       /* don't write CR */
+       }
+      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;
+       (void) cutthrough_put_nl();
+       if (ch == '\r') continue;       /* don't write CR */
+       ch_state = 1;
+       }
+      break;
+    }
+
+  /* Add the character to the spool file, unless skipping */
+
+  message_size++;
+  linelength++;
+  if (fout)
+    {
+    if (fputc(ch, fout) == EOF) return END_WERROR;
+    if (message_size > thismessage_size_limit) return END_SIZE;
+    }
+  if(ch == '\n')
+    (void) cutthrough_put_nl();
+  else
+    {
+    uschar c = ch;
+    (void) cutthrough_puts(&c, 1);
+    }
+  }
+/*NOTREACHED*/
+}
+
+
+
+
 /*************************************************
 *             Swallow SMTP message               *
 *************************************************/
 /*************************************************
 *             Swallow SMTP message               *
 *************************************************/
@@ -895,6 +1003,7 @@ Returns:     nothing
 void
 receive_swallow_smtp(void)
 {
 void
 receive_swallow_smtp(void)
 {
+/*XXX CHUNKING: not enough.  read chunks until RSET? */
 if (message_ended >= END_NOTENDED)
   message_ended = read_message_data_smtp(NULL);
 }
 if (message_ended >= END_NOTENDED)
   message_ended = read_message_data_smtp(NULL);
 }
@@ -917,6 +1026,7 @@ 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);
+smtp_notquit_exit(US"connection-lost", NULL, NULL);
 return US"421 Lost incoming connection";
 }
 
 return US"421 Lost incoming connection";
 }
 
@@ -951,10 +1061,12 @@ if (error_handling == ERRORS_SENDER)
   error_block eblock;
   eblock.next = NULL;
   eblock.text1 = text1;
   error_block eblock;
   eblock.next = NULL;
   eblock.text1 = text1;
+  eblock.text2 = US"";
   if (!moan_to_sender(errcode, &eblock, hptr, f, FALSE))
     error_rc = EXIT_FAILURE;
   }
   if (!moan_to_sender(errcode, &eblock, hptr, f, FALSE))
     error_rc = EXIT_FAILURE;
   }
-else fprintf(stderr, "exim: %s%s\n", text2, text1);  /* Sic */
+else
+  fprintf(stderr, "exim: %s%s\n", text2, text1);  /* Sic */
 (void)fclose(f);
 exim_exit(error_rc);
 }
 (void)fclose(f);
 exim_exit(error_rc);
 }
@@ -984,38 +1096,41 @@ Returns:     nothing
 */
 
 static void
 */
 
 static void
-add_acl_headers(uschar *acl_name)
+add_acl_headers(int where, uschar *acl_name)
 {
 header_line *h, *next;
 header_line *last_received = NULL;
 
 {
 header_line *h, *next;
 header_line *last_received = NULL;
 
+switch(where)
+  {
+  case ACL_WHERE_DKIM:
+  case ACL_WHERE_MIME:
+  case ACL_WHERE_DATA:
+    if (cutthrough.fd >= 0 && (acl_removed_headers || acl_added_headers))
+    {
+    log_write(0, LOG_MAIN|LOG_PANIC, "Header modification in data ACLs"
+                       " will not take effect on cutthrough deliveries");
+    return;
+    }
+  }
+
 if (acl_removed_headers != NULL)
   {
   DEBUG(D_receive|D_acl) debug_printf(">>Headers removed by %s ACL:\n", acl_name);
 
 if (acl_removed_headers != NULL)
   {
   DEBUG(D_receive|D_acl) debug_printf(">>Headers removed by %s ACL:\n", acl_name);
 
-  for (h = header_list; h != NULL; h = h->next)
+  for (h = header_list; h != NULL; h = h->next) if (h->type != htype_old)
     {
     {
-    uschar *list;
-    BOOL include_header;
-
-    if (h->type == htype_old) continue;
-
-    include_header = TRUE;
-    list = acl_removed_headers;
-
+    const uschar * list = acl_removed_headers;
     int sep = ':';         /* This is specified as a colon-separated list */
     uschar *s;
     uschar buffer[128];
     int sep = ':';         /* This is specified as a colon-separated list */
     uschar *s;
     uschar buffer[128];
-    while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))
-            != NULL)
-      {
-      int len = Ustrlen(s);
-      if (header_testname(h, s, len, FALSE))
+
+    while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
+      if (header_testname(h, s, Ustrlen(s), FALSE))
        {
        h->type = htype_old;
         DEBUG(D_receive|D_acl) debug_printf("  %s", h->text);
        }
        {
        h->type = htype_old;
         DEBUG(D_receive|D_acl) debug_printf("  %s", h->text);
        }
-      }
     }
   acl_removed_headers = NULL;
   DEBUG(D_receive|D_acl) debug_printf(">>\n");
     }
   acl_removed_headers = NULL;
   DEBUG(D_receive|D_acl) debug_printf(">>\n");
@@ -1108,17 +1223,17 @@ Returns:      the extended string
 */
 
 static uschar *
 */
 
 static uschar *
-add_host_info_for_log(uschar *s, int *sizeptr, int *ptrptr)
+add_host_info_for_log(uschar * s, int * sizeptr, int * ptrptr)
 {
 {
-if (sender_fullhost != NULL)
+if (sender_fullhost)
   {
   {
+  if (LOGGING(dnssec) && sender_host_dnssec)   /*XXX sender_helo_dnssec? */
+    s = string_cat(s, sizeptr, ptrptr, US" DS");
   s = string_append(s, sizeptr, ptrptr, 2, US" H=", sender_fullhost);
   s = string_append(s, sizeptr, ptrptr, 2, US" H=", sender_fullhost);
-  if ((log_extra_selector & LX_incoming_interface) != 0 &&
-       interface_address != NULL)
+  if (LOGGING(incoming_interface) && interface_address != NULL)
     {
     {
-    uschar *ss = string_sprintf(" I=[%s]:%d", interface_address,
-      interface_port);
-    s = string_cat(s, sizeptr, ptrptr, ss, Ustrlen(ss));
+    s = string_cat(s, sizeptr, ptrptr,
+      string_sprintf(" I=[%s]:%d", interface_address, interface_port));
     }
   }
 if (sender_ident != NULL)
     }
   }
 if (sender_ident != NULL)
@@ -1224,47 +1339,45 @@ if (Ustrlen(rfc822_file_path) > 0)
 if (rc == OK)
   {
   uschar temp_path[1024];
 if (rc == OK)
   {
   uschar temp_path[1024];
-  int n;
-  struct dirent *entry;
-  DIR *tempdir;
+  struct dirent * entry;
+  DIR * tempdir;
 
 
-  (void)string_format(temp_path, 1024, "%s/scan/%s", spool_directory,
-    message_id);
+  (void) string_format(temp_path, sizeof(temp_path), "%s/scan/%s",
+    spool_directory, message_id);
 
   tempdir = opendir(CS temp_path);
 
   tempdir = opendir(CS temp_path);
-  n = 0;
-  do
+  for (;;)
     {
     {
-    entry = readdir(tempdir);
-    if (entry == NULL) break;
-    if (strncmpic(US entry->d_name,US"__rfc822_",9) == 0)
+    if (!(entry = readdir(tempdir)))
+      break;
+    if (strncmpic(US entry->d_name, US"__rfc822_", 9) == 0)
       {
       {
-      (void)string_format(rfc822_file_path, 2048,"%s/scan/%s/%s", spool_directory, message_id, entry->d_name);
-      debug_printf("RFC822 attachment detected: running MIME ACL for '%s'\n", rfc822_file_path);
+      (void) string_format(rfc822_file_path, sizeof(rfc822_file_path),
+       "%s/scan/%s/%s", spool_directory, message_id, entry->d_name);
+      debug_printf("RFC822 attachment detected: running MIME ACL for '%s'\n",
+       rfc822_file_path);
       break;
       }
       break;
       }
-    } while (1);
+    }
   closedir(tempdir);
 
   closedir(tempdir);
 
-  if (entry != NULL)
+  if (entry)
     {
     {
-    mbox_file = Ufopen(rfc822_file_path,"rb");
-    if (mbox_file == NULL)
+    if ((mbox_file = Ufopen(rfc822_file_path, "rb")))
       {
       {
-      log_write(0, LOG_PANIC,
-         "acl_smtp_mime: can't open RFC822 spool file, skipping.");
-      unlink(CS rfc822_file_path);
-      goto END_MIME_ACL;
+      /* set RFC822 expansion variable */
+      mime_is_rfc822 = 1;
+      mime_part_count_buffer = mime_part_count;
+      goto MIME_ACL_CHECK;
       }
       }
-    /* set RFC822 expansion variable */
-    mime_is_rfc822 = 1;
-    mime_part_count_buffer = mime_part_count;
-    goto MIME_ACL_CHECK;
+    log_write(0, LOG_PANIC,
+       "acl_smtp_mime: can't open RFC822 spool file, skipping.");
+    unlink(CS rfc822_file_path);
     }
   }
 
 END_MIME_ACL:
     }
   }
 
 END_MIME_ACL:
-add_acl_headers(US"MIME");
+add_acl_headers(ACL_WHERE_MIME, US"MIME");
 if (rc == DISCARD)
   {
   recipients_count = 0;
 if (rc == DISCARD)
   {
   recipients_count = 0;
@@ -1277,10 +1390,12 @@ else if (rc != OK)
 #ifdef EXPERIMENTAL_DCC
   dcc_ok = 0;
 #endif
 #ifdef EXPERIMENTAL_DCC
   dcc_ok = 0;
 #endif
-  if (smtp_input && smtp_handle_acl_fail(ACL_WHERE_MIME, rc, user_msg, log_msg) != 0) {
-    *smtp_yield_ptr = FALSE;    /* No more messsages after dropped connection */
+  if (  smtp_input
+     && smtp_handle_acl_fail(ACL_WHERE_MIME, rc, user_msg, log_msg) != 0)
+    {
+    *smtp_yield_ptr = FALSE;    /* No more messages after dropped connection */
     *smtp_reply_ptr = US"";     /* Indicate reply already sent */
     *smtp_reply_ptr = US"";     /* Indicate reply already sent */
-  }
+    }
   message_id[0] = 0;            /* Indicate no message accepted */
   return FALSE;                 /* Cause skip to end of receive function */
   }
   message_id[0] = 0;            /* Indicate no message accepted */
   return FALSE;                 /* Cause skip to end of receive function */
   }
@@ -1304,7 +1419,7 @@ if (recipients_count == 1) received_for = recipients_list[0].address;
 received = expand_string(received_header_text);
 received_for = NULL;
 
 received = expand_string(received_header_text);
 received_for = NULL;
 
-if (received == NULL)
+if (!received)
   {
   if(spool_name[0] != 0)
     Uunlink(spool_name);           /* Lose the data file */
   {
   if(spool_name[0] != 0)
     Uunlink(spool_name);           /* Lose the data file */
@@ -1454,7 +1569,7 @@ BOOL resents_exist = FALSE;
 uschar *resent_prefix = US"";
 uschar *blackholed_by = NULL;
 uschar *blackhole_log_msg = US"";
 uschar *resent_prefix = US"";
 uschar *blackholed_by = NULL;
 uschar *blackhole_log_msg = US"";
-enum {NOT_TRIED, TMP_REJ, PERM_REJ, ACCEPTED} cutthrough_done;
+enum {NOT_TRIED, TMP_REJ, PERM_REJ, ACCEPTED} cutthrough_done = NOT_TRIED;
 
 flock_t lock_data;
 error_block *bad_addresses = NULL;
 
 flock_t lock_data;
 error_block *bad_addresses = NULL;
@@ -1528,7 +1643,7 @@ yet, initialize the size and warning count, and deal with no size limit. */
 message_id[0] = 0;
 data_file = NULL;
 data_fd = -1;
 message_id[0] = 0;
 data_file = NULL;
 data_fd = -1;
-spool_name[0] = 0;
+spool_name = US"";
 message_size = 0;
 warning_count = 0;
 received_count = 1;            /* For the one we will add */
 message_size = 0;
 warning_count = 0;
 received_count = 1;            /* For the one we will add */
@@ -1541,8 +1656,10 @@ message_linecount = body_linecount = body_zerocount =
   max_received_linelength = 0;
 
 #ifndef DISABLE_DKIM
   max_received_linelength = 0;
 
 #ifndef DISABLE_DKIM
-/* Call into DKIM to set up the context. */
-if (smtp_input && !smtp_batched_input && !dkim_disable_verify) dkim_exim_verify_init();
+/* 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);
 #endif
 
 #ifdef EXPERIMENTAL_DMARC
 #endif
 
 #ifdef EXPERIMENTAL_DMARC
@@ -1598,7 +1715,7 @@ next->text. */
 
 for (;;)
   {
 
 for (;;)
   {
-  int ch = (receive_getc)();
+  int ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
 
   /* 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. */
@@ -1677,10 +1794,10 @@ for (;;)
 
   if (ptr == 0 && ch == '.' && (smtp_input || dot_ends))
     {
 
   if (ptr == 0 && ch == '.' && (smtp_input || dot_ends))
     {
-    ch = (receive_getc)();
+    ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
     if (ch == '\r')
       {
     if (ch == '\r')
       {
-      ch = (receive_getc)();
+      ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
       if (ch != '\n')
         {
         receive_ungetc(ch);
       if (ch != '\n')
         {
         receive_ungetc(ch);
@@ -1711,7 +1828,7 @@ for (;;)
 
   if (ch == '\r')
     {
 
   if (ch == '\r')
     {
-    ch = (receive_getc)();
+    ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
     if (ch == '\n')
       {
       if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = TRUE;
     if (ch == '\n')
       {
       if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = TRUE;
@@ -1806,7 +1923,7 @@ for (;;)
 
   if (ch != EOF)
     {
 
   if (ch != EOF)
     {
-    int nextch = (receive_getc)();
+    int nextch = (receive_getc)(GETC_BUFFER_UNLIMITED);
     if (nextch == ' ' || nextch == '\t')
       {
       next->text[ptr++] = nextch;
     if (nextch == ' ' || nextch == '\t')
       {
       next->text[ptr++] = nextch;
@@ -2000,6 +2117,21 @@ for (;;)
       }
     }
 
       }
     }
 
+  /* Reject CHUNKING messages that do not CRLF their first header line */
+
+  if (!first_line_ended_crlf && chunking_state > CHUNKING_OFFERED)
+    {
+    log_write(L_size_reject, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: "
+      "Non-CRLF-terminated header, under CHUNKING: message abandoned",
+      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");
+    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. */
 
   /* The line has been handled. If we have hit EOF, break out of the loop,
   indicating no pending data line. */
 
@@ -2024,7 +2156,7 @@ normal case). */
 DEBUG(D_receive)
   {
   debug_printf(">>Headers received:\n");
 DEBUG(D_receive)
   {
   debug_printf(">>Headers received:\n");
-  for (h = header_list->next; h != NULL; h = h->next)
+  for (h = header_list->next; h; h = h->next)
     debug_printf("%s", h->text);
   debug_printf("\n");
   }
     debug_printf("%s", h->text);
   debug_printf("\n");
   }
@@ -2051,7 +2183,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 != NULL; h = h->next)
+for (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;
@@ -2267,7 +2399,7 @@ if (extract_recip)
 
   /* Now scan the headers */
 
 
   /* Now scan the headers */
 
-  for (h = header_list->next; h != NULL; h = h->next)
+  for (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))
@@ -2300,9 +2432,23 @@ if (extract_recip)
         pp = recipient = store_get(ss - s + 1);
         for (p = s; p < ss; p++) if (*p != '\n') *pp++ = *p;
         *pp = 0;
         pp = recipient = store_get(ss - s + 1);
         for (p = s; p < ss; p++) if (*p != '\n') *pp++ = *p;
         *pp = 0;
+
+#ifdef SUPPORT_I18N
+       {
+       BOOL b = allow_utf8_domains;
+       allow_utf8_domains = TRUE;
+#endif
         recipient = parse_extract_address(recipient, &errmess, &start, &end,
           &domain, FALSE);
 
         recipient = parse_extract_address(recipient, &errmess, &start, &end,
           &domain, FALSE);
 
+#ifdef SUPPORT_I18N
+       if (string_is_utf8(recipient))
+         message_smtputf8 = TRUE;
+       else
+         allow_utf8_domains = b;
+       }
+#endif
+
         /* Keep a list of all the bad addresses so we can send a single
         error message at the end. However, an empty address is not an error;
         just ignore it. This can come from an empty group list like
         /* Keep a list of all the bad addresses so we can send a single
         error message at the end. However, an empty address is not an error;
         just ignore it. This can come from an empty group list like
@@ -2443,7 +2589,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;
+message_subdir[0] = split_spool_directory ? message_id[5] : 0;
 
 /* Now that we have the message-id, if there is no message-id: header, generate
 one, but only for local (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
@@ -2512,7 +2658,7 @@ if (msgid_header == NULL &&
 rewriting. Must copy the count, because later ACLs and the local_scan()
 function may mess with the real recipients. */
 
 rewriting. Must copy the count, because later ACLs and the local_scan()
 function may mess with the real recipients. */
 
-if ((log_extra_selector & LX_received_recipients) != 0)
+if (LOGGING(received_recipients))
   {
   raw_recipients = store_get(recipients_count * sizeof(uschar *));
   for (i = 0; i < recipients_count; i++)
   {
   raw_recipients = store_get(recipients_count * sizeof(uschar *));
   for (i = 0; i < recipients_count; i++)
@@ -2747,11 +2893,11 @@ 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 != NULL; h = h->next)
+for (h = header_list->next; h; h = h->next)
   {
   header_line *newh = rewrite_header(h, NULL, NULL, global_rewrite_rules,
     rewrite_existflags, TRUE);
   {
   header_line *newh = rewrite_header(h, NULL, NULL, global_rewrite_rules,
     rewrite_existflags, TRUE);
-  if (newh != NULL) h = newh;
+  if (newh) h = newh;
   }
 
 
   }
 
 
@@ -2802,13 +2948,20 @@ if (filter_test != FTEST_NONE)
   return message_ended == END_DOT;
   }
 
   return message_ended == END_DOT;
   }
 
-/* Cutthrough delivery:
-       We have to create the Received header now rather than at the end of reception,
-       so the timestamp behaviour is a change to the normal case.
-       XXX Ensure this gets documented XXX.
-       Having created it, send the headers to the destination.
+/*XXX CHUNKING: need to cancel cutthrough under BDAT, for now.  In future,
+think more if it could be handled.  Cannot do onward CHUNKING unless
+inbound is, but inbound chunking ought to be ok with outbound plain.
+Could we do onward CHUNKING given inbound CHUNKING?
 */
 */
-if (cutthrough_fd >= 0)
+if (chunking_state > CHUNKING_OFFERED)
+  cancel_cutthrough_connection("chunking active");
+
+/* Cutthrough delivery:
+We have to create the Received header now rather than at the end of reception,
+so the timestamp behaviour is a change to the normal case.
+XXX Ensure this gets documented XXX.
+Having created it, send the headers to the destination. */
+if (cutthrough.fd >= 0)
   {
   if (received_count > received_headers_max)
     {
   {
   if (received_count > received_headers_max)
     {
@@ -2826,27 +2979,25 @@ if (cutthrough_fd >= 0)
     goto TIDYUP;                             /* Skip to end of function */
     }
   received_header_gen();
     goto TIDYUP;                             /* Skip to end of function */
     }
   received_header_gen();
-  add_acl_headers(US"MAIL or RCPT");
+  add_acl_headers(ACL_WHERE_RCPT, US"MAIL or RCPT");
   (void) cutthrough_headers_send();
   }
 
 
 /* Open a new spool file for the data portion of the message. We need
 to access it both via a file descriptor and a stream. Try to make the
   (void) cutthrough_headers_send();
   }
 
 
 /* Open a new spool file for the data portion of the message. We need
 to access it both via a file descriptor and a stream. Try to make the
-directory if it isn't there. Note re use of sprintf: spool_directory
-is checked on input to be < 200 characters long. */
+directory if it isn't there. */
+
+spool_name = spool_fname(US"input", message_subdir, message_id, US"-D");
+DEBUG(D_receive) debug_printf("Data file name: %s\n", spool_name);
 
 
-sprintf(CS spool_name, "%s/input/%s/%s-D", spool_directory, message_subdir,
-  message_id);
-data_fd = Uopen(spool_name, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE);
-if (data_fd < 0)
+if ((data_fd = Uopen(spool_name, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE)) < 0)
   {
   if (errno == ENOENT)
     {
   {
   if (errno == ENOENT)
     {
-    uschar temp[16];
-    sprintf(CS temp, "input/%s", message_subdir);
-    if (message_subdir[0] == 0) temp[5] = 0;
-    (void)directory_make(spool_directory, temp, INPUT_DIRECTORY_MODE, TRUE);
+    (void) directory_make(spool_directory,
+                       spool_sname(US"input", message_subdir),
+                       INPUT_DIRECTORY_MODE, TRUE);
     data_fd = Uopen(spool_name, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE);
     }
   if (data_fd < 0)
     data_fd = Uopen(spool_name, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE);
     }
   if (data_fd < 0)
@@ -2902,7 +3053,9 @@ if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT)
   {
   if (smtp_input)
     {
   {
   if (smtp_input)
     {
-    message_ended = read_message_data_smtp(data_file);
+    message_ended = chunking_state > CHUNKING_OFFERED
+      ? read_message_bdat_smtp(data_file)
+      : read_message_data_smtp(data_file);
     receive_linecount++;                /* The terminating "." line */
     }
   else message_ended = read_message_data(data_file);
     receive_linecount++;                /* The terminating "." line */
     }
   else message_ended = read_message_data(data_file);
@@ -2910,51 +3063,64 @@ if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT)
   receive_linecount += body_linecount;  /* For BSMTP errors mainly */
   message_linecount += body_linecount;
 
   receive_linecount += body_linecount;  /* For BSMTP errors mainly */
   message_linecount += body_linecount;
 
-  /* Handle premature termination of SMTP */
-
-  if (smtp_input && message_ended == END_EOF)
+  switch (message_ended)
     {
     {
-    Uunlink(spool_name);                     /* Lose data file when closed */
-    cancel_cutthrough_connection("sender closed connection");
-    message_id[0] = 0;                       /* Indicate no message accepted */
-    smtp_reply = handle_lost_connection(US"");
-    smtp_yield = FALSE;
-    goto TIDYUP;                             /* Skip to end of function */
-    }
+    /* Handle premature termination of SMTP */
 
 
-  /* Handle message that is too big. Don't use host_or_ident() in the log
-  message; we want to see the ident value even for non-remote messages. */
+    case END_EOF:
+      if (smtp_input)
+       {
+       Uunlink(spool_name);                 /* Lose data file when closed */
+       cancel_cutthrough_connection("sender closed connection");
+       message_id[0] = 0;                   /* Indicate no message accepted */
+       smtp_reply = handle_lost_connection(US"");
+       smtp_yield = FALSE;
+       goto TIDYUP;                         /* Skip to end of function */
+       }
+      break;
 
 
-  if (message_ended == END_SIZE)
-    {
-    Uunlink(spool_name);                /* Lose the data file when closed */
-    cancel_cutthrough_connection("mail too big");
-    if (smtp_input) receive_swallow_smtp();  /* Swallow incoming SMTP */
+    /* Handle message that is too big. Don't use host_or_ident() in the log
+    message; we want to see the ident value even for non-remote messages. */
 
 
-    log_write(L_size_reject, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: "
-      "message too big: read=%d max=%d",
-      sender_address,
-      (sender_fullhost == NULL)? "" : " H=",
-      (sender_fullhost == NULL)? US"" : sender_fullhost,
-      (sender_ident == NULL)? "" : " U=",
-      (sender_ident == NULL)? US"" : sender_ident,
-      message_size,
-      thismessage_size_limit);
+    case END_SIZE:
+      Uunlink(spool_name);                /* Lose the data file when closed */
+      cancel_cutthrough_connection("mail too big");
+      if (smtp_input) receive_swallow_smtp();  /* Swallow incoming SMTP */
 
 
-    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 */
-      }
-    else
-      {
-      fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
-      give_local_error(ERRMESS_TOOBIG,
-        string_sprintf("message too big (max=%d)", thismessage_size_limit),
-        US"message rejected: ", error_rc, data_file, header_list);
-      /* Does not return */
-      }
+      log_write(L_size_reject, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: "
+       "message too big: read=%d max=%d",
+       sender_address,
+       (sender_fullhost == NULL)? "" : " H=",
+       (sender_fullhost == NULL)? US"" : sender_fullhost,
+       (sender_ident == NULL)? "" : " U=",
+       (sender_ident == NULL)? US"" : sender_ident,
+       message_size,
+       thismessage_size_limit);
+
+      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 */
+       }
+      else
+       {
+       fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+       give_local_error(ERRMESS_TOOBIG,
+         string_sprintf("message too big (max=%d)", thismessage_size_limit),
+         US"message rejected: ", error_rc, data_file, header_list);
+       /* Does not return */
+       }
+      break;
+
+    /* Handle bad BDAT protocol sequence */
+
+    case END_PROTOCOL:
+      Uunlink(spool_name);             /* Lose the data file when closed */
+      cancel_cutthrough_connection("sender protocol error");
+      smtp_reply = US"";               /* Response already sent */
+      message_id[0] = 0;               /* Indicate no message accepted */
+      goto TIDYUP;                     /* Skip to end of function */
     }
   }
 
     }
   }
 
@@ -3118,7 +3284,7 @@ if (received_header->text == NULL)        /* Non-cutthrough case */
   /* If an ACL from any RCPT commands set up any warning headers to add, do so
   now, before running the DATA ACL. */
 
   /* If an ACL from any RCPT commands set up any warning headers to add, do so
   now, before running the DATA ACL. */
 
-  add_acl_headers(US"MAIL or RCPT");
+  add_acl_headers(ACL_WHERE_RCPT, US"MAIL or RCPT");
   }
 else
   message_body_size = (fstat(data_fd, &statbuf) == 0)?
   }
 else
   message_body_size = (fstat(data_fd, &statbuf) == 0)?
@@ -3137,9 +3303,8 @@ user_msg = NULL;
 enable_dollar_recipients = TRUE;
 
 if (recipients_count == 0)
 enable_dollar_recipients = TRUE;
 
 if (recipients_count == 0)
-  {
-  blackholed_by = recipients_discarded? US"MAIL ACL" : US"RCPT ACL";
-  }
+  blackholed_by = recipients_discarded ? US"MAIL ACL" : US"RCPT ACL";
+
 else
   {
   /* Handle interactive SMTP messages */
 else
   {
   /* Handle interactive SMTP messages */
@@ -3155,83 +3320,80 @@ else
       dkim_exim_verify_finish();
 
       /* Check if we must run the DKIM ACL */
       dkim_exim_verify_finish();
 
       /* Check if we must run the DKIM ACL */
-      if ((acl_smtp_dkim != NULL) &&
-          (dkim_verify_signers != NULL) &&
-          (dkim_verify_signers[0] != '\0'))
+      if (acl_smtp_dkim && dkim_verify_signers && *dkim_verify_signers)
         {
         uschar *dkim_verify_signers_expanded =
           expand_string(dkim_verify_signers);
         {
         uschar *dkim_verify_signers_expanded =
           expand_string(dkim_verify_signers);
-        if (dkim_verify_signers_expanded == NULL)
-          {
+        if (!dkim_verify_signers_expanded)
           log_write(0, LOG_MAIN|LOG_PANIC,
             "expansion of dkim_verify_signers option failed: %s",
             expand_string_message);
           log_write(0, LOG_MAIN|LOG_PANIC,
             "expansion of dkim_verify_signers option failed: %s",
             expand_string_message);
-          }
+
         else
           {
           int sep = 0;
         else
           {
           int sep = 0;
-          uschar *ptr = dkim_verify_signers_expanded;
+          const uschar *ptr = dkim_verify_signers_expanded;
           uschar *item = NULL;
           uschar *seen_items = NULL;
           int     seen_items_size = 0;
           int     seen_items_offset = 0;
           uschar *item = NULL;
           uschar *seen_items = NULL;
           int     seen_items_size = 0;
           int     seen_items_offset = 0;
-          uschar itembuf[256];
           /* Default to OK when no items are present */
           rc = OK;
           /* Default to OK when no items are present */
           rc = OK;
-          while ((item = string_nextinlist(&ptr, &sep,
-                                           itembuf,
-                                           sizeof(itembuf))) != NULL)
+          while ((item = string_nextinlist(&ptr, &sep, NULL, 0)))
             {
             /* Prevent running ACL for an empty item */
             {
             /* Prevent running ACL for an empty item */
-            if (!item || (item[0] == '\0')) continue;
-            /* Only run ACL once for each domain or identity, no matter how often it
-               appears in the expanded list. */
-            if (seen_items != NULL)
+            if (!item || !*item) continue;
+
+            /* Only run ACL once for each domain or identity,
+           no matter how often it appears in the expanded list. */
+            if (seen_items)
               {
               uschar *seen_item = NULL;
               {
               uschar *seen_item = NULL;
-              uschar seen_item_buf[256];
-              uschar *seen_items_list = seen_items;
-              int seen_this_item = 0;
+              const uschar *seen_items_list = seen_items;
+              BOOL seen_this_item = FALSE;
 
               while ((seen_item = string_nextinlist(&seen_items_list, &sep,
 
               while ((seen_item = string_nextinlist(&seen_items_list, &sep,
-                                                    seen_item_buf,
-                                                    sizeof(seen_item_buf))) != NULL)
-                {
-                  if (Ustrcmp(seen_item,item) == 0)
-                    {
-                      seen_this_item = 1;
-                      break;
-                    }
-                }
-
-              if (seen_this_item > 0)
+                                                    NULL, 0)))
+               if (Ustrcmp(seen_item,item) == 0)
+                 {
+                 seen_this_item = TRUE;
+                 break;
+                 }
+
+              if (seen_this_item)
                 {
                 DEBUG(D_receive)
                 {
                 DEBUG(D_receive)
-                  debug_printf("acl_smtp_dkim: skipping signer %s, already seen\n", item);
+                  debug_printf("acl_smtp_dkim: skipping signer %s, "
+                   "already seen\n", item);
                 continue;
                 }
 
                 continue;
                 }
 
-              seen_items = string_append(seen_items,&seen_items_size,&seen_items_offset,1,":");
+              seen_items = string_append(seen_items, &seen_items_size,
+               &seen_items_offset, 1, ":");
               }
 
               }
 
-            seen_items = string_append(seen_items,&seen_items_size,&seen_items_offset,1,item);
+            seen_items = string_append(seen_items, &seen_items_size,
+             &seen_items_offset, 1, item);
             seen_items[seen_items_offset] = '\0';
 
             DEBUG(D_receive)
             seen_items[seen_items_offset] = '\0';
 
             DEBUG(D_receive)
-              debug_printf("calling acl_smtp_dkim for dkim_cur_signer=%s\n", item);
+              debug_printf("calling acl_smtp_dkim for dkim_cur_signer=%s\n",
+               item);
 
             dkim_exim_acl_setup(item);
 
             dkim_exim_acl_setup(item);
-            rc = acl_check(ACL_WHERE_DKIM, NULL, acl_smtp_dkim, &user_msg, &log_msg);
+            rc = acl_check(ACL_WHERE_DKIM, NULL, acl_smtp_dkim,
+                 &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("dkim acl not ok");
-                break;
-              }
+             {
+             DEBUG(D_receive)
+               debug_printf("acl_smtp_dkim: acl_check returned %d on %s, "
+                 "skipping remaining items\n", rc, item);
+             cancel_cutthrough_connection("dkim acl not ok");
+             break;
+             }
             }
             }
-          add_acl_headers(US"DKIM");
+          add_acl_headers(ACL_WHERE_DKIM, US"DKIM");
           if (rc == DISCARD)
             {
             recipients_count = 0;
           if (rc == DISCARD)
             {
             recipients_count = 0;
@@ -3243,7 +3405,7 @@ else
             {
             Uunlink(spool_name);
             if (smtp_handle_acl_fail(ACL_WHERE_DKIM, rc, user_msg, log_msg) != 0)
             {
             Uunlink(spool_name);
             if (smtp_handle_acl_fail(ACL_WHERE_DKIM, rc, user_msg, log_msg) != 0)
-              smtp_yield = FALSE;    /* No more messsages after dropped connection */
+              smtp_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 */
             smtp_reply = US"";       /* Indicate reply already sent */
             message_id[0] = 0;       /* Indicate no message accepted */
             goto TIDYUP;             /* Skip to end of function */
@@ -3264,8 +3426,8 @@ else
     dmarc_up = dmarc_store_data(from_header);
 #endif /* EXPERIMENTAL_DMARC */
 
     dmarc_up = dmarc_store_data(from_header);
 #endif /* EXPERIMENTAL_DMARC */
 
-#ifdef EXPERIMENTAL_PRDR
-    if (prdr_requested && recipients_count > 1 && acl_smtp_data_prdr != NULL )
+#ifndef DISABLE_PRDR
+    if (prdr_requested && recipients_count > 1 && acl_smtp_data_prdr)
       {
       unsigned int c;
       int all_pass = OK;
       {
       unsigned int c;
       int all_pass = OK;
@@ -3333,7 +3495,7 @@ else
       }
     else
       prdr_requested = FALSE;
       }
     else
       prdr_requested = FALSE;
-#endif /* EXPERIMENTAL_PRDR */
+#endif /* !DISABLE_PRDR */
 
     /* 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. */
@@ -3341,7 +3503,7 @@ else
     if (acl_smtp_data != NULL && recipients_count > 0)
       {
       rc = acl_check(ACL_WHERE_DATA, NULL, acl_smtp_data, &user_msg, &log_msg);
     if (acl_smtp_data != NULL && recipients_count > 0)
       {
       rc = acl_check(ACL_WHERE_DATA, NULL, acl_smtp_data, &user_msg, &log_msg);
-      add_acl_headers(US"DATA");
+      add_acl_headers(ACL_WHERE_DATA, US"DATA");
       if (rc == DISCARD)
         {
         recipients_count = 0;
       if (rc == DISCARD)
         {
         recipients_count = 0;
@@ -3361,7 +3523,7 @@ else
        dcc_ok = 0;
 #endif
         if (smtp_handle_acl_fail(ACL_WHERE_DATA, rc, user_msg, log_msg) != 0)
        dcc_ok = 0;
 #endif
         if (smtp_handle_acl_fail(ACL_WHERE_DATA, rc, user_msg, log_msg) != 0)
-          smtp_yield = FALSE;    /* No more messsages after dropped connection */
+          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 */
         smtp_reply = US"";       /* Indicate reply already sent */
         message_id[0] = 0;       /* Indicate no message accepted */
         goto TIDYUP;             /* Skip to end of function */
@@ -3424,7 +3586,7 @@ else
           /* Does not return */
           }
         }
           /* Does not return */
           }
         }
-      add_acl_headers(US"non-SMTP");
+      add_acl_headers(ACL_WHERE_NOTSMTP, US"non-SMTP");
       }
     }
 
       }
     }
 
@@ -3552,7 +3714,7 @@ else
     goto TEMPREJECT;
 
     case LOCAL_SCAN_REJECT_NOLOGHDR:
     goto TEMPREJECT;
 
     case LOCAL_SCAN_REJECT_NOLOGHDR:
-    log_extra_selector &= ~LX_rejected_header;
+    BIT_CLEAR(log_selector, log_selector_size, Li_rejected_header);
     /* Fall through */
 
     case LOCAL_SCAN_REJECT:
     /* Fall through */
 
     case LOCAL_SCAN_REJECT:
@@ -3561,7 +3723,7 @@ else
     break;
 
     case LOCAL_SCAN_TEMPREJECT_NOLOGHDR:
     break;
 
     case LOCAL_SCAN_TEMPREJECT_NOLOGHDR:
-    log_extra_selector &= ~LX_rejected_header;
+    BIT_CLEAR(log_selector, log_selector_size, Li_rejected_header);
     /* Fall through */
 
     case LOCAL_SCAN_TEMPREJECT:
     /* Fall through */
 
     case LOCAL_SCAN_TEMPREJECT:
@@ -3617,14 +3779,14 @@ signal(SIGINT, SIG_IGN);
 deliver_firsttime = TRUE;
 
 #ifdef EXPERIMENTAL_BRIGHTMAIL
 deliver_firsttime = TRUE;
 
 #ifdef EXPERIMENTAL_BRIGHTMAIL
-if (bmi_run == 1) {
-  /* rewind data file */
+if (bmi_run == 1)
+  /* rewind data file */
   lseek(data_fd, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
   bmi_verdicts = bmi_process_message(header_list, data_fd);
   lseek(data_fd, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
   bmi_verdicts = bmi_process_message(header_list, data_fd);
-};
+  }
 #endif
 
 #endif
 
-/* Update the timstamp in our Received: header to account for any time taken by
+/* Update the timestamp in our Received: header to account for any time taken by
 an ACL or by local_scan(). The new time is the time that all reception
 processing is complete. */
 
 an ACL or by local_scan(). The new time is the time that all reception
 processing is complete. */
 
@@ -3659,7 +3821,6 @@ if (host_checking || blackholed_by != NULL)
 /* Write the -H file */
 
 else
 /* Write the -H file */
 
 else
-  {
   if ((msg_size = spool_write_header(message_id, SW_RECEIVING, &errmsg)) < 0)
     {
     log_write(0, LOG_MAIN, "Message abandoned: %s", errmsg);
   if ((msg_size = spool_write_header(message_id, SW_RECEIVING, &errmsg)) < 0)
     {
     log_write(0, LOG_MAIN, "Message abandoned: %s", errmsg);
@@ -3679,7 +3840,6 @@ else
       /* Does not return */
       }
     }
       /* Does not return */
       }
     }
-  }
 
 
 /* The message has now been successfully received. */
 
 
 /* The message has now been successfully received. */
@@ -3711,51 +3871,59 @@ string as required. Since we commonly want to add two items at a time, use a
 macro to simplify the coding. We log the arrival of a new message while the
 file is still locked, just in case the machine is *really* fast, and delivers
 it first! Include any message id that is in the message - since the syntax of a
 macro to simplify the coding. We log the arrival of a new message while the
 file is still locked, just in case the machine is *really* fast, and delivers
 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 canonicize
+message id is actually an addr-spec, we can use the parse routine to canonicalize
 it. */
 
 size = 256;
 sptr = 0;
 s = store_get(size);
 
 it. */
 
 size = 256;
 sptr = 0;
 s = store_get(size);
 
-s = string_append(s, &size, &sptr, 2, US"<= ",
-  (sender_address[0] == 0)? US"<>" : sender_address);
-if (message_reference != NULL)
+s = string_append(s, &size, &sptr, 2,
+  fake_response == FAIL ? US"(= " : US"<= ",
+  sender_address[0] == 0 ? US"<>" : sender_address);
+if (message_reference)
   s = string_append(s, &size, &sptr, 2, US" R=", message_reference);
 
 s = add_host_info_for_log(s, &size, &sptr);
 
 #ifdef SUPPORT_TLS
   s = string_append(s, &size, &sptr, 2, US" R=", message_reference);
 
 s = add_host_info_for_log(s, &size, &sptr);
 
 #ifdef SUPPORT_TLS
-if ((log_extra_selector & LX_tls_cipher) != 0 && tls_in.cipher != NULL)
+if (LOGGING(tls_cipher) && tls_in.cipher)
   s = string_append(s, &size, &sptr, 2, US" X=", tls_in.cipher);
   s = string_append(s, &size, &sptr, 2, US" X=", tls_in.cipher);
-if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
-     tls_in.cipher != NULL)
+if (LOGGING(tls_certificate_verified) && tls_in.cipher)
   s = string_append(s, &size, &sptr, 2, US" CV=",
   s = string_append(s, &size, &sptr, 2, US" CV=",
-    tls_in.certificate_verified? "yes":"no");
-if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_in.peerdn != NULL)
+    tls_in.certificate_verified ? "yes":"no");
+if (LOGGING(tls_peerdn) && tls_in.peerdn)
   s = string_append(s, &size, &sptr, 3, US" DN=\"",
     string_printing(tls_in.peerdn), US"\"");
   s = string_append(s, &size, &sptr, 3, US" DN=\"",
     string_printing(tls_in.peerdn), US"\"");
-if ((log_extra_selector & LX_tls_sni) != 0 && tls_in.sni != NULL)
+if (LOGGING(tls_sni) && tls_in.sni)
   s = string_append(s, &size, &sptr, 3, US" SNI=\"",
     string_printing(tls_in.sni), US"\"");
 #endif
 
   s = string_append(s, &size, &sptr, 3, US" SNI=\"",
     string_printing(tls_in.sni), US"\"");
 #endif
 
-if (sender_host_authenticated != NULL)
+if (sender_host_authenticated)
   {
   s = string_append(s, &size, &sptr, 2, US" A=", sender_host_authenticated);
   {
   s = string_append(s, &size, &sptr, 2, US" A=", sender_host_authenticated);
-  if (authenticated_id != NULL)
+  if (authenticated_id)
     {
     s = string_append(s, &size, &sptr, 2, US":", authenticated_id);
     {
     s = string_append(s, &size, &sptr, 2, US":", authenticated_id);
-    if (log_extra_selector & LX_smtp_mailauth  &&  authenticated_sender != NULL)
+    if (LOGGING(smtp_mailauth) && authenticated_sender)
       s = string_append(s, &size, &sptr, 2, US":", authenticated_sender);
     }
   }
 
       s = string_append(s, &size, &sptr, 2, US":", authenticated_sender);
     }
   }
 
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
 if (prdr_requested)
 if (prdr_requested)
-  s = string_append(s, &size, &sptr, 1, US" PRDR");
+  s = string_catn(s, &size, &sptr, US" PRDR", 5);
 #endif
 
 #endif
 
+#ifdef SUPPORT_PROXY
+if (proxy_session && LOGGING(proxy))
+  s = string_append(s, &size, &sptr, 2, US" PRX=", proxy_local_address);
+#endif
+
+if (chunking_state > CHUNKING_OFFERED)
+  s = string_catn(s, &size, &sptr, US" K", 2);
+
 sprintf(CS big_buffer, "%d", msg_size);
 s = string_append(s, &size, &sptr, 2, US" S=", big_buffer);
 
 sprintf(CS big_buffer, "%d", msg_size);
 s = string_append(s, &size, &sptr, 2, US" S=", big_buffer);
 
@@ -3763,18 +3931,21 @@ s = string_append(s, &size, &sptr, 2, US" S=", big_buffer);
    0 ... no BODY= used
    7 ... 7BIT
    8 ... 8BITMIME */
    0 ... no BODY= used
    7 ... 7BIT
    8 ... 8BITMIME */
-if (log_extra_selector & LX_8bitmime)
+if (LOGGING(8bitmime))
   {
   sprintf(CS big_buffer, "%d", body_8bitmime);
   s = string_append(s, &size, &sptr, 2, US" M8S=", big_buffer);
   }
 
   {
   sprintf(CS big_buffer, "%d", body_8bitmime);
   s = string_append(s, &size, &sptr, 2, US" M8S=", big_buffer);
   }
 
+if (*queue_name)
+  s = string_append(s, &size, &sptr, 2, US" Q=", queue_name);
+
 /* If an addr-spec in a message-id contains a quoted string, it can contain
 any characters except " \ and CR and so in particular it can contain NL!
 Therefore, make sure we use a printing-characters only version for the log.
 Also, allow for domain literals in the message id. */
 
 /* If an addr-spec in a message-id contains a quoted string, it can contain
 any characters except " \ and CR and so in particular it can contain NL!
 Therefore, make sure we use a printing-characters only version for the log.
 Also, allow for domain literals in the message id. */
 
-if (msgid_header != NULL)
+if (msgid_header)
   {
   uschar *old_id;
   BOOL save_allow_domain_literals = allow_domain_literals;
   {
   uschar *old_id;
   BOOL save_allow_domain_literals = allow_domain_literals;
@@ -3789,7 +3960,7 @@ if (msgid_header != NULL)
 /* If subject logging is turned on, create suitable printing-character
 text. By expanding $h_subject: we make use of the MIME decoding. */
 
 /* If subject logging is turned on, create suitable printing-character
 text. By expanding $h_subject: we make use of the MIME decoding. */
 
-if ((log_extra_selector & LX_subject) != 0 && subject_header != NULL)
+if (LOGGING(subject) && subject_header != NULL)
   {
   int i;
   uschar *p = big_buffer;
   {
   int i;
   uschar *p = big_buffer;
@@ -3823,16 +3994,15 @@ if (message_logs && blackholed_by == NULL)
   {
   int fd;
 
   {
   int fd;
 
-  sprintf(CS spool_name, "%s/msglog/%s/%s", spool_directory, message_subdir,
-    message_id);
-  fd = Uopen(spool_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE);
-
-  if (fd < 0 && errno == ENOENT)
+  spool_name = spool_fname(US"msglog", message_subdir, message_id, US"");
+  
+  if (  (fd = Uopen(spool_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE)) < 0
+     && errno == ENOENT
+     )
     {
     {
-    uschar temp[16];
-    sprintf(CS temp, "msglog/%s", message_subdir);
-    if (message_subdir[0] == 0) temp[6] = 0;
-    (void)directory_make(spool_directory, temp, MSGLOG_DIRECTORY_MODE, TRUE);
+    (void)directory_make(spool_directory,
+                       spool_sname(US"msglog", message_subdir),
+                       MSGLOG_DIRECTORY_MODE, TRUE);
     fd = Uopen(spool_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE);
     }
 
     fd = Uopen(spool_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE);
     }
 
@@ -3858,7 +4028,9 @@ if (message_logs && blackholed_by == NULL)
       if (deliver_freeze) fprintf(message_log, "%s frozen by %s\n", now,
         frozen_by);
       if (queue_only_policy) fprintf(message_log,
       if (deliver_freeze) fprintf(message_log, "%s frozen by %s\n", now,
         frozen_by);
       if (queue_only_policy) fprintf(message_log,
-        "%s no immediate delivery: queued by %s\n", now, queued_by);
+        "%s no immediate delivery: queued%s%s by %s\n", now,
+        *queue_name ? " in " : "", *queue_name ? CS queue_name : "",
+       queued_by);
       (void)fclose(message_log);
       }
     }
       (void)fclose(message_log);
       }
     }
@@ -3900,34 +4072,26 @@ if (smtp_input && sender_host_address != NULL && !sender_host_notsocket &&
 
   if (select(fileno(smtp_in) + 1, &select_check, NULL, NULL, &tv) != 0)
     {
 
   if (select(fileno(smtp_in) + 1, &select_check, NULL, NULL, &tv) != 0)
     {
-    int c = (receive_getc)();
+    int c = (receive_getc)(GETC_BUFFER_UNLIMITED);
     if (c != EOF) (receive_ungetc)(c); else
       {
     if (c != EOF) (receive_ungetc)(c); else
       {
-      uschar *msg = US"SMTP connection lost after final dot";
+      smtp_notquit_exit(US"connection-lost", NULL, NULL);
       smtp_reply = US"";    /* No attempt to send a response */
       smtp_yield = FALSE;   /* Nothing more on this connection */
 
       /* Re-use the log line workspace */
 
       sptr = 0;
       smtp_reply = US"";    /* No attempt to send a response */
       smtp_yield = FALSE;   /* Nothing more on this connection */
 
       /* Re-use the log line workspace */
 
       sptr = 0;
-      s = string_cat(s, &size, &sptr, msg, Ustrlen(msg));
+      s = string_cat(s, &size, &sptr, US"SMTP connection lost after final dot");
       s = add_host_info_for_log(s, &size, &sptr);
       s[sptr] = 0;
       log_write(0, LOG_MAIN, "%s", s);
 
       /* Delete the files for this aborted message. */
 
       s = add_host_info_for_log(s, &size, &sptr);
       s[sptr] = 0;
       log_write(0, LOG_MAIN, "%s", s);
 
       /* Delete the files for this aborted message. */
 
-      sprintf(CS spool_name, "%s/input/%s/%s-D", spool_directory,
-        message_subdir, message_id);
-      Uunlink(spool_name);
-
-      sprintf(CS spool_name, "%s/input/%s/%s-H", spool_directory,
-        message_subdir, message_id);
-      Uunlink(spool_name);
-
-      sprintf(CS spool_name, "%s/msglog/%s/%s", spool_directory,
-        message_subdir, message_id);
-      Uunlink(spool_name);
+      Uunlink(spool_fname(US"input", message_subdir, message_id, US"-D"));
+      Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H"));
+      Uunlink(spool_fname(US"msglog", message_subdir, message_id, US""));
 
       goto TIDYUP;
       }
 
       goto TIDYUP;
       }
@@ -3942,17 +4106,17 @@ for this message. */
 
    Send dot onward.  If accepted, wipe the spooled files, log as delivered and accept
    the sender's dot (below).
 
    Send dot onward.  If accepted, wipe the spooled files, log as delivered and accept
    the sender's dot (below).
-   If rejected: copy response to sender, wipe the spooled files, log approriately.
-   If temp-reject: accept to sender, keep the spooled files.
+   If rejected: copy response to sender, wipe the spooled files, log appropriately.
+   If temp-reject: normally accept to sender, keep the spooled file - unless defer=pass
+   in which case pass temp-reject back to initiator and dump the files.
 
    Having the normal spool files lets us do data-filtering, and store/forward on temp-reject.
 
    XXX We do not handle queue-only, freezing, or blackholes.
 */
 
    Having the normal spool files lets us do data-filtering, and store/forward on temp-reject.
 
    XXX We do not handle queue-only, freezing, or blackholes.
 */
-cutthrough_done = NOT_TRIED;
-if(cutthrough_fd >= 0)
+if(cutthrough.fd >= 0)
   {
   {
-  uschar * msg= cutthrough_finaldot(); /* Ask the target system to accept the messsage */
+  uschar * msg= cutthrough_finaldot(); /* Ask the target system to accept the message */
                                        /* Logging was done in finaldot() */
   switch(msg[0])
     {
                                        /* Logging was done in finaldot() */
   switch(msg[0])
     {
@@ -3960,34 +4124,40 @@ if(cutthrough_fd >= 0)
       cutthrough_done = ACCEPTED;
       break;                                   /* message_id needed for SMTP accept below */
 
       cutthrough_done = ACCEPTED;
       break;                                   /* message_id needed for SMTP accept below */
 
+    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);
+      /*FALLTRHOUGH*/
+
     default:   /* Unknown response, or error.  Treat as temp-reject.         */
     default:   /* Unknown response, or error.  Treat as temp-reject.         */
-    case '4':  /* Temp-reject. Keep spoolfiles and accept. */
       cutthrough_done = TMP_REJ;               /* Avoid the usual immediate delivery attempt */
       break;                                   /* message_id needed for SMTP accept below */
 
     case '5':  /* Perm-reject.  Do the same to the source.  Dump any spoolfiles */
       cutthrough_done = TMP_REJ;               /* Avoid the usual immediate delivery attempt */
       break;                                   /* message_id needed for SMTP accept below */
 
     case '5':  /* Perm-reject.  Do the same to the source.  Dump any spoolfiles */
-      smtp_reply= msg;         /* Pass on the exact error */
+      smtp_reply = string_copy_malloc(msg);            /* Pass on the exact error */
       cutthrough_done = PERM_REJ;
       break;
     }
   }
 
       cutthrough_done = PERM_REJ;
       break;
     }
   }
 
-if(smtp_reply == NULL
-#ifdef EXPERIMENTAL_PRDR
-                     || prdr_requested
+#ifndef DISABLE_PRDR
+if(!smtp_reply || prdr_requested)
+#else
+if(!smtp_reply)
 #endif
 #endif
-  )
   {
   log_write(0, LOG_MAIN |
   {
   log_write(0, LOG_MAIN |
-    (((log_extra_selector & LX_received_recipients) != 0)? LOG_RECIPIENTS : 0) |
-    (((log_extra_selector & LX_received_sender) != 0)? LOG_SENDER : 0),
+    (LOGGING(received_recipients)? LOG_RECIPIENTS : 0) |
+    (LOGGING(received_sender)? LOG_SENDER : 0),
     "%s", s);
 
   /* Log any control actions taken by an ACL or local_scan(). */
 
   if (deliver_freeze) log_write(0, LOG_MAIN, "frozen by %s", frozen_by);
   if (queue_only_policy) log_write(L_delay_delivery, LOG_MAIN,
     "%s", s);
 
   /* Log any control actions taken by an ACL or local_scan(). */
 
   if (deliver_freeze) log_write(0, LOG_MAIN, "frozen by %s", frozen_by);
   if (queue_only_policy) log_write(L_delay_delivery, LOG_MAIN,
-    "no immediate delivery: queued by %s", queued_by);
+    "no immediate delivery: queued%s%s by %s",
+    *queue_name ? " in " : "", *queue_name ? CS queue_name : "",       
+    queued_by);
   }
 receive_call_bombout = FALSE;
 
   }
 receive_call_bombout = FALSE;
 
@@ -4043,26 +4213,33 @@ if (smtp_input)
 
   if (!smtp_batched_input)
     {
 
   if (!smtp_batched_input)
     {
-    if (smtp_reply == NULL)
+    if (!smtp_reply)
       {
       if (fake_response != OK)
       {
       if (fake_response != OK)
-        smtp_respond((fake_response == DEFER)? US"450" : US"550", 3, TRUE,
-          fake_response_text);
+        smtp_respond(fake_response == DEFER ? US"450" : US"550",
+         3, TRUE, fake_response_text);
 
       /* An OK response is required; use "message" text if present. */
 
 
       /* An OK response is required; use "message" text if present. */
 
-      else if (user_msg != NULL)
+      else if (user_msg)
         {
         uschar *code = US"250";
         int len = 3;
         {
         uschar *code = US"250";
         int len = 3;
-        smtp_message_code(&code, &len, &user_msg, NULL);
+        smtp_message_code(&code, &len, &user_msg, NULL, TRUE);
         smtp_respond(code, len, TRUE, user_msg);
         }
 
       /* Default OK response */
 
         smtp_respond(code, len, TRUE, user_msg);
         }
 
       /* Default OK response */
 
+      else if (chunking_state > CHUNKING_OFFERED)
+       {
+        smtp_printf("250- %u byte chunk, total %d\r\n250 OK id=%s\r\n",
+           chunking_datasize, message_size+message_linecount, message_id);
+       chunking_state = CHUNKING_OFFERED;
+       }
       else
         smtp_printf("250 OK id=%s\r\n", message_id);
       else
         smtp_printf("250 OK id=%s\r\n", message_id);
+
       if (host_checking)
         fprintf(stdout,
           "\n**** SMTP testing: that is not a real message id!\n\n");
       if (host_checking)
         fprintf(stdout,
           "\n**** SMTP testing: that is not a real message id!\n\n");
@@ -4071,39 +4248,45 @@ if (smtp_input)
     /* smtp_reply is set non-empty */
 
     else if (smtp_reply[0] != 0)
     /* smtp_reply is set non-empty */
 
     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);
       else
         smtp_printf("%.1024s\r\n", smtp_reply);
       if (fake_response != OK && (smtp_reply[0] == '2'))
         smtp_respond((fake_response == DEFER)? US"450" : US"550", 3, TRUE,
           fake_response_text);
       else
         smtp_printf("%.1024s\r\n", smtp_reply);
-      }
 
     switch (cutthrough_done)
       {
 
     switch (cutthrough_done)
       {
-      case ACCEPTED: log_write(0, LOG_MAIN, "Completed");/* Delivery was done */
-      case PERM_REJ: {                                 /* Delete spool files */
-             sprintf(CS spool_name, "%s/input/%s/%s-D", spool_directory,
-               message_subdir, message_id);
-             Uunlink(spool_name);
-             sprintf(CS spool_name, "%s/input/%s/%s-H", spool_directory,
-               message_subdir, message_id);
-             Uunlink(spool_name);
-             sprintf(CS spool_name, "%s/msglog/%s/%s", spool_directory,
-               message_subdir, message_id);
-             Uunlink(spool_name);
-             }
-      case TMP_REJ: message_id[0] = 0;   /* Prevent a delivery from starting */
-      default:break;
+      case ACCEPTED:
+       log_write(0, LOG_MAIN, "Completed");/* Delivery was done */
+      case PERM_REJ:
+                                                        /* Delete spool files */
+       Uunlink(spool_fname(US"input", message_subdir, message_id, US"-D"));
+       Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H"));
+       Uunlink(spool_fname(US"msglog", message_subdir, message_id, US""));
+       message_id[0] = 0;        /* Prevent a delivery from starting */
+       break;
+
+      case TMP_REJ:
+       if (cutthrough.defer_pass)
+         {
+         Uunlink(spool_fname(US"input", message_subdir, message_id, US"-D"));
+         Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H"));
+         Uunlink(spool_fname(US"msglog", message_subdir, message_id, US""));
+         }
+       message_id[0] = 0;        /* Prevent a delivery from starting */
+      default:
+       break;
       }
       }
-    cutthrough_delivery = FALSE;
+    cutthrough.delivery = FALSE;
+    cutthrough.defer_pass = FALSE;
     }
 
   /* For batched SMTP, generate an error message on failure, and do
   nothing on success. The function moan_smtp_batch() does not return -
   it exits from the program with a non-zero return code. */
 
     }
 
   /* For batched SMTP, generate an error message on failure, and do
   nothing on success. The function moan_smtp_batch() does not return -
   it exits from the program with a non-zero return code. */
 
-  else if (smtp_reply != NULL) moan_smtp_batch(NULL, "%s", smtp_reply);
+  else if (smtp_reply)
+    moan_smtp_batch(NULL, "%s", smtp_reply);
   }
 
 
   }
 
 
@@ -4112,11 +4295,11 @@ file has already been unlinked, and the header file was never written to disk.
 We must now indicate that nothing was received, to prevent a delivery from
 starting. */
 
 We must now indicate that nothing was received, to prevent a delivery from
 starting. */
 
-if (blackholed_by != NULL)
+if (blackholed_by)
   {
   {
-  uschar *detail = (local_scan_data != NULL)?
-    string_printing(local_scan_data) :
-    string_sprintf("(%s discarded recipients)", blackholed_by);
+  const uschar *detail = local_scan_data
+    ? string_printing(local_scan_data)
+    string_sprintf("(%s discarded recipients)", blackholed_by);
   log_write(0, LOG_MAIN, "=> blackhole %s%s", detail, blackhole_log_msg);
   log_write(0, LOG_MAIN, "Completed");
   message_id[0] = 0;
   log_write(0, LOG_MAIN, "=> blackhole %s%s", detail, blackhole_log_msg);
   log_write(0, LOG_MAIN, "Completed");
   message_id[0] = 0;