commit 173bd1c8f9cf83ad8c0e61a9e32678e7e371d41d
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Tue Jun 2 14:50:31 2020 +0100

    Taint: fix pam expansion condition.  Bug 2587
    
    (cherry picked from commit f7f933a199be8bb7362c715e0040545b514cddca)

diff --git a/src/src/auths/call_pam.c b/src/src/auths/call_pam.c
index 2959cbbf3..80bb23ec3 100644
--- a/src/src/auths/call_pam.c
+++ b/src/src/auths/call_pam.c
@@ -83,8 +83,7 @@ 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)
+      if (!(arg = string_nextinlist(&pam_args, &sep, NULL, 0)))
 	{
 	arg = US"";
 	pam_arg_ended = TRUE;
@@ -155,7 +154,7 @@ pam_arg_ended = FALSE;
 fail. PAM doesn't support authentication with an empty user (it prompts for it,
 causing a potential mis-interpretation). */
 
-user = string_nextinlist(&pam_args, &sep, big_buffer, big_buffer_size);
+user = string_nextinlist(&pam_args, &sep, NULL, 0);
 if (user == NULL || user[0] == 0) return FAIL;
 
 /* Start off PAM interaction */

commit 63652bbaf66c4bdb388b08fdf3eb8ab1e4d91475
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Tue Jun 2 15:03:36 2020 +0100

    Taint: fix listcount expansion operator.  Bug 2586
    
    (cherry picked from commit 44644c2e404a3ea0191db0b0458e86924fb240bb)

diff --git a/src/src/expand.c b/src/src/expand.c
index 26f7f10ac..6ed22c14d 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -7208,9 +7208,8 @@ while (*s != 0)
         {
 	int cnt = 0;
 	int sep = 0;
-	uschar buffer[256];
 
-	while (string_nextinlist(CUSS &sub, &sep, buffer, sizeof(buffer))) cnt++;
+	while (string_nextinlist(CUSS &sub, &sep, NULL, 0)) cnt++;
 	yield = string_fmt_append(yield, "%d", cnt);
         continue;
         }

commit aabe0ebe82297d7dec3abdfff9c3b1edc34fd8ab
Author: Patrick Boutilier <boutilpj@ednet.ns.ca>
Date:   Tue Jun 2 15:16:10 2020 +0100

    Docs: fix mistaken variable name
    
    (cherry picked from commit eb55cb1d2c5552209e24345e9d21f83ec1eaccf6)

diff --git a/src/README.UPDATING b/src/README.UPDATING
index a0afa8df0..708027f2c 100644
--- a/src/README.UPDATING
+++ b/src/README.UPDATING
@@ -31,9 +31,9 @@ Exim version 4.94
 
 Some Transports now refuse to use tainted data in constructing their delivery
 location; this WILL BREAK configurations which are not updated accordingly.
-In particular: any Transport use of $local_user which has been relying upon
+In particular: any Transport use of $local_part which has been relying upon
 check_local_user far away in the Router to make it safe, should be updated to
-replace $local_user with $local_part_data.
+replace $local_part with $local_part_data.
 
 Attempting to remove, in router or transport, a header name that ends with
 an asterisk (which is a standards-legal name) will now result in all headers

commit 623f07cfdcaca96274ca765d0fcf0761bdf7151b
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Wed Jun 3 11:40:17 2020 +0100

    Taint: fix multiple ACL actions to properly manage tainted argument data
    
    (cherry picked from commit 12b7f811de4a540d0724585aecfa33b5881e2a30)

diff --git a/src/src/acl.c b/src/src/acl.c
index c1d60bbd9..8619cd5ef 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -3349,11 +3349,11 @@ for (; cb; cb = cb->next)
       {
       /* Separate the regular expression and any optional parameters. */
       const uschar * list = arg;
-      uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size);
+      uschar *ss = string_nextinlist(&list, &sep, NULL, 0);
       /* Run the dcc backend. */
       rc = dcc_process(&ss);
       /* Modify return code based upon the existence of options. */
-      while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
+      while ((ss = string_nextinlist(&list, &sep, NULL, 0)))
         if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER)
           rc = FAIL;   /* FAIL so that the message is passed to the next ACL */
       }
@@ -3514,7 +3514,7 @@ for (; cb; cb = cb->next)
       int sep = 0;
       const uschar *s = arg;
       uschar * ss;
-      while ((ss = string_nextinlist(&s, &sep, big_buffer, big_buffer_size)))
+      while ((ss = string_nextinlist(&s, &sep, NULL, 0)))
         {
         if (Ustrcmp(ss, "main") == 0) logbits |= LOG_MAIN;
         else if (Ustrcmp(ss, "panic") == 0) logbits |= LOG_PANIC;
@@ -3567,7 +3567,7 @@ for (; cb; cb = cb->next)
       {
       /* Separate the regular expression and any optional parameters. */
       const uschar * list = arg;
-      uschar * ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size);
+      uschar * ss = string_nextinlist(&list, &sep, NULL, 0);
       uschar * opt;
       BOOL defer_ok = FALSE;
       int timeout = 0;
@@ -3672,11 +3672,11 @@ for (; cb; cb = cb->next)
       {
       /* Separate the regular expression and any optional parameters. */
       const uschar * list = arg;
-      uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size);
+      uschar *ss = string_nextinlist(&list, &sep, NULL, 0);
 
       rc = spam(CUSS &ss);
       /* Modify return code based upon the existence of options. */
-      while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
+      while ((ss = string_nextinlist(&list, &sep, NULL, 0)))
         if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER)
           rc = FAIL;	/* FAIL so that the message is passed to the next ACL */
       }

commit 0e8319c3edebfec2158fbaa4898af27cb3225c99
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Thu Jun 4 15:28:15 2020 +0100

        Fix -bi.  Bug 2590
    
        Actual fix from pierre.labastie@neuf.fr ; additional coding and testcase bu jgh
        Broken-by: bdcc6f2bd5
    
        (Cherry-picked from: 0e0e171628)

diff --git a/src/src/exim.c b/src/src/exim.c
index a60488e95..6143fe989 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -2148,7 +2148,7 @@ on the second character (the one after '-'), to save some effort. */
 	concept of *the* alias file, but since Sun's YP make script calls
 	sendmail this way, some support must be provided. */
 	case 'i':
-	  if (!*++argrest) bi_option = TRUE;
+	  if (!*argrest) bi_option = TRUE;
 	  else badarg = TRUE;
 	  break;
 

commit 701af1005a6effaac5ce249f1c2086dc6c0c2a7f
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Mon Jun 8 13:00:55 2020 +0100

    Filters: fix "vacation" in Exim filter.  Bug 2593
    
    Broken-by: cfb9cf20cb (4.90)
    (cherry picked from commit 59eee1bc902f106d20f507ba16f37cb8ab5a5e8d)

