commit 0111e9faaba8126eac5f50992968cce768050399
Author: Phil Pennock <pdp@exim.org>
Date:   Mon Apr 16 15:24:34 2018 -0400

    Belated README.UPDATING notes for Exim 4.91
    
    People skip versions and move past them later, so while it's too late
    for 4.91, this will still help people moving to 4.92 from pre-4.91 in
    future.
    
    Note that none of these strictly needed to be documented here:
    experimental features, features marked as deprecated for many many
    years, etc.  But let's err on the side of caution and include "things
    which will break if you try to upgrade without changing Local/Makefile".

diff --git a/src/README.UPDATING b/src/README.UPDATING
index 05b3d9d9..73b52e4a 100644
--- a/src/README.UPDATING
+++ b/src/README.UPDATING
@@ -26,6 +26,27 @@ The rest of this document contains information about changes in 4.xx releases
 that might affect a running system.
 
 
+Exim version 4.91
+-----------------
+
+ * DANE and SPF have been promoted from Experimental to Supported status, thus
+   the options to enable them in Local/Makefile have been renamed.
+   See current src/EDITME for full details, including changes in dependencies,
+   but loosely: replace EXPERIMENTAL_SPF with SUPPORT_SPF and replace
+   EXPERIMENTAL_DANE with SUPPORT_DANE.
+
+ * Ancient ClamAV stream support, long deprecated by ClamAV, has been removed;
+   if you were building with WITH_OLD_CLAMAV_STREAM enabled then your problems
+   have marginally increased.
+
+ * A number of logging changes; if relying upon the previous DKIM additional
+   log-line, explicit log_selector configuration is needed to keep it.
+
+ * Other incompatible changes in EXPERIMENTAL_* features, read NewStuff and
+   ChangeLog carefully if relying upon an experimental feature such as DMARC.
+   Note that this includes changes to SPF as it was promoted into Supported.
+
+
 Exim version 4.89
 -----------------
 

commit 101702337f7eb652fc99f440b64904b1e1629c2f
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Mon Apr 23 12:41:31 2018 +0100

            Avoid doing logging in signal-handlers.  Bug 1007
    
            Cherry-picked from 9723f96673, with the LOCAL_SCAN changes removed

diff --git a/src/src/functions.h b/src/src/functions.h
index 9be5c32a..5f9deab6 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -427,6 +427,10 @@ extern int     sieve_interpret(uschar *, int, uschar *, uschar *, uschar *,
 extern void    sigalrm_handler(int);
 extern BOOL    smtp_buffered(void);
 extern void    smtp_closedown(uschar *);
+extern void    smtp_command_timeout_exit(void);
+extern void    smtp_command_sigterm_exit(void);
+extern void    smtp_data_timeout_exit(void);
+extern void    smtp_data_sigint_exit(void);
 extern uschar *smtp_cmd_hist(void);
 extern int     smtp_connect(host_item *, int, uschar *, int,
 	       	 transport_instance *);
diff --git a/src/src/globals.c b/src/src/globals.c
index 7d18b38b..5b480572 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -770,6 +770,10 @@ uschar *gecos_name             = NULL;
 uschar *gecos_pattern          = NULL;
 rewrite_rule  *global_rewrite_rules = NULL;
 
+volatile sig_atomic_t had_command_timeout = 0;
+volatile sig_atomic_t had_command_sigterm = 0;
+volatile sig_atomic_t had_data_timeout    = 0;
+volatile sig_atomic_t had_data_sigint     = 0;
 uschar *headers_charset        = US HEADERS_CHARSET;
 int     header_insert_maxlen   = 64 * 1024;
 header_line  *header_last      = NULL;
diff --git a/src/src/globals.h b/src/src/globals.h
index da1230b7..2915513f 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -492,6 +492,10 @@ extern uschar *gecos_name;             /* To be expanded when pattern matches */
 extern uschar *gecos_pattern;          /* Pattern to match */
 extern rewrite_rule *global_rewrite_rules;  /* Chain of rewriting rules */
 
+extern volatile sig_atomic_t had_command_timeout;   /* Alarm sighandler called */
+extern volatile sig_atomic_t had_command_sigterm;   /* TERM  sighandler called */
+extern volatile sig_atomic_t had_data_timeout;      /* Alarm sighandler called */
+extern volatile sig_atomic_t had_data_sigint;       /* TERM/INT  sighandler called */
 extern int     header_insert_maxlen;   /* Max for inserting headers */
 extern int     header_maxsize;         /* Max total length for header */
 extern int     header_line_maxsize;    /* Max for an individual line */
diff --git a/src/src/local_scan.c b/src/src/local_scan.c
index 3500047c..4dd0b2ba 100644
--- a/src/src/local_scan.c
+++ b/src/src/local_scan.c
@@ -12,6 +12,7 @@ If you want to implement your own version, you should copy this file to, say
 Local/local_scan.c, and edit the copy. To use your version instead of the
 default, you must set
 
+HAVE_LOCAL_SCAN=yes
 LOCAL_SCAN_SOURCE=Local/local_scan.c
 
 in your Local/Makefile. This makes it easy to copy your version for use with
diff --git a/src/src/receive.c b/src/src/receive.c
index cba53c20..3b215f23 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -8,6 +8,7 @@
 /* Code for receiving a message and setting up spool files. */
 
 #include "exim.h"
+#include <setjmp.h>
 
 #ifdef EXPERIMENTAL_DCC
 extern int dcc_ok;
@@ -27,6 +28,10 @@ static uschar *spool_name = US"";
 
 enum CH_STATE {LF_SEEN, MID_LINE, CR_SEEN};
 
+jmp_buf local_scan_env;		/* error-handling context for local_scan */
+unsigned had_local_scan_crash;
+unsigned had_local_scan_timeout;
+
 
 /*************************************************
 *      Non-SMTP character reading functions      *
@@ -40,7 +45,27 @@ changing the pointer variables.) */
 int
 stdin_getc(unsigned lim)
 {
-return getc(stdin);
+int c = getc(stdin);
+
+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)
+  {
+  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 */
+  }
+return c;
 }
 
 int
@@ -316,11 +341,13 @@ if (spool_name[0] != '\0')
 
 /* Now close the file if it is open, either as a fd or a stream. */
 
-if (data_file != NULL)
+if (data_file)
   {
   (void)fclose(data_file);
   data_file = NULL;
-} else if (data_fd >= 0) {
+  }
+else if (data_fd >= 0)
+  {
   (void)close(data_fd);
   data_fd = -1;
   }
@@ -361,27 +388,7 @@ Returns:   nothing
 static void
 data_timeout_handler(int sig)
 {
-uschar *msg = NULL;
-
-sig = sig;    /* Keep picky compilers happy */
-
-if (smtp_input)
-  {
-  msg = US"SMTP incoming data timeout";
-  log_write(L_lost_incoming_connection,
-            LOG_MAIN, "SMTP data timeout (message abandoned) on connection "
-            "from %s F=<%s>",
-            (sender_fullhost != NULL)? sender_fullhost : US"local process",
-            sender_address);
-  }
-else
-  {
-  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", msg);   /* Does not return */
+had_data_timeout = sig;
 }
 
 
@@ -391,7 +398,18 @@ receive_bomb_out(US"data-timeout", msg);   /* Does not return */
 *************************************************/
 
 /* Handler function for timeouts that occur while running a local_scan()
-function.
+function.  Posix recommends against calling longjmp() from a signal-handler,
+but the GCC manual says you can so we will, and trust that it's better than
+calling probably non-signal-safe functions during logging from within the
+handler, even with other compilers.
+
+See also https://cwe.mitre.org/data/definitions/745.html which also lists
+it as unsafe.
+
+This is all because we have no control over what might be written for a
+local-scan function, so cannot sprinkle had-signal checks after each
+call-site.  At least with the default "do-nothing" function we won't
+ever get here.
 
 Argument:  the signal number
 Returns:   nothing
@@ -400,11 +418,8 @@ Returns:   nothing
 static void
 local_scan_timeout_handler(int sig)
 {
-sig = sig;    /* Keep picky compilers happy */
-log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() function timed out - "
-  "message temporarily rejected (size %d)", message_size);
-/* Does not return */
-receive_bomb_out(US"local-scan-timeout", US"local verification problem");
+had_local_scan_timeout = sig;
+siglongjmp(local_scan_env, 1);
 }
 
 
