commit 00b35aee4cdc8a002b537d51585f925aba19db09
Author: Fabian Groffen <grobian@gentoo.org>
Date:   Tue Dec 10 17:32:12 2019 +0000

    PAM: fix crash in the pam expansion condition.  Bug 2489
    
    Broken-by: f3ebb786e4
    (cherry picked from commit dcbfbadac2f557dc73bbcf2b26aadf50f8414ca1)

diff --git a/src/src/auths/call_pam.c b/src/src/auths/call_pam.c
index 9a46b6b63..c96e146d1 100644
--- a/src/src/auths/call_pam.c
+++ b/src/src/auths/call_pam.c
@@ -69,9 +69,11 @@ pam_converse (int num_msg, PAM_CONVERSE_ARG2_TYPE **msg,
 int sep = 0;
 struct pam_response *reply;
 
-if (pam_arg_ended) return PAM_CONV_ERR;
+/* It seems that PAM frees reply[] */
 
-reply = store_get(sizeof(struct pam_response) * num_msg, FALSE);
+if (  pam_arg_ended
+   || !(reply = malloc(sizeof(struct pam_response) * num_msg)))
+  return PAM_CONV_ERR;
 
 for (int i = 0; i < num_msg; i++)
   {
@@ -80,25 +82,26 @@ for (int i = 0; i < num_msg; i++)
     {
     case PAM_PROMPT_ECHO_ON:
     case PAM_PROMPT_ECHO_OFF:
-    arg = string_nextinlist(&pam_args, &sep, big_buffer, big_buffer_size);
-    if (arg == NULL)
-      {
-      arg = US"";
-      pam_arg_ended = TRUE;
-      }
-    reply[i].resp = CS string_copy_perm(arg, FALSE); /* PAM frees resp */
-    reply[i].resp_retcode = PAM_SUCCESS;
-    break;
+      arg = string_nextinlist(&pam_args, &sep, big_buffer, big_buffer_size);
+      if (!arg)
+	{
+	arg = US"";
+	pam_arg_ended = TRUE;
+	}
+      reply[i].resp = CS string_copy_malloc(arg); /* PAM frees resp */
+      reply[i].resp_retcode = PAM_SUCCESS;
+      break;
 
     case PAM_TEXT_INFO:    /* Just acknowledge messages */
     case PAM_ERROR_MSG:
-    reply[i].resp_retcode = PAM_SUCCESS;
-    reply[i].resp = NULL;
-    break;
+      reply[i].resp_retcode = PAM_SUCCESS;
+      reply[i].resp = NULL;
+      break;
 
     default:  /* Must be an error of some sort... */
-    pam_conv_had_error = TRUE;
-    return PAM_CONV_ERR;
+      free(reply);
+      pam_conv_had_error = TRUE;
+      return PAM_CONV_ERR;
     }
   }
 

commit 5cb18fca8873da273fb80931b620892586cbffaf
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Nov 8 22:30:04 2019 +0000

    Regard command-line recipients as tainted
    
    (cherry picked from commit f0fe22cbc29ee4f887aa254f2590a9e72401e237)

diff --git a/src/src/exim.c b/src/src/exim.c
index 6a2e7a644..0e839c5d4 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -4822,8 +4822,9 @@ if (verify_address_mode || f.address_test_mode)
     {
     while (recipients_arg < argc)
       {
-      uschar *s = argv[recipients_arg++];
-      while (*s != 0)
+      /* Supplied addresses are tainted since they come from a user */
+      uschar * s = string_copy_taint(argv[recipients_arg++], TRUE);
+      while (*s)
         {
         BOOL finished = FALSE;
         uschar *ss = parse_find_address_end(s, FALSE);
@@ -4831,16 +4832,16 @@ if (verify_address_mode || f.address_test_mode)
         test_address(s, flags, &exit_value);
         s = ss;
         if (!finished)
-          while (*(++s) != 0 && (*s == ',' || isspace(*s)));
+          while (*++s == ',' || isspace(*s)) ;
         }
       }
     }
 
   else for (;;)
     {
-    uschar *s = get_stdinput(NULL, NULL);
-    if (s == NULL) break;
-    test_address(s, flags, &exit_value);
+    uschar * s = get_stdinput(NULL, NULL);
+    if (!s) break;
+    test_address(string_copy_taint(s, TRUE), flags, &exit_value);
     }
 
   route_tidyup();
@@ -5334,13 +5335,13 @@ while (more)
 
     raw_sender = string_copy(sender_address);
 
-    /* Loop for each argument */
+    /* Loop for each argument (supplied by user hence tainted) */
 
     for (int i = 0; i < count; i++)
       {
       int start, end, domain;
-      uschar *errmess;
-      uschar *s = list[i];
+      uschar * errmess;
+      uschar * s = string_copy_taint(list[i], TRUE);
 
       /* Loop for each comma-separated address */
 

commit 0582221c4a875e35b5ccefe9a17eef584819e4f0
Author: Heiko Schlittermann (HS12-RIPE) <hs@schlittermann.de>
Date:   Tue Dec 10 23:14:35 2019 +0100

    TFO: disable for FreeBSD
    
    (cherry-picked from 52503083b9eded01c25e1c73f4510963c3cee799)

diff --git a/src/src/ip.c b/src/src/ip.c
index 19be51a03..70e3e2064 100644
--- a/src/src/ip.c
+++ b/src/src/ip.c
@@ -245,7 +245,7 @@ callout_address = string_sprintf("[%s]:%d", address, port);
 sigalrm_seen = FALSE;
 if (timeout > 0) ALARM(timeout);
 
-#ifdef TCP_FASTOPEN
+#if defined(TCP_FASTOPEN) && (defined(MSG_FASTOPEN) || defined(EXIM_TFO_CONNECTX))
 /* TCP Fast Open, if the system has a cookie from a previous call to
 this peer, can send data in the SYN packet.  The peer can send data
 before it gets our ACK of its SYN,ACK - the latter is useful for
@@ -255,8 +255,8 @@ possibly use the data-on-syn, so support that too. */
 if (fastopen_blob && f.tcp_fastopen_ok)
   {
 # ifdef MSG_FASTOPEN
-  /* This is a Linux implementation.  It might be useable on FreeBSD; I have
-  not checked. */
+  /* This is a Linux implementation.  FreeBSD does not seem to have MSG_FASTOPEN so
+  how to get TFO is unknown. */
 
   if ((rc = sendto(sock, fastopen_blob->data, fastopen_blob->len,
 		    MSG_FASTOPEN | MSG_DONTWAIT, s_ptr, s_len)) >= 0)

commit 6397b292615a5ffb9fdb5bd305736e2ef5f00e0d
Author: Samuel Thibault <samuel.thibault@ens-lyon.org>
Date:   Wed Dec 4 21:30:01 2019 +0000

    Hurd: errno really uses more than a short-sized value.  Bug 2476
    
    (cherry picked from commit 377da0430697e6bcb8c48744eb5af4272a8f8075)

diff --git a/src/src/structs.h b/src/src/structs.h
index 3b5818df0..c05778161 100644
--- a/src/src/structs.h
+++ b/src/src/structs.h
@@ -648,11 +648,11 @@ typedef struct address_item {
   unsigned int domain_cache[(MAX_NAMED_LIST * 2)/32];
   unsigned int localpart_cache[(MAX_NAMED_LIST * 2)/32];
   int     mode;                   /* mode for local transporting to a file */
+  int	  basic_errno;		  /* status after failure */
   int     more_errno;             /* additional error information */
                                   /* (may need to hold a timestamp) */
   unsigned int delivery_usec;	  /* subsecond part of delivery time */
 
-  short int basic_errno;          /* status after failure */
   unsigned short child_count;     /* number of child addresses */
   short int return_file;          /* fileno of return data file */
   short int special_action;       /* ( used when when deferred or failed */

commit 8ab104034bd456843cd5a4ab34af6ef339f9ac8d
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Tue Dec 10 20:04:15 2019 +0000

    local_scan: align local_scan.h and docs re. store_get()
    
    (cherry picked from commit 4ec0fcb6737da9bc2603056d56fbf6e41e061967)

diff --git a/src/src/local_scan.h b/src/src/local_scan.h
index e0a97088f..da9a8911a 100644
--- a/src/src/local_scan.h
+++ b/src/src/local_scan.h
@@ -193,6 +193,14 @@ extern void    smtp_vprintf(const char *, BOOL, va_list);
 	string_sprintf_trc(fmt, US __FUNCTION__, __LINE__, __VA_ARGS__)
 extern uschar *string_sprintf_trc(const char *, const uschar *, unsigned, ...) ALMOST_PRINTF(1,4);
 
+#define store_get(size, tainted) \
+	store_get_3(size, tainted, __FUNCTION__, __LINE__)
+extern void   *store_get_3(int, BOOL, const char *, int)	ALLOC ALLOC_SIZE(1) WARN_UNUSED_RESULT;
+#define store_get_perm(size, tainted) \
+	store_get_perm_3(size, tainted, __FUNCTION__, __LINE__)
+extern void   *store_get_perm_3(int, BOOL, const char *, int)	ALLOC ALLOC_SIZE(1) WARN_UNUSED_RESULT;
+
+
 #if defined(LOCAL_SCAN) || defined(DLFUNC_IMPL)
 /* When compiling a local_scan() file we want to rename a published API, so that
 we can use an inlined implementation in the compiles of the main Exim files,
diff --git a/src/src/store.h b/src/src/store.h
index 52f211ef5..39b0f6ecf 100644
--- a/src/src/store.h
+++ b/src/src/store.h
@@ -34,10 +34,7 @@ tracing information for debugging. */
 
 #define store_free(addr) \
 	store_free_3(addr, __FUNCTION__, __LINE__)
-#define store_get(size, tainted) \
-	store_get_3(size, tainted, __FUNCTION__, __LINE__)
-#define store_get_perm(size, tainted) \
-	store_get_perm_3(size, tainted, __FUNCTION__, __LINE__)
+/* store_get & store_get_perm are in local_scan.h */
 #define store_malloc(size) \
 	store_malloc_3(size, __FUNCTION__, __LINE__)
 #define store_mark(void) \
@@ -55,8 +52,7 @@ typedef void ** rmark;
 
 extern BOOL    store_extend_3(void *, BOOL, int, int, const char *, int);
 extern void    store_free_3(void *, const char *, int);
-extern void   *store_get_3(int, BOOL, const char *, int)	ALLOC ALLOC_SIZE(1) WARN_UNUSED_RESULT;
-extern void   *store_get_perm_3(int, BOOL, const char *, int)	ALLOC ALLOC_SIZE(1) WARN_UNUSED_RESULT;
+/* store_get_3 & store_get_perm_3 are in local_scan.h */
 extern void   *store_malloc_3(int, const char *, int)		ALLOC ALLOC_SIZE(1) WARN_UNUSED_RESULT;
 extern rmark   store_mark_3(const char *, int);
 extern void   *store_newblock_3(void *, BOOL, int, int, const char *, int);

commit 5169b5fa9104c024a3d3b31b163bfa3b9ec14c9c
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Wed Dec 11 14:32:23 2019 +0000

    Fix taint issue in transport with DSN.  Bug 2491
    
    (cherry picked from commit c58803fd11185cbcf673e99cebf8397c9194357a)

diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index 3fd94a19c..bb5fda1f4 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -3411,26 +3411,26 @@ BOOL pass_message = FALSE;
 uschar *message = NULL;
 uschar new_message_id[MESSAGE_ID_LENGTH + 1];
 
-smtp_context sx;
+smtp_context * sx = store_get(sizeof(*sx), TRUE);	/* tainted, for the data buffers */
 
 gettimeofday(&start_delivery_time, NULL);
 suppress_tls = suppress_tls;  /* stop compiler warning when no TLS support */
 *message_defer = FALSE;
 
-sx.addrlist = addrlist;
-sx.conn_args.host = host;
-sx.conn_args.host_af = host_af,
-sx.port = defport;
-sx.conn_args.interface = interface;
-sx.helo_data = NULL;
-sx.conn_args.tblock = tblock;
-sx.verify = FALSE;
-sx.sync_addr = sx.first_addr = addrlist;
+sx->addrlist = addrlist;
+sx->conn_args.host = host;
+sx->conn_args.host_af = host_af,
+sx->port = defport;
+sx->conn_args.interface = interface;
+sx->helo_data = NULL;
+sx->conn_args.tblock = tblock;
+sx->verify = FALSE;
+sx->sync_addr = sx->first_addr = addrlist;
 
 /* Get the channel set up ready for a message (MAIL FROM being the next
 SMTP command to send */
 
-if ((rc = smtp_setup_conn(&sx, suppress_tls)) != OK)
+if ((rc = smtp_setup_conn(sx, suppress_tls)) != OK)
   return rc;
 
 /* If there is a filter command specified for this transport, we can now
@@ -3456,10 +3456,10 @@ if (tblock->filter_command)
   if (  transport_filter_argv
      && *transport_filter_argv
      && **transport_filter_argv
-     && sx.peer_offered & OPTION_CHUNKING
+     && sx->peer_offered & OPTION_CHUNKING
      )
     {
-    sx.peer_offered &= ~OPTION_CHUNKING;
+    sx->peer_offered &= ~OPTION_CHUNKING;
     DEBUG(D_transport) debug_printf("CHUNKING not usable due to transport filter\n");
     }
   }
@@ -3473,11 +3473,11 @@ code was to use a goto to jump back to this point when there is another
 transaction to handle. */
 
 SEND_MESSAGE:
-sx.from_addr = return_path;
-sx.sync_addr = sx.first_addr;
-sx.ok = FALSE;
-sx.send_rset = TRUE;
-sx.completed_addr = FALSE;
+sx->from_addr = return_path;
+sx->sync_addr = sx->first_addr;
+sx->ok = FALSE;
+sx->send_rset = TRUE;
+sx->completed_addr = FALSE;
 
 
 /* If we are a continued-connection-after-verify the MAIL and RCPT
@@ -3487,10 +3487,10 @@ always has a sequence number greater than one. */
 
 if (continue_hostname && continue_sequence == 1)
   {
-  sx.peer_offered = smtp_peer_options;
-  sx.pending_MAIL = FALSE;
-  sx.ok = TRUE;
-  sx.next_addr = NULL;
+  sx->peer_offered = smtp_peer_options;
+  sx->pending_MAIL = FALSE;
+  sx->ok = TRUE;
+  sx->next_addr = NULL;
 
   for (address_item * addr = addrlist; addr; addr = addr->next)
     addr->transport_return = PENDING_OK;
@@ -3499,7 +3499,7 @@ else
   {
   /* Initiate a message transfer. */
 
-  switch(smtp_write_mail_and_rcpt_cmds(&sx, &yield))
+  switch(smtp_write_mail_and_rcpt_cmds(sx, &yield))
     {
     case 0:		break;
     case -1: case -2:	goto RESPONSE_FAILED;
@@ -3517,13 +3517,13 @@ else
     address_item * a;
     unsigned cnt;
 
-    for (a = sx.first_addr, cnt = 0; a && cnt < sx.max_rcpt; a = a->next, cnt++)
+    for (a = sx->first_addr, cnt = 0; a && cnt < sx->max_rcpt; a = a->next, cnt++)
       if (a->transport_return != PENDING_OK)
 	{
 	/*XXX could we find a better errno than 0 here? */
 	set_errno_nohost(addrlist, 0, a->message, FAIL,
 	  testflag(a, af_pass_message));
-	sx.ok = FALSE;
+	sx->ok = FALSE;
 	break;
 	}
     }
@@ -3537,20 +3537,20 @@ are pipelining. The responses are all handled by sync_responses().
 If using CHUNKING, do not send a BDAT until we know how big a chunk we want
 to send is. */
 
-if (  !(sx.peer_offered & OPTION_CHUNKING)
-   && (sx.ok || (pipelining_active && !mua_wrapper)))
+if (  !(sx->peer_offered & OPTION_CHUNKING)
+   && (sx->ok || (pipelining_active && !mua_wrapper)))
   {
-  int count = smtp_write_command(&sx, SCMD_FLUSH, "DATA\r\n");
+  int count = smtp_write_command(sx, SCMD_FLUSH, "DATA\r\n");
 
   if (count < 0) goto SEND_FAILED;
-  switch(sync_responses(&sx, count, sx.ok ? +1 : -1))
+  switch(sync_responses(sx, count, sx->ok ? +1 : -1))
     {
-    case 3: sx.ok = TRUE;            /* 2xx & 5xx => OK & progress made */
-    case 2: sx.completed_addr = TRUE;    /* 5xx (only) => progress made */
+    case 3: sx->ok = TRUE;            /* 2xx & 5xx => OK & progress made */
+    case 2: sx->completed_addr = TRUE;    /* 5xx (only) => progress made */
     break;
 
-    case 1: sx.ok = TRUE;            /* 2xx (only) => OK, but if LMTP, */
-    if (!sx.lmtp) sx.completed_addr = TRUE; /* can't tell about progress yet */
+    case 1: sx->ok = TRUE;            /* 2xx (only) => OK, but if LMTP, */
+    if (!sx->lmtp) sx->completed_addr = TRUE; /* can't tell about progress yet */
     case 0: break;			/* No 2xx or 5xx, but no probs */
 
     case -1: goto END_OFF;		/* Timeout on RCPT */
@@ -3573,18 +3573,18 @@ for handling the SMTP dot-handling protocol, flagging to apply to headers as
 well as body. Set the appropriate timeout value to be used for each chunk.
 (Haven't been able to make it work using select() for writing yet.) */
 
-if (  !sx.ok
-   && (!(sx.peer_offered & OPTION_CHUNKING) || !pipelining_active))
+if (  !sx->ok
+   && (!(sx->peer_offered & OPTION_CHUNKING) || !pipelining_active))
   {
   /* Save the first address of the next batch. */
-  sx.first_addr = sx.next_addr;
+  sx->first_addr = sx->next_addr;
 
-  sx.ok = TRUE;
+  sx->ok = TRUE;
   }
 else
   {
   transport_ctx tctx = {
-    .u = {.fd = sx.cctx.sock},	/*XXX will this need TLS info? */
+    .u = {.fd = sx->cctx.sock},	/*XXX will this need TLS info? */
     .tblock =	tblock,
     .addr =	addrlist,
     .check_string = US".",
@@ -3603,32 +3603,32 @@ else
   of responses.  The callback needs a whole bunch of state so set up
   a transport-context structure to be passed around. */
 
-  if (sx.peer_offered & OPTION_CHUNKING)
+  if (sx->peer_offered & OPTION_CHUNKING)
     {
     tctx.check_string = tctx.escape_string = NULL;
     tctx.options |= topt_use_bdat;
     tctx.chunk_cb = smtp_chunk_cmd_callback;
-    sx.pending_BDAT = FALSE;
-    sx.good_RCPT = sx.ok;
-    sx.cmd_count = 0;
-    tctx.smtp_context = &sx;
+    sx->pending_BDAT = FALSE;
+    sx->good_RCPT = sx->ok;
+    sx->cmd_count = 0;
+    tctx.smtp_context = sx;
     }
   else
     tctx.options |= topt_end_dot;
 
   /* Save the first address of the next batch. */
-  sx.first_addr = sx.next_addr;
+  sx->first_addr = sx->next_addr;
 
   /* Responses from CHUNKING commands go in buffer.  Otherwise,
   there has not been a response. */
 
-  sx.buffer[0] = 0;
+  sx->buffer[0] = 0;
 
   sigalrm_seen = FALSE;
   transport_write_timeout = ob->data_timeout;
   smtp_command = US"sending data block";   /* For error messages */
   DEBUG(D_transport|D_v)
-    if (sx.peer_offered & OPTION_CHUNKING)
+    if (sx->peer_offered & OPTION_CHUNKING)
       debug_printf("         will write message using CHUNKING\n");
     else
       debug_printf("  SMTP>> writing message and terminating \".\"\n");
@@ -3651,7 +3651,7 @@ else
 	if (!f.expand_string_forcedfail)
 	  {
 	  message = US"failed to expand arc_sign";
-	  sx.ok = FALSE;
+	  sx->ok = FALSE;
 	  goto SEND_FAILED;
 	  }
 	}
@@ -3668,9 +3668,9 @@ else
   report_time_since(&t0, US"dkim_exim_sign_init (delta)");
 # endif
   }
-  sx.ok = dkim_transport_write_message(&tctx, &ob->dkim, CUSS &message);
+  sx->ok = dkim_transport_write_message(&tctx, &ob->dkim, CUSS &message);
 #else
-  sx.ok = transport_write_message(&tctx, 0);
+  sx->ok = transport_write_message(&tctx, 0);
 #endif
 
   /* transport_write_message() uses write() because it is called from other
@@ -3684,7 +3684,7 @@ else
   or the failure of a transport filter or the expansion of added headers.
   Or, when CHUNKING, it can be a protocol-detected failure. */
 
-  if (!sx.ok)
+  if (!sx->ok)
     if (message) goto SEND_FAILED;
     else         goto RESPONSE_FAILED;
 
@@ -3696,17 +3696,17 @@ else
 
   smtp_command = US"end of data";
 
-  if (sx.peer_offered & OPTION_CHUNKING && sx.cmd_count > 1)
+  if (sx->peer_offered & OPTION_CHUNKING && sx->cmd_count > 1)
     {
     /* Reap any outstanding MAIL & RCPT commands, but not a DATA-go-ahead */
-    switch(sync_responses(&sx, sx.cmd_count-1, 0))
+    switch(sync_responses(sx, sx->cmd_count-1, 0))
       {
-      case 3: sx.ok = TRUE;            /* 2xx & 5xx => OK & progress made */
-      case 2: sx.completed_addr = TRUE;    /* 5xx (only) => progress made */
+      case 3: sx->ok = TRUE;            /* 2xx & 5xx => OK & progress made */
+      case 2: sx->completed_addr = TRUE;    /* 5xx (only) => progress made */
 	      break;
 
-      case 1: sx.ok = TRUE;		/* 2xx (only) => OK, but if LMTP, */
-      if (!sx.lmtp) sx.completed_addr = TRUE; /* can't tell about progress yet */
+      case 1: sx->ok = TRUE;		/* 2xx (only) => OK, but if LMTP, */
+      if (!sx->lmtp) sx->completed_addr = TRUE; /* can't tell about progress yet */
       case 0: break;			/* No 2xx or 5xx, but no probs */
 
       case -1: goto END_OFF;		/* Timeout on RCPT */
@@ -3725,17 +3725,17 @@ else
   individual responses, before going on with the overall response.  If we don't
   get the warning then deal with per non-PRDR. */
 
-  if(sx.prdr_active)
+  if(sx->prdr_active)
     {
-    sx.ok = smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '3', ob->final_timeout);
-    if (!sx.ok && errno == 0) switch(sx.buffer[0])
+    sx->ok = smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '3', ob->final_timeout);
+    if (!sx->ok && errno == 0) switch(sx->buffer[0])
       {
-      case '2': sx.prdr_active = FALSE;
-		sx.ok = TRUE;
+      case '2': sx->prdr_active = FALSE;
+		sx->ok = TRUE;
 		break;
       case '4': errno = ERRNO_DATA4XX;
 		addrlist->more_errno |=
-		  ((sx.buffer[1] - '0')*10 + sx.buffer[2] - '0') << 8;
+		  ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
 		break;
       }
     }
@@ -3745,14 +3745,14 @@ else
   /* For non-PRDR SMTP, we now read a single response that applies to the
   whole message.  If it is OK, then all the addresses have been delivered. */
 
-  if (!sx.lmtp)
+  if (!sx->lmtp)
     {
-    sx.ok = smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '2',
+    sx->ok = smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2',
       ob->final_timeout);
-    if (!sx.ok && errno == 0 && sx.buffer[0] == '4')
+    if (!sx->ok && errno == 0 && sx->buffer[0] == '4')
       {
       errno = ERRNO_DATA4XX;
-      addrlist->more_errno |= ((sx.buffer[1] - '0')*10 + sx.buffer[2] - '0') << 8;
+      addrlist->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
       }
     }
 
@@ -3768,7 +3768,7 @@ else
   software before the spool gets updated. Also record the final SMTP
   confirmation if needed (for SMTP only). */
 
-  if (sx.ok)
+  if (sx->ok)
     {
     int flag = '=';
     struct timeval delivery_time;
@@ -3776,7 +3776,7 @@ else
     uschar * conf = NULL;
 
     timesince(&delivery_time, &start_delivery_time);
-    sx.send_rset = FALSE;
+    sx->send_rset = FALSE;
     pipelining_active = FALSE;
 
     /* Set up confirmation if needed - applies only to SMTP */
@@ -3785,18 +3785,18 @@ else
 #ifdef DISABLE_EVENT
           LOGGING(smtp_confirmation) &&
 #endif
-          !sx.lmtp
+          !sx->lmtp
        )
       {
-      const uschar *s = string_printing(sx.buffer);
+      const uschar *s = string_printing(sx->buffer);
       /* deconst cast ok here as string_printing was checked to have alloc'n'copied */
-      conf = (s == sx.buffer)? US string_copy(s) : US s;
+      conf = (s == sx->buffer)? US string_copy(s) : US s;
       }
 
     /* Process all transported addresses - for LMTP or PRDR, read a status for
     each one. */
 
-    for (address_item * addr = addrlist; addr != sx.first_addr; addr = addr->next)
+    for (address_item * addr = addrlist; addr != sx->first_addr; addr = addr->next)
       {
       if (addr->transport_return != PENDING_OK) continue;
 
@@ -3806,43 +3806,43 @@ else
       it doesn't get tried again too soon. */
 
 #ifndef DISABLE_PRDR
-      if (sx.lmtp || sx.prdr_active)
+      if (sx->lmtp || sx->prdr_active)
 #else
-      if (sx.lmtp)
+      if (sx->lmtp)
 #endif
         {
-        if (!smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '2',
+        if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2',
             ob->final_timeout))
           {
-          if (errno != 0 || sx.buffer[0] == 0) goto RESPONSE_FAILED;
+          if (errno != 0 || sx->buffer[0] == 0) goto RESPONSE_FAILED;
           addr->message = string_sprintf(
 #ifndef DISABLE_PRDR
-	    "%s error after %s: %s", sx.prdr_active ? "PRDR":"LMTP",
+	    "%s error after %s: %s", sx->prdr_active ? "PRDR":"LMTP",
 #else
 	    "LMTP error after %s: %s",
 #endif
-	    data_command, string_printing(sx.buffer));
+	    data_command, string_printing(sx->buffer));
           setflag(addr, af_pass_message);   /* Allow message to go to user */
-          if (sx.buffer[0] == '5')
+          if (sx->buffer[0] == '5')
             addr->transport_return = FAIL;
           else
             {
             errno = ERRNO_DATA4XX;
-            addr->more_errno |= ((sx.buffer[1] - '0')*10 + sx.buffer[2] - '0') << 8;
+            addr->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
             addr->transport_return = DEFER;
 #ifndef DISABLE_PRDR
-            if (!sx.prdr_active)
+            if (!sx->prdr_active)
 #endif
               retry_add_item(addr, addr->address_retry_key, 0);
             }
           continue;
           }
-        sx.completed_addr = TRUE;   /* NOW we can set this flag */
+        sx->completed_addr = TRUE;   /* NOW we can set this flag */
         if (LOGGING(smtp_confirmation))
           {
-          const uschar *s = string_printing(sx.buffer);
+          const uschar *s = string_printing(sx->buffer);
 	  /* deconst cast ok here as string_printing was checked to have alloc'n'copied */
-          conf = (s == sx.buffer) ? US string_copy(s) : US s;
+          conf = (s == sx->buffer) ? US string_copy(s) : US s;
           }
         }
 
@@ -3862,18 +3862,18 @@ else
 	if (tcp_out_fastopen >= TFO_USED_NODATA) setflag(addr, af_tcp_fastopen);
 	if (tcp_out_fastopen >= TFO_USED_DATA) setflag(addr, af_tcp_fastopen_data);
 	}
-      if (sx.pipelining_used) setflag(addr, af_pipelining);
+      if (sx->pipelining_used) setflag(addr, af_pipelining);
 #ifdef SUPPORT_PIPE_CONNECT
-      if (sx.early_pipe_active) setflag(addr, af_early_pipe);
+      if (sx->early_pipe_active) setflag(addr, af_early_pipe);
 #endif
 #ifndef DISABLE_PRDR
-      if (sx.prdr_active) setflag(addr, af_prdr_used);
+      if (sx->prdr_active) setflag(addr, af_prdr_used);
 #endif
-      if (sx.peer_offered & OPTION_CHUNKING) setflag(addr, af_chunking_used);
+      if (sx->peer_offered & OPTION_CHUNKING) setflag(addr, af_chunking_used);
       flag = '-';
 
 #ifndef DISABLE_PRDR
-      if (!sx.prdr_active)
+      if (!sx->prdr_active)
 #endif
         {
         /* Update the journal. For homonymic addresses, use the base address plus
@@ -3882,37 +3882,37 @@ else
         write error, as it may prove possible to update the spool file later. */
 
         if (testflag(addr, af_homonym))
-          sprintf(CS sx.buffer, "%.500s/%s\n", addr->unique + 3, tblock->name);
+          sprintf(CS sx->buffer, "%.500s/%s\n", addr->unique + 3, tblock->name);
         else
-          sprintf(CS sx.buffer, "%.500s\n", addr->unique);
+          sprintf(CS sx->buffer, "%.500s\n", addr->unique);
 
-        DEBUG(D_deliver) debug_printf("S:journalling %s\n", sx.buffer);
-        len = Ustrlen(CS sx.buffer);
-        if (write(journal_fd, sx.buffer, len) != len)
+        DEBUG(D_deliver) debug_printf("S:journalling %s\n", sx->buffer);
+        len = Ustrlen(CS sx->buffer);
+        if (write(journal_fd, sx->buffer, len) != len)
           log_write(0, LOG_MAIN|LOG_PANIC, "failed to write journal for "
-            "%s: %s", sx.buffer, strerror(errno));
+            "%s: %s", sx->buffer, strerror(errno));
         }
       }
 
 #ifndef DISABLE_PRDR
-      if (sx.prdr_active)
+      if (sx->prdr_active)
         {
 	const uschar * overall_message;
 
 	/* PRDR - get the final, overall response.  For any non-success
 	upgrade all the address statuses. */
 
-        sx.ok = smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '2',
+        sx->ok = smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2',
           ob->final_timeout);
-        if (!sx.ok)
+        if (!sx->ok)
 	  {
-	  if(errno == 0 && sx.buffer[0] == '4')
+	  if(errno == 0 && sx->buffer[0] == '4')
             {
             errno = ERRNO_DATA4XX;
-            addrlist->more_errno |= ((sx.buffer[1] - '0')*10 + sx.buffer[2] - '0') << 8;
+            addrlist->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
             }
-	  for (address_item * addr = addrlist; addr != sx.first_addr; addr = addr->next)
-            if (sx.buffer[0] == '5' || addr->transport_return == OK)
+	  for (address_item * addr = addrlist; addr != sx->first_addr; addr = addr->next)
+            if (sx->buffer[0] == '5' || addr->transport_return == OK)
               addr->transport_return = PENDING_OK; /* allow set_errno action */
 	  goto RESPONSE_FAILED;
 	  }
@@ -3920,24 +3920,24 @@ else
 	/* Append the overall response to the individual PRDR response for logging
 	and update the journal, or setup retry. */
 
-	overall_message = string_printing(sx.buffer);
-        for (address_item * addr = addrlist; addr != sx.first_addr; addr = addr->next)
+	overall_message = string_printing(sx->buffer);
+        for (address_item * addr = addrlist; addr != sx->first_addr; addr = addr->next)
 	  if (addr->transport_return == OK)
 	    addr->message = string_sprintf("%s\\n%s", addr->message, overall_message);
 
-        for (address_item * addr = addrlist; addr != sx.first_addr; addr = addr->next)
+        for (address_item * addr = addrlist; addr != sx->first_addr; addr = addr->next)
 	  if (addr->transport_return == OK)
 	    {
 	    if (testflag(addr, af_homonym))
-	      sprintf(CS sx.buffer, "%.500s/%s\n", addr->unique + 3, tblock->name);
+	      sprintf(CS sx->buffer, "%.500s/%s\n", addr->unique + 3, tblock->name);
 	    else
-	      sprintf(CS sx.buffer, "%.500s\n", addr->unique);
+	      sprintf(CS sx->buffer, "%.500s\n", addr->unique);
 
-	    DEBUG(D_deliver) debug_printf("journalling(PRDR) %s\n", sx.buffer);
-	    len = Ustrlen(CS sx.buffer);
-	    if (write(journal_fd, sx.buffer, len) != len)
+	    DEBUG(D_deliver) debug_printf("journalling(PRDR) %s\n", sx->buffer);
+	    len = Ustrlen(CS sx->buffer);
+	    if (write(journal_fd, sx->buffer, len) != len)
 	      log_write(0, LOG_MAIN|LOG_PANIC, "failed to write journal for "
-		"%s: %s", sx.buffer, strerror(errno));
+		"%s: %s", sx->buffer, strerror(errno));
 	    }
 	  else if (addr->transport_return == DEFER)
 	    retry_add_item(addr, addr->address_retry_key, -2);
@@ -3961,7 +3961,7 @@ assumed if errno == 0 and there is no text in the buffer. If control reaches
 here during the setting up phase (i.e. before MAIL FROM) then always defer, as
 the problem is not related to this specific message. */
 
-if (!sx.ok)
+if (!sx->ok)
   {
   int code, set_rc;
   uschar * set_message;
@@ -3970,8 +3970,8 @@ if (!sx.ok)
     {
     save_errno = errno;
     message = NULL;
-    sx.send_quit = check_response(host, &save_errno, addrlist->more_errno,
-      sx.buffer, &code, &message, &pass_message);
+    sx->send_quit = check_response(host, &save_errno, addrlist->more_errno,
+      sx->buffer, &code, &message, &pass_message);
     goto FAILED;
     }
 
@@ -3981,7 +3981,7 @@ if (!sx.ok)
     code = '4';
     message = string_sprintf("send() to %s [%s] failed: %s",
       host->name, host->address, message ? message : US strerror(save_errno));
-    sx.send_quit = FALSE;
+    sx->send_quit = FALSE;
     goto FAILED;
     }
 
@@ -3989,7 +3989,7 @@ if (!sx.ok)
     {
     BOOL message_error;
 
-    sx.ok = FALSE;                /* For when reached by GOTO */
+    sx->ok = FALSE;                /* For when reached by GOTO */
     set_message = message;
 
   /* We want to handle timeouts after MAIL or "." and loss of connection after
@@ -4051,7 +4051,7 @@ if (!sx.ok)
         if (save_errno > 0)
           message = US string_sprintf("%s: %s", message, strerror(save_errno));
 
-        write_logs(host, message, sx.first_addr ? sx.first_addr->basic_errno : 0);
+        write_logs(host, message, sx->first_addr ? sx->first_addr->basic_errno : 0);
 
         *message_defer = TRUE;
         }
@@ -4084,7 +4084,7 @@ if (!sx.ok)
 
   set_errno(addrlist, save_errno, set_message, set_rc, pass_message, host
 #ifdef EXPERIMENTAL_DSN_INFO
-	    , sx.smtp_greeting, sx.helo_response
+	    , sx->smtp_greeting, sx->helo_response
 #endif
 	    );
   }
@@ -4120,10 +4120,10 @@ hosts_nopass_tls. */
 
 DEBUG(D_transport)
   debug_printf("ok=%d send_quit=%d send_rset=%d continue_more=%d "
-    "yield=%d first_address is %sNULL\n", sx.ok, sx.send_quit,
-    sx.send_rset, f.continue_more, yield, sx.first_addr ? "not " : "");
+    "yield=%d first_address is %sNULL\n", sx->ok, sx->send_quit,
+    sx->send_rset, f.continue_more, yield, sx->first_addr ? "not " : "");
 
-if (sx.completed_addr && sx.ok && sx.send_quit)
+if (sx->completed_addr && sx->ok && sx->send_quit)
   {
   BOOL more;
   smtp_compare_t t_compare;
@@ -4131,7 +4131,7 @@ if (sx.completed_addr && sx.ok && sx.send_quit)
   t_compare.tblock = tblock;
   t_compare.current_sender_address = sender_address;
 
-  if (  sx.first_addr != NULL
+  if (  sx->first_addr != NULL
      || f.continue_more
      || (
 #ifndef DISABLE_TLS
@@ -4148,20 +4148,20 @@ if (sx.completed_addr && sx.ok && sx.send_quit)
     uschar *msg;
     BOOL pass_message;
 
-    if (sx.send_rset)
-      if (! (sx.ok = smtp_write_command(&sx, SCMD_FLUSH, "RSET\r\n") >= 0))
+    if (sx->send_rset)
+      if (! (sx->ok = smtp_write_command(sx, SCMD_FLUSH, "RSET\r\n") >= 0))
         {
         msg = US string_sprintf("send() to %s [%s] failed: %s", host->name,
           host->address, strerror(errno));
-        sx.send_quit = FALSE;
+        sx->send_quit = FALSE;
         }
-      else if (! (sx.ok = smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer),
+      else if (! (sx->ok = smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
 		  '2', ob->command_timeout)))
         {
         int code;
-        sx.send_quit = check_response(host, &errno, 0, sx.buffer, &code, &msg,
+        sx->send_quit = check_response(host, &errno, 0, sx->buffer, &code, &msg,
           &pass_message);
-        if (!sx.send_quit)
+        if (!sx->send_quit)
           {
           DEBUG(D_transport) debug_printf("H=%s [%s] %s\n",
 	    host->name, host->address, msg);
@@ -4170,15 +4170,15 @@ if (sx.completed_addr && sx.ok && sx.send_quit)
 
     /* Either RSET was not needed, or it succeeded */
 
-    if (sx.ok)
+    if (sx->ok)
       {
 #ifndef DISABLE_TLS
       int pfd[2];
 #endif
-      int socket_fd = sx.cctx.sock;
+      int socket_fd = sx->cctx.sock;
 
 
-      if (sx.first_addr != NULL)         /* More addresses still to be sent */
+      if (sx->first_addr != NULL)         /* More addresses still to be sent */
         {                                /*   in this run of the transport */
         continue_sequence++;             /* Causes * in logging */
         goto SEND_MESSAGE;
@@ -4200,16 +4200,16 @@ if (sx.completed_addr && sx.ok && sx.send_quit)
 	  a new EHLO. If we don't get a good response, we don't attempt to pass
 	  the socket on. */
 
-	  tls_close(sx.cctx.tls_ctx, TLS_SHUTDOWN_WAIT);
-	  sx.cctx.tls_ctx = NULL;
+	  tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_WAIT);
+	  sx->cctx.tls_ctx = NULL;
 	  smtp_peer_options = smtp_peer_options_wrap;
-	  sx.ok = !sx.smtps
-	    && smtp_write_command(&sx, SCMD_FLUSH, "EHLO %s\r\n", sx.helo_data)
+	  sx->ok = !sx->smtps
+	    && smtp_write_command(sx, SCMD_FLUSH, "EHLO %s\r\n", sx->helo_data)
 		>= 0
-	    && smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer),
+	    && smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
 				      '2', ob->command_timeout);
 
-	  if (sx.ok && f.continue_more)
+	  if (sx->ok && f.continue_more)
 	    return yield;		/* More addresses for another run */
 	  }
 	else
@@ -4217,13 +4217,13 @@ if (sx.completed_addr && sx.ok && sx.send_quit)
 	  /* Set up a pipe for proxying TLS for the new transport process */
 
 	  smtp_peer_options |= OPTION_TLS;
-	  if ((sx.ok = socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) == 0))
+	  if ((sx->ok = socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) == 0))
 	    socket_fd = pfd[1];
 	  else
-	    set_errno(sx.first_addr, errno, US"internal allocation problem",
+	    set_errno(sx->first_addr, errno, US"internal allocation problem",
 		    DEFER, FALSE, host
 # ifdef EXPERIMENTAL_DSN_INFO
-		    , sx.smtp_greeting, sx.helo_response
+		    , sx->smtp_greeting, sx->helo_response
 # endif
 		    );
 	  }
@@ -4238,10 +4238,10 @@ if (sx.completed_addr && sx.ok && sx.send_quit)
 /*XXX DSN_INFO: assume likely to do new HELO; but for greet we'll want to
 propagate it from the initial
 */
-      if (sx.ok && transport_pass_socket(tblock->name, host->name,
+      if (sx->ok && transport_pass_socket(tblock->name, host->name,
 	    host->address, new_message_id, socket_fd))
 	{
-        sx.send_quit = FALSE;
+        sx->send_quit = FALSE;
 
 	/* We have passed the client socket to a fresh transport process.
 	If TLS is still active, we need to proxy it for the transport we
@@ -4256,7 +4256,7 @@ propagate it from the initial
 	    {
 	    testharness_pause_ms(100); /* let parent debug out */
 	    /* does not return */
-	    smtp_proxy_tls(sx.cctx.tls_ctx, sx.buffer, sizeof(sx.buffer), pfd,
+	    smtp_proxy_tls(sx->cctx.tls_ctx, sx->buffer, sizeof(sx->buffer), pfd,
 			    ob->command_timeout);
 	    }
 
@@ -4266,10 +4266,10 @@ propagate it from the initial
 	    close(pfd[0]);
 	    /* tidy the inter-proc to disconn the proxy proc */
 	    waitpid(pid, NULL, 0);
-	    tls_close(sx.cctx.tls_ctx, TLS_NO_SHUTDOWN);
-	    sx.cctx.tls_ctx = NULL;
-	    (void)close(sx.cctx.sock);
-	    sx.cctx.sock = -1;
+	    tls_close(sx->cctx.tls_ctx, TLS_NO_SHUTDOWN);
+	    sx->cctx.tls_ctx = NULL;
+	    (void)close(sx->cctx.sock);
+	    sx->cctx.sock = -1;
 	    continue_transport = NULL;
 	    continue_hostname = NULL;
 	    return yield;
@@ -4282,9 +4282,9 @@ propagate it from the initial
 
     /* If RSET failed and there are addresses left, they get deferred. */
     else
-      set_errno(sx.first_addr, errno, msg, DEFER, FALSE, host
+      set_errno(sx->first_addr, errno, msg, DEFER, FALSE, host
 #ifdef EXPERIMENTAL_DSN_INFO
-		  , sx.smtp_greeting, sx.helo_response
+		  , sx->smtp_greeting, sx->helo_response
 #endif
 		  );
     }
@@ -4310,15 +4310,15 @@ operation, the old commented-out code was removed on 17-Sep-99. */
 
 SEND_QUIT:
 #ifdef TCP_CORK
-(void) setsockopt(sx.cctx.sock, IPPROTO_TCP, TCP_CORK, US &on, sizeof(on));
+(void) setsockopt(sx->cctx.sock, IPPROTO_TCP, TCP_CORK, US &on, sizeof(on));
 #endif
-if (sx.send_quit) (void)smtp_write_command(&sx, SCMD_FLUSH, "QUIT\r\n");
+if (sx->send_quit) (void)smtp_write_command(sx, SCMD_FLUSH, "QUIT\r\n");
 
 END_OFF:
 
 #ifndef DISABLE_TLS
-tls_close(sx.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
-sx.cctx.tls_ctx = NULL;
+tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+sx->cctx.tls_ctx = NULL;
 #endif
 
 /* Close the socket, and return the appropriate value, first setting
@@ -4332,16 +4332,16 @@ specified in the transports, and therefore not visible at top level, in which
 case continue_more won't get set. */
 
 HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
-if (sx.send_quit)
+if (sx->send_quit)
   {
-  shutdown(sx.cctx.sock, SHUT_WR);
+  shutdown(sx->cctx.sock, SHUT_WR);
   millisleep(20);
   testharness_pause_ms(200);
-  if (fcntl(sx.cctx.sock, F_SETFL, O_NONBLOCK) == 0)
-    for (int i = 16; read(sx.cctx.sock, sx.inbuffer, sizeof(sx.inbuffer)) > 0 && i > 0;)
+  if (fcntl(sx->cctx.sock, F_SETFL, O_NONBLOCK) == 0)
+    for (int i = 16; read(sx->cctx.sock, sx->inbuffer, sizeof(sx->inbuffer)) > 0 && i > 0;)
       i--;				/* drain socket */
   }
-(void)close(sx.cctx.sock);
+(void)close(sx->cctx.sock);
 
 #ifndef DISABLE_EVENT
 (void) event_raise(tblock->event_action, US"tcp:close", NULL);
diff --git a/src/src/verify.c b/src/src/verify.c
index fc8cd84ea..5c0678c4b 100644
--- a/src/src/verify.c
+++ b/src/src/verify.c
@@ -625,8 +625,8 @@ coding means skipping this whole loop and doing the append separately.  */
     {
     int host_af;
     int port = 25;
-    uschar *interface = NULL;  /* Outgoing interface to use; NULL => any */
-    smtp_context sx;
+    uschar * interface = NULL;  /* Outgoing interface to use; NULL => any */
+    smtp_context * sx = store_get(sizeof(*sx), TRUE);	/* tainted buffers */
 
     if (!host->address)
       {
@@ -666,14 +666,14 @@ coding means skipping this whole loop and doing the append separately.  */
       log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address,
         addr->message);
 
-    sx.addrlist = addr;
-    sx.conn_args.host = host;
-    sx.conn_args.host_af = host_af,
-    sx.port = port;
-    sx.conn_args.interface = interface;
-    sx.helo_data = tf->helo_data;
-    sx.conn_args.tblock = addr->transport;
-    sx.verify = TRUE;
+    sx->addrlist = addr;
+    sx->conn_args.host = host;
+    sx->conn_args.host_af = host_af,
+    sx->port = port;
+    sx->conn_args.interface = interface;
+    sx->helo_data = tf->helo_data;
+    sx->conn_args.tblock = addr->transport;
+    sx->verify = TRUE;
 
 tls_retry_connection:
     /* Set the address state so that errors are recorded in it */
@@ -686,7 +686,7 @@ tls_retry_connection:
     SMTP command to send.  If we tried TLS but it failed, try again without
     if permitted */
 
-    yield = smtp_setup_conn(&sx, FALSE);
+    yield = smtp_setup_conn(sx, FALSE);
 #ifndef DISABLE_TLS
     if (  yield == DEFER
        && addr->basic_errno == ERRNO_TLSFAILURE
@@ -698,7 +698,7 @@ tls_retry_connection:
 	"%s: callout unencrypted to %s [%s] (not in hosts_require_tls)",
 	addr->message, host->name, host->address);
       addr->transport_return = PENDING_DEFER;
-      yield = smtp_setup_conn(&sx, TRUE);
+      yield = smtp_setup_conn(sx, TRUE);
       }
 #endif
     if (yield != OK)
@@ -728,11 +728,11 @@ tls_retry_connection:
     addr->authenticator = client_authenticator;
     addr->auth_id = client_authenticated_id;
 
-    sx.from_addr = from_address;
-    sx.first_addr = sx.sync_addr = addr;
-    sx.ok = FALSE;			/*XXX these 3 last might not be needed for verify? */
-    sx.send_rset = TRUE;
-    sx.completed_addr = FALSE;
+    sx->from_addr = from_address;
+    sx->first_addr = sx->sync_addr = addr;
+    sx->ok = FALSE;			/*XXX these 3 last might not be needed for verify? */
+    sx->send_rset = TRUE;
+    sx->completed_addr = FALSE;
 
     new_domain_record.result = old_domain_cache_result == ccache_reject_mfnull
       ? ccache_reject_mfnull : ccache_accept;
@@ -789,12 +789,12 @@ tls_retry_connection:
       Avoid using a SIZE option on the MAIL for all random-rcpt checks.
       */
 
-      sx.avoid_option = OPTION_SIZE;
+      sx->avoid_option = OPTION_SIZE;
 
       /* Remember when we last did a random test */
       new_domain_record.random_stamp = time(NULL);
 
-      if (smtp_write_mail_and_rcpt_cmds(&sx, &yield) == 0)
+      if (smtp_write_mail_and_rcpt_cmds(sx, &yield) == 0)
 	switch(addr->transport_return)
 	  {
 	  case PENDING_OK:	/* random was accepted, unfortunately */
@@ -805,36 +805,36 @@ tls_retry_connection:
 	    goto no_conn;
 	  case FAIL:		/* rejected: the preferred result */
 	    new_domain_record.random_result = ccache_reject;
-	    sx.avoid_option = 0;
+	    sx->avoid_option = 0;
 
 	    /* Between each check, issue RSET, because some servers accept only
 	    one recipient after MAIL FROM:<>.
 	    XXX We don't care about that for postmaster_full.  Should we? */
 
 	    if ((done =
-	      smtp_write_command(&sx, SCMD_FLUSH, "RSET\r\n") >= 0 &&
-	      smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '2', callout)))
+	      smtp_write_command(sx, SCMD_FLUSH, "RSET\r\n") >= 0 &&
+	      smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2', callout)))
 	      break;
 
 	    HDEBUG(D_acl|D_v)
 	      debug_printf_indent("problem after random/rset/mfrom; reopen conn\n");
 	    random_local_part = NULL;
 #ifndef DISABLE_TLS
-	    tls_close(sx.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+	    tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
 #endif
 	    HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
-	    (void)close(sx.cctx.sock);
-	    sx.cctx.sock = -1;
+	    (void)close(sx->cctx.sock);
+	    sx->cctx.sock = -1;
 #ifndef DISABLE_EVENT
 	    (void) event_raise(addr->transport->event_action,
 			      US"tcp:close", NULL);
 #endif
 	    addr->address = main_address;
 	    addr->transport_return = PENDING_DEFER;
-	    sx.first_addr = sx.sync_addr = addr;
-	    sx.ok = FALSE;
-	    sx.send_rset = TRUE;
-	    sx.completed_addr = FALSE;
+	    sx->first_addr = sx->sync_addr = addr;
+	    sx->ok = FALSE;
+	    sx->send_rset = TRUE;
+	    sx->completed_addr = FALSE;
 	    goto tls_retry_connection;
 	  case DEFER:		/* 4xx response to random */
 	    break;		/* Just to be clear. ccache_unknown, !done. */
@@ -843,10 +843,10 @@ tls_retry_connection:
       /* Re-setup for main verify, or for the error message when failing */
       addr->address = main_address;
       addr->transport_return = PENDING_DEFER;
-      sx.first_addr = sx.sync_addr = addr;
-      sx.ok = FALSE;
-      sx.send_rset = TRUE;
-      sx.completed_addr = FALSE;
+      sx->first_addr = sx->sync_addr = addr;
+      sx->ok = FALSE;
+      sx->send_rset = TRUE;
+      sx->completed_addr = FALSE;
       }
     else
       done = TRUE;
@@ -857,10 +857,10 @@ tls_retry_connection:
     if (done)
       {
       if (!(options & vopt_is_recipient  &&  options & vopt_callout_no_cache))
-	sx.avoid_option = OPTION_SIZE;
+	sx->avoid_option = OPTION_SIZE;
 
       done = FALSE;
-      switch(smtp_write_mail_and_rcpt_cmds(&sx, &yield))
+      switch(smtp_write_mail_and_rcpt_cmds(sx, &yield))
 	{
 	case 0:  switch(addr->transport_return)	/* ok so far */
 		    {
@@ -878,7 +878,7 @@ tls_retry_connection:
 
 	case -1:				/* MAIL response error */
 		  *failure_ptr = US"mail";
-		  if (errno == 0 && sx.buffer[0] == '5')
+		  if (errno == 0 && sx->buffer[0] == '5')
 		    {
 		    setflag(addr, af_verify_nsfail);
 		    if (from_address[0] == 0)
@@ -908,8 +908,8 @@ tls_retry_connection:
       cancel_cutthrough_connection(TRUE, US"postmaster verify");
       HDEBUG(D_acl|D_v) debug_printf_indent("Cutthrough cancelled by presence of postmaster verify\n");
 
-      done = smtp_write_command(&sx, SCMD_FLUSH, "RSET\r\n") >= 0
-          && smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '2', callout);
+      done = smtp_write_command(sx, SCMD_FLUSH, "RSET\r\n") >= 0
+          && smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2', callout);
 
       if (done)
 	{
@@ -919,23 +919,23 @@ tls_retry_connection:
 	addr->address = string_sprintf("postmaster@%.1000s", addr->domain);
 	addr->transport_return = PENDING_DEFER;
 
-	sx.from_addr = pm_mailfrom;
-	sx.first_addr = sx.sync_addr = addr;
-	sx.ok = FALSE;
-	sx.send_rset = TRUE;
-	sx.completed_addr = FALSE;
-	sx.avoid_option = OPTION_SIZE;
+	sx->from_addr = pm_mailfrom;
+	sx->first_addr = sx->sync_addr = addr;
+	sx->ok = FALSE;
+	sx->send_rset = TRUE;
+	sx->completed_addr = FALSE;
+	sx->avoid_option = OPTION_SIZE;
 
-	if(  smtp_write_mail_and_rcpt_cmds(&sx, &yield) == 0
+	if(  smtp_write_mail_and_rcpt_cmds(sx, &yield) == 0
 	  && addr->transport_return == PENDING_OK
 	  )
 	  done = TRUE;
 	else
 	  done = (options & vopt_callout_fullpm) != 0
-	      && smtp_write_command(&sx, SCMD_FLUSH,
+	      && smtp_write_command(sx, SCMD_FLUSH,
 			    "RCPT TO:<postmaster>\r\n") >= 0
-	      && smtp_read_response(&sx, sx.buffer,
-			    sizeof(sx.buffer), '2', callout);
+	      && smtp_read_response(sx, sx->buffer,
+			    sizeof(sx->buffer), '2', callout);
 
 	/* Sort out the cache record */
 
@@ -943,7 +943,7 @@ tls_retry_connection:
 
 	if (done)
 	  new_domain_record.postmaster_result = ccache_accept;
-	else if (errno == 0 && sx.buffer[0] == '5')
+	else if (errno == 0 && sx->buffer[0] == '5')
 	  {
 	  *failure_ptr = US"postmaster";
 	  setflag(addr, af_verify_pmfail);
@@ -968,7 +968,7 @@ no_conn:
       {
       case ETIMEDOUT:
 	HDEBUG(D_verify) debug_printf("SMTP timeout\n");
-	sx.send_quit = FALSE;
+	sx->send_quit = FALSE;
 	break;
 
 #ifdef SUPPORT_I18N
@@ -986,11 +986,11 @@ no_conn:
 	break;
 #endif
       case ECONNREFUSED:
-	sx.send_quit = FALSE;
+	sx->send_quit = FALSE;
 	break;
 
       case 0:
-	if (*sx.buffer == 0) Ustrcpy(sx.buffer, US"connection dropped");
+	if (*sx->buffer == 0) Ustrcpy(sx->buffer, US"connection dropped");
 
 	/*XXX test here is ugly; seem to have a split of responsibility for
 	building this message.  Need to rationalise.  Where is it done
@@ -999,7 +999,7 @@ no_conn:
 	*/
 	if (!addr->message) addr->message =
 	  string_sprintf("response to \"%s\" was: %s",
-			  big_buffer, string_printing(sx.buffer));
+			  big_buffer, string_printing(sx->buffer));
 
 	/* RFC 5321 section 4.2: the text portion of the response may have only
 	HT, SP, Printable US-ASCII.  Deal with awkward chars by cutting the
@@ -1007,14 +1007,14 @@ no_conn:
 	just become a multiline response (but wrapped in the error code we
 	produce). */
 
-	for (uschar * s = sx.buffer;
-	     *s && s < sx.buffer + sizeof(sx.buffer);
+	for (uschar * s = sx->buffer;
+	     *s && s < sx->buffer + sizeof(sx->buffer);
 	     s++)
 	  {
 	  uschar c = *s;
 	  if (c != '\t' && c != '\n' && (c < ' ' || c > '~'))
 	    {
-	    if (s - sx.buffer < sizeof(sx.buffer) - 12)
+	    if (s - sx->buffer < sizeof(sx->buffer) - 12)
 	      memcpy(s, "(truncated)", 12);
 	    else
 	      *s = '\0';
@@ -1022,13 +1022,13 @@ no_conn:
 	    }
 	  }
 	addr->user_message = options & vopt_is_recipient
-	  ? string_sprintf("Callout verification failed:\n%s", sx.buffer)
+	  ? string_sprintf("Callout verification failed:\n%s", sx->buffer)
 	  : string_sprintf("Called:   %s\nSent:     %s\nResponse: %s",
-	    host->address, big_buffer, sx.buffer);
+	    host->address, big_buffer, sx->buffer);
 
 	/* Hard rejection ends the process */
 
-	if (sx.buffer[0] == '5')   /* Address rejected */
+	if (sx->buffer[0] == '5')   /* Address rejected */
 	  {
 	  yield = FAIL;
 	  done = TRUE;
@@ -1075,7 +1075,7 @@ no_conn:
        && !random_local_part
        && !pm_mailfrom
        && cutthrough.cctx.sock < 0
-       && !sx.lmtp
+       && !sx->lmtp
        )
       {
       HDEBUG(D_acl|D_v) debug_printf_indent("holding verify callout open for %s\n",
@@ -1085,7 +1085,7 @@ no_conn:
       cutthrough.callout_hold_only = !cutthrough.delivery;
       cutthrough.is_tls =	tls_out.active.sock >= 0;
       /* We assume no buffer in use in the outblock */
-      cutthrough.cctx =		sx.cctx;
+      cutthrough.cctx =		sx->cctx;
       cutthrough.nrcpt =	1;
       cutthrough.transport =	addr->transport->name;
       cutthrough.interface =	interface;
@@ -1121,23 +1121,23 @@ no_conn:
       /* Ensure no cutthrough on multiple verifies that were incompatible */
       if (options & vopt_callout_recipsender)
         cancel_cutthrough_connection(TRUE, US"not usable for cutthrough");
-      if (sx.send_quit)
-	if (smtp_write_command(&sx, SCMD_FLUSH, "QUIT\r\n") != -1)
+      if (sx->send_quit)
+	if (smtp_write_command(sx, SCMD_FLUSH, "QUIT\r\n") != -1)
 	  /* Wait a short time for response, and discard it */
-	  smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '2', 1);
+	  smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2', 1);
 
-      if (sx.cctx.sock >= 0)
+      if (sx->cctx.sock >= 0)
 	{
 #ifndef DISABLE_TLS
-	if (sx.cctx.tls_ctx)
+	if (sx->cctx.tls_ctx)
 	  {
-	  tls_close(sx.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
-	  sx.cctx.tls_ctx = NULL;
+	  tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+	  sx->cctx.tls_ctx = NULL;
 	  }
 #endif
 	HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP(close)>>\n");
-	(void)close(sx.cctx.sock);
-	sx.cctx.sock = -1;
+	(void)close(sx->cctx.sock);
+	sx->cctx.sock = -1;
 #ifndef DISABLE_EVENT
 	(void) event_raise(addr->transport->event_action, US"tcp:close", NULL);
 #endif

commit 92a158a6b870886fef89ecc084e6e745f91060e7
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Thu Dec 12 14:06:54 2019 +0000

    ARC: fix crash induced by misordered headers.  Bug 2493
    
    (cherry picked from commit e9dfcfb7b87107c2a55441c93aa463f9ea7586e1)

diff --git a/src/src/arc.c b/src/src/arc.c
index 857e0c046..94365498f 100644
--- a/src/src/arc.c
+++ b/src/src/arc.c
@@ -381,7 +381,7 @@ adding instances as needed and checking for duplicate lines.
 
 static uschar *
 arc_insert_hdr(arc_ctx * ctx, header_line * h, unsigned off, unsigned hoff,
-  BOOL instance_only)
+  BOOL instance_only, arc_line ** alp_ret)
 {
 unsigned i;
 arc_set * as;
@@ -401,6 +401,7 @@ if (!(as = arc_find_set(ctx, i)))	return US"set find";
 if (*(alp = (arc_line **)(US as + hoff))) return US"dup hdr";
 
 *alp = al;
+if (alp_ret) *alp_ret = al;
 return NULL;
 }
 
@@ -424,7 +425,7 @@ if (strncmpic(ARC_HDR_AAR, h->text, ARC_HDRLEN_AAR) == 0)
     debug_printf("ARC: found AAR: %.*s\n", len, h->text);
     }
   if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AAR, offsetof(arc_set, hdr_aar),
-			  TRUE)))
+			  TRUE, NULL)))
     {
     DEBUG(D_acl) debug_printf("inserting AAR: %s\n", e);
     return US"inserting AAR";
@@ -443,15 +444,13 @@ else if (strncmpic(ARC_HDR_AMS, h->text, ARC_HDRLEN_AMS) == 0)
     debug_printf("ARC: found AMS: %.*s\n", len, h->text);
     }
   if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AMS, offsetof(arc_set, hdr_ams),
-			  instance_only)))
+			  instance_only, &ams)))
     {
     DEBUG(D_acl) debug_printf("inserting AMS: %s\n", e);
     return US"inserting AMS";
     }
 
   /* defaults */
-  /*XXX dubious selection of ams here */
-  ams = ctx->arcset_chain->hdr_ams;
   if (!ams->c.data)
     {
     ams->c_head.data = US"simple"; ams->c_head.len = 6;
@@ -469,7 +468,7 @@ else if (strncmpic(ARC_HDR_AS, h->text, ARC_HDRLEN_AS) == 0)
     debug_printf("ARC: found AS: %.*s\n", len, h->text);
     }
   if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AS, offsetof(arc_set, hdr_as),
-			  instance_only)))
+			  instance_only, NULL)))
     {
     DEBUG(D_acl) debug_printf("inserting AS: %s\n", e);
     return US"inserting AS";

commit 2529a7a64df7aa824d453c6419af07825409e519
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Thu Dec 12 23:43:10 2019 +0000

    Fix taint issue with retry records.  Bug 2492
    
    (cherry picked from commit 5fae29d5b430d6a5f58c6c02cdefbbf307e258a9)

diff --git a/src/src/retry.c b/src/src/retry.c
index d068f547d..175da216e 100644
--- a/src/src/retry.c
+++ b/src/src/retry.c
@@ -659,7 +659,8 @@ for (int i = 0; i < 3; i++)
         /* Read a retry record from the database or construct a new one.
         Ignore an old one if it is too old since it was last updated. */
 
-        retry_record = dbfn_read(dbm_file, rti->key);
+        retry_record = dbfn_read_with_length(dbm_file, rti->key,
+					      &message_space);
         if (  retry_record
 	   && now - retry_record->time_stamp > retry_data_expire)
           retry_record = NULL;
@@ -675,7 +676,7 @@ for (int i = 0; i < 3; i++)
           retry_record->expired = FALSE;
           retry_record->text[0] = 0;      /* just in case */
           }