diff --git a/src/src/transports/autoreply.c b/src/src/transports/autoreply.c
index 4c2c08b70..865abbf4f 100644
--- a/src/src/transports/autoreply.c
+++ b/src/src/transports/autoreply.c
@@ -474,10 +474,10 @@ if (oncelog && *oncelog && to)
   else
     {
     EXIM_DATUM key_datum, result_datum;
-    uschar * dirname = string_copy(oncelog);
-    uschar * s;
+    uschar * dirname, * s;
 
-    if ((s = Ustrrchr(dirname, '/'))) *s = '\0';
+    dirname = (s = Ustrrchr(oncelog, '/'))
+      ? string_copyn(oncelog, s - oncelog) : NULL;
     EXIM_DBOPEN(oncelog, dirname, O_RDWR|O_CREAT, ob->mode, &dbm_file);
     if (!dbm_file)
       {

commit 3fe5ec41e81831028c992f77a15292872fbbac75
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Thu Jun 11 20:45:05 2020 +0100

        TLS: use RFC 6125 rules for certifucate name checks when CNAMES are present. Bug 2594
    
        (cherry picked from commit 0851a3bbf4667081d47f5d85b6b3a5cb33cbdba6)

diff --git a/src/src/host.c b/src/src/host.c
index 0e0e0130b..817d4446c 100644
--- a/src/src/host.c
+++ b/src/src/host.c
@@ -1950,6 +1950,13 @@ BOOL temp_error = FALSE;
 int af;
 #endif
 
+#ifndef DISABLE_TLS
+/* Copy the host name at this point to the value which is used for
+TLS certificate name checking, before anything modifies it.  */
+
+host->certname = host->name;
+#endif
+
 /* Make sure DNS options are set as required. This appears to be necessary in
 some circumstances when the get..byname() function actually calls the DNS. */
 
@@ -2117,6 +2124,9 @@ for (int i = 1; i <= times;
       {
       host_item *next = store_get(sizeof(host_item), FALSE);
       next->name = host->name;
+#ifndef DISABLE_TLS
+      next->certname = host->certname;
+#endif
       next->mx = host->mx;
       next->address = text_address;
       next->port = PORT_NONE;
@@ -2260,6 +2270,13 @@ BOOL v6_find_again = FALSE;
 BOOL dnssec_fail = FALSE;
 int i;
 
+#ifndef DISABLE_TLS
+/* Copy the host name at this point to the value which is used for
+TLS certificate name checking, before any CNAME-following modifies it.  */
+
+host->certname = host->name;
+#endif
+
 /* If allow_ip is set, a name which is an IP address returns that value
 as its address. This is used for MX records when allow_mx_to_ip is set, for
 those sites that feel they have to flaunt the RFC rules. */
diff --git a/src/src/structs.h b/src/src/structs.h
index c6700d513..206237f04 100644
--- a/src/src/structs.h
+++ b/src/src/structs.h
@@ -80,14 +80,17 @@ typedef enum {DS_UNK=-1, DS_NO, DS_YES} dnssec_status_t;
 
 typedef struct host_item {
   struct host_item *next;
-  const uschar *name;             /* Host name */
-  const uschar *address;          /* IP address in text form */
-  int     port;                   /* port value in host order (if SRV lookup) */
-  int     mx;                     /* MX value if found via MX records */
-  int     sort_key;               /* MX*1000 plus random "fraction" */
-  int     status;                 /* Usable, unusable, or unknown */
-  int     why;                    /* Why host is unusable */
-  int     last_try;               /* Time of last try if known */
+  const uschar *name;		/* Host name */
+#ifndef DISABLE_TLS
+  const uschar *certname;	/* Name used for certificate checks */
+#endif
+  const uschar *address;	/* IP address in text form */
+  int     port;			/* port value in host order (if SRV lookup) */
+  int     mx;			/* MX value if found via MX records */
+  int     sort_key;		/* MX*1000 plus random "fraction" */
+  int     status;		/* Usable, unusable, or unknown */
+  int     why;			/* Why host is unusable */
+  int     last_try;		/* Time of last try if known */
   dnssec_status_t dnssec;
 } host_item;
 
diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c
index 24114f05e..875c82efa 100644
--- a/src/src/tls-gnu.c
+++ b/src/src/tls-gnu.c
@@ -2601,9 +2601,9 @@ if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK)
   {
   state->exp_tls_verify_cert_hostnames =
 #ifdef SUPPORT_I18N
-    string_domain_utf8_to_alabel(host->name, NULL);
+    string_domain_utf8_to_alabel(host->certname, NULL);
 #else
-    host->name;
+    host->certname;
 #endif
   DEBUG(D_tls)
     debug_printf("TLS: server cert verification includes hostname: \"%s\".\n",
diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c
index 8c9d8aa69..a62322928 100644
--- a/src/src/tls-openssl.c
+++ b/src/src/tls-openssl.c
@@ -372,10 +372,10 @@ typedef struct ocsp_resp {
 } ocsp_resplist;
 
 typedef struct tls_ext_ctx_cb {
-  tls_support * tlsp;
-  uschar *certificate;
-  uschar *privatekey;
-  BOOL is_server;
+  tls_support *	tlsp;
+  uschar *	certificate;
+  uschar *	privatekey;
+  BOOL		is_server;
 #ifndef DISABLE_OCSP
   STACK_OF(X509) *verify_stack;		/* chain for verifying the proof */
   union {
@@ -390,14 +390,14 @@ typedef struct tls_ext_ctx_cb {
     } client;
   } u_ocsp;
 #endif
-  uschar *dhparam;
+  uschar *	dhparam;
   /* these are cached from first expand */
-  uschar *server_cipher_list;
+  uschar *	server_cipher_list;
   /* only passed down to tls_error: */
-  host_item *host;
+  host_item *	host;
   const uschar * verify_cert_hostnames;
 #ifndef DISABLE_EVENT
-  uschar * event_action;
+  uschar *	event_action;
 #endif
 } tls_ext_ctx_cb;
 
@@ -2915,9 +2915,9 @@ if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK)
   {
   cbinfo->verify_cert_hostnames =
 #ifdef SUPPORT_I18N
-    string_domain_utf8_to_alabel(host->name, NULL);
+    string_domain_utf8_to_alabel(host->certname, NULL);
 #else
-    host->name;
+    host->certname;
 #endif
   DEBUG(D_tls) debug_printf("Cert hostname to check: \"%s\"\n",
 		    cbinfo->verify_cert_hostnames);

commit 94d719d803caf2c0c902dceeb787795eac11a63b
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Jun 12 00:46:34 2020 +0100

    Taint: fix radius expansion condition
    
    (cherry picked from commit f91219c114a3d95792d052555664a5a7a3984a8d)

diff --git a/src/src/auths/call_radius.c b/src/src/auths/call_radius.c
index cc269dcd5..9d10b34c6 100644
--- a/src/src/auths/call_radius.c
+++ b/src/src/auths/call_radius.c
@@ -96,8 +96,7 @@ int sep = 0;
 #endif
 
 
-user = string_nextinlist(&radius_args, &sep, big_buffer, big_buffer_size);
-if (!user) user = US"";
+if (!(user = string_nextinlist(&radius_args, &sep, NULL, 0))) user = US"";
 
 DEBUG(D_auth) debug_printf("Running RADIUS authentication for user \"%s\" "
                "and \"%s\"\n", user, radius_args);

commit c165e95889471bc1a644104dd9a6129c47c56c09
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Jun 12 20:43:43 2020 +0100

    smtp_accept_map_per_host: call search_tidyup in fail path.  Bug 2597
    
    (cherry-picked from: d3a538c8fe)

diff --git a/src/src/daemon.c b/src/src/daemon.c
index 2bed143a1..9d491593f 100644
--- a/src/src/daemon.c
+++ b/src/src/daemon.c
@@ -336,6 +336,7 @@ if ((max_for_this_host > 0) &&
     log_write(L_connection_reject,
               LOG_MAIN, "Connection from %s refused: too many connections "
       "from that IP address", whofrom->s);
+    search_tidyup();
     goto ERROR_RETURN;
     }
   }

commit ecf1e77accda6355ebb745a0a03e97ba7eb298b2
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sun Jun 14 22:14:11 2020 +0100

    Taint: fix verify.  Bug 2598
    
    (cherry-picked from 2b60ac1021 and 9eed571fd7)

diff --git a/src/src/acl.c b/src/src/acl.c
index 8619cd5ef..11d1fd028 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -1767,7 +1767,7 @@ switch(vp->value)
 /* Remaining items are optional; they apply to sender and recipient
 verification, including "header sender" verification. */
 
-while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
+while ((ss = string_nextinlist(&list, &sep, NULL, 0)))
   {
   if (strcmpic(ss, US"defer_ok") == 0) defer_ok = TRUE;
   else if (strcmpic(ss, US"no_details") == 0) no_details = TRUE;
@@ -1804,7 +1804,7 @@ while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
 	uschar * opt;
 
         while (isspace(*sublist)) sublist++;
-        while ((opt = string_nextinlist(&sublist, &optsep, buffer, sizeof(buffer))))
+        while ((opt = string_nextinlist(&sublist, &optsep, NULL, 0)))
           {
 	  callout_opt_t * op;
 	  double period = 1.0F;

commit 5c608b75d5bd734ddca41e4468fb22544ef96265
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sat Jun 20 00:54:05 2020 +0100

    Fix string_copy() macro to not multiple-eval args. Bug 2603
    
    Broken-by: a76d120aed
    (cherry picked from commit 80c2ec2e47c556daff00c79ee068ce68f25fd264)

diff --git a/src/src/functions.h b/src/src/functions.h
index 0028deb0d..0050cdeeb 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -767,9 +767,9 @@ string_copy_trc(const uschar * s, const char * func, int line)
 /* Simple string-copy functions maintaining the taint */
 
 #define string_copyn(s, len) \
-	string_copyn_taint_trc((s), (len), is_tainted(s), __FUNCTION__, __LINE__)
+	string_copyn_trc((s), (len), __FUNCTION__, __LINE__)
 #define string_copy(s) \
-	string_copy_taint_trc((s), is_tainted(s), __FUNCTION__, __LINE__)
+	string_copy_trc((s), __FUNCTION__, __LINE__)
 
 
 /*************************************************

commit cdee8a5f76cc013de5622112cd04e42d0dcf333b
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Mon Jun 22 17:27:18 2020 +0100

    Cutthrough: handle request when a callout-hold is active.  Bug 2604
    
    (cherry picked from commit 99bfcf2b678e7bd8125a7eb44409e46549bfc111)

diff --git a/src/src/acl.c b/src/src/acl.c
index 11d1fd028..62cb68561 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -3264,37 +3264,41 @@ for (; cb; cb = cb->next)
 	    the case where both sides handle prdr and this-node prdr acl
 	    is "accept" */
 	    ignored = US"PRDR active";
+	  else if (f.deliver_freeze)
+	    ignored = US"frozen";
+	  else if (f.queue_only_policy)
+	    ignored = US"queue-only";
+	  else if (fake_response == FAIL)
+	    ignored = US"fakereject";
+	  else if (rcpt_count != 1)
+	    ignored = US"nonfirst rcpt";
+	  else if (cutthrough.delivery)
+	    ignored = US"repeated";
+	  else if (cutthrough.callout_hold_only)
+	    {
+	    DEBUG(D_acl)
+	      debug_printf_indent(" cutthrough request upgrades callout hold\n");
+	    cutthrough.callout_hold_only = FALSE;
+	    cutthrough.delivery = TRUE;	/* control accepted */
+	    }
 	  else
 	    {
-	    if (f.deliver_freeze)
-	      ignored = US"frozen";
-	    else if (f.queue_only_policy)
-	      ignored = US"queue-only";
-	    else if (fake_response == FAIL)
-	      ignored = US"fakereject";
-	    else
+	    cutthrough.delivery = TRUE;	/* control accepted */
+	    while (*p == '/')
 	      {
-	      if (rcpt_count == 1)
+	      const uschar * pp = p+1;
+	      if (Ustrncmp(pp, "defer=", 6) == 0)
 		{
-		cutthrough.delivery = TRUE;	/* control accepted */
-		while (*p == '/')
-		  {
-		  const uschar * pp = p+1;
-		  if (Ustrncmp(pp, "defer=", 6) == 0)
-		    {
-		    pp += 6;
-		    if (Ustrncmp(pp, "pass", 4) == 0) cutthrough.defer_pass = TRUE;
-		    /* else if (Ustrncmp(pp, "spool") == 0) ;	default */
-		    }
-		  else
-		    while (*pp && *pp != '/') pp++;
-		  p = pp;
-		  }
+		pp += 6;
+		if (Ustrncmp(pp, "pass", 4) == 0) cutthrough.defer_pass = TRUE;
+		/* else if (Ustrncmp(pp, "spool") == 0) ;	default */
 		}
 	      else
-		ignored = US"nonfirst rcpt";
+		while (*pp && *pp != '/') pp++;
+	      p = pp;
 	      }
 	    }
+
 	  DEBUG(D_acl) if (ignored)
 	    debug_printf(" cutthrough request ignored on %s item\n", ignored);
 	  }
diff --git a/src/src/verify.c b/src/src/verify.c
index fba1f6e9e..5f4181de9 100644
--- a/src/src/verify.c
+++ b/src/src/verify.c
@@ -875,12 +875,12 @@ tls_retry_connection:
 		    case PENDING_OK:  done = TRUE;
 				      new_address_record.result = ccache_accept;
 				      break;
-		    case FAIL:	    done = TRUE;
+		    case FAIL:	      done = TRUE;
 				      yield = FAIL;
 				      *failure_ptr = US"recipient";
 				      new_address_record.result = ccache_reject;
 				      break;
-		    default:	    break;
+		    default:	      break;
 		    }
 		  break;
 

commit 777ee8ae75277c05fb72cc94f568ba4d2bfe15a6
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Thu Jun 25 11:16:54 2020 +0100

    Lookups: Fix "subdir" filter on a dsearch.
    
    (cherry picked from commit e0e21929b7426b9b5bbf5e3747797043801b1151)

diff --git a/src/src/lookups/dsearch.c b/src/src/lookups/dsearch.c
index 455273fb1..501293ac0 100644
--- a/src/src/lookups/dsearch.c
+++ b/src/src/lookups/dsearch.c
@@ -125,8 +125,7 @@ if (  Ulstat(filename, &statbuf) >= 0
        	 && S_ISDIR(statbuf.st_mode)
 	 && (  flags & FILTER_DIR
 	    || keystring[0] != '.'
-	    || keystring[1] != '.'
-	    || keystring[1] && keystring[2]
+	    || keystring[1] && keystring[1] != '.'
    )  )  )  )
   {
   /* Since the filename exists in the filesystem, we can return a
@@ -135,10 +134,10 @@ if (  Ulstat(filename, &statbuf) >= 0
   return OK;
   }
 
-if (errno == ENOENT) return FAIL;
+if (errno == ENOENT || errno == 0) return FAIL;
 
 save_errno = errno;
-*errmsg = string_sprintf("%s: lstat failed", filename);
+*errmsg = string_sprintf("%s: lstat: %s", filename, strerror(errno));
 errno = save_errno;
 return DEFER;
 }

commit 2be77199fc9009ab796ad2d67eed20d8da4773c7
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sun Jun 28 15:24:21 2020 +0100

    Sqlite: fix segfault on bad/missing sqlite_dbfile.  Bug 2606
    
    (cherry picked from commit 3d0472791a0928963a3f8184fe28479e80d1a47d)

diff --git a/src/src/lookups/sqlite.c b/src/src/lookups/sqlite.c
index dc4439153..1638ea401 100644
--- a/src/src/lookups/sqlite.c
+++ b/src/src/lookups/sqlite.c
@@ -24,16 +24,23 @@ sqlite_open(const uschar * filename, uschar ** errmsg)
 sqlite3 *db = NULL;
 int ret;
 
-if (!filename || !*filename) filename = sqlite_dbfile;
-if (*filename != '/')
+if (!filename || !*filename)
+  {
+  DEBUG(D_lookup) debug_printf_indent("Using sqlite_dbfile: %s\n", sqlite_dbfile);
+  filename = sqlite_dbfile;
+  }
+if (!filename || *filename != '/')
   *errmsg = US"absolute file name expected for \"sqlite\" lookup";
 else if ((ret = sqlite3_open(CCS filename, &db)) != 0)
   {
   *errmsg = (void *)sqlite3_errmsg(db);
+  sqlite3_close(db);
+  db = NULL;
   DEBUG(D_lookup) debug_printf_indent("Error opening database: %s\n", *errmsg);
   }
 
-sqlite3_busy_timeout(db, 1000 * sqlite_lock_timeout);
+if (db)
+  sqlite3_busy_timeout(db, 1000 * sqlite_lock_timeout);
 return db;
 }
 

commit 5f3e2ac9f39db5c8ef5a408929c8a5aba957b20f
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Mon Jun 29 17:26:36 2020 +0100

    Taint: fix ACL "spam" condition, to permit tainted name arguments.
    
    Cherry-picked from: 62b2ccce05

diff --git a/src/src/spam.c b/src/src/spam.c
index 5eff1ad5c..63ced4f65 100644
--- a/src/src/spam.c
+++ b/src/src/spam.c
@@ -190,7 +190,6 @@ spam(const uschar **listptr)
 int sep = 0;
 const uschar *list = *listptr;
 uschar *user_name;
-uschar user_name_buffer[128];
 unsigned long mbox_size;
 FILE *mbox_file;
 client_conn_ctx spamd_cctx = {.sock = -1};
@@ -218,9 +217,7 @@ spamd_address_container * sd;
 result = 0;
 
 /* find the username from the option list */
-if ((user_name = string_nextinlist(&list, &sep,
-				   user_name_buffer,
-				   sizeof(user_name_buffer))) == NULL)
+if (!(user_name = string_nextinlist(&list, &sep, NULL, 0)))
   {
   /* no username given, this means no scanning should be done */
   return FAIL;

commit cafcab0848e35047a93a8920b5105baf55f8e702
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Tue Jun 30 21:16:42 2020 +0100

    Fix message-reception clock usage.  Bug 2615
    
    Broken-by: 6906c131d1 (4.94)
    (cherry picked from commit c9bce82e3064126be34d85280d0a7fbf65b3abec)

diff --git a/src/src/exim.c b/src/src/exim.c
index 6143fe989..dde991065 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -384,14 +384,20 @@ return 0;
 *************************************************/
 
 #ifdef _POSIX_MONOTONIC_CLOCK
-/* Amount CLOCK_MONOTONIC is behind realtime, at startup. */
+# ifdef CLOCK_BOOTTIME
+#  define EXIM_CLOCKTYPE CLOCK_BOOTTIME
+# else
+#  define EXIM_CLOCKTYPE CLOCK_MONOTONIC
+# endif
+
+/* Amount EXIM_CLOCK is behind realtime, at startup. */
 static struct timespec offset_ts;
 
 static void
 exim_clock_init(void)
 {
 struct timeval tv;
-if (clock_gettime(CLOCK_MONOTONIC, &offset_ts) != 0) return;
+if (clock_gettime(EXIM_CLOCKTYPE, &offset_ts) != 0) return;
 (void)gettimeofday(&tv, NULL);
 offset_ts.tv_sec = tv.tv_sec - offset_ts.tv_sec;
 offset_ts.tv_nsec = tv.tv_usec * 1000 - offset_ts.tv_nsec;
@@ -402,6 +408,29 @@ offset_ts.tv_nsec += 1000*1000*1000;
 #endif
 
 
+void
+exim_gettime(struct timeval * tv)
+{
+#ifdef _POSIX_MONOTONIC_CLOCK
+struct timespec now_ts;
+
+if (clock_gettime(EXIM_CLOCKTYPE, &now_ts) == 0)
+  {
+  now_ts.tv_sec += offset_ts.tv_sec;
+  if ((now_ts.tv_nsec += offset_ts.tv_nsec) >= 1000*1000*1000)
+    {
+    now_ts.tv_sec++;
+    now_ts.tv_nsec -= 1000*1000*1000;
+    }
+  tv->tv_sec = now_ts.tv_sec;
+  tv->tv_usec = now_ts.tv_nsec / 1000;
+  }
+else
+#endif
+  (void)gettimeofday(tv, NULL);
+}
+
+
 /* Exim uses a time + a pid to generate a unique identifier in two places: its
 message IDs, and in file names for maildir deliveries. Because some OS now
 re-use pids within the same second, sub-second times are now being used.
@@ -428,28 +457,9 @@ exim_wait_tick(struct timeval * tgt_tv, int resolution)
 struct timeval now_tv;
 long int now_true_usec;
 
-#ifdef _POSIX_MONOTONIC_CLOCK
-struct timespec now_ts;
-
-if (clock_gettime(CLOCK_MONOTONIC, &now_ts) == 0)
-  {
-  now_ts.tv_sec += offset_ts.tv_sec;
-  if ((now_ts.tv_nsec += offset_ts.tv_nsec) >= 1000*1000*1000)
-    {
-    now_ts.tv_sec++;
-    now_ts.tv_nsec -= 1000*1000*1000;
-    }
-  now_tv.tv_sec = now_ts.tv_sec;
-  now_true_usec = (now_ts.tv_nsec / (resolution * 1000)) * resolution;
-  now_tv.tv_usec = now_true_usec;
-  }
-else
-#endif
-  {
-  (void)gettimeofday(&now_tv, NULL);
-  now_true_usec = now_tv.tv_usec;
-  now_tv.tv_usec = (now_true_usec/resolution) * resolution;
-  }
+exim_gettime(&now_tv);
+now_true_usec = now_tv.tv_usec;
+now_tv.tv_usec = (now_true_usec/resolution) * resolution;
 
 while (exim_tvcmp(&now_tv, tgt_tv) <= 0)
   {
@@ -1728,7 +1738,7 @@ make quite sure. */
 
 setlocale(LC_ALL, "C");
 
-/* Get the offset between CLOCK_MONOTONIC and wallclock */
+/* Get the offset between CLOCK_MONOTONIC/CLOCK_BOOTTIME and wallclock */
 
 #ifdef _POSIX_MONOTONIC_CLOCK
 exim_clock_init();
diff --git a/src/src/functions.h b/src/src/functions.h
index 0050cdeeb..8b17531ed 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -234,6 +234,7 @@ extern void    msg_event_raise(const uschar *, const address_item *);
 extern int     exim_chown_failure(int, const uschar*, uid_t, gid_t);
 extern const uschar * exim_errstr(int);
 extern void    exim_exit(int) NORETURN;
+extern void    exim_gettimg(struct timeval *);
 extern void    exim_nullstd(void);
 extern void    exim_setugid(uid_t, gid_t, BOOL, uschar *);
 extern void    exim_underbar_exit(int) NORETURN;
diff --git a/src/src/receive.c b/src/src/receive.c
index 0fbd35f82..0db897e9e 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -1784,7 +1784,7 @@ if (sender_host_address) dmarc_init();	/* initialize libopendmarc */
 ids, and fractions of a second are required. See the comments that precede the
 message id creation below. */
 
-(void)gettimeofday(&message_id_tv, NULL);
+exim_gettime(&message_id_tv);
 
 /* For other uses of the received time we can operate with granularity of one
 second, and for that we use the global variable received_time. This is for

commit 436c0d208b49421d3d126f7c284bc3130b50ff14
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Jul 3 20:35:58 2020 +0100

    typoes

diff --git a/src/src/functions.h b/src/src/functions.h
index 8b17531ed..afe9f1bf4 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -234,7 +234,7 @@ extern void    msg_event_raise(const uschar *, const address_item *);
 extern int     exim_chown_failure(int, const uschar*, uid_t, gid_t);
 extern const uschar * exim_errstr(int);
 extern void    exim_exit(int) NORETURN;
-extern void    exim_gettimg(struct timeval *);
+extern void    exim_gettime(struct timeval *);
 extern void    exim_nullstd(void);
 extern void    exim_setugid(uid_t, gid_t, BOOL, uschar *);
 extern void    exim_underbar_exit(int) NORETURN;

commit de99158d2927863a6826c9f5f2c6fa4547326e75
Author: Guillaume Outters <guillaume-exim@outters.eu>
Date:   Mon Jul 6 22:31:51 2020 +0100

    Fix DKIM signing to always ;-terminate.  Bug 2295
    
    (cherry picked from commit 65fe780259d0009354b5dfc9a4f1b48ad6513db2)

diff --git a/src/src/pdkim/pdkim.c b/src/src/pdkim/pdkim.c
index 7712409b5..3a6ca4e91 100644
--- a/src/src/pdkim/pdkim.c
+++ b/src/src/pdkim/pdkim.c
@@ -1111,14 +1111,14 @@ return string_catn(str, US"\r\n\t", 3);
 
 /*
  * RFC 5322 specifies that header line length SHOULD be no more than 78
- * lets make it so!
  *  pdkim_headcat
  *
- * returns uschar * (not nul-terminated)
+ * Returns gstring (not nul-terminated) appending to one supplied
  *
  * col: this int holds and receives column number (octets since last '\n')
  * str: partial string to append to
- * pad: padding, split line or space after before or after eg: ";"
+ * pad: padding, split line or space after before or after eg: ";".
+ *               Only the initial charater is used.
  * intro: - must join to payload eg "h=", usually the tag name
  * payload: eg base64 data - long data can be split arbitrarily.
  *
@@ -1127,7 +1127,7 @@ return string_catn(str, US"\r\n\t", 3);
  * pairs and inside long values. it also always spaces or breaks after the
  * "pad"
  *
- * no guarantees are made for output given out-of range input. like tag
+ * No guarantees are made for output given out-of range input. like tag
  * names longer than 78, or bogus col. Input is assumed to be free of line breaks.
  */
 
@@ -1135,92 +1135,64 @@ static gstring *
 pdkim_headcat(int * col, gstring * str,
   const uschar * pad, const uschar * intro, const uschar * payload)
 {
-size_t l;
+int len, chomp, padded = 0;
 
-if (pad)
-  {
-  l = Ustrlen(pad);
-  if (*col + l > 78)
-    str = pdkim_hdr_cont(str, col);
-  str = string_catn(str, pad, l);
-  *col += l;
-  }
-
-l = (pad?1:0) + (intro?Ustrlen(intro):0);
+/* If we can fit at least the pad at the end of current line, do it now.
+Otherwise, wrap if there is a pad. */
 
-if (*col + l > 78)
-  { /*can't fit intro - start a new line to make room.*/
-  str = pdkim_hdr_cont(str, col);
-  l = intro?Ustrlen(intro):0;
-  }
-
-l += payload ? Ustrlen(payload):0 ;
-
-while (l>77)
-  { /* this fragment will not fit on a single line */
-  if (pad)
-    {
-    str = string_catn(str, US" ", 1);
-    *col += 1;
-    pad = NULL; /* only want this once */
-    l--;
-    }
-
-  if (intro)
-    {
-    size_t sl = Ustrlen(intro);
-
-    str = string_catn(str, intro, sl);
-    *col += sl;
-    l -= sl;
-    intro = NULL; /* only want this once */
-    }
-
-  if (payload)
+if (pad)
+  if (*col + 1 <= 78)
     {
-    size_t sl = Ustrlen(payload);
-    size_t chomp = *col+sl < 77 ? sl : 78-*col;
-
-    str = string_catn(str, payload, chomp);
-    *col += chomp;
-    payload += chomp;
-    l -= chomp-1;
+    str = string_catn(str, pad, 1);
+    (*col)++;
+    pad = NULL;
+    padded = 1;
     }
+  else
+    str = pdkim_hdr_cont(str, col);
 
-  /* the while precondition tells us it didn't fit. */
-  str = pdkim_hdr_cont(str, col);
-  }
+/* Special case: if the whole addition does not fit at the end of the current
+line, but could fit on a new line, wrap to give it its full, dedicated line.  */
 
-if (*col + l > 78)
+len = (pad ? 2 : padded)
+    + (intro ? Ustrlen(intro) : 0)
+    + (payload ? Ustrlen(payload) : 0);
+if (len <= 77 && *col+len > 78)
   {
   str = pdkim_hdr_cont(str, col);
-  pad = NULL;
+  padded = 0;
   }
 
+/* Either we already dealt with the pad or we know there is room */
+
 if (pad)
   {
+  str = string_catn(str, pad, 1);
   str = string_catn(str, US" ", 1);
-  *col += 1;
-  pad = NULL;
+  *col += 2;
   }
-
-if (intro)
+else if (padded && *col < 78)
   {
-  size_t sl = Ustrlen(intro);
-
-  str = string_catn(str, intro, sl);
-  *col += sl;
-  l -= sl;
-  intro = NULL;
+  str = string_catn(str, US" ", 1);
+  (*col)++;
   }
 
-if (payload)
-  {
-  size_t sl = Ustrlen(payload);
+/* Call recursively with intro as payload: it gets the same, special treatment
+(that is, not split if < 78).  */
 
-  str = string_catn(str, payload, sl);
-  *col += sl;
-  }
+if (intro)
+  str = pdkim_headcat(col, str, NULL, NULL, intro);
+
+if (payload)
+  for (len = Ustrlen(payload); len; len -= chomp)
+    {
+    if (*col >= 78)
+      str = pdkim_hdr_cont(str, col);
+    chomp = *col+len > 78 ? 78 - *col : len;
+    str = string_catn(str, payload, chomp);
+    *col += chomp;
+    payload += chomp;
+    }
 
 return str;
 }

commit 86ba540670517109809d93cd73716d320a6a0923
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Thu Jul 9 15:30:55 2020 +0100

    Fix taint trap in parse_fix_phrase().  Bug 2617
    
    (cherry picked from commit 3c90bbcdc7cf73298156f7bcd5f5e750e7814e72)

diff --git a/src/src/acl.c b/src/src/acl.c
index 62cb68561..105b1b473 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -3199,8 +3199,7 @@ for (; cb; cb = cb->next)
 	      {
 	      const uschar *pp = p + 6;
 	      while (*pp) pp++;
-	      submission_name = string_copy(parse_fix_phrase(p+6, pp-p-6,
-		big_buffer, big_buffer_size));
+	      submission_name = parse_fix_phrase(p+6, pp-p-6);
 	      p = pp;
 	      }
 	    else break;
diff --git a/src/src/exim.c b/src/src/exim.c
index dde991065..ac0ff5523 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -4769,8 +4769,7 @@ if (originator_login == NULL || f.running_in_test_harness)
 /* Ensure that the user name is in a suitable form for use as a "phrase" in an
 RFC822 address.*/
 
-originator_name = string_copy(parse_fix_phrase(originator_name,
-  Ustrlen(originator_name), big_buffer, big_buffer_size));
+originator_name = parse_fix_phrase(originator_name, Ustrlen(originator_name));
 
 /* If a message is created by this call of Exim, the uid/gid of its originator
 are those of the caller. These values are overridden if an existing message is
diff --git a/src/src/expand.c b/src/src/expand.c
index 6ed22c14d..791222324 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -7571,13 +7571,10 @@ while (*s != 0)
       prescribed by the RFC, if there are characters that need to be encoded */
 
       case EOP_RFC2047:
-        {
-        uschar buffer[2048];
         yield = string_cat(yield,
 			    parse_quote_2047(sub, Ustrlen(sub), headers_charset,
-			      buffer, sizeof(buffer), FALSE));
+			      FALSE));
         continue;
-        }
 
       /* RFC 2047 decode */
 
diff --git a/src/src/functions.h b/src/src/functions.h
index afe9f1bf4..f4d1622dc 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -366,9 +366,9 @@ extern int     parse_forward_list(uschar *, int, address_item **, uschar **,
                  const uschar *, uschar *, error_block **);
 extern uschar *parse_find_address_end(uschar *, BOOL);
 extern uschar *parse_find_at(uschar *);
-extern const uschar *parse_fix_phrase(const uschar *, int, uschar *, int);
+extern const uschar *parse_fix_phrase(const uschar *, int);
 extern uschar *parse_message_id(uschar *, uschar **, uschar **);
-extern const uschar *parse_quote_2047(const uschar *, int, uschar *, uschar *, int, BOOL);
+extern const uschar *parse_quote_2047(const uschar *, int, uschar *, BOOL);
 extern uschar *parse_date_time(uschar *str, time_t *t);
 extern int     vaguely_random_number(int);
 #ifndef DISABLE_TLS
diff --git a/src/src/parse.c b/src/src/parse.c
index e3b471f1a..3ea758ac9 100644
--- a/src/src/parse.c
+++ b/src/src/parse.c
@@ -843,8 +843,7 @@ return NULL;
 
 /* This function is used for quoting text in headers according to RFC 2047.
 If the only characters that strictly need quoting are spaces, we return the
-original string, unmodified. If a quoted string is too long for the buffer, it
-is truncated. (This shouldn't happen: this is normally handling short strings.)
+original string, unmodified.
 
 Hmmph. As always, things get perverted for other uses. This function was
 originally for the "phrase" part of addresses. Now it is being used for much
@@ -856,77 +855,57 @@ Arguments:
                  chars
   len          the length of the string
   charset      the name of the character set; NULL => iso-8859-1
-  buffer       the buffer to put the answer in
-  buffer_size  the size of the buffer
   fold         if TRUE, a newline is inserted before the separating space when
                  more than one encoded-word is generated
 
 Returns:       pointer to the original string, if no quoting needed, or
-               pointer to buffer containing the quoted string, or
-               a pointer to "String too long" if the buffer can't even hold
-               the introduction
+               pointer to allocated memory containing the quoted string
 */
 
 const uschar *
-parse_quote_2047(const uschar *string, int len, uschar *charset, uschar *buffer,
-  int buffer_size, BOOL fold)
+parse_quote_2047(const uschar *string, int len, uschar *charset, BOOL fold)
 {
-const uschar *s = string;
-uschar *p, *t;
-int hlen;
+const uschar * s = string;
+int hlen, l;
 BOOL coded = FALSE;
 BOOL first_byte = FALSE;
+gstring * g =
+  string_fmt_append(NULL, "=?%s?Q?", charset ? charset : US"iso-8859-1");
 
-if (!charset) charset = US"iso-8859-1";
+hlen = l = g->ptr;
 
-/* We don't expect this to fail! */
-
-if (!string_format(buffer, buffer_size, "=?%s?Q?", charset))
-  return US"String too long";
-
-hlen = Ustrlen(buffer);
-t = buffer + hlen;
-p = buffer;
-
-for (; len > 0; len--)
+for (s = string; len > 0; s++, len--)
   {
-  int ch = *s++;
-  if (t > buffer + buffer_size - hlen - 8) break;
+  int ch = *s;
 
-  if ((t - p > 67) && !first_byte)
+  if (g->ptr - l > 67 && !first_byte)
     {
-    *t++ = '?';
-    *t++ = '=';
-    if (fold) *t++ = '\n';
-    *t++ = ' ';
-    p = t;
-    Ustrncpy(p, buffer, hlen);
-    t += hlen;
+    g = fold ? string_catn(g, US"?=\n ", 4) : string_catn(g, US"?= ", 3);
+    l = g->ptr;
+    g = string_catn(g, g->s, hlen);
     }
 
-  if (ch < 33 || ch > 126 ||
-      Ustrchr("?=()<>@,;:\\\".[]_", ch) != NULL)
+  if (  ch < 33 || ch > 126
+     || Ustrchr("?=()<>@,;:\\\".[]_", ch) != NULL)
     {
     if (ch == ' ')
       {
-      *t++ = '_';
+      g = string_catn(g, US"_", 1);
       first_byte = FALSE;
       }
     else
       {
-      t += sprintf(CS t, "=%02X", ch);
+      g = string_fmt_append(g, "=%02X", ch);
       coded = TRUE;
       first_byte = !first_byte;
       }
     }
-  else { *t++ = ch; first_byte = FALSE; }
+  else
+    { g = string_catn(g, s, 1); first_byte = FALSE; }
   }
 
-*t++ = '?';
-*t++ = '=';
-*t = 0;
-
-return coded ? buffer : string;
+g = string_catn(g, US"?=", 2);
+return coded ? string_from_gstring(g) : string;
 }
 
 
@@ -969,32 +948,25 @@ August 2000: Additional code added:
   We *could* use this for all cases, getting rid of the messy original code,
   but leave it for now. It would complicate simple cases like "John Q. Smith".
 
-The result is passed back in the buffer; it is usually going to be added to
-some other string. In order to be sure there is going to be no overflow,
-restrict the length of the input to 1/4 of the buffer size - this allows for
-every single character to be quoted or encoded without overflowing, and that
-wouldn't happen because of amalgamation. If the phrase is too long, return a
-fixed string.
+The result is passed back in allocated memory.
 
 Arguments:
   phrase       an RFC822 phrase
   len          the length of the phrase
-  buffer       a buffer to put the result in
-  buffer_size  the size of the buffer
 
 Returns:       the fixed RFC822 phrase
 */
 
 const uschar *
-parse_fix_phrase(const uschar *phrase, int len, uschar *buffer, int buffer_size)
+parse_fix_phrase(const uschar *phrase, int len)
 {
 int ch, i;
 BOOL quoted = FALSE;
 const uschar *s, *end;
+uschar * buffer;
 uschar *t, *yield;
 
 while (len > 0 && isspace(*phrase)) { phrase++; len--; }
-if (len > buffer_size/4) return US"Name too long";
 
 /* See if there are any non-printing characters, and if so, use the RFC 2047
 encoding for the whole thing. */
@@ -1002,11 +974,13 @@ encoding for the whole thing. */
 for (i = 0, s = phrase; i < len; i++, s++)
   if ((*s < 32 && *s != '\t') || *s > 126) break;
 
-if (i < len) return parse_quote_2047(phrase, len, headers_charset, buffer,
-  buffer_size, FALSE);
+if (i < len)
+  return parse_quote_2047(phrase, len, headers_charset, FALSE);
 
 /* No non-printers; use the RFC 822 quoting rules */
 
+buffer = store_get(len*4, is_tainted(phrase));
+
 s = phrase;
 end = s + len;
 yield = t = buffer + 1;
@@ -1173,6 +1147,7 @@ while (s < end)
   }
 
 *t = 0;
+store_release_above(t+1);
 return yield;
 }
 
@@ -2102,7 +2077,6 @@ int main(void)
 {
 int start, end, domain;
 uschar buffer[1024];
-uschar outbuff[1024];
 
 big_buffer = store_malloc(big_buffer_size);
 
@@ -2115,8 +2089,7 @@ while (Ufgets(buffer, sizeof(buffer), stdin) != NULL)
   {
   buffer[Ustrlen(buffer)-1] = 0;
   if (buffer[0] == 0) break;
-  printf("%s\n", CS parse_fix_phrase(buffer, Ustrlen(buffer), outbuff,
-    sizeof(outbuff)));
+  printf("%s\n", CS parse_fix_phrase(buffer, Ustrlen(buffer)));
   }
 
 printf("Testing parse_extract_address without group syntax and without UTF-8\n");
diff --git a/src/src/rewrite.c b/src/src/rewrite.c
index f942bec05..7bff8a273 100644
--- a/src/src/rewrite.c
+++ b/src/src/rewrite.c
@@ -292,16 +292,11 @@ for (rewrite_rule * rule = rewrite_rules;
         uschar *p1 = new + start - 1;
         uschar *p2 = new + end + 1;
         const uschar *pf1, *pf2;
-        uschar buff1[256], buff2[256];
 
         while (p1 > new && p1[-1] == ' ') p1--;
-        pf1 = parse_fix_phrase(new, p1 - new, buff1, sizeof(buff1));
+        pf1 = parse_fix_phrase(new, p1 - new);
         while (*p2 == ' ') p2++;
-        pf2 = parse_fix_phrase(p2, Ustrlen(p2), buff2, sizeof(buff2));
-
-        /* Note that pf1 and pf2 are NOT necessarily buff1 and buff2. For
-        a non-RFC 2047 phrase that does not need to be RFC 2822 quoted, they
-        will be buff1+1 and buff2+1. */
+        pf2 = parse_fix_phrase(p2, Ustrlen(p2));
 
         start = Ustrlen(pf1) + start + new - p1;
         end = start + Ustrlen(newparsed);
diff --git a/src/src/sieve.c b/src/src/sieve.c
index 18aa3e609..2038f336b 100644
--- a/src/src/sieve.c
+++ b/src/src/sieve.c
@@ -3087,11 +3087,8 @@ while (*filter->pc)
             if ((pid = child_open_exim2(&fd, envelope_from, envelope_from,
 			US"sieve-notify")) >= 1)
               {
-              FILE *f;
-              uschar *buffer;
-              int buffer_capacity;
+              FILE * f = fdopen(fd, "wb");
 
-              f = fdopen(fd, "wb");
               fprintf(f,"From: %s\n", from.length == -1
 		? expand_string(US"$local_part_prefix$local_part$local_part_suffix@$domain")
 		: from.character);
@@ -3104,12 +3101,9 @@ while (*filter->pc)
                 message.character=US"Notification";
                 message.length=Ustrlen(message.character);
                 }
-              /* Allocation is larger than necessary, but enough even for split MIME words */
-              buffer_capacity = 32 + 4*message.length;
-              buffer=store_get(buffer_capacity, TRUE);
               if (message.length != -1)
 		fprintf(f, "Subject: %s\n", parse_quote_2047(message.character,
-		  message.length, US"utf-8", buffer, buffer_capacity, TRUE));
+		  message.length, US"utf-8", TRUE));
               fprintf(f,"\n");
               if (body.length>0) fprintf(f,"%s\n",body.character);
               fflush(f);
@@ -3263,8 +3257,6 @@ while (*filter->pc)
     if (exec)
       {
       address_item *addr;
-      uschar *buffer;
-      int buffer_capacity;
       md5 base;
       uschar digest[16];
       uschar hexdigest[33];
@@ -3342,11 +3334,8 @@ while (*filter->pc)
             addr->reply->from = expand_string(US"$local_part@$domain");
           else
             addr->reply->from = from.character;
-          /* Allocation is larger than necessary, but enough even for split MIME words */
-          buffer_capacity=32+4*subject.length;
-          buffer = store_get(buffer_capacity, is_tainted(subject.character));
 	  /* deconst cast safe as we pass in a non-const item */
-          addr->reply->subject = US parse_quote_2047(subject.character, subject.length, US"utf-8", buffer, buffer_capacity, TRUE);
+          addr->reply->subject = US parse_quote_2047(subject.character, subject.length, US"utf-8", TRUE);
           addr->reply->oncelog = string_from_gstring(once);
           addr->reply->once_repeat=days*86400;
 

commit 9681a2140b43cfc028e61b4e7ffb13539cecffe5
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Mon Jul 13 13:46:14 2020 +0100

    Taint: fix ACL "spam" condition, to permit tainted name arguments
    
    Follow-on from: 62b2ccce05
    
    (cherry picked from commit 532800c8bf0e4bc2c27739477e70e0d7eef7df21)

diff --git a/src/src/spam.c b/src/src/spam.c
index 63ced4f65..340f8b92f 100644
--- a/src/src/spam.c
+++ b/src/src/spam.c
@@ -18,7 +18,7 @@ uschar spam_score_int_buffer[16];
 uschar spam_bar_buffer[128];
 uschar spam_action_buffer[32];
 uschar spam_report_buffer[32600];
-uschar prev_user_name[128] = "";
+uschar * prev_user_name = NULL;
 int spam_ok = 0;
 int spam_rc = 0;
 uschar *prev_spamd_address_work = NULL;
@@ -393,13 +393,12 @@ if (sd->is_rspamd)
   }
 else
   {				/* spamassassin variant */
-  (void)string_format(spamd_buffer,
-	  sizeof(spamd_buffer),
-	  "REPORT SPAMC/1.2\r\nUser: %s\r\nContent-length: %ld\r\n\r\n",
-	  user_name,
-	  mbox_size);
+  int n;
+  uschar * s = string_sprintf(
+	  "REPORT SPAMC/1.2\r\nUser: %s\r\nContent-length: %ld\r\n\r\n%n",
+	  user_name, mbox_size, &n);
   /* send our request */
-  wrote = send(spamd_cctx.sock, spamd_buffer, Ustrlen(spamd_buffer), 0);
+  wrote = send(spamd_cctx.sock, s, n, 0);
   }
 
 if (wrote == -1)
@@ -630,7 +629,7 @@ if (spamd_address_work != spamd_address)
   prev_spamd_address_work = string_copy(spamd_address_work);
 
 /* remember user name and "been here" for it */
-Ustrcpy(prev_user_name, user_name);
+prev_user_name = user_name;
 spam_ok = 1;
 
 return override

commit 81cc39a7f5c17099f93b5c611bde5f58daaab71b
Author: Heiko Schlittermann (HS12-RIPE) <hs@schlittermann.de>
Date:   Thu Jul 16 23:45:55 2020 +0200

    Fix debug_print_socket()
    
    debug_print_socket() crashed on AF_UNIX sockets

diff --git a/src/src/debug.c b/src/src/debug.c
index 3a7d6a6f5..acc723a29 100644
--- a/src/src/debug.c
+++ b/src/src/debug.c
@@ -328,20 +328,21 @@ if (fstat(fd, &s) == 0 && (s.st_mode & S_IFMT) == S_IFSOCK)
   gstring * g = NULL;
   int val;
   socklen_t vlen = sizeof(val);
-  struct sockaddr a;
+  struct sockaddr_storage a;
   socklen_t alen = sizeof(a);
   struct sockaddr_in * sinp = (struct sockaddr_in *)&a;
   struct sockaddr_in6 * sin6p = (struct sockaddr_in6 *)&a;
-  struct sockaddr_un * sa_unp ; (struct sockaddr_un *)&a;
+  struct sockaddr_un * sunp = (struct sockaddr_un *)&a;
 
-  if (getsockname(fd, &a, &alen) == 0)
-    switch (sinp->sin_family)
+  if (getsockname(fd, (struct sockaddr*)&a, &alen) == 0)
+    switch (a.ss_family)
       {
       case AF_INET:
 	g = string_cat(g, US" domain AF_INET");
 	g = string_fmt_append(g, " lcl [%s]:%u",
 	  inet_ntoa(sinp->sin_addr), ntohs(sinp->sin_port));
-	if (getpeername(fd, &a, &alen) == 0)
+	alen = sizeof(*sinp);
+	if (getpeername(fd, sinp, &alen) == 0)
 	  g = string_fmt_append(g, " rmt [%s]:%u",
 	    inet_ntoa(sinp->sin_addr), ntohs(sinp->sin_port));
 	break;
@@ -352,22 +353,25 @@ if (fstat(fd, &s) == 0 && (s.st_mode & S_IFMT) == S_IFSOCK)
 	g = string_fmt_append(g, " lcl [%s]:%u",
 	  inet_ntop(AF_INET6, &sin6p->sin6_addr, CS buf, sizeof(buf)),
 	  ntohs(sin6p->sin6_port));
-	if (getpeername(fd, &a, &alen) == 0)
+	alen = sizeof(*sin6p);
+	if (getpeername(fd, sin6p, &alen) == 0)
 	  g = string_fmt_append(g, " rmt [%s]:%u",
 	    inet_ntop(AF_INET6, &sin6p->sin6_addr, CS buf, sizeof(buf)),
 	    ntohs(sin6p->sin6_port));
 	break;
 	}
       case AF_UNIX:
-	g = string_cat(g, US" domain AF_UNIX");
-	g = string_fmt_append(g, " lcl %s%s",
-	  sa_unp->sun_path[0] ? US"" : US"@",
-	  sa_unp->sun_path[0] ? sa_unp->sun_path : sa_unp->sun_path+1);
-	if (getpeername(fd, &a, &alen) == 0)
-	  g = string_fmt_append(g, " rmt %s%s",
-	    sa_unp->sun_path[0] ? US"" : US"@",
-	    sa_unp->sun_path[0] ? sa_unp->sun_path : sa_unp->sun_path+1);
-	break;
+        g = string_cat(g, US"domain AF_UNIX");
+        if (alen > sizeof(sa_family_t)) /* not unix(7) "unnamed socket" */
+          g = string_fmt_append(g, " lcl %s%s",
+            sunp->sun_path[0] ? US"" : US"@",
+            sunp->sun_path[0] ? sunp->sun_path : sunp->sun_path+1);
+        alen = sizeof(*sunp);
+        if (getpeername(fd, sunp, &alen) == 0)
+          g = string_fmt_append(g, " rmt %s%s",
+            sunp->sun_path[0] ? US"" : US"@",
+            sunp->sun_path[0] ? sunp->sun_path : sunp->sun_path+1);
+        break;
       default:
 	g = string_fmt_append(g, " domain %u", sinp->sin_family);
 	break;

commit 73b748711caf8a4b18dd1c0d7c662b5d57798dfe
Author: Heiko Schlittermann (HS12-RIPE) <hs@schlittermann.de>
Date:   Thu Jul 16 23:53:27 2020 +0200

    debug_print_socket(): output formatting

diff --git a/src/src/debug.c b/src/src/debug.c
index acc723a29..6d6132e39 100644
--- a/src/src/debug.c
+++ b/src/src/debug.c
@@ -338,7 +338,7 @@ if (fstat(fd, &s) == 0 && (s.st_mode & S_IFMT) == S_IFSOCK)
     switch (a.ss_family)
       {
       case AF_INET:
-	g = string_cat(g, US" domain AF_INET");
+	g = string_cat(g, US"domain AF_INET");
 	g = string_fmt_append(g, " lcl [%s]:%u",
 	  inet_ntoa(sinp->sin_addr), ntohs(sinp->sin_port));
 	alen = sizeof(*sinp);
@@ -349,7 +349,7 @@ if (fstat(fd, &s) == 0 && (s.st_mode & S_IFMT) == S_IFSOCK)
       case AF_INET6:
 	{
 	uschar buf[46];
-	g = string_cat(g, US" domain AF_INET6");
+	g = string_cat(g, US"domain AF_INET6");
 	g = string_fmt_append(g, " lcl [%s]:%u",
 	  inet_ntop(AF_INET6, &sin6p->sin6_addr, CS buf, sizeof(buf)),
 	  ntohs(sin6p->sin6_port));
@@ -373,7 +373,7 @@ if (fstat(fd, &s) == 0 && (s.st_mode & S_IFMT) == S_IFSOCK)
             sunp->sun_path[0] ? sunp->sun_path : sunp->sun_path+1);
         break;
       default:
-	g = string_fmt_append(g, " domain %u", sinp->sin_family);
+	g = string_fmt_append(g, "domain %u", sinp->sin_family);
 	break;
       }
   if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &val, &vlen) == 0)