@@ -423,13 +438,12 @@ Returns:   nothing
 static void
 local_scan_crash_handler(int sig)
 {
-log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() function crashed with "
-  "signal %d - message temporarily rejected (size %d)", sig, message_size);
-/* Does not return */
-receive_bomb_out(US"local-scan-error", US"local verification problem");
+had_local_scan_crash = sig;
+siglongjmp(local_scan_env, 1);
 }
 
 
+
 /*************************************************
 *           SIGTERM or SIGINT received           *
 *************************************************/
@@ -444,26 +458,7 @@ Returns:   nothing
 static void
 data_sigterm_sigint_handler(int sig)
 {
-uschar *msg = NULL;
-
-if (smtp_input)
-  {
-  msg = US"Service not available - SIGTERM or SIGINT received";
-  log_write(0, LOG_MAIN, "%s closed after %s", smtp_get_connection_info(),
-    (sig == SIGTERM)? "SIGTERM" : "SIGINT");
-  }
-else
-  {
-  if (filter_test == FTEST_NONE)
-    {
-    fprintf(stderr, "\nexim: %s received - message abandoned\n",
-      (sig == SIGTERM)? "SIGTERM" : "SIGINT");
-    log_write(0, LOG_MAIN, "%s received while reading local message",
-      (sig == SIGTERM)? "SIGTERM" : "SIGINT");
-    }
-  }
-
-receive_bomb_out(US"signal-exit", msg);    /* Does not return */
+had_data_sigint = sig;
 }
 
 
@@ -1678,6 +1673,7 @@ int dmarc_up = 0;
 uschar *timestamp;
 int tslen;
 
+
 /* Release any open files that might have been cached while preparing to
 accept the message - e.g. by verifying addresses - because reading a message
 might take a fair bit of real time. */
@@ -1751,7 +1747,9 @@ received_time = message_id_tv;
 /* 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) os_non_restarting_signal(SIGALRM, data_timeout_handler);
+had_data_timeout = 0;
+if (smtp_input)
+  os_non_restarting_signal(SIGALRM, data_timeout_handler);
 
 /* If not SMTP input, timeout happens only if configured, and we just set a
 single timeout for the whole message. */
@@ -1764,6 +1762,7 @@ else if (receive_timeout > 0)
 
 /* SIGTERM and SIGINT are caught always. */
 
+had_data_sigint = 0;
 signal(SIGTERM, data_sigterm_sigint_handler);
 signal(SIGINT, data_sigterm_sigint_handler);
 
@@ -3638,43 +3637,65 @@ dcc_ok = 0;
 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. */
-/*XXS could we avoid this for the standard case, given that few people will use it? */
 
 lseek(data_fd, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
 
 /* Arrange to catch crashes in local_scan(), so that the -D file gets
 deleted, and the incident gets logged. */
 
-os_non_restarting_signal(SIGSEGV, local_scan_crash_handler);
-os_non_restarting_signal(SIGFPE, local_scan_crash_handler);
-os_non_restarting_signal(SIGILL, local_scan_crash_handler);
-os_non_restarting_signal(SIGBUS, local_scan_crash_handler);
-
-DEBUG(D_receive) debug_printf("calling local_scan(); timeout=%d\n",
-  local_scan_timeout);
-local_scan_data = NULL;
-
-os_non_restarting_signal(SIGALRM, local_scan_timeout_handler);
-if (local_scan_timeout > 0) alarm(local_scan_timeout);
-rc = local_scan(data_fd, &local_scan_data);
-alarm(0);
-os_non_restarting_signal(SIGALRM, sigalrm_handler);
-
-enable_dollar_recipients = FALSE;
-
-store_pool = POOL_MAIN;   /* In case changed */
-DEBUG(D_receive) debug_printf("local_scan() returned %d %s\n", rc,
-  local_scan_data);
-
-os_non_restarting_signal(SIGSEGV, SIG_DFL);
-os_non_restarting_signal(SIGFPE, SIG_DFL);
-os_non_restarting_signal(SIGILL, SIG_DFL);
-os_non_restarting_signal(SIGBUS, SIG_DFL);
+if (sigsetjmp(local_scan_env, 1) == 0)
+  {
+  had_local_scan_crash = 0;
+  os_non_restarting_signal(SIGSEGV, local_scan_crash_handler);
+  os_non_restarting_signal(SIGFPE, local_scan_crash_handler);
+  os_non_restarting_signal(SIGILL, local_scan_crash_handler);
+  os_non_restarting_signal(SIGBUS, local_scan_crash_handler);
+
+  DEBUG(D_receive) debug_printf("calling local_scan(); timeout=%d\n",
+    local_scan_timeout);
+  local_scan_data = NULL;
+
+  had_local_scan_timeout = 0;
+  os_non_restarting_signal(SIGALRM, local_scan_timeout_handler);
+  if (local_scan_timeout > 0) alarm(local_scan_timeout);
+  rc = local_scan(data_fd, &local_scan_data);
+  alarm(0);
+  os_non_restarting_signal(SIGALRM, sigalrm_handler);
+
+  enable_dollar_recipients = FALSE;
+
+  store_pool = POOL_MAIN;   /* In case changed */
+  DEBUG(D_receive) debug_printf("local_scan() returned %d %s\n", rc,
+    local_scan_data);
+
+  os_non_restarting_signal(SIGSEGV, SIG_DFL);
+  os_non_restarting_signal(SIGFPE, SIG_DFL);
+  os_non_restarting_signal(SIGILL, SIG_DFL);
+  os_non_restarting_signal(SIGBUS, SIG_DFL);
+  }
+else
+  {
+  if (had_local_scan_crash)
+    {
+    log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() function crashed with "
+      "signal %d - message temporarily rejected (size %d)",
+      had_local_scan_crash, message_size);
+    /* Does not return */
+    receive_bomb_out(US"local-scan-error", US"local verification problem");
+    }
+  if (had_local_scan_timeout)
+    {
+    log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() function timed out - "
+      "message temporarily rejected (size %d)", message_size);
+    /* Does not return */
+    receive_bomb_out(US"local-scan-timeout", US"local verification problem");
+    }
+  }
 
 /* The length check is paranoia against some runaway code, and also because
 (for a success return) lines in the spool file are read into big_buffer. */
 