-        else message_space = Ustrlen(retry_record->text);
+	else message_space -= sizeof(dbdata_retry);
 
         /* Compute how long this destination has been failing */
 
@@ -806,15 +807,17 @@ for (int i = 0; i < 3; i++)
         if (next_try - now > retry_interval_max)
           next_try = now + retry_interval_max;
 
-        /* If the new message length is greater than the previous one, we
-        have to copy the record first. */
-
-        if (message_length > message_space)
-          {
-          dbdata_retry *newr = store_get(sizeof(dbdata_retry) + message_length, FALSE);
-          memcpy(newr, retry_record, sizeof(dbdata_retry));
-          retry_record = newr;
-          }
+        /* If the new message length is greater than the previous one, we have
+	to copy the record first.  If we're using an old one, the read used
+	tainted memory so we're ok to write into it. */
+
+	if (message_length > message_space)
+	  {
+	  dbdata_retry * newr =
+	    store_get(sizeof(dbdata_retry) + message_length, is_tainted(message));
+	  memcpy(newr, retry_record, sizeof(dbdata_retry));
+	  retry_record = newr;
+	  }
 
         /* Set up the retry record; message_length may be less than the string
         length for very long error strings. */

commit 134c037ed24f77957556f40856659c1456df1bcc
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Dec 13 14:26:17 2019 +0000

    DMARC: default dmarc_tld_file to unset.  Bug 2494
    
    (cherry picked from commit 39fdec3c4a4b4c1cc60cd17413b096dd07344734)