@@ -388,7 +388,7 @@ if (fstat(fd, &s) == 0 && (s.st_mode & S_IFMT) == S_IFSOCK)
     {
     struct protoent * p = getprotobynumber(val);
     g = p
-      ? string_fmt_append(g, " proto %s\n", p->p_name)
+      ? string_fmt_append(g, " proto %s", p->p_name)
       : string_fmt_append(g, " proto %d", val);
     }
 #endif

commit d08a4ba2c5216195c107b123842da17bc1b0559f
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sun Aug 9 01:38:00 2020 +0100

    Fix spelling of local_part_data in docs and debug output
    
    (cherry picked from commit ccec2d82e2fda6d764f6cd1a9dd21c4f6285b614)

diff --git a/src/src/routers/rf_queue_add.c b/src/src/routers/rf_queue_add.c
index 938eee30a..4dab60dfe 100644
--- a/src/src/routers/rf_queue_add.c
+++ b/src/src/routers/rf_queue_add.c
@@ -98,7 +98,7 @@ DEBUG(D_route)
     "  errors_to=%s\n",
     addr->transport ? addr->transport->name : US"<unset>",
     addr->local_part, addr->domain, addr->prop.errors_address);
-  debug_printf("  domain_data=%s localpart_data=%s\n", addr->prop.domain_data,
+  debug_printf("  domain_data=%s local_part_data=%s\n", addr->prop.domain_data,
     addr->prop.localpart_data);
   }
 