-if (local_scan_data != NULL)
+if (local_scan_data)
   {
   int len = Ustrlen(local_scan_data);
   if (len > LOCAL_SCAN_MAX_RETURN) len = LOCAL_SCAN_MAX_RETURN;
@@ -3706,7 +3727,7 @@ the spool file gets corrupted. Ensure that all recipients are qualified. */
 
 if (rc == LOCAL_SCAN_ACCEPT)
   {
-  if (local_scan_data != NULL)
+  if (local_scan_data)
     {
     uschar *s;
     for (s = local_scan_data; *s != 0; s++) if (*s == '\n') *s = ' ';
@@ -4331,9 +4352,9 @@ starting. */
 
 if (blackholed_by)
   {
-  const uschar *detail = local_scan_data
-    ? 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;
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index 33d6d3cc..433c677e 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -424,6 +424,53 @@ log_write(L_smtp_incomplete_transaction, LOG_MAIN|LOG_SENDER|LOG_RECIPIENTS,
 
 
 
+void
+smtp_command_timeout_exit(void)
+{
+log_write(L_lost_incoming_connection,
+	  LOG_MAIN, "SMTP command timeout on%s connection from %s",
+	  tls_in.active >= 0 ? " TLS" : "", host_and_ident(FALSE));
+if (smtp_batched_input)
+  moan_smtp_batch(NULL, "421 SMTP command timeout"); /* Does not return */
+smtp_notquit_exit(US"command-timeout", US"421",
+  US"%s: SMTP command timeout - closing connection",
+  smtp_active_hostname);
+exim_exit(EXIT_FAILURE, US"receiving");
+}
+
+void
+smtp_command_sigterm_exit(void)
+{
+log_write(0, LOG_MAIN, "%s closed after SIGTERM", smtp_get_connection_info());
+if (smtp_batched_input)
+  moan_smtp_batch(NULL, "421 SIGTERM received");  /* Does not return */
+smtp_notquit_exit(US"signal-exit", US"421",
+  US"%s: Service not available - closing connection", smtp_active_hostname);
+exim_exit(EXIT_FAILURE, US"receiving");
+}
+
+void
+smtp_data_timeout_exit(void)
+{
+log_write(L_lost_incoming_connection,
+  LOG_MAIN, "SMTP data timeout (message abandoned) on connection from %s F=<%s>",
+  sender_fullhost ? sender_fullhost : US"local process", sender_address);
+receive_bomb_out(US"data-timeout", US"SMTP incoming data timeout");
+/* Does not return */
+}
+
+void
+smtp_data_sigint_exit(void)
+{
+log_write(0, LOG_MAIN, "%s closed after %s",
+  smtp_get_connection_info(), had_data_sigint == SIGTERM ? "SIGTERM":"SIGINT");
+receive_bomb_out(US"signal-exit",
+  US"Service not available - SIGTERM or SIGINT received");
+/* Does not return */
+}
+
+
+
 /* Refill the buffer, and notify DKIM verification code.
 Return false for error or EOF.
 */
@@ -441,18 +488,28 @@ Take care to not touch the safety NUL at the end of the buffer. */
 
 rc = read(fileno(smtp_in), smtp_inbuffer, MIN(IN_BUFFER_SIZE-1, lim));
 save_errno = errno;
-alarm(0);
+if (smtp_receive_timeout > 0) alarm(0);
 if (rc <= 0)
   {
   /* Must put the error text in fixed store, because this might be during
   header reading, where it releases unused store above the header. */
   if (rc < 0)
     {
+    if (had_command_timeout)		/* set by signal handler */
+      smtp_command_timeout_exit();	/* does not return */
+    if (had_command_sigterm)
+      smtp_command_sigterm_exit();
+    if (had_data_timeout)
+      smtp_data_timeout_exit();
+    if (had_data_sigint)
+      smtp_data_sigint_exit();
+
     smtp_had_error = save_errno;
     smtp_read_error = string_copy_malloc(
       string_sprintf(" (error: %s)", strerror(save_errno)));
     }
-  else smtp_had_eof = 1;
+  else
+    smtp_had_eof = 1;
   return FALSE;
   }
 #ifndef DISABLE_DKIM
@@ -914,16 +971,7 @@ Returns:  nothing
 static void
 command_timeout_handler(int sig)
 {
-sig = sig;    /* Keep picky compilers happy */
-log_write(L_lost_incoming_connection,
-          LOG_MAIN, "SMTP command timeout on%s connection from %s",
-          (tls_in.active >= 0)? " TLS" : "",
-          host_and_ident(FALSE));
-if (smtp_batched_input)
-  moan_smtp_batch(NULL, "421 SMTP command timeout");  /* Does not return */
-smtp_notquit_exit(US"command-timeout", US"421",
-  US"%s: SMTP command timeout - closing connection", smtp_active_hostname);
-exim_exit(EXIT_FAILURE, US"receiving");
+had_command_timeout = sig;
 }
 
 
@@ -941,13 +989,7 @@ Returns:  nothing
 static void
 command_sigterm_handler(int sig)
 {
-sig = sig;    /* Keep picky compilers happy */
-log_write(0, LOG_MAIN, "%s closed after SIGTERM", smtp_get_connection_info());
-if (smtp_batched_input)
-  moan_smtp_batch(NULL, "421 SIGTERM received");  /* Does not return */
-smtp_notquit_exit(US"signal-exit", US"421",
-  US"%s: Service not available - closing connection", smtp_active_hostname);
-exim_exit(EXIT_FAILURE, US"receiving");
+had_command_sigterm = sig;
 }
 
 
@@ -1507,6 +1549,7 @@ int ptr = 0;
 smtp_cmd_list *p;
 BOOL hadnull = FALSE;
 
+had_command_timeout = 0;
 os_non_restarting_signal(SIGALRM, command_timeout_handler);
 
 while ((c = (receive_getc)(buffer_lim)) != '\n' && c != EOF)
@@ -3811,6 +3854,7 @@ cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = TRUE;
 
 /* Set the local signal handler for SIGTERM - it tries to end off tidily */
 
+had_command_sigterm = 0;
 os_non_restarting_signal(SIGTERM, command_sigterm_handler);
 
 /* Batched SMTP is handled in a different function. */
diff --git a/src/src/spool_out.c b/src/src/spool_out.c
index 8bebf107..2aa60322 100644
--- a/src/src/spool_out.c
+++ b/src/src/spool_out.c
@@ -221,7 +221,7 @@ if (host_lookup_deferred) fprintf(f, "-host_lookup_deferred\n");
 if (host_lookup_failed) fprintf(f, "-host_lookup_failed\n");
 if (sender_local) fprintf(f, "-local\n");
 if (local_error_message) fprintf(f, "-localerror\n");
-if (local_scan_data != NULL) fprintf(f, "-local_scan %s\n", local_scan_data);
+if (local_scan_data) fprintf(f, "-local_scan %s\n", local_scan_data);
 #ifdef WITH_CONTENT_SCAN
 if (spam_bar)       fprintf(f,"-spam_bar %s\n",       spam_bar);
 if (spam_score)     fprintf(f,"-spam_score %s\n",     spam_score);
@@ -231,7 +231,7 @@ if (deliver_manual_thaw) fprintf(f, "-manual_thaw\n");
 if (sender_set_untrusted) fprintf(f, "-sender_set_untrusted\n");
 
 #ifdef EXPERIMENTAL_BRIGHTMAIL
-if (bmi_verdicts != NULL) fprintf(f, "-bmi_verdicts %s\n", bmi_verdicts);
+if (bmi_verdicts) fprintf(f, "-bmi_verdicts %s\n", bmi_verdicts);
 #endif
 
 #ifdef SUPPORT_TLS
diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c
index d7318827..35816cd6 100644
--- a/src/src/tls-gnu.c
+++ b/src/src/tls-gnu.c
@@ -2511,12 +2511,20 @@ sigalrm_seen = FALSE;
 if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
 inbytes = gnutls_record_recv(state->session, state->xfer_buffer,
   MIN(ssl_xfer_buffer_size, lim));
-alarm(0);
-
-/* Timeouts do not get this far; see command_timeout_handler().
-   A zero-byte return appears to mean that the TLS session has been
-   closed down, not that the socket itself has been closed down. Revert to
-   non-TLS handling. */
+if (smtp_receive_timeout > 0) alarm(0);
+
+if (had_command_timeout)		/* set by signal handler */
+  smtp_command_timeout_exit();		/* does not return */
+if (had_command_sigterm)
+  smtp_command_sigterm_exit();
+if (had_data_timeout)
+  smtp_data_timeout_exit();
+if (had_data_sigint)
+  smtp_data_sigint_exit();
+
+/* Timeouts do not get this far.  A zero-byte return appears to mean that the
+TLS session has been closed down, not that the socket itself has been closed
+down. Revert to non-TLS handling. */
 
 if (sigalrm_seen)
   {
diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c
index bfdfe211..fb59217d 100644
--- a/src/src/tls-openssl.c
+++ b/src/src/tls-openssl.c
@@ -2475,7 +2475,16 @@ if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
 inbytes = SSL_read(server_ssl, CS ssl_xfer_buffer,
 		  MIN(ssl_xfer_buffer_size, lim));
 error = SSL_get_error(server_ssl, inbytes);
-alarm(0);
+if (smtp_receive_timeout > 0) alarm(0);
+
+if (had_command_timeout)		/* set by signal handler */
+  smtp_command_timeout_exit();		/* does not return */
+if (had_command_sigterm)
+  smtp_command_sigterm_exit();
+if (had_data_timeout)
+  smtp_data_timeout_exit();
+if (had_data_sigint)
+  smtp_data_sigint_exit();
 
 /* SSL_ERROR_ZERO_RETURN appears to mean that the SSL session has been
 closed down, not that the socket itself has been closed down. Revert to

commit d862036b77ae978f3d105adb7c11f0795bbb2732
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Mon Apr 16 09:15:17 2018 +0100

    Fix typo in arc.  Bug 2262
    
    (cherry picked from commit 69a82da3e9018b082cee870030ecc557497301df)

diff --git a/src/src/arc.c b/src/src/arc.c
index 6f567dc5..557ea851 100644
--- a/src/src/arc.c
+++ b/src/src/arc.c
@@ -1546,7 +1546,7 @@ pdkim_bodyhash * b;
 
 identity = string_nextinlist(&signspec, &sep, NULL, 0);
 selector = string_nextinlist(&signspec, &sep, NULL, 0);
-if (  !*identity | !*selector
+if (  !*identity || !*selector
    || !(privkey = string_nextinlist(&signspec, &sep, NULL, 0)) || !*privkey)
   {
   log_write(0, LOG_MAIN, "ARC: bad signing-specification (%s)",

commit 2349d6c47c1922b661dcd2e3ce57fc34ade42230
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Mon Apr 16 18:45:04 2018 +0100

    Fix OpenSSL non-OCSP build
    
    (cherry picked from commit 37f0ce65959019e417ff79b9d0959e13470c5290)

diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c
index fb59217d..cefa94fe 100644
--- a/src/src/tls-openssl.c
+++ b/src/src/tls-openssl.c
@@ -2505,10 +2505,12 @@ if (error == SSL_ERROR_ZERO_RETURN)
   if (SSL_get_shutdown(server_ssl) == SSL_RECEIVED_SHUTDOWN)
  	SSL_shutdown(server_ssl);
 
+#ifndef DISABLE_OCSP
   sk_X509_pop_free(server_static_cbinfo->verify_stack, X509_free);
+  server_static_cbinfo->verify_stack = NULL;
+#endif
   SSL_free(server_ssl);
   SSL_CTX_free(server_ctx);
-  server_static_cbinfo->verify_stack = NULL;
   server_ctx = NULL;
   server_ssl = NULL;
   tls_in.active = -1;
@@ -2782,11 +2784,13 @@ if (shutdown)
     }
   }
 
+#ifndef DISABLE_OCSP
 if (is_server)
   {
   sk_X509_pop_free(server_static_cbinfo->verify_stack, X509_free);
   server_static_cbinfo->verify_stack = NULL;
   }
+#endif
 
 SSL_CTX_free(*ctxp);
 SSL_free(*sslp);

commit e20e2d6d5e2dd50b29b785b3831be655138d046e
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Mon Apr 23 11:26:52 2018 +0100

    DKIM: enforce limit of 20 on received DKIM-Signature: headers.  Bug 2269
    
    (cherry picked from commit 64b67b658a37dd780cc1b2fd0ef87febe461a0ba)

diff --git a/src/exim_monitor/em_globals.c b/src/exim_monitor/em_globals.c
index 50da58c8..e0ed0244 100644
--- a/src/exim_monitor/em_globals.c
+++ b/src/exim_monitor/em_globals.c
@@ -139,7 +139,7 @@ uschar *dkim_signers             = NULL;
 uschar *dkim_signing_domain      = NULL;
 uschar *dkim_signing_selector    = NULL;
 uschar *dkim_verify_signers      = US"$dkim_signers";
-BOOL    dkim_collect_input       = FALSE;
+unsigned dkim_collect_input      = 0;
 BOOL    dkim_disable_verify      = FALSE;
 #endif
 
diff --git a/src/src/dkim.c b/src/src/dkim.c
index 59445a85..c86dd5f0 100644
--- a/src/src/dkim.c
+++ b/src/src/dkim.c
@@ -34,6 +34,8 @@ pdkim_signature *dkim_signatures = NULL;
 pdkim_signature *dkim_cur_sig = NULL;
 static const uschar * dkim_collect_error = NULL;
 
+#define DKIM_MAX_SIGNATURES 20
+
 
 
 /*XXX the caller only uses the first record if we return multiple.
@@ -115,7 +117,7 @@ if (dkim_verify_ctx)
 /* Create new context */
 
 dkim_verify_ctx = pdkim_init_verify(&dkim_exim_query_dns_txt, dot_stuffing);
-dkim_collect_input = !!dkim_verify_ctx;
+dkim_collect_input = dkim_verify_ctx ? DKIM_MAX_SIGNATURES : 0;
 dkim_collect_error = NULL;
 
 /* Start feed up with any cached data */
@@ -137,7 +139,7 @@ if (  dkim_collect_input
   dkim_collect_error = pdkim_errstr(rc);
   log_write(0, LOG_MAIN,
 	     "DKIM: validation error: %.100s", dkim_collect_error);
-  dkim_collect_input = FALSE;
+  dkim_collect_input = 0;
   }
 store_pool = dkim_verify_oldpool;
 }
@@ -309,7 +311,7 @@ if (dkim_collect_error)
   goto out;
   }
 
-dkim_collect_input = FALSE;
+dkim_collect_input = 0;
 
 /* Finish DKIM operation and fetch link to signatures chain */
 
diff --git a/src/src/globals.c b/src/src/globals.c
index 5b480572..9b34da87 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -669,7 +669,7 @@ BOOL    disable_ipv6           = FALSE;
 BOOL    disable_logging        = FALSE;
 
 #ifndef DISABLE_DKIM
-BOOL    dkim_collect_input       = FALSE;
+unsigned dkim_collect_input      = 0;
 uschar *dkim_cur_signer          = NULL;
 BOOL    dkim_disable_verify      = FALSE;
 int     dkim_key_length          = 0;
diff --git a/src/src/globals.h b/src/src/globals.h
index 2915513f..a8835ed3 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -392,7 +392,7 @@ extern BOOL    disable_ipv6;           /* Don't do any IPv6 things */
 extern BOOL    disable_logging;        /* Disables log writing when TRUE */
 
 #ifndef DISABLE_DKIM
-extern BOOL    dkim_collect_input;     /* Runtime flag that tracks wether SMTP input is fed to DKIM validation */
+extern unsigned dkim_collect_input;    /* Runtime count of dkim signtures; tracks wether SMTP input is fed to DKIM validation */
 extern uschar *dkim_cur_signer;        /* Expansion variable, holds the current "signer" domain or identity during a acl_smtp_dkim run */
 extern BOOL    dkim_disable_verify;    /* Set via ACL control statement. When set, DKIM verification is disabled for the current message */
 extern int     dkim_key_length;        /* Expansion variable, length of signing key in bits */
diff --git a/src/src/pdkim/pdkim.c b/src/src/pdkim/pdkim.c
index 5688c78a..594af03c 100644
--- a/src/src/pdkim/pdkim.c
+++ b/src/src/pdkim/pdkim.c
@@ -192,6 +192,7 @@ switch(status)
   case PDKIM_ERR_RSA_SIGNING:	return US"SIGNING";
   case PDKIM_ERR_LONG_LINE:	return US"LONG_LINE";
   case PDKIM_ERR_BUFFER_TOO_SMALL:	return US"BUFFER_TOO_SMALL";
+  case PDKIM_ERR_EXCESS_SIGS:	return US"EXCESS_SIGS";
   case PDKIM_SIGN_PRIVKEY_WRAP:	return US"PRIVKEY_WRAP";
   case PDKIM_SIGN_PRIVKEY_B64D:	return US"PRIVKEY_B64D";
   default: return US"(unknown)";
@@ -1008,6 +1009,13 @@ else
       while (last_sig->next) last_sig = last_sig->next;
       last_sig->next = sig;
       }
+
+    if (--dkim_collect_input == 0)
+      {
+      ctx->headers = pdkim_prepend_stringlist(ctx->headers, ctx->cur_header->s);
+      ctx->cur_header->s[ctx->cur_header->ptr = 0] = '\0';
+      return PDKIM_ERR_EXCESS_SIGS;
+      }
     }
 
   /* all headers are stored for signature verification */
@@ -1033,7 +1041,7 @@ int p, rc;
 if (!data)
   pdkim_body_complete(ctx);
 
-else for (p = 0; p<len; p++)
+else for (p = 0; p < len; p++)
   {
   uschar c = data[p];
 
diff --git a/src/src/pdkim/pdkim.h b/src/src/pdkim/pdkim.h
index 59ac0388..02938754 100644
--- a/src/src/pdkim/pdkim.h
+++ b/src/src/pdkim/pdkim.h
@@ -48,8 +48,9 @@
 #define PDKIM_ERR_RSA_SIGNING      -102
 #define PDKIM_ERR_LONG_LINE        -103
 #define PDKIM_ERR_BUFFER_TOO_SMALL -104
-#define PDKIM_SIGN_PRIVKEY_WRAP    -105
-#define PDKIM_SIGN_PRIVKEY_B64D    -106
+#define PDKIM_ERR_EXCESS_SIGS	   -105
+#define PDKIM_SIGN_PRIVKEY_WRAP    -106
+#define PDKIM_SIGN_PRIVKEY_B64D    -107
 
 /* -------------------------------------------------------------------------- */
 /* Main/Extended verification status */
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index 433c677e..e4d02cea 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -595,7 +595,7 @@ uschar * log_msg;
 for(;;)
   {
 #ifndef DISABLE_DKIM
-  BOOL dkim_save;
+  unsigned dkim_save;
 #endif
 
   if (chunking_data_left > 0)
@@ -606,7 +606,7 @@ for(;;)
   receive_ungetc = lwr_receive_ungetc;
 #ifndef DISABLE_DKIM
   dkim_save = dkim_collect_input;
-  dkim_collect_input = FALSE;
+  dkim_collect_input = 0;
 #endif
 
   /* Unless PIPELINING was offered, there should be no next command
@@ -2041,7 +2041,8 @@ dnslist_domain = dnslist_matched = NULL;
 dkim_cur_signer = dkim_signers =
 dkim_signing_domain = dkim_signing_selector = NULL;
 dkim_cur_signer = dkim_signers = dkim_signing_domain = dkim_signing_selector = NULL;
-dkim_disable_verify = dkim_collect_input = FALSE;
+dkim_disable_verify = FALSE;
+dkim_collect_input = 0;
 dkim_verify_overall = dkim_verify_status = dkim_verify_reason = NULL;
 dkim_key_length = 0;
 dkim_verify_signers = US"$dkim_signers";
diff --git a/src/src/spool_in.c b/src/src/spool_in.c
index e34b5417..f0275bb7 100644
--- a/src/src/spool_in.c
+++ b/src/src/spool_in.c
@@ -261,7 +261,7 @@ bmi_verdicts = NULL;
 #ifndef DISABLE_DKIM
 dkim_signers = NULL;
 dkim_disable_verify = FALSE;
-dkim_collect_input = FALSE;
+dkim_collect_input = 0;
 #endif
 
 #ifdef SUPPORT_TLS

commit 56b566daa255a8c3ebf59e6b71ba9576f21a9b2c
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sat May 5 22:47:58 2018 +0100

    Cutthrough: fix race resulting in duplicate-delivery.  Bug 2273
    
    Cherry-picked from: cfbb0d24e8

diff --git a/src/src/receive.c b/src/src/receive.c
index 3b215f23..e7dc910b 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -4228,22 +4228,30 @@ if (deliver_freeze && freeze_tell != NULL && freeze_tell[0] != 0)
 
 /* Either a message has been successfully received and written to the two spool
 files, or an error in writing the spool has occurred for an SMTP message, or
-an SMTP message has been rejected for policy reasons. (For a non-SMTP message
-we will have already given up because there's no point in carrying on!) In
-either event, we must now close (and thereby unlock) the data file. In the
-successful case, this leaves the message on the spool, ready for delivery. In
-the error case, the spool file will be deleted. Then tidy up store, interact
-with an SMTP call if necessary, and return.
+an SMTP message has been rejected for policy reasons, or a message was passed on
+by cutthrough delivery. (For a non-SMTP message we will have already given up
+because there's no point in carrying on!) For non-cutthrough we must now close
+(and thereby unlock) the data file. In the successful case, this leaves the
+message on the spool, ready for delivery. In the error case, the spool file will
+be deleted. Then tidy up store, interact with an SMTP call if necessary, and
+return.
+
+For cutthrough we hold the data file locked until we have deleted it, otherwise
+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
 possible for fclose() to fail - but what to do? What has happened to the lock
-if this happens? */
+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. */
 
 
 TIDYUP:
-process_info[process_info_len] = 0;                /* Remove message id */
-if (data_file != NULL) (void)fclose(data_file);    /* Frees the lock */
+process_info[process_info_len] = 0;			/* Remove message id */
+if (data_file && cutthrough_done == NOT_TRIED)
+  if (fclose(data_file))				/* Frees the lock */
+    log_write(0, LOG_MAIN|LOG_PANIC,
+      "spoolfile error on close: %s", strerror(errno));
 
 /* Now reset signal handlers to their defaults */
 
@@ -4330,6 +4338,8 @@ if (smtp_input)
       }
     if (cutthrough_done != NOT_TRIED)
       {
+      if (data_file)
+	(void) fclose(data_file);  /* Frees the lock; do not care if error */
       message_id[0] = 0;	  /* Prevent a delivery from starting */
       cutthrough.delivery = cutthrough.callout_hold_only = FALSE;
       cutthrough.defer_pass = FALSE;

commit d4de30edf98c3ea326f6f949a9bf934d17d8412e
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri May 11 16:26:17 2018 +0100

    ARC: fix crash on signing with missing key file
    
    Cherry-picked from: 97e939dfe2

diff --git a/src/src/arc.c b/src/src/arc.c
index 557ea851..a8562a72 100644
--- a/src/src/arc.c
+++ b/src/src/arc.c
@@ -1632,7 +1632,9 @@ g = arc_sign_append_ams(g, &arc_sign_ctx, instance, identity, selector,
         including self (but with an empty b= in self)
 */
 
-g = arc_sign_prepend_as(g, &arc_sign_ctx, instance, identity, selector, &ar, privkey);
+if (g)
+  g = arc_sign_prepend_as(g, &arc_sign_ctx, instance, identity, selector, &ar,
+      privkey);
 
 /* Finally, append the dkim headers and return the lot. */
 

commit 9bd1e34631cc3d6f2b79b546a50c64ed6c5a48b6
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri May 11 18:02:29 2018 +0100

    Content scanning: Fix locking on message spool files.  Bug 2275
    
    (cherry picked from commit 1bd642c265dae5643f16d023879043b7576f66a9)

diff --git a/src/src/globals.c b/src/src/globals.c
index 9b34da87..45f49c85 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -1392,6 +1392,7 @@ uschar *spf_smtp_comment       = NULL;
 #endif
 
 BOOL    split_spool_directory  = FALSE;
+FILE   *spool_data_file	       = NULL;
 uschar *spool_directory        = US SPOOL_DIRECTORY
                            "\0<--------------Space to patch spool_directory->";
 BOOL    spool_file_wireformat  = FALSE;
diff --git a/src/src/globals.h b/src/src/globals.h
index a8835ed3..77b742d8 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -890,6 +890,7 @@ extern BOOL    spf_result_guessed;     /* spf result is of best-guess operation
 extern uschar *spf_smtp_comment;       /* spf comment to include in SMTP reply */
 #endif
 extern BOOL    split_spool_directory;  /* TRUE to use multiple subdirs */
+extern FILE   *spool_data_file;	       /* handle for -D file */
 extern uschar *spool_directory;        /* Name of spool directory */
 extern BOOL    spool_file_wireformat;  /* current -D file has CRLF rather than NL */
 extern BOOL    spool_wireformat;       /* can write wireformat -D files */
diff --git a/src/src/receive.c b/src/src/receive.c
index e7dc910b..f6db46c4 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -22,7 +22,6 @@ extern int dcc_ok;
 *                Local static variables          *
 *************************************************/
 
-static FILE   *data_file = NULL;
 static int     data_fd = -1;
 static uschar *spool_name = US"";
 
@@ -341,10 +340,10 @@ if (spool_name[0] != '\0')
 
 /* Now close the file if it is open, either as a fd or a stream. */
 
-if (data_file)
+if (spool_data_file)
   {
-  (void)fclose(data_file);
-  data_file = NULL;
+  (void)fclose(spool_data_file);
+  spool_data_file = NULL;
   }
 else if (data_fd >= 0)
   {
@@ -1445,10 +1444,12 @@ if (rc == DISCARD)
   {
   recipients_count = 0;
   *blackholed_by_ptr = US"MIME ACL";
+  cancel_cutthrough_connection(TRUE, US"mime acl discard");
   }
 else if (rc != OK)
   {
   Uunlink(spool_name);
+  cancel_cutthrough_connection(TRUE, US"mime acl not ok");
   unspool_mbox();
 #ifdef EXPERIMENTAL_DCC
   dcc_ok = 0;
@@ -1706,7 +1707,7 @@ header names list to be the normal list. Indicate there is no data file open
 yet, initialize the size and warning count, and deal with no size limit. */
 
 message_id[0] = 0;
-data_file = NULL;
+spool_data_file = NULL;
 data_fd = -1;
 spool_name = US"";
 message_size = 0;
@@ -3048,7 +3049,7 @@ the first line of the file (containing the message ID) because otherwise there
 are problems when Exim is run under Cygwin (I'm told). See comments in
 spool_in.c, where the same locking is done. */
 
-data_file = fdopen(data_fd, "w+");
+spool_data_file = fdopen(data_fd, "w+");
 lock_data.l_type = F_WRLCK;
 lock_data.l_whence = SEEK_SET;
 lock_data.l_start = 0;
@@ -3065,31 +3066,32 @@ data line (which was read as a header but then turned out not to have the right
 format); write it (remembering that it might contain binary zeros). The result
 of fwrite() isn't inspected; instead we call ferror() below. */
 
-fprintf(data_file, "%s-D\n", message_id);
+fprintf(spool_data_file, "%s-D\n", message_id);
 if (next)
   {
   uschar *s = next->text;
   int len = next->slen;
-  len = fwrite(s, 1, len, data_file);  len = len; /* compiler quietening */
-  body_linecount++;                 /* Assumes only 1 line */
+  if (fwrite(s, 1, len, spool_data_file) == len) /* "if" for compiler quietening */
+    body_linecount++;                 /* Assumes only 1 line */
   }
 
 /* Note that we might already be at end of file, or the logical end of file
 (indicated by '.'), or might have encountered an error while writing the
 message id or "next" line. */
 
-if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT)
+if (!ferror(spool_data_file) && !(receive_feof)() && message_ended != END_DOT)
   {
   if (smtp_input)
     {
     message_ended = chunking_state <= CHUNKING_OFFERED
-      ? read_message_data_smtp(data_file)
+      ? read_message_data_smtp(spool_data_file)
       : spool_wireformat
-      ? read_message_bdat_smtp_wire(data_file)
-      : read_message_bdat_smtp(data_file);
+      ? read_message_bdat_smtp_wire(spool_data_file)
+      : read_message_bdat_smtp(spool_data_file);
     receive_linecount++;                /* The terminating "." line */
     }
-  else message_ended = read_message_data(data_file);
+  else
+    message_ended = read_message_data(spool_data_file);
 
   receive_linecount += body_linecount;  /* For BSMTP errors mainly */
   message_linecount += body_linecount;
@@ -3136,10 +3138,10 @@ if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT)
 	}
       else
 	{
-	fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+	fseek(spool_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);
+	  US"message rejected: ", error_rc, spool_data_file, header_list);
 	/* Does not return */
 	}
       break;
@@ -3169,8 +3171,8 @@ we can then give up. Note that for SMTP input we must swallow the remainder of
 the input in cases of output errors, since the far end doesn't expect to see
 anything until the terminating dot line is sent. */
 
-if (fflush(data_file) == EOF || ferror(data_file) ||
-    EXIMfsync(fileno(data_file)) < 0 || (receive_ferror)())
+if (fflush(spool_data_file) == EOF || ferror(spool_data_file) ||
+    EXIMfsync(fileno(spool_data_file)) < 0 || (receive_ferror)())
   {
   uschar *msg_errno = US strerror(errno);
   BOOL input_error = (receive_ferror)() != 0;
@@ -3198,8 +3200,8 @@ if (fflush(data_file) == EOF || ferror(data_file) ||
 
   else
     {
-    fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
-    give_local_error(ERRMESS_IOERR, msg, US"", error_rc, data_file,
+    fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+    give_local_error(ERRMESS_IOERR, msg, US"", error_rc, spool_data_file,
       header_list);
     /* Does not return */
     }
@@ -3240,7 +3242,7 @@ if (extract_recip && (bad_addresses || recipients_count == 0))
       }
     }
 
-  fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+  fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, 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
@@ -3254,7 +3256,7 @@ if (extract_recip && (bad_addresses || recipients_count == 0))
           (bad_addresses == NULL)?
             (extracted_ignored? ERRMESS_IGADDRESS : ERRMESS_NOADDRESS) :
           (recipients_list == NULL)? ERRMESS_BADNOADDRESS : ERRMESS_BADADDRESS,
-          bad_addresses, header_list, data_file, FALSE))
+          bad_addresses, header_list, spool_data_file, FALSE))
       error_rc = (bad_addresses == NULL)? EXIT_NORECIPIENTS : EXIT_FAILURE;
     }
   else
@@ -3282,7 +3284,7 @@ if (extract_recip && (bad_addresses || recipients_count == 0))
   if (recipients_count == 0 || error_handling == ERRORS_STDERR)
     {
     Uunlink(spool_name);
-    (void)fclose(data_file);
+    (void)fclose(spool_data_file);
     exim_exit(error_rc, US"receiving");
     }
   }