diff --git a/src/src/globals.c b/src/src/globals.c
index 358c380a8..85a25a7f2 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -844,7 +844,7 @@ uschar *dmarc_forensic_sender   = NULL;
 uschar *dmarc_history_file      = NULL;
 uschar *dmarc_status            = NULL;
 uschar *dmarc_status_text       = NULL;
-uschar *dmarc_tld_file          = US DMARC_TLD_FILE;
+uschar *dmarc_tld_file          = NULL;
 uschar *dmarc_used_domain       = NULL;
 #endif
 
diff --git a/src/src/receive.c b/src/src/receive.c
index 31e3f7cbb..4e85ffacf 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -1703,10 +1703,6 @@ header_line *msgid_header = NULL;
 header_line *received_header;
 BOOL msgid_header_newly_created = FALSE;
 
-#ifdef SUPPORT_DMARC
-int dmarc_up = 0;
-#endif
-
 /* Variables for use when building the Received: header. */
 
 uschar *timestamp;
@@ -1768,7 +1764,7 @@ if (smtp_input && !smtp_batched_input && !f.dkim_disable_verify)
 #endif
 
 #ifdef SUPPORT_DMARC
-dmarc_up = dmarc_init();	/* initialize libopendmarc */
+if (sender_host_address) dmarc_init();	/* initialize libopendmarc */
 #endif
 
 /* Remember the time of reception. Exim uses time+pid for uniqueness of message
@@ -3499,7 +3495,7 @@ else
 #endif /* WITH_CONTENT_SCAN */
 
 #ifdef SUPPORT_DMARC