commit 18a352ae8a799bb7be3a03b14bbf3ce9e0ba4945
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Aug 14 13:09:53 2020 +0100

    Fix ${readsocket } eol-replacement.  Bug 2630
    
    (cherry picked from commit 7f83b348ccf4cd815e9758ab9ca1012e66324e9d)

diff --git a/src/src/expand.c b/src/src/expand.c
index 791222324..bb9fd79ef 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -4920,7 +4920,7 @@ while (*s != 0)
           {
           expand_string_message =
             string_sprintf("lookup of \"%s\" gave DEFER: %s",
-              string_printing2(key, FALSE), search_error_message);
+              string_printing2(key, SP_TAB), search_error_message);
           goto EXPAND_FAILED;
           }
         if (expand_setup > 0) expand_nmax = expand_setup;
@@ -5334,11 +5334,14 @@ while (*s != 0)
 	  while ((item = string_nextinlist(&list, &sep, NULL, 0)))
 	    g = string_append_listele(g, ',', item);
 
-	  /* possibly plus an EOL string */
+	  /* possibly plus an EOL string.  Process with escapes, to protect
+	  from list-processing.  The only current user of eol= in search
+	  options is the readsock expansion. */
+
 	  if (sub_arg[3] && *sub_arg[3])
 	    g = string_append_listele(g, ',',
-		  string_sprintf("eol=%s", sub_arg[3]));
-
+		  string_sprintf("eol=%s",
+		    string_printing2(sub_arg[3], SP_TAB|SP_SPACE)));
 	  }
 
 	/* Gat a (possibly cached) handle for the connection */
diff --git a/src/src/functions.h b/src/src/functions.h
index f4d1622dc..51bb17a09 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -529,7 +529,7 @@ extern int     string_is_ip_address(const uschar *, int *);
 #ifdef SUPPORT_I18N
 extern BOOL    string_is_utf8(const uschar *);
 #endif
-extern const uschar *string_printing2(const uschar *, BOOL);
+extern const uschar *string_printing2(const uschar *, int);
 extern uschar *string_split_message(uschar *);
 extern uschar *string_unprinting(uschar *);
 #ifdef SUPPORT_I18N
diff --git a/src/src/lookups/readsock.c b/src/src/lookups/readsock.c
index c2088b7a5..cfc9b4ad8 100644
--- a/src/src/lookups/readsock.c
+++ b/src/src/lookups/readsock.c
@@ -186,7 +186,9 @@ FILE * fp;
 gstring * yield;
 int ret = DEFER;
 
-DEBUG(D_lookup) debug_printf_indent("readsock: file=\"%s\" key=\"%s\" len=%d opts=\"%s\"\n", filename, keystring, length, opts);
+DEBUG(D_lookup)
+  debug_printf_indent("readsock: file=\"%s\" key=\"%s\" len=%d opts=\"%s\"\n",
+    filename, keystring, length, opts);
 
 /* Parse options */
 
@@ -200,7 +202,7 @@ if (opts) for (uschar * s; s = string_nextinlist(&opts, &sep, NULL, 0); )
     lf.do_tls = TRUE;
 #endif
   else if (Ustrncmp(s, "eol=", 4) == 0)
-    eol = s + 4;
+    eol = string_unprinting(s + 4);
   else if (Ustrcmp(s, "cache=yes") == 0)
     lf.cache = TRUE;
   else if (Ustrcmp(s, "send=no") == 0)
diff --git a/src/src/macros.h b/src/src/macros.h
index a507bbf83..2378773cb 100644
--- a/src/src/macros.h
+++ b/src/src/macros.h
@@ -41,9 +41,11 @@ manipulate them. */
 
 
 /* For almost all calls to convert things to printing characters, we want to
-allow tabs. A macro just makes life a bit easier. */
+allow tabs & spaces. A macro just makes life a bit easier. */
 
-#define string_printing(s) string_printing2((s), TRUE)
+#define string_printing(s) string_printing2((s), 0)
+#define SP_TAB		BIT(0)
+#define SP_SPACE	BIT(1)
 
 
 /* We need a special return code for "no recipients and failed to send an error
diff --git a/src/src/readconf.c b/src/src/readconf.c
index 0d0769c88..948fa2403 100644
--- a/src/src/readconf.c
+++ b/src/src/readconf.c
@@ -1546,7 +1546,7 @@ if (flags & opt_fn_print)
   {
   if (flags & opt_fn_print_label) printf("%s = ", name);
   printf("%s\n", smtp_receive_timeout_s
-    ? string_printing2(smtp_receive_timeout_s, FALSE)
+    ? string_printing2(smtp_receive_timeout_s, SP_TAB)
     : readconf_printtime(smtp_receive_timeout));
   }
 else if (*str == '$')
@@ -2463,7 +2463,7 @@ switch(ol->type & opt_mask)
   case opt_rewrite:        /* Show the text value */
     s = *(USS value);
     if (!no_labels) printf("%s = ", name);
-    printf("%s\n", s ? string_printing2(s, FALSE) : US"");
+    printf("%s\n", s ? string_printing2(s, SP_TAB) : US"");
     break;
 
   case opt_int:
diff --git a/src/src/string.c b/src/src/string.c
index 5acee1b00..f91a6a428 100644
--- a/src/src/string.c
+++ b/src/src/string.c
@@ -281,17 +281,17 @@ return ch;
 /* This function is called for critical strings. It checks for any
 non-printing characters, and if any are found, it makes a new copy
 of the string with suitable escape sequences. It is most often called by the
-macro string_printing(), which sets allow_tab TRUE.
+macro string_printing(), which sets flags to 0.
 
 Arguments:
   s             the input string
-  allow_tab     TRUE to allow tab as a printing character
+  flags		Bit 0: convert tabs.  Bit 1: convert spaces.
 
 Returns:        string with non-printers encoded as printing sequences
 */
 
 const uschar *
-string_printing2(const uschar *s, BOOL allow_tab)
+string_printing2(const uschar *s, int flags)
 {
 int nonprintcount = 0;
 int length = 0;
@@ -301,7 +301,10 @@ uschar *ss, *tt;
 while (*t != 0)
   {
   int c = *t++;
-  if (!mac_isprint(c) || (!allow_tab && c == '\t')) nonprintcount++;
+  if (  !mac_isprint(c)
+     || flags & SP_TAB && c == '\t'
+     || flags & SP_SPACE && c == ' '
+     ) nonprintcount++;
   length++;
   }
 
@@ -310,17 +313,19 @@ if (nonprintcount == 0) return s;
 /* Get a new block of store guaranteed big enough to hold the
 expanded string. */
 
-ss = store_get(length + nonprintcount * 3 + 1, is_tainted(s));
+tt = ss = store_get(length + nonprintcount * 3 + 1, is_tainted(s));
 
 /* Copy everything, escaping non printers. */
 
-t = s;
-tt = ss;
-
-while (*t != 0)
+for (t = s; *t; )
   {
   int c = *t;
-  if (mac_isprint(c) && (allow_tab || c != '\t')) *tt++ = *t++; else
+  if (  mac_isprint(c)
+     && (!(flags & SP_TAB) || c != '\t')
+     && (!(flags & SP_SPACE) || c != ' ')
+     )
+    *tt++ = *t++;
+  else
     {
     *tt++ = '\\';
     switch (*t)
@@ -947,7 +952,10 @@ else
     s = ss;
     if (!*s || *++s != sep || sep_is_special) break;
     }
-  while (g->ptr > 0 && isspace(g->s[g->ptr-1])) g->ptr--;
+  /* while (g->ptr > 0 && isspace(g->s[g->ptr-1])) g->ptr--; */
+  while (  g->ptr > 0 && isspace(g->s[g->ptr-1])
+	&& (g->ptr == 1 || g->s[g->ptr-2] != '\\') )
+    g->ptr--;
   buffer = string_from_gstring(g);
   gstring_release_unused(g);
   }

commit d2671b04d025dee3b8311d2d83e0a0342c670f52
Author: Gavan <gavan@coolfactor.org>
Date:   Fri Aug 21 15:46:01 2020 +0100

    Taint: fix off-by-one in is_tainted().  Bug 2634
    
    (cherry picked from commit e0ae68c8ee6788508da4989ee0d6fcbaf40c7b97)

diff --git a/src/src/store.c b/src/src/store.c
index c460ba383..7d08c9804 100644
--- a/src/src/store.c
+++ b/src/src/store.c
@@ -188,14 +188,14 @@ 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;
+    if (US p >= bc && US p < bc + b->length) return TRUE;
     }
 
 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;