@@ -3607,9 +3609,9 @@ else
           }
         else
           {
-          fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+          fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
           give_local_error(ERRMESS_LOCAL_ACL, user_msg,
-            US"message rejected by non-SMTP ACL: ", error_rc, data_file,
+            US"message rejected by non-SMTP ACL: ", error_rc, spool_data_file,
               header_list);
           /* Does not return */
           }
@@ -3807,9 +3809,9 @@ else
     }
   else
     {
-    fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+    fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
     give_local_error(ERRMESS_LOCAL_SCAN, errmsg,
-      US"message rejected by local scan code: ", error_rc, data_file,
+      US"message rejected by local scan code: ", error_rc, spool_data_file,
         header_list);
     /* Does not return */
     }
@@ -3882,8 +3884,8 @@ else
       }
     else
       {
-      fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
-      give_local_error(ERRMESS_IOERR, errmsg, US"", error_rc, data_file,
+      fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+      give_local_error(ERRMESS_IOERR, errmsg, US"", error_rc, spool_data_file,
         header_list);
       /* Does not return */
       }
@@ -3909,7 +3911,7 @@ 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. */
 
-fflush(data_file);
+fflush(spool_data_file);
 fstat(data_fd, &statbuf);
 
 msg_size += statbuf.st_size - SPOOL_DATA_START_OFFSET + 1;
@@ -4248,10 +4250,13 @@ then we can think about properly declaring the message not-received. */
 
 TIDYUP:
 process_info[process_info_len] = 0;			/* Remove message id */
-if (data_file && cutthrough_done == NOT_TRIED)
-  if (fclose(data_file))				/* Frees the lock */
+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));
+  spool_data_file = NULL;
+  }
 
 /* Now reset signal handlers to their defaults */
 
