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;
 }