-    if (US p >= bc && US p <= bc + b->length) return TRUE;
+    if (US p >= bc && US p < bc + b->length) return TRUE;
     }
 return FALSE;
 }

commit 4970c58b394eb0778542c97e3f86b565f9e3daa7
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Mon Aug 24 20:14:34 2020 +0100

    Build: ifdef guard for EXPERIMENTAL_QUEUEFILE
    
    (cherry picked from commit 1f5d0a9551205febf6729c7ee36c27626a76b4a4)

diff --git a/src/src/transports/queuefile.c b/src/src/transports/queuefile.c
index 21ed3527f..97218548b 100644
--- a/src/src/transports/queuefile.c
+++ b/src/src/transports/queuefile.c
@@ -8,7 +8,10 @@
 /* See the file NOTICE for conditions of use and distribution. */
 
 
+
 #include "../exim.h"
+
+#ifdef EXPERIMENTAL_QUEUEFILE	/* whole file */
 #include "queuefile.h"
 
 /* Options specific to the appendfile transport. They must be in alphabetic
@@ -276,3 +279,4 @@ return FALSE;
 }
 
 #endif /*!MACRO_PREDEF*/
+#endif /*EXPERIMENTAL_QUEUEFILE*/

commit d8e99d6047e709b35eabb1395c2046100d1a1dda
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Wed Aug 19 21:09:04 2020 +0100

    DANE: force SNI to use $domain.  Bug 2265
    
    Note: this is not a complete fix for the issue
    (cherry picked from commit 7044dd8fd62e215572ecf5a2c7f1bb9581cf6628)

diff --git a/src/src/receive.c b/src/src/receive.c
index 0db897e9e..ec90e93cd 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -4004,7 +4004,7 @@ if (LOGGING(tls_certificate_verified) && tls_in.cipher)
 if (LOGGING(tls_peerdn) && tls_in.peerdn)
   g = string_append(g, 3, US" DN=\"", string_printing(tls_in.peerdn), US"\"");
 if (LOGGING(tls_sni) && tls_in.sni)
-  g = string_append(g, 3, US" SNI=\"", string_printing(tls_in.sni), US"\"");
+  g = string_append(g, 2, US" SNI=", string_printing2(tls_in.sni, SP_TAB|SP_SPACE));
 #endif
 
 if (sender_host_authenticated)
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index 526164c46..a13f0ed63 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -1811,7 +1811,7 @@ if (LOGGING(tls_certificate_verified) && tls_in.cipher)
 if (LOGGING(tls_peerdn) && tls_in.peerdn)
   g = string_append(g, 3, US" DN=\"", string_printing(tls_in.peerdn), US"\"");
 if (LOGGING(tls_sni) && tls_in.sni)
-  g = string_append(g, 3, US" SNI=\"", string_printing(tls_in.sni), US"\"");
+  g = string_append(g, 2, US" SNI=", string_printing2(tls_in.sni, SP_TAB|SP_SPACE));
 return g;
 }
 #endif
diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c
index 875c82efa..a34633390 100644
--- a/src/src/tls-gnu.c
+++ b/src/src/tls-gnu.c
@@ -2863,7 +2863,7 @@ DEBUG(D_tls) debug_printf("initialising GnuTLS as a client on fd %d\n", cctx->so
 /* If dane is flagged, have either request or require dane for this host, and
 a TLSA record found.  Therefore, dane verify required.  Which implies cert must
 be requested and supplied, dane verify must pass, and cert verify irrelevant
-(incl.  hostnames), and (caller handled) require_tls */
+(incl.  hostnames), and (caller handled) require_tls and sni=$domain */
 
 if (conn_args->dane && ob->dane_require_tls_ciphers)
   {
diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c
index a62322928..054b23d0c 100644
--- a/src/src/tls-openssl.c
+++ b/src/src/tls-openssl.c
@@ -3197,6 +3197,7 @@ tlsp->tlsa_usage = 0;
 #ifndef DISABLE_OCSP
   {
 # ifdef SUPPORT_DANE
+  /*XXX this should be moved to caller, to be common across gnutls/openssl */
   if (  conn_args->dane
      && ob->hosts_request_ocsp[0] == '*'
      && ob->hosts_request_ocsp[1] == '\0'
diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index 8492a7f25..28dd8ff24 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -2020,6 +2020,7 @@ if (!continue_hostname)
 	  {
 	  case OK:		sx->conn_args.dane = TRUE;
 				ob->tls_tempfail_tryclear = FALSE;
+				ob->tls_sni = sx->addrlist->domain;
 				break;
 	  case FAIL_FORCED:	break;
 	  default:		set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,

commit a505cf777f90755bce69ab53a899b284a304127b
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sun Aug 23 15:32:48 2020 +0100

    DANE: Fix 2-rcpt message, diff domins case.  Bug 2265
    
    (cherry picked from commit 99350dede64ad634300ddf15d0d97a81fd75d330)

diff --git a/src/src/debug.c b/src/src/debug.c
index 6d6132e39..819e83331 100644
--- a/src/src/debug.c
+++ b/src/src/debug.c
@@ -30,7 +30,16 @@ const uschar * rc_names[] = {		/* Mostly for debug output */
   [UNEXPECTED] =	US"UNEXPECTED",
   [CANCELLED] =		US"CANCELLED",
   [FAIL_SEND] =		US"FAIL_SEND",
-  [FAIL_DROP] =		US"FAIL_DROP"
+  [FAIL_DROP] =		US"FAIL_DROP",
+  [DANE] =		US"DANE",
+};
+
+const uschar * dns_rc_names[] = {
+  [DNS_SUCCEED] =	US"DNS_SUCCEED",
+  [DNS_NOMATCH] =	US"DNS_NOMATCH",
+  [DNS_NODATA] =	US"DNS_NODATA",
+  [DNS_AGAIN] =		US"DNS_AGAIN",
+  [DNS_FAIL] =		US"DNS_FAIL",
 };
 
 
diff --git a/src/src/deliver.c b/src/src/deliver.c
index 40db50084..f5e28941f 100644
--- a/src/src/deliver.c
+++ b/src/src/deliver.c
@@ -460,6 +460,9 @@ TRUE if the lists refer to the same hosts in the same order, except that
 This enables Exim to use a single SMTP transaction for sending to two entirely
 different domains that happen to end up pointing at the same hosts.
 
+We do not try to batch up different A-record host names that refer to the
+same IP.
+
 Arguments:
   one       points to the first host list
   two       points to the second host list
diff --git a/src/src/macros.h b/src/src/macros.h
index 2378773cb..6fd5db94c 100644
--- a/src/src/macros.h
+++ b/src/src/macros.h
@@ -304,6 +304,7 @@ Use rc_names[] for debug strings. */
 #define CANCELLED     13    /* Authentication cancelled */
 #define FAIL_SEND     14    /* send() failed in authenticator */
 #define FAIL_DROP     15    /* Fail and drop connection (used in ACL) */
+#define DANE	      16    /* Deferred for domain mismatch (used in transport) */
 
 /* Returns from the deliver_message() function */
 
diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index 28dd8ff24..6ca4552a6 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -2019,11 +2019,12 @@ if (!continue_hostname)
 	switch (rc = tlsa_lookup(sx->conn_args.host, &sx->conn_args.tlsa_dnsa, sx->dane_required))
 	  {
 	  case OK:		sx->conn_args.dane = TRUE;
-				ob->tls_tempfail_tryclear = FALSE;
-				ob->tls_sni = sx->addrlist->domain;
+				ob->tls_tempfail_tryclear = FALSE;	/* force TLS */
+				ob->tls_sni = sx->first_addr->domain;	/* force SNI */
 				break;
 	  case FAIL_FORCED:	break;
-	  default:		set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
+	  default:
+	  set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
 				  string_sprintf("DANE error: tlsa lookup %s",
 				    rc_to_string(rc)),
 				  rc, FALSE, &sx->delivery_start);
@@ -3430,6 +3431,7 @@ 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 */
+BOOL dane_held;
 
 suppress_tls = suppress_tls;  /* stop compiler warning when no TLS support */
 *message_defer = FALSE;
@@ -3446,13 +3448,36 @@ sx->conn_args.tblock = tblock;
 gettimeofday(&sx->delivery_start, NULL);
 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 */
+DANE_DOMAINS:
+dane_held = FALSE;
+
+/* 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)
   {
   timesince(&addrlist->delivery_time, &sx->delivery_start);
-  return rc;
+  yield = rc;
+  goto TIDYUP;
+  }
+
+/*XXX*/
+/* If the connection used DANE, ignore for now any addresses with incompatible
+domains.  The SNI has to be the domain.  Arrange a whole new TCP conn later,
+just in case only TLS isn't enough. */
+
+if (sx->conn_args.dane)
+  {
+  const uschar * dane_domain = sx->first_addr->domain;
+
+  for (address_item * a = sx->first_addr->next; a; a = a->next)
+    if (  a->transport_return == PENDING_DEFER
+       && Ustrcmp(dane_domain, a->domain) != 0)
+      {
+      DEBUG(D_transport) debug_printf("DANE: holding %s for later\n", a->domain);
+      dane_held = TRUE;
+      a->transport_return = DANE;
+      }
   }
 
 /* If there is a filter command specified for this transport, we can now
@@ -4203,7 +4228,7 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
 
 
       if (sx->first_addr != NULL)	/* More addresses still to be sent */
-        {				/*   in this run of the transport */
+        {				/*   on this connection            */
         continue_sequence++;		/* Causes * in logging */
 	pipelining_active = sx->pipelining_used;    /* was cleared at DATA */
         goto SEND_MESSAGE;
@@ -4235,7 +4260,7 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
 				      '2', ob->command_timeout);
 
 	  if (sx->ok && f.continue_more)
-	    return yield;		/* More addresses for another run */
+	    goto TIDYUP;		/* More addresses for another run */
 	  }
 	else
 	  {
@@ -4255,7 +4280,7 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
       else
 #endif
 	if (f.continue_more)
-	  return yield;			/* More addresses for another run */
+	  goto TIDYUP;			/* More addresses for another run */
 
       /* If the socket is successfully passed, we mustn't send QUIT (or
       indeed anything!) from here. */
@@ -4295,7 +4320,7 @@ propagate it from the initial
 	    sx->cctx.sock = -1;
 	    continue_transport = NULL;
 	    continue_hostname = NULL;
-	    return yield;
+	    goto TIDYUP;
 	    }
 	  log_write(0, LOG_PANIC_DIE, "fork failed");
 	  }
@@ -4370,9 +4395,35 @@ if (sx->send_quit)
 (void) event_raise(tblock->event_action, US"tcp:close", NULL);
 #endif
 
+/*XXX*/
+if (dane_held)
+  {
+  sx->first_addr = NULL;
+  for (address_item * a = sx->addrlist->next; a; a = a->next)
+    if (a->transport_return == DANE)
+      {
+      a->transport_return = PENDING_DEFER;
+      if (!sx->first_addr)
+	{
+	/* Remember the new start-point in the addrlist, for smtp_setup_conn()
+	to get the domain string for SNI */
+
+	sx->first_addr = a;
+	DEBUG(D_transport) debug_printf("DANE: go-around for %s\n", a->domain);
+	}
+      }
+  goto DANE_DOMAINS;
+  }
+
 continue_transport = NULL;
 continue_hostname = NULL;
 return yield;
+
+TIDYUP:
+if (dane_held) for (address_item * a = sx->addrlist->next; a; a = a->next)
+  if (a->transport_return == DANE)
+    a->transport_return = PENDING_DEFER;
+return yield;
 }
 
 
diff --git a/src/src/verify.c b/src/src/verify.c
index 5f4181de9..43343a646 100644
--- a/src/src/verify.c
+++ b/src/src/verify.c
@@ -674,7 +674,7 @@ coding means skipping this whole loop and doing the append separately.  */
     if (!sx) sx = store_get(sizeof(*sx), TRUE);	/* tainted buffers */
     memset(sx, 0, sizeof(*sx));
 
-    sx->addrlist = addr;
+    sx->addrlist = sx->first_addr = addr;
     sx->conn_args.host = host;
     sx->conn_args.host_af = host_af,
     sx->port = port;

commit 7dad62cced33eebd10d13fd1f6cd59696175fa44
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sun Aug 23 17:27:30 2020 +0100

    Fix non-DANE build
    
    (cherry picked from commit 79b19a30d9fc64a7b7f70928cdefe4f51064280b)

diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index 6ca4552a6..d63379e37 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -2023,8 +2023,7 @@ if (!continue_hostname)
 				ob->tls_sni = sx->first_addr->domain;	/* force SNI */
 				break;
 	  case FAIL_FORCED:	break;
-	  default:
-	  set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
+	  default:		set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
 				  string_sprintf("DANE error: tlsa lookup %s",
 				    rc_to_string(rc)),
 				  rc, FALSE, &sx->delivery_start);
@@ -3431,7 +3430,9 @@ 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 */
+#if !defined(DISABLE_TLS) && defined(SUPPORT_DANE)
 BOOL dane_held;
+#endif
 
 suppress_tls = suppress_tls;  /* stop compiler warning when no TLS support */
 *message_defer = FALSE;
@@ -3448,8 +3449,10 @@ sx->conn_args.tblock = tblock;
 gettimeofday(&sx->delivery_start, NULL);
 sx->sync_addr = sx->first_addr = addrlist;
 
+#if !defined(DISABLE_TLS) && defined(SUPPORT_DANE)
 DANE_DOMAINS:
 dane_held = FALSE;
+#endif
 
 /* Get the channel set up ready for a message, MAIL FROM being the next
 SMTP command to send. */
@@ -3461,7 +3464,7 @@ if ((rc = smtp_setup_conn(sx, suppress_tls)) != OK)
   goto TIDYUP;
   }
 
-/*XXX*/
+#if !defined(DISABLE_TLS) && defined(SUPPORT_DANE)
 /* If the connection used DANE, ignore for now any addresses with incompatible
 domains.  The SNI has to be the domain.  Arrange a whole new TCP conn later,
 just in case only TLS isn't enough. */
@@ -3479,6 +3482,7 @@ if (sx->conn_args.dane)
       a->transport_return = DANE;
       }
   }
+#endif
 
 /* If there is a filter command specified for this transport, we can now
 set it up. This cannot be done until the identity of the host is known. */
@@ -4395,7 +4399,7 @@ if (sx->send_quit)
 (void) event_raise(tblock->event_action, US"tcp:close", NULL);
 #endif
 
-/*XXX*/
+#if !defined(DISABLE_TLS) && defined(SUPPORT_DANE)
 if (dane_held)
   {
   sx->first_addr = NULL;
@@ -4414,15 +4418,18 @@ if (dane_held)
       }
   goto DANE_DOMAINS;
   }
+#endif
 
 continue_transport = NULL;
 continue_hostname = NULL;
 return yield;
 
 TIDYUP:
+#if !defined(DISABLE_TLS) && defined(SUPPORT_DANE)
 if (dane_held) for (address_item * a = sx->addrlist->next; a; a = a->next)
   if (a->transport_return == DANE)
     a->transport_return = PENDING_DEFER;
+#endif
 return yield;
 }
 