@@ -4338,8 +4343,11 @@ if (smtp_input)
       }
     if (cutthrough_done != NOT_TRIED)
       {
-      if (data_file)
-	(void) fclose(data_file);  /* Frees the lock; do not care if error */
+      if (spool_data_file)
+	{
+	(void) fclose(spool_data_file);  /* Frees the lock; do not care if error */
+	spool_data_file = NULL;
+	}
       message_id[0] = 0;	  /* Prevent a delivery from starting */
       cutthrough.delivery = cutthrough.callout_hold_only = FALSE;
       cutthrough.defer_pass = FALSE;
diff --git a/src/src/spool_mbox.c b/src/src/spool_mbox.c
index 749484f2..05f90a81 100644
--- a/src/src/spool_mbox.c
+++ b/src/src/spool_mbox.c
@@ -35,9 +35,7 @@ uschar message_subdir[2];
 uschar buffer[16384];
 uschar *temp_string;
 uschar *mbox_path;
-FILE *mbox_file = NULL;
-FILE *data_file = NULL;
-FILE *yield = NULL;
+FILE *mbox_file = NULL, *l_data_file = NULL, *yield = NULL;
 header_line *my_headerlist;
 struct stat statbuf;
 int i, j;
@@ -108,21 +106,25 @@ if (!spool_mbox_ok)
     goto OUT;
     }
 