-    dmarc_up = dmarc_store_data(from_header);
+    dmarc_store_data(from_header);
 #endif
 
 #ifndef DISABLE_PRDR

commit a0f5040c4d5878342bc6d391d692cf88007d06c0
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sat Dec 14 19:39:15 2019 +0000

    Zero smtp context structure after allocation

diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index bb5fda1f4..8fd9ff94a 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -895,8 +895,9 @@ else
     sx->ehlo_resp = er->data;
     dbfn_close(dbm_file);
     DEBUG(D_transport) debug_printf(
-	"EHLO response bits from cache: cleartext 0x%04x crypted 0x%04x\n",
-	er->data.cleartext_features, er->data.crypted_features);
+	"EHLO response bits from cache: cleartext 0x%04x/0x%04x crypted 0x%04x/0x%04x\n",
+	er->data.cleartext_features, er->data.cleartext_auths,
+	er->data.crypted_features, er->data.crypted_auths);
     return TRUE;
     }
   dbfn_close(dbm_file);
@@ -1904,39 +1905,39 @@ sx->conn_args.ob = ob;
 
 sx->lmtp = strcmpic(ob->protocol, US"lmtp") == 0;
 sx->smtps = strcmpic(ob->protocol, US"smtps") == 0;
-sx->ok = FALSE;
+/* sx->ok = FALSE; */
 sx->send_rset = TRUE;
 sx->send_quit = TRUE;
 sx->setting_up = TRUE;
 sx->esmtp = TRUE;
-sx->esmtp_sent = FALSE;
+/* sx->esmtp_sent = FALSE; */
 #ifdef SUPPORT_I18N
-sx->utf8_needed = FALSE;
+/* sx->utf8_needed = FALSE; */
 #endif
 sx->dsn_all_lasthop = TRUE;
 #ifdef SUPPORT_DANE
-sx->conn_args.dane = FALSE;
+/* sx->conn_args.dane = FALSE; */
 sx->dane_required =
   verify_check_given_host(CUSS &ob->hosts_require_dane, sx->conn_args.host) == OK;
 #endif
 #ifdef SUPPORT_PIPE_CONNECT
-sx->early_pipe_active = sx->early_pipe_ok = FALSE;
-sx->ehlo_resp.cleartext_features = sx->ehlo_resp.crypted_features = 0;
-sx->pending_BANNER = sx->pending_EHLO = FALSE;
+/* sx->early_pipe_active = sx->early_pipe_ok = FALSE; */
+/* sx->ehlo_resp.cleartext_features = sx->ehlo_resp.crypted_features = 0; */
+/* sx->pending_BANNER = sx->pending_EHLO = sx->pending_MAIL = FALSE; */
 #endif
 
 if ((sx->max_rcpt = sx->conn_args.tblock->max_addresses) == 0) sx->max_rcpt = 999999;
-sx->peer_offered = 0;
-sx->avoid_option = 0;
+/* sx->peer_offered = 0; */
+/* sx->avoid_option = 0; */
 sx->igquotstr = US"";
 if (!sx->helo_data) sx->helo_data = ob->helo_data;
 #ifdef EXPERIMENTAL_DSN_INFO
-sx->smtp_greeting = NULL;
-sx->helo_response = NULL;
+/* sx->smtp_greeting = NULL; */
+/* sx->helo_response = NULL; */
 #endif
 
 smtp_command = US"initial connection";
-sx->buffer[0] = '\0';
+/* sx->buffer[0] = '\0'; */
 
 /* Set up the buffer for reading SMTP response packets. */
 
@@ -1950,9 +1951,9 @@ sx->inblock.ptrend = sx->inbuffer;
 sx->outblock.buffer = sx->outbuffer;
 sx->outblock.buffersize = sizeof(sx->outbuffer);
 sx->outblock.ptr = sx->outbuffer;
-sx->outblock.cmd_count = 0;
-sx->outblock.authenticating = FALSE;
-sx->outblock.conn_args = NULL;
+/* sx->outblock.cmd_count = 0; */
+/* sx->outblock.authenticating = FALSE; */
+/* sx->outblock.conn_args = NULL; */
 
 /* Reset the parameters of a TLS session. */
 
@@ -3410,13 +3411,13 @@ struct timeval start_delivery_time;
 BOOL pass_message = FALSE;
 uschar *message = NULL;
 uschar new_message_id[MESSAGE_ID_LENGTH + 1];
-
 smtp_context * sx = store_get(sizeof(*sx), TRUE);	/* tainted, for the data buffers */
 
 gettimeofday(&start_delivery_time, NULL);
 suppress_tls = suppress_tls;  /* stop compiler warning when no TLS support */
 *message_defer = FALSE;
 
+memset(sx, 0, sizeof(*sx));
 sx->addrlist = addrlist;
 sx->conn_args.host = host;
 sx->conn_args.host_af = host_af,
@@ -3424,7 +3425,7 @@ sx->port = defport;
 sx->conn_args.interface = interface;
 sx->helo_data = NULL;
 sx->conn_args.tblock = tblock;