commit 569be4bc51fd4806edcf6b3abcf550dbbba90df5
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Wed Aug 26 23:43:54 2020 +0100

    DANE: Fix 2 messages from queue case
    
    (cherry picked from commit b6054898ace169a0e5143117397a4f666a5e7283)

diff --git a/src/src/deliver.c b/src/src/deliver.c
index f5e28941f..8f21c607e 100644
--- a/src/src/deliver.c
+++ b/src/src/deliver.c
@@ -1195,7 +1195,7 @@ else
   if (addr->host_used)
     {
     g = d_hostlog(g, addr);
-    if (continue_sequence > 1)
+    if (continue_sequence > 1)		/*XXX this is wrong for a dropped proxyconn.  Would have to pass back from transport */
       g = string_catn(g, US"*", 1);
 
 #ifndef DISABLE_EVENT
@@ -4275,6 +4275,10 @@ for (int delivery_count = 0; addr_remote; delivery_count++)
       }
     }
 
+/*XXX need to defeat this when DANE is used - but we don't know that yet.
+So look out for the place it gets used.
+*/
+
   /* Get the flag which specifies whether the transport can handle different
   domains that nevertheless resolve to the same set of hosts. If it needs
   expanding, get variables set: $address_data, $domain_data, $localpart_data,
@@ -4353,6 +4357,11 @@ for (int delivery_count = 0; addr_remote; delivery_count++)
   /************************************************************************/
 
 
+/*XXX don't know yet if DANE will be used.  So tpt will have to
+check at the point if gets next addr from list, and skip/defer any
+nonmatch domains
+*/
+
   /* Pick off all addresses which have the same transport, errors address,
   destination, and extra headers. In some cases they point to the same host
   list, but we also need to check for identical host lists generated from
@@ -4499,6 +4508,7 @@ for (int delivery_count = 0; addr_remote; delivery_count++)
   if (continue_transport)
     {
     BOOL ok = Ustrcmp(continue_transport, tp->name) == 0;
+/*XXX do we need to check for a DANEd conn vs. a change of domain? */
 
     /* If the transport is about to override the host list do not check
     it here but take the cost of running the transport process to discover
diff --git a/src/src/exim.c b/src/src/exim.c
index ac0ff5523..630ac4038 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -2806,10 +2806,22 @@ on the second character (the one after '-'), to save some effort. */
 	case 'S': smtp_peer_options |= OPTION_SIZE; break;
 
 #ifndef DISABLE_TLS
+    /* -MCs: used with -MCt; SNI was sent */
+    /* -MCr: ditto, DANE */
+
+	case 'r':
+	case 's': if (++i < argc)
+		    {
+		    continue_proxy_sni = string_copy_taint(argv[i], TRUE);
+		    if (argrest[1] == 'r') continue_proxy_dane = TRUE;
+		    }
+		  else badarg = TRUE;
+		  break;
+
     /* -MCt: similar to -MCT below but the connection is still open
     via a proxy process which handles the TLS context and coding.
     Require three arguments for the proxied local address and port,
-    and the TLS cipher.  */
+    and the TLS cipher. */
 
 	case 't': if (++i < argc)
 		    sending_ip_address = string_copy_taint(argv[i], TRUE);
diff --git a/src/src/globals.c b/src/src/globals.c
index fc3086f72..c34ac9ddd 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -729,6 +729,8 @@ uid_t   config_uid             = 0;
 
 int     connection_max_messages= -1;
 uschar *continue_proxy_cipher  = NULL;
+BOOL    continue_proxy_dane    = FALSE;
+uschar *continue_proxy_sni     = NULL;
 uschar *continue_hostname      = NULL;
 uschar *continue_host_address  = NULL;
 int     continue_sequence      = 1;
diff --git a/src/src/globals.h b/src/src/globals.h
index c80c8532f..a4c1143b7 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -425,6 +425,8 @@ extern uschar *config_main_filename;   /* File name actually used */
 extern uschar *config_main_directory;  /* Directory where the main config file was found */
 extern uid_t   config_uid;             /* Additional owner */
 extern uschar *continue_proxy_cipher;  /* TLS cipher for proxied continued delivery */
+extern BOOL    continue_proxy_dane;    /* proxied conn is DANE */
+extern uschar *continue_proxy_sni;     /* proxied conn SNI */
 extern uschar *continue_hostname;      /* Host for continued delivery */
 extern uschar *continue_host_address;  /* IP address for ditto */
 extern int     continue_sequence;      /* Sequence num for continued delivery */
diff --git a/src/src/spool_in.c b/src/src/spool_in.c
index a0147d5ee..1b4cefdb2 100644
--- a/src/src/spool_in.c
+++ b/src/src/spool_in.c
@@ -55,7 +55,7 @@ for (int i = 0; i < 2; i++)
 
   set_subdir_str(message_subdir, id, i);
   fname = spool_fname(US"input", message_subdir, id, US"-D");
-  DEBUG(D_deliver) debug_printf("Trying spool file %s\n", fname);
+  DEBUG(D_deliver) debug_printf_indent("Trying spool file %s\n", fname);
 
   /* We protect against symlink attacks both in not propagating the
    * file-descriptor to other processes as we exec, and also ensuring that we
@@ -367,7 +367,7 @@ for (int n = 0; n < 2; n++)
 errno = 0;
 
 #ifndef COMPILE_UTILITY
-DEBUG(D_deliver) debug_printf("reading spool file %s\n", name);
+DEBUG(D_deliver) debug_printf_indent("reading spool file %s\n", name);
 #endif  /* COMPILE_UTILITY */
 
 /* The first line of a spool file contains the message id followed by -H (i.e.
@@ -430,7 +430,7 @@ if (f.running_in_test_harness)
 #endif
 
 #ifndef COMPILE_UTILITY
-DEBUG(D_deliver) debug_printf("user=%s uid=%ld gid=%ld sender=%s\n",
+DEBUG(D_deliver) debug_printf_indent("user=%s uid=%ld gid=%ld sender=%s\n",
   originator_login, (long int)originator_uid, (long int)originator_gid,
   sender_address);
 #endif
@@ -715,7 +715,7 @@ host_build_sender_fullhost();
 
 #ifndef COMPILE_UTILITY
 DEBUG(D_deliver)
-  debug_printf("sender_local=%d ident=%s\n", f.sender_local,
+  debug_printf_indent("sender_local=%d ident=%s\n", f.sender_local,
     sender_ident ? sender_ident : US"unset");
 #endif  /* COMPILE_UTILITY */
 
@@ -743,7 +743,7 @@ if (sscanf(CS big_buffer, "%d", &rcount) != 1 || rcount > 16384)
   goto SPOOL_FORMAT_ERROR;
 
 #ifndef COMPILE_UTILITY
-DEBUG(D_deliver) debug_printf("recipients_count=%d\n", rcount);
+DEBUG(D_deliver) debug_printf_indent("recipients_count=%d\n", rcount);
 #endif  /* COMPILE_UTILITY */
 
 recipients_list_max = rcount;
@@ -814,7 +814,7 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
     {
     int dummy;
 #if !defined (COMPILE_UTILITY)
-    DEBUG(D_deliver) debug_printf("**** SPOOL_IN - Exim 3 spool file\n");
+    DEBUG(D_deliver) debug_printf_indent("**** SPOOL_IN - Exim 3 spool file\n");
 #endif
     while (isdigit(*(--p)) || *p == ',');
     if (*p == ' ')
@@ -829,7 +829,7 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
   else if (*p == ' ')
     {
 #if !defined (COMPILE_UTILITY)
-    DEBUG(D_deliver) debug_printf("**** SPOOL_IN - early Exim 4 spool file\n");
+    DEBUG(D_deliver) debug_printf_indent("**** SPOOL_IN - early Exim 4 spool file\n");
 #endif
     *p++ = 0;
     (void)sscanf(CS p, "%d", &pno);
@@ -842,7 +842,7 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
     int flags;
 
 #if !defined (COMPILE_UTILITY)
-    DEBUG(D_deliver) debug_printf("**** SPOOL_IN - Exim standard format spoolfile\n");
+    DEBUG(D_deliver) debug_printf_indent("**** SPOOL_IN - Exim standard format spoolfile\n");
 #endif
 
     (void)sscanf(CS p+1, "%d", &flags);
@@ -878,13 +878,13 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++)
     }
 #if !defined(COMPILE_UTILITY)
   else
-    { DEBUG(D_deliver) debug_printf("**** SPOOL_IN - No additional fields\n"); }
+    { DEBUG(D_deliver) debug_printf_indent("**** SPOOL_IN - No additional fields\n"); }
 
   if (orcpt || dsn_flags)
-    DEBUG(D_deliver) debug_printf("**** SPOOL_IN - address: <%s> orcpt: <%s> dsn_flags: 0x%x\n",
+    DEBUG(D_deliver) debug_printf_indent("**** SPOOL_IN - address: <%s> orcpt: <%s> dsn_flags: 0x%x\n",
       big_buffer, orcpt, dsn_flags);
   if (errors_to)
-    DEBUG(D_deliver) debug_printf("**** SPOOL_IN - address: <%s> errorsto: <%s>\n",
+    DEBUG(D_deliver) debug_printf_indent("**** SPOOL_IN - address: <%s> errorsto: <%s>\n",
       big_buffer, errors_to);
 #endif
 
@@ -956,7 +956,7 @@ line count by adding the body linecount to the header linecount. Close the file
 and give a positive response. */
 
 #ifndef COMPILE_UTILITY
-DEBUG(D_deliver) debug_printf("body_linecount=%d message_linecount=%d\n",
+DEBUG(D_deliver) debug_printf_indent("body_linecount=%d message_linecount=%d\n",
   body_linecount, message_linecount);
 #endif  /* COMPILE_UTILITY */
 
diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c
index a34633390..dafe1be0c 100644
--- a/src/src/tls-gnu.c
+++ b/src/src/tls-gnu.c
@@ -545,7 +545,10 @@ else
 
 /* peercert is set in peer_status() */
 tlsp->peerdn = state->peerdn;
-tlsp->sni =    state->received_sni;
+
+/* do not corrupt sni sent by client; record sni rxd by server */
+if (!state->host)
+  tlsp->sni = state->received_sni;
 
 /* record our certificate */
   {
@@ -2890,6 +2893,7 @@ if (!cipher_list)
       cipher_list, &state, tlsp, errstr) != OK)
     return FALSE;
 
+
 #ifdef MEASURE_TIMING
   report_time_since(&t0, US"client tls_init (delta)");
 #endif
diff --git a/src/src/transport.c b/src/src/transport.c
index 2d8426f29..b1cda55fd 100644
--- a/src/src/transport.c
+++ b/src/src/transport.c
@@ -1661,6 +1661,7 @@ DEBUG(D_transport)
   debug_printf("transport_check_waiting entered\n");
   debug_printf("  sequence=%d local_max=%d global_max=%d\n",
     continue_sequence, local_message_max, connection_max_messages);
+  acl_level++;
   }
 
 /* Do nothing if we have hit the maximum number that can be send down one
@@ -1670,23 +1671,23 @@ if (connection_max_messages >= 0) local_message_max = connection_max_messages;
 if (local_message_max > 0 && continue_sequence >= local_message_max)
   {
   DEBUG(D_transport)
-    debug_printf("max messages for one connection reached: returning\n");
-  return FALSE;
+    debug_printf_indent("max messages for one connection reached: returning\n");
+  goto retfalse;
   }
 
 /* Open the waiting information database. */
 
 if (!(dbm_file = dbfn_open(string_sprintf("wait-%.200s", transport_name),
 			  O_RDWR, &dbblock, TRUE, TRUE)))
-  return FALSE;
+  goto retfalse;
 
 /* See if there is a record for this host; if not, there's nothing to do. */
 
 if (!(host_record = dbfn_read(dbm_file, hostname)))
   {
   dbfn_close(dbm_file);
-  DEBUG(D_transport) debug_printf("no messages waiting for %s\n", hostname);
-  return FALSE;
+  DEBUG(D_transport) debug_printf_indent("no messages waiting for %s\n", hostname);
+  goto retfalse;
   }
 
 /* If the data in the record looks corrupt, just log something and
@@ -1697,7 +1698,7 @@ if (host_record->count > WAIT_NAME_MAX)
   dbfn_close(dbm_file);
   log_write(0, LOG_MAIN|LOG_PANIC, "smtp-wait database entry for %s has bad "
     "count=%d (max=%d)", hostname, host_record->count, WAIT_NAME_MAX);
-  return FALSE;
+  goto retfalse;
   }
 
 /* Scan the message ids in the record from the end towards the beginning,
@@ -1835,8 +1836,8 @@ while (1)
   if (host_length <= 0)
     {
     dbfn_close(dbm_file);
-    DEBUG(D_transport) debug_printf("waiting messages already delivered\n");
-    return FALSE;
+    DEBUG(D_transport) debug_printf_indent("waiting messages already delivered\n");
+    goto retfalse;
     }
 
   /* we were not able to find an acceptable message, nor was there a
@@ -1847,7 +1848,7 @@ while (1)
     {
     Ustrcpy(new_message_id, message_id);
     dbfn_close(dbm_file);
-    return FALSE;
+    goto retfalse;
     }
   }		/* we need to process a continuation record */
 
@@ -1865,7 +1866,12 @@ if (host_length > 0)
   }
 
 dbfn_close(dbm_file);
+DEBUG(D_transport) {acl_level--; debug_printf("transport_check_waiting: TRUE\n"); }
 return TRUE;
+
+retfalse:
+DEBUG(D_transport) {acl_level--; debug_printf("transport_check_waiting: FALSE\n"); }
+return FALSE;
 }
 
 /*************************************************
@@ -1877,7 +1883,7 @@ void
 transport_do_pass_socket(const uschar *transport_name, const uschar *hostname,
   const uschar *hostaddress, uschar *id, int socket_fd)
 {
-int i = 20;
+int i = 22;
 const uschar **argv;
 
 /* Set up the calling arguments; use the standard function for the basics,
@@ -1898,6 +1904,12 @@ if (smtp_peer_options & OPTION_TLS)
     argv[i++] = sending_ip_address;
     argv[i++] = string_sprintf("%d", sending_port);
     argv[i++] = tls_out.active.sock >= 0 ? tls_out.cipher : continue_proxy_cipher;
+
+    if (tls_out.sni)
+      {
+      argv[i++] = tls_out.dane_verified ? US"-MCr" : US"-MCs";
+      argv[i++] = tls_out.sni;
+      }
     }
   else
     argv[i++] = US"-MCT";
diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index d63379e37..7fc2a48bb 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -1620,8 +1620,8 @@ return FALSE;
 
 typedef struct smtp_compare_s
 {
-    uschar                          *current_sender_address;
-    struct transport_instance       *tblock;
+    uschar *			current_sender_address;
+    struct transport_instance *	tblock;
 } smtp_compare_t;
 
 
@@ -1991,6 +1991,74 @@ if (sx->smtps)
   }
 #endif
 
+#ifdef SUPPORT_DANE
+/* If we have a proxied TLS connection, check usability for this message */
+
+if (continue_hostname && continue_proxy_cipher)
+  {
+  int rc;
+  const uschar * sni = US"";
+
+  /* Check if the message will be DANE-verified; if so force its SNI */
+
+  smtp_port_for_connect(sx->conn_args.host, sx->port);
+  if (  sx->conn_args.host->dnssec == DS_YES
+     && (  sx->dane_required
+	|| verify_check_given_host(CUSS &ob->hosts_try_dane, sx->conn_args.host) == OK
+     )  )
+    switch (rc = tlsa_lookup(sx->conn_args.host, &sx->conn_args.tlsa_dnsa, sx->dane_required))
+      {
+      case OK:		sx->conn_args.dane = TRUE;
+			ob->tls_tempfail_tryclear = FALSE;	/* force TLS */
+			ob->tls_sni = sx->first_addr->domain;	/* force SNI */
+			break;
+      case FAIL_FORCED:	break;
+      default:		set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
+			      string_sprintf("DANE error: tlsa lookup %s",
+				rc_to_string(rc)),
+			      rc, FALSE, &sx->delivery_start);
+# ifndef DISABLE_EVENT
+			    (void) event_raise(sx->conn_args.tblock->event_action,
+			      US"dane:fail", sx->dane_required
+				?  US"dane-required" : US"dnssec-invalid");
+# endif
+			    return rc;
+      }
+
+  /* If the SNI required for the new message differs from the existing conn
+  drop the connection to force a new one. */
+
+  if (ob->tls_sni && !(sni = expand_cstring(ob->tls_sni)))
+    log_write(0, LOG_MAIN|LOG_PANIC,
+      "<%s>: failed to expand transport's tls_sni value: %s",
+      sx->addrlist->address, expand_string_message);
+
+  if (  (continue_proxy_sni ? (Ustrcmp(continue_proxy_sni, sni) == 0) : !*sni)
+     && continue_proxy_dane == sx->conn_args.dane)
+    {
+    tls_out.sni = US sni;
+    if ((tls_out.dane_verified = continue_proxy_dane))
+      sx->conn_args.host->dnssec = DS_YES;
+    }
+  else
+    {
+    DEBUG(D_transport)
+      debug_printf("Closing proxied-TLS connection due to SNI mismatch\n");
+
+    HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP>> QUIT\n");
+    write(0, "QUIT\r\n", 6);
+    close(0);
+    tls_out.dane_verified = FALSE;
+    continue_hostname = continue_proxy_cipher = NULL;
+    f.continue_more = FALSE;
+    continue_sequence = 1;	/* Unfortunately, this process cannot affect success log
+    				which is done by delivery proc.  Would have to pass this
+				back through reporting pipe. */
+    }
+  }
+#endif
+
+
 /* Make a connection to the host if this isn't a continued delivery, and handle
 the initial interaction and HELO/EHLO/LHLO. Connect timeout errors are handled
 specially so they can be identified for retries. */