-  /* copy body file */
-  if (!source_file_override)
+  /* Copy body file.  If the main receive still has it open then it is holding
+  a lock, and we must not close it (which releases the lock), so just use the
+  global file handle. */
+  if (source_file_override)
+    l_data_file = Ufopen(source_file_override, "rb");
+  else if (spool_data_file)
+    l_data_file = spool_data_file;
+  else
     {
     message_subdir[1] = '\0';
     for (i = 0; i < 2; i++)
       {
       message_subdir[0] = split_spool_directory == (i == 0) ? message_id[5] : 0;
       temp_string = spool_fname(US"input", message_subdir, message_id, US"-D");
-      if ((data_file = Ufopen(temp_string, "rb"))) break;
+      if ((l_data_file = Ufopen(temp_string, "rb"))) break;
       }
     }
-  else
-    data_file = Ufopen(source_file_override, "rb");
 
-  if (!data_file)
+  if (!l_data_file)
     {
     log_write(0, LOG_MAIN|LOG_PANIC, "Could not open datafile for message %s",
       message_id);
@@ -131,7 +133,7 @@ if (!spool_mbox_ok)
 
   /* The code used to use this line, but it doesn't work in Cygwin.
 
-      (void)fread(data_buffer, 1, 18, data_file);
+      (void)fread(data_buffer, 1, 18, l_data_file);
 
      What's happening is that spool_mbox used to use an fread to jump over the
      file header. That fails under Cygwin because the header is locked, but
@@ -139,23 +141,23 @@ if (!spool_mbox_ok)
      explicitly, because the one in the file is parted of the locked area.  */
 
   if (!source_file_override)
-    (void)fseek(data_file, SPOOL_DATA_START_OFFSET, SEEK_SET);
+    (void)fseek(l_data_file, SPOOL_DATA_START_OFFSET, SEEK_SET);
 
   do
     {
     uschar * s;
 
     if (!spool_file_wireformat || source_file_override)
-      j = fread(buffer, 1, sizeof(buffer), data_file);
+      j = fread(buffer, 1, sizeof(buffer), l_data_file);
     else						/* needs CRLF -> NL */
-      if ((s = US fgets(CS buffer, sizeof(buffer), data_file)))
+      if ((s = US fgets(CS buffer, sizeof(buffer), l_data_file)))
 	{
 	uschar * p = s + Ustrlen(s) - 1;
 
 	if (*p == '\n' && p[-1] == '\r')
 	  *--p = '\n';
 	else if (*p == '\r')
-	  ungetc(*p--, data_file);
+	  ungetc(*p--, l_data_file);
 
 	j = p - buffer;
 	}
@@ -190,7 +192,7 @@ else
   *mbox_file_size = statbuf.st_size;
 
 OUT:
-if (data_file) (void)fclose(data_file);
+if (l_data_file && !spool_data_file) (void)fclose(l_data_file);
 if (mbox_file) (void)fclose(mbox_file);
 store_reset(reset_point);
 return yield;

commit 3b9730e3deaf4eb03a99977d830da446dcc85cfb
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Thu May 24 16:31:27 2018 +0100

    Use serial number 1 for self-generated selfsigned certificate
    
    Broken-by: 23bb69826c
    (cherry picked from commit 1613fd68b5931757016c3c25fdc3b0f37827e7f1)

diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c
index 35816cd6..08c1d939 100644
--- a/src/src/tls-gnu.c
+++ b/src/src/tls-gnu.c
@@ -790,7 +790,7 @@ if ((rc = gnutls_x509_privkey_generate(pkey, GNUTLS_PK_RSA,
   goto err;
 
 where = US"configuring cert";
-now = 0;
+now = 1;
 if (  (rc = gnutls_x509_crt_set_version(cert, 3))
    || (rc = gnutls_x509_crt_set_serial(cert, &now, sizeof(now)))
    || (rc = gnutls_x509_crt_set_activation_time(cert, now = time(NULL)))
diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c
index cefa94fe..068a0d87 100644
--- a/src/src/tls-openssl.c
+++ b/src/src/tls-openssl.c
@@ -1000,7 +1000,7 @@ if (!EVP_PKEY_assign_RSA(pkey, rsa))
   goto err;
 
 X509_set_version(x509, 2);				/* N+1 - version 3 */
-ASN1_INTEGER_set(X509_get_serialNumber(x509), 0);
+ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
 X509_gmtime_adj(X509_get_notBefore(x509), 0);
 X509_gmtime_adj(X509_get_notAfter(x509), (long)60 * 60);	/* 1 hour */
 X509_set_pubkey(x509, pkey);

commit d5bd2a53d8a226793321b0c3011092ab97e63902
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Wed Jun 6 11:19:23 2018 +0100

    Fix logging of cmdline args when starting in an unlinked cwd.  Bug 2274

diff --git a/src/src/exim.c b/src/src/exim.c
index d740814f..93a65dbc 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -4083,14 +4083,23 @@ a debugging feature for finding out what arguments certain MUAs actually use.
 Don't attempt it if logging is disabled, or if listing variables or if
 verifying/testing addresses or expansions. */
 
-if (((debug_selector & D_any) != 0 || LOGGING(arguments))
-      && really_exim && !list_options && !checking)
+if (  (debug_selector & D_any  ||  LOGGING(arguments))
+   && really_exim && !list_options && !checking)
   {
   int i;
   uschar *p = big_buffer;
   Ustrcpy(p, "cwd= (failed)");
 
-  Ustrncpy(p + 4, initial_cwd, big_buffer_size-5);
+  if (!initial_cwd)
+    p += 13;
+  else
+    {
+    Ustrncpy(p + 4, initial_cwd, big_buffer_size-5);
+    p += 4 + Ustrlen(initial_cwd);
+    /* in case p is near the end and we don't provide enough space for
+     * string_format to be willing to write. */
+    *p = '\0';
+    }
 
   while (*p) p++;
   (void)string_format(p, big_buffer_size - (p - big_buffer), " %d args:", argc);

commit ebf2c3666fd2e9bbb208dbeb297a901ba287c72c
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Thu Jun 7 16:24:31 2018 +0100

    ARC: Fix signing for case when DKIM signing failed

diff --git a/src/src/arc.c b/src/src/arc.c
index a8562a72..c2e7e508 100644
--- a/src/src/arc.c
+++ b/src/src/arc.c
@@ -1638,7 +1638,7 @@ if (g)
 
 /* Finally, append the dkim headers and return the lot. */
 
-g = string_catn(g, sigheaders->s, sigheaders->ptr);
+if (sigheaders) g = string_catn(g, sigheaders->s, sigheaders->ptr);
 (void) string_from_gstring(g);
 gstring_reset_unused(g);
 return g;

commit e9f733e658a1605edca1bd6b689232e18c8f53c6
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Thu Jun 21 17:03:38 2018 +0100

    DKIM: Fix signing for body lines starting with a pair of dots.  Bug 2284
    
    Broken-by: 42055a3385

diff --git a/src/src/dkim_transport.c b/src/src/dkim_transport.c
index 11458680..13db0f2f 100644
--- a/src/src/dkim_transport.c
+++ b/src/src/dkim_transport.c
@@ -155,7 +155,10 @@ if (!rc) return FALSE;
 arc_sign_init();
 #endif
 
-dkim->dot_stuffed = !!(save_options & topt_end_dot);
+/* The dotstuffed status of the datafile depends on whether it was stored
+in wireformat. */
+
+dkim->dot_stuffed = spool_file_wireformat;
 if (!(dkim_signature = dkim_exim_sign(deliver_datafile, SPOOL_DATA_START_OFFSET,
 				    hdrs, dkim, &errstr)))
   if (!(rc = dkt_sign_fail(dkim, &errno)))
@@ -271,7 +274,9 @@ if (!rc)
 arc_sign_init();
 #endif
 
-/* Feed the file to the goats^W DKIM lib */
+/* Feed the file to the goats^W DKIM lib.  At this point the dotstuffed
+status of the file depends on the output of transport_write_message() just
+above, which should be the result of the end_dot flag in tctx->options. */
 
 dkim->dot_stuffed = !!(options & topt_end_dot);
 if (!(dkim_signature = dkim_exim_sign(dkim_fd, 0, NULL, dkim, &errstr)))

commit 64469eb46f042bea1c12996d69c5fedd0bf44510
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Mon Jun 25 12:08:37 2018 +0100

    ARC: Fix verification to do AS checks in reverse order
    
    Broken from the original introduction (617d39327e)

diff --git a/src/src/arc.c b/src/src/arc.c
index c2e7e508..3bde81e2 100644
--- a/src/src/arc.c
+++ b/src/src/arc.c
@@ -966,16 +966,13 @@ return NULL;
 static const uschar *
 arc_verify_seals(arc_ctx * ctx)
 {
-arc_set * as = ctx->arcset_chain;
+arc_set * as = ctx->arcset_chain_last;
 
 if (!as)
   return US"none";
 
-while (as)
-  {
-  if (arc_seal_verify(ctx, as)) return US"fail";
-  as = as->next;
-  }
+for ( ; as; as = as->prev) if (arc_seal_verify(ctx, as)) return US"fail";
+
 DEBUG(D_acl) debug_printf("ARC: AS vfy overall pass\n");
 return NULL;
 }

commit 9dbbd9a5b838b85c2db09eaee9f26ef25b7c4ae4
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Wed Jul 18 22:16:38 2018 +0100

    I18N: Fix protocol recorded for a multi-SMTPUTF8-message connection.  Bug 2287
    
    (cherry picked from commit 946515bfe62796f6c0d6554e9e1e227f33253e7c)

diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index e4d02cea..b1f442c8 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -4670,13 +4670,15 @@ while (done <= 0)
         case ENV_MAIL_OPT_UTF8:
 	  if (smtputf8_advertised)
 	    {
-	    int old_pool = store_pool;
-
 	    DEBUG(D_receive) debug_printf("smtputf8 requested\n");
 	    message_smtputf8 = allow_utf8_domains = TRUE;
-	    store_pool = POOL_PERM;
-	    received_protocol = string_sprintf("utf8%s", received_protocol);
-	    store_pool = old_pool;
+	    if (Ustrncmp(received_protocol, US"utf8", 4) != 0)
+	      {
+	      int old_pool = store_pool;
+	      store_pool = POOL_PERM;
+	      received_protocol = string_sprintf("utf8%s", received_protocol);
+	      store_pool = old_pool;
+	      }
 	    }
 	  break;
 #endif