-sx->verify = FALSE;
+/* sx->verify = FALSE; */
 sx->sync_addr = sx->first_addr = addrlist;
 
 /* Get the channel set up ready for a message (MAIL FROM being the next
@@ -3488,9 +3489,9 @@ always has a sequence number greater than one. */
 if (continue_hostname && continue_sequence == 1)
   {
   sx->peer_offered = smtp_peer_options;
-  sx->pending_MAIL = FALSE;
+  /* sx->pending_MAIL = FALSE; */
   sx->ok = TRUE;
-  sx->next_addr = NULL;
+  /* sx->next_addr = NULL; */
 
   for (address_item * addr = addrlist; addr; addr = addr->next)
     addr->transport_return = PENDING_OK;
diff --git a/src/src/verify.c b/src/src/verify.c
index 5c0678c4b..edc4869ac 100644
--- a/src/src/verify.c
+++ b/src/src/verify.c
@@ -574,6 +574,7 @@ else
   {
   smtp_transport_options_block *ob =
     (smtp_transport_options_block *)addr->transport->options_block;
+  smtp_context * sx = NULL;
 
   /* The information wasn't available in the cache, so we have to do a real
   callout and save the result in the cache for next time, unless no_cache is set,
@@ -626,7 +627,6 @@ coding means skipping this whole loop and doing the append separately.  */
     int host_af;
     int port = 25;
     uschar * interface = NULL;  /* Outgoing interface to use; NULL => any */
-    smtp_context * sx = store_get(sizeof(*sx), TRUE);	/* tainted buffers */
 
     if (!host->address)
       {
@@ -666,6 +666,9 @@ coding means skipping this whole loop and doing the append separately.  */
       log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address,
         addr->message);
 
+    if (!sx) sx = store_get(sizeof(*sx), TRUE);	/* tainted buffers */
+    memset(sx, 0, sizeof(*sx));
+
     sx->addrlist = addr;
     sx->conn_args.host = host;
     sx->conn_args.host_af = host_af,

commit 0445bf5044cafd376d9d788bbf7551e21411a8e6
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Tue Dec 17 10:46:21 2019 +0000

        ARC: Reset received ARC instance counter before next message on a connection.  Bug 2498
    
    (cherry picked from commit 1415d1a6c91e66922036079b3e3b481e579cdb00)

diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index f37b2f6e0..d0fb6b81c 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -2087,6 +2087,7 @@ dmarc_used_domain = NULL;
 #endif
 #ifdef EXPERIMENTAL_ARC
 arc_state = arc_state_reason = NULL;
+arc_received_instance = 0;
 #endif
 dsn_ret = 0;
 dsn_envid = NULL;

commit 1bfc75b19e528a82ce2ff59f7c7c9b91e6e13226
Author: Wolfgang Breyha <wbreyha@gmx.net>
Date:   Fri Dec 20 14:01:23 2019 +0000

    SPF: only require "v=spf1" on TXT DNS records during lookups.  Bug 2499
    
    (cherry picked from commit 4a3709fb2cfb1ec26ac60bcdb8bbc078d3377ddc)

diff --git a/src/src/spf.c b/src/src/spf.c
index 1955b5d96..b7041d3e3 100644
--- a/src/src/spf.c
+++ b/src/src/spf.c
@@ -43,17 +43,15 @@ dns_answer * dnsa = store_get_dns_answer();
 dns_scan dnss;
 SPF_dns_rr_t * spfrr;
 
-DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup\n");
+DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", domain);
 
 if (dns_lookup(dnsa, US domain, rr_type, NULL) == DNS_SUCCEED)
   for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
        rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
     if (  rr->type == rr_type
-       && Ustrncmp(rr->data+1, "v=spf1", 6) == 0)
+       && (rr_type != T_TXT || Ustrncmp(rr->data+1, "v=spf1", 6) == 0))
       {
-      gstring * g = NULL;
-      uschar chunk_len;
-      uschar * s;
+      const uschar * s = rr->data;
       SPF_dns_rr_t srr = {
 	.domain = CS rr->name,			/* query information */
 	.domain_buf_len = DNS_MAXNAME,
@@ -71,19 +69,49 @@ if (dns_lookup(dnsa, US domain, rr_type, NULL) == DNS_SUCCEED)
 	.source = spf_dns_server
       };
 
-      for (int off = 0; off < rr->size; off += chunk_len)
+      switch(rr_type)
 	{
-	chunk_len = (rr->data)[off++];
-	g = string_catn(g, US ((rr->data)+off), chunk_len);
+        case T_MX:
+          s += 2;			/* skip the MX precedence field */
+        case T_PTR:
+	  {
+          uschar * buf = store_malloc(256);
+          (void)dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, s,
+            (DN_EXPAND_ARG4_TYPE)buf, 256);
+          s = buf;
+          break;
+	  }
+
+        case T_TXT:
+	  {
+	  gstring * g = NULL;
+	  uschar chunk_len;
+          for (int off = 0; off < rr->size; off += chunk_len)
+            {
+            if (!(chunk_len = s[off++])) break;
+            g = string_catn(g, s+off, chunk_len);
+            }
+          if (!g)
+            {
+            HDEBUG(D_host_lookup) debug_printf("IP address lookup yielded an "
+              "empty name: treated as non-existent host name\n");
+            continue;
+            }
+          gstring_release_unused(g);
+          s = string_copy_malloc(string_from_gstring(g));
+          break;
+	  }
+
+        case T_A:
+        case T_AAAA:
+        default:
+	  {
+          uschar * buf = store_malloc(dnsa->answerlen + 1);
+	  s = memcpy(buf, s, dnsa->answerlen + 1);
+          break;
+	  }
 	}
-      if (!g)
-	{
-	HDEBUG(D_host_lookup) debug_printf("IP address lookup yielded an "
-	  "empty name: treated as non-existent host name\n");
-	continue;
-	}
-      gstring_release_unused(g);
-      s = string_copy_malloc(string_from_gstring(g));
+      DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", s);
       srr.rr = (void *) &s;
 
       /* spfrr->rr must have been malloc()d for this */

commit f7be88fdcef970d66332dabe702a15fe3e1cdd9a
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sat Dec 21 20:31:31 2019 +0000

    Eximon: fix string-handling.  Bug 2500
    
    (cherry picked from commit 8aa16eb712afac844fb28ed465c6076c6b0ea22b)

diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base
index 1f42e0ec3..f4977614f 100644
--- a/src/OS/Makefile-Base
+++ b/src/OS/Makefile-Base
@@ -868,9 +868,9 @@ em_text.o:       ../exim_monitor/em_text.c    ../exim_monitor/em_hdr.h
 em_xs.o:         ../exim_monitor/em_xs.c      ../exim_monitor/em_hdr.h
 em_version.o:    ../exim_monitor/em_version.c ../exim_monitor/em_hdr.h
 $(MONBIN): $(HDRS)
-		 @echo "$(CC) exim_monitor/`echo $@ | sed 's/o$$/c/'`"
-		 $(FE)$(CC) -o $@ -c $(CFLAGS) -I. -I../exim_monitor $(INCLUDE) $(XINCLUDE) \
-		   ../exim_monitor/`echo $@ | sed 's/o$$/c/'`
+		 @echo "$(CC) exim_monitor/$(@:.o=.c)"
+		 $(FE)$(CC) -o $@ -c $(CFLAGS) -DCOMPILE_UTILITY -I. -I../exim_monitor $(INCLUDE) $(XINCLUDE) \
+		   ../exim_monitor/$(@:.o=.c)
 
 
 # Targets for the various libraries that Exim uses.
diff --git a/src/src/exim_dbmbuild.c b/src/src/exim_dbmbuild.c
index 85bc3fd88..1185cfa44 100644
--- a/src/src/exim_dbmbuild.c
+++ b/src/src/exim_dbmbuild.c
@@ -56,6 +56,11 @@ string_vformat_trc(gstring * g, const uschar * func, unsigned line,
 uschar *
 string_sprintf_trc(const char * a, const uschar * b, unsigned c, ...)
 { return NULL; }
+BOOL
+string_format_trc(uschar * buf, int len, const uschar * func, unsigned line,
+  const char * fmt, ...)
+{ return FALSE; }
+
 
 struct global_flags	f;
 unsigned int		log_selector[1];
diff --git a/src/src/exim_dbutil.c b/src/src/exim_dbutil.c
index 80f656530..a86fcbbdc 100644
--- a/src/src/exim_dbutil.c
+++ b/src/src/exim_dbutil.c
@@ -59,6 +59,10 @@ string_vformat_trc(gstring * g, const uschar * func, unsigned line,
 uschar *
 string_sprintf_trc(const char * fmt, const uschar * func, unsigned line, ...)
 { return NULL; }
+BOOL
+string_format_trc(uschar * buf, int len, const uschar * func, unsigned line,
+  const char * fmt, ...)
+{ return FALSE; }
 
 struct global_flags	f;
 unsigned int		log_selector[1];
diff --git a/src/src/functions.h b/src/src/functions.h
index f4fcd1e19..87d1a04d8 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -902,9 +902,18 @@ return string_sprintf("%s/%s/%s/%s/%s%s",
 
 static inline uschar *
 spool_fname(const uschar * purpose, const uschar * subdir, const uschar * fname,
-       	const uschar * suffix)
+	const uschar * suffix)
 {
+#ifdef COMPILE_UTILITY		/* version avoiding string-extension */
+int len = Ustrlen(spool_directory) + 1 + Ustrlen(queue_name) + 1 + Ustrlen(purpose) + 1
+	+ Ustrlen(subdir) + 1 + Ustrlen(fname) + Ustrlen(suffix) + 1;
+uschar * buf = store_get(len, FALSE);
+string_format(buf, len, "%s/%s/%s/%s/%s%s",
+	spool_directory, queue_name, purpose, subdir, fname, suffix);
+return buf;
+#else
 return spool_q_fname(purpose, queue_name, subdir, fname, suffix);
+#endif
 }
 
 static inline void
diff --git a/src/src/string.c b/src/src/string.c
index a20807054..49a028343 100644
--- a/src/src/string.c
+++ b/src/src/string.c
@@ -678,12 +678,20 @@ Returns:    pointer to fresh piece of store containing sprintf'ed string
 uschar *
 string_sprintf_trc(const char *format, const uschar * func, unsigned line, ...)
 {
-gstring * g;
-va_list ap;
+#ifdef COMPILE_UTILITY
+uschar buffer[STRING_SPRINTF_BUFFER_SIZE];
+gstring gs = { .size = STRING_SPRINTF_BUFFER_SIZE, .ptr = 0, .s = buffer };
+gstring * g = &gs;
+unsigned flags = 0;
+#else
+gstring * g = NULL;
+unsigned flags = SVFMT_REBUFFER|SVFMT_EXTEND;
+#endif
 
+va_list ap;
 va_start(ap, line);
-g = string_vformat_trc(NULL, func, line, STRING_SPRINTF_BUFFER_SIZE,
-	SVFMT_REBUFFER|SVFMT_EXTEND, format, ap);
+g = string_vformat_trc(g, func, line, STRING_SPRINTF_BUFFER_SIZE,
+	flags, format, ap);
 va_end(ap);
 
 if (!g)
@@ -692,8 +700,12 @@ if (!g)
     " called from %s %d\n",
     STRING_SPRINTF_BUFFER_SIZE, format, func, line);
 
+#ifdef COMPILE_UTILITY
+return string_copyn(g->s, g->ptr);
+#else
 gstring_release_unused(g);
 return string_from_gstring(g);
+#endif
 }
 
 

commit 53afb7482744e6bb127f624d47844d8b6bd54997
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sun Dec 22 21:51:53 2019 +0000

    Fix build with heimdal-gssapi.  Bug 2501
    
    (cherry picked from commit 5031095fd4553935c70e1c24a9936dfc609cdc67)

diff --git a/src/src/auths/heimdal_gssapi.c b/src/src/auths/heimdal_gssapi.c
index 273d4f47b..3dfcb8c6a 100644
--- a/src/src/auths/heimdal_gssapi.c
+++ b/src/src/auths/heimdal_gssapi.c
@@ -415,7 +415,7 @@ while (step < 4)
 	  NULL,                 /* conf_state: no confidentiality applied */
 	  &gbufdesc_out         /* output buffer */
 	  );
-      if (GSS_ERROR(maj_stat)
+      if (GSS_ERROR(maj_stat))
         {
 	exim_gssapi_error_defer(NULL, maj_stat, min_stat,
 	    "gss_wrap(SASL state after auth)");

commit db78a5c40bc27d6f9fe9151442e9dbb9abb11d8a
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sun Dec 29 14:34:12 2019 +0000

    Fix the variables set by gsasl authenticator
    
    (cherry picked from commit 98eb95929140ee1e2b2b367b12abb45762d155e9)

diff --git a/src/src/auths/gsasl_exim.c b/src/src/auths/gsasl_exim.c
index faf30bb8a..ee6cb4f03 100644
--- a/src/src/auths/gsasl_exim.c
+++ b/src/src/auths/gsasl_exim.c
@@ -461,11 +461,11 @@ switch (prop)
   case GSASL_VALIDATE_SIMPLE:
     /* GSASL_AUTHID, GSASL_AUTHZID, and GSASL_PASSWORD */
     propval = US  gsasl_property_fast(sctx, GSASL_AUTHID);
-    auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
+    auth_vars[0] = expand_nstring[1] = propval ? string_copy(propval) : US"";
     propval = US  gsasl_property_fast(sctx, GSASL_AUTHZID);
-    auth_vars[1] = expand_nstring[2] = propval ? propval : US"";
+    auth_vars[1] = expand_nstring[2] = propval ? string_copy(propval) : US"";
     propval = US  gsasl_property_fast(sctx, GSASL_PASSWORD);
-    auth_vars[2] = expand_nstring[3] = propval ? propval : US"";
+    auth_vars[2] = expand_nstring[3] = propval ? string_copy(propval) : US"";
     expand_nmax = 3;
     for (int i = 1; i <= 3; ++i)
       expand_nlength[i] = Ustrlen(expand_nstring[i]);
@@ -483,7 +483,7 @@ switch (prop)
       }
     propval = US  gsasl_property_fast(sctx, GSASL_AUTHZID);
     /* We always set $auth1, even if only to empty string. */
-    auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
+    auth_vars[0] = expand_nstring[1] = propval ? string_copy(propval) : US"";
     expand_nlength[1] = Ustrlen(expand_nstring[1]);
     expand_nmax = 1;
 
@@ -501,7 +501,7 @@ switch (prop)
       }
     propval = US  gsasl_property_fast(sctx, GSASL_ANONYMOUS_TOKEN);
     /* We always set $auth1, even if only to empty string. */
-    auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
+    auth_vars[0] = expand_nstring[1] = propval ? string_copy(propval) : US"";
     expand_nlength[1] = Ustrlen(expand_nstring[1]);
     expand_nmax = 1;
 
@@ -521,9 +521,9 @@ switch (prop)
     to the first release of Exim with this authenticator, they've been
     switched to match the ordering of GSASL_VALIDATE_SIMPLE. */
     propval = US  gsasl_property_fast(sctx, GSASL_GSSAPI_DISPLAY_NAME);
-    auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
+    auth_vars[0] = expand_nstring[1] = propval ? string_copy(propval) : US"";
     propval = US  gsasl_property_fast(sctx, GSASL_AUTHZID);
-    auth_vars[1] = expand_nstring[2] = propval ? propval : US"";
+    auth_vars[1] = expand_nstring[2] = propval ? string_copy(propval) : US"";
     expand_nmax = 2;
     for (int i = 1; i <= 2; ++i)
       expand_nlength[i] = Ustrlen(expand_nstring[i]);
@@ -558,11 +558,11 @@ switch (prop)
     needing to add more glue, since avoiding that is a large part of the
     point of SASL. */
     propval = US  gsasl_property_fast(sctx, GSASL_AUTHID);
-    auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
+    auth_vars[0] = expand_nstring[1] = propval ? string_copy(propval) : US"";
     propval = US  gsasl_property_fast(sctx, GSASL_AUTHZID);
-    auth_vars[1] = expand_nstring[2] = propval ? propval : US"";
+    auth_vars[1] = expand_nstring[2] = propval ? string_copy(propval) : US"";
     propval = US  gsasl_property_fast(sctx, GSASL_REALM);
-    auth_vars[2] = expand_nstring[3] = propval ? propval : US"";
+    auth_vars[2] = expand_nstring[3] = propval ? string_copy(propval) : US"";
     expand_nmax = 3;
     for (int i = 1; i <= 3; ++i)
       expand_nlength[i] = Ustrlen(expand_nstring[i]);

commit bf4d783783e07ca6c3af9546658dbe86769f177c
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sun Dec 29 14:46:10 2019 +0000

    SPF: fix handling mix of spf and other txt records.  Bug 2499
    
    (cherry picked from commits 4533e306fc, b17ea87dd9, 44e90dfa83, 10897f5d74)

diff --git a/src/src/spf.c b/src/src/spf.c
index b7041d3e3..8ead817b9 100644
--- a/src/src/spf.c
+++ b/src/src/spf.c
@@ -37,90 +37,111 @@ SPF_dns_rr_t  * spf_nxdomain = NULL;
 
 static SPF_dns_rr_t *
 SPF_dns_exim_lookup(SPF_dns_server_t *spf_dns_server,
-const char *domain, ns_type rr_type, int should_cache)
+  const char *domain, ns_type rr_type, int should_cache)
 {
 dns_answer * dnsa = store_get_dns_answer();
 dns_scan dnss;
 SPF_dns_rr_t * spfrr;
+unsigned found = 0;
+
+SPF_dns_rr_t srr = {
+  .domain = CS domain,			/* query information */
+  .domain_buf_len = 0,
+  .rr_type = rr_type,
+
+  .rr_buf_len = 0,			/* answer information */
+  .rr_buf_num = 0, /* no free of s */
+  .utc_ttl = 0,
+
+  .hook = NULL,				/* misc information */
+  .source = spf_dns_server
+};
+int dns_rc;
 
 DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", domain);
 
-if (dns_lookup(dnsa, US domain, rr_type, NULL) == DNS_SUCCEED)
-  for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
-       rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
-    if (  rr->type == rr_type
-       && (rr_type != T_TXT || Ustrncmp(rr->data+1, "v=spf1", 6) == 0))
+switch (dns_rc = dns_lookup(dnsa, US domain, rr_type, NULL))
+  {
+  case DNS_SUCCEED:	srr.herrno = NETDB_SUCCESS;	break;
+  case DNS_AGAIN:	srr.herrno = TRY_AGAIN;		break;
+  case DNS_NOMATCH:	srr.herrno = HOST_NOT_FOUND;	break;
+  case DNS_NODATA:	srr.herrno = NO_DATA;		break;
+  case DNS_FAIL:
+  default:		srr.herrno = NO_RECOVERY;	break;
+  } 
+
+for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
+     rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
+  if (rr->type == rr_type) found++;
+
+if (found == 0)
+  {
+  SPF_dns_rr_dup(&spfrr, &srr);
+  return spfrr;
+  }
+
+srr.rr = store_malloc(sizeof(SPF_dns_rr_data_t) * found);
+
+found = 0;
+for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
+   rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
+  if (rr->type == rr_type)
+    {
+    const uschar * s = rr->data;
+
+    srr.ttl = rr->ttl;
+    switch(rr_type)
       {
-      const uschar * s = rr->data;
-      SPF_dns_rr_t srr = {
-	.domain = CS rr->name,			/* query information */
-	.domain_buf_len = DNS_MAXNAME,
-	.rr_type = rr->type,
-
-	.num_rr = 1,				/* answer information */
-	.rr = NULL,
-	.rr_buf_len = 0,
-	.rr_buf_num = 0,
-	.ttl = rr->ttl,
-	.utc_ttl = 0,
-	.herrno = NETDB_SUCCESS,
-
-	.hook = NULL,				/* misc information */
-	.source = spf_dns_server
-      };
-
-      switch(rr_type)
+      case T_MX:
+	s += 2;	/* skip the MX precedence field */
+      case T_PTR:
 	{
-        case T_MX:
-          s += 2;			/* skip the MX precedence field */
-        case T_PTR:
-	  {
-          uschar * buf = store_malloc(256);
-          (void)dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, s,
-            (DN_EXPAND_ARG4_TYPE)buf, 256);
-          s = buf;
-          break;
-	  }
+	uschar * buf = store_malloc(256);
+	(void)dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, s,
+	  (DN_EXPAND_ARG4_TYPE)buf, 256);
+	s = buf;
+	break;
+	}
 
-        case T_TXT:
+      case T_TXT:
+	{
+	gstring * g = NULL;
+	uschar chunk_len;
+
+	if (strncmpic(rr->data+1, US"v=spf1", 6) != 0)
 	  {
-	  gstring * g = NULL;
-	  uschar chunk_len;
-          for (int off = 0; off < rr->size; off += chunk_len)
-            {
-            if (!(chunk_len = s[off++])) break;
-            g = string_catn(g, s+off, chunk_len);
-            }
-          if (!g)
-            {
-            HDEBUG(D_host_lookup) debug_printf("IP address lookup yielded an "
-              "empty name: treated as non-existent host name\n");
-            continue;
-            }
-          gstring_release_unused(g);
-          s = string_copy_malloc(string_from_gstring(g));
-          break;
+	  HDEBUG(D_host_lookup) debug_printf("not an spf record\n");
+	  continue;
 	  }
 
-        case T_A:
-        case T_AAAA:
-        default:
+	for (int off = 0; off < rr->size; off += chunk_len)
 	  {
-          uschar * buf = store_malloc(dnsa->answerlen + 1);
-	  s = memcpy(buf, s, dnsa->answerlen + 1);
-          break;
+	  if (!(chunk_len = s[off++])) break;
+	  g = string_catn(g, s+off, chunk_len);
 	  }
+	if (!g)
+	  continue;
+	gstring_release_unused(g);
+	s = string_copy_malloc(string_from_gstring(g));
+	break;
 	}
-      DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", s);
-      srr.rr = (void *) &s;
-
-      /* spfrr->rr must have been malloc()d for this */
-      SPF_dns_rr_dup(&spfrr, &srr);
 
-      return spfrr;
+      case T_A:
+      case T_AAAA:
+      default:
+	{
+	uschar * buf = store_malloc(dnsa->answerlen + 1);
+	s = memcpy(buf, s, dnsa->answerlen + 1);
+	break;
+	}
       }
+    DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", s);
+    srr.rr[found++] = (void *) s;
+    }
 