@@ -3430,7 +3498,7 @@ 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 */
-#if !defined(DISABLE_TLS) && defined(SUPPORT_DANE)
+#ifdef SUPPORT_DANE
 BOOL dane_held;
 #endif
 
@@ -3449,7 +3517,7 @@ sx->conn_args.tblock = tblock;
 gettimeofday(&sx->delivery_start, NULL);
 sx->sync_addr = sx->first_addr = addrlist;
 
-#if !defined(DISABLE_TLS) && defined(SUPPORT_DANE)
+#ifdef SUPPORT_DANE
 DANE_DOMAINS:
 dane_held = FALSE;
 #endif
@@ -3464,7 +3532,7 @@ if ((rc = smtp_setup_conn(sx, suppress_tls)) != OK)
   goto TIDYUP;
   }
 
-#if !defined(DISABLE_TLS) && defined(SUPPORT_DANE)
+#ifdef SUPPORT_DANE
 /* If the connection used DANE, ignore for now any addresses with incompatible
 domains.  The SNI has to be the domain.  Arrange a whole new TCP conn later,
 just in case only TLS isn't enough. */
@@ -4184,8 +4252,8 @@ 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
-     || f.continue_more
+  if (  sx->first_addr != NULL		/* more addrs for this message */
+     || f.continue_more			/* more addrs for coninued-host */
      || (
 #ifndef DISABLE_TLS
 	   (  tls_out.active.sock < 0  &&  !continue_proxy_cipher
@@ -4232,7 +4300,7 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
 
 
       if (sx->first_addr != NULL)	/* More addresses still to be sent */
-        {				/*   on this connection            */
+        {				/*   for this message              */
         continue_sequence++;		/* Causes * in logging */
 	pipelining_active = sx->pipelining_used;    /* was cleared at DATA */
         goto SEND_MESSAGE;
@@ -4256,6 +4324,7 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
 
 	  tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_WAIT);
 	  sx->cctx.tls_ctx = NULL;
+	  tls_out.active.sock = -1;
 	  smtp_peer_options = smtp_peer_options_wrap;
 	  sx->ok = !sx->smtps
 	    && smtp_write_command(sx, SCMD_FLUSH, "EHLO %s\r\n", sx->helo_data)
@@ -4399,7 +4468,7 @@ if (sx->send_quit)
 (void) event_raise(tblock->event_action, US"tcp:close", NULL);
 #endif
 
-#if !defined(DISABLE_TLS) && defined(SUPPORT_DANE)
+#ifdef SUPPORT_DANE
 if (dane_held)
   {
   sx->first_addr = NULL;
@@ -4425,7 +4494,7 @@ continue_hostname = NULL;
 return yield;
 
 TIDYUP:
-#if !defined(DISABLE_TLS) && defined(SUPPORT_DANE)
+#ifdef SUPPORT_DANE
 if (dane_held) for (address_item * a = sx->addrlist->next; a; a = a->next)
   if (a->transport_return == DANE)
     a->transport_return = PENDING_DEFER;
diff --git a/src/src/transports/smtp.h b/src/src/transports/smtp.h
index 6e63a002d..213bca1a8 100644
--- a/src/src/transports/smtp.h
+++ b/src/src/transports/smtp.h
@@ -87,7 +87,7 @@ typedef struct {
 # ifdef EXPERIMENTAL_TLS_RESUME
   uschar *tls_resumption_hosts;
 # endif
-  uschar *tls_sni;
+  const uschar *tls_sni;
   uschar *tls_verify_certificates;
   int     tls_dh_min_bits;
   BOOL    tls_tempfail_tryclear;

commit 6742503035564e057edc7d3eba88b108f7d3d74c
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Wed Aug 26 23:59:28 2020 +0100

    Fix non-DANE build
    
    (cherry picked from commit 651946cbf8e3849687332049730e5fa23d42b4b7)

diff --git a/src/src/smtp_out.c b/src/src/smtp_out.c
index 86c3e4127..c4c409677 100644
--- a/src/src/smtp_out.c
+++ b/src/src/smtp_out.c
@@ -375,7 +375,7 @@ smtp_port_for_connect(host_item * host, int port)
 {
 if (host->port != PORT_NONE)
   {
-  HDEBUG(D_transport|D_acl|D_v)
+  HDEBUG(D_transport|D_acl|D_v) if (port != host->port)
     debug_printf_indent("Transport port=%d replaced by host-specific port=%d\n", port,
       host->port);
   port = host->port;
diff --git a/src/src/transport.c b/src/src/transport.c
index b1cda55fd..609fd128c 100644
--- a/src/src/transport.c
+++ b/src/src/transport.c
@@ -1907,7 +1907,11 @@ if (smtp_peer_options & OPTION_TLS)
 
     if (tls_out.sni)
       {
-      argv[i++] = tls_out.dane_verified ? US"-MCr" : US"-MCs";
+      argv[i++] =
+#ifdef SUPPORT_DANE
+        tls_out.dane_verified ? US"-MCr" :
+#endif
+        US"-MCs";
       argv[i++] = tls_out.sni;
       }
     }
diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index 7fc2a48bb..77335af09 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -1991,7 +1991,6 @@ if (sx->smtps)
   }
 #endif
 
-#ifdef SUPPORT_DANE
 /* If we have a proxied TLS connection, check usability for this message */
 
 if (continue_hostname && continue_proxy_cipher)
@@ -1999,8 +1998,10 @@ if (continue_hostname && continue_proxy_cipher)
   int rc;
   const uschar * sni = US"";
 
+#ifdef SUPPORT_DANE
   /* Check if the message will be DANE-verified; if so force its SNI */
 
+  tls_out.dane_verified = FALSE;
   smtp_port_for_connect(sx->conn_args.host, sx->port);
   if (  sx->conn_args.host->dnssec == DS_YES
      && (  sx->dane_required
@@ -2024,15 +2025,17 @@ if (continue_hostname && continue_proxy_cipher)
 # endif
 			    return rc;
       }
+#endif
 
-  /* If the SNI required for the new message differs from the existing conn
-  drop the connection to force a new one. */
+  /* If the SNI or the DANE status required for the new message differs from the
+  existing conn drop the connection to force a new one. */
 
   if (ob->tls_sni && !(sni = expand_cstring(ob->tls_sni)))
     log_write(0, LOG_MAIN|LOG_PANIC,
       "<%s>: failed to expand transport's tls_sni value: %s",
       sx->addrlist->address, expand_string_message);
 
+#ifdef SUPPORT_DANE
   if (  (continue_proxy_sni ? (Ustrcmp(continue_proxy_sni, sni) == 0) : !*sni)
      && continue_proxy_dane == sx->conn_args.dane)
     {
@@ -2040,6 +2043,10 @@ if (continue_hostname && continue_proxy_cipher)
     if ((tls_out.dane_verified = continue_proxy_dane))
       sx->conn_args.host->dnssec = DS_YES;
     }
+#else
+  if ((continue_proxy_sni ? (Ustrcmp(continue_proxy_sni, sni) == 0) : !*sni))
+    tls_out.sni = US sni;
+#endif
   else
     {
     DEBUG(D_transport)
@@ -2048,7 +2055,6 @@ if (continue_hostname && continue_proxy_cipher)
     HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP>> QUIT\n");
     write(0, "QUIT\r\n", 6);
     close(0);
-    tls_out.dane_verified = FALSE;
     continue_hostname = continue_proxy_cipher = NULL;
     f.continue_more = FALSE;
     continue_sequence = 1;	/* Unfortunately, this process cannot affect success log
@@ -2056,7 +2062,6 @@ if (continue_hostname && continue_proxy_cipher)
 				back through reporting pipe. */
     }
   }
-#endif
 
 
 /* Make a connection to the host if this isn't a continued delivery, and handle

commit 49d173f4e4c05bbc9e6f256f8914979dad85e9d3
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sun Sep 6 12:15:10 2020 +0100

    GnuTLS: clear errno before any data i/o op, so error logging does not see stale values
    
    (cherry picked from commit d52a8ce8499fbb88f4670623df9f52d3e790292b)

diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c
index dafe1be0c..6ee603595 100644
--- a/src/src/tls-gnu.c
+++ b/src/src/tls-gnu.c
@@ -3162,6 +3162,7 @@ DEBUG(D_tls) debug_printf("Calling gnutls_record_recv(session=%p, buffer=%p, buf
 sigalrm_seen = FALSE;
 if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout);
 
+errno = 0;
 do
   inbytes = gnutls_record_recv(state->session, state->xfer_buffer,
     MIN(ssl_xfer_buffer_size, lim));
@@ -3322,6 +3323,7 @@ DEBUG(D_tls)
   debug_printf("Calling gnutls_record_recv(session=%p, buffer=%p, len=" SIZE_T_FMT ")\n",
       state->session, buff, len);
 
+errno = 0;
 do
   inbytes = gnutls_record_recv(state->session, buff, len);
 while (inbytes == GNUTLS_E_AGAIN);
@@ -3385,6 +3387,7 @@ while (left > 0)
   DEBUG(D_tls) debug_printf("gnutls_record_send(session=%p, buffer=%p, left=" SIZE_T_FMT ")\n",
       state->session, buff, left);
 
+  errno = 0;
   do
     outbytes = gnutls_record_send(state->session, buff, left);
   while (outbytes == GNUTLS_E_AGAIN);

commit 7a534c812646a7a6f680827352d6209c6ff7be96
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Thu Aug 27 21:15:19 2020 +0100

    Fix non-TLS build
    
    (cherry picked from commit b38a477da0a5248ed1d2b7590922c89c6337ec3b)

diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index 77335af09..b0dedfa8c 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -1989,7 +1989,7 @@ if (sx->smtps)
 	    DEFER, FALSE, &sx->delivery_start);
   return ERROR;
   }
-#endif
+#else
 
 /* If we have a proxied TLS connection, check usability for this message */
 
@@ -1998,7 +1998,7 @@ if (continue_hostname && continue_proxy_cipher)
   int rc;
   const uschar * sni = US"";
 
-#ifdef SUPPORT_DANE
+# ifdef SUPPORT_DANE
   /* Check if the message will be DANE-verified; if so force its SNI */
 
   tls_out.dane_verified = FALSE;
@@ -2018,14 +2018,14 @@ if (continue_hostname && continue_proxy_cipher)
 			      string_sprintf("DANE error: tlsa lookup %s",
 				rc_to_string(rc)),
 			      rc, FALSE, &sx->delivery_start);
-# ifndef DISABLE_EVENT
+#  ifndef DISABLE_EVENT
 			    (void) event_raise(sx->conn_args.tblock->event_action,
 			      US"dane:fail", sx->dane_required
 				?  US"dane-required" : US"dnssec-invalid");
-# endif
+#  endif
 			    return rc;
       }
-#endif
+# endif
 
   /* If the SNI or the DANE status required for the new message differs from the
   existing conn drop the connection to force a new one. */
@@ -2035,7 +2035,7 @@ if (continue_hostname && continue_proxy_cipher)
       "<%s>: failed to expand transport's tls_sni value: %s",
       sx->addrlist->address, expand_string_message);
 
-#ifdef SUPPORT_DANE
+# ifdef SUPPORT_DANE
   if (  (continue_proxy_sni ? (Ustrcmp(continue_proxy_sni, sni) == 0) : !*sni)
      && continue_proxy_dane == sx->conn_args.dane)
     {
@@ -2043,10 +2043,10 @@ if (continue_hostname && continue_proxy_cipher)
     if ((tls_out.dane_verified = continue_proxy_dane))
       sx->conn_args.host->dnssec = DS_YES;
     }
-#else
+# else
   if ((continue_proxy_sni ? (Ustrcmp(continue_proxy_sni, sni) == 0) : !*sni))
     tls_out.sni = US sni;
-#endif
+# endif
   else
     {
     DEBUG(D_transport)
@@ -2062,7 +2062,7 @@ if (continue_hostname && continue_proxy_cipher)
 				back through reporting pipe. */
     }
   }
-
+#endif	/*!DISABLE_TLS*/
 
 /* Make a connection to the host if this isn't a continued delivery, and handle
 the initial interaction and HELO/EHLO/LHLO. Connect timeout errors are handled

commit f521f0d2120be2ccfb93306cc05790b9b0f162c1
Author: Richard Clayton <richard@highwayman.com>
Date:   Sat Sep 12 22:10:04 2020 +0100

    eximon: fix FreeBSD build
    
    (cherry picked from commit ba00bdd4609501dd3ffe187074ff7f8197a9059f)

diff --git a/src/exim_monitor/em_menu.c b/src/exim_monitor/em_menu.c
index 33b3e0c94..2a70a1831 100644
--- a/src/exim_monitor/em_menu.c
+++ b/src/exim_monitor/em_menu.c
@@ -670,7 +670,7 @@ if (spool_read_header(buffer, TRUE, FALSE) != spool_read_OK)
     sprintf(CS big_buffer, "%s/input/%s", spool_directory, buffer);
     if (Ustat(big_buffer, &statbuf) == 0)
       text_showf(text, "Format error in spool file %s: size=%lu\n", buffer,
-        (ulong)statbuf.st_size);
+        (unsigned long)statbuf.st_size);
     else text_showf(text, "Format error in spool file %s\n", buffer);
     }
   else text_showf(text, "Read error for spool file %s\n", buffer);

commit e12d2e7bc2e9f0c30a1029602e57e5ae8df1b9db
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Oct 30 13:58:01 2020 +0000

        LDAP: fix taint-check in server list walk.  Bug 2646
    
        (cherry picked from commit 51b611aa81d7ee01243b196abc34a0e2eabd293c)

diff --git a/src/src/lookups/ldap.c b/src/src/lookups/ldap.c
index ef550a08d..34908a351 100644
--- a/src/src/lookups/ldap.c
+++ b/src/src/lookups/ldap.c
@@ -1093,7 +1093,6 @@ uschar *password = NULL;
 uschar *local_servers = NULL;
 uschar *server;
 const uschar *list;
-uschar buffer[512];
 
 while (isspace(*url)) url++;
 
@@ -1254,7 +1253,7 @@ if (!eldap_default_servers && !local_servers  || p[3] != '/')
 /* Loop through the default servers until OK or FAIL. Use local_servers list
  * if defined in the lookup, otherwise use the global default list */
 list = !local_servers ? eldap_default_servers : local_servers;
-while ((server = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
+while ((server = string_nextinlist(&list, &sep, NULL, 0)))
   {
   int rc;
   int port = 0;

commit a3ab48f23ee4a83f796440ef67d7ac7b43aad4b5
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sat Oct 31 23:58:11 2020 +0000

    Pass authenticator pubname through spool.  Bug 2648
    
    (cherry picked from commit a75ebe0dcc5faeb915cacb0d9db66d2475789116)

diff --git a/src/exim_monitor/em_globals.c b/src/exim_monitor/em_globals.c
index 925e88e05..30d22b5eb 100644
--- a/src/exim_monitor/em_globals.c
+++ b/src/exim_monitor/em_globals.c
@@ -205,6 +205,7 @@ uschar *sender_address         = NULL;
 uschar *sender_fullhost        = NULL;
 uschar *sender_helo_name       = NULL;
 uschar *sender_host_address    = NULL;
+uschar *sender_host_auth_pubname = NULL;
 uschar *sender_host_authenticated = NULL;
 uschar *sender_host_name       = NULL;
 int     sender_host_port       = 0;
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index a13f0ed63..f53c3cf65 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -5935,12 +5935,14 @@ if (!sender_host_authenticated)
 
 g = string_append(g, 2, US";\n\tauth=pass (", sender_host_auth_pubname);
 
-if (Ustrcmp(sender_host_auth_pubname, "tls") != 0)
-  g = string_append(g, 2, US") smtp.auth=", authenticated_id);
-else if (authenticated_id)
-  g = string_append(g, 2, US") x509.auth=", authenticated_id);
+if (Ustrcmp(sender_host_auth_pubname, "tls") == 0)
+  g = authenticated_id
+    ? string_append(g, 2, US") x509.auth=", authenticated_id)
+    : string_cat(g, US") reason=x509.auth");
 else
-  g = string_catn(g, US") reason=x509.auth", 17);
+  g = authenticated_id
+    ? string_append(g, 2, US") smtp.auth=", authenticated_id)
+    : string_cat(g, US", no id saved)");
 
 if (authenticated_sender)
   g = string_append(g, 2, US" smtp.mailfrom=", authenticated_sender);
diff --git a/src/src/spool_in.c b/src/src/spool_in.c
index 1b4cefdb2..35e44df26 100644
--- a/src/src/spool_in.c
+++ b/src/src/spool_in.c
@@ -253,7 +253,7 @@ sender_helo_name = NULL;
 sender_host_address = NULL;
 sender_host_name = NULL;
 sender_host_port = 0;
-sender_host_authenticated = NULL;
+sender_host_authenticated = sender_host_auth_pubname = NULL;
 sender_ident = NULL;
 f.sender_local = FALSE;
 f.sender_set_untrusted = FALSE;
@@ -580,6 +580,8 @@ for (;;)
       host_lookup_deferred = TRUE;
     else if (Ustrcmp(p, "ost_lookup_failed") == 0)
       host_lookup_failed = TRUE;
+    else if (Ustrncmp(p, "ost_auth_pubname", 16) == 0)
+      sender_host_auth_pubname = string_copy_taint(var + 18, tainted);
     else if (Ustrncmp(p, "ost_auth", 8) == 0)
       sender_host_authenticated = string_copy_taint(var + 10, tainted);
     else if (Ustrncmp(p, "ost_name", 8) == 0)
diff --git a/src/src/spool_out.c b/src/src/spool_out.c
index 4b6539ecd..0851ce956 100644
--- a/src/src/spool_out.c
+++ b/src/src/spool_out.c
@@ -174,9 +174,11 @@ if (sender_host_address)
   fprintf(fp, "-host_address %s.%d\n", sender_host_address, sender_host_port);
   if (sender_host_name)
     spool_var_write(fp, US"host_name", sender_host_name);
-  if (sender_host_authenticated)
-    spool_var_write(fp, US"host_auth", sender_host_authenticated);
   }
+if (sender_host_authenticated)
+  spool_var_write(fp, US"host_auth", sender_host_authenticated);
+if (sender_host_auth_pubname)
+  spool_var_write(fp, US"host_auth_pubname", sender_host_auth_pubname);
 
 /* Also about the interface a message came in on */
 

commit 60d8457eb7f7cb868413185d9d285538703234a5
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Wed Dec 16 20:05:07 2020 +0000

        Fix matching of long addresses.  Bug 2677
        (cherry picked from commit 183389fae10672e8d5ffb1f14f23a179798f483a)

diff --git a/src/src/match.c b/src/src/match.c
index 65d44198e..53cd22353 100644
--- a/src/src/match.c
+++ b/src/src/match.c
@@ -1288,9 +1288,11 @@ compared. Therefore, Exim now forces the entire address into lower case here,
 provided that "caseless" is set. (It is FALSE for calls for matching rewriting
 patterns.) Otherwise just the domain is lower cases. A magic item "+caseful" in
 the list can be used to restore a caseful copy of the local part from the
-original address. */
+original address.
+Limit the subject address size to avoid mem-exhastion attacks.  The size chosen
+is historical (we used to use big_buffer her). */
 
-if ((len = Ustrlen(address)) > 255) len = 255;
+if ((len = Ustrlen(address)) > BIG_BUFFER_SIZE) len = BIG_BUFFER_SIZE;
 ab.address = string_copyn(address, len);
 
 for (uschar * p = ab.address + len - 1; p >= ab.address; p--)

commit 50c8b751ccf3018701f90e861113a80353e5d69f
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Thu Dec 17 09:39:59 2020 +0000

    Remove the X_ prefix from the PIPE_CONNECT SMTP service extension keyword.
    
    (cherry picked from commit 958af3bdb77dc5c190b7f5117c68d2b0acd7b5bc)

diff --git a/src/src/macros.h b/src/src/macros.h
index 6fd5db94c..2fe364ed4 100644
--- a/src/src/macros.h
+++ b/src/src/macros.h
@@ -1070,8 +1070,8 @@ should not be one active. */
 
 #define AUTHS_REGEX US"\\n250[\\s\\-]AUTH\\s+([\\-\\w \\t]+)(?:\\n|$)"
 
-#define EARLY_PIPE_FEATURE_NAME "X_PIPE_CONNECT"
-#define EARLY_PIPE_FEATURE_LEN  14
+#define EARLY_PIPE_FEATURE_NAME "PIPE_CONNECT"
+#define EARLY_PIPE_FEATURE_LEN  12
 
 
 /* Flags for auth_client_item() */

commit 79ed24aec9d7dbac5b4b889f9ede92140eca13bd
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Thu Dec 17 09:59:23 2020 +0000

    Fix the PIPE_CONNECT feature control in the template Makefile, the
    default having changed to "included" for 4.93
    
    Broken-by: 81344b40e3
    (cherry picked from commit 46694b802ce0302f3c3344be933cc9737d4d4f4c)

diff --git a/src/src/EDITME b/src/src/EDITME
index e568bdbb1..8da36a353 100644
--- a/src/src/EDITME
+++ b/src/src/EDITME
@@ -564,9 +564,9 @@ DISABLE_MAL_MKS=yes
 # DISABLE_EVENT=yes
 
 
-# Uncomment this line to include support for early pipelining, per
+# Uncomment this line to remove support for early pipelining, per
 # https://datatracker.ietf.org/doc/draft-harris-early-pipe/
-# SUPPORT_PIPE_CONNECT=yes
+# DISABLE_PIPE_CONNECT=yes
 
 
 #------------------------------------------------------------------------------

commit 2dd9a491d1ff0ddc6e6fc22d6db3e9d8ba191f64
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sun Dec 20 15:55:59 2020 +0000

    Fix local delivery delay when combined with remote callout/hold.  Bug 2599
    
    (cherry picked from commit 94ecf089d68ac5b85c2a99177a8e4b4d35d5aa2e)

diff --git a/src/src/deliver.c b/src/src/deliver.c
index 8f21c607e..d85edd70e 100644
--- a/src/src/deliver.c
+++ b/src/src/deliver.c
@@ -7079,10 +7079,20 @@ if (  mua_wrapper
 
 
 /* If this is a run to continue deliveries to an external channel that is
-already set up, defer any local deliveries. */
+already set up, defer any local deliveries.
 
-if (continue_transport)
+jgh 2020/12/20: I don't see why; locals should be quick.
+The defer goes back to version 1.62 in 1997.  A local being still deliverable
+during a continued run might result from something like a defer during the
+original delivery, eg. in a DB lookup.  Unlikely but possible.
+
+To avoid delaying a local when combined with a callout-hold for a remote
+delivery, test continue_sequence rather than continue_transport. */
+
+if (continue_sequence > 1 && addr_local)
   {
+  DEBUG(D_deliver|D_retry|D_route)
+    debug_printf("deferring local deliveries due to continued-transport\n");
   if (addr_defer)
     {
     address_item *addr = addr_defer;

commit f8f129d658e8b2e25714b09737c47623ebf2ed2f
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Wed Dec 23 22:35:04 2020 +0000

    Fix ${listextract } from a tainted list
    
    (cherry picked from commit 942f0be6c2cd3ec8c39ca234a449561d9d3c1075)

diff --git a/src/src/expand.c b/src/src/expand.c
index bb9fd79ef..05de94c49 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -1298,15 +1298,16 @@ expand_getlistele(int field, const uschar * list)
 {
 const uschar * tlist = list;
 int sep = 0;
-uschar dummy;
+/* Tainted mem for the throwaway element copies */
+uschar * dummy = store_get(2, TRUE);
 
 if (field < 0)
   {
-  for (field++; string_nextinlist(&tlist, &sep, &dummy, 1); ) field++;
+  for (field++; string_nextinlist(&tlist, &sep, dummy, 1); ) field++;
   sep = 0;
   }
 if (field == 0) return NULL;
-while (--field > 0 && (string_nextinlist(&list, &sep, &dummy, 1))) ;
+while (--field > 0 && (string_nextinlist(&list, &sep, dummy, 1))) ;
 return string_nextinlist(&list, &sep, NULL, 0);
 }
 

commit ae13c7671a1651300395f41b9df6990dafdce4a5
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sat Dec 26 18:18:33 2020 +0000

    Fix build warning on 32-bit int platfowms.  Bug 2678
    
    (cherry picked from commit abca11df7f354c123b0ff8a9bfb89a669d7742d4)

diff --git a/src/src/pdkim/pdkim.c b/src/src/pdkim/pdkim.c
index 3a6ca4e91..f68324097 100644
--- a/src/src/pdkim/pdkim.c
+++ b/src/src/pdkim/pdkim.c
@@ -1324,7 +1324,8 @@ check_bare_ed25519_pubkey(pdkim_pubkey * p)
 int excess = p->key.len - 32;
 if (excess > 0)
   {
-  DEBUG(D_acl) debug_printf("DKIM: unexpected pubkey len %lu\n", p->key.len);
+  DEBUG(D_acl)
+    debug_printf("DKIM: unexpected pubkey len %lu\n", (unsigned long) p->key.len);
   p->key.data += excess; p->key.len = 32;
   }
 }

commit 2aead3fdc151cf0fc5ec6563a59cc893307c518e
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sat Dec 26 18:55:29 2020 +0000

    Fix build on GNU/Hurd [supports openat()].  Bug 2608
    
    (cherry picked from commit 5f4ca3541f228b0b9b880406e70342dea5a2a7a9)

diff --git a/src/OS/os.h-GNU b/src/OS/os.h-GNU
index 59130ef10..2c3b7b5f5 100644
--- a/src/OS/os.h-GNU
+++ b/src/OS/os.h-GNU
@@ -1,6 +1,8 @@
 /* Exim: OS-specific C header file for GNU/Hurd */
 /* Copyright (c) The Exim Maintainers 2020 */
 
+#include <features.h>
+
 #define CRYPT_H
 #define GLIBC_IP_OPTIONS
 #define HAVE_BSD_GETLOADAVG
@@ -25,4 +27,8 @@ typedef struct flock flock_t;
 as well as any supplementary groups*/
 #define OS_SETGROUPS_ZERO_DROPS_ALL
 
+#if _POSIX_C_SOURCE >= 200809L || _ATFILE_SOURCE
+# define EXIM_HAVE_OPENAT
+#endif
+
 /* End */

commit cd0c558c51a380786b03122acdc541cae67157f6
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sat Jan 9 13:08:35 2021 +0000

    Utilities: harden exim_tidydb against corrupt wait-records.  Bug 2343
    
    (cherry picked from commit fc96555ab63243de9d468325aeaaa14cd77b9943)

diff --git a/src/src/exim_dbutil.c b/src/src/exim_dbutil.c
index 5c7b6650e..bd7b53865 100644
--- a/src/src/exim_dbutil.c
+++ b/src/src/exim_dbutil.c
@@ -391,7 +391,7 @@ pick out the timestamps, etc., do the copying centrally here.
 Arguments:
   dbblock   a pointer to an open database block
   key       the key of the record to be read
-  length    where to put the length (or NULL if length not wanted)
+  length    where to put the length (or NULL if length not wanted). Includes overhead.
 
 Returns: a pointer to the retrieved record, or
          NULL if the record is not found
@@ -419,7 +419,7 @@ we should store the taint status along with the data. */
 
 yield = store_get(EXIM_DATUM_SIZE(result_datum), TRUE);
 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
-if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
+if (length) *length = EXIM_DATUM_SIZE(result_datum);
 
 EXIM_DATUM_FREE(result_datum);    /* Some DBM libs require freeing */
 return yield;
@@ -620,6 +620,7 @@ for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
 	t = wait->text;
 	name[MESSAGE_ID_LENGTH] = 0;
 
+    /* Leave corrupt records alone */
 	if (wait->count > WAIT_NAME_MAX)
 	  {
 	  fprintf(stderr,
@@ -1222,7 +1223,7 @@ for (; keychain && (reset_point = store_mark()); store_reset(reset_point))
   /* A continuation record may have been deleted or renamed already, so
   non-existence is not serious. */
 
-  if (value == NULL) continue;
+  if (!value) continue;
 
   /* Delete if too old */
 
@@ -1243,12 +1244,33 @@ for (; keychain && (reset_point = store_mark()); store_reset(reset_point))
 
     /* Leave corrupt records alone */
 
+    if (wait->time_stamp > time(NULL))
+      {
+      printf("**** Data for '%s' corrupted\n  time in future: %s\n",
+        key, print_time(((dbdata_generic *)value)->time_stamp));
+      continue;
+      }
     if (wait->count > WAIT_NAME_MAX)
       {
-      printf("**** Data for %s corrupted\n  count=%d=0x%x max=%d\n",
+      printf("**** Data for '%s' corrupted\n  count=%d=0x%x max=%d\n",
         key, wait->count, wait->count, WAIT_NAME_MAX);
       continue;
       }
+    if (wait->sequence > WAIT_CONT_MAX)
+      {
+      printf("**** Data for '%s' corrupted\n  sequence=%d=0x%x max=%d\n",
+        key, wait->sequence, wait->sequence, WAIT_CONT_MAX);
+      continue;
+      }
+
+    /* Record over 1 year old; just remove it */
+
+    if (wait->time_stamp < time(NULL) - 365*24*60*60)
+      {
+      dbfn_delete(dbm, key);
+      printf("deleted %s (too old)\n", key);
+      continue;
+      }
 
     /* Loop for renamed continuation records. For each message id,
     check to see if the message exists, and if not, remove its entry
diff --git a/src/src/macros.h b/src/src/macros.h
index 2fe364ed4..f78ae2e3d 100644
--- a/src/src/macros.h
+++ b/src/src/macros.h
@@ -191,9 +191,10 @@ message id with a trailing "-H" or "-D" added. */
 #define SPOOL_NAME_LENGTH (MESSAGE_ID_LENGTH+2)
 
 /* The maximum number of message ids to store in a waiting database
-record. */
+record, and the max number of continuation records allowed. */
 
 #define WAIT_NAME_MAX 50
+#define WAIT_CONT_MAX 1000
 
 /* Wait this long before determining that a Proxy Protocol configured
 host isn't speaking the protocol, and so is disallowed. Can be moved to

commit d2e88d0371ae8f8eebd23f56684e65f1146fcb90
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Tue Jan 12 15:36:09 2021 +0000

    Auths: in plaintext authenticator, fix parsing of consecutive circuflex.  Bug 2687
    
    (cherry picked from commit ca22cc0abe93c28f3d296d99c239413bb0d079c4)

diff --git a/src/src/auths/get_data.c b/src/src/auths/get_data.c
index 602a1181a..88359658a 100644
--- a/src/src/auths/get_data.c
+++ b/src/src/auths/get_data.c
@@ -168,14 +168,20 @@ if (!ss)
 len = Ustrlen(ss);
 
 /* The character ^ is used as an escape for a binary zero character, which is
-needed for the PLAIN mechanism. It must be doubled if really needed. */
+needed for the PLAIN mechanism. It must be doubled if really needed.
+
+The parsing ambiguity of ^^^ is taken as ^^ -> ^ ; ^ -> NUL - and there is
+no way to get a leading ^ after a NUL.  We would need to intro new syntax to
+support that (probably preferring to take a more-standard exim list as a source
+and concat the elements with intervening NULs.  Either a magic marker on the
+source string for client_send, or a new option). */
 
 for (int i = 0; i < len; i++)
   if (ss[i] == '^')
     if (ss[i+1] != '^')
       ss[i] = 0;
     else
-      if (--len > ++i) memmove(ss + i, ss + i + 1, len - i);
+      if (--len > i+1) memmove(ss + i + 1, ss + i + 2, len - i);
 
 /* The first string is attached to the AUTH command; others are sent
 unembellished. */