-SPF_dns_rr_dup(&spfrr, spf_nxdomain);
+srr.num_rr = found;
+/* spfrr->rr must have been malloc()d for this */
+SPF_dns_rr_dup(&spfrr, &srr);
 return spfrr;
 }
 
@@ -129,14 +150,11 @@ return spfrr;
 SPF_dns_server_t *
 SPF_dns_exim_new(int debug)
 {
-SPF_dns_server_t *spf_dns_server;
+SPF_dns_server_t * spf_dns_server = store_malloc(sizeof(SPF_dns_server_t));
 
 DEBUG(D_receive) debug_printf("SPF_dns_exim_new\n");
 
-if (!(spf_dns_server = malloc(sizeof(SPF_dns_server_t))))
-  return NULL;
 memset(spf_dns_server, 0, sizeof(SPF_dns_server_t));
-
 spf_dns_server->destroy      = NULL;
 spf_dns_server->lookup       = SPF_dns_exim_lookup;
 spf_dns_server->get_spf      = NULL;

commit dfb8f72b2237627b26767d1e803e8ed95ad659d2
Author: Wolfgang Breyha <wbreyha@gmx.net>
Date:   Tue Jan 7 13:03:18 2020 +0000

    SPF: fix result for case of only non-spf TXT RRs.  Bug 2499
    
    (cherry picked from commit 67794d2b830fc580f87b0635718d95e32b467be1)

diff --git a/src/src/spf.c b/src/src/spf.c
index 8ead817b9..12b756b46 100644
--- a/src/src/spf.c
+++ b/src/src/spf.c
@@ -139,7 +139,12 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
     srr.rr[found++] = (void *) s;
     }
 
-srr.num_rr = found;
+/* Did we filter out all TXT RRs? Return NO_DATA instead of SUCCESS with
+empty ANSWER section. */
+
+if (!(srr.num_rr = found))
+  srr.herrno = NO_DATA;
+
 /* spfrr->rr must have been malloc()d for this */
 SPF_dns_rr_dup(&spfrr, &srr);
 return spfrr;

commit 338f36842f10ef84e684dddf59819837fd7792a3
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Wed Jan 15 10:40:20 2020 +0000

    Fix error logging for dynamically-loaded modules.  Bug 2507
    
    (cherry picked from commits b1c673ddfa, 3fc07bd570)

diff --git a/src/src/drtables.c b/src/src/drtables.c
index 059756284..ca051bd20 100644
--- a/src/src/drtables.c
+++ b/src/src/drtables.c
@@ -740,10 +740,11 @@ init_lookup_list(void)
 
         dl = dlopen(CS big_buffer, RTLD_NOW);// TJ was LAZY
         if (dl == NULL) {
-          fprintf(stderr, "Error loading %s: %s\n", name, dlerror());
-          moduleerrors++;
-          log_write(0, LOG_MAIN|LOG_PANIC, "Error loading lookup module %s: %s\n", name, dlerror());
-          continue;
+	  errormessage = dlerror();
+	  fprintf(stderr, "Error loading %s: %s\n", name, errormessage);
+	  log_write(0, LOG_MAIN|LOG_PANIC, "Error loading lookup module %s: %s\n", name, errormessage);
+	  moduleerrors++;
+	  continue;
         }
 
         /* FreeBSD nsdispatch() can trigger dlerror() errors about
@@ -756,16 +757,16 @@ init_lookup_list(void)
         info = (struct lookup_module_info*) dlsym(dl, "_lookup_module_info");
         if ((errormsg = dlerror()) != NULL) {
           fprintf(stderr, "%s does not appear to be a lookup module (%s)\n", name, errormsg);
+          log_write(0, LOG_MAIN|LOG_PANIC, "%s does not appear to be a lookup module (%s)\n", name, errormsg);
           dlclose(dl);
           moduleerrors++;
-          log_write(0, LOG_MAIN|LOG_PANIC, "%s does not appear to be a lookup module (%s)\n", name, errormsg);
           continue;
         }
         if (info->magic != LOOKUP_MODULE_INFO_MAGIC) {
           fprintf(stderr, "Lookup module %s is not compatible with this version of Exim\n", name);
+          log_write(0, LOG_MAIN|LOG_PANIC, "Lookup module %s is not compatible with this version of Exim\n", name);
           dlclose(dl);
           moduleerrors++;
-          log_write(0, LOG_MAIN|LOG_PANIC, "Lookup module %s is not compatible with this version of Exim\n", name);
           continue;
         }
 

commit bbeab68df3b3c2d5507b1fdca07509fdbb3ec5a1
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Tue Jan 14 17:48:57 2020 +0000

    heimdal auth: fix the increase of big_buffer size.  Bug 2501
    
    (cherry picked from commit 7a66b3afa11a70021297c176acf56831692be89a)

diff --git a/src/src/auths/README b/src/src/auths/README
index d4f125c30..66bdcdcf8 100644
--- a/src/src/auths/README
+++ b/src/src/auths/README
@@ -34,7 +34,7 @@ instance block for this configured mechanism. It must set the flags called
 the server and/or client functions are available for this authenticator.
 Typically this depends on whether server or client configuration options have
 been set, but it is also possible to have an authenticator that has only one of
-the server or client functions.
+the server or client functions.  The function may not touch big_buffer.
 
 SERVER AUTHENTICATION
 
diff --git a/src/src/auths/heimdal_gssapi.c b/src/src/auths/heimdal_gssapi.c
index 3dfcb8c6a..523f7c69a 100644
--- a/src/src/auths/heimdal_gssapi.c
+++ b/src/src/auths/heimdal_gssapi.c
@@ -200,16 +200,6 @@ if (krc)
 
 krb5_free_context(context);
 
-/* RFC 4121 section 5.2, SHOULD support 64K input buffers */
-if (big_buffer_size < (64 * 1024))
-  {
-  uschar *newbuf;
-  big_buffer_size = 64 * 1024;
-  newbuf = store_malloc(big_buffer_size);
-  store_free(big_buffer);
-  big_buffer = newbuf;
-  }
-
 ablock->server = TRUE;
 }
 
diff --git a/src/src/macros.h b/src/src/macros.h
index 76913d64e..4e6b1b8a9 100644
--- a/src/src/macros.h
+++ b/src/src/macros.h
@@ -152,12 +152,19 @@ enough to hold all the headers from a normal kind of message. */
 into big_buffer_size and in some circumstances increased. It should be at least
 as long as the maximum path length. */
 
-#if defined PATH_MAX && PATH_MAX > 16384
+#ifdef AUTH_HEIMDAL_GSSAPI
+		/* RFC 4121 section 5.2, SHOULD support 64K input buffers */
+# define __BIG_BUFFER_SIZE 65536
+#else
+# define __BIG_BUFFER_SIZE 16384
+#endif
+
+#if defined PATH_MAX && PATH_MAX > __BIG_BUFFER_SIZE
 # define BIG_BUFFER_SIZE PATH_MAX
-#elif defined MAXPATHLEN && MAXPATHLEN > 16384
+#elif defined MAXPATHLEN && MAXPATHLEN > __BIG_BUFFER_SIZE
 # define BIG_BUFFER_SIZE MAXPATHLEN
 #else
-# define BIG_BUFFER_SIZE 16384
+# define BIG_BUFFER_SIZE __BIG_BUFFER_SIZE
 #endif
 
 /* header size of pipe content
diff --git a/src/src/readconf.c b/src/src/readconf.c
index 0233019cf..62cfcfbf9 100644
--- a/src/src/readconf.c
+++ b/src/src/readconf.c
@@ -3788,6 +3788,7 @@ while ((buffer = get_config_line()) != NULL)
       if (!d->driver_name)
         log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
           "no driver defined for %s \"%s\"", class, d->name);
+      /* s is using big_buffer, so this call had better not */
       (d->info->init)(d);
       d = NULL;
       }

commit 1ccd26e24267ffa0c40b70c2c3282481fe4977c7
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Thu Jan 16 14:12:56 2020 +0000

    Taint: hybrid checking mode
    
    (cherry picked from commit 36eb5d3d77426d8cbf4243ea752f8d8cd1d5c682)

diff --git a/src/exim_monitor/em_version.c b/src/exim_monitor/em_version.c
index 52c55a4a3..9b9c7d417 100644
--- a/src/exim_monitor/em_version.c
+++ b/src/exim_monitor/em_version.c
@@ -5,6 +5,8 @@
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
+#define EM_VERSION_C
+
 #include "mytypes.h"
 #include "store.h"
 #include "macros.h"
diff --git a/src/src/functions.h b/src/src/functions.h
index 87d1a04d8..0b5905562 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -187,6 +187,7 @@ extern void    deliver_succeeded(address_item *);
 extern uschar *deliver_get_sender_address (uschar *id);
 extern void    delivery_re_exec(int);
 
+extern void    die_tainted(const uschar *, const uschar *, int);
 extern BOOL    directory_make(const uschar *, const uschar *, int, BOOL);
 #ifndef DISABLE_DKIM
 extern uschar *dkim_exim_query_dns_txt(const uschar *);
@@ -602,6 +603,61 @@ extern BOOL    write_chunk(transport_ctx *, uschar *, int);
 extern ssize_t write_to_fd_buf(int, const uschar *, size_t);
 
 
+/******************************************************************************/
+/* Predicate: if an address is in a tainted pool.
+By extension, a variable pointing to this address is tainted.
+*/
+
+static inline BOOL
+is_tainted(const void * p)
+{
+#if defined(COMPILE_UTILITY) || defined(MACRO_PREDEF) || defined(EM_VERSION_C)
+return FALSE;
+
+#else
+extern BOOL is_tainted_fn(const void *);
+extern void * tainted_base, * tainted_top;
+
+return f.taint_check_slow
+  ? is_tainted_fn(p) : p >= tainted_base && p < tainted_top;
+#endif
+}
+
+/******************************************************************************/
+/* String functions */
+static inline uschar * __Ustrcat(uschar * dst, const uschar * src, const char * func, int line)
+{
+#if !defined(COMPILE_UTILITY) && !defined(MACRO_PREDEF)
+if (!is_tainted(dst) && is_tainted(src)) die_tainted(US"Ustrcat", CUS func, line);
+#endif
+return US strcat(CS dst, CCS src);
+}
+static inline uschar * __Ustrcpy(uschar * dst, const uschar * src, const char * func, int line)
+{
+#if !defined(COMPILE_UTILITY) && !defined(MACRO_PREDEF)
+if (!is_tainted(dst) && is_tainted(src)) die_tainted(US"Ustrcpy", CUS func, line);
+#endif
+return US strcpy(CS dst, CCS src);
+}
+static inline uschar * __Ustrncat(uschar * dst, const uschar * src, size_t n, const char * func, int line)
+{
+#if !defined(COMPILE_UTILITY) && !defined(MACRO_PREDEF)
+if (!is_tainted(dst) && is_tainted(src)) die_tainted(US"Ustrncat", CUS func, line);
+#endif
+return US strncat(CS dst, CCS src, n);
+}
+static inline uschar * __Ustrncpy(uschar * dst, const uschar * src, size_t n, const char * func, int line)
+{
+#if !defined(COMPILE_UTILITY) && !defined(MACRO_PREDEF)
+if (!is_tainted(dst) && is_tainted(src)) die_tainted(US"Ustrncpy", CUS func, line);
+#endif
+return US strncpy(CS dst, CCS src, n);
+}
+/*XXX will likely need unchecked copy also */
+
+
+/******************************************************************************/
+
 #if !defined(MACRO_PREDEF) && !defined(COMPILE_UTILITY)
 /* exim_chown - in some NFSv4 setups *seemes* to be an issue with
 chown(<exim-uid>, <exim-gid>).
@@ -634,8 +690,8 @@ exim_chown(const uschar *name, uid_t owner, gid_t group)
 return chown(CCS name, owner, group)
   ? exim_chown_failure(-1, name, owner, group) : 0;
 }
-
 #endif	/* !MACRO_PREDEF && !COMPILE_UTILITY */
+
 /******************************************************************************/
 /* String functions */
 
diff --git a/src/src/globals.c b/src/src/globals.c
index 85a25a7f2..72449229e 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -311,6 +311,7 @@ struct global_flags f =
 	.synchronous_delivery   = FALSE,
 	.system_filtering       = FALSE,
 
+	.taint_check_slow       = FALSE,
 	.tcp_fastopen_ok        = FALSE,
 	.tcp_in_fastopen        = FALSE,
 	.tcp_in_fastopen_data   = FALSE,
diff --git a/src/src/globals.h b/src/src/globals.h
index ca342acc2..ac7bb8ef3 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -272,6 +272,7 @@ extern struct global_flags {
  BOOL   synchronous_delivery		:1; /* TRUE if -odi is set */
  BOOL   system_filtering		:1; /* TRUE when running system filter */
 
+ BOOL   taint_check_slow		:1; /* malloc/mmap are not returning distinct ranges */
  BOOL   tcp_fastopen_ok			:1; /* appears to be supported by kernel */
  BOOL   tcp_in_fastopen			:1; /* conn usefully used fastopen */
  BOOL   tcp_in_fastopen_data		:1; /* fastopen carried data */
diff --git a/src/src/mytypes.h b/src/src/mytypes.h
index ceb9f1b55..e31ee8c1a 100644
--- a/src/src/mytypes.h
+++ b/src/src/mytypes.h
@@ -100,19 +100,15 @@ functions that are called quite often; for other calls to external libraries
 #define Uread(f,b,l)       read(f,CS(b),l)
 #define Urename(s,t)       rename(CCS(s),CCS(t))
 #define Ustat(s,t)         stat(CCS(s),t)
-#define Ustrcat(s,t)       __Ustrcat(s, CUS(t), __FUNCTION__, __LINE__)
 #define Ustrchr(s,n)       US strchr(CCS(s),n)
 #define CUstrchr(s,n)      CUS strchr(CCS(s),n)
 #define CUstrerror(n)      CUS strerror(n)
 #define Ustrcmp(s,t)       strcmp(CCS(s),CCS(t))
-#define Ustrcpy(s,t)       __Ustrcpy(s, CUS(t), __FUNCTION__, __LINE__)
 #define Ustrcpy_nt(s,t)    strcpy(CS s, CCS t)		/* no taint check */
 #define Ustrcspn(s,t)      strcspn(CCS(s),CCS(t))
 #define Ustrftime(s,m,f,t) strftime(CS(s),m,f,t)
 #define Ustrlen(s)         (int)strlen(CCS(s))
-#define Ustrncat(s,t,n)    __Ustrncat(s, CUS(t),n, __FUNCTION__, __LINE__)
 #define Ustrncmp(s,t,n)    strncmp(CCS(s),CCS(t),n)
-#define Ustrncpy(s,t,n)    __Ustrncpy(s, CUS(t),n, __FUNCTION__, __LINE__)
 #define Ustrncpy_nt(s,t,n) strncpy(CS s, CCS t, n)	/* no taint check */
 #define Ustrpbrk(s,t)      strpbrk(CCS(s),CCS(t))
 #define Ustrrchr(s,n)      US strrchr(CCS(s),n)
@@ -125,57 +121,17 @@ functions that are called quite often; for other calls to external libraries
 #define Ustrtoul(s,t,b)    strtoul(CCS(s),CSS(t),b)
 #define Uunlink(s)         unlink(CCS(s))
 
-extern void die_tainted(const uschar *, const uschar *, int);
-
-/* Predicate: if an address is in a tainted pool.
-By extension, a variable pointing to this address is tainted.
-*/
-
-static inline BOOL
-is_tainted(const void * p)
-{
-#if defined(COMPILE_UTILITY) || defined(MACRO_PREDEF)
-return FALSE;
-
-#elif defined(TAINT_CHECK_SLOW)
-extern BOOL is_tainted_fn(const void *);
-return is_tainted_fn(p);
-
+#ifdef EM_VERSION_C
+# define Ustrcat(s,t)       strcat(CS(s), CCS(t))
+# define Ustrcpy(s,t)       strcpy(CS(s), CCS(t))
+# define Ustrncat(s,t,n)    strncat(CS(s), CCS(t), n)
+# define Ustrncpy(s,t,n)    strncpy(CS(s), CCS(t), n)
 #else
-extern void * tainted_base, * tainted_top;
-return p >= tainted_base && p < tainted_top;
-#endif
-}
-
-static inline uschar * __Ustrcat(uschar * dst, const uschar * src, const char * func, int line)
-{
-#if !defined(COMPILE_UTILITY) && !defined(MACRO_PREDEF)
-if (!is_tainted(dst) && is_tainted(src)) die_tainted(US"Ustrcat", CUS func, line);
-#endif
-return US strcat(CS dst, CCS src);
-}
-static inline uschar * __Ustrcpy(uschar * dst, const uschar * src, const char * func, int line)
-{
-#if !defined(COMPILE_UTILITY) && !defined(MACRO_PREDEF)
-if (!is_tainted(dst) && is_tainted(src)) die_tainted(US"Ustrcpy", CUS func, line);
-#endif
-return US strcpy(CS dst, CCS src);
-}
-static inline uschar * __Ustrncat(uschar * dst, const uschar * src, size_t n, const char * func, int line)
-{
-#if !defined(COMPILE_UTILITY) && !defined(MACRO_PREDEF)
-if (!is_tainted(dst) && is_tainted(src)) die_tainted(US"Ustrncat", CUS func, line);
-#endif
-return US strncat(CS dst, CCS src, n);
-}
-static inline uschar * __Ustrncpy(uschar * dst, const uschar * src, size_t n, const char * func, int line)
-{
-#if !defined(COMPILE_UTILITY) && !defined(MACRO_PREDEF)
-if (!is_tainted(dst) && is_tainted(src)) die_tainted(US"Ustrncpy", CUS func, line);
+# define Ustrcat(s,t)       __Ustrcat(s, CUS(t), __FUNCTION__, __LINE__)
+# define Ustrcpy(s,t)       __Ustrcpy(s, CUS(t), __FUNCTION__, __LINE__)
+# define Ustrncat(s,t,n)    __Ustrncat(s, CUS(t), n, __FUNCTION__, __LINE__)
+# define Ustrncpy(s,t,n)    __Ustrncpy(s, CUS(t), n, __FUNCTION__, __LINE__)
 #endif
-return US strncpy(CS dst, CCS src, n);
-}
-/*XXX will likely need unchecked copy also */
 
 #endif
 /* End of mytypes.h */
diff --git a/src/src/store.c b/src/src/store.c
index a06e1c19a..692a993e9 100644
--- a/src/src/store.c
+++ b/src/src/store.c
@@ -162,8 +162,14 @@ static void   internal_tainted_free(storeblock *, const char *, int linenumber);
 
 /******************************************************************************/
 
-/* Slower version check, for use when platform intermixes malloc and mmap area
-addresses. */
+/* Test if a pointer refers to tainted memory.
+
+Slower version check, for use when platform intermixes malloc and mmap area
+addresses. Test against the current-block of all tainted pools first, then all
+blocks of all tainted pools.
+
+Return: TRUE iff tainted
+*/
 
 BOOL
 is_tainted_fn(const void * p)
@@ -171,23 +177,20 @@ is_tainted_fn(const void * p)
 storeblock * b;
 int pool;
 
-for (pool = 0; pool < nelem(chainbase); pool++)
+for (pool = POOL_TAINT_BASE; pool < nelem(chainbase); pool++)
   if ((b = current_block[pool]))
     {
-    char * bc = CS b + ALIGNED_SIZEOF_STOREBLOCK;
-    if (CS p >= bc && CS p <= bc + b->length) goto hit;
+    uschar * bc = US b + ALIGNED_SIZEOF_STOREBLOCK;
+    if (US p >= bc && US p <= bc + b->length) return TRUE;
     }
 
-for (pool = 0; pool < nelem(chainbase); pool++)
+for (pool = POOL_TAINT_BASE; pool < nelem(chainbase); pool++)
   for (b = chainbase[pool]; b; b = b->next)
     {
-    char * bc = CS b + ALIGNED_SIZEOF_STOREBLOCK;
-    if (CS p >= bc && CS p <= bc + b->length) goto hit;
+    uschar * bc = US b + ALIGNED_SIZEOF_STOREBLOCK;
+    if (US p >= bc && US p <= bc + b->length) return TRUE;
     }
 return FALSE;
-
-hit:
-return pool >= POOL_TAINT_BASE;
 }
 
 
@@ -198,6 +201,13 @@ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Taint mismatch, %s: %s %d\n",
 	msg, func, line);
 }
 
+static void
+use_slow_taint_check(void)
+{
+DEBUG(D_any) debug_printf("switching to slow-mode taint checking\n");
+f.taint_check_slow = TRUE;
+}
+
 
 /*************************************************
 *       Get a block from the current pool        *
@@ -820,6 +830,14 @@ if (!(yield = malloc((size_t)size)))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to malloc %d bytes of memory: "
     "called from line %d in %s", size, linenumber, func);
 
+/* If malloc ever returns apparently tainted memory, which glibc
+malloc will as it uses mmap for larger requests, we must switch to
+the slower checking for tainting (checking an address against all
+the tainted pool block spans, rather than just the mmap span) */
+
+if (!f.taint_check_slow && is_tainted(yield))
+  use_slow_taint_check();
+
 return store_alloc_tail(yield, size, func, linenumber, US"Malloc");
 }
 

commit ccf4e2396b27b519174aa79552e61d11aafbdc36
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Jan 17 21:55:11 2020 +0000

    Fix taint hybrid-checking on BSD
    
    (cherry-picked from commit 677481d4fc)
    Broken-by: 1ccd26e242

diff --git a/src/src/store.c b/src/src/store.c
index 692a993e9..6118ef28d 100644
--- a/src/src/store.c
+++ b/src/src/store.c
@@ -175,16 +175,15 @@ BOOL
 is_tainted_fn(const void * p)
 {
 storeblock * b;
-int pool;
 
-for (pool = POOL_TAINT_BASE; pool < nelem(chainbase); pool++)
+for (int pool = POOL_TAINT_BASE; pool < nelem(chainbase); pool++)
   if ((b = current_block[pool]))
     {
     uschar * bc = US b + ALIGNED_SIZEOF_STOREBLOCK;
     if (US p >= bc && US p <= bc + b->length) return TRUE;
     }
 
-for (pool = POOL_TAINT_BASE; pool < nelem(chainbase); pool++)
+for (int pool = POOL_TAINT_BASE; pool < nelem(chainbase); pool++)
   for (b = chainbase[pool]; b; b = b->next)
     {
     uschar * bc = US b + ALIGNED_SIZEOF_STOREBLOCK;
@@ -204,10 +203,28 @@ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Taint mismatch, %s: %s %d\n",
 static void
 use_slow_taint_check(void)
 {
+#ifndef COMPILE_UTILITY
 DEBUG(D_any) debug_printf("switching to slow-mode taint checking\n");
+#endif
 f.taint_check_slow = TRUE;
 }
 
+static void
+verify_all_untainted(void)
+{
+for (int pool = 0; pool < POOL_TAINT_BASE; pool++)
+  for (storeblock * b = chainbase[pool]; b; b = b->next)
+    {
+    uschar * bc = US b + ALIGNED_SIZEOF_STOREBLOCK;
+    if (is_tainted(bc))
+      {
+      use_slow_taint_check();
+      return;
+      }
+    }
+}
+
+
 
 /*************************************************
 *       Get a block from the current pool        *
@@ -740,7 +757,7 @@ int pool = tainted ? store_pool + POOL_TAINT_BASE : store_pool;
 BOOL release_ok = !tainted && store_last_get[pool] == block;
 uschar * newtext;
 
-#ifndef MACRO_PREDEF
+#if !defined(MACRO_PREDEF) && !defined(COMPILE_UTILITY)
 if (is_tainted(block) != tainted)
   die_tainted(US"store_newblock", CUS func, linenumber);
 #endif
@@ -799,6 +816,7 @@ if (!(yield = mmap(NULL, (size_t)size,
 
 if (yield < tainted_base) tainted_base = yield;
 if ((top = US yield + size) > tainted_top) tainted_top = top;
+if (!f.taint_check_slow) use_slow_taint_check();
 
 return store_alloc_tail(yield, size, func, line, US"Mmap");
 }

commit 4ce411ffa737df738e18e1e7b008ad3d3ac5c398
Author: Brian Foley <bpfoley@google.com>
Date:   Sat Jan 25 15:27:49 2020 +0000

    TFO: even in binary built for modern Linux, handle error returned by old Linux kernel.  Bug 2518
    
    (cherry picked from commit c3da38a12a2372a7f6a48be97ebfd80aeceda828)

diff --git a/src/src/ip.c b/src/src/ip.c
index 70e3e2064..43ca6a1c9 100644
--- a/src/src/ip.c
+++ b/src/src/ip.c
@@ -269,28 +269,34 @@ if (fastopen_blob && f.tcp_fastopen_ok)
     /*XXX also seen on successful TFO, sigh */
     tcp_out_fastopen = fastopen_blob->len > 0 ?  TFO_ATTEMPTED_DATA : TFO_ATTEMPTED_NODATA;
     }
-  else if (errno == EINPROGRESS)	/* expected if we had no cookie for peer */
+  else switch (errno)
+    {
+    case EINPROGRESS:	/* expected if we had no cookie for peer */
 	/* seen for no-data, proper TFO option, both cookie-request and with-cookie cases */
 	/*  apparently no visibility of the diffference at this point */
 	/* seen for with-data, proper TFO opt, cookie-req */
 	/*   with netwk delay, post-conn tcp_info sees unacked 1 for R, 2 for C; code in smtp_out.c */
 	/* ? older Experimental TFO option behaviour ? */
-    {					/* queue unsent data */
-    DEBUG(D_transport|D_v) debug_printf(" TFO mode sendto, %s data: EINPROGRESS\n",
-      fastopen_blob->len > 0 ? "with"  : "no");
-    if (!fastopen_blob->data)
-      {
-      tcp_out_fastopen = TFO_ATTEMPTED_NODATA;		/* we tried; unknown if useful yet */
-      rc = 0;
-      }
-    else
-      rc = send(sock, fastopen_blob->data, fastopen_blob->len, 0);
-    }
-  else if(errno == EOPNOTSUPP)
-    {
-    DEBUG(D_transport)
-      debug_printf("Tried TCP Fast Open but apparently not enabled by sysctl\n");
-    goto legacy_connect;
+      DEBUG(D_transport|D_v) debug_printf(" TFO mode sendto, %s data: EINPROGRESS\n",
+	fastopen_blob->len > 0 ? "with"  : "no");
+      if (!fastopen_blob->data)
+	{
+	tcp_out_fastopen = TFO_ATTEMPTED_NODATA;		/* we tried; unknown if useful yet */
+	rc = 0;
+	}
+      else					/* queue unsent data */
+	rc = send(sock, fastopen_blob->data, fastopen_blob->len, 0);
+      break;
+
+    case EOPNOTSUPP:
+      DEBUG(D_transport)
+	debug_printf("Tried TCP Fast Open but apparently not enabled by sysctl\n");
+      goto legacy_connect;
+
+    case EPIPE:
+      DEBUG(D_transport)
+	debug_printf("Tried TCP Fast Open but kernel too old to support it\n");
+      goto legacy_connect;
     }
 # endif
 # ifdef EXIM_TFO_CONNECTX

commit 69b2f92c0b5da548eaafe4813319f4647fa9c19a
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Thu Jan 30 11:38:30 2020 +0000

    Taint: slow-mode checking only
    
    (cherry-picked from 4381d60bc9)

diff --git a/src/src/functions.h b/src/src/functions.h
index 0b5905562..af633851b 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -616,10 +616,7 @@ return FALSE;
 
 #else
 extern BOOL is_tainted_fn(const void *);
-extern void * tainted_base, * tainted_top;
-
-return f.taint_check_slow
-  ? is_tainted_fn(p) : p >= tainted_base && p < tainted_top;
+return is_tainted_fn(p);
 #endif
 }
 
diff --git a/src/src/store.c b/src/src/store.c
index 6118ef28d..c81744a7b 100644
--- a/src/src/store.c
+++ b/src/src/store.c
@@ -102,13 +102,6 @@ static storeblock *current_block[NPOOLS];
 static void *next_yield[NPOOLS];
 static int yield_length[NPOOLS] = { -1, -1, -1,  -1, -1, -1 };
 
-/* The limits of the tainted pools.  Tracking these on new allocations enables
-a fast is_tainted implementation. We assume the kernel only allocates mmaps using
-one side or the other of data+heap, not both. */
-
-void * tainted_base = (void *)-1;
-void * tainted_top = (void *)0;
-
 /* pool_malloc holds the amount of memory used by the store pools; this goes up
 and down as store is reset or released. nonpool_malloc is the total got by
 malloc from other calls; this doesn't go down because it is just freed by
@@ -200,30 +193,6 @@ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Taint mismatch, %s: %s %d\n",
 	msg, func, line);
 }
 
-static void
-use_slow_taint_check(void)
-{
-#ifndef COMPILE_UTILITY
-DEBUG(D_any) debug_printf("switching to slow-mode taint checking\n");
-#endif
-f.taint_check_slow = TRUE;
-}
-
-static void
-verify_all_untainted(void)
-{
-for (int pool = 0; pool < POOL_TAINT_BASE; pool++)
-  for (storeblock * b = chainbase[pool]; b; b = b->next)
-    {
-    uschar * bc = US b + ALIGNED_SIZEOF_STOREBLOCK;
-    if (is_tainted(bc))
-      {
-      use_slow_taint_check();
-      return;
-      }
-    }
-}
-
 
 
 /*************************************************
@@ -814,10 +783,6 @@ if (!(yield = mmap(NULL, (size_t)size,
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to mmap %d bytes of memory: "
     "called from line %d of %s", size, line, func);
 
-if (yield < tainted_base) tainted_base = yield;
-if ((top = US yield + size) > tainted_top) tainted_top = top;
-if (!f.taint_check_slow) use_slow_taint_check();
-
 return store_alloc_tail(yield, size, func, line, US"Mmap");
 }
 
@@ -848,14 +813,6 @@ if (!(yield = malloc((size_t)size)))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to malloc %d bytes of memory: "
     "called from line %d in %s", size, linenumber, func);
 
-/* If malloc ever returns apparently tainted memory, which glibc
-malloc will as it uses mmap for larger requests, we must switch to
-the slower checking for tainting (checking an address against all
-the tainted pool block spans, rather than just the mmap span) */
-
-if (!f.taint_check_slow && is_tainted(yield))
-  use_slow_taint_check();
-
 return store_alloc_tail(yield, size, func, linenumber, US"Malloc");
 }
 

commit 59bcc75f56ffeb9fa220f1eb53d45bf254258ac7
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Thu Feb 13 14:08:31 2020 +0000

    Auths: fix cyrus-sasl driver for gssapi use.  Bug 2524
    
    Broken-by: c0fb53b74e
    Cherry-picked from: 5c329a4388

diff --git a/src/src/auths/cyrus_sasl.c b/src/src/auths/cyrus_sasl.c
index 480010bab..19416a1bb 100644
--- a/src/src/auths/cyrus_sasl.c
+++ b/src/src/auths/cyrus_sasl.c
@@ -347,10 +347,10 @@ for (rc = SASL_CONTINUE; rc == SASL_CONTINUE; )
     }
   else
     {
-    /* make sure that we have a null-terminated string */
-    out2 = string_copyn(output, outlen);
+    /* auth_get_data() takes a length-specfied block of binary
+    which can include zeroes; no terminating NUL is needed */
 
-    if ((rc = auth_get_data(&input, out2, outlen)) != OK)
+    if ((rc = auth_get_data(&input, output, outlen)) != OK)
       {
       /* we couldn't get the data, so free up the library before
        * returning whatever error we get */

commit 26b045604bd574a6d93868ed437c08503c67d289
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Thu Feb 13 16:52:52 2020 +0000

    GnuTLS: fix hanging callout connections
    
    Broken-by: 925ac8e4f1
    Cherry-picked from: bd95ffc2ba

diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c
index fc426a251..574dcafd9 100644
--- a/src/src/tls-gnu.c
+++ b/src/src/tls-gnu.c
@@ -181,6 +181,10 @@ typedef struct exim_gnutls_state {
   BOOL			peer_dane_verified;
   BOOL			trigger_sni_changes;
   BOOL			have_set_peerdn;
+#ifdef SUPPORT_CORK
+  BOOL			corked:1;
+#endif
+
   const struct host_item *host;		/* NULL if server */
   gnutls_x509_crt_t	peercert;
   uschar		*peerdn;
@@ -3309,9 +3313,8 @@ ssize_t outbytes;
 size_t left = len;
 exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server;
 #ifdef SUPPORT_CORK
-static BOOL corked = FALSE;
 
-if (more && !corked) gnutls_record_cork(state->session);
+if (more && !state->corked) gnutls_record_cork(state->session);
 #endif
 
 DEBUG(D_tls) debug_printf("%s(%p, " SIZE_T_FMT "%s)\n", __FUNCTION__,
@@ -3352,10 +3355,10 @@ if (len > INT_MAX)
   }
 
 #ifdef SUPPORT_CORK
-if (more != corked)
+if (more != state->corked)
   {
   if (!more) (void) gnutls_record_uncork(state->session, 0);
-  corked = more;
+  state->corked = more;
   }
 #endif
 

commit da5bc28fbc92521e5fe5ab65a8bce1a10610b70c
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Wed Feb 26 10:54:56 2020 +0000

    Fix ${tr } expansion item.  Bug 2533
    
    Broken-by: acec9514b1
    (cherry picked from commit 03f110c5d92f3c8aa9dc447253a33e9c039a78b0)

diff --git a/src/src/expand.c b/src/src/expand.c
index 21686e99c..dea26494a 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -5029,7 +5029,7 @@ while (*s != 0)
       {
       client_conn_ctx cctx;
       int timeout = 5;
-      int save_ptr = yield->ptr;
+      int save_ptr = yield ? yield->ptr : 0;
       FILE * fp = NULL;
       uschar * arg;
       uschar * sub_arg[4];
@@ -5262,7 +5262,7 @@ while (*s != 0)
 
         if (sigalrm_seen)
           {
-          yield->ptr = save_ptr;
+          if (yield) yield->ptr = save_ptr;
           expand_string_message = US "socket read timed out";
           goto SOCK_FAIL;
           }
@@ -5429,7 +5429,7 @@ while (*s != 0)
 
     case EITEM_TR:
       {
-      int oldptr = yield->ptr;
+      int oldptr = yield ? yield->ptr : 0;
       int o2m;
       uschar *sub[3];
 
@@ -6170,7 +6170,7 @@ while (*s != 0)
     case EITEM_REDUCE:
       {
       int sep = 0;
-      int save_ptr = yield->ptr;
+      int save_ptr = yield ? yield->ptr : 0;
       uschar outsep[2] = { '\0', '\0' };
       const uschar *list, *expr, *temp;
       uschar *save_iterate_item = iterate_item;
@@ -6317,7 +6317,8 @@ while (*s != 0)
         item of the output list, add in a space if the new item begins with the
         separator character, or is an empty string. */
 
-        if (yield->ptr != save_ptr && (temp[0] == *outsep || temp[0] == 0))
+        if (  yield && yield->ptr != save_ptr
+	   && (temp[0] == *outsep || temp[0] == 0))
           yield = string_catn(yield, US" ", 1);
 
         /* Add the string in "temp" to the output list that we are building,
@@ -6357,7 +6358,7 @@ while (*s != 0)
       the redundant final separator. Even though an empty item at the end of a
       list does not count, this is tidier. */
 
-      else if (yield->ptr != save_ptr) yield->ptr--;
+      else if (yield && yield->ptr != save_ptr) yield->ptr--;
 
       /* Restore preserved $item */
 
@@ -7222,7 +7223,7 @@ while (*s != 0)
         {
         uschar outsep[2] = { ':', '\0' };
         uschar *address, *error;
-        int save_ptr = yield->ptr;
+        int save_ptr = yield ? yield->ptr : 0;
         int start, end, domain;  /* Not really used */
 
         while (isspace(*sub)) sub++;
@@ -7253,7 +7254,7 @@ while (*s != 0)
 
           if (address)
             {
-            if (yield->ptr != save_ptr && address[0] == *outsep)
+            if (yield && yield->ptr != save_ptr && address[0] == *outsep)
               yield = string_catn(yield, US" ", 1);
 
             for (;;)
@@ -7282,7 +7283,7 @@ while (*s != 0)
         /* If we have generated anything, remove the redundant final
         separator. */
 
-        if (yield->ptr != save_ptr) yield->ptr--;
+        if (yield && yield->ptr != save_ptr) yield->ptr--;
         f.parse_allow_group = FALSE;
         continue;
         }

commit 1560edd9600ee87a18854a9bf8cdd68d54544205
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sun Mar 1 15:19:22 2020 +0000

    Fix $mime_part_count for non-mime message on multi-message connection.  Bug 2537
    
    Cherry-picked from b273058b34

diff --git a/src/src/receive.c b/src/src/receive.c
index 4e85ffacf..350c4eda8 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -1756,6 +1756,13 @@ if (thismessage_size_limit <= 0) thismessage_size_limit = INT_MAX;
 message_linecount = body_linecount = body_zerocount =
   max_received_linelength = 0;
 
+#ifdef WITH_CONTENT_SCAN
+/* reset non-per-part mime variables */
+mime_is_coverletter    = 0;
+mime_is_rfc822         = 0;
+mime_part_count        = -1;
+#endif
+
 #ifndef DISABLE_DKIM
 /* Call into DKIM to set up the context.  In CHUNKING mode
 we clear the dot-stuffing flag */

commit 8d844fbac46c4dcafd507d66b497a537581f0994
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Thu Mar 5 19:45:04 2020 +0000

        Taint: track in ${utf8clean:} operator
    
        (cherry picked from commit e68def51cb753d730249565e630b549a73857ec1)

diff --git a/src/src/expand.c b/src/src/expand.c
index dea26494a..f489c5b8b 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -7422,10 +7422,10 @@ while (*s != 0)
 
       case EOP_FROM_UTF8:
         {
-        while (*sub != 0)
+	uschar * buff = store_get(4, is_tainted(sub));
+        while (*sub)
           {
           int c;
-          uschar buff[4];
           GETUTF8INC(c, sub);
           if (c > 255) c = '_';
           buff[0] = c;
@@ -7446,7 +7446,17 @@ while (*s != 0)
         int complete;
         uschar seq_buff[4];			/* accumulate utf-8 here */
 
-        while (*sub != 0)
+	/* Manually track tainting, as we deal in individual chars below */
+
+	if (is_tainted(sub))
+	  if (yield->s && yield->ptr)
+	    gstring_rebuffer(yield);
+	  else
+	    yield->s = store_get(yield->size = Ustrlen(sub), TRUE);
+
+	/* Check the UTF-8, byte-by-byte */
+
+        while (*sub)
 	  {
 	  complete = 0;
 	  uschar c = *sub++;
diff --git a/src/src/functions.h b/src/src/functions.h
index af633851b..a1dab5db0 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -902,6 +902,18 @@ va_end(ap);
 return g;
 }
 
+
+/* Copy the content of a string to tainted memory */
+
+static inline void
+gstring_rebuffer(gstring * g)
+{
+uschar * s = store_get(g->size, TRUE);
+memcpy(s, g->s, g->ptr);
+g->s = s;
+}
+
+
 /******************************************************************************/
 
 #define store_get_dns_answer() store_get_dns_answer_trc(CUS __FUNCTION__, __LINE__)
diff --git a/src/src/string.c b/src/src/string.c
index 49a028343..6fa01250f 100644
--- a/src/src/string.c
+++ b/src/src/string.c
@@ -12,7 +12,6 @@ utilities and tests, and are cut out by the COMPILE_UTILITY macro. */
 #include "exim.h"
 #include <assert.h>
 
-static void gstring_rebuffer(gstring * g);
 
 #ifndef COMPILE_UTILITY
 /*************************************************
@@ -1243,16 +1242,6 @@ return !!gp;
 
 
 
-/* Copy the content of a string to tainted memory */
-static void
-gstring_rebuffer(gstring * g)
-{
-uschar * s = store_get(g->size, TRUE);
-memcpy(s, g->s, g->ptr);
-g->s = s;
-}
-
-
 
 /* Build or append to a growing-string, sprintf-style.
 

commit 7d0615457f5f26bab3900804d60e9b96c09738c9
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sun Mar 15 21:08:28 2020 +0000

    Fix spurious detection of timeout while writing to transport filter
    
    (cherry picked from commit 130212d30d47e588c2bc03edcfce11bf3857da03)

diff --git a/src/src/transport.c b/src/src/transport.c
index df7fd1628..70e96c065 100644
--- a/src/src/transport.c
+++ b/src/src/transport.c
@@ -253,6 +253,7 @@ for (int i = 0; i < 100; i++)
 
   else
     {
+    sigalrm_seen = FALSE;
     ALARM(local_timeout);
 
     rc =

commit 768f456c80387efb40ae35d7873d9eee548c4232
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Mar 20 19:52:14 2020 +0000

        Fix segfault on bad cmdline -f (sender) argument.  Bug 2541
    
        (cherry picked from commit 5fcc791a74a6f6933b3fb03f36e9ea3553152cf7)

diff --git a/src/src/exim.c b/src/src/exim.c
index 0e839c5d4..96c020c8e 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -2554,8 +2554,10 @@ for (i = 1; i < argc; i++)
 #ifdef SUPPORT_I18N
 	allow_utf8_domains = TRUE;
 #endif
-        sender_address = parse_extract_address(argrest, &errmess,
-          &dummy_start, &dummy_end, &sender_address_domain, TRUE);
+        if (!(sender_address = parse_extract_address(argrest, &errmess,
+		  &dummy_start, &dummy_end, &sender_address_domain, TRUE)))
+          exim_fail("exim: bad -f address \"%s\": %s\n", argrest, errmess);
+
 	sender_address = string_copy_taint(sender_address, TRUE);
 #ifdef SUPPORT_I18N
 	message_smtputf8 =  string_is_utf8(sender_address);
@@ -2563,8 +2565,6 @@ for (i = 1; i < argc; i++)
 #endif
         allow_domain_literals = FALSE;
         strip_trailing_dot = FALSE;
-        if (!sender_address)
-          exim_fail("exim: bad -f address \"%s\": %s\n", argrest, errmess);
         }
       f.sender_address_forced = TRUE;
       }

commit 35250a206044733546c5a4ac63cd9b4e3aa13e70
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sun Mar 22 11:35:50 2020 +0000

        Taint: fix dsearch result to be untainted
    
        (cherry-picked from commits 36b600bb77, eb763a7aaf)

diff --git a/src/src/lookups/dsearch.c b/src/src/lookups/dsearch.c
index c27f5d6e6..1eb2924f0 100644
--- a/src/src/lookups/dsearch.c
+++ b/src/src/lookups/dsearch.c
@@ -28,7 +28,7 @@ static void *
 dsearch_open(uschar *dirname, uschar **errmsg)
 {
 DIR *dp = opendir(CS dirname);
-if (dp == NULL)
+if (!dp)
   {
   int save_errno = errno;
   *errmsg = string_open_failed(errno, "%s for directory search", dirname);
@@ -47,8 +47,8 @@ return (void *)(-1);
 /* The handle will always be (void *)(-1), but don't try casting it to an
 integer as this gives warnings on 64-bit systems. */
 
-BOOL
-static dsearch_check(void *handle, uschar *filename, int modemask, uid_t *owners,
+static BOOL
+dsearch_check(void *handle, uschar *filename, int modemask, uid_t *owners,
   gid_t *owngroups, uschar **errmsg)
 {
 handle = handle;
@@ -87,7 +87,9 @@ if (Ustrchr(keystring, '/') != 0)
 filename = string_sprintf("%s/%s", dirname, keystring);
 if (Ulstat(filename, &statbuf) >= 0)
   {
-  *result = string_copy(keystring);
+  /* Since the filename exists in the filesystem, we can return a
+  non-tainted result. */
+  *result = string_copy_taint(keystring, FALSE);
   return OK;
   }
 

commit 26d90d189dbad5890090340a5d4f478f3b63af0a
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Thu Mar 26 16:05:19 2020 +0000

    Fix argument checking for ${readsocket }
    
    (cherry picked from commit c06ddd664f4a0f6e485f6f9f6b5086aa4719a441)

diff --git a/src/src/expand.c b/src/src/expand.c
index f489c5b8b..df3669ca0 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -5070,8 +5070,9 @@ while (*s != 0)
 	uschar * item;
 	int sep = 0;
 
-	item = string_nextinlist(&list, &sep, NULL, 0);
-        if ((timeout = readconf_readtime(item, 0, FALSE)) < 0)
+	if (  !(item = string_nextinlist(&list, &sep, NULL, 0))
+	   || !*item
+	   || (timeout = readconf_readtime(item, 0, FALSE)) < 0)
           {
           expand_string_message = string_sprintf("bad time value %s", item);
           goto EXPAND_FAILED;

commit 6c43594701b8fa2056702b343ad5bfe9dd618ef0
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Mar 27 21:07:50 2020 +0000

    OpenSSL: avoid loading server's CA-list-for-client-notification on client
    
    (cherry picked from commit d06f582fd0714042fcc110c9e0f7665657bb9da5)

diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c
index 063c23df7..b3c079303 100644
--- a/src/src/tls-openssl.c
+++ b/src/src/tls-openssl.c
@@ -2480,7 +2480,7 @@ This is inconsistent with the need to verify the OCSP proof of the server cert.
 #endif
 	}
 
-      /* If a certificate file is empty, the next function fails with an
+      /* If a certificate file is empty, the load function fails with an
       unhelpful error message. If we skip it, we get the correct behaviour (no
       certificates are recognized, but the error message is still misleading (it
       says no certificate was supplied).  But this is better. */
@@ -2489,9 +2489,9 @@ This is inconsistent with the need to verify the OCSP proof of the server cert.
          && !SSL_CTX_load_verify_locations(sctx, CS file, CS dir))
 	return tls_error(US"SSL_CTX_load_verify_locations", host, NULL, errstr);
 
-      /* Load the list of CAs for which we will accept certs, for sending
-      to the client.  This is only for the one-file tls_verify_certificates
-      variant.
+      /* On the server load the list of CAs for which we will accept certs, for
+      sending to the client.  This is only for the one-file
+      tls_verify_certificates variant.
       If a list isn't loaded into the server, but some verify locations are set,
       the server end appears to make a wildcard request for client certs.
       Meanwhile, the client library as default behaviour *ignores* the list
@@ -2503,7 +2503,7 @@ This is inconsistent with the need to verify the OCSP proof of the server cert.
 	{
 	STACK_OF(X509_NAME) * names = SSL_load_client_CA_file(CS file);
 
-	SSL_CTX_set_client_CA_list(sctx, names);
+	if (!host) SSL_CTX_set_client_CA_list(sctx, names);
 	DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n",
 				    sk_X509_NAME_num(names));
 	}

commit 10f790627709d66cb9b08755306de7096c015ef1
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sun Apr 12 17:47:27 2020 +0100

    Taint: fix parsing of ACL ratelimit condition
    
    (cherry picked from commit 0d7a24c68f30ce73df5f0859b2de6a39e74b92bc)

diff --git a/src/src/acl.c b/src/src/acl.c
index 799af39c3..537514d21 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -2259,7 +2259,7 @@ count = 1.0;
 
 /* Parse the other options. */
 
-while ((ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size)))
+while ((ss = string_nextinlist(&arg, &sep, NULL, 0)))
   {
   if (strcmpic(ss, US"leaky") == 0) leaky = TRUE;
   else if (strcmpic(ss, US"strict") == 0) strict = TRUE;

commit 62eab38cc8b135cb2d356f3cae23e005c6b45bcd
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Mon Apr 20 23:32:48 2020 +0100

        Fix spool space check to account for SIZE.  Bug 2552
    
        (cherry picked from commit dd6711f05c57e1e284306ccc9bb6cd6c926cea01)

diff --git a/src/src/receive.c b/src/src/receive.c
index 350c4eda8..bafd91b0b 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -270,8 +270,8 @@ if (check_spool_space > 0 || msg_size > 0 || check_spool_inodes > 0)
       "check_space = " PR_EXIM_ARITH "K inodes = %d msg_size = %d\n",
       space, inodes, check_spool_space, check_spool_inodes, msg_size);
 
-  if ((space >= 0 && space < check_spool_space) ||
-      (inodes >= 0 && inodes < check_spool_inodes))
+  if (  space >= 0 && space + msg_size / 1024 < check_spool_space
+     || inodes >= 0 && inodes < check_spool_inodes)
     {
     log_write(0, LOG_MAIN, "spool directory space check failed: space="
       PR_EXIM_ARITH " inodes=%d", space, inodes);

commit 15d9a1a2ccebc2fe9fdff75309481437df624379
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sat Apr 25 21:03:51 2020 +0100

    DKIM: fix $dkim_key_length in verify
    
    (cherry picked from commit a841a6eca79ff08b36f2225dcf89c1c162bb8777)

diff --git a/src/src/arc.c b/src/src/arc.c
index 94365498f..56dfa01a0 100644
--- a/src/src/arc.c
+++ b/src/src/arc.c
@@ -735,7 +735,7 @@ arc_get_verify_hhash(ctx, ams, &hhash);
 
 /* Setup the interface to the signing library */
 
-if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx)))
+if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx, NULL)))
   {
   DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr);
   as->ams_verify_done = arc_state_reason = US"internal sigverify init error";
@@ -964,7 +964,7 @@ if (!(p = arc_line_to_pubkey(hdr_as)))
 /* We know the b-tag blob is of a nul-term string, so safe as a string */
 pdkim_decode_base64(hdr_as->b.data, &sighash);
 
-if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx)))
+if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx, NULL)))
   {
   DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr);
   return US"fail";
diff --git a/src/src/dkim.c b/src/src/dkim.c
index 065170444..89516bf10 100644
--- a/src/src/dkim.c
+++ b/src/src/dkim.c
@@ -401,7 +401,7 @@ for (pdkim_signature * sig = dkim_signatures; sig; sig = sig->next)
     dkim_cur_sig = sig;
     dkim_signing_domain = US sig->domain;
     dkim_signing_selector = US sig->selector;
-    dkim_key_length = sig->sighash.len * 8;
+    dkim_key_length = sig->keybits;
 
     /* These two return static strings, so we can compare the addr
     later to see if the ACL overwrote them.  Check that when logging */
diff --git a/src/src/pdkim/pdkim.c b/src/src/pdkim/pdkim.c
index 7fcfbc76a..0d2674f50 100644
--- a/src/src/pdkim/pdkim.c
+++ b/src/src/pdkim/pdkim.c
@@ -1430,7 +1430,7 @@ if (sig->keytype == KEYTYPE_ED25519)
 
 if ((*errstr = exim_dkim_verify_init(&p->key,
 	    sig->keytype == KEYTYPE_ED25519 ? KEYFMT_ED25519_BARE : KEYFMT_DER,
-	    vctx)))
+	    vctx, &sig->keybits)))
   {
   DEBUG(D_acl) debug_printf("verify_init: %s\n", *errstr);
   sig->verify_status =      PDKIM_VERIFY_INVALID;
diff --git a/src/src/pdkim/pdkim.h b/src/src/pdkim/pdkim.h
index 0c9d46d01..ee5b9a2c4 100644
--- a/src/src/pdkim/pdkim.h
+++ b/src/src/pdkim/pdkim.h
@@ -140,8 +140,9 @@ typedef struct pdkim_signature {
   int version;
 
   /* (a=) The signature algorithm. */
-  int keytype;	/* pdkim_keytypes index */
-  int hashtype;	/* pdkim_hashes index */
+  int		keytype;	/* pdkim_keytypes index */
+  unsigned	keybits;	/* size of the key */
+  int		hashtype;	/* pdkim_hashes index */
 
   /* (c=x/) Header canonicalization method. Either PDKIM_CANON_SIMPLE
      or PDKIM_CANON_RELAXED */
diff --git a/src/src/pdkim/signing.c b/src/src/pdkim/signing.c
index 102e7bf51..b55bd9f5f 100644
--- a/src/src/pdkim/signing.c
+++ b/src/src/pdkim/signing.c
@@ -155,7 +155,8 @@ return NULL;
 Return: NULL for success, or an error string */
 
 const uschar *
-exim_dkim_verify_init(blob * pubkey, keyformat fmt, ev_ctx * verify_ctx)
+exim_dkim_verify_init(blob * pubkey, keyformat fmt, ev_ctx * verify_ctx,
+  unsigned * bits)
 {
 gnutls_datum_t k;
 int rc;
@@ -182,6 +183,7 @@ switch(fmt)
     ret = US"pubkey format not handled";
     break;
   }
+if (!ret && bits) gnutls_pubkey_get_pk_algorithm(verify_ctx->key, bits);
 return ret;
 }
 
@@ -552,7 +554,8 @@ return NULL;
 Return: NULL for success, or an error string */
 
 const uschar *
-exim_dkim_verify_init(blob * pubkey, keyformat fmt, ev_ctx * verify_ctx)
+exim_dkim_verify_init(blob * pubkey, keyformat fmt, ev_ctx * verify_ctx,
+  unsigned * bits)
 {
 /*
 in code sequence per b81207d2bfa92 rsa_parse_public_key() and asn1_get_mpi()
@@ -560,6 +563,7 @@ in code sequence per b81207d2bfa92 rsa_parse_public_key() and asn1_get_mpi()
 uschar tag_class;
 int taglen;
 long alen;
+unsigned nbits;
 int rc;
 uschar * errstr;
 gcry_error_t gerr;
@@ -608,10 +612,10 @@ if ((rc = as_tag(pubkey, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, NULL))
 
 /* read two integers */
 DEBUG(D_acl) stage = US"MPI";
-if (  (errstr = as_mpi(pubkey, &verify_ctx->n))
-   || (errstr = as_mpi(pubkey, &verify_ctx->e))
-   )
-  return errstr;
+nbits = pubkey->len;
+if ((errstr = as_mpi(pubkey, &verify_ctx->n))) return errstr;
+nbits = (nbits - pubkey->len) * 8;
+if ((errstr = as_mpi(pubkey, &verify_ctx->e))) return errstr;
 
 #ifdef extreme_debug
 DEBUG(D_acl) debug_printf_indent("rsa_verify_init:\n");
@@ -624,6 +628,7 @@ DEBUG(D_acl) debug_printf_indent("rsa_verify_init:\n");
 	}
 
 #endif
+if (bits) *bits = nbits;
 return NULL;
 
 asn_err:
@@ -794,7 +799,8 @@ return US ERR_error_string(ERR_get_error(), NULL);
 Return: NULL for success, or an error string */
 
 const uschar *
-exim_dkim_verify_init(blob * pubkey, keyformat fmt, ev_ctx * verify_ctx)
+exim_dkim_verify_init(blob * pubkey, keyformat fmt, ev_ctx * verify_ctx,
+  unsigned * bits)
 {
 const uschar * s = pubkey->data;
 uschar * ret = NULL;
@@ -818,6 +824,7 @@ switch(fmt)
     break;
   }
 
+if (!ret && bits) *bits = EVP_PKEY_bits(verify_ctx->key);
 return ret;
 }
 
diff --git a/src/src/pdkim/signing.h b/src/src/pdkim/signing.h
index 96a0720fd..6ecde7537 100644
--- a/src/src/pdkim/signing.h
+++ b/src/src/pdkim/signing.h
@@ -90,7 +90,7 @@ extern gstring * exim_dkim_data_append(gstring *, uschar *);
 
 extern const uschar * exim_dkim_signing_init(const uschar *, es_ctx *);
 extern const uschar * exim_dkim_sign(es_ctx *, hashmethod, blob *, blob *);
-extern const uschar * exim_dkim_verify_init(blob *, keyformat, ev_ctx *);
+extern const uschar * exim_dkim_verify_init(blob *, keyformat, ev_ctx *, unsigned *);
 extern const uschar * exim_dkim_verify(ev_ctx *, hashmethod, blob *, blob *);
 
 #endif	/*DISABLE_DKIM*/
