commit 51894f20e0c03113b4c4e07898ac5e955b21ec41
Merge: caba96329 f70940c94
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sat Nov 4 14:20:45 2023 +0000

    Merge branch '4.next'

diff --git a/src/src/acl.c b/src/src/acl.c
index 578cf32cb..cdf60bbb8 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -203,7 +203,14 @@ static condition_def conditions[] = {
   [ACLC_DELAY] =		{ US"delay",		TRUE, TRUE, ACL_BIT_NOTQUIT },
 #ifndef DISABLE_DKIM
   [ACLC_DKIM_SIGNER] =		{ US"dkim_signers",	TRUE, FALSE, (unsigned int) ~ACL_BIT_DKIM },
-  [ACLC_DKIM_STATUS] =		{ US"dkim_status",	TRUE, FALSE, (unsigned int) ~ACL_BIT_DKIM },
+  [ACLC_DKIM_STATUS] =		{ US"dkim_status",	TRUE, FALSE,
+				  (unsigned int)
+				  ~(ACL_BIT_DKIM | ACL_BIT_DATA | ACL_BIT_MIME
+# ifndef DISABLE_PRDR
+				  | ACL_BIT_PRDR
+# endif
+      ),
+  },
 #endif
 #ifdef SUPPORT_DMARC
   [ACLC_DMARC_STATUS] =		{ US"dmarc_status",	TRUE, FALSE, (unsigned int) ~ACL_BIT_DATA },
@@ -3764,8 +3771,14 @@ for (; cb; cb = cb->next)
       break;
 
     case ACLC_DKIM_STATUS:
-      rc = match_isinlist(dkim_verify_status,
-			  &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
+      {		/* return good for any match */
+      const uschar * s = dkim_verify_status ? dkim_verify_status : US"none";
+      int sep = 0;
+      for (uschar * ss; ss = string_nextinlist(&s, &sep, NULL, 0); )
+	if (   (rc = match_isinlist(ss, &arg,
+				    0, NULL, NULL, MCL_STRING, TRUE, NULL))
+	    == OK) break;
+      }
       break;
 #endif
 
diff --git a/src/src/auths/get_data.c b/src/src/auths/get_data.c
index 4c67f17e2..0c9fb37fe 100644
--- a/src/src/auths/get_data.c
+++ b/src/src/auths/get_data.c
@@ -77,7 +77,7 @@ auth_get_data(uschar ** aptr, const uschar * challenge, int challen)
 {
 int c;
 int p = 0;
-smtp_printf("334 %s\r\n", FALSE, b64encode(challenge, challen));
+smtp_printf("334 %s\r\n", SP_NO_MORE, b64encode(challenge, challen));
 while ((c = receive_getc(GETC_BUFFER_UNLIMITED)) != '\n' && c != EOF)
   {
   if (p >= big_buffer_size - 1) return BAD64;
diff --git a/src/src/auths/get_no64_data.c b/src/src/auths/get_no64_data.c
index e2cadfbc6..ae11ae5dc 100644
--- a/src/src/auths/get_no64_data.c
+++ b/src/src/auths/get_no64_data.c
@@ -32,7 +32,7 @@ auth_get_no64_data(uschar **aptr, uschar *challenge)
 {
 int c;
 int p = 0;
-smtp_printf("334 %s\r\n", FALSE, challenge);
+smtp_printf("334 %s\r\n", SP_NO_MORE, challenge);
 while ((c = receive_getc(GETC_BUFFER_UNLIMITED)) != '\n' && c != EOF)
   {
   if (p >= big_buffer_size - 1) return BAD64;
diff --git a/src/src/daemon.c b/src/src/daemon.c
index 9709845e5..f2183c735 100644
--- a/src/src/daemon.c
+++ b/src/src/daemon.c
@@ -128,7 +128,7 @@ never_error(uschar *log_msg, uschar *smtp_msg, int was_errno)
 uschar *emsg = was_errno <= 0
   ? US"" : string_sprintf(": %s", strerror(was_errno));
 log_write(0, LOG_MAIN|LOG_PANIC, "%s%s", log_msg, emsg);
-if (smtp_out) smtp_printf("421 %s\r\n", FALSE, smtp_msg);
+if (smtp_out) smtp_printf("421 %s\r\n", SP_NO_MORE, smtp_msg);
 }
 
 
@@ -233,7 +233,7 @@ if (getsockname(accept_socket, (struct sockaddr *)(&interface_sockaddr),
   {
   log_write(0, LOG_MAIN | ((errno == ECONNRESET)? 0 : LOG_PANIC),
     "getsockname() failed: %s", strerror(errno));
-  smtp_printf("421 Local problem: getsockname() failed; please try again later\r\n", FALSE);
+  smtp_printf("421 Local problem: getsockname() failed; please try again later\r\n", SP_NO_MORE);
   goto ERROR_RETURN;
   }
 
@@ -263,7 +263,7 @@ if (smtp_accept_max > 0 && smtp_accept_count >= smtp_accept_max)
   DEBUG(D_any) debug_printf("rejecting SMTP connection: count=%d max=%d\n",
     smtp_accept_count, smtp_accept_max);
   smtp_printf("421 Too many concurrent SMTP connections; "
-    "please try again later.\r\n", FALSE);
+    "please try again later.\r\n", SP_NO_MORE);
   log_write(L_connection_reject,
             LOG_MAIN, "Connection from %Y refused: too many connections",
     whofrom);
@@ -282,7 +282,7 @@ if (smtp_load_reserve >= 0)
     {
     DEBUG(D_any) debug_printf("rejecting SMTP connection: load average = %.2f\n",
       (double)load_average/1000.0);
-    smtp_printf("421 Too much load; please try again later.\r\n", FALSE);
+    smtp_printf("421 Too much load; please try again later.\r\n", SP_NO_MORE);
     log_write(L_connection_reject,
               LOG_MAIN, "Connection from %Y refused: load average = %.2f",
       whofrom, (double)load_average/1000.0);
@@ -351,7 +351,7 @@ if (max_for_this_host > 0 && smtp_accept_count >= max_for_this_host)
       "IP address: count=%d max=%d\n",
       host_accept_count, max_for_this_host);
     smtp_printf("421 Too many concurrent SMTP connections "
-      "from this IP address; please try again later.\r\n", FALSE);
+      "from this IP address; please try again later.\r\n", SP_NO_MORE);
     log_write(L_connection_reject,
               LOG_MAIN, "Connection from %Y refused: too many connections "
       "from that IP address", whofrom);
@@ -445,7 +445,7 @@ if (pid == 0)
           "(smtp_active_hostname): %s", raw_active_hostname,
           expand_string_message);
         smtp_printf("421 Local configuration error; "
-          "please try again later.\r\n", FALSE);
+          "please try again later.\r\n", SP_NO_MORE);
         mac_smtp_fflush();
         search_tidyup();
         exim_underbar_exit(EXIT_FAILURE);
diff --git a/src/src/macros.h b/src/src/macros.h
index 2658b9ab7..969393561 100644
--- a/src/src/macros.h
+++ b/src/src/macros.h
@@ -1159,4 +1159,12 @@ typedef unsigned mcs_flags;
 #define TSUC_ALLOW_TAINTED_ARGS	BIT(1)
 #define TSUC_ALLOW_RECIPIENTS	BIT(2)
 
+/* Flags for smtp_printf */
+#define SP_MORE		TRUE
+#define SP_NO_MORE	FALSE
+
+/* Flags for smtp_respond */
+#define SR_FINAL	TRUE
+#define SR_NOT_FINAL	FALSE
+
 /* End of macros.h */
diff --git a/src/src/pdkim/pdkim.c b/src/src/pdkim/pdkim.c
index 36e559b59..c723ae6c8 100644
--- a/src/src/pdkim/pdkim.c
+++ b/src/src/pdkim/pdkim.c
@@ -1868,9 +1868,9 @@ for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
     if (*dkim_verify_min_keysizes)
       {
       unsigned minbits;
-      uschar * ss = expand_getkeyed(US pdkim_keytypes[sig->keytype],
+      const uschar * ss = expand_getkeyed(US pdkim_keytypes[sig->keytype],
 				    dkim_verify_min_keysizes);
-      if (ss &&  (minbits = atoi(CS ss)) > sig->keybits)
+      if (ss &&  (minbits = atoi(CCS ss)) > sig->keybits)
 	{
 	DEBUG(D_acl) debug_printf("Key too short: Actual: %s %u  Minima '%s'\n",
 	  pdkim_keytypes[sig->keytype], sig->keybits, dkim_verify_min_keysizes);
diff --git a/src/src/receive.c b/src/src/receive.c
index 8a400caf0..8190c5941 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -570,7 +570,7 @@ smtp_user_msg(uschar *code, uschar *user_msg)
 {
 int len = 3;
 smtp_message_code(&code, &len, &user_msg, NULL, TRUE);
-smtp_respond(code, len, TRUE, user_msg);
+smtp_respond(code, len, SR_FINAL, user_msg);
 }
 #endif
 
@@ -1457,7 +1457,7 @@ if (!(mbox_file = spool_mbox(&mbox_size, NULL, &mbox_filename)))
 #ifdef EXPERIMENTAL_DCC
   dcc_ok = 0;
 #endif
-  smtp_respond(US"451", 3, TRUE, US"temporary local problem");
+  smtp_respond(US"451", 3, SR_FINAL, US"temporary local problem");
   message_id[0] = 0;            /* Indicate no message accepted */
   *smtp_reply_ptr = US"";       /* Indicate reply already sent */
   return FALSE;                 /* Indicate skip to end of receive function */
@@ -2309,7 +2309,7 @@ OVERSIZE:
       sender_address,
       sender_fullhost ? " H=" : "", sender_fullhost ? sender_fullhost : US"",
       sender_ident ? " U=" : "",    sender_ident ? sender_ident : US"");
-    smtp_printf("552 Message header not CRLF terminated\r\n", FALSE);
+    smtp_printf("552 Message header not CRLF terminated\r\n", SP_NO_MORE);
     bdat_flush_data();
     smtp_reply = US"";
     goto TIDYUP;                             /* Skip to end of function */
@@ -3251,10 +3251,9 @@ if (!ferror(spool_data_file) && !(receive_feof)() && message_ended != END_DOT)
 	{
 	Uunlink(spool_name);		/* Lose data file when closed */
 	cancel_cutthrough_connection(TRUE, US"sender closed connection");
-	message_id[0] = 0;		/* Indicate no message_accepted */
 	smtp_reply = handle_lost_connection(US"");
 	smtp_yield = FALSE;
-	goto TIDYUP;				/* Skip to end of function */
+	goto NOT_ACCEPTED;				/* Skip to end of function */
 	}
       break;
 
@@ -3597,7 +3596,7 @@ else
       int all_pass = OK;
       int all_fail = FAIL;
 
-      smtp_printf("353 PRDR content analysis beginning\r\n", TRUE);
+      smtp_printf("353 PRDR content analysis beginning\r\n", SP_MORE);
       /* Loop through recipients, responses must be in same order received */
       for (unsigned int c = 0; recipients_count > c; c++)
         {
@@ -3661,7 +3660,7 @@ else
     /* Check the recipients count again, as the MIME ACL might have changed
     them. */
 
-    if (acl_smtp_data != NULL && recipients_count > 0)
+    if (acl_smtp_data && recipients_count > 0)
       {
       rc = acl_check(ACL_WHERE_DATA, NULL, acl_smtp_data, &user_msg, &log_msg);
       add_acl_headers(ACL_WHERE_DATA, US"DATA");
@@ -3922,7 +3921,7 @@ else
   if (smtp_input)
     if (!smtp_batched_input)
       {
-      smtp_respond(smtp_code, 3, TRUE, errmsg);
+      smtp_respond(smtp_code, 3, SR_FINAL, errmsg);
       smtp_reply = US"";            /* Indicate reply already sent */
       goto NOT_ACCEPTED;			/* Skip to end of function */
       }
@@ -4030,11 +4029,6 @@ else
 
 receive_messagecount++;
 
-/* Add data size to written header size. We do not count the initial file name
-that is in the file, but we do add one extra for the notional blank line that
-precedes the data. This total differs from message_size in that it include the
-added Received: header and any other headers that got created locally. */
-
 if (fflush(spool_data_file))
   {
   errmsg = string_sprintf("Spool write error: %s", strerror(errno));
@@ -4054,8 +4048,13 @@ if (fflush(spool_data_file))
     /* Does not return */
     }
   }
-fstat(data_fd, &statbuf);
 
+/* Add data size to written header size. We do not count the initial file name
+that is in the file, but we do add one extra for the notional blank line that
+precedes the data. This total differs from message_size in that it include the
+added Received: header and any other headers that got created locally. */
+
+fstat(data_fd, &statbuf);
 msg_size += statbuf.st_size - spool_data_start_offset(message_id) + 1;
 
 /* Generate a "message received" log entry. We do this by building up a dynamic
@@ -4349,9 +4348,9 @@ if(!smtp_reply)
 #endif
   {
   log_write(0, LOG_MAIN |
-    (LOGGING(received_recipients) ? LOG_RECIPIENTS : 0) |
-    (LOGGING(received_sender) ? LOG_SENDER : 0),
-    "%Y", g);
+		(LOGGING(received_recipients) ? LOG_RECIPIENTS : 0) |
+		(LOGGING(received_sender) ? LOG_SENDER : 0),
+	    "%Y", g);
 
   /* Log any control actions taken by an ACL or local_scan(). */
 
@@ -4389,9 +4388,16 @@ a queue-runner could grab it in the window.
 
 A fflush() was done earlier in the expectation that any write errors on the
 data file will be flushed(!) out thereby. Nevertheless, it is theoretically
-possible for fclose() to fail - but what to do? What has happened to the lock
-if this happens?  We can at least log it; if it is observed on some platform
-then we can think about properly declaring the message not-received. */
+possible for fclose() to fail - and this has been seen on obscure filesystems
+(probably one that delayed the actual media write as long as possible)
+but what to do? What has happened to the lock if this happens?
+It's a mes because we already logged the acceptance.
+We can at least log the issue, try to remove spoolfiles and respond with
+a temp-reject.  We do not want to close before logging acceptance because
+we want to hold the lock until we know that logging worked.
+Could we make this less likely by doing an fdatasync() just after the fflush()?
+That seems like a good thing on data-security grounds, but how much will it hit
+performance? */
 
 
 goto TIDYUP;
@@ -4404,8 +4410,27 @@ process_info[process_info_len] = 0;			/* Remove message id */
 if (spool_data_file && cutthrough_done == NOT_TRIED)
   {
   if (fclose(spool_data_file))				/* Frees the lock */
-    log_write(0, LOG_MAIN|LOG_PANIC,
-      "spoolfile error on close: %s", strerror(errno));
+    {
+    log_msg = string_sprintf("spoolfile error on close: %s", strerror(errno));
+    log_write(0, LOG_MAIN|LOG_PANIC |
+		  (LOGGING(received_recipients) ? LOG_RECIPIENTS : 0) |
+		  (LOGGING(received_sender) ? LOG_SENDER : 0),
+	      "%s", log_msg);
+    log_write(0, LOG_MAIN |
+		  (LOGGING(received_recipients) ? LOG_RECIPIENTS : 0) |
+		  (LOGGING(received_sender) ? LOG_SENDER : 0),
+	      "rescind the above message-accept");
+
+    Uunlink(spool_name);
+    Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H"));
+    Uunlink(spool_fname(US"msglog", message_subdir, message_id, US""));
+
+    /* Claim a data ACL temp-reject, just to get reject logging and resposponse */
+    smtp_handle_acl_fail(ACL_WHERE_DATA, rc, NULL, log_msg);
+    smtp_reply = US"";		/* Indicate reply already sent */
+
+    message_id[0] = 0;		/* no message accepted */
+    }
   spool_data_file = NULL;
   }
 
@@ -4434,7 +4459,7 @@ if (smtp_input)
       {
       if (fake_response != OK)
         smtp_respond(fake_response == DEFER ? US"450" : US"550",
-	  3, TRUE, fake_response_text);
+	  3, SR_FINAL, fake_response_text);
 
       /* An OK response is required; use "message" text if present. */
 
@@ -4443,7 +4468,7 @@ if (smtp_input)
         uschar *code = US"250";
         int len = 3;
         smtp_message_code(&code, &len, &user_msg, NULL, TRUE);
-        smtp_respond(code, len, TRUE, user_msg);
+        smtp_respond(code, len, SR_FINAL, user_msg);
         }
 
       /* Default OK response */
@@ -4471,10 +4496,10 @@ if (smtp_input)
 
     else if (smtp_reply[0] != 0)
       if (fake_response != OK && smtp_reply[0] == '2')
-        smtp_respond(fake_response == DEFER ? US"450" : US"550", 3, TRUE,
-          fake_response_text);
+        smtp_respond(fake_response == DEFER ? US"450" : US"550",
+		      3, SR_FINAL, fake_response_text);
       else
-        smtp_printf("%.1024s\r\n", FALSE, smtp_reply);
+        smtp_printf("%.1024s\r\n", SP_NO_MORE, smtp_reply);
 
     switch (cutthrough_done)
       {
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index 8cc9f74bf..c565d522d 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -764,7 +764,7 @@ for(;;)
     return EOD;
     }
 
-  smtp_printf("250 %u byte chunk received\r\n", FALSE, chunking_datasize);
+  smtp_printf("250 %u byte chunk received\r\n", SP_NO_MORE, chunking_datasize);
   chunking_state = CHUNKING_OFFERED;
   DEBUG(D_receive) debug_printf("chunking state %d\n", (int)chunking_state);
 
@@ -802,7 +802,7 @@ next_cmd:
 
     case NOOP_CMD:
       HAD(SCH_NOOP);
-      smtp_printf("250 OK\r\n", FALSE);
+      smtp_printf("250 OK\r\n", SP_NO_MORE);
       goto next_cmd;
 
     case BDAT_CMD:
@@ -1298,7 +1298,7 @@ smtp_closedown(uschar * message)
 {
 if (!smtp_in || smtp_batched_input) return;
 receive_swallow_smtp();
-smtp_printf("421 %s\r\n", FALSE, message);
+smtp_printf("421 %s\r\n", SP_NO_MORE, message);
 
 for (;;) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
   {
@@ -1307,16 +1307,16 @@ for (;;) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
 
   case QUIT_CMD:
     f.smtp_in_quit = TRUE;
-    smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
+    smtp_printf("221 %s closing connection\r\n", SP_NO_MORE, smtp_active_hostname);
     mac_smtp_fflush();
     return;
 
   case RSET_CMD:
-    smtp_printf("250 Reset OK\r\n", FALSE);
+    smtp_printf("250 Reset OK\r\n", SP_NO_MORE);
     break;
 
   default:
-    smtp_printf("421 %s\r\n", FALSE, message);
+    smtp_printf("421 %s\r\n", SP_NO_MORE, message);
     break;
   }
 }
@@ -2266,7 +2266,7 @@ if (!f.sender_host_unknown)
         {
         log_write(0, LOG_MAIN, "getsockopt() failed from %s: %s",
           host_and_ident(FALSE), strerror(errno));
-        smtp_printf("451 SMTP service not available\r\n", FALSE);
+        smtp_printf("451 SMTP service not available\r\n", SP_NO_MORE);
         return FALSE;
         }
       }
@@ -2370,7 +2370,7 @@ if (!f.sender_host_unknown)
       log_write(0, LOG_MAIN|LOG_REJECT,
         "connection from %s refused (IP options)", host_and_ident(FALSE));
 
-      smtp_printf("554 SMTP service not available\r\n", FALSE);
+      smtp_printf("554 SMTP service not available\r\n", SP_NO_MORE);
       return FALSE;
       }
 
@@ -2425,7 +2425,7 @@ if (!f.sender_host_unknown)
 #ifndef DISABLE_TLS
     if (!tls_in.on_connect)
 #endif
-      smtp_printf("554 SMTP service not available\r\n", FALSE);
+      smtp_printf("554 SMTP service not available\r\n", SP_NO_MORE);
     return FALSE;
     }
 
@@ -2456,7 +2456,7 @@ if (!f.sender_host_unknown)
       log_write(L_connection_reject,
                 LOG_MAIN|LOG_REJECT, "refused connection from %s "
                 "(tcp wrappers)", host_and_ident(FALSE));
-      smtp_printf("554 SMTP service not available\r\n", FALSE);
+      smtp_printf("554 SMTP service not available\r\n", SP_NO_MORE);
       }
     else
       {
@@ -2466,7 +2466,7 @@ if (!f.sender_host_unknown)
       log_write(L_connection_reject,
                 LOG_MAIN|LOG_REJECT, "temporarily refused connection from %s "
                 "(tcp wrappers errno=%d)", host_and_ident(FALSE), save_errno);
-      smtp_printf("451 Temporary local problem - please try later\r\n", FALSE);
+      smtp_printf("451 Temporary local problem - please try later\r\n", SP_NO_MORE);
       }
     return FALSE;
     }
@@ -2486,7 +2486,7 @@ if (!f.sender_host_unknown)
         host_and_ident(FALSE), smtp_accept_count - 1, smtp_accept_max,
         smtp_accept_reserve, (rc == DEFER)? " (lookup deferred)" : "");
       smtp_printf("421 %s: Too many concurrent SMTP connections; "
-        "please try again later\r\n", FALSE, smtp_active_hostname);
+        "please try again later\r\n", SP_NO_MORE, smtp_active_hostname);
       return FALSE;
       }
     reserved_host = TRUE;
@@ -2507,7 +2507,7 @@ if (!f.sender_host_unknown)
       LOG_MAIN, "temporarily refused connection from %s: not in "
       "reserve list and load average = %.2f", host_and_ident(FALSE),
       (double)load_average/1000.0);
-    smtp_printf("421 %s: Too much load; please try again later\r\n", FALSE,
+    smtp_printf("421 %s: Too much load; please try again later\r\n", SP_NO_MORE,
       smtp_active_hostname);
     return FALSE;
     }
@@ -2681,7 +2681,7 @@ if (!check_sync())
       "synchronization error (input sent without waiting for greeting): "
       "rejected connection from %s input=\"%s\"", host_and_ident(TRUE),
       string_printing(string_copyn(smtp_inptr, n)));
-    smtp_printf("554 SMTP synchronization error\r\n", FALSE);
+    smtp_printf("554 SMTP synchronization error\r\n", SP_NO_MORE);
     return FALSE;
     }
 
@@ -2692,7 +2692,7 @@ smtp_printf("%Y",
 #ifndef DISABLE_PIPE_CONNECT
   fl.pipe_connect_acceptable && pipeline_connect_sends(),
 #else
-  FALSE,
+  SP_NO_MORE,
 #endif
   ss);
 
@@ -2751,10 +2751,10 @@ if (++synprot_error_count > smtp_max_synprot_errors)
 
 if (code > 0)
   {
-  smtp_printf("%d%c%s%s%s\r\n", FALSE, code, yield == 1 ? '-' : ' ',
+  smtp_printf("%d%c%s%s%s\r\n", SP_NO_MORE, code, yield == 1 ? '-' : ' ',
     data ? data : US"", data ? US": " : US"", errmess);
   if (yield == 1)
-    smtp_printf("%d Too many syntax or protocol errors\r\n", FALSE, code);
+    smtp_printf("%d Too many syntax or protocol errors\r\n", SP_NO_MORE, code);
   }
 
 return yield;
@@ -2781,7 +2781,7 @@ Returns:        nothing
 */
 
 void
-smtp_respond(uschar* code, int codelen, BOOL final, uschar *msg)
+smtp_respond(uschar * code, int codelen, BOOL final, uschar * msg)
 {
 int esclen = 0;
 uschar *esc = US"";
@@ -2830,7 +2830,7 @@ for (;;)
     }
   else
     {
-    smtp_printf("%.3s-%.*s%.*s\r\n", TRUE, code, esclen, esc, (int)(nl - msg), msg);
+    smtp_printf("%.3s-%.*s%.*s\r\n", SP_MORE, code, esclen, esc, (int)(nl - msg), msg);
     msg = nl + 1;
     Uskip_whitespace(&msg);
     }
@@ -2936,7 +2936,7 @@ Returns:     0 in most cases
 */
 
 int
-smtp_handle_acl_fail(int where, int rc, uschar *user_msg, uschar *log_msg)
+smtp_handle_acl_fail(int where, int rc, uschar * user_msg, uschar * log_msg)
 {
 BOOL drop = rc == FAIL_DROP;
 int codelen = 3;
@@ -3022,7 +3022,7 @@ if (sender_verified_failed &&
       string_sprintf(": %s", sender_verified_failed->message));
 
   if (rc == FAIL && sender_verified_failed->user_message)
-    smtp_respond(smtp_code, codelen, FALSE, string_sprintf(
+    smtp_respond(smtp_code, codelen, SR_NOT_FINAL, string_sprintf(
         testflag(sender_verified_failed, af_verify_pmfail)?
           "Postmaster verification failed while checking <%s>\n%s\n"
           "Several RFCs state that you are required to have a postmaster\n"
@@ -3054,7 +3054,7 @@ always a 5xx one - see comments at the start of this function. If the original
 rc was FAIL_DROP we drop the connection and yield 2. */
 
 if (rc == FAIL)
-  smtp_respond(smtp_code, codelen, TRUE,
+  smtp_respond(smtp_code, codelen, SR_FINAL,
     user_msg ? user_msg : US"Administrative prohibition");
 
 /* Send temporary failure response to the command. Don't give any details,
@@ -3072,12 +3072,12 @@ else
        && sender_verified_failed
        && sender_verified_failed->message
        )
-      smtp_respond(smtp_code, codelen, FALSE, sender_verified_failed->message);
+      smtp_respond(smtp_code, codelen, SR_NOT_FINAL, sender_verified_failed->message);
 
-    smtp_respond(smtp_code, codelen, TRUE, user_msg);
+    smtp_respond(smtp_code, codelen, SR_FINAL, user_msg);
     }
   else
-    smtp_respond(smtp_code, codelen, TRUE,
+    smtp_respond(smtp_code, codelen, SR_FINAL,
       US"Temporary local problem - please try later");
 
 /* Log the incident to the logs that are specified by log_reject_target
@@ -3194,7 +3194,7 @@ responses are all internal, they should be reasonable size. */
 if (code && defaultrespond)
   {
   if (user_msg)
-    smtp_respond(code, 3, TRUE, user_msg);
+    smtp_respond(code, 3, SR_FINAL, user_msg);
   else
     {
     gstring * g;
@@ -3203,7 +3203,7 @@ if (code && defaultrespond)
     va_start(ap, defaultrespond);
     g = string_vformat(NULL, SVFMT_EXTEND|SVFMT_REBUFFER, CS defaultrespond, ap);
     va_end(ap);
-    smtp_printf("%s %Y\r\n", FALSE, code, g);
+    smtp_printf("%s %Y\r\n", SP_NO_MORE, code, g);
     }
   mac_smtp_fflush();
   }
@@ -3360,7 +3360,7 @@ smtp_user_msg(uschar *code, uschar *user_msg)
 {
 int len = 3;
 smtp_message_code(&code, &len, &user_msg, NULL, TRUE);
-smtp_respond(code, len, TRUE, user_msg);
+smtp_respond(code, len, SR_FINAL, user_msg);
 }
 
 
@@ -3495,7 +3495,7 @@ if (f.allow_unqualified_recipient || strcmpic(*recipient, US"postmaster") == 0)
   *recipient = US rewrite_address_qualify(*recipient, TRUE);
   return rd;
   }
-smtp_printf("501 %s: recipient address must contain a domain\r\n", FALSE,
+smtp_printf("501 %s: recipient address must contain a domain\r\n", SP_NO_MORE,
   smtp_cmd_data);
 log_write(L_smtp_syntax_error,
   LOG_MAIN|LOG_REJECT, "unqualified %s rejected: <%s> %s%s",
@@ -3523,9 +3523,9 @@ if (  acl_smtp_quit
 #endif
 
 if (*user_msgp)
-  smtp_respond(US"221", 3, TRUE, *user_msgp);
+  smtp_respond(US"221", 3, SR_FINAL, *user_msgp);
 else
-  smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
+  smtp_printf("221 %s closing connection\r\n", SP_NO_MORE, smtp_active_hostname);
 
 #ifdef SERVERSIDE_CLOSE_NOWAIT
 # ifndef DISABLE_TLS
@@ -3554,7 +3554,7 @@ smtp_rset_handler(void)
 {
 HAD(SCH_RSET);
 incomplete_transaction_log(US"RSET");
-smtp_printf("250 Reset OK\r\n", FALSE);
+smtp_printf("250 Reset OK\r\n", SP_NO_MORE);
 cmd_list[CL_RSET].is_mail_cmd = FALSE;
 if (chunking_state > CHUNKING_OFFERED)
   chunking_state = CHUNKING_OFFERED;
@@ -3809,7 +3809,7 @@ while (done <= 0)
 	  {
 	  int rc = smtp_in_auth(au, &smtp_resp, &errmsg);
 
-	  smtp_printf("%s\r\n", FALSE, smtp_resp);
+	  smtp_printf("%s\r\n", SP_NO_MORE, smtp_resp);
 	  if (rc != OK)
 	    {
 	    uschar * logmsg = NULL;
@@ -3870,7 +3870,7 @@ while (done <= 0)
 
       if (!check_helo(smtp_cmd_data))
 	{
-	smtp_printf("501 Syntactically invalid %s argument(s)\r\n", FALSE, hello);
+	smtp_printf("501 Syntactically invalid %s argument(s)\r\n", SP_NO_MORE, hello);
 
 	log_write(0, LOG_MAIN|LOG_REJECT, "rejected %s from %s: syntactically "
 	  "invalid argument(s): %s", hello, host_and_ident(FALSE),
@@ -3935,7 +3935,7 @@ while (done <= 0)
 	    {
 	    if (fl.helo_verify_required)
 	      {
-	      smtp_printf("%d %s argument does not match calling host\r\n", FALSE,
+	      smtp_printf("%d %s argument does not match calling host\r\n", SP_NO_MORE,
 		tempfail? 451 : 550, hello);
 	      log_write(0, LOG_MAIN|LOG_REJECT, "%srejected \"%s %s\" from %s",
 		tempfail? "temporarily " : "",
@@ -4288,7 +4288,7 @@ while (done <= 0)
 	  done = synprot_error(L_smtp_syntax_error, resp, NULL, errmsg);
 	else
 	  {
-	  smtp_printf("%d %s\r\n", FALSE, resp, errmsg);
+	  smtp_printf("%d %s\r\n", SP_NO_MORE, resp, errmsg);
 	  log_write(0, LOG_MAIN|LOG_REJECT, "rejected XCLIENT from %s: %s",
 	    host_and_ident(FALSE), errmsg);
 	  }
@@ -4302,7 +4302,7 @@ while (done <= 0)
 	We require that we do; the following HELO/EHLO handling will set
 	sender_helo_name as normal. */
 
-	smtp_printf("%s XCLIENT success\r\n", FALSE, smtp_code);
+	smtp_printf("%s XCLIENT success\r\n", SP_NO_MORE, smtp_code);
 	}
       break; /* XCLIENT */
       }
@@ -4326,7 +4326,7 @@ while (done <= 0)
 	if (  fl.helo_verify_required
 	   || verify_check_host(&hosts_require_helo) == OK)
 	  {
-	  smtp_printf("503 HELO or EHLO required\r\n", FALSE);
+	  smtp_printf("503 HELO or EHLO required\r\n", SP_NO_MORE);
 	  log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL from %s: no "
 	    "HELO/EHLO given", host_and_ident(FALSE));
 	  break;
@@ -4353,7 +4353,7 @@ while (done <= 0)
 
       if (smtp_mailcmd_max > 0 && smtp_mailcmd_count > smtp_mailcmd_max)
 	{
-	smtp_printf("421 too many messages in this connection\r\n", FALSE);
+	smtp_printf("421 too many messages in this connection\r\n", SP_NO_MORE);
 	log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL command %s: too many "
 	  "messages in one connection", host_and_ident(TRUE));
 	break;
@@ -4626,7 +4626,7 @@ while (done <= 0)
 
       if (thismessage_size_limit > 0 && message_size > thismessage_size_limit)
 	{
-	smtp_printf("552 Message size exceeds maximum permitted\r\n", FALSE);
+	smtp_printf("552 Message size exceeds maximum permitted\r\n", SP_NO_MORE);
 	log_write(L_size_reject,
 	    LOG_MAIN|LOG_REJECT, "rejected MAIL FROM:<%s> %s: "
 	    "message too big: size%s=%d max=%d",
@@ -4651,7 +4651,7 @@ while (done <= 0)
 	   smtp_check_spool_space && message_size >= 0
 	      ? message_size + 5000 : 0))
 	{
-	smtp_printf("452 Space shortage, please try later\r\n", FALSE);
+	smtp_printf("452 Space shortage, please try later\r\n", SP_NO_MORE);
 	sender_address = NULL;
 	break;
 	}
@@ -4673,7 +4673,7 @@ while (done <= 0)
 	  }
 	else
 	  {
-	  smtp_printf("501 %s: sender address must contain a domain\r\n", FALSE,
+	  smtp_printf("501 %s: sender address must contain a domain\r\n", SP_NO_MORE,
 	    smtp_cmd_data);
 	  log_write(L_smtp_syntax_error,
 	    LOG_MAIN|LOG_REJECT,
@@ -4753,7 +4753,7 @@ while (done <= 0)
 	{
 	if (f.smtp_in_pipelining_advertised && last_was_rej_mail)
 	  {
-	  smtp_printf("503 sender not yet given\r\n", FALSE);
+	  smtp_printf("503 sender not yet given\r\n", SP_NO_MORE);
 	  was_rej_mail = TRUE;
 	  }
 	else
@@ -4902,7 +4902,7 @@ while (done <= 0)
 	if (recipients_max_reject)
 	  {
 	  rcpt_fail_count++;
-	  smtp_printf("552 too many recipients\r\n", FALSE);
+	  smtp_printf("552 too many recipients\r\n", SP_NO_MORE);
 	  if (!toomany)
 	    log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: message "
 	      "rejected: sender=<%s> %s", sender_address, host_and_ident(TRUE));
@@ -4910,7 +4910,7 @@ while (done <= 0)
 	else
 	  {
 	  rcpt_defer_count++;
-	  smtp_printf("452 too many recipients\r\n", FALSE);
+	  smtp_printf("452 too many recipients\r\n", SP_NO_MORE);
 	  if (!toomany)
 	    log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: excess "
 	      "temporarily rejected: sender=<%s> %s", sender_address,
@@ -4976,7 +4976,7 @@ while (done <= 0)
 	if (user_msg)
 	  smtp_user_msg(US"250", user_msg);
 	else
-	  smtp_printf("250 Accepted\r\n", FALSE);
+	  smtp_printf("250 Accepted\r\n", SP_NO_MORE);
 	rcpt_fail_count++;
 	discarded = TRUE;
 	log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> RCPT %s: "
@@ -5063,15 +5063,15 @@ while (done <= 0)
 	  {
 	  uschar *code = US"503";
 	  int len = Ustrlen(rcpt_smtp_response);
-	  smtp_respond(code, 3, FALSE, US"All RCPT commands were rejected with "
+	  smtp_respond(code, 3, SR_NOT_FINAL, US"All RCPT commands were rejected with "
 	    "this error:");
 	  /* Responses from smtp_printf() will have \r\n on the end */
 	  if (len > 2 && rcpt_smtp_response[len-2] == '\r')
 	    rcpt_smtp_response[len-2] = 0;
-	  smtp_respond(code, 3, FALSE, rcpt_smtp_response);
+	  smtp_respond(code, 3, SR_NOT_FINAL, rcpt_smtp_response);
 	  }
 	if (f.smtp_in_pipelining_advertised && last_was_rcpt)
-	  smtp_printf("503 Valid RCPT command must precede %s\r\n", FALSE,
+	  smtp_printf("503 Valid RCPT command must precede %s\r\n", SP_NO_MORE,
 	    smtp_names[smtp_connection_had[SMTP_HBUFF_PREV(smtp_ch_index)]]);
 	else
 	  done = synprot_error(L_smtp_protocol_error, 503, NULL,
@@ -5091,7 +5091,7 @@ while (done <= 0)
 	{
 	sender_address = NULL;  /* This will allow a new MAIL without RSET */
 	sender_address_unrewritten = NULL;
-	smtp_printf("554 Too many recipients\r\n", FALSE);
+	smtp_printf("554 Too many recipients\r\n", SP_NO_MORE);
 
 	if (chunking_state > CHUNKING_OFFERED)
 	  {
@@ -5132,7 +5132,7 @@ while (done <= 0)
 	  smtp_user_msg(US"354", user_msg);
 	else
 	  smtp_printf(
-	    "354 Enter message, ending with \".\" on a line by itself\r\n", FALSE);
+	    "354 Enter message, ending with \".\" on a line by itself\r\n", SP_NO_MORE);
 	}
 
       if (f.bdat_readers_wanted)
@@ -5158,7 +5158,7 @@ while (done <= 0)
       if (!(address = parse_extract_address(smtp_cmd_data, &errmess,
             &start, &end, &recipient_domain, FALSE)))
 	{
-	smtp_printf("501 %s\r\n", FALSE, errmess);
+	smtp_printf("501 %s\r\n", SP_NO_MORE, errmess);
 	break;
 	}
 
@@ -5197,7 +5197,7 @@ while (done <= 0)
 	    break;
 	  }
 
-	smtp_printf("%s\r\n", FALSE, s);
+	smtp_printf("%s\r\n", SP_NO_MORE, s);
 	}
       break;
       }
@@ -5325,7 +5325,7 @@ while (done <= 0)
 
       if (rc == DEFER)
 	{
-	smtp_printf("454 TLS currently unavailable\r\n", FALSE);
+	smtp_printf("454 TLS currently unavailable\r\n", SP_NO_MORE);
 	break;
 	}
 
@@ -5358,15 +5358,15 @@ while (done <= 0)
 	      log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s",
 		log_msg);
 	  if (user_msg)
-	    smtp_respond(US"221", 3, TRUE, user_msg);
+	    smtp_respond(US"221", 3, SR_FINAL, user_msg);
 	  else
-	    smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
+	    smtp_printf("221 %s closing connection\r\n", SP_NO_MORE, smtp_active_hostname);
 	  log_close_event(US"by QUIT");
 	  done = 2;
 	  break;
 
 	default:
-	  smtp_printf("554 Security failure\r\n", FALSE);
+	  smtp_printf("554 Security failure\r\n", SP_NO_MORE);
 	  break;
 	}
       tls_close(NULL, TLS_SHUTDOWN_NOWAIT);
@@ -5394,7 +5394,7 @@ while (done <= 0)
 
     case NOOP_CMD:
       HAD(SCH_NOOP);
-      smtp_printf("250 OK\r\n", FALSE);
+      smtp_printf("250 OK\r\n", SP_NO_MORE);
       break;
 
 
@@ -5405,23 +5405,23 @@ while (done <= 0)
 
     case HELP_CMD:
       HAD(SCH_HELP);
-      smtp_printf("214-Commands supported:\r\n214", TRUE);
-      smtp_printf(" AUTH", TRUE);
+      smtp_printf("214-Commands supported:\r\n214", SP_MORE);
+      smtp_printf(" AUTH", SP_MORE);
 #ifndef DISABLE_TLS
       if (tls_in.active.sock < 0 &&
 	  verify_check_host(&tls_advertise_hosts) != FAIL)
-	smtp_printf(" STARTTLS", TRUE);
+	smtp_printf(" STARTTLS", SP_MORE);
 #endif
-      smtp_printf(" HELO EHLO MAIL RCPT DATA BDAT", TRUE);
-      smtp_printf(" NOOP QUIT RSET HELP", TRUE);
-      if (acl_smtp_etrn) smtp_printf(" ETRN", TRUE);
-      if (acl_smtp_expn) smtp_printf(" EXPN", TRUE);
-      if (acl_smtp_vrfy) smtp_printf(" VRFY", TRUE);
+      smtp_printf(" HELO EHLO MAIL RCPT DATA BDAT", SP_MORE);
+      smtp_printf(" NOOP QUIT RSET HELP", SP_MORE);
+      if (acl_smtp_etrn) smtp_printf(" ETRN", SP_MORE);
+      if (acl_smtp_expn) smtp_printf(" EXPN", SP_MORE);
+      if (acl_smtp_vrfy) smtp_printf(" VRFY", SP_MORE);
 #ifdef EXPERIMENTAL_XCLIENT
       if (proxy_session || verify_check_host(&hosts_xclient) != FAIL)
-	smtp_printf(" XCLIENT", TRUE);
+	smtp_printf(" XCLIENT", SP_MORE);
 #endif
-      smtp_printf("\r\n", FALSE);
+      smtp_printf("\r\n", SP_NO_MORE);
       break;
 
 
@@ -5500,7 +5500,7 @@ while (done <= 0)
 	  {
 	  log_write(0, LOG_MAIN|LOG_PANIC, "failed to set up ETRN command: %s",
 	    error);
-	  smtp_printf("458 Internal failure\r\n", FALSE);
+	  smtp_printf("458 Internal failure\r\n", SP_NO_MORE);
 	  break;
 	  }
 	}
@@ -5531,7 +5531,7 @@ while (done <= 0)
 	  debug_printf("ETRN command is: %s\n", etrn_command);
 	  debug_printf("ETRN command execution skipped\n");
 	  }
-	if (user_msg == NULL) smtp_printf("250 OK\r\n", FALSE);
+	if (user_msg == NULL) smtp_printf("250 OK\r\n", SP_NO_MORE);
 	  else smtp_user_msg(US"250", user_msg);
 	break;
 	}
@@ -5542,7 +5542,7 @@ while (done <= 0)
 
       if (smtp_etrn_serialize && !enq_start(etrn_serialize_key, 1))
 	{
-	smtp_printf("458 Already processing %s\r\n", FALSE, smtp_cmd_data);
+	smtp_printf("458 Already processing %s\r\n", SP_NO_MORE, smtp_cmd_data);
 	break;
 	}
 
@@ -5607,12 +5607,12 @@ while (done <= 0)
 	{
 	log_write(0, LOG_MAIN|LOG_PANIC, "fork of process for ETRN failed: %s",
 	  strerror(errno));
-	smtp_printf("458 Unable to fork process\r\n", FALSE);
+	smtp_printf("458 Unable to fork process\r\n", SP_NO_MORE);
 	if (smtp_etrn_serialize) enq_end(etrn_serialize_key);
 	}
       else
 	if (!user_msg)
-	  smtp_printf("250 OK\r\n", FALSE);
+	  smtp_printf("250 OK\r\n", SP_NO_MORE);
 	else
 	  smtp_user_msg(US"250", user_msg);
 
@@ -5632,7 +5632,7 @@ while (done <= 0)
       done = synprot_error(L_smtp_syntax_error, 0, NULL,       /* Just logs */
 	US"NUL character(s) present (shown as '?')");
       smtp_printf("501 NUL characters are not allowed in SMTP commands\r\n",
-		  FALSE);
+		  SP_NO_MORE);
       break;
 
 
@@ -5669,7 +5669,7 @@ while (done <= 0)
 
 #ifdef SUPPORT_PROXY
     case PROXY_FAIL_IGNORE_CMD:
-      smtp_printf("503 Command refused, required Proxy negotiation failed\r\n", FALSE);
+      smtp_printf("503 Command refused, required Proxy negotiation failed\r\n", SP_NO_MORE);
       break;
 #endif
 
diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c
index efa6004a3..a17597e8b 100644
--- a/src/src/tls-gnu.c
+++ b/src/src/tls-gnu.c
@@ -3000,7 +3000,7 @@ exim_gnutls_state_st * state = NULL;
 if (tls_in.active.sock >= 0)
   {
   tls_error(US"STARTTLS received after TLS started", US "", NULL, errstr);
-  smtp_printf("554 Already in TLS\r\n", FALSE);
+  smtp_printf("554 Already in TLS\r\n", SP_NO_MORE);
   return FAIL;
   }
 
@@ -3079,7 +3079,7 @@ mode, the fflush() happens when smtp_getc() is called. */
 
 if (!state->tlsp->on_connect)
   {
-  smtp_printf("220 TLS go ahead\r\n", FALSE);
+  smtp_printf("220 TLS go ahead\r\n", SP_NO_MORE);
   fflush(smtp_out);
   }
 
diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c
index b02605eae..ef11de593 100644
--- a/src/src/tls-openssl.c
+++ b/src/src/tls-openssl.c
@@ -3504,7 +3504,7 @@ static uschar peerdn[256];
 if (tls_in.active.sock >= 0)
   {
   tls_error(US"STARTTLS received after TLS started", NULL, US"", errstr);
-  smtp_printf("554 Already in TLS\r\n", FALSE);
+  smtp_printf("554 Already in TLS\r\n", SP_NO_MORE);
   return FAIL;
   }
 
@@ -3624,7 +3624,7 @@ mode, the fflush() happens when smtp_getc() is called. */
 SSL_set_session_id_context(ssl, sid_ctx, Ustrlen(sid_ctx));
 if (!tls_in.on_connect)
   {
-  smtp_printf("220 TLS go ahead\r\n", FALSE);
+  smtp_printf("220 TLS go ahead\r\n", SP_NO_MORE);
   fflush(smtp_out);
   }
 

commit 44e35f5f592b4fb993a6a23ad9e0446af8a6d527
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sun Nov 5 21:29:53 2023 +0000

    DANE: handle servefail for TLSA during Rverify.  Bug 3030

diff --git a/src/src/verify.c b/src/src/verify.c
index d8ebf5925..6361aa434 100644
--- a/src/src/verify.c
+++ b/src/src/verify.c
@@ -677,7 +677,7 @@ coding means skipping this whole loop and doing the append separately.  */
     sx->conn_args.interface = interface;
     sx->helo_data = tf->helo_data;
     sx->conn_args.tblock = addr->transport;
-    sx->conn_args.sock = -1;
+    sx->cctx.sock = sx->conn_args.sock = -1;
     sx->verify = TRUE;
 
 tls_retry_connection:
@@ -1152,7 +1152,7 @@ no_conn:
       /* Ensure no cutthrough on multiple verifies that were incompatible */
       if (options & vopt_callout_recipsender)
         cancel_cutthrough_connection(TRUE, US"not usable for cutthrough");
-      if (sx->send_quit)
+      if (sx->send_quit && sx->cctx.sock >= 0)
 	if (smtp_write_command(sx, SCMD_FLUSH, "QUIT\r\n") != -1)
 	  /* Wait a short time for response, and discard it */
 	  smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2', 1);

commit 37b849dca4dfd855212a763662825e967a4d77b1
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Tue Nov 7 15:02:18 2023 +0000

    OpenSSL: fix non-DANE build

diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c
index ef11de593..851ac77c5 100644
--- a/src/src/tls-openssl.c
+++ b/src/src/tls-openssl.c
@@ -2605,7 +2605,7 @@ if (!(bs = OCSP_response_get1_basic(rsp)))
     asking for certificate-status under DANE, so this callback won't run for
     that combination. It still will for non-DANE. */
 
-#ifdef EXIM_HAVE_OPENSSL_OCSP_RESP_GET0_SIGNER
+#if defined(EXIM_HAVE_OPENSSL_OCSP_RESP_GET0_SIGNER) && defined(SUPPORT_DANE)
     X509 * signer;
 
     if (  tls_out.dane_verified

commit eb09d0da1fb995ad88f9a1c2998766c3e530be68
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Tue Nov 7 19:38:22 2023 +0000

    Fix use of empty log_reject_target.  Bug 3039
    
    Broken-by: 4243a209fd94

diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index c565d522d..e19c86ff8 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -2051,16 +2051,19 @@ else DEBUG(D_receive)
 static void
 log_connect_tls_drop(const uschar * what, const uschar * log_msg)
 {
-gstring * g = s_tlslog(NULL);
-uschar * tls = string_from_gstring(g);
-
-log_write(L_connection_reject,
-  log_reject_target, "%s%s%s dropped by %s%s%s",
-  LOGGING(dnssec) && sender_host_dnssec ? US" DS" : US"",
-  host_and_ident(TRUE),
-  tls ? tls : US"",
-  what,
-  log_msg ? US": " : US"", log_msg);
+if (log_reject_target)
+  {
+  gstring * g = s_tlslog(NULL);
+  uschar * tls = string_from_gstring(g);
+
+  log_write(L_connection_reject,
+    log_reject_target, "%s%s%s dropped by %s%s%s",
+    LOGGING(dnssec) && sender_host_dnssec ? US" DS" : US"",
+    host_and_ident(TRUE),
+    tls ? tls : US"",
+    what,
+    log_msg ? US": " : US"", log_msg);
+  }
 }
 
 
@@ -3085,7 +3088,7 @@ else
 the connection is not forcibly to be dropped, return 0. Otherwise, log why it
 is closing if required and return 2.  */
 
-if (log_reject_target != 0)
+if (log_reject_target)
   {
 #ifndef DISABLE_TLS
   gstring * g = s_tlslog(NULL);

commit caf57fe7eb5018b8df196e6d9f99586232798eb3
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Wed Nov 8 14:22:37 2023 +0000

    typoes

diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c
index 851ac77c5..237303ba9 100644
--- a/src/src/tls-openssl.c
+++ b/src/src/tls-openssl.c
@@ -77,9 +77,9 @@ change this guard and punt the issue for a while longer. */
 #  define EXIM_HAVE_OPENSSL_KEYLOG
 #  define EXIM_HAVE_OPENSSL_CIPHER_GET_ID
 #  define EXIM_HAVE_SESSION_TICKET
-#  define EXIM_HAVE_OPESSL_TRACE
-#  define EXIM_HAVE_OPESSL_GET0_SERIAL
-#  define EXIM_HAVE_OPESSL_OCSP_RESP_GET0_CERTS
+#  define EXIM_HAVE_OPENSSL_TRACE
+#  define EXIM_HAVE_OPENSSL_GET0_SERIAL
+#  define EXIM_HAVE_OPENSSL_OCSP_RESP_GET0_CERTS
 #  define EXIM_HAVE_SSL_GET0_VERIFIED_CHAIN
 #  ifndef DISABLE_OCSP
 #   define EXIM_HAVE_OCSP
@@ -1756,7 +1756,7 @@ level. */
 DEBUG(D_tls)
   {
   SSL_CTX_set_info_callback(ctx, info_callback);
-#if defined(EXIM_HAVE_OPESSL_TRACE) && !defined(OPENSSL_NO_SSL_TRACE)
+#if defined(EXIM_HAVE_OPENSSL_TRACE) && !defined(OPENSSL_NO_SSL_TRACE)
   /* this needs a debug build of OpenSSL */
   SSL_CTX_set_msg_callback(ctx, SSL_trace);
 #endif
@@ -2442,7 +2442,7 @@ tls_in.ocsp = OCSP_NOT_RESP;
 if (!olist)
   return SSL_TLSEXT_ERR_NOACK;
 
-#ifdef EXIM_HAVE_OPESSL_GET0_SERIAL
+#ifdef EXIM_HAVE_OPENSSL_GET0_SERIAL
  {
   const X509 * cert_sent = SSL_get_certificate(s);
   const ASN1_INTEGER * cert_serial = X509_get0_serialNumber(cert_sent);
@@ -2646,7 +2646,7 @@ if (!(bs = OCSP_response_get1_basic(rsp)))
 
       debug_printf("certs contained in basicresp:\n");
       x509_stack_dump_cert_s_names(
-#ifdef EXIM_HAVE_OPESSL_OCSP_RESP_GET0_CERTS
+#ifdef EXIM_HAVE_OPENSSL_OCSP_RESP_GET0_CERTS
 	OCSP_resp_get0_certs(bs)
 #else
 	bs->certs

commit 4f780c09d2ab95bd9562ebe307a01043933adcd9
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Nov 10 14:37:23 2023 +0000

    fix doubled logging of message_id

diff --git a/src/src/receive.c b/src/src/receive.c
index 8190c5941..041898a6b 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -3380,8 +3380,8 @@ if (extract_recip && (bad_addresses || recipients_count == 0))
       }
     }
 
-  log_write(0, LOG_MAIN|LOG_PANIC, "%s %s found in headers",
-    message_id, bad_addresses ? "bad addresses" : "no recipients");
+  log_write(0, LOG_MAIN|LOG_PANIC, "%s found in headers",
+    bad_addresses ? "bad addresses" : "no recipients");
 
   fseek(spool_data_file, (long int)spool_data_start_offset(message_id), SEEK_SET);
 

commit 5d5ad9fb16a2511ff2e0e7d4528d399f06f608da
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Mon Nov 13 18:12:31 2023 +0000

    TLS: fix resumption for TLS-on-connect

diff --git a/src/src/macros.h b/src/src/macros.h
index 969393561..e2c1d0f94 100644
--- a/src/src/macros.h
+++ b/src/src/macros.h
@@ -1103,14 +1103,22 @@ should not be one active. */
 #define RESUME_USED		BIT(4)
 
 #define RESUME_DECODE_STRING \
-	  US"not requested or offered : 0x02 :client requested, no server ticket" \
-    ": 0x04 : 0x05 : 0x06 :client offered session, no server action" \
-    ": 0x08 :no client request: 0x0A :client requested new ticket, server provided" \
-    ": 0x0C :client offered session, not used: 0x0E :client offered session, server only provided new ticket" \
-    ": 0x10 :session resumed unasked: 0x12 :session resumed unasked" \
-    ": 0x14 : 0x15 : 0x16 :session resumed" \
-    ": 0x18 :session resumed unasked: 0x1A :session resumed unasked" \
-    ": 0x1C :session resumed: 0x1E :session resumed, also new ticket"
+  US"not requested or offered" \
+    ": 0x02 :client requested, no server ticket" \
+    ": 0x04 : 0x05 " \
+    ": 0x06 :client offered session, no server action" \
+    ": 0x08 :no client request" \
+    ": 0x0A :client requested new ticket, server provided" \
+    ": 0x0C :client offered session, not used" \
+    ": 0x0E :client offered session, server only provided new ticket" \
+    ": 0x10 :session resumed unasked" \
+    ": 0x12 :session resumed unasked" \
+    ": 0x14 : 0x15" \
+    ": 0x16 :session resumed" \
+    ": 0x18 :session resumed unasked" \
+    ": 0x1A :session resumed unasked" \
+    ": 0x1C :session resumed" \
+    ": 0x1E :session resumed, also new ticket"
 
 /* Flags for string_vformat */
 #define SVFMT_EXTEND		BIT(0)
diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c
index a17597e8b..56ea93935 100644
--- a/src/src/tls-gnu.c
+++ b/src/src/tls-gnu.c
@@ -2851,7 +2851,7 @@ static int
 tls_server_ticket_cb(gnutls_session_t sess, u_int htype, unsigned when,
   unsigned incoming, const gnutls_datum_t * msg)
 {
-DEBUG(D_tls) debug_printf("newticket cb\n");
+DEBUG(D_tls) debug_printf("newticket cb (on server)\n");
 tls_in.resumption |= RESUME_CLIENT_REQUESTED;
 return 0;
 }
@@ -2888,9 +2888,12 @@ tls_server_resume_posthandshake(exim_gnutls_state_st * state)
 {
 if (gnutls_session_resumption_requested(state->session))
   {
-  /* This tells us the client sent a full ticket.  We use a
+  /* This tells us the client sent a full (?) ticket.  We use a
   callback on session-ticket request, elsewhere, to tell
-  if a client asked for a ticket. */
+  if a client asked for a ticket.
+  XXX As of GnuTLS 3.0.1 it seems to be returning true even for
+  a pure ticket-req (a zero-length Session Ticket extension
+  in the Client Hello, for 1.2) which mucks up our logic. */
 
   tls_in.resumption |= RESUME_CLIENT_SUGGESTED;
   DEBUG(D_tls) debug_printf("client requested resumption\n");
@@ -3319,7 +3322,8 @@ tls_retrieve_session(tls_support * tlsp, gnutls_session_t session,
 tlsp->resumption = RESUME_SUPPORTED;
 
 if (!conn_args->have_lbserver)
-  { DEBUG(D_tls) debug_printf("resumption not supported on continued-connection\n"); }
+  { DEBUG(D_tls) debug_printf(
+      "resumption not supported: no LB detection done (continued-conn?)\n"); }
 else if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, conn_args->host) == OK)
   {
   dbdata_tls_session * dt;
@@ -3347,6 +3351,7 @@ else if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, conn_args->host
     dbfn_close(dbm_file);
     }
   }
+else DEBUG(D_tls) debug_printf("no resumption for this host\n");
 }
 
 
@@ -3374,7 +3379,7 @@ if (gnutls_session_get_flags(session) & GNUTLS_SFLAGS_SESSION_TICKET)
       int dlen = sizeof(dbdata_tls_session) + tkt.size;
       dbdata_tls_session * dt = store_get(dlen, GET_TAINTED);
 
-      DEBUG(D_tls) debug_printf("session data size %u\n", (unsigned)tkt.size);
+      DEBUG(D_tls) debug_printf(" session data size %u\n", (unsigned)tkt.size);
       memcpy(dt->session, tkt.data, tkt.size);
       gnutls_free(tkt.data);
 
@@ -3385,11 +3390,15 @@ if (gnutls_session_get_flags(session) & GNUTLS_SFLAGS_SESSION_TICKET)
 	dbfn_close(dbm_file);
 
 	DEBUG(D_tls)
-	  debug_printf("wrote session db (len %u)\n", (unsigned)dlen);
+	  debug_printf(" wrote session db (len %u)\n", (unsigned)dlen);
 	}
       }
-    else DEBUG(D_tls)
-      debug_printf("extract session data: %s\n", US gnutls_strerror(rc));
+    else
+      { DEBUG(D_tls)
+      debug_printf(" extract session data: %s\n", US gnutls_strerror(rc));
+      }
+  else DEBUG(D_tls)
+      debug_printf(" host not resmable; not saving ticket\n");
   }
 }
 
@@ -3406,7 +3415,7 @@ tls_client_ticket_cb(gnutls_session_t sess, u_int htype, unsigned when,
 exim_gnutls_state_st * state = gnutls_session_get_ptr(sess);
 tls_support * tlsp = state->tlsp;
 
-DEBUG(D_tls) debug_printf("newticket cb\n");
+DEBUG(D_tls) debug_printf("newticket cb (on client)\n");
 
 if (!tlsp->ticket_received)
   tls_save_session(tlsp, sess, state->host);
diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index af2e1f2dd..8c00a1ef2 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -203,9 +203,6 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   .tls_tempfail_tryclear =	TRUE,
   .tls_try_verify_hosts =	US"*",
   .tls_verify_cert_hostnames =	US"*",
-# ifndef DISABLE_TLS_RESUME
-  .host_name_extract =		US"${if and {{match{$host}{.outlook.com\\$}} {match{$item}{\\N^250-([\\w.]+)\\s\\N}}} {$1}}",
-# endif
 #endif
 #ifdef SUPPORT_I18N
   .utf8_downconvert =		US"-1",
@@ -352,7 +349,7 @@ Returns:    nothing
 void
 smtp_transport_init(transport_instance *tblock)
 {
-smtp_transport_options_block *ob = SOB tblock->options_block;
+smtp_transport_options_block * ob = SOB tblock->options_block;
 int old_pool = store_pool;
 
 /* Retry_use_local_part defaults FALSE if unset */
@@ -769,7 +766,7 @@ return TRUE;
 resumption when such servers do not share a session-cache */
 
 static void
-ehlo_response_lbserver(smtp_context * sx, smtp_transport_options_block * ob)
+ehlo_response_lbserver(smtp_context * sx, const uschar * name_extract)
 {
 #if !defined(DISABLE_TLS) && !defined(DISABLE_TLS_RESUME)
 const uschar * s;
@@ -778,7 +775,7 @@ uschar * save_item = iterate_item;
 if (sx->conn_args.have_lbserver)
   return;
 iterate_item = sx->buffer;
-s = expand_cstring(ob->host_name_extract);
+s = expand_cstring(name_extract);
 iterate_item = save_item;
 sx->conn_args.host_lbserver = s && !*s ? NULL : s;
 sx->conn_args.have_lbserver = TRUE;
@@ -1067,6 +1064,8 @@ sx->pending_EHLO = FALSE;
 
 if (pending_BANNER)
   {
+  const uschar * s;
+
   DEBUG(D_transport) debug_printf("%s expect banner\n", __FUNCTION__);
   (*countp)--;
   if (!smtp_reap_banner(sx))
@@ -1076,7 +1075,10 @@ if (pending_BANNER)
     goto fail;
     }
   /*XXX EXPERIMENTAL_ESMTP_LIMITS ? */
-  ehlo_response_lbserver(sx, sx->conn_args.ob);
+
+  s = ((smtp_transport_options_block *)sx->conn_args.ob)->host_name_extract;
+  if (!s) s = HNE_DEFAULT;
+  ehlo_response_lbserver(sx, s);
   }
 
 if (pending_EHLO)
@@ -2474,10 +2476,20 @@ goto SEND_QUIT;
 #ifndef DISABLE_TLS
   if (sx->smtps)
     {
+    const uschar * s;
+
     smtp_peer_options |= OPTION_TLS;
     suppress_tls = FALSE;
     ob->tls_tempfail_tryclear = FALSE;
     smtp_command = US"SSL-on-connect";
+
+    /* Having no EHLO response yet, cannot peek there for a servername to detect
+    an LB.  Call this anyway, so that a dummy host_name_extract option value can
+    force resumption attempts. */
+
+    if (!(s = ob->host_name_extract)) s = US"never-LB";
+    ehlo_response_lbserver(sx, s);
+
     goto TLS_NEGOTIATE;
     }
 #endif
@@ -2565,6 +2577,8 @@ goto SEND_QUIT;
     if (!sx->early_pipe_active)
 #endif
       {
+      const uschar * s;
+
       sx->peer_offered = ehlo_response(sx->buffer,
 	OPTION_TLS	/* others checked later */
 #ifndef DISABLE_PIPE_CONNECT
@@ -2600,7 +2614,8 @@ goto SEND_QUIT;
 	  }
 	}
 #endif
-      ehlo_response_lbserver(sx, ob);
+      if (!(s = ob->host_name_extract)) s = HNE_DEFAULT;
+      ehlo_response_lbserver(sx, s);
       }
 
   /* Set tls_offered if the response to EHLO specifies support for STARTTLS. */
diff --git a/src/src/transports/smtp.h b/src/src/transports/smtp.h
index cb1d72625..0d15b9626 100644
--- a/src/src/transports/smtp.h
+++ b/src/src/transports/smtp.h
@@ -109,6 +109,7 @@ typedef struct {
   uschar	*tls_privatekey;
   uschar	*tls_require_ciphers;
 # ifndef DISABLE_TLS_RESUME
+#  define HNE_DEFAULT US"${if and {{match{$host}{.outlook.com\\$}} {match{$item}{\\N^250-([\\w.]+)\\s\\N}}} {$1}}"
   uschar	*host_name_extract;
   uschar	*tls_resumption_hosts;
 # endif

commit fba5586e6d47f55e024e97681c166e857c4f3d1c
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Tue Nov 14 14:08:36 2023 +0000

    constification

diff --git a/src/src/deliver.c b/src/src/deliver.c
index fa624f9de..9d459167e 100644
--- a/src/src/deliver.c
+++ b/src/src/deliver.c
@@ -6388,7 +6388,7 @@ Returns:      When the global variable mua_wrapper is FALSE:
 */
 
 int
-deliver_message(uschar * id, BOOL forced, BOOL give_up)
+deliver_message(const uschar * id, BOOL forced, BOOL give_up)
 {
 int i, rc;
 int final_yield = DELIVER_ATTEMPTED_NORMAL;
diff --git a/src/src/exim.c b/src/src/exim.c
index 118fe5c7c..3cc2fa2fb 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -1537,7 +1537,7 @@ Returns:        DOES NOT RETURN
 */
 
 static void
-exim_usage(uschar *progname)
+exim_usage(const uschar * progname)
 {
 
 /* Handle specific program invocation variants */
@@ -1744,9 +1744,9 @@ Returns:    EXIT_SUCCESS if terminated successfully
 */
 
 int
-main(int argc, char **cargv)
+main(int argc, char ** cargv)
 {
-uschar **argv = USS cargv;
+const uschar ** argv = CUSS cargv;
 int  arg_receive_timeout = -1;
 int  arg_smtp_receive_timeout = -1;
 int  arg_error_handling = error_handling;
@@ -1795,18 +1795,18 @@ BOOL verify_address_mode = FALSE;
 BOOL verify_as_sender = FALSE;
 BOOL rcpt_verify_quota = FALSE;
 BOOL version_printed = FALSE;
-uschar *alias_arg = NULL;
-uschar *called_as = US"";
-uschar *cmdline_syslog_name = NULL;
-uschar *start_queue_run_id = NULL;
-uschar *stop_queue_run_id = NULL;
-uschar *expansion_test_message = NULL;
+const uschar *alias_arg = NULL;
+const uschar *called_as = US"";
+const uschar *cmdline_syslog_name = NULL;
+const uschar *start_queue_run_id = NULL;
+const uschar *stop_queue_run_id = NULL;
+const uschar *expansion_test_message = NULL;
 const uschar *ftest_domain = NULL;
 const uschar *ftest_localpart = NULL;
 const uschar *ftest_prefix = NULL;
 const uschar *ftest_suffix = NULL;
 uschar *log_oneline = NULL;
-uschar *malware_test_file = NULL;
+const uschar *malware_test_file = NULL;
 uschar *real_sender_address;
 uschar *originator_home = US"/";
 size_t sz;
@@ -2152,8 +2152,8 @@ on the second character (the one after '-'), to save some effort. */
  for (i = 1; i < argc; i++)
   {
   BOOL badarg = FALSE;
-  uschar * arg = argv[i];
-  uschar * argrest;
+  const uschar * arg = argv[i];
+  const uschar * argrest;
   uschar switchchar;
 
   /* An argument not starting with '-' is the start of a recipients list;
@@ -2347,7 +2347,7 @@ on the second character (the one after '-'), to save some effort. */
 	case 'I':
 	  if (Ustrlen(argrest) >= 1 && *argrest == ':')
 	    {
-	    uschar *p = argrest+1;
+	    const uschar * p = argrest+1;
 	    info_flag = CMDINFO_HELP;
 	    if (Ustrlen(p))
 	      if (strcmpic(p, CUS"sieve") == 0)
@@ -2674,9 +2674,9 @@ on the second character (the one after '-'), to save some effort. */
 #else
       {
       int ptr = 0;
-      macro_item *m;
+      macro_item * m;
       uschar name[24];
-      uschar *s = argrest;
+      const uschar * s = argrest;
 
       opt_D_used = TRUE;
       while (isspace(*s)) s++;
@@ -2833,7 +2833,7 @@ on the second character (the one after '-'), to save some effort. */
         *(sender_address = store_get(1, GET_UNTAINTED)) = '\0';  /* Ensure writeable memory */
       else
         {
-        uschar * temp = argrest + Ustrlen(argrest) - 1;
+        const uschar * temp = argrest + Ustrlen(argrest) - 1;
         while (temp >= argrest && isspace(*temp)) temp--;
         if (temp >= argrest && *temp == '.') f_end_dot = TRUE;
         allow_domain_literals = TRUE;
@@ -3272,7 +3272,7 @@ on the second character (the one after '-'), to save some effort. */
       /* -oB: Set a connection message max value for remote deliveries */
       case 'B':
 	{
-	uschar * p = argrest;
+	const uschar * p = argrest;
 	if (!*p)
 	  if (i+1 < argc && isdigit((argv[i+1][0])))
 	    p = argv[++i];
@@ -4432,7 +4432,7 @@ if (bi_option)
   if (bi_command && *bi_command)
     {
     int i = 0;
-    uschar *argv[3];
+    const uschar * argv[3];
     argv[i++] = bi_command;	/* nonexpanded option so assume untainted */
     if (alias_arg) argv[i++] = alias_arg;
     argv[i++] = NULL;
@@ -4761,7 +4761,7 @@ if (rcpt_verify_quota)
     exim_fail("exim: missing recipient for quota check\n");
   else
     {
-    verify_quota(argv[recipients_arg]);
+    verify_quota(US argv[recipients_arg]);	/*XXX we lose track of const here */
     exim_exit(EXIT_SUCCESS);
     }
 
@@ -5768,7 +5768,7 @@ for (BOOL more = TRUE; more; )
     {
     int rcount = 0;
     int count = argc - recipients_arg;
-    uschar **list = argv + recipients_arg;
+    const uschar ** list = argv + recipients_arg;
 
     /* These options cannot be changed dynamically for non-SMTP messages */
 
diff --git a/src/src/filtertest.c b/src/src/filtertest.c
index 2426f7feb..2af5f87c2 100644
--- a/src/src/filtertest.c
+++ b/src/src/filtertest.c
@@ -164,7 +164,7 @@ Returns:      TRUE if no errors
 */
 
 BOOL
-filter_runtest(int fd, uschar *filename, BOOL is_system, BOOL dot_ended)
+filter_runtest(int fd, const uschar * filename, BOOL is_system, BOOL dot_ended)
 {
 int rc, filter_type;
 BOOL yield;
diff --git a/src/src/functions.h b/src/src/functions.h
index 39119ca09..ba4fd215c 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -218,7 +218,7 @@ extern void    delete_pid_file(void);
 extern void    deliver_local(address_item *, BOOL);
 extern address_item *deliver_make_addr(uschar *, BOOL);
 extern void    delivery_log(int, address_item *, int, uschar *);
-extern int     deliver_message(uschar *, BOOL, BOOL);
+extern int     deliver_message(const uschar *, BOOL, BOOL);
 extern void    deliver_msglog(const char *, ...) PRINTF_FUNCTION(1,2);
 extern void    deliver_set_expansions(address_item *);
 extern int     deliver_split_address(address_item *);
@@ -284,7 +284,7 @@ extern BOOL    fd_ready(int, time_t);
 
 extern int     filter_interpret(const uschar *, int, address_item **, uschar **);
 extern BOOL    filter_personal(string_item *, BOOL);
-extern BOOL    filter_runtest(int, uschar *, BOOL, BOOL);
+extern BOOL    filter_runtest(int, const uschar *, BOOL, BOOL);
 extern BOOL    filter_system_interpret(address_item **, uschar **);
 
 extern uschar * fn_hdrs_added(void);
@@ -345,7 +345,7 @@ extern uschar *macros_expand(int, int *, BOOL *);
 extern void    mainlog_close(void);
 #ifdef WITH_CONTENT_SCAN
 extern int     malware(const uschar *, BOOL, int);
-extern int     malware_in_file(uschar *);
+extern int     malware_in_file(const uschar *);
 extern void    malware_init(void);
 extern gstring * malware_show_supported(gstring *);
 #endif
@@ -408,15 +408,15 @@ extern BOOL	proxy_protocol_host(void);
 extern void	proxy_protocol_setup(void);
 #endif
 
-extern BOOL    queue_action(uschar *, int, uschar **, int, int);
+extern BOOL    queue_action(const uschar *, int, const uschar **, int, int);
 extern void    queue_check_only(void);
 extern unsigned queue_count(void);
 extern unsigned queue_count_cached(void);
-extern void    queue_list(int, uschar **, int);
+extern void    queue_list(int, const uschar **, int);
 #ifndef DISABLE_QUEUE_RAMP
 extern void    queue_notify_daemon(const uschar * hostname);
 #endif
-extern void    queue_run(qrunner *, uschar *, uschar *, BOOL);
+extern void    queue_run(qrunner *, const uschar *, const uschar *, BOOL);
 
 extern int     random_number(int);
 extern const uschar *rc_to_string(int);
@@ -430,7 +430,7 @@ extern void    readconf_driver_init(uschar *, driver_instance **,
 extern uschar *readconf_find_option(void *);
 extern void    readconf_main(BOOL);
 extern void    readconf_options_from_list(optionlist *, unsigned, const uschar *, uschar *);
-extern BOOL    readconf_print(const uschar *, uschar *, BOOL);
+extern BOOL    readconf_print(const uschar *, const uschar *, BOOL);
 extern uschar *readconf_printtime(int);
 extern const uschar *readconf_readname(uschar *, int, const uschar *);
 extern int     readconf_readtime(const uschar *, int, BOOL);
@@ -503,7 +503,7 @@ extern int     sieve_interpret(const uschar *, int, const uschar *,
 		 const uschar *, const uschar *, const uschar *,
 		 address_item **, uschar **);
 extern void    sigalrm_handler(int);
-extern void    single_queue_run(qrunner *, uschar *, uschar *);
+extern void    single_queue_run(qrunner *, const uschar *, const uschar *);
 extern int     smtp_boundsock(smtp_connect_args *);
 extern void    smtp_closedown(uschar *);
 extern void    smtp_command_timeout_exit(void) NORETURN;
@@ -544,12 +544,12 @@ extern int     spam(const uschar **);
 extern FILE   *spool_mbox(unsigned long *, const uschar *, uschar **);
 #endif
 extern void    spool_clear_header_globals(void);
-extern BOOL    spool_move_message(uschar *, uschar *, uschar *, uschar *);
-extern int     spool_open_datafile(uschar *);
+extern BOOL    spool_move_message(const uschar *, const uschar *, const uschar *, const uschar *);
+extern int     spool_open_datafile(const uschar *);
 extern int     spool_open_temp(uschar *);
 extern int     spool_read_header(uschar *, BOOL, BOOL);
 extern uschar *spool_sender_from_msgid(const uschar *);
-extern int     spool_write_header(uschar *, int, uschar **);
+extern int     spool_write_header(const uschar *, int, uschar **);
 extern int     stdin_getc(unsigned);
 extern int     stdin_feof(void);
 extern int     stdin_ferror(void);
@@ -1144,7 +1144,7 @@ return string_sprintf("%s/%s/%s/%s",
 # endif
 
 static inline uschar *
-spool_q_sname(const uschar * purpose, const uschar * q, uschar * subdir)
+spool_q_sname(const uschar * purpose, const uschar * q, const uschar * subdir)
 {
 return string_sprintf("%s%s%s%s%s",
 		    q, *q ? "/" : "",
@@ -1153,7 +1153,7 @@ return string_sprintf("%s%s%s%s%s",
 }
 
 static inline uschar *
-spool_sname(const uschar * purpose, uschar * subdir)
+spool_sname(const uschar * purpose, const uschar * subdir)
 {
 return spool_q_sname(purpose, queue_name, subdir);
 }
diff --git a/src/src/globals.c b/src/src/globals.c
index e94bef75c..46ca72f20 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -726,7 +726,7 @@ gid_t   config_gid             = CONFIGURE_GROUP;
 #else
 gid_t   config_gid             = 0;
 #endif
-uschar *config_main_filelist   = US CONFIGURE_FILE
+const uschar * config_main_filelist = US CONFIGURE_FILE
                          "\0<-----------Space to patch configure_filename->";
 uschar *config_main_filename   = NULL;
 uschar *config_main_directory  = NULL;
@@ -939,8 +939,8 @@ uschar *fake_response_text     = US"Your message has been rejected but is "
 int     filter_n[FILTER_VARIABLE_COUNT];
 int     filter_sn[FILTER_VARIABLE_COUNT];
 int     filter_test            = FTEST_NONE;
-uschar *filter_test_sfile      = NULL;
-uschar *filter_test_ufile      = NULL;
+const uschar * filter_test_sfile = NULL;
+const uschar * filter_test_ufile = NULL;
 uschar *filter_thisaddress     = NULL;
 int     finduser_retries       = 0;
 uid_t   fixed_never_users[]    = { FIXED_NEVER_USERS };
@@ -1185,7 +1185,7 @@ uschar *message_size_limit     = US"50M";
 int     message_utf8_downconvert = 0;	/* -1 ifneeded; 0 never; 1 always */
 #endif
 uschar  message_subdir[2]      = { 0, 0 };
-uschar *message_reference      = NULL;
+const uschar *message_reference= NULL;
 
 /* MIME ACL expandables */
 #ifdef WITH_CONTENT_SCAN
@@ -1219,7 +1219,7 @@ uschar *originator_login       = NULL;
 uschar *originator_name        = NULL;
 uid_t   originator_uid;
 uschar *override_local_interfaces = NULL;
-uschar *override_pid_file_path = NULL;
+const uschar *override_pid_file_path = NULL;
 
 BOOL    panic_coredump	       = FALSE;
 pcre2_general_context * pcre_gen_ctx = NULL;
@@ -1229,7 +1229,7 @@ pcre2_general_context * pcre_mlc_ctx = NULL;
 pcre2_compile_context * pcre_mlc_cmp_ctx = NULL;
 
 uschar *percent_hack_domains   = NULL;
-uschar *pid_file_path          = US PID_FILE_PATH
+const uschar *pid_file_path    = US PID_FILE_PATH
                            "\0<--------------Space to patch pid_file_path->";
 #ifndef DISABLE_PIPE_CONNECT
 uschar *pipe_connect_advertise_hosts = US"*";
@@ -1485,7 +1485,7 @@ uschar *sending_ip_address     = NULL;
 int     sending_port           = -1;
 SIGNAL_BOOL sigalrm_seen       = FALSE;
 const uschar *sigalarm_setter  = NULL;
-uschar **sighup_argv           = NULL;
+const uschar **sighup_argv     = NULL;
 int     slow_lookup_log        = 0;	/* millisecs, zero disables */
 int     smtp_accept_count      = 0;
 int     smtp_accept_max        = 20;
@@ -1572,7 +1572,7 @@ int     string_datestamp_type  = -1;
 const uschar *submission_domain = NULL;
 const uschar *submission_name  = NULL;
 int     syslog_facility        = LOG_MAIL;
-uschar *syslog_processname     = US"exim";
+const uschar *syslog_processname= US"exim";
 uschar *system_filter          = NULL;
 
 uschar *system_filter_directory_transport = NULL;
diff --git a/src/src/globals.h b/src/src/globals.h
index 6ec4e619d..9ec44487f 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -438,7 +438,7 @@ extern FILE   *config_file;            /* Configuration file */
 extern const uschar *config_filename;  /* Configuration file name */
 extern gid_t   config_gid;             /* Additional group owner */
 extern int     config_lineno;          /* Line number */
-extern uschar *config_main_filelist;   /* List of possible config files */
+extern const uschar *config_main_filelist; /* List of possible config files */
 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 */
@@ -625,8 +625,8 @@ extern uschar *fake_response_text;     /* User defined message for the above. De
 extern int     filter_n[FILTER_VARIABLE_COUNT]; /* filter variables */
 extern int     filter_sn[FILTER_VARIABLE_COUNT]; /* variables set by system filter */
 extern int     filter_test;            /* Filter test type */
-extern uschar *filter_test_sfile;      /* System filter test file */
-extern uschar *filter_test_ufile;      /* User filter test file */
+extern const uschar *filter_test_sfile;/* System filter test file */
+extern const uschar *filter_test_ufile;/* User filter test file */
 extern uschar *filter_thisaddress;     /* For address looping */
 extern int     finduser_retries;       /* Retry count for getpwnam() */
 extern uid_t   fixed_never_users[];    /* Can't be overridden */
@@ -755,7 +755,7 @@ extern int     message_utf8_downconvert; /* convert from utf8 */
 const extern pcre2_code *regex_UTF8;         /* For recognizing SMTPUTF8 settings */
 #endif
 extern uschar  message_subdir[];       /* Subdirectory for messages */
-extern uschar *message_reference;      /* Reference for error messages */
+extern const uschar *message_reference;/* Reference for error messages */
 
 /* MIME ACL expandables */
 #ifdef WITH_CONTENT_SCAN
@@ -798,7 +798,7 @@ extern uschar *originator_login;       /* Login of same */
 extern uschar *originator_name;        /* Full name of same */
 extern uid_t   originator_uid;         /* Uid of ditto */
 extern uschar *override_local_interfaces; /* Value of -oX argument */
-extern uschar *override_pid_file_path; /* Value of -oP argument */
+extern const uschar *override_pid_file_path; /* Value of -oP argument */
 
 extern BOOL    panic_coredump;	       /* SEGV rather than exit, on LOG_PANIC_DIE */
 extern pcre2_general_context * pcre_gen_ctx;	/* pcre memory management */
@@ -808,7 +808,7 @@ extern pcre2_general_context * pcre_mlc_ctx;
 extern pcre2_compile_context * pcre_mlc_cmp_ctx;
 
 extern uschar *percent_hack_domains;   /* Local domains for which '% operates */
-extern uschar *pid_file_path;          /* For writing daemon pids */
+extern const uschar *pid_file_path;    /* For writing daemon pids */
 #ifndef DISABLE_PIPE_CONNECT
 extern uschar *pipe_connect_advertise_hosts; /* for banner/EHLO pipelining */
 #endif
@@ -970,7 +970,7 @@ extern uschar *sending_ip_address;     /* Address of outgoing (SMTP) interface *
 extern int     sending_port;           /* Port of outgoing interface */
 extern SIGNAL_BOOL sigalrm_seen;       /* Flag for sigalrm_handler */
 extern const uschar *sigalarm_setter;  /* For debug, set to callpoint of alarm() */
-extern uschar **sighup_argv;           /* Args for re-execing after SIGHUP */
+extern const uschar **sighup_argv;     /* Args for re-execing after SIGHUP */
 extern int     slow_lookup_log;        /* Log DNS lookups taking longer than N millisecs */
 extern int     smtp_accept_count;      /* Count of connections */
 extern BOOL    smtp_accept_keepalive;  /* Set keepalive on incoming */
@@ -1065,7 +1065,7 @@ extern const uschar *submission_name;  /* User name set from ACL */
 extern BOOL    syslog_duplication;     /* FALSE => no duplicate logging */
 extern int     syslog_facility;        /* As defined by Syslog.h */
 extern BOOL    syslog_pid;             /* TRUE if PID on syslogs */
-extern uschar *syslog_processname;     /* 'ident' param to openlog() */
+extern const uschar *syslog_processname; /* 'ident' param to openlog() */
 extern BOOL    syslog_timestamp;       /* TRUE if time on syslogs */
 extern uschar *system_filter;          /* Name of system filter file */
 
diff --git a/src/src/malware.c b/src/src/malware.c
index 9455d4769..760278079 100644
--- a/src/src/malware.c
+++ b/src/src/malware.c
@@ -2238,7 +2238,7 @@ Returns:        Exim message processing code (OK, FAIL, DEFER, ...)
                 where true means malware was found (condition applies)
 */
 int
-malware_in_file(uschar *eml_filename)
+malware_in_file(const uschar * eml_filename)
 {
 uschar message_id_buf[64];
 int ret;
diff --git a/src/src/queue.c b/src/src/queue.c
index fa4fc0aec..cfe87e953 100644
--- a/src/src/queue.c
+++ b/src/src/queue.c
@@ -354,7 +354,7 @@ Returns:     nothing
 */
 
 void
-queue_run(qrunner * q, uschar * start_id, uschar * stop_id, BOOL recurse)
+queue_run(qrunner * q, const uschar * start_id, const uschar * stop_id, BOOL recurse)
 {
 BOOL force_delivery = q->queue_run_force
   || deliver_selectstring || deliver_selectstring_sender;
@@ -813,7 +813,7 @@ if (!recurse)
 
 
 void
-single_queue_run(qrunner * q, uschar * start_id, uschar * stop_id)
+single_queue_run(qrunner * q, const uschar * start_id, const uschar * stop_id)
 {
 DEBUG(D_queue_run) debug_printf("Single queue run%s%s%s%s\n",
   start_id ? US" starting at " : US"",
@@ -919,7 +919,7 @@ Returns:      nothing
 */
 
 void
-queue_list(int option, uschar ** list, int count)
+queue_list(int option, const uschar ** list, int count)
 {
 int subcount;
 int now = (int)time(NULL);
@@ -1093,7 +1093,7 @@ Returns:          FALSE if there was any problem
 */
 
 BOOL
-queue_action(uschar * id, int action, uschar ** argv, int argc,
+queue_action(const uschar * id, int action, const uschar ** argv, int argc,
   int recipients_arg)
 {
 BOOL yield = TRUE;
diff --git a/src/src/readconf.c b/src/src/readconf.c
index e77458d68..bbcca87f7 100644
--- a/src/src/readconf.c
+++ b/src/src/readconf.c
@@ -2741,7 +2741,7 @@ Returns:      Boolean success
 */
 
 BOOL
-readconf_print(const uschar *name, uschar *type, BOOL no_labels)
+readconf_print(const uschar * name, const uschar * type, BOOL no_labels)
 {
 BOOL names_only = FALSE;
 optionlist *ol2 = NULL;
@@ -3121,8 +3121,8 @@ readconf_main(BOOL nowarn)
 {
 int sep = 0;
 struct stat statbuf;
-uschar *s, *filename;
-const uschar *list = config_main_filelist;
+uschar * s, * filename;
+const uschar * list = config_main_filelist;
 
 /* Loop through the possible file names */
 
@@ -3459,7 +3459,7 @@ leading "log_". */
 if (syslog_facility_str)
   {
   int i;
-  uschar *s = syslog_facility_str;
+  uschar * s = syslog_facility_str;
 
   if ((Ustrlen(syslog_facility_str) >= 4) &&
         (strncmpic(syslog_facility_str, US"log_", 4) == 0))
@@ -3481,10 +3481,11 @@ if (syslog_facility_str)
 
 if (*pid_file_path)
   {
-  if (!(s = expand_string(pid_file_path)))
+  const uschar * t = expand_cstring(pid_file_path);
+  if (!t)
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand pid_file_path "
       "\"%s\": %s", pid_file_path, expand_string_message);
-  pid_file_path = s;
+  pid_file_path = t;
   }
 
 /* Set default value of process_log_path */
diff --git a/src/src/spool_in.c b/src/src/spool_in.c
index 737a01cdd..588effd2c 100644
--- a/src/src/spool_in.c
+++ b/src/src/spool_in.c
@@ -36,7 +36,7 @@ Side effect: message_subdir is set for the (possibly split) spool directory
 */
 
 int
-spool_open_datafile(uschar * id)
+spool_open_datafile(const uschar * id)
 {
 struct stat statbuf;
 flock_t lock_data;
diff --git a/src/src/spool_out.c b/src/src/spool_out.c
index 9c5a683e3..f3373faed 100644
--- a/src/src/spool_out.c
+++ b/src/src/spool_out.c
@@ -152,7 +152,7 @@ Returns:  the size of the header texts on success;
 */
 
 int
-spool_write_header(uschar * id, int where, uschar ** errmsg)
+spool_write_header(const uschar * id, int where, uschar ** errmsg)
 {
 int fd, size_correction;
 FILE * fp;
@@ -444,7 +444,7 @@ start-up time.
 
 Arguments:
   dir        base directory name
-  dq	     destiinationqueue name
+  dq	     destination queue name
   subdir     subdirectory name
   id         message id
   suffix     suffix to add to id
@@ -457,8 +457,8 @@ Returns:     TRUE if all went well
 */
 
 static BOOL
-make_link(uschar * dir, uschar * dq, uschar * subdir, uschar * id, uschar * suffix,
-  uschar * from, uschar * to, BOOL noentok)
+make_link(const uschar * dir, const uschar * dq, const uschar * subdir, const uschar * id,
+  const uschar * suffix, const uschar * from, const uschar * to, BOOL noentok)
 {
 uschar * fname = spool_fname(string_sprintf("%s%s", from, dir), subdir, id, suffix);
 uschar * tname = spool_q_fname(string_sprintf("%s%s", to,   dir), dq, subdir, id, suffix);
@@ -494,8 +494,8 @@ Returns:     TRUE if all went well
 */
 
 static BOOL
-break_link(uschar * dir, uschar * subdir, uschar * id, uschar * suffix, uschar * from,
-  BOOL noentok)
+break_link(const uschar * dir, const uschar * subdir, const uschar * id,
+  const uschar * suffix, const uschar * from, BOOL noentok)
 {
 uschar * fname = spool_fname(string_sprintf("%s%s", from, dir), subdir, id, suffix);
 if (Uunlink(fname) < 0 && (!noentok || errno != ENOENT))
@@ -528,7 +528,8 @@ Returns:      TRUE if all is well
 */
 
 BOOL
-spool_move_message(uschar * id, uschar * subdir, uschar * from, uschar * to)
+spool_move_message(const uschar * id, const uschar * subdir,
+  const uschar * from, const uschar * to)
 {
 uschar * dest_qname = queue_name_dest ? queue_name_dest : queue_name;
 

commit b8e078953178c757578f2e104d9d2c822ae0943c
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Tue Nov 14 12:10:36 2023 +0000

    Check for missing commandline arg after options taking one.  Bug 3049

diff --git a/src/src/exim.c b/src/src/exim.c
index 3cc2fa2fb..14c057f40 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -847,7 +847,7 @@ exit(EXIT_FAILURE);
 
 /* fail if a length is too long */
 static inline void
-exim_len_fail_toolong(int itemlen, int maxlen, const char *description)
+exim_len_fail_toolong(int itemlen, int maxlen, const char * description)
 {
 if (itemlen <= maxlen)
   return;
@@ -858,8 +858,10 @@ exit(EXIT_FAILURE);
 
 /* only pass through the string item back to the caller if it's short enough */
 static inline const uschar *
-exim_str_fail_toolong(const uschar *item, int maxlen, const char *description)
+exim_str_fail_toolong(const uschar * item, int maxlen, const char * description)
 {
+if (!item)
+  exim_fail("exim: bad item for: %s\n", description);
 exim_len_fail_toolong(Ustrlen(item), maxlen, description);
 return item;
 }
@@ -884,10 +886,10 @@ log_write(0, LOG_MAIN|LOG_PANIC,
 struct stat buf;
 
 if (0 == (fd < 0 ? stat(name, &buf) : fstat(fd, &buf)))
-{
+  {
   if (buf.st_uid == owner && buf.st_gid == group) return 0;
   log_write(0, LOG_MAIN|LOG_PANIC, "Wrong ownership on %s", name);
-}
+  }
 else log_write(0, LOG_MAIN|LOG_PANIC, "Stat failed on %s: %s", name, strerror(errno));
 
 #endif
@@ -896,6 +898,18 @@ return -1;
 }
 
 
+/* Bump the index for argv, checking for overflow,
+and return the argument. */
+
+static const uschar *
+next_argv(const uschar ** argv, int * pi, int argc, const uschar * where)
+{
+int i = *pi;
+if (++i >= argc) exim_fail("exim: bad item for: %s\n", where);
+return argv[*pi = i];
+}
+
+
 /*************************************************
 *         Extract port from host address         *
 *************************************************/
@@ -3151,7 +3165,8 @@ on the second character (the one after '-'), to save some effort. */
       {
       msg_action = MSG_SETQUEUE;
       queue_name_dest = string_copy_taint(
-	exim_str_fail_toolong(argv[++i], EXIM_DRIVERNAME_MAX, "-MG"),
+	exim_str_fail_toolong(next_argv(argv, &i, argc, arg),
+			      EXIM_DRIVERNAME_MAX, "-MG"),
 	GET_TAINTED);
       }
     else if (Ustrcmp(argrest, "mad") == 0) msg_action = MSG_MARK_ALL_DELIVERED;
@@ -3363,36 +3378,36 @@ on the second character (the one after '-'), to save some effort. */
 
 	if (Ustrcmp(argrest, "a") == 0)
 	  sender_host_address = string_copy_taint(
-	    exim_str_fail_toolong(argv[++i], EXIM_IPADDR_MAX, "-oMa"),
-	    GET_TAINTED);
+	    exim_str_fail_toolong(next_argv(argv, &i, argc, arg),
+				  EXIM_IPADDR_MAX, "-oMa"), GET_TAINTED);
 
 	/* -oMaa: Set authenticator name */
 
 	else if (Ustrcmp(argrest, "aa") == 0)
 	  sender_host_authenticated = string_copy_taint(
-	    exim_str_fail_toolong(argv[++i], EXIM_DRIVERNAME_MAX, "-oMaa"),
-	    GET_TAINTED);
+	    exim_str_fail_toolong(next_argv(argv, &i, argc, arg),
+				  EXIM_DRIVERNAME_MAX, "-oMaa"), GET_TAINTED);
 
 	/* -oMas: setting authenticated sender */
 
 	else if (Ustrcmp(argrest, "as") == 0)
 	  authenticated_sender = string_copy_taint(
-	    exim_str_fail_toolong(argv[++i], EXIM_EMAILADDR_MAX, "-oMas"),
-	    GET_TAINTED);
+	    exim_str_fail_toolong(next_argv(argv, &i, argc, arg),
+				  EXIM_EMAILADDR_MAX, "-oMas"), GET_TAINTED);
 
 	/* -oMai: setting authenticated id */
 
 	else if (Ustrcmp(argrest, "ai") == 0)
 	  authenticated_id = string_copy_taint(
-	    exim_str_fail_toolong(argv[++i], EXIM_EMAILADDR_MAX, "-oMai"),
-	    GET_TAINTED);
+	    exim_str_fail_toolong(next_argv(argv, &i, argc, arg),
+				  EXIM_EMAILADDR_MAX, "-oMai"), GET_TAINTED);
 
 	/* -oMi: Set incoming interface address */
 
 	else if (Ustrcmp(argrest, "i") == 0)
 	  interface_address = string_copy_taint(
-	    exim_str_fail_toolong(argv[++i], EXIM_IPADDR_MAX, "-oMi"),
-	    GET_TAINTED);
+	    exim_str_fail_toolong(next_argv(argv, &i, argc, arg),
+				  EXIM_IPADDR_MAX, "-oMi"), GET_TAINTED);
 
 	/* -oMm: Message reference */
 
@@ -3402,7 +3417,7 @@ on the second character (the one after '-'), to save some effort. */
 	      exim_fail("-oMm must be a valid message ID\n");
 	  if (!f.trusted_config)
 	      exim_fail("-oMm must be called by a trusted user/config\n");
-	    message_reference = argv[++i];
+	    message_reference = next_argv(argv, &i, argc, arg);
 	  }
 
 	/* -oMr: Received protocol */
@@ -3412,26 +3427,32 @@ on the second character (the one after '-'), to save some effort. */
 	  if (received_protocol)
 	    exim_fail("received_protocol is set already\n");
 	  else
-	    received_protocol = string_copy_taint(
-	      exim_str_fail_toolong(argv[++i], EXIM_DRIVERNAME_MAX, "-oMr"),
-	      GET_TAINTED);
+	    if (++i >= argc) badarg = TRUE;
+	    else
+	      received_protocol = string_copy_taint(
+		exim_str_fail_toolong(argv[i], EXIM_DRIVERNAME_MAX, "-oMr"),
+		GET_TAINTED);
 
 	/* -oMs: Set sender host name */
 
 	else if (Ustrcmp(argrest, "s") == 0)
-	  sender_host_name = string_copy_taint(
-	    exim_str_fail_toolong(argv[++i], EXIM_HOSTNAME_MAX, "-oMs"),
-	    GET_TAINTED);
+	  if (++i >= argc) badarg = TRUE;
+	  else
+	    sender_host_name = string_copy_taint(
+	      exim_str_fail_toolong(argv[i], EXIM_HOSTNAME_MAX, "-oMs"),
+	      GET_TAINTED);
 
 	/* -oMt: Set sender ident */
 
 	else if (Ustrcmp(argrest, "t") == 0)
-	  {
-	  sender_ident_set = TRUE;
-	  sender_ident = string_copy_taint(
-	    exim_str_fail_toolong(argv[++i], EXIM_IDENTUSER_MAX, "-oMt"),
-	    GET_TAINTED);
-	  }
+	  if (++i >= argc) badarg = TRUE;
+	  else
+	    {
+	    sender_ident_set = TRUE;
+	    sender_ident = string_copy_taint(
+	      exim_str_fail_toolong(argv[i], EXIM_IDENTUSER_MAX, "-oMt"),
+	      GET_TAINTED);
+	    }
 
 	/* Else a bad argument */
 
@@ -3459,7 +3480,9 @@ on the second character (the one after '-'), to save some effort. */
 	  exim_fail("exim: only uid=%d or uid=%d can use -oP and -oPX "
                     "(uid=%d euid=%d | %d)\n",
                     root_uid, exim_uid, getuid(), geteuid(), real_uid);
-	if (!*argrest) override_pid_file_path = argv[++i];
+	if (!*argrest)
+	  if (++i < argc) override_pid_file_path = argv[i];
+	  else badarg = TRUE;
 	else if (Ustrcmp(argrest, "X") == 0) delete_pid_file();
 	else badarg = TRUE;
 	break;
@@ -3487,10 +3510,9 @@ on the second character (the one after '-'), to save some effort. */
       /* Limits: Is there a real limit we want here?  1024 is very arbitrary. */
 
       case 'X':
-	if (*argrest) badarg = TRUE;
+	if (*argrest || ++i >= argc) badarg = TRUE;
 	else override_local_interfaces = string_copy_taint(
-	  exim_str_fail_toolong(argv[++i], 1024, "-oX"),
-	  GET_TAINTED);
+	  exim_str_fail_toolong(argv[i], 1024, "-oX"), GET_TAINTED);
 	break;
 
       /* -oY: Override creation of daemon notifier socket */
@@ -3528,7 +3550,7 @@ on the second character (the one after '-'), to save some effort. */
     which sets the host protocol and host name */
 
     if (!*argrest)
-      if (i+1 < argc) argrest = argv[++i]; else { badarg = TRUE; break; }
+      argrest = next_argv(argv, &i, argc, arg);
 
     if (*argrest)
       {
@@ -3614,8 +3636,14 @@ on the second character (the one after '-'), to save some effort. */
 
 	else
 	  {
-	  int intvl = readconf_readtime(*argrest ? argrest : argv[++i], 0, FALSE);
-	  if (intvl <= 0)
+	  int intvl;
+	  const uschar * s;
+
+	  if (*argrest) s = argrest;
+	  else if (++i < argc) { badarg = TRUE; break; }
+	  else s = argv[i];
+
+	  if ((intvl = readconf_readtime(s, 0, FALSE)) <= 0)
 	    exim_fail("exim: bad time value %s: abandoned\n", argv[i]);
 
 	  for (qrunner * qq = qrunners; qq; qq = qq->next)
@@ -3706,8 +3734,8 @@ on the second character (the one after '-'), to save some effort. */
     tested. Otherwise variability of clock ticks etc. cause problems. */
 
     case 'T':
-    if (f.running_in_test_harness && Ustrcmp(argrest, "qt") == 0)
-      fudged_queue_times = string_copy_taint(argv[++i], GET_TAINTED);
+    if (f.running_in_test_harness && Ustrcmp(argrest, "qt") == 0 && ++i < argc)
+      fudged_queue_times = string_copy_taint(argv[i], GET_TAINTED);
     else badarg = TRUE;
     break;
 

commit fa1c8faf169384bebaa8d172f491574c45ae2aa4
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Wed Nov 15 02:14:02 2023 +0000

    constification

diff --git a/src/exim_monitor/em_globals.c b/src/exim_monitor/em_globals.c
index cf9b1075e..3822746ba 100644
--- a/src/exim_monitor/em_globals.c
+++ b/src/exim_monitor/em_globals.c
@@ -204,7 +204,7 @@ recipient_item *recipients_list = NULL;
 int     recipients_list_max    = 0;
 BOOL    running_in_test_harness=FALSE;
 
-uschar *sender_address         = NULL;
+const uschar *sender_address    = NULL;
 uschar *sender_fullhost        = NULL;
 uschar *sender_helo_name       = NULL;
 uschar *sender_host_address    = NULL;
diff --git a/src/exim_monitor/em_hdr.h b/src/exim_monitor/em_hdr.h
index 61f390d2c..feffa6bfc 100644
--- a/src/exim_monitor/em_hdr.h
+++ b/src/exim_monitor/em_hdr.h
@@ -309,7 +309,7 @@ extern uschar *copystring(uschar *);
 extern void    create_dialog(uschar *, uschar *);
 extern void    create_stripchart(Widget, uschar *);
 extern void    debug(char *, ...);
-extern dest_item *find_dest(queue_item *, uschar *, int, BOOL);
+extern dest_item *find_dest(queue_item *, const uschar *, int, BOOL);
 extern queue_item *find_queue(uschar *, int, int);
 extern void    init(int, uschar **);
 extern void    menu_create(Widget, XEvent *, String *, Cardinal *);
diff --git a/src/exim_monitor/em_queue.c b/src/exim_monitor/em_queue.c
index 4c3d3fd18..c7c61a652 100644
--- a/src/exim_monitor/em_queue.c
+++ b/src/exim_monitor/em_queue.c
@@ -66,18 +66,16 @@ address is lowercased to start with, unless it begins with
 "*", which it does for error messages. */
 
 dest_item *
-find_dest(queue_item *q, uschar *name, int action, BOOL caseless)
+find_dest(queue_item * q, const uschar * name, int action, BOOL caseless)
 {
-dest_item *dd;
-dest_item **d = &(q->destinations);
+dest_item * dd;
+dest_item ** d = &q->destinations;
 
-while (*d != NULL)
+while (*d)
   {
   if ((caseless? strcmpic(name,(*d)->address) : Ustrcmp(name,(*d)->address))
         == 0)
     {
-    dest_item *ddd;
-
     if (action != dest_remove) return *d;
     dd = *d;
     *d = dd->next;
@@ -85,14 +83,12 @@ while (*d != NULL)
 
     /* Unset any parent pointers that were to this address */
 
-    for (ddd = q->destinations; ddd != NULL; ddd = ddd->next)
-      {
+    for (dest_item * ddd = q->destinations; ddd; ddd = ddd->next)
       if (ddd->parent == dd) ddd->parent = NULL;
-      }
 
     return NULL;
     }
-  d = &((*d)->next);
+  d = &(*d)->next;
   }
 
 if (action != dest_add) return NULL;
@@ -207,8 +203,9 @@ if it's there. */
 else
   {
   q->update_time = q->input_time = received_time.tv_sec;
-  if ((p = strstric(sender_address+1, qualify_domain, FALSE)) != NULL &&
-    *(--p) == '@') *p = 0;
+  /* deconst ok; strstric is actually safe */
+  if ((p = strstric(US sender_address+1, qualify_domain, FALSE)) != NULL &&
+    *--p == '@') *p = 0;
   }
 
 /* If we didn't read the whole header successfully, generate an error
@@ -279,10 +276,11 @@ been delivered, and removing visible names. */
 if (recipients_list)
   for (i = 0; i < recipients_count; i++)
     {
-    uschar * r = recipients_list[i].address;
+    const uschar * r = recipients_list[i].address;
     if (tree_search(tree_nonrecipients, r) == NULL)
       {
-      if ((p = strstric(r+1, qualify_domain, FALSE)) != NULL &&
+      /* deconst ok; strstric is actually safe */
+      if ((p = strstric(US r+1, qualify_domain, FALSE)) != NULL &&
         *(--p) == '@') *p = 0;
       (void)find_dest(q, r, dest_add, FALSE);
       }
@@ -663,13 +661,14 @@ if (recipients_list)
   for (i = 0; i < recipients_count; i++)
     {
     uschar * pp;
-    uschar * r = recipients_list[i].address;
+    const uschar * r = recipients_list[i].address;
     tree_node * node;
 
     if (!(node = tree_search(tree_nonrecipients, r)))
       node = tree_search(tree_nonrecipients, string_copylc(r));
 
-    if ((pp = strstric(r+1, qualify_domain, FALSE)) && *(--pp) == '@')
+    /* deconst ok; strstric is actually safe */
+    if ((pp = strstric(US r+1, qualify_domain, FALSE)) && *(--pp) == '@')
        *pp = 0;
     if (!node)
       (void)find_dest(p, r, dest_add, FALSE);
diff --git a/src/src/acl.c b/src/src/acl.c
index cdf60bbb8..9223fcec8 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -1711,10 +1711,10 @@ BOOL no_details = FALSE;
 BOOL success_on_redirect = FALSE;
 BOOL quota = FALSE;
 int quota_pos_cache = QUOTA_POS_DEFAULT, quota_neg_cache = QUOTA_NEG_DEFAULT;
-address_item *sender_vaddr = NULL;
-uschar *verify_sender_address = NULL;
-uschar *pm_mailfrom = NULL;
-uschar *se_mailfrom = NULL;
+address_item * sender_vaddr = NULL;
+const uschar * verify_sender_address = NULL;
+uschar * pm_mailfrom = NULL;
+uschar * se_mailfrom = NULL;
 
 /* Some of the verify items have slash-separated options; some do not. Diagnose
 an error if options are given for items that don't expect them.
@@ -4713,8 +4713,8 @@ Returns:       OK         access is granted by an ACCEPT verb
 int acl_where = ACL_WHERE_UNKNOWN;
 
 int
-acl_check(int where, uschar *recipient, uschar *s, uschar **user_msgptr,
-  uschar **log_msgptr)
+acl_check(int where, const uschar * recipient, uschar * s,
+  uschar ** user_msgptr, uschar ** log_msgptr)
 {
 int rc;
 address_item adb;
diff --git a/src/src/deliver.c b/src/src/deliver.c
index 9d459167e..e96733f73 100644
--- a/src/src/deliver.c
+++ b/src/src/deliver.c
@@ -27,7 +27,7 @@ typedef struct pardata {
   int transport_count;         /* returned transport count value */
   BOOL done;                   /* no more data needed */
   uschar *msg;                 /* error message */
-  uschar *return_path;         /* return_path for these addresses */
+  const uschar *return_path;   /* return_path for these addresses */
 } pardata;
 
 /* Values for the process_recipients variable */
@@ -77,7 +77,7 @@ static pardata *parlist = NULL;
 static struct pollfd *parpoll;
 static int  return_count;
 static uschar *frozen_info = US"";
-static uschar *used_return_path = NULL;
+static const uschar * used_return_path = NULL;
 
 
 
@@ -144,7 +144,7 @@ Returns:      a pointer to an initialized address_item
 */
 
 address_item *
-deliver_make_addr(uschar *address, BOOL copy)
+deliver_make_addr(const uschar * address, BOOL copy)
 {
 address_item * addr = store_get(sizeof(address_item), GET_UNTAINTED);
 *addr = address_defaults;
@@ -575,7 +575,7 @@ Returns:    TRUE or FALSE
 */
 
 static BOOL
-same_strings(uschar *one, uschar *two)
+same_strings(const uschar * one, const uschar * two)
 {
 if (one == two) return TRUE;   /* Includes the case where both NULL */
 if (!one || !two) return FALSE;
@@ -898,7 +898,7 @@ void
 msg_event_raise(const uschar * event, const address_item * addr)
 {
 const uschar * save_domain = deliver_domain;
-uschar * save_local =  deliver_localpart;
+const uschar * save_local =  deliver_localpart;
 const uschar * save_host = deliver_host;
 const uschar * save_address = deliver_host_address;
 const int      save_port =   deliver_host_port;
@@ -950,13 +950,13 @@ router_name = transport_name = NULL;
 *        Generate local part for logging         *
 *************************************************/
 
-static uschar *
-string_get_lpart_sub(const address_item * addr, uschar * s)
+static const uschar *
+string_get_lpart_sub(const address_item * addr, const uschar * s)
 {
 #ifdef SUPPORT_I18N
 if (testflag(addr, af_utf8_downcvt))
   {
-  uschar * t = string_localpart_utf8_to_alabel(s, NULL);
+  const uschar * t = string_localpart_utf8_to_alabel(s, NULL);
   return t ? t : s;	/* t is NULL on a failed conversion */
   }
 #endif
@@ -975,7 +975,7 @@ Returns:      the new value of the buffer pointer
 static gstring *
 string_get_localpart(address_item * addr, gstring * yield)
 {
-uschar * s;
+const uschar * s;
 
 if (testflag(addr, af_include_affixes) && (s = addr->prefix))
   yield = string_cat(yield, string_get_lpart_sub(addr, s));
@@ -5086,7 +5086,7 @@ Returns:    OK
 int
 deliver_split_address(address_item * addr)
 {
-uschar * address = addr->address;
+const uschar * address = addr->address;
 uschar * domain;
 uschar * t;
 int len;
@@ -5103,7 +5103,7 @@ where they are locally interpreted. [The new draft "821" is more explicit on
 this, Jan 1999.] We know the syntax is valid, so this can be done by simply
 removing quoting backslashes and any unquoted doublequotes. */
 
-t = addr->cc_local_part = store_get(len+1, address);
+addr->cc_local_part = t = store_get(len+1, address);
 while(len-- > 0)
   {
   int c = *address++;
@@ -5115,7 +5115,7 @@ while(len-- > 0)
     }
   else *t++ = c;
   }
-*t = 0;
+*t = '\0';
 
 /* We do the percent hack only for those domains that are listed in
 percent_hack_domains. A loop is required, to copy with multiple %-hacks. */
@@ -5123,8 +5123,8 @@ percent_hack_domains. A loop is required, to copy with multiple %-hacks. */
 if (percent_hack_domains)
   {
   int rc;
-  uschar *new_address = NULL;
-  uschar *local_part = addr->cc_local_part;
+  uschar * new_address = NULL;
+  const uschar * local_part = addr->cc_local_part;
 
   deliver_domain = addr->domain;  /* set $domain */
 
@@ -5261,12 +5261,12 @@ Returns:       TRUE if the address is not hidden
 */
 
 static BOOL
-print_address_information(address_item *addr, FILE *f, uschar *si, uschar *sc,
-  uschar *se)
+print_address_information(address_item * addr, FILE * f, uschar * si,
+  uschar * sc, uschar * se)
 {
 BOOL yield = TRUE;
-uschar *printed = US"";
-address_item *ancestor = addr;
+const uschar * printed = US"";
+address_item * ancestor = addr;
 while (ancestor->parent) ancestor = ancestor->parent;
 
 fprintf(f, "%s", CS si);
@@ -5281,8 +5281,8 @@ else if (!testflag(addr, af_pfr) || !addr->parent)
 
 else
   {
-  uschar *s = addr->address;
-  uschar *ss;
+  const uschar * s = addr->address;
+  const uschar * ss;
 
   if (addr->address[0] == '>') { ss = US"mail"; s++; }
   else if (addr->address[0] == '|') ss = US"pipe";
@@ -5296,7 +5296,7 @@ fprintf(f, "%s", CS string_printing(printed));
 
 if (ancestor != addr)
   {
-  uschar *original = ancestor->onetime_parent;
+  const uschar * original = ancestor->onetime_parent;
   if (!original) original= ancestor->address;
   if (strcmpic(original, printed) != 0)
     fprintf(f, "%s(%sgenerated from %s)", sc,
@@ -6973,7 +6973,7 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT)
 
         if (!p->transport)
           {
-          address_item *badp = p;
+          address_item * badp = p;
           p = p->next;
           if (!addr_last) addr_new = p; else addr_last->next = p;
           badp->local_part = badp->address;   /* Needed for log line */
@@ -7114,9 +7114,10 @@ if (process_recipients != RECIP_IGNORE)
 #ifndef DISABLE_EVENT
       if (process_recipients != RECIP_ACCEPT && event_action)
 	{
-	uschar * save_local =  deliver_localpart;
+	const uschar * save_local =  deliver_localpart;
 	const uschar * save_domain = deliver_domain;
-	uschar * addr = new->address, * errmsg = NULL;
+	const uschar * addr = new->address;
+	uschar * errmsg = NULL;
 	int start, end, dom;
 
 	if (!parse_extract_address(addr, &errmsg, &start, &end, &dom, TRUE))
@@ -8365,7 +8366,7 @@ else if (addr_defer != (address_item *)(+1))
 
       for (i = 0; i < recipients_count; i++)
         {
-        uschar *r = recipients_list[i].address;
+        const uschar * r = recipients_list[i].address;
         if (Ustrcmp(otaddr->onetime_parent, r) == 0) t = i;
         if (Ustrcmp(otaddr->address, r) == 0) break;
         }
@@ -8393,7 +8394,7 @@ else if (addr_defer != (address_item *)(+1))
 
     if (sender_address[0])
       {
-      uschar * s = addr->prop.errors_address;
+      const uschar * s = addr->prop.errors_address;
       if (!s) s = sender_address;
       if (Ustrstr(recipients, s) == NULL)
 	recipients = string_sprintf("%s%s%s", recipients,
diff --git a/src/src/exim.c b/src/src/exim.c
index 14c057f40..4473373b3 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -1379,17 +1379,15 @@ Argument:    the local part
 Returns:     the local part, quoted if necessary
 */
 
-uschar *
-local_part_quote(uschar *lpart)
+const uschar *
+local_part_quote(const uschar * lpart)
 {
 BOOL needs_quote = FALSE;
 gstring * g;
 
-for (uschar * t = lpart; !needs_quote && *t != 0; t++)
-  {
+for (const uschar * t = lpart; !needs_quote && *t; t++)
   needs_quote = !isalnum(*t) && strchr("!#$%&'*+-/=?^_`{|}~", *t) == NULL &&
     (*t != '.' || t == lpart || t[1] == 0);
-  }
 
 if (!needs_quote) return lpart;
 
@@ -1397,8 +1395,8 @@ g = string_catn(NULL, US"\"", 1);
 
 for (;;)
   {
-  uschar *nq = US Ustrpbrk(lpart, "\\\"");
-  if (nq == NULL)
+  uschar * nq = US Ustrpbrk(lpart, "\\\"");
+  if (!nq)
     {
     g = string_cat(g, lpart);
     break;
@@ -1809,20 +1807,20 @@ BOOL verify_address_mode = FALSE;
 BOOL verify_as_sender = FALSE;
 BOOL rcpt_verify_quota = FALSE;
 BOOL version_printed = FALSE;
-const uschar *alias_arg = NULL;
-const uschar *called_as = US"";
-const uschar *cmdline_syslog_name = NULL;
-const uschar *start_queue_run_id = NULL;
-const uschar *stop_queue_run_id = NULL;
-const uschar *expansion_test_message = NULL;
-const uschar *ftest_domain = NULL;
-const uschar *ftest_localpart = NULL;
-const uschar *ftest_prefix = NULL;
-const uschar *ftest_suffix = NULL;
-uschar *log_oneline = NULL;
-const uschar *malware_test_file = NULL;
-uschar *real_sender_address;
-uschar *originator_home = US"/";
+const uschar * alias_arg = NULL;
+const uschar * called_as = US"";
+const uschar * cmdline_syslog_name = NULL;
+const uschar * start_queue_run_id = NULL;
+const uschar * stop_queue_run_id = NULL;
+const uschar * expansion_test_message = NULL;
+const uschar * ftest_domain = NULL;
+const uschar * ftest_localpart = NULL;
+const uschar * ftest_prefix = NULL;
+const uschar * ftest_suffix = NULL;
+uschar * log_oneline = NULL;
+const uschar * malware_test_file = NULL;
+const uschar * real_sender_address;
+uschar * originator_home = US"/";
 size_t sz;
 
 struct passwd *pw;
@@ -2844,7 +2842,11 @@ on the second character (the one after '-'), to save some effort. */
         if (i+1 < argc) argrest = argv[++i]; else { badarg = TRUE; break; }
       (void) exim_str_fail_toolong(argrest, EXIM_DISPLAYMAIL_MAX, "-f");
       if (!*argrest)
-        *(sender_address = store_get(1, GET_UNTAINTED)) = '\0';  /* Ensure writeable memory */
+	{
+        uschar * s = store_get(1, GET_UNTAINTED);	/* Ensure writeable memory */
+        *s = '\0';
+        sender_address = s;
+	}
       else
         {
         const uschar * temp = argrest + Ustrlen(argrest) - 1;
diff --git a/src/src/expand.c b/src/src/expand.c
index 40cc8d73a..9a88d38ca 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -1804,20 +1804,19 @@ return g;
 /* A recipients list is available only during system message filtering,
 during ACL processing after DATA, and while expanding pipe commands
 generated from a system filter, but not elsewhere.  Note that this does
-not check for comman in the elements, and uses comma-space as seperator -
+not check for commas in the elements, and uses comma-space as seperator -
 so cannot be used as an exim list as-is. */
 
 static uschar *
 fn_recipients(void)
 {
-uschar * s;
 gstring * g = NULL;
 
 if (!f.enable_dollar_recipients) return NULL;
 
 for (int i = 0; i < recipients_count; i++)
   {
-  s = recipients_list[i].address;
+  const uschar * s = recipients_list[i].address;
   g = string_append2_listele_n(g, US", ", s, Ustrlen(s));
   }
 gstring_release_unused(g);
diff --git a/src/src/functions.h b/src/src/functions.h
index ba4fd215c..74cd15424 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -100,7 +100,7 @@ extern int     tlsa_lookup(const host_item *, dns_answer *, BOOL);
 /* Everything else... */
 
 extern acl_block *acl_read(uschar *(*)(void), uschar **);
-extern int     acl_check(int, uschar *, uschar *, uschar **, uschar **);
+extern int     acl_check(int, const uschar *, uschar *, uschar **, uschar **);
 extern uschar *acl_current_verb(void);
 extern int     acl_eval(int, uschar *, uschar **, uschar **);
 extern uschar *acl_standalone_setvar(const uschar *);
@@ -216,7 +216,7 @@ extern void    decode_bits(unsigned int *, size_t, int *,
 	           const uschar *, bit_table *, int, uschar *, int);
 extern void    delete_pid_file(void);
 extern void    deliver_local(address_item *, BOOL);
-extern address_item *deliver_make_addr(uschar *, BOOL);
+extern address_item *deliver_make_addr(const uschar *, BOOL);
 extern void    delivery_log(int, address_item *, int, uschar *);
 extern int     deliver_message(const uschar *, BOOL, BOOL);
 extern void    deliver_msglog(const char *, ...) PRINTF_FUNCTION(1,2);
@@ -335,7 +335,7 @@ extern int     ip_streamsocket(const uschar *, uschar **, int, host_item *);
 
 extern int     ipv6_nmtoa(int *, uschar *);
 
-extern uschar *local_part_quote(uschar *);
+extern const uschar *local_part_quote(const uschar *);
 extern int     log_open_as_exim(const uschar * const);
 extern void    log_close_all(void);
 
@@ -375,11 +375,11 @@ extern ssize_t mime_decode_base64(FILE *, FILE *, uschar *);
 extern int     mime_regex(const uschar **, BOOL);
 extern void    mime_set_anomaly(int);
 #endif
-extern uschar *moan_check_errorcopy(uschar *);
+extern uschar *moan_check_errorcopy(const uschar *);
 extern BOOL    moan_skipped_syntax_errors(uschar *, error_block *, uschar *,
                  BOOL, uschar *);
 extern void    moan_smtp_batch(uschar *, const char *, ...) PRINTF_FUNCTION(2,3);
-extern BOOL    moan_send_message(uschar *, int, error_block *eblock,
+extern BOOL    moan_send_message(const uschar *, int, error_block *eblock,
 		 header_line *, FILE *, uschar *);
 extern void    moan_tell_someone(uschar *, address_item *,
                  const uschar *, const char *, ...) PRINTF_FUNCTION(4,5);
@@ -440,7 +440,7 @@ extern void    readconf_save_config(const uschar *);
 extern void    read_message_body(BOOL);
 extern void    receive_bomb_out(uschar *, uschar *) NORETURN;
 extern BOOL    receive_check_fs(int);
-extern BOOL    receive_check_set_sender(uschar *);
+extern BOOL    receive_check_set_sender(const uschar *);
 extern BOOL    receive_msg(BOOL);
 extern int_eximarith_t receive_statvfs(BOOL, int *);
 extern void    receive_swallow_smtp(void);
@@ -636,7 +636,7 @@ extern BOOL    transport_pass_socket(const uschar *, const uschar *, const uscha
 			, unsigned, unsigned, unsigned
 #endif
 			);
-extern uschar *transport_rcpt_address(address_item *, BOOL);
+extern const uschar *transport_rcpt_address(address_item *, BOOL);
 extern BOOL    transport_set_up_command(const uschar ***, const uschar *,
 		 unsigned, int, address_item *, const uschar *, uschar **);
 extern void    transport_update_waiting(host_item *, uschar *);
@@ -644,7 +644,7 @@ extern BOOL    transport_write_block(transport_ctx *, uschar *, int, BOOL);
 extern void    transport_write_reset(int);
 extern BOOL    transport_write_string(int, const char *, ...);
 extern BOOL    transport_headers_send(transport_ctx *,
-                 BOOL (*)(transport_ctx *, uschar *, int));
+                 BOOL (*)(transport_ctx *, const uschar *, int));
 extern gstring * transport_show_supported(gstring *);
 extern BOOL    transport_write_message(transport_ctx *, int);
 extern void    tree_add_duplicate(const uschar *, address_item *);
@@ -679,7 +679,7 @@ extern int     verify_check_notblind(BOOL);
 extern int     verify_check_given_host(const uschar **, const host_item *);
 extern int     verify_check_this_host(const uschar **, unsigned int *,
 	         const uschar*, const uschar *, const uschar **);
-extern address_item *verify_checked_sender(uschar *);
+extern address_item *verify_checked_sender(const uschar *);
 extern void    verify_get_ident(int);
 extern void    verify_quota(uschar *);
 extern int     verify_quota_call(const uschar *, int, int, uschar **);
@@ -687,7 +687,7 @@ extern BOOL    verify_sender(int *, uschar **);
 extern BOOL    verify_sender_preliminary(int *, uschar **);
 extern void    version_init(void);
 
-extern BOOL    write_chunk(transport_ctx *, uschar *, int);
+extern BOOL    write_chunk(transport_ctx *, const uschar *, int);
 extern ssize_t write_to_fd_buf(int, const uschar *, size_t);
 extern uschar *wrap_header(const uschar *, unsigned, unsigned, const uschar *, unsigned);
 
diff --git a/src/src/globals.c b/src/src/globals.c
index 46ca72f20..bb5bd6b1a 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -610,8 +610,8 @@ address_item address_defaults = {
   }
 };
 
-uschar *address_file           = NULL;
-uschar *address_pipe           = NULL;
+const uschar *address_file           = NULL;
+const uschar *address_pipe           = NULL;
 tree_node *addresslist_anchor  = NULL;
 int     addresslist_count      = 0;
 gid_t  *admin_groups           = NULL;
@@ -683,7 +683,7 @@ int     body_linecount         = 0;
 int     body_zerocount         = 0;
 uschar *bounce_message_file    = NULL;
 uschar *bounce_message_text    = NULL;
-uschar *bounce_recipient       = NULL;
+const uschar *bounce_recipient = NULL;
 int     bounce_return_linesize_limit = 998;
 int     bounce_return_size_limit = 100*1024;
 uschar *bounce_sender_authentication = NULL;
@@ -842,14 +842,14 @@ const uschar *deliver_host_address = NULL;
 int     deliver_host_port      = 0;
 uschar *deliver_in_buffer      = NULL;
 ino_t   deliver_inode          = 0;
-uschar *deliver_localpart      = NULL;
+const uschar *deliver_localpart= NULL;
 uschar *deliver_localpart_data = NULL;
-uschar *deliver_localpart_orig = NULL;
-uschar *deliver_localpart_parent = NULL;
-uschar *deliver_localpart_prefix = NULL;
-uschar *deliver_localpart_prefix_v = NULL;
-uschar *deliver_localpart_suffix = NULL;
-uschar *deliver_localpart_suffix_v = NULL;
+const uschar *deliver_localpart_orig = NULL;
+const uschar *deliver_localpart_parent = NULL;
+const uschar *deliver_localpart_prefix = NULL;
+const uschar *deliver_localpart_prefix_v = NULL;
+const uschar *deliver_localpart_suffix = NULL;
+const uschar *deliver_localpart_suffix_v = NULL;
 uschar *deliver_out_buffer     = NULL;
 int     deliver_queue_load_max = -1;
 address_item  *deliver_recipients = NULL;
@@ -1276,8 +1276,8 @@ tree_node *ratelimiters_cmd    = NULL;
 tree_node *ratelimiters_conn   = NULL;
 tree_node *ratelimiters_mail   = NULL;
 uschar *raw_active_hostname    = NULL;
-uschar *raw_sender             = NULL;
-uschar **raw_recipients        = NULL;
+const uschar *raw_sender       = NULL;
+const uschar **raw_recipients  = NULL;
 int     raw_recipients_count   = 0;
 
 int     rcpt_count             = 0;
@@ -1289,7 +1289,7 @@ int     receive_linecount      = 0;
 int     receive_messagecount   = 0;
 int     receive_timeout        = 0;
 int     received_count         = 0;
-uschar *received_for           = NULL;
+const uschar *received_for     = NULL;
 
 /*  This is the default text for Received headers generated by Exim. The
 date  will be automatically added on the end. */
@@ -1348,7 +1348,7 @@ int     retry_data_expire      = 7*24*60*60;
 int     retry_interval_max     = 24*60*60;
 int     retry_maximum_timeout  = 0;        /* set from retry config */
 retry_config  *retries         = NULL;
-uschar *return_path            = NULL;
+const uschar *return_path            = NULL;
 int     rewrite_existflags     = 0;
 uschar *rfc1413_hosts          = US"@[]";
 int     rfc1413_query_timeout  = 0;
@@ -1456,10 +1456,10 @@ int     runrc                  = 0;
 
 uschar *search_error_message   = NULL;
 uschar *self_hostname          = NULL;
-uschar *sender_address         = NULL;
+const uschar *sender_address   = NULL;
 unsigned int sender_address_cache[(MAX_NAMED_LIST * 2)/32];
 uschar *sender_address_data    = NULL;
-uschar *sender_address_unrewritten = NULL;
+const uschar *sender_address_unrewritten = NULL;
 uschar *sender_data            = NULL;
 unsigned int sender_domain_cache[(MAX_NAMED_LIST * 2)/32];
 uschar *sender_fullhost        = NULL;
diff --git a/src/src/globals.h b/src/src/globals.h
index 9ec44487f..dff3762a8 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -350,8 +350,8 @@ extern uschar *acl_wherecodes[];       /* Response codes for ACL fails */
 extern uschar *acl_wherenames[];       /* Names for messages */
 extern address_item *addr_duplicate;   /* Duplicate address list */
 extern address_item address_defaults;  /* Default data for address item */
-extern uschar *address_file;           /* Name of file when delivering to one */
-extern uschar *address_pipe;           /* Pipe command when delivering to one */
+extern const uschar *address_file;           /* Name of file when delivering to one */
+extern const uschar *address_pipe;           /* Pipe command when delivering to one */
 extern tree_node *addresslist_anchor;  /* Tree of defined address lists */
 extern int     addresslist_count;      /* Number defined */
 extern gid_t  *admin_groups;           /* List of admin groups */
@@ -400,7 +400,7 @@ extern int     bsmtp_transaction_linecount; /* Start of last transaction */
 extern int     body_8bitmime;          /* sender declared BODY= ; 7=7BIT, 8=8BITMIME */
 extern uschar *bounce_message_file;    /* Template file */
 extern uschar *bounce_message_text;    /* One-liner */
-extern uschar *bounce_recipient;       /* When writing an errmsg */
+extern const uschar *bounce_recipient; /* When writing an errmsg */
 extern BOOL    bounce_return_body;     /* Include body in returned message */
 extern int     bounce_return_linesize_limit; /* Max line length in return */
 extern BOOL    bounce_return_message;  /* Include message in bounce */
@@ -517,14 +517,14 @@ extern const uschar *deliver_host_address; /* Address for remote delivery filter
 extern int     deliver_host_port;      /* Address for remote delivery filter */
 extern uschar *deliver_in_buffer;      /* Buffer for copying file */
 extern ino_t   deliver_inode;          /* Inode for appendfile */
-extern uschar *deliver_localpart;      /* The local part for delivery */
+extern const uschar *deliver_localpart;/* The local part for delivery */
 extern uschar *deliver_localpart_data; /* From local part lookup (de-tainted) */
-extern uschar *deliver_localpart_orig; /* The original local part for delivery */
-extern uschar *deliver_localpart_parent; /* The parent local part for delivery */
-extern uschar *deliver_localpart_prefix; /* The stripped prefix, if any */
-extern uschar *deliver_localpart_prefix_v; /* The stripped-prefix variable portion, if any */
-extern uschar *deliver_localpart_suffix; /* The stripped suffix, if any */
-extern uschar *deliver_localpart_suffix_v; /* The stripped-suffix variable portion, if any */
+extern const uschar *deliver_localpart_orig;		/* The original local part for delivery */
+extern const uschar *deliver_localpart_parent;		/* The parent local part for delivery */
+extern const uschar *deliver_localpart_prefix;		/* The stripped prefix, if any */
+extern const uschar *deliver_localpart_prefix_v;	/* The stripped-prefix variable portion, if any */
+extern const uschar *deliver_localpart_suffix;		/* The stripped suffix, if any */
+extern const uschar *deliver_localpart_suffix_v;	/* The stripped-suffix variable portion, if any */
 extern uschar *deliver_out_buffer;     /* Buffer for copying file */
 extern int     deliver_queue_load_max; /* Different value for queue running */
 extern address_item *deliver_recipients; /* Current set of addresses */
@@ -872,8 +872,8 @@ extern tree_node *ratelimiters_cmd;    /* Results of command ratelimit checks */
 extern tree_node *ratelimiters_conn;   /* Results of connection ratelimit checks */
 extern tree_node *ratelimiters_mail;   /* Results of per-mail ratelimit checks */
 extern uschar *raw_active_hostname;    /* Pre-expansion */
-extern uschar *raw_sender;             /* Before rewriting */
-extern uschar **raw_recipients;        /* Before rewriting */
+extern const uschar *raw_sender;       /* Before rewriting */
+extern const uschar **raw_recipients;  /* Before rewriting */
 extern int     raw_recipients_count;
 extern const uschar * rc_names[];      /* Mostly for debug output */
 extern int     rcpt_count;             /* Count of RCPT commands in a message */
@@ -885,7 +885,7 @@ extern int     receive_linecount;      /* Mainly for BSMTP errors */
 extern int     receive_messagecount;   /* Mainly for BSMTP errors */
 extern int     receive_timeout;        /* For non-SMTP acceptance */
 extern int     received_count;         /* Count of Received: headers */
-extern uschar *received_for;           /* For "for" field */
+extern const uschar *received_for;     /* For "for" field */
 extern uschar *received_header_text;   /* Definition of Received: header */
 extern int     received_headers_max;   /* Max count of Received: headers */
 extern struct timeval received_time;   /* Time the message started to be received */
@@ -926,7 +926,7 @@ extern retry_config *retries;          /* Chain of retry config information */
 extern int     retry_data_expire;      /* When to expire retry data */
 extern int     retry_interval_max;     /* Absolute maximum */
 extern int     retry_maximum_timeout;  /* The maximum timeout */
-extern uschar *return_path;            /* Return path for a message */
+extern const uschar *return_path;            /* Return path for a message */
 extern BOOL    return_path_remove;     /* Remove return-path headers */
 extern int     rewrite_existflags;     /* Indicate which headers have rewrites */
 extern uschar *rfc1413_hosts;          /* RFC hosts */
@@ -947,7 +947,7 @@ extern uschar *search_error_message;   /* Details of lookup problem */
 extern uschar *self_hostname;          /* Self host after routing->directors */
 extern unsigned int sender_address_cache[(MAX_NAMED_LIST * 2)/32]; /* Cache bits for sender */
 extern uschar *sender_address_data;    /* address_data from sender verify */
-extern uschar *sender_address_unrewritten; /* Set if rewritten by verify */
+extern const uschar *sender_address_unrewritten; /* Set if rewritten by verify */
 extern uschar *sender_data;            /* lookup result for senders */
 extern unsigned int sender_domain_cache[(MAX_NAMED_LIST * 2)/32]; /* Cache bits for sender domain */
 extern uschar *sender_fullhost;        /* Sender host name + address */
diff --git a/src/src/local_scan.h b/src/src/local_scan.h
index 153b4cb3c..ed9f26d62 100644
--- a/src/src/local_scan.h
+++ b/src/src/local_scan.h
@@ -153,9 +153,9 @@ field is always NULL except for one_time aliases that had errors_to on the
 routers that generated them. */
 
 typedef struct recipient_item {
-  uschar *address;              /* the recipient address */
+  const uschar *address;        /* the recipient address */
   int     pno;                  /* parent number for "one_time" alias, or -1 */
-  uschar *errors_to;            /* the errors_to address or NULL */
+  const uschar *errors_to;      /* the errors_to address or NULL */
   uschar *orcpt;                /* DSN orcpt */
   int     dsn_flags;            /* DSN flags */
 #ifdef EXPERIMENTAL_BRIGHTMAIL
@@ -181,7 +181,7 @@ extern uschar *message_id;             /* Internal id of message being handled *
 extern uschar *received_protocol;      /* Name of incoming protocol */
 extern int     recipients_count;       /* Number of recipients */
 extern recipient_item *recipients_list;/* List of recipient addresses */
-extern unsigned char *sender_address;  /* Sender address */
+extern const unsigned char *sender_address;	/* Sender address */
 extern uschar *sender_host_address;    /* IP address of sender, as chars */
 extern uschar *sender_host_authenticated; /* Name of authentication mechanism */
 extern uschar *sender_host_name;       /* Host name from lookup */
@@ -207,8 +207,8 @@ extern int     lss_match_domain(uschar *, uschar *);
 extern int     lss_match_local_part(uschar *, uschar *, BOOL);
 extern int     lss_match_address(uschar *, uschar *, BOOL);
 extern int     lss_match_host(uschar *, uschar *, uschar *);
-extern void    receive_add_recipient(uschar *, int);
-extern BOOL    receive_remove_recipient(uschar *);
+extern void    receive_add_recipient(const uschar *, int);
+extern BOOL    receive_remove_recipient(const uschar *);
 extern uschar *rfc2047_decode(uschar *, BOOL, const uschar *, int, int *,
 			      uschar **);
 extern int     smtp_fflush(void);
diff --git a/src/src/log.c b/src/src/log.c
index af6e8b01b..f85823520 100644
--- a/src/src/log.c
+++ b/src/src/log.c
@@ -1032,11 +1032,10 @@ if (  flags & LOG_RECIPIENTS
    && g->ptr < LOG_BUFFER_SIZE - 6
    && raw_recipients_count > 0)
   {
-  int i;
   g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " for", NULL);
-  for (i = 0; i < raw_recipients_count; i++)
+  for (int i = 0; i < raw_recipients_count; i++)
     {
-    uschar * s = raw_recipients[i];
+    const uschar * s = raw_recipients[i];
     if (LOG_BUFFER_SIZE - g->ptr < Ustrlen(s) + 3) break;
     g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " %s", s);
     }
diff --git a/src/src/moan.c b/src/src/moan.c
index 25595816c..c2e38d4f7 100644
--- a/src/src/moan.c
+++ b/src/src/moan.c
@@ -158,8 +158,8 @@ Returns:         TRUE if message successfully sent
 */
 
 BOOL
-moan_send_message(uschar *recipient, int ident, error_block *eblock,
-  header_line *headers, FILE *message_file, uschar *firstline)
+moan_send_message(const uschar * recipient, int ident, error_block * eblock,
+  header_line * headers, FILE * message_file, uschar * firstline)
 {
 int written = 0;
 int fd;
@@ -631,7 +631,7 @@ if (addr)
   fprintf(f, "\nThe following address(es) have yet to be delivered:\n");
   for (; addr; addr = addr->next)
     {
-    uschar * parent = addr->parent ? addr->parent->address : NULL;
+    const uschar * parent = addr->parent ? addr->parent->address : NULL;
     fprintf(f, "  %s", addr->address);
     if (parent) fprintf(f, " <%s>", parent);
     if (addr->basic_errno > 0) fprintf(f, ": %s", strerror(addr->basic_errno));
@@ -734,22 +734,23 @@ Returns:    additional recipient list or NULL
 */
 
 uschar *
-moan_check_errorcopy(uschar *recipient)
+moan_check_errorcopy(const uschar * recipient)
 {
-uschar *item, *localpart, *domain;
-const uschar *listptr = errors_copy;
+uschar * item;
+const uschar * localpart, * domain;
+const uschar * listptr = errors_copy;
 uschar *yield = NULL;
 int sep = 0;
 int llen;
 
-if (errors_copy == NULL) return NULL;
+if (!errors_copy) return NULL;
 
 /* Set up pointer to the local part and domain, and compute the
 length of the local part. */
 
 localpart = recipient;
 domain = Ustrrchr(recipient, '@');
-if (domain == NULL) return NULL;  /* should not occur, but avoid crash */
+if (!domain) return NULL;	/* should not occur, but avoid crash */
 llen = domain++ - recipient;
 
 /* Scan through the configured items */
diff --git a/src/src/queue.c b/src/src/queue.c
index cfe87e953..deedcde6f 100644
--- a/src/src/queue.c
+++ b/src/src/queue.c
@@ -585,7 +585,7 @@ for (int i = queue_run_in_order ? -1 : 0;
       else if (  deliver_selectstring_sender
 	      && !(f.deliver_selectstring_sender_regex
 		  ? regex_match(selectstring_regex_sender, sender_address, -1, NULL)
-		  : (strstric(sender_address, deliver_selectstring_sender, FALSE)
+		  : (strstric_c(sender_address, deliver_selectstring_sender, FALSE)
 		      != NULL)
 	      )   )
         {
@@ -601,10 +601,10 @@ for (int i = queue_run_in_order ? -1 : 0;
         int i;
         for (i = 0; i < recipients_count; i++)
           {
-          uschar *address = recipients_list[i].address;
+          const uschar * address = recipients_list[i].address;
           if (  (f.deliver_selectstring_regex
 		? regex_match(selectstring_regex, address, -1, NULL)
-                : (strstric(address, deliver_selectstring, FALSE) != NULL)
+                : (strstric_c(address, deliver_selectstring, FALSE) != NULL)
 		)
              && tree_search(tree_nonrecipients, address) == NULL
 	     )
@@ -1369,9 +1369,10 @@ switch(action)
 	  tree_search(tree_nonrecipients, recipients_list[i].address);
 	if (!delivered)
 	  {
-	  uschar * save_local = deliver_localpart;
+	  const uschar * save_local = deliver_localpart;
 	  const uschar * save_domain = deliver_domain;
-	  uschar * addr = recipients_list[i].address, * errmsg = NULL;
+	  const uschar * addr = recipients_list[i].address;
+	  uschar * errmsg = NULL;
 	  int start, end, dom;
 
 	  if (!parse_extract_address(addr, &errmsg, &start, &end, &dom, TRUE))
diff --git a/src/src/rda.c b/src/src/rda.c
index 9c2aa5022..7d329c580 100644
--- a/src/src/rda.c
+++ b/src/src/rda.c
@@ -466,7 +466,7 @@ Returns:     FALSE if data missing
 */
 
 static BOOL
-rda_read_string(int fd, uschar **sp)
+rda_read_string(int fd, uschar ** sp)
 {
 int len;
 
@@ -866,9 +866,9 @@ if (yield == FF_DELIVERED || yield == FF_NOTDELIVERED ||
   for (;;)
     {
     int i, reply_options;
-    address_item *addr;
-    uschar *recipient;
-    uschar *expandn[EXPAND_MAXN + 2];
+    address_item * addr;
+    uschar * recipient, * s;
+    uschar * expandn[EXPAND_MAXN + 2];
 
     /* First string is the address; NULL => end of addresses */
 
@@ -885,10 +885,11 @@ if (yield == FF_DELIVERED || yield == FF_NOTDELIVERED ||
 
     if (  read(fd, &addr->mode, sizeof(addr->mode)) != sizeof(addr->mode)
        || read(fd, &addr->flags, sizeof(addr->flags)) != sizeof(addr->flags)
-       || !rda_read_string(fd, &addr->prop.errors_address)
+       || !rda_read_string(fd, &s)
        || read(fd, &i, sizeof(i)) != sizeof(i)
        )
       goto DISASTER;
+    addr->prop.errors_address = s;
     addr->prop.ignore_error = (i != 0);
 
     /* Next comes a possible setting for $thisaddress and any numerical
diff --git a/src/src/receive.c b/src/src/receive.c
index 041898a6b..a459061ef 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -137,9 +137,9 @@ Returns:    TRUE for a trusted caller
 */
 
 BOOL
-receive_check_set_sender(uschar *newsender)
+receive_check_set_sender(const uschar * newsender)
 {
-uschar *qnewsender;
+const uschar * qnewsender;
 if (f.trusted_caller) return TRUE;
 if (!newsender || !untrusted_set_sender) return FALSE;
 qnewsender = Ustrchr(newsender, '@')
@@ -514,7 +514,7 @@ Returns:      nothing
 */
 
 void
-receive_add_recipient(uschar * recipient, int pno)
+receive_add_recipient(const uschar * recipient, int pno)
 {
 if (recipients_count >= recipients_list_max)
   {
@@ -591,7 +591,7 @@ Returns:      TRUE if it did remove something; FALSE otherwise
 */
 
 BOOL
-receive_remove_recipient(uschar *recipient)
+receive_remove_recipient(const uschar * recipient)
 {
 DEBUG(D_receive) debug_printf("receive_remove_recipient(\"%s\") called\n",
   recipient);
@@ -3600,7 +3600,7 @@ else
       /* Loop through recipients, responses must be in same order received */
       for (unsigned int c = 0; recipients_count > c; c++)
         {
-	uschar * addr= recipients_list[c].address;
+	const uschar * addr = recipients_list[c].address;
 	uschar * msg= US"PRDR R=<%s> %s";
 	uschar * code;
         DEBUG(D_receive)
diff --git a/src/src/rewrite.c b/src/src/rewrite.c
index ec5c2cffc..c0d173abf 100644
--- a/src/src/rewrite.c
+++ b/src/src/rewrite.c
@@ -119,9 +119,9 @@ for (rewrite_rule * rule = rewrite_rules;
   {
   int start, end, pdomain;
   int count = 0;
-  uschar *save_localpart;
-  const uschar *save_domain;
-  uschar *error, *new;
+  const uschar * save_localpart;
+  const uschar * save_domain;
+  uschar * error, * new;
   const uschar * newparsed;
 
   /* Come back here for a repeat after a successful rewrite. We do this
diff --git a/src/src/route.c b/src/src/route.c
index 82d51bc68..0bd87b837 100644
--- a/src/src/route.c
+++ b/src/src/route.c
@@ -859,12 +859,12 @@ Returns:       OK if all the tests succeed
 */
 
 static BOOL
-check_router_conditions(router_instance *r, address_item *addr, int verify,
-  struct passwd **pw, uschar **perror)
+check_router_conditions(router_instance * r, address_item * addr, int verify,
+  struct passwd ** pw, uschar ** perror)
 {
 int rc;
-uschar *check_local_part;
-unsigned int *localpart_cache;
+uschar * check_local_part;
+unsigned int * localpart_cache;
 
 /* Reset variables to hold a home directory and data from lookup of a domain or
 local part, and ensure search_find_defer is unset, in case there aren't any
@@ -928,15 +928,12 @@ because that doesn't have the prefix or suffix stripped. A bit of massaging is
 required. Also, we only use the match cache for local parts that have not had
 a prefix or suffix stripped. */
 
+check_local_part = string_copy(addr->cc_local_part);
 if (!addr->prefix && !addr->suffix)
-  {
   localpart_cache = addr->localpart_cache;
-  check_local_part = addr->cc_local_part;
-  }
 else
   {
   localpart_cache = NULL;
-  check_local_part = string_copy(addr->cc_local_part);
   if (addr->prefix)
     check_local_part += Ustrlen(addr->prefix);
   if (addr->suffix)
diff --git a/src/src/routers/accept.c b/src/src/routers/accept.c
index 63c8c22e4..8b72bfac1 100644
--- a/src/src/routers/accept.c
+++ b/src/src/routers/accept.c
@@ -104,9 +104,9 @@ accept_router_options_block *ob =
   (accept_router_options_block *)(rblock->options_block);
 */
 int rc;
-uschar *errors_to;
-uschar *remove_headers;
-header_line *extra_headers;
+const uschar * errors_to;
+uschar * remove_headers;
+header_line * extra_headers;
 
 DEBUG(D_route) debug_printf("%s router called for %s\n  domain = %s\n",
   rblock->name, addr->address, addr->domain);
diff --git a/src/src/routers/redirect.c b/src/src/routers/redirect.c
index 6a17c2f8d..4c8e30563 100644
--- a/src/src/routers/redirect.c
+++ b/src/src/routers/redirect.c
@@ -306,9 +306,8 @@ redirect_router_options_block *ob =
 
 while (generated)
   {
-  address_item *parent;
-  address_item *next = generated;
-  uschar *errors_address = next->prop.errors_address;
+  address_item * next = generated, * parent;
+  const uschar * errors_address = next->prop.errors_address;
 
   generated = next->next;
   next->parent = addr;
diff --git a/src/src/routers/rf_functions.h b/src/src/routers/rf_functions.h
index 91ccfb132..40d4cde1c 100644
--- a/src/src/routers/rf_functions.h
+++ b/src/src/routers/rf_functions.h
@@ -15,7 +15,7 @@ extern void rf_add_generated(router_instance *, address_item **,
 extern void rf_change_domain(address_item *, const uschar *, BOOL, address_item **);
 extern uschar *rf_expand_data(address_item *, uschar *, int *);
 extern int  rf_get_errors_address(address_item *, router_instance *,
-              int, uschar **);
+              int, const uschar **);
 extern int  rf_get_munge_headers(address_item *, router_instance *,
               header_line **, uschar **);
 extern BOOL rf_get_transport(uschar *, transport_instance **,  address_item *,
diff --git a/src/src/routers/rf_get_errors_address.c b/src/src/routers/rf_get_errors_address.c
index f70bdf25e..222d157d8 100644
--- a/src/src/routers/rf_get_errors_address.c
+++ b/src/src/routers/rf_get_errors_address.c
@@ -35,13 +35,13 @@ Returns:       OK if no problem
 */
 
 int
-rf_get_errors_address(address_item *addr, router_instance *rblock,
-  int verify, uschar **errors_to)
+rf_get_errors_address(address_item * addr, router_instance * rblock,
+  int verify, const uschar ** errors_to)
 {
 uschar *s;
 
 *errors_to = addr->prop.errors_address;
-if (rblock->errors_to == NULL) return OK;
+if (!rblock->errors_to) return OK;
 
 s = expand_string(rblock->errors_to);
 
@@ -84,17 +84,14 @@ if (verify != v_none)
 else
   {
   BOOL save_address_test_mode = f.address_test_mode;
-  int save1 = 0;
+  const uschar * save_sender = sender_address;
   int i;
   const uschar ***p;
   const uschar *address_expansions_save[ADDRESS_EXPANSIONS_COUNT];
   address_item *snew = deliver_make_addr(s, FALSE);
 
   if (sender_address)
-    {
-    save1 = sender_address[0];
-    sender_address[0] = 0;
-    }
+    sender_address = US"";
 
   for (i = 0, p = address_expansions; *p;)
     address_expansions_save[i++] = **p++;
@@ -124,7 +121,7 @@ else
   for (i = 0, p = address_expansions; *p; )
     **p++ = address_expansions_save[i++];
 
-  if (sender_address) sender_address[0] = save1;
+  sender_address = save_sender;
   }
 
 return OK;
diff --git a/src/src/spf.c b/src/src/spf.c
index e72051708..2e3f861eb 100644
--- a/src/src/spf.c
+++ b/src/src/spf.c
@@ -341,7 +341,8 @@ else for (int i = 0; i < SPF_response_messages(spf_response); i++)
 Return: OK/FAIL  */
 
 int
-spf_process(const uschar **listptr, uschar *spf_envelope_sender, int action)
+spf_process(const uschar ** listptr, const uschar * spf_envelope_sender,
+  int action)
 {
 int sep = 0;
 const uschar *list = *listptr;
diff --git a/src/src/spf.h b/src/src/spf.h
index 76c7522bd..afee742ed 100644
--- a/src/src/spf.h
+++ b/src/src/spf.h
@@ -29,7 +29,7 @@ typedef struct spf_result_id {
 gstring * spf_lib_version_report(gstring *);
 BOOL spf_init(void);
 BOOL spf_conn_init(uschar *, uschar *);
-int  spf_process(const uschar **, uschar *, int);
+int  spf_process(const uschar **, const uschar *, int);
 void spf_response_debug(SPF_response_t *);
 
 #define SPF_PROCESS_NORMAL  0
diff --git a/src/src/spool_in.c b/src/src/spool_in.c
index 588effd2c..3e3b246ab 100644
--- a/src/src/spool_in.c
+++ b/src/src/spool_in.c
@@ -456,9 +456,12 @@ n = Ustrlen(big_buffer);
 if (n < 3 || big_buffer[0] != '<' || big_buffer[n-2] != '>')
   goto SPOOL_FORMAT_ERROR;
 
-sender_address = store_get(n-2, GET_TAINTED);
-Ustrncpy(sender_address, big_buffer+1, n-3);
-sender_address[n-3] = 0;
+ {
+  uschar * s = store_get(n-2, GET_TAINTED);
+  Ustrncpy(s, big_buffer+1, n-3);
+  s[n-3] = '\0';
+  sender_address = s;
+ }
 
 where = US"time";
 if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
diff --git a/src/src/structs.h b/src/src/structs.h
index 214819482..209d657c6 100644
--- a/src/src/structs.h
+++ b/src/src/structs.h
@@ -526,7 +526,7 @@ typedef struct address_item_propagated {
   uschar *address_data;           /* arbitrary data to keep with the address */
   uschar *domain_data;            /* from "domains" lookup */
   uschar *localpart_data;         /* from "local_parts" lookup */
-  uschar *errors_address;         /* where to send errors (NULL => sender) */
+  const uschar *errors_address;         /* where to send errors (NULL => sender) */
   header_line *extra_headers;     /* additional headers */
   uschar *remove_headers;         /* list of those to remove */
   void   *variables;		  /* router-vasriables */
@@ -561,15 +561,15 @@ typedef struct address_item {
   reply_item *reply;              /* data for autoreply */
   retry_item *retries;            /* chain of retry information */
 
-  uschar *address;                /* address being delivered or routed */
+  const uschar *address;                /* address being delivered or routed */
   uschar *unique;                 /* used for disambiguating */
-  uschar *cc_local_part;          /* caseful local part */
-  uschar *lc_local_part;          /* lowercased local part */
-  uschar *local_part;             /* points to cc or lc version */
-  uschar *prefix;                 /* stripped prefix of local part */
-  uschar *prefix_v;		  /*  variable part of above */
-  uschar *suffix;                 /* stripped suffix of local part */
-  uschar *suffix_v;		  /*  variable part of above */
+  const uschar *cc_local_part;    /* caseful local part */
+  const uschar *lc_local_part;    /* lowercased local part */
+  const uschar *local_part;       /* points to cc or lc version */
+  const uschar *prefix;           /* stripped prefix of local part */
+  const uschar *prefix_v;	  /*  variable part of above */
+  const uschar *suffix;           /* stripped suffix of local part */
+  const uschar *suffix_v;	  /*  variable part of above */
   const uschar *domain;           /* working domain (lower cased) */
 
   uschar *address_retry_key;      /* retry key including full address */
@@ -580,7 +580,7 @@ typedef struct address_item {
   uschar *message;                /* error message */
   uschar *user_message;           /* error message that can be sent over SMTP
                                      or quoted in bounce message */
-  uschar *onetime_parent;         /* saved original parent for onetime */
+  const uschar *onetime_parent;         /* saved original parent for onetime */
   uschar **pipe_expandn;          /* numeric expansions for pipe from filter */
   uschar *return_filename;        /* name of return file */
   uschar *self_hostname;          /* after self=pass */
diff --git a/src/src/transport.c b/src/src/transport.c
index b3b05c0a3..a0f097579 100644
--- a/src/src/transport.c
+++ b/src/src/transport.c
@@ -434,10 +434,10 @@ Returns:     TRUE on success, FALSE on failure (with errno preserved)
 */
 
 BOOL
-write_chunk(transport_ctx * tctx, uschar *chunk, int len)
+write_chunk(transport_ctx * tctx, const uschar * chunk, int len)
 {
-uschar *start = chunk;
-uschar *end = chunk + len;
+const uschar * start = chunk;
+const uschar * end = chunk + len;
 int mlen = DELIVER_OUT_BUFFER_SIZE - nl_escape_length - 2;
 
 /* The assumption is made that the check string will never stretch over move
@@ -474,7 +474,7 @@ if (nl_partial_match >= 0)
 for possible escaping. The code for the non-NL route should be as fast as
 possible. */
 
-for (uschar * ptr = start; ptr < end; ptr++)
+for (const uschar * ptr = start; ptr < end; ptr++)
   {
   int ch, len;
 
@@ -580,7 +580,7 @@ Arguments:
 Returns:            a string
 */
 
-uschar *
+const uschar *
 transport_rcpt_address(address_item *addr, BOOL include_affixes)
 {
 uschar *at;
@@ -704,7 +704,7 @@ Returns:                TRUE on success; FALSE on failure.
 */
 BOOL
 transport_headers_send(transport_ctx * tctx,
-  BOOL (*sendfn)(transport_ctx * tctx, uschar * s, int len))
+  BOOL (*sendfn)(transport_ctx * tctx, const uschar * s, int len))
 {
 const uschar * list;
 transport_instance * tblock = tctx ? tctx->tblock : NULL;
@@ -2302,7 +2302,7 @@ if (flags & TSUC_EXPAND_ARGS)
       address_pipe_argv = store_get((address_pipe_max_args+1)*sizeof(uschar *), GET_UNTAINTED);
 
       /* +1 because addr->local_part[0] == '|' since af_force_command is set */
-      s = expand_string(addr->local_part + 1);
+      s = expand_cstring(addr->local_part + 1);
 
       if (!s || !*s)
         {
diff --git a/src/src/transports/appendfile.c b/src/src/transports/appendfile.c
index 2c7bea11d..ec41ca035 100644
--- a/src/src/transports/appendfile.c
+++ b/src/src/transports/appendfile.c
@@ -502,7 +502,7 @@ Returns:     nothing
 */
 
 static void
-notify_comsat(uschar *user, off_t offset)
+notify_comsat(const uschar * user, off_t offset)
 {
 struct servent *sp;
 host_item host;
diff --git a/src/src/transports/pipe.c b/src/src/transports/pipe.c
index b9cc32df1..64c70e719 100644
--- a/src/src/transports/pipe.c
+++ b/src/src/transports/pipe.c
@@ -292,7 +292,7 @@ Returns:             TRUE if all went well; otherwise an error will be
 */
 
 static BOOL
-set_up_direct_command(const uschar *** argvptr, uschar * cmd,
+set_up_direct_command(const uschar *** argvptr, const uschar * cmd,
   BOOL expand_arguments, int expand_fail, address_item * addr, uschar * tname,
   pipe_transport_options_block * ob)
 {
@@ -414,8 +414,8 @@ Returns:             TRUE if all went well; otherwise an error will be
 */
 
 static BOOL
-set_up_shell_command(const uschar ***argvptr, uschar *cmd,
-  BOOL expand_arguments, int expand_fail, address_item *addr, uschar *tname)
+set_up_shell_command(const uschar *** argvptr, const uschar * cmd,
+  BOOL expand_arguments, int expand_fail, address_item * addr, uschar * tname)
 {
 const uschar **argv;
 
@@ -462,10 +462,10 @@ if (expand_arguments)
       }
 
     g = string_cat(g, q);
-    argv[2] = (cmd = string_from_gstring(g)) ? expand_string(cmd) : NULL;
+    argv[2] = (cmd = string_from_gstring(g)) ? expand_cstring(cmd) : NULL;
     }
   else
-    argv[2] = expand_string(cmd);
+    argv[2] = expand_cstring(cmd);
 
   f.enable_dollar_recipients = FALSE;
 
@@ -518,11 +518,12 @@ pipe_transport_options_block *ob =
 int timeout = ob->timeout;
 BOOL written_ok = FALSE;
 BOOL expand_arguments;
-const uschar **argv;
-uschar *envp[50];
-const uschar *envlist = ob->environment;
-uschar *cmd, *ss;
-uschar *eol = ob->use_crlf ? US"\r\n" : US"\n";
+const uschar ** argv;
+uschar * envp[50];
+const uschar * envlist = ob->environment;
+const uschar * cmd;
+uschar * ss;
+uschar * eol = ob->use_crlf ? US"\r\n" : US"\n";
 transport_ctx tctx = {
   .tblock = tblock,
   .addr = addr,
diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index 8c00a1ef2..c53e1000f 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -656,8 +656,7 @@ static void
 deferred_event_raise(address_item * addr, host_item * host, uschar * evstr)
 {
 uschar * action = addr->transport->event_action;
-const uschar * save_domain;
-uschar * save_local;
+const uschar * save_domain, * save_local;
 
 if (!action)
   return;
@@ -1793,7 +1792,7 @@ return FALSE;
 
 typedef struct smtp_compare_s
 {
-    uschar *			current_sender_address;
+    const uschar *		current_sender_address;
     struct transport_instance *	tblock;
 } smtp_compare_t;
 
@@ -1803,7 +1802,7 @@ sender_address, helo_data and tls_certificate if enabled.
 */
 
 static uschar *
-smtp_local_identity(uschar * sender, struct transport_instance * tblock)
+smtp_local_identity(const uschar * sender, struct transport_instance * tblock)
 {
 address_item * addr1;
 uschar * if1 = US"";
@@ -1811,7 +1810,7 @@ uschar * helo1 = US"";
 #ifndef DISABLE_TLS
 uschar * tlsc1 = US"";
 #endif
-uschar * save_sender_address = sender_address;
+const uschar * save_sender_address = sender_address;
 uschar * local_identity = NULL;
 smtp_transport_options_block * ob = SOB tblock->options_block;
 
@@ -3434,7 +3433,7 @@ buffer. */
 sx->pending_MAIL = TRUE;     /* The block starts with MAIL */
 
   {
-  uschar * s = sx->from_addr;
+  const uschar * s = sx->from_addr;
 #ifdef SUPPORT_I18N
   uschar * errstr = NULL;
 
@@ -3505,7 +3504,7 @@ for (addr = sx->first_addr, address_count = 0, pipe_limit = 100;
   {
   int cmds_sent;
   BOOL no_flush;
-  uschar * rcpt_addr;
+  const uschar * rcpt_addr;
 
 #ifdef EXPERIMENTAL_ESMTP_LIMITS
   if (  sx->single_rcpt_domain					/* restriction on domains */
diff --git a/src/src/transports/smtp.h b/src/src/transports/smtp.h
index 0d15b9626..1f888ee0b 100644
--- a/src/src/transports/smtp.h
+++ b/src/src/transports/smtp.h
@@ -140,7 +140,7 @@ typedef struct {
 
 /* smtp connect context */
 typedef struct {
-  uschar *		from_addr;
+  const uschar *	from_addr;
   address_item *	addrlist;
 
   smtp_connect_args	conn_args;
diff --git a/src/src/verify.c b/src/src/verify.c
index 6361aa434..bc3666285 100644
--- a/src/src/verify.c
+++ b/src/src/verify.c
@@ -105,8 +105,8 @@ Return: TRUE if result found
 */
 
 static BOOL
-cached_callout_lookup(address_item * addr, uschar * address_key,
-  uschar * from_address, int * opt_ptr, uschar ** pm_ptr,
+cached_callout_lookup(address_item * addr, const uschar * address_key,
+  const uschar * from_address, int * opt_ptr, uschar ** pm_ptr,
   int * yield, uschar ** failure_ptr,
   dbdata_callout_cache * new_domain_record, int * old_domain_res)
 {
@@ -278,10 +278,10 @@ return FALSE;
 */
 static void
 cache_callout_write(dbdata_callout_cache * dom_rec, const uschar * domain,
-  int done, dbdata_callout_cache_address * addr_rec, uschar * address_key)
+  int done, dbdata_callout_cache_address * addr_rec, const uschar * address_key)
 {
 open_db dbblock;
-open_db *dbm_file = NULL;
+open_db * dbm_file = NULL;
 
 /* If we get here with done == TRUE, a successful callout happened, and yield
 will be set OK or FAIL according to the response to the RCPT command.
@@ -502,11 +502,11 @@ do_callout(address_item *addr, host_item *host_list, transport_feedback *tf,
 int yield = OK;
 int old_domain_cache_result = ccache_accept;
 BOOL done = FALSE;
-uschar *address_key;
-uschar *from_address;
-uschar *random_local_part = NULL;
-const uschar *save_deliver_domain = deliver_domain;
-uschar **failure_ptr = options & vopt_is_recipient
+const uschar * address_key;
+const uschar * from_address;
+uschar * random_local_part = NULL;
+const uschar * save_deliver_domain = deliver_domain;
+uschar ** failure_ptr = options & vopt_is_recipient
   ? &recipient_verify_failure : &sender_verify_failure;
 dbdata_callout_cache new_domain_record;
 dbdata_callout_cache_address new_address_record;
@@ -771,7 +771,7 @@ tls_retry_connection:
 
     if (random_local_part)
       {
-      uschar * main_address = addr->address;
+      const uschar * main_address = addr->address;
       const uschar * rcpt_domain = addr->domain;
 
 #ifdef SUPPORT_I18N
@@ -942,7 +942,7 @@ tls_retry_connection:
 
       if (done)
 	{
-	uschar * main_address = addr->address;
+	const uschar * main_address = addr->address;
 
 	/*XXX oops, affixes */
 	addr->address = string_sprintf("postmaster@%.1000s", addr->domain);
@@ -1286,7 +1286,7 @@ return FALSE;
 
 
 static BOOL
-_cutthrough_puts(uschar * cp, int n)
+_cutthrough_puts(const uschar * cp, int n)
 {
 while(n--)
  {
@@ -1301,7 +1301,7 @@ return TRUE;
 
 /* Buffered output of counted data block.   Return boolean success */
 static BOOL
-cutthrough_puts(uschar * cp, int n)
+cutthrough_puts(const uschar * cp, int n)
 {
 if (cutthrough.cctx.sock < 0) return TRUE;
 if (_cutthrough_puts(cp, n))  return TRUE;
@@ -1407,9 +1407,9 @@ return cutthrough_response(&cutthrough.cctx, '3', NULL, CUTTHROUGH_DATA_TIMEOUT)
 
 /* tctx arg only to match write_chunk() */
 static BOOL
-cutthrough_write_chunk(transport_ctx * tctx, uschar * s, int len)
+cutthrough_write_chunk(transport_ctx * tctx, const uschar * s, int len)
 {
-uschar * s2;
+const uschar * s2;
 while(s && (s2 = Ustrchr(s, '\n')))
  {
  if(!cutthrough_puts(s, s2-s) || !cutthrough_put_nl())
@@ -1697,16 +1697,16 @@ int yield = OK;
 int verify_type = expn ? v_expn :
    f.address_test_mode ? v_none :
           options & vopt_is_recipient ? v_recipient : v_sender;
-address_item *addr_list;
-address_item *addr_new = NULL;
-address_item *addr_remote = NULL;
-address_item *addr_local = NULL;
-address_item *addr_succeed = NULL;
-uschar **failure_ptr = options & vopt_is_recipient
+address_item * addr_list;
+address_item * addr_new = NULL;
+address_item * addr_remote = NULL;
+address_item * addr_local = NULL;
+address_item * addr_succeed = NULL;
+uschar ** failure_ptr = options & vopt_is_recipient
   ? &recipient_verify_failure : &sender_verify_failure;
-uschar *ko_prefix, *cr;
-uschar *address = vaddr->address;
-uschar *save_sender;
+uschar * ko_prefix, * cr;
+const uschar * address = vaddr->address;
+const uschar * save_sender;
 uschar null_sender[] = { 0 };             /* Ensure writeable memory */
 
 /* Clear, just in case */
@@ -1751,9 +1751,8 @@ may have been set by domains and local part tests during an ACL. */
 
 if (global_rewrite_rules)
   {
-  uschar *old = address;
-  /* deconst ok as address was not const */
-  address = US rewrite_address(address, options & vopt_is_recipient, FALSE,
+  const uschar * old = address;
+  address = rewrite_address(address, options & vopt_is_recipient, FALSE,
     global_rewrite_rules, rewrite_existflags);
   if (address != old)
     {
@@ -1919,8 +1918,8 @@ while (addr_new)
 	  if (tf.hosts && (!host_list || tf.hosts_override))
 	    {
 	    uschar *s;
-	    const uschar *save_deliver_domain = deliver_domain;
-	    uschar *save_deliver_localpart = deliver_localpart;
+	    const uschar * save_deliver_domain = deliver_domain;
+	    const uschar * save_deliver_localpart = deliver_localpart;
 
 	    host_list = NULL;    /* Ignore the router's hosts */
 
@@ -2455,11 +2454,11 @@ verify_check_notblind(BOOL case_sensitive)
 for (int i = 0; i < recipients_count; i++)
   {
   BOOL found = FALSE;
-  uschar *address = recipients_list[i].address;
+  const uschar * address = recipients_list[i].address;
 
   for (header_line * h = header_list; !found && h; h = h->next)
     {
-    uschar *colon, *s;
+    uschar * colon, * s;
 
     if (h->type != htype_to && h->type != htype_cc) continue;
 
@@ -2533,7 +2532,7 @@ Returns:     pointer to an address item, or NULL
 */
 
 address_item *
-verify_checked_sender(uschar *sender)
+verify_checked_sender(const uschar * sender)
 {
 for (address_item * addr = sender_verified_list; addr; addr = addr->next)
   if (Ustrcmp(sender, addr->address) == 0) return addr;

commit 9643095d7cae6866b716d361a45c9a95f605040c
Author: Andrew Aitchison <exim@aitchison.me.uk>
Date:   Thu Nov 16 17:09:57 2023 +0000

    Compiler quiteneing

diff --git a/src/src/globals.h b/src/src/globals.h
index dff3762a8..fe4a1610a 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -323,7 +323,7 @@ extern uschar *acl_smtp_connect;       /* ACL run on SMTP connection */
 extern uschar *acl_smtp_data;          /* ACL run after DATA received */
 #ifndef DISABLE_PRDR
 extern uschar *acl_smtp_data_prdr;     /* ACL run after DATA received if in PRDR mode*/
-const extern pcre2_code *regex_PRDR;         /* For recognizing PRDR settings */
+extern const pcre2_code *regex_PRDR;         /* For recognizing PRDR settings */
 #endif
 #ifndef DISABLE_DKIM
 extern uschar *acl_smtp_dkim;          /* ACL run for DKIM signatures / domains */
@@ -752,7 +752,7 @@ extern uschar *message_size_limit;     /* As it says */
 #ifdef SUPPORT_I18N
 extern BOOL    message_smtputf8;       /* Internationalized mail handling */
 extern int     message_utf8_downconvert; /* convert from utf8 */
-const extern pcre2_code *regex_UTF8;         /* For recognizing SMTPUTF8 settings */
+extern const   pcre2_code *regex_UTF8;         /* For recognizing SMTPUTF8 settings */
 #endif
 extern uschar  message_subdir[];       /* Subdirectory for messages */
 extern const uschar *message_reference;/* Reference for error messages */

commit 79670d3c32ccb37fe06f25d8192943b58606a32a
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Nov 17 16:55:17 2023 +0000

    Lookups: Fix dnsdb lookup of multi-chunk TXT.  Bug 3054
    
    Broken=by: f6b1f8e7d642

diff --git a/src/src/dns.c b/src/src/dns.c
index a652dcd31..74c5a58c5 100644
--- a/src/src/dns.c
+++ b/src/src/dns.c
@@ -431,7 +431,9 @@ namelen = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, dnss->aptr,
 if (namelen < 0) goto null_return;
 
 /* Move the pointer past the name and fill in the rest of the data structure
-from the following bytes. */
+from the following bytes.  We seem to be assuming here that the RR blob passed
+to us by the resolver library is the same as that defined for an RR by RFC 1035
+section 3.2.1 */
 
 TRACE trace = "R-name";
 if (dnss_inc_aptr(dnsa, dnss, namelen)) goto null_return;
diff --git a/src/src/lookups/dnsdb.c b/src/src/lookups/dnsdb.c
index ff51dec23..8288896ca 100644
--- a/src/src/lookups/dnsdb.c
+++ b/src/src/lookups/dnsdb.c
@@ -387,38 +387,31 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))
         }
 
       /* Other kinds of record just have one piece of data each, but there may be
-      several of them, of course. */
+      several of them, of course.  TXT & SPF can have data in multiple chunks. */
 
       if (yield->ptr) yield = string_catn(yield, outsep, 1);
 
       if (type == T_TXT || type == T_SPF)
-        {
-        if (!outsep2)			/* output only the first item of data */
+	for (unsigned data_offset = 0; data_offset + 1 < rr->size; )
 	  {
-	  uschar n = (rr->data)[0];
-	  /* size byte + data bytes must not excced the RRs length */
-	  if (n + 1 <= rr->size)
-	    yield = string_catn(yield, US (rr->data+1), n);
+	  uschar chunk_len = (rr->data)[data_offset];
+	  int remain;
+
+	  if (outsep2 && *outsep2 && data_offset != 0)
+	    yield = string_catn(yield, outsep2, 1);
+
+	  /* Apparently there are resolvers that do not check RRs before passing
+	  them on, and glibc fails to do so.  So every application must...
+	  Check for chunk len exceeding RR */
+
+	  remain = rr->size - ++data_offset;
+	  if (chunk_len > remain)
+	    chunk_len = remain;
+	  yield = string_catn(yield, US ((rr->data) + data_offset), chunk_len);
+	  data_offset += chunk_len;
+
+	  if (!outsep2) break;		/* output only the first chunk of the RR */
 	  }
-        else
-          for (unsigned data_offset = 0; data_offset < rr->size; )
-            {
-            uschar chunk_len = (rr->data)[data_offset];
-	    int remain = rr->size - data_offset;
-
-	    /* Apparently there are resolvers that do not check RRs before passing
-	    them on, and glibc fails to do so.  So every application must...
-	    Check for chunk len exceeding RR */
-
-	    if (chunk_len > remain)
-	      chunk_len = remain;
-
-            if (*outsep2  && data_offset != 0)
-              yield = string_catn(yield, outsep2, 1);
-            yield = string_catn(yield, US ((rr->data) + ++data_offset), --chunk_len);
-            data_offset += chunk_len;
-            }
-        }
       else if (type == T_TLSA)
 	if (rr->size < 3)
 	  continue;

commit 1d19531abc45f656eff8f3dd5ba6986d919fee11
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sun Nov 19 11:31:45 2023 +0000

    Support old-format message_id spoolfiles for mailq / -bp.  Bug 3050
    
    Broken-by: 46a36afae41f

diff --git a/src/src/queue.c b/src/src/queue.c
index deedcde6f..2c3d014f5 100644
--- a/src/src/queue.c
+++ b/src/src/queue.c
@@ -961,7 +961,9 @@ each time. */
 
 if (option == QL_MSGID_ONLY)	/* Print only the message IDs from the chain */
   for (; qf; qf = qf->next)
-    fprintf(stdout, "%.*s\n", MESSAGE_ID_LENGTH, qf->text);
+    fprintf(stdout, "%.*s\n",
+      is_old_message_id(qf->text) ? MESSAGE_ID_LENGTH_OLD : MESSAGE_ID_LENGTH,
+      qf->text);
 
 else for (;
 	  qf && (reset_point = store_mark());
@@ -1021,7 +1023,9 @@ else for (;
     }
 
   fprintf(stdout, "%s %.*s",
-    string_format_size(size, big_buffer), MESSAGE_ID_LENGTH, qf->text);
+    string_format_size(size, big_buffer),
+    is_old_message_id(qf->text) ? MESSAGE_ID_LENGTH_OLD : MESSAGE_ID_LENGTH,
+    qf->text);
 
   if (env_read && sender_address)
     {

commit 8213f508a8c66e7ed8e00a9123f72763ff99010b
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sat Nov 25 15:46:12 2023 +0000

    DMARC: fix reporting, no-ARC-support case

diff --git a/src/src/dmarc.c b/src/src/dmarc.c
index 042ebe982..f684e08ea 100644
--- a/src/src/dmarc.c
+++ b/src/src/dmarc.c
@@ -311,7 +311,7 @@ g = string_fmt_append(g, "align_dkim %d\nalign_spf %d\naction %d\n",
 # ifdef EXPERIMENTAL_ARC
 g = arc_dmarc_hist_append(g);
 # else
-g = string_fmt_append(g, "arc %d\narc_policy $d json:[]\n",
+g = string_fmt_append(g, "arc %d\narc_policy %d json:[]\n",
 		      ARES_RESULT_UNKNOWN, DMARC_ARC_POLICY_RESULT_UNUSED);
 # endif
 #endif

commit 33c9e43ec040c0a29394d8e3b745180ddddc0ed8
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sat Nov 25 16:47:35 2023 +0000

    DMARC: fix reporting, ARC-support but not in message

diff --git a/src/src/arc.c b/src/src/arc.c
index defe9b171..bd2fa3bae 100644
--- a/src/src/arc.c
+++ b/src/src/arc.c
@@ -2011,7 +2011,7 @@ if (arc_state)
   g = string_catn(g, US" ]\n", 3);
   }
 else
-  g = string_fmt_append(g, "arc %d\narc_policy $d json:[]\n",
+  g = string_fmt_append(g, "arc %d\narc_policy %d json:[]\n",
 			ARES_RESULT_UNKNOWN, DMARC_ARC_POLICY_RESULT_UNUSED);
 return g;
 }

commit 5d0d124a8b49b484cda869bcb0d3e5224716d276
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sun Nov 26 20:28:07 2023 +0000

    Unbreak DISABLE_TLS_RESUME build
    
    Broken-by: 5d5ad9fb16a2

diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index c53e1000f..4cf58436d 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -761,13 +761,14 @@ return TRUE;
 }
 
 
+#if !defined(DISABLE_TLS) && !defined(DISABLE_TLS_RESUME)
+
 /* Grab a string differentiating server behind a loadbalancer, for TLS
 resumption when such servers do not share a session-cache */
 
 static void
 ehlo_response_lbserver(smtp_context * sx, const uschar * name_extract)
 {
-#if !defined(DISABLE_TLS) && !defined(DISABLE_TLS_RESUME)
 const uschar * s;
 uschar * save_item = iterate_item;
 
@@ -778,8 +779,8 @@ s = expand_cstring(name_extract);
 iterate_item = save_item;
 sx->conn_args.host_lbserver = s && !*s ? NULL : s;
 sx->conn_args.have_lbserver = TRUE;
-#endif
 }
+#endif
 
 
 
@@ -1075,9 +1076,11 @@ if (pending_BANNER)
     }
   /*XXX EXPERIMENTAL_ESMTP_LIMITS ? */
 
+# ifndef DISABLE_TLS_RESUME
   s = ((smtp_transport_options_block *)sx->conn_args.ob)->host_name_extract;
   if (!s) s = HNE_DEFAULT;
   ehlo_response_lbserver(sx, s);
+# endif
   }
 
 if (pending_EHLO)
@@ -2482,13 +2485,14 @@ goto SEND_QUIT;
     ob->tls_tempfail_tryclear = FALSE;
     smtp_command = US"SSL-on-connect";
 
+# ifndef DISABLE_TLS_RESUME
     /* Having no EHLO response yet, cannot peek there for a servername to detect
     an LB.  Call this anyway, so that a dummy host_name_extract option value can
     force resumption attempts. */
 
     if (!(s = ob->host_name_extract)) s = US"never-LB";
     ehlo_response_lbserver(sx, s);
-
+# endif
     goto TLS_NEGOTIATE;
     }
 #endif
@@ -2613,8 +2617,10 @@ goto SEND_QUIT;
 	  }
 	}
 #endif
+#ifndef DISABLE_TLS_RESUME
       if (!(s = ob->host_name_extract)) s = HNE_DEFAULT;
       ehlo_response_lbserver(sx, s);
+#endif
       }
 
   /* Set tls_offered if the response to EHLO specifies support for STARTTLS. */

commit 5bca25510e7313419c4ed0107ae153177bee938c
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Mon Nov 27 17:51:25 2023 +0000

    Build: fix for Solaris 11

diff --git a/src/src/lookups/dnsdb.c b/src/src/lookups/dnsdb.c
index 8288896ca..050baa3b7 100644
--- a/src/src/lookups/dnsdb.c
+++ b/src/src/lookups/dnsdb.c
@@ -445,20 +445,20 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))
 	switch (type)
 	  {
 	  case T_MXH:
-	    if (rr_bad_size(rr, sizeof(u_int16_t))) continue;
+	    if (rr_bad_size(rr, sizeof(uint16_t))) continue;
 	    /* mxh ignores the priority number and includes only the hostnames */
 	    GETSHORT(priority, p);
 	    break;
 
 	  case T_MX:
-	    if (rr_bad_size(rr, sizeof(u_int16_t))) continue;
+	    if (rr_bad_size(rr, sizeof(uint16_t))) continue;
 	    GETSHORT(priority, p);
 	    sprintf(CS s, "%d%c", priority, *outsep2);
 	    yield = string_cat(yield, s);
 	    break;
 
 	  case T_SRV:
-	    if (rr_bad_size(rr, 3*sizeof(u_int16_t))) continue;
+	    if (rr_bad_size(rr, 3*sizeof(uint16_t))) continue;
 	    GETSHORT(priority, p);
 	    GETSHORT(weight, p);
 	    GETSHORT(port, p);
@@ -468,7 +468,7 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))
 	    break;
 
 	  case T_CSA:
-	    if (rr_bad_size(rr, 3*sizeof(u_int16_t))) continue;
+	    if (rr_bad_size(rr, 3*sizeof(uint16_t))) continue;
 	    /* See acl_verify_csa() for more comments about CSA. */
 	    GETSHORT(priority, p);
 	    GETSHORT(weight, p);

commit e4520c7817b76d6a5453237d94b1ed10fa6d2ef3
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Dec 1 15:44:57 2023 +0000

    Avoid trying to send smtp repoonse for non-smtp input, on datafile close error
    
    Broken-by: f70940c9489d

diff --git a/src/src/receive.c b/src/src/receive.c
index a459061ef..c4b80c236 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -4425,8 +4425,8 @@ if (spool_data_file && cutthrough_done == NOT_TRIED)
     Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H"));
     Uunlink(spool_fname(US"msglog", message_subdir, message_id, US""));
 
-    /* Claim a data ACL temp-reject, just to get reject logging and resposponse */
-    smtp_handle_acl_fail(ACL_WHERE_DATA, rc, NULL, log_msg);
+    /* Claim a data ACL temp-reject, just to get reject logging and response */
+    if (smtp_input) smtp_handle_acl_fail(ACL_WHERE_DATA, rc, NULL, log_msg);
     smtp_reply = US"";		/* Indicate reply already sent */
 
     message_id[0] = 0;		/* no message accepted */

commit 87229a18d1fbe959d38f82bde661b57fed11d220
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Tue Dec 5 17:49:06 2023 +0000

    Compiler quietening

diff --git a/src/src/verify.c b/src/src/verify.c
index bc3666285..d080ddd63 100644
--- a/src/src/verify.c
+++ b/src/src/verify.c
@@ -3005,6 +3005,8 @@ if ((semicolon = Ustrchr(ss, ';')))
     endname = semicolon;
     opts = NULL;
     }
+else
+  opts = NULL;
 
 /* If we are doing an IP address only match, then all lookups must be IP
 address lookups, even if there is no "net-". */

commit 2658a023286fcce529031e9f071c5579650687b8
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Tue Dec 5 21:23:46 2023 +0000

    DKIM: tighten up parsing for DKIM DNS and header records.  Bug 3056

diff --git a/src/src/functions.h b/src/src/functions.h
index 74cd15424..b201467b2 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -760,7 +760,7 @@ return US strncpy(CS dst, CCS src, n);
 
 
 /* Advance the string pointer given over any whitespace.
-Return the next char as there's enought places using it to be useful. */
+Return the next char as there's enough places using it to be useful. */
 
 #define Uskip_whitespace(sp) skip_whitespace(CUSS sp)
 
diff --git a/src/src/pdkim/pdkim.c b/src/src/pdkim/pdkim.c
index c723ae6c8..21e17c61e 100644
--- a/src/src/pdkim/pdkim.c
+++ b/src/src/pdkim/pdkim.c
@@ -512,10 +512,6 @@ for (uschar * p = raw_hdr; ; p++)
     }
 
   if (where == PDKIM_HDR_TAG)
-    {
-    if (c >= 'a' && c <= 'z')
-      cur_tag = string_catn(cur_tag, p, 1);
-
     if (c == '=')
       {
       if (Ustrcmp(string_from_gstring(cur_tag), "b") == 0)
@@ -526,7 +522,8 @@ for (uschar * p = raw_hdr; ; p++)
       where = PDKIM_HDR_VALUE;
       goto NEXT_CHAR;
       }
-    }
+    else if (!isspace(c))
+      cur_tag = string_catn(cur_tag, p, 1);
 
   if (where == PDKIM_HDR_VALUE)
     {
@@ -553,17 +550,17 @@ for (uschar * p = raw_hdr; ; p++)
 	    switch (cur_tag->s[1])
 	      {
 	      case '\0': pdkim_decode_base64(cur_val->s, &sig->sighash); break;
-	      case 'h':  if (cur_tag->ptr == 2)
-			   pdkim_decode_base64(cur_val->s, &sig->bodyhash);
+	      case 'h':  if (cur_tag->ptr != 2) goto bad_tag;
+			 pdkim_decode_base64(cur_val->s, &sig->bodyhash);
 			 break;
-	      default:   break;
+	      default:   goto bad_tag;
 	      }
 	    break;
 	  case 'v':					/* version */
 	      /* We only support version 1, and that is currently the
 		 only version there is. */
 	    sig->version =
-	      Ustrcmp(cur_val->s, PDKIM_SIGNATURE_VERSION) == 0 ? 1 : -1;
+		Ustrcmp(cur_val->s, PDKIM_SIGNATURE_VERSION) == 0 ? 1 : -1;
 	    break;
 	  case 'a':					/* algorithm */
 	    {
@@ -578,6 +575,7 @@ for (uschar * p = raw_hdr; ; p++)
 		if (Ustrcmp(elem, pdkim_hashes[i].dkim_hashname) == 0)
 		  { sig->hashtype = i; break; }
 	    }
+	    break;
 
 	  case 'c':					/* canonicalization */
 	    pdkim_cstring_to_canons(cur_val->s, 0,
@@ -586,15 +584,15 @@ for (uschar * p = raw_hdr; ; p++)
 	  case 'q':				/* Query method (for pubkey)*/
 	    for (int i = 0; pdkim_querymethods[i]; i++)
 	      if (Ustrcmp(cur_val->s, pdkim_querymethods[i]) == 0)
-	        {
+		{
 		sig->querymethod = i;	/* we never actually use this */
 		break;
 		}
 	    break;
 	  case 's':					/* Selector */
-	    sig->selector = string_copyn(cur_val->s, cur_val->ptr); break;
+	    sig->selector = string_copy_from_gstring(cur_val); break;
 	  case 'd':					/* SDID */
-	    sig->domain = string_copyn(cur_val->s, cur_val->ptr); break;
+	    sig->domain = string_copy_from_gstring(cur_val); break;
 	  case 'i':					/* AUID */
 	    sig->identity = pdkim_decode_qp(cur_val->s); break;
 	  case 't':					/* Timestamp */
@@ -604,16 +602,18 @@ for (uschar * p = raw_hdr; ; p++)
 	  case 'l':					/* Body length count */
 	    sig->bodylength = strtol(CS cur_val->s, NULL, 10); break;
 	  case 'h':					/* signed header fields */
-	    sig->headernames = string_copyn(cur_val->s, cur_val->ptr); break;
+	    sig->headernames = string_copy_from_gstring(cur_val); break;
 	  case 'z':					/* Copied headfields */
 	    sig->copiedheaders = pdkim_decode_qp(cur_val->s); break;
 /*XXX draft-ietf-dcrup-dkim-crypto-05 would need 'p' tag support
 for rsafp signatures.  But later discussion is dropping those. */
 	  default:
-	    DEBUG(D_acl) debug_printf(" Unknown tag encountered\n");
-	    break;
+	    goto bad_tag;
 	  }
 	}
+	else
+bad_tag:  DEBUG(D_acl) debug_printf(" Unknown tag encountered: %Y\n", cur_tag);
+
       cur_tag = cur_val = NULL;
       in_b_val = FALSE;
       where = PDKIM_HDR_LIMBO;
@@ -659,25 +659,37 @@ return sig;
 /* -------------------------------------------------------------------------- */
 
 pdkim_pubkey *
-pdkim_parse_pubkey_record(const uschar *raw_record)
+pdkim_parse_pubkey_record(const uschar * raw_record)
 {
-const uschar * ele;
-int sep = ';';
-pdkim_pubkey * pub;
+pdkim_pubkey * pub = store_get(sizeof(pdkim_pubkey), GET_TAINTED);
 
-pub = store_get(sizeof(pdkim_pubkey), GET_TAINTED);
 memset(pub, 0, sizeof(pdkim_pubkey));
 
-while ((ele = string_nextinlist(&raw_record, &sep, NULL, 0)))
+for (const uschar * ele = raw_record, * tspec, * end, * val; *ele; ele = end)
   {
-  const uschar * val;
+  Uskip_whitespace(&ele);
+  end = US strchrnul(CS ele, ';');
+  tspec = string_copyn(ele, end - ele);
+  if (*end) end++;			/* skip the ; */
 
-  if ((val = Ustrchr(ele, '=')))
+  if ((val = Ustrchr(tspec, '=')))
     {
-    int taglen = val++ - ele;
+    int taglen = val++ - tspec;
+
+    DEBUG(D_acl) debug_printf(" %.*s=%s\n", taglen, tspec, val);
+    while (taglen > 1 && isspace(tspec[taglen-1]))
+      taglen--;			/* Ignore whitespace before = */
+    while (isspace(*val))
+      val++;			/* Ignore whitespace after = */
+    if (isspace(val[ Ustrlen(val)-1 ]))
+      {				/* Ignore whitespace after value */
+      gstring * g = string_cat(NULL, val);
+      while (isspace(gstring_last_char(g)))
+	gstring_trim(g, 1);
+      val = string_from_gstring(g);
+      }
 
-    DEBUG(D_acl) debug_printf(" %.*s=%s\n", taglen, ele, val);
-    switch (ele[0])
+    if (taglen == 1) switch (tspec[0])
       {
       case 'v': pub->version = val;			break;
       case 'h': pub->hashes = val;			break;
@@ -689,8 +701,11 @@ while ((ele = string_nextinlist(&raw_record, &sep, NULL, 0)))
       case 't': if (Ustrchr(val, 'y')) pub->testing = 1;
 		if (Ustrchr(val, 's')) pub->no_subdomaining = 1;
 		break;
-      default:  DEBUG(D_acl) debug_printf(" Unknown tag encountered\n"); break;
+      default:  goto bad_tag;
       }
+    else
+bad_tag:
+       DEBUG(D_acl) debug_printf(" Unknown tag encountered\n");
     }
   }
 

commit 5930166b443390a522f210c2e60ec447a7dbd080
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Wed Dec 6 12:25:14 2023 +0000

    Supply strchrnul() for platforms apparently missing it
    
    Broken-by: 2658a023286f

diff --git a/src/OS/os.h-FreeBSD b/src/OS/os.h-FreeBSD
index 6756d42f6..11f8b1ae7 100644
--- a/src/OS/os.h-FreeBSD
+++ b/src/OS/os.h-FreeBSD
@@ -78,5 +78,7 @@ extern ssize_t os_sendfile(int, int, off_t *, size_t);
 /*******************/
 
 #define EXIM_HAVE_KEVENT
+#define EXIM_HAVE_STRCHRNUL
+
 
 /* End */
diff --git a/src/OS/os.h-Linux b/src/OS/os.h-Linux
index 25a12862b..cebda3367 100644
--- a/src/OS/os.h-Linux
+++ b/src/OS/os.h-Linux
@@ -102,4 +102,6 @@ then change the 0 to 1 in the next block. */
 # define NS_MAXMSG 65535
 #endif
 
+#define EXIM_HAVE_STRCHRNUL
+
 /* End */
diff --git a/src/src/os.c b/src/src/os.c
index 5c52c6d4d..5e5899eeb 100644
--- a/src/src/os.c
+++ b/src/src/os.c
@@ -893,6 +893,21 @@ return buffer ? buffer : realloc(b, strlen(b) + 1);
 #endif
 
 /* ----------------------------------------------------------------------- */
+/***********************************************************
+*             strchrnul()                                  *
+***********************************************************/
+
+#if !defined(EXIM_HAVE_STRCHRNUL)
+char *
+strchrnul(const char * s, int c)
+{
+while (*s != c && *s) s++
+return CS s;
+}
+#endif
+
+
+/* ----------------------------------------------------------------------- */
 
 
 

commit b0e63c7efdc2133c61545b051042d3617ecd2bbd
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Wed Dec 6 19:54:40 2023 +0000

    Logging: ensure that an error for a mistyped IPv6 address in a search
    list is available for logging.  Bug 3057

diff --git a/src/src/match.c b/src/src/match.c
index df440108e..8b1a3bef6 100644
--- a/src/src/match.c
+++ b/src/src/match.c
@@ -886,14 +886,19 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
 	  log_write(0, LOG_MAIN, "%s: accepted by +include_defer", error);
 	  goto OK_RETURN;
 
-        case ERROR:		/* host name lookup failed - this can only */
-	  if (ignore_unknown)	/* be for an incoming host (not outgoing) */
+        /* The ERROR return occurs when checking hosts, when either a forward
+        or reverse lookup has failed. It can also occur in a match_ip list if a
+        non-IP address item is encountered. The error string gives details of
+        which it was. */
+
+        case ERROR:
+	  if (ignore_unknown)
 	    {
 	    HDEBUG(D_lists) debug_printf_indent("%s: item ignored by +ignore_unknown\n",
 	      error);
 	    }
 	  else
-	   {
+	    {
 	    HDEBUG(D_lists) debug_printf_indent("%s %s (%s)\n", ot,
 	      include_unknown? "yes":"no", error);
 	    (void)fclose(f);
diff --git a/src/src/string.c b/src/src/string.c
index dfe0f2451..3bf2f1df7 100644
--- a/src/src/string.c
+++ b/src/src/string.c
@@ -57,6 +57,7 @@ union { /* we do not need this, but inet_pton() needs a place for storage */
 we return failure, as we do if the mask isn't a pure numerical value,
 or if it is negative. The actual length is checked later, once we know
 the address family. */
+
 if (slash = Ustrchr(ip_addr, '/'))
   {
   uschar * rest;
@@ -74,10 +75,11 @@ if (slash = Ustrchr(ip_addr, '/'))
     return 0;
     }
 
-  *maskptr = slash - ip_addr;     /* offset of the slash */
+  *maskptr = slash - ip_addr;	/* offset of the slash */
   endp = slash;
   }
-else if (maskptr) *maskptr = 0; /* no slash found */
+else if (maskptr)
+  *maskptr = 0;			/* no slash found */
 
 /* The interface-ID suffix (%<id>) is optional (for IPv6). If it
 exists, we check it syntactically. Later, if we know the address
@@ -160,7 +162,7 @@ switch (af)
 int
 string_is_ip_address(const uschar * ip_addr, int * maskptr)
 {
-return string_is_ip_addressX(ip_addr, maskptr, 0);
+return string_is_ip_addressX(ip_addr, maskptr, NULL);
 }
 
 #endif  /* COMPILE_UTILITY */
diff --git a/src/src/verify.c b/src/src/verify.c
index d080ddd63..194e9a76a 100644
--- a/src/src/verify.c
+++ b/src/src/verify.c
@@ -2971,7 +2971,7 @@ if (*ss == '@')
 a (possibly masked) comparison with the current IP address. */
 
 if (string_is_ip_address(ss, &maskoffset) != 0)
-  return (host_is_in_net(cb->host_address, ss, maskoffset)? OK : FAIL);
+  return host_is_in_net(cb->host_address, ss, maskoffset) ? OK : FAIL;
 
 /* The pattern is not an IP address. A common error that people make is to omit
 one component of an IPv4 address, either by accident, or believing that, for
@@ -2982,13 +2982,25 @@ ancient specification.) To aid in debugging these cases, we give a specific
 error if the pattern contains only digits and dots or contains a slash preceded
 only by digits and dots (a slash at the start indicates a file name and of
 course slashes may be present in lookups, but not preceded only by digits and
-dots). */
+dots).  Then the equivalent for IPv6 (roughly). */
 
-for (t = ss; isdigit(*t) || *t == '.'; ) t++;
-if (!*t  || (*t == '/' && t != ss))
+if (Ustrchr(ss, ':'))
   {
-  *error = string_sprintf("malformed IPv4 address or address mask: %.*s", (int)(t - ss), ss);
-  return ERROR;
+  for (t = ss; isxdigit(*t) || *t == ':' || *t == '.'; ) t++;
+  if (!*t  ||  (*t == '/' || *t == '%') && t != ss)
+    {
+    *error = string_sprintf("malformed IPv6 address or address mask: %.*s", (int)(t - ss), ss);
+    return ERROR;
+    }
+  }
+else
+  {
+  for (t = ss; isdigit(*t) || *t == '.'; ) t++;
+  if (!*t  || (*t == '/' && t != ss))
+    {
+    *error = string_sprintf("malformed IPv4 address or address mask: %.*s", (int)(t - ss), ss);
+    return ERROR;
+    }
   }
 
 /* See if there is a semicolon in the pattern, separating a searchtype

commit b037dd69615b2ce9e084932e8a61477afc67345e
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Wed Dec 6 22:08:08 2023 +0000

    typo
    
    Broken-by: 5930166b4433

diff --git a/src/src/os.c b/src/src/os.c
index 5e5899eeb..c6d1e06c2 100644
--- a/src/src/os.c
+++ b/src/src/os.c
@@ -901,7 +901,7 @@ return buffer ? buffer : realloc(b, strlen(b) + 1);
 char *
 strchrnul(const char * s, int c)
 {
-while (*s != c && *s) s++
+while (*s != c && *s) s++;
 return CS s;
 }
 #endif

commit ce336ea1afd8d5f6b79d36fc7ec4313f3d80146b
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Thu Dec 7 19:59:35 2023 +0000

    Handle expansion fails in router "set" options.  Bug 3058

diff --git a/src/src/route.c b/src/src/route.c
index 0bd87b837..3401f15b4 100644
--- a/src/src/route.c
+++ b/src/src/route.c
@@ -1492,7 +1492,11 @@ for (uschar * ele; (ele = string_nextinlist(&varlist, &sep, NULL, 0)); )
       {
       addr->message = string_sprintf("expansion of \"%s\" failed "
 	"in %s router: %s", ele, r->name, expand_string_message);
-      return DEFER;
+      /* Caller will replace that for logging, if a DB lookup, to avoid exposing
+      passwords */
+      DEBUG(D_route) debug_printf("%s\n", addr->message);
+      if (!f.search_find_defer)
+      return f.search_find_defer ? DEFER : FAIL;
       }
 
   if (!(node = tree_search(*root, name)))
@@ -1546,8 +1550,8 @@ route_address(address_item *addr, address_item **paddr_local,
 {
 int yield = OK;
 BOOL unseen;
-router_instance *r, *nextr;
-const uschar *old_domain = addr->domain;
+router_instance * r, * nextr;
+const uschar * old_domain = addr->domain;
 
 HDEBUG(D_route)
   {
@@ -1561,8 +1565,8 @@ instead of at the first router. */
 
 for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
   {
-  uschar *error;
-  struct passwd *pw = NULL;
+  uschar * error;
+  struct passwd * pw = NULL;
   struct passwd pwcopy;
   BOOL loop_detected = FALSE;
   BOOL more;
@@ -1736,11 +1740,11 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
   router traversal.  On the addr string they are held as a variable tree, so
   as to maintain the post-expansion taints separate. */
 
-  switch (set_router_vars(addr, r))
+  switch (rc = set_router_vars(addr, r))
     {
     case OK:	break;
     case PASS:	continue;		/* with next router */
-    default:	 goto ROUTE_EXIT;
+    default: 	yield = rc; goto ROUTE_EXIT;
     }
 
   /* Finally, expand the address_data field in the router. Forced failure

commit ced0bbf43860a2e4ddc88e3ba20ae731324586c5
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Dec 15 21:05:32 2023 +0000

    Fix periodic queue runs.  Bug 3046
    
    Broken-by: 7d5055276a22

diff --git a/src/src/daemon.c b/src/src/daemon.c
index f2183c735..aff05120a 100644
--- a/src/src/daemon.c
+++ b/src/src/daemon.c
@@ -1258,10 +1258,9 @@ static const uschar * queuerun_msg_qname;
 
 /* The notifier socket has something to read. Pull the message from it, decode
 and do the action.
+*/
 
-Return TRUE if a sigalrm should be emulated */
-
-static BOOL
+static void
 daemon_notification(void)
 {
 uschar buf[256], cbuf[256];
@@ -1277,8 +1276,8 @@ struct msghdr msg = { .msg_name = &sa_un,
 ssize_t sz;
 
 buf[sizeof(buf)-1] = 0;
-if ((sz = recvmsg(daemon_notifier_fd, &msg, 0)) <= 0) return FALSE;
-if (sz >= sizeof(buf)) return FALSE;
+if ((sz = recvmsg(daemon_notifier_fd, &msg, 0)) <= 0) return;
+if (sz >= sizeof(buf)) return;
 
 #ifdef notdef
 debug_printf("addrlen %d\n", msg.msg_namelen);
@@ -1351,7 +1350,7 @@ switch (buf[0])
 	  : !buf[1+MESSAGE_ID_LENGTH+1]
 	 )
 	{ queuerun_msg_qname = q->name; break; }
-    return TRUE;
+    return;
 #endif
 
   case NOTIFY_QUEUE_SIZE_REQ:
@@ -1373,7 +1372,7 @@ switch (buf[0])
     regex_at_daemon(buf);
     break;
   }
-return FALSE;
+return;
 }
 
 
@@ -1432,7 +1431,7 @@ for (qrunner * q = qrunners, * next; q; q = next)
   if (sorted)
     {
     qrunner ** p = &sorted;
-    for (qrunner * qq; qq = *p; p = &(qq->next))
+    for (qrunner * qq; qq = *p; p = &qq->next)
       if (  q->next_tick < qq->next_tick
 	 || q->next_tick == qq->next_tick && q->interval < qq->interval
 	 )
@@ -1451,6 +1450,13 @@ qrunners = sorted;
 return qrunners ? qrunners->next_tick - time(NULL) : 0;
 }
 
+/* See if we can do a queue run.  If policy limit permit, kick one off.
+If both notification and timer events are present, handle the former
+and leave the timer outstanding.
+
+Return the number of seconds until the next due runner.
+*/
+
 static int
 daemon_qrun(int local_queue_run_max, struct pollfd * fd_polls, int listen_socket_count)
 {
@@ -1464,13 +1470,16 @@ DEBUG(D_any) debug_printf("%s received\n",
 enough queue runners on the go. If we are not running as root, a re-exec is
 required. In the calling process, restart the alamr timer for the next run.  */
 
-if (is_multiple_qrun())
+if (is_multiple_qrun())				/* we are managing periodic runs */
   if (local_queue_run_max <= 0 || queue_run_count < local_queue_run_max)
     {
     qrunner * q = NULL;
 
 #ifndef DISABLE_QUEUE_RAMP
-    if (*queuerun_msgid)	/* See if we can start another runner for this queue */
+    /* If this is a triggered run for a specific message, see if we can start
+    another runner for this queue. */
+
+    if (*queuerun_msgid)
       {
       for (qrunner * qq = qrunners; qq; qq = qq->next)
 	if (qq->name == queuerun_msg_qname)
@@ -1481,13 +1490,13 @@ if (is_multiple_qrun())
       }
     else
 #endif
-      /* In order of run priority, find the first queue for which we can start
-      a runner */
+      /* Normal periodic runL in order of run priority, find the first queue
+      for which we can start a runner */
 
       for (q = qrunners; q; q = q->next)
 	if (q->run_count < q->run_max) break;
 
-    if (q)
+    if (q)					/* found a queue to run */
       {
       pid_t pid;
 
@@ -1619,19 +1628,23 @@ if (is_multiple_qrun())
       }
     }
 
-sigalrm_seen = FALSE;
+/* The queue run has been initiated (unless we were already running enough) */
+
 #ifndef DISABLE_QUEUE_RAMP
-if (*queuerun_msgid)		/* it was a fast-ramp kick */
+if (*queuerun_msgid)		/* it was a fast-ramp kick; dealt with */
   *queuerun_msgid = 0;
 else				/* periodic or one-time queue run */
 #endif
-  {		/* Impose a minimum 1s tick, even when a run was outstanding */
+  /* Set up next timer callback. Impose a minimum 1s tick,
+  even when a run was outstanding */
+  {
   int interval = next_qrunner_interval();
   if (interval <= 0) interval = 1;
 
+  sigalrm_seen = FALSE;
   if (qrunners)			/* there are still periodic qrunners */
     {
-    ALARM(interval);
+    ALARM(interval);		/* set up next qrun tick */
     return interval;
     }
   }
@@ -2612,7 +2625,7 @@ for (;;)
 
   The other option is that we have an inetd wait timeout specified to -bw. */
 
-  if (sigalrm_seen)
+  if (sigalrm_seen || *queuerun_msgid)
     if (inetd_wait_timeout > 0)
       daemon_inetd_wtimeout(last_connection_time);	/* Might not return */
     else
@@ -2641,7 +2654,9 @@ for (;;)
     select() was interrupted so that we reap the child. This might still leave
     a small window when a SIGCHLD could get lost. However, since we use SIGCHLD
     only to do the reaping more quickly, it shouldn't result in anything other
-    than a delay until something else causes a wake-up. */
+    than a delay until something else causes a wake-up.
+    For the normal case, wait for either a pollable fd (eg. new connection) or
+    or a SIGALRM (for a periodic queue run) */
 
     if (sigchld_seen)
       {
@@ -2706,10 +2721,13 @@ for (;;)
 	  break;	/* to top of daemon loop */
 	  }
 #endif
+	/* Handle the daemon-notifier socket.  If it was a fast-ramp
+	notification then queuerun_msgid will have a nonzerolength string. */
+
 	if (dnotify_poll && dnotify_poll->revents & POLLIN)
 	  {
 	  dnotify_poll->revents = 0;
-	  sigalrm_seen = daemon_notification();
+	  daemon_notification();
 	  break;	/* to top of daemon loop */
 	  }
 	for (struct pollfd * p = fd_polls; p < fd_polls + listen_socket_count;
diff --git a/src/src/structs.h b/src/src/structs.h
index 209d657c6..256560ef8 100644
--- a/src/src/structs.h
+++ b/src/src/structs.h
@@ -964,7 +964,7 @@ typedef struct qrunner {
   struct qrunner * next;	/* list sorted by next tick */
 
   uschar *	name;		/* NULL for the default queue */
-  unsigned	interval;	/* tick rate, seconds */
+  unsigned	interval;	/* tick rate, seconds. Zero for a one-time run */
   time_t	next_tick;	/* next run should, or should have, start(ed) */
   unsigned	run_max;	/* concurrent queue runner limit */
   unsigned	run_count;	/* current runners */

commit 5ce637f7a74d392ddafb1d38e3cefb744996b287
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sat Dec 16 16:31:06 2023 +0000

    tidying

diff --git a/src/src/EDITME b/src/src/EDITME
index f68b3f104..85d24f73f 100644
--- a/src/src/EDITME
+++ b/src/src/EDITME
@@ -52,6 +52,7 @@
 # they are correctly installed, via their compatibility interfaces. However,
 # Exim can also be configured to use the native calls for Berkeley DB (obsolete
 # versions 1.85, 2.x, 3.x, or the current 4.x version) and also for gdbm.
+# See definitions for DBMLIB below.
 
 # For some operating systems, a default DBM library (other than ndbm) is
 # selected by a setting in the OS-specific Makefile. Most modern OS now have
diff --git a/src/src/receive.c b/src/src/receive.c
index c4b80c236..e35400aec 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -4391,7 +4391,7 @@ data file will be flushed(!) out thereby. Nevertheless, it is theoretically
 possible for fclose() to fail - and this has been seen on obscure filesystems
 (probably one that delayed the actual media write as long as possible)
 but what to do? What has happened to the lock if this happens?
-It's a mes because we already logged the acceptance.
+It's a mess because we already logged the acceptance.
 We can at least log the issue, try to remove spoolfiles and respond with
 a temp-reject.  We do not want to close before logging acceptance because
 we want to hold the lock until we know that logging worked.
diff --git a/src/src/store.c b/src/src/store.c
index 684f83cf0..03322a097 100644
--- a/src/src/store.c
+++ b/src/src/store.c
@@ -1145,11 +1145,11 @@ Returns:      pointer to gotten store (panic on failure)
 */
 
 static void *
-internal_store_malloc(size_t size, const char *func, int line)
+internal_store_malloc(size_t size, const char * func, int line)
 {
 void * yield;
 
-/* Check specifically for a possibly result of conversion from
+/* Check specifically for a possible result of conversion from
 a negative int, to the (unsigned, wider) size_t */
 
 if (size >= INT_MAX/2)

commit 322ae953e144d9e1fdc06d993d2e4551f5fb1322
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Thu Dec 21 23:33:10 2023 +0000

    tweak observability

diff --git a/src/src/functions.h b/src/src/functions.h
index b201467b2..ef84e3d7c 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -1400,6 +1400,11 @@ HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP%c> %s\n",
     client_cmd_log = string_catn(client_cmd_log, US"|", 1); 
     (void) string_from_gstring(client_cmd_log);
     }
+  else if (mode == SCMD_MORE)
+    {
+    client_cmd_log = string_catn(client_cmd_log, US"+", 1);
+    (void) string_from_gstring(client_cmd_log);
+    }
   store_pool = old_pool;
   }
 #  endif
diff --git a/src/src/smtp_out.c b/src/src/smtp_out.c
index 90a0eb23e..62557072e 100644
--- a/src/src/smtp_out.c
+++ b/src/src/smtp_out.c
@@ -649,7 +649,7 @@ Arguments:
   sx	     SMTP connection, contains buffer for pipelining, and socket
   mode       buffer, write-with-more-likely, write
   format     a format, starting with one of
-             of HELO, MAIL FROM, RCPT TO, DATA, ".", or QUIT.
+             of HELO, MAIL FROM, RCPT TO, DATA, BDAT, ".", or QUIT.
 	     If NULL, flush pipeline buffer only.
   ...        data for the format
 
diff --git a/src/src/transport.c b/src/src/transport.c
index a0f097579..79a69f941 100644
--- a/src/src/transport.c
+++ b/src/src/transport.c
@@ -1658,6 +1658,10 @@ another message waiting for the same host. However, it doesn't do this if the
 current continue sequence is greater than the maximum supplied as an argument,
 or greater than the global connection_max_messages, which, if set, overrides.
 
+It is also called if conditions are otherwise right for pipelining a QUIT after
+the message data, since if there is another message waiting we do not want to
+send that QUIT.
+
 Arguments:
   transport_name     name of the transport
   hostname           name of the host

commit cf1376206284f2a4f11e32d931d4aade34c206c5
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Dec 22 23:57:05 2023 +0000

    Reject "dot, LF" as ending data phase.  Bug 3063

diff --git a/src/src/receive.c b/src/src/receive.c
index e35400aec..c6f612832 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -1960,8 +1960,10 @@ for (;;)
 
   if (ch == '\n')
     {
-    if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = FALSE;
-      else if (first_line_ended_crlf) receive_ungetc(' ');
+    if (first_line_ended_crlf == TRUE_UNSET)
+      first_line_ended_crlf = FALSE;
+    else if (first_line_ended_crlf)
+      receive_ungetc(' ');
     goto EOL;
     }
 
@@ -1970,6 +1972,7 @@ for (;;)
   This implements the dot-doubling rule, though header lines starting with
   dots aren't exactly common. They are legal in RFC 822, though. If the
   following is CRLF or LF, this is the line that that terminates the
+
   entire message. We set message_ended to indicate this has happened (to
   prevent further reading), and break out of the loop, having freed the
   empty header, and set next = NULL to indicate no data line. */
@@ -1977,7 +1980,11 @@ for (;;)
   if (f.dot_ends && ptr == 0 && ch == '.')
     {
     ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
-    if (ch == '\r')
+    if (ch == '\n' && first_line_ended_crlf == TRUE /* and not TRUE_UNSET */ )
+    		/* dot, LF  but we are in CRLF mode.  Attack? */
+      ch = ' ';	/* replace the LF with a space */
+
+    else if (ch == '\r')
       {
       ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
       if (ch != '\n')
@@ -2013,7 +2020,8 @@ for (;;)
     ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
     if (ch == '\n')
       {
-      if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = TRUE;
+      if (first_line_ended_crlf == TRUE_UNSET)
+	first_line_ended_crlf = TRUE;
       goto EOL;
       }
 
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index e19c86ff8..aeaffeb37 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -5105,15 +5105,18 @@ while (done <= 0)
 	}
 
       if (chunking_state > CHUNKING_OFFERED)
-	rc = OK;			/* No predata ACL or go-ahead output for BDAT */
+	rc = OK;	/* There is no predata ACL or go-ahead output for BDAT */
       else
 	{
-	/* If there is an ACL, re-check the synchronization afterwards, since the
-	ACL may have delayed.  To handle cutthrough delivery enforce a dummy call
-	to get the DATA command sent. */
+	/* If there is a predata-ACL, re-check the synchronization afterwards,
+	since the ACL may have delayed.  To handle cutthrough delivery enforce a
+	dummy call to get the DATA command sent. */
 
 	if (!acl_smtp_predata && cutthrough.cctx.sock < 0)
+	  {
+	  if (!check_sync()) goto SYNC_FAILURE;
 	  rc = OK;
+	  }
 	else
 	  {
 	  uschar * acl = acl_smtp_predata ? acl_smtp_predata : US"accept";

commit 4596719398f6f2365bed563aafd757a6433ce7b4
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sat Dec 23 15:59:53 2023 +0000

    Use enum for body data input state-machine

diff --git a/src/src/receive.c b/src/src/receive.c
index c6f612832..e8bd80596 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -836,93 +836,95 @@ Returns:    One of the END_xxx values indicating why it stopped reading
 */
 
 static int
-read_message_data_smtp(FILE *fout)
+read_message_data_smtp(FILE * fout)
 {
-int ch_state = 0;
-int ch;
-int linelength = 0;
+enum { s_linestart, s_normal, s_had_cr, s_had_nl_dot, s_had_dot_cr } ch_state =
+	      s_linestart;
+int linelength = 0, ch;
 
 while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF)
   {
   if (ch == 0) body_zerocount++;
   switch (ch_state)
     {
-    case 0:                             /* After LF or CRLF */
-    if (ch == '.')
-      {
-      ch_state = 3;
-      continue;                         /* Don't ever write . after LF */
-      }
-    ch_state = 1;
+    case s_linestart:			/* After LF or CRLF */
+      if (ch == '.')
+	{
+	ch_state = s_had_nl_dot;
+	continue;			/* Don't ever write . after LF */
+	}
+      ch_state = s_normal;
 
-    /* Else fall through to handle as normal uschar. */
+      /* Else fall through to handle as normal uschar. */
 
-    case 1:                             /* Normal state */
-    if (ch == '\n')
-      {
-      ch_state = 0;
-      body_linecount++;
+    case s_normal:			/* Normal state */
+      if (ch == '\r')
+	{
+	ch_state = s_had_cr;
+	continue;			/* Don't write the CR */
+	}
+      if (ch == '\n')			/* Bare NL ends line */
+	{
+	ch_state = s_linestart;
+	body_linecount++;
+	if (linelength > max_received_linelength)
+	  max_received_linelength = linelength;
+	linelength = -1;
+	}
+      break;
+
+    case s_had_cr:			/* After (unwritten) CR */
+      body_linecount++;			/* Any char ends line */
       if (linelength > max_received_linelength)
-        max_received_linelength = linelength;
+	max_received_linelength = linelength;
       linelength = -1;
-      }
-    else if (ch == '\r')
-      {
-      ch_state = 2;
-      continue;
-      }
-    break;
+      if (ch == '\n')			/* proper CRLF */
+	ch_state = s_linestart;
+      else
+	{
+	message_size++;		/* convert the dropped CR to a stored NL */
+	if (fout && fputc('\n', fout) == EOF) return END_WERROR;
+	cutthrough_data_put_nl();
+	if (ch == '\r')			/* CR; do not write */
+	  continue;
+	ch_state = s_normal;		/* not LF or CR; process as standard */
+	}
+      break;
 
-    case 2:                             /* After (unwritten) CR */
-    body_linecount++;
-    if (linelength > max_received_linelength)
-      max_received_linelength = linelength;
-    linelength = -1;
-    if (ch == '\n')
-      {
-      ch_state = 0;
-      }
-    else
-      {
-      message_size++;
-      if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
-      cutthrough_data_put_nl();
-      if (ch != '\r') ch_state = 1; else continue;
-      }
-    break;
+    case s_had_nl_dot:			/* After [CR] LF . */
+      if (ch == '\n')			/* [CR] LF . LF */
+	return END_DOT;
+      if (ch == '\r')			/* [CR] LF . CR */
+	{
+	ch_state = s_had_dot_cr;
+	continue;			/* Don't write the CR */
+	}
+      /* The dot was removed on reaching s_had_nl_dot. For a doubled dot, here,
+      reinstate it to cutthrough. The current ch, dot or not, is passed both to
+      cutthrough and to file below. */
+      if (ch == '.')
+	{
+	uschar c = ch;
+	cutthrough_data_puts(&c, 1);
+	}
+      ch_state = s_normal;
+      break;
 
-    case 3:                             /* After [CR] LF . */
-    if (ch == '\n')
-      return END_DOT;
-    if (ch == '\r')
-      {
-      ch_state = 4;
-      continue;
-      }
-    /* The dot was removed at state 3. For a doubled dot, here, reinstate
-    it to cutthrough. The current ch, dot or not, is passed both to cutthrough
-    and to file below. */
-    if (ch == '.')
-      {
-      uschar c= ch;
-      cutthrough_data_puts(&c, 1);
-      }
-    ch_state = 1;
-    break;
+    case s_had_dot_cr:			/* After [CR] LF . CR */
+      if (ch == '\n')
+	return END_DOT;			/* Preferred termination */
 
-    case 4:                             /* After [CR] LF . CR */
-    if (ch == '\n') return END_DOT;
-    message_size++;
-    body_linecount++;
-    if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
-    cutthrough_data_put_nl();
-    if (ch == '\r')
-      {
-      ch_state = 2;
-      continue;
-      }
-    ch_state = 1;
-    break;
+      message_size++;		/* convert the dropped CR to a stored NL */
+      body_linecount++;
+      if (fout && fputc('\n', fout) == EOF) return END_WERROR;
+      cutthrough_data_put_nl();
+      if (ch == '\r')
+	{
+	ch_state = s_had_cr;
+	continue;			/* CR; do not write */
+	}
+      ch_state = s_normal;
+      break;
     }
 
   /* Add the character to the spool file, unless skipping; then loop for the
@@ -1979,6 +1981,7 @@ for (;;)
 
   if (f.dot_ends && ptr == 0 && ch == '.')
     {
+    /* leading dot while in headers-read mode */
     ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
     if (ch == '\n' && first_line_ended_crlf == TRUE /* and not TRUE_UNSET */ )
     		/* dot, LF  but we are in CRLF mode.  Attack? */
@@ -3169,7 +3172,7 @@ if (cutthrough.cctx.sock >= 0 && cutthrough.delivery)
 
 
 /* Open a new spool file for the data portion of the message. We need
-to access it both via a file descriptor and a stream. Try to make the
+to access it both via a file descriptor and a stdio stream. Try to make the
 directory if it isn't there. */
 
 spool_name = spool_fname(US"input", message_subdir, message_id, US"-D");

commit 5bb786d5ad568a88d50d15452aacc8404047e5ca
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sat Dec 23 17:42:57 2023 +0000

    Reject "dot, LF" as ending data phase (pt. 2).  Bug 3063

diff --git a/src/src/receive.c b/src/src/receive.c
index e8bd80596..ae7045068 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -829,14 +829,20 @@ July 2003: Bare CRs cause trouble. We now treat them as line terminators as
 well, so that there are no CRs in spooled messages. However, the message
 terminating dot is not recognized between two bare CRs.
 
+Dec 2023: getting a site to send a body including an "LF . LF" sequence
+followed by SMTP commands is a possible "smtp smuggling" attack.  If
+the first (header) line for the message has a proper CRLF then enforce
+that for the body: convert bare LF to a space.
+
 Arguments:
-  fout      a FILE to which to write the message; NULL if skipping
+  fout		a FILE to which to write the message; NULL if skipping
+  strict_crlf	require full CRLF sequence as a line ending
 
 Returns:    One of the END_xxx values indicating why it stopped reading
 */
 
 static int
-read_message_data_smtp(FILE * fout)
+read_message_data_smtp(FILE * fout, BOOL strict_crlf)
 {
 enum { s_linestart, s_normal, s_had_cr, s_had_nl_dot, s_had_dot_cr } ch_state =
 	      s_linestart;
@@ -863,14 +869,17 @@ while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF)
 	ch_state = s_had_cr;
 	continue;			/* Don't write the CR */
 	}
-      if (ch == '\n')			/* Bare NL ends line */
-	{
-	ch_state = s_linestart;
-	body_linecount++;
-	if (linelength > max_received_linelength)
-	  max_received_linelength = linelength;
-	linelength = -1;
-	}
+      if (ch == '\n')			/* Bare LF at end of line */
+	if (strict_crlf)
+	  ch = ' ';			/* replace LF with space */
+	else
+	  {				/* treat as line ending */
+	  ch_state = s_linestart;
+	  body_linecount++;
+	  if (linelength > max_received_linelength)
+	    max_received_linelength = linelength;
+	  linelength = -1;
+	  }
       break;
 
     case s_had_cr:			/* After (unwritten) CR */
@@ -893,8 +902,11 @@ while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF)
 
     case s_had_nl_dot:			/* After [CR] LF . */
       if (ch == '\n')			/* [CR] LF . LF */
-	return END_DOT;
-      if (ch == '\r')			/* [CR] LF . CR */
+	if (strict_crlf)
+	  ch = ' ';			/* replace LF with space */
+	else
+	  return END_DOT;
+      else if (ch == '\r')		/* [CR] LF . CR */
 	{
 	ch_state = s_had_dot_cr;
 	continue;			/* Don't write the CR */
@@ -902,7 +914,7 @@ while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF)
       /* The dot was removed on reaching s_had_nl_dot. For a doubled dot, here,
       reinstate it to cutthrough. The current ch, dot or not, is passed both to
       cutthrough and to file below. */
-      if (ch == '.')
+      else if (ch == '.')
 	{
 	uschar c = ch;
 	cutthrough_data_puts(&c, 1);
@@ -1140,7 +1152,7 @@ receive_swallow_smtp(void)
 {
 if (message_ended >= END_NOTENDED)
   message_ended = chunking_state <= CHUNKING_OFFERED
-     ? read_message_data_smtp(NULL)
+     ? read_message_data_smtp(NULL, FALSE)
      : read_message_bdat_smtp_wire(NULL);
 }
 
@@ -3241,7 +3253,7 @@ if (!ferror(spool_data_file) && !(receive_feof)() && message_ended != END_DOT)
   if (smtp_input)
     {
     message_ended = chunking_state <= CHUNKING_OFFERED
-      ? read_message_data_smtp(spool_data_file)
+      ? read_message_data_smtp(spool_data_file, first_line_ended_crlf)
       : spool_wireformat
       ? read_message_bdat_smtp_wire(spool_data_file)
       : read_message_bdat_smtp(spool_data_file);

commit f78e417c3c2e7348ce58bb03efc7343bb6d32383
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Mon Dec 25 16:50:23 2023 +0000

    Testsuite: testcase for "smtp smuggling".  Bug 3063
    Also remove the unneeded sync point added in cf1376206284

diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index aeaffeb37..541288301 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -5113,10 +5113,7 @@ while (done <= 0)
 	dummy call to get the DATA command sent. */
 
 	if (!acl_smtp_predata && cutthrough.cctx.sock < 0)
-	  {
-	  if (!check_sync()) goto SYNC_FAILURE;
 	  rc = OK;
-	  }
 	else
 	  {
 	  uschar * acl = acl_smtp_predata ? acl_smtp_predata : US"accept";

commit 80bacd22c5aaacd7fca966af7384ac6f2679cb21
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Dec 29 12:40:22 2023 +0000

    DKIM: logging for signing in transport. Bug 3062

diff --git a/src/src/deliver.c b/src/src/deliver.c
index e96733f73..2cef3d731 100644
--- a/src/src/deliver.c
+++ b/src/src/deliver.c
@@ -1245,6 +1245,14 @@ else
     g = string_catn(g, US" K", 2);
   }
 
+#ifndef DISABLE_DKIM
+  if (addr->dkim_used && LOGGING(dkim_verbose))
+    {
+    g = string_catn(g, US" DKIM=", 6);
+    g = string_cat(g, addr->dkim_used);
+    }
+#endif
+
 /* confirmation message (SMTP (host_used) and LMTP (driver_name)) */
 
 if (  LOGGING(smtp_confirmation)
@@ -3571,7 +3579,14 @@ while (!done)
 
       switch (*subid)
 	{
-	case 3:		/* explicit notification of continued-connection (non)use;
+#ifndef DISABLE_DKIM
+	case '4':	/* DKIM information */
+	  addr->dkim_used = string_copy(ptr);
+	  while(*ptr++);
+	  break;
+#endif
+
+	case '3':	/* explicit notification of continued-connection (non)use;
 			overrides caller's knowlege. */
 	  if (*ptr & BIT(1))      setflag(addr, af_new_conn);
 	  else if (*ptr & BIT(2)) setflag(addr, af_cont_conn);
@@ -4886,6 +4901,15 @@ all pipes, so I do not see a reason to use non-blocking IO here
         rmt_dlv_checked_write(fd, 'R', '0', big_buffer, ptr - big_buffer);
         }
 
+#ifndef DISABLE_DKIM
+      if (addr->dkim_used && LOGGING(dkim_verbose))
+	{
+	DEBUG(D_deliver) debug_printf("dkim used: %s\n", addr->dkim_used);
+	ptr = big_buffer + sprintf(CS big_buffer, "%.128s", addr->dkim_used) + 1;
+        rmt_dlv_checked_write(fd, 'A', '4', big_buffer, ptr - big_buffer);
+	}
+#endif
+
       if (testflag(addr, af_new_conn) || testflag(addr, af_cont_conn))
 	{
 	DEBUG(D_deliver) debug_printf("%scontinued-connection\n",
diff --git a/src/src/dkim.c b/src/src/dkim.c
index ea82f34ea..59025608f 100644
--- a/src/src/dkim.c
+++ b/src/src/dkim.c
@@ -741,6 +741,9 @@ if (dkim_domain)
     if (!pdkim_set_sig_bodyhash(&dkim_sign_ctx, sig))
       goto bad;
 
+    dkim_signing_record = string_append_listele(dkim_signing_record, ':', dkim_signing_domain);
+    dkim_signing_record = string_append_listele(dkim_signing_record, ':', dkim_signing_selector);
+
     if (!dkim_sign_ctx.sig)		/* link sig to context chain */
       dkim_sign_ctx.sig = sig;
     else
diff --git a/src/src/dkim_transport.c b/src/src/dkim_transport.c
index fae2f4891..38f849af6 100644
--- a/src/src/dkim_transport.c
+++ b/src/src/dkim_transport.c
@@ -384,6 +384,8 @@ BOOL
 dkim_transport_write_message(transport_ctx * tctx,
   struct ob_dkim * dkim, const uschar ** err)
 {
+BOOL yield;
+
 /* If we can't sign, just call the original function. */
 
 if (  !(dkim->dkim_private_key && dkim->dkim_domain && dkim->dkim_selector)
@@ -398,12 +400,16 @@ if (  !transport_filter_argv
    || !*transport_filter_argv
    || !**transport_filter_argv
    )
-  return dkt_direct(tctx, dkim, err);
+  yield = dkt_direct(tctx, dkim, err);
+
+else
+  /* Use the transport path to write a file, calculate a dkim signature,
+  send the signature and then send the file. */
 
-/* Use the transport path to write a file, calculate a dkim signature,
-send the signature and then send the file. */
+  yield = dkt_via_kfile(tctx, dkim, err);
 
-return dkt_via_kfile(tctx, dkim, err);
+tctx->addr->dkim_used = string_from_gstring(dkim_signing_record);
+return yield;
 }
 
 #endif	/* whole file */
diff --git a/src/src/globals.c b/src/src/globals.c
index bb5bd6b1a..c535ea121 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -864,6 +864,7 @@ void   *dkim_signatures		 = NULL;
 uschar *dkim_signers             = NULL;
 uschar *dkim_signing_domain      = NULL;
 uschar *dkim_signing_selector    = NULL;
+gstring *dkim_signing_record	 = NULL;
 uschar *dkim_verify_hashes       = US"sha256:sha512";
 uschar *dkim_verify_keytypes     = US"ed25519:rsa";
 uschar *dkim_verify_min_keysizes = US"rsa=1024 ed25519=250";
diff --git a/src/src/globals.h b/src/src/globals.h
index fe4a1610a..ffc7150f2 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -541,6 +541,7 @@ extern uschar *dkim_cur_signer;        /* Expansion variable, holds the current
 extern int     dkim_key_length;        /* Expansion variable, length of signing key in bits */
 extern void   *dkim_signatures;	       /* Actually a (pdkim_signature *) but most files do not need to know */
 extern uschar *dkim_signers;           /* Expansion variable, holds colon-separated list of domains and identities that have signed a message */
+extern gstring *dkim_signing_record;   /* domains+selectors used */
 extern uschar *dkim_signing_domain;    /* Expansion variable, domain used for signing a message. */
 extern uschar *dkim_signing_selector;  /* Expansion variable, selector used for signing a message. */
 extern uschar *dkim_verify_hashes;     /* Preference order for signatures */
diff --git a/src/src/structs.h b/src/src/structs.h
index 256560ef8..b16a8a3f2 100644
--- a/src/src/structs.h
+++ b/src/src/structs.h
@@ -586,6 +586,9 @@ typedef struct address_item {
   uschar *self_hostname;          /* after self=pass */
   uschar *shadow_message;         /* info about shadow transporting */
 
+  uid_t   uid;                    /* uid for transporting */
+  gid_t   gid;                    /* gid for transporting */
+
 #ifndef DISABLE_TLS
   const uschar *tlsver;           /* version used for transport */
   uschar *cipher;                 /* Cipher used for transport */
@@ -608,9 +611,9 @@ typedef struct address_item {
   int     dsn_flags;              /* DSN flags */
   int     dsn_aware;              /* DSN aware flag */
 
-  uid_t   uid;                    /* uid for transporting */
-  gid_t   gid;                    /* gid for transporting */
-
+#ifndef DISABLE_DKIM
+  const uschar * dkim_used;	  /* DKIM info, or NULL */
+#endif
   				  /* flags */
   struct {
     BOOL af_allow_file:1;		/* allow file in generated address */

commit 582f61444fa806694c2caae7c156830dbf8531d7
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Dec 29 15:29:27 2023 +0000

    Tidying: defines for sync_responses() retcodes

diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index 4cf58436d..194671272 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -235,6 +235,17 @@ static BOOL    pipelining_active;	/* current transaction is in pipe mode */
 
 static unsigned ehlo_response(uschar * buf, unsigned checks);
 
+/* sync_responses() return codes */
+
+#define RESP_BIT_HAD_5XX	BIT(1)
+#define RESP_BIT_HAD_2XX	BIT(0)
+#define RESP_HAD_2_AND_5	(RESP_BIT_HAD_2XX | RESP_BIT_HAD_5XX)
+#define RESP_NOERROR		0
+#define RESP_RCPT_TIMEO		-1
+#define RESP_RCPT_ERROR		-2
+#define RESP_MAIL_OR_DATA_ERROR	-3
+#define RESP_EPIPE_EHLO_ERR	-4
+#define RESP_EHLO_ERR_TLS	-5
 
 /******************************************************************************/
 
@@ -1225,7 +1236,7 @@ int yield = 0;
 #ifndef DISABLE_PIPE_CONNECT
 int rc;
 if ((rc = smtp_reap_early_pipe(sx, &count)) != OK)
-  return rc == FAIL ? -4 : -5;
+  return rc == FAIL ? RESP_EPIPE_EHLO_ERR : RESP_EHLO_ERR_TLS;
 #endif
 
 /* Handle the response for a MAIL command. On error, reinstate the original
@@ -1243,7 +1254,7 @@ if (sx->pending_MAIL)
     DEBUG(D_transport) debug_printf("bad response for MAIL\n");
     Ustrcpy(big_buffer, mail_command);  /* Fits, because it came from there! */
     if (errno == ERRNO_TLSFAILURE)
-      return -5;
+      return RESP_EHLO_ERR_TLS;
     if (errno == 0 && sx->buffer[0] != 0)
       {
       int save_errno = 0;
@@ -1263,7 +1274,7 @@ if (sx->pending_MAIL)
       addr->host_used = sx->conn_args.host;
       addr = addr->next;
       }
-    return -3;
+    return RESP_MAIL_OR_DATA_ERROR;
     }
   }
 
@@ -1277,7 +1288,7 @@ while (count-- > 0)
   {
   while (addr->transport_return != PENDING_DEFER)
     if (!(addr = addr->next))
-      return -2;
+      return RESP_RCPT_ERROR;
 
   /* The address was accepted */
   addr->host_used = sx->conn_args.host;
@@ -1286,7 +1297,7 @@ while (count-- > 0)
   if (smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
 			  '2', ob->command_timeout))
     {
-    yield |= 1;
+    yield |= RESP_BIT_HAD_2XX;
     addr->transport_return = PENDING_OK;
 
     /* If af_dr_retry_exists is set, there was a routing delay on this address;
@@ -1305,7 +1316,7 @@ while (count-- > 0)
   /* Error on first TLS read */
 
   else if (errno == ERRNO_TLSFAILURE)
-    return -5;
+    return RESP_EHLO_ERR_TLS;
 
   /* Timeout while reading the response */
 
@@ -1316,7 +1327,7 @@ while (count-- > 0)
     set_errno_nohost(sx->first_addr, ETIMEDOUT, message, DEFER, FALSE, &sx->delivery_start);
     retry_add_item(addr, addr->address_retry_key, 0);
     update_waiting = FALSE;
-    return -1;
+    return RESP_RCPT_TIMEO;
     }
 
   /* Handle other errors in obtaining an SMTP response by returning -1. This
@@ -1334,7 +1345,7 @@ while (count-- > 0)
     g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, "RCPT TO:<%s>",
       transport_rcpt_address(addr, sx->conn_args.tblock->rcpt_include_affixes));
     string_from_gstring(g);
-    return -2;
+    return RESP_RCPT_ERROR;
     }
 
   /* Handle SMTP permanent and temporary response codes. */
@@ -1354,7 +1365,7 @@ while (count-- > 0)
     if (sx->buffer[0] == '5')
       {
       addr->transport_return = FAIL;
-      yield |= 2;
+      yield |= RESP_BIT_HAD_5XX;
       }
 
     /* The response was 4xx */
@@ -1374,7 +1385,7 @@ while (count-- > 0)
 	/* If a 452 and we've had at least one 2xx or 5xx, set next_addr to the
 	start point for another MAIL command. */
 
-	if (addr->more_errno >> 8 == 52  &&  yield & 3)
+	if (addr->more_errno >> 8 == 52  &&  yield > 0)
 	  {
 	  if (!sx->RCPT_452)		/* initialised at MAIL-ack above */
 	    {
@@ -1424,7 +1435,7 @@ while (count-- > 0)
       }
     }
   if (count && !(addr = addr->next))
-    return -2;
+    return RESP_RCPT_ERROR;
   }       /* Loop for next RCPT response */
 
 /* Update where to start at for the next block of responses, unless we
@@ -1446,16 +1457,16 @@ if (pending_DATA != 0)
     BOOL pass_message;
 
     if (errno == ERRNO_TLSFAILURE)	/* Error on first TLS read */
-      return -5;
+      return RESP_EHLO_ERR_TLS;
 
-    if (pending_DATA > 0 || (yield & 1) != 0)
+    if (pending_DATA > 0 || yield & RESP_BIT_HAD_2XX)
       {
       if (errno == 0 && sx->buffer[0] == '4')
 	{
 	errno = ERRNO_DATA4XX;
 	sx->first_addr->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
 	}
-      return -3;
+      return RESP_MAIL_OR_DATA_ERROR;
       }
     (void)check_response(sx->conn_args.host, &errno, 0, sx->buffer, &code, &msg, &pass_message);
     DEBUG(D_transport) debug_printf("%s\nerror for DATA ignored: pipelining "
@@ -2008,18 +2019,18 @@ if (flags & tc_reap_prev  &&  prev_cmd_count > 0)
 
   switch(sync_responses(sx, prev_cmd_count, 0))
     {
-    case 1:				/* 2xx (only) => OK */
-    case 3: sx->good_RCPT = TRUE;	/* 2xx & 5xx => OK & progress made */
-    case 2: sx->completed_addr = TRUE;	/* 5xx (only) => progress made */
-    case 0: break;			/* No 2xx or 5xx, but no probs */
+    case RESP_BIT_HAD_2XX:				/* OK */
+    case RESP_HAD_2_AND_5: sx->good_RCPT = TRUE;	/* OK & progress made */
+    case RESP_BIT_HAD_5XX: sx->completed_addr = TRUE;	/* progress made */
+    case RESP_NOERROR:	   break;	/* No 2xx or 5xx, but no probs */
 
-    case -5: errno = ERRNO_TLSFAILURE;
-	     return DEFER;
+    case RESP_EHLO_ERR_TLS:errno = ERRNO_TLSFAILURE;
+			   return DEFER;
 #ifndef DISABLE_PIPE_CONNECT
-    case -4:				/* non-2xx for pipelined banner or EHLO */
+    case RESP_EPIPE_EHLO_ERR:		/* non-2xx for pipelined banner or EHLO */
 #endif
-    case -1:				/* Timeout on RCPT */
-    default: return ERROR;		/* I/O error, or any MAIL/DATA error */
+    case RESP_RCPT_TIMEO:		/* Timeout on RCPT */
+    default:		   return ERROR;/* I/O error, or any MAIL/DATA error */
     }
   cmd_count = 1;
   if (!sx->pending_BDAT)
@@ -2523,7 +2534,7 @@ goto SEND_QUIT;
 	DEBUG(D_transport) debug_printf("may need to auth, so pipeline no further\n");
 	if (smtp_write_command(sx, SCMD_FLUSH, NULL) < 0)
 	  goto SEND_FAILED;
-	if (sync_responses(sx, 2, 0) != 0)
+	if (sync_responses(sx, 2, 0) != RESP_NOERROR)
 	  {
 	  HDEBUG(D_transport)
 	    debug_printf("failed reaping pipelined cmd responses\n");
@@ -2714,7 +2725,7 @@ if (  smtp_peer_options & OPTION_TLS
 
   if (sx->early_pipe_active)
     {
-    if (sync_responses(sx, 2, 0) != 0)
+    if (sync_responses(sx, 2, 0) != RESP_NOERROR)
       {
       HDEBUG(D_transport)
 	debug_printf("failed reaping pipelined cmd responses\n");
@@ -3568,14 +3579,14 @@ for (addr = sx->first_addr, address_count = 0, pipe_limit = 100;
     {
     switch(sync_responses(sx, cmds_sent, 0))
       {
-      case 3: sx->ok = TRUE;			/* 2xx & 5xx => OK & progress made */
-      case 2: sx->completed_addr = TRUE;	/* 5xx (only) => progress made */
+      case RESP_HAD_2_AND_5: sx->ok = TRUE;	/* OK & progress made */
+      case RESP_BIT_HAD_5XX: sx->completed_addr = TRUE;	/* progress made */
 	      break;
 
-      case 1: sx->ok = TRUE;			/* 2xx (only) => OK, but if LMTP, */
+      case RESP_BIT_HAD_2XX: sx->ok = TRUE;	/* OK, but if LMTP, */
 	      if (!sx->lmtp)			/*  can't tell about progress yet */
 		sx->completed_addr = TRUE;
-      case 0:					/* No 2xx or 5xx, but no probs */
+      case RESP_NOERROR:			/* No 2xx or 5xx, but no probs */
 	      /* If any RCPT got a 452 response then next_addr has been updated
 	      for restarting with a new MAIL on the same connection.  Send no more
 	      RCPTs for this MAIL. */
@@ -3589,13 +3600,13 @@ for (addr = sx->first_addr, address_count = 0, pipe_limit = 100;
 		}
 	      break;
 
-      case -1: return -3;			/* Timeout on RCPT */
-      case -2: return -2;			/* non-MAIL read i/o error */
-      default: return -1;			/* any MAIL error */
+      case RESP_RCPT_TIMEO:	    return -3;	/* Timeout on RCPT */
+      case RESP_RCPT_ERROR:	    return -2;	/* non-MAIL read i/o error */
+      default:			    return -1;	/* any MAIL error */
 
 #ifndef DISABLE_PIPE_CONNECT
-      case -4: return -1;			/* non-2xx for pipelined banner or EHLO */
-      case -5: return -1;			/* TLS first-read error */
+      case RESP_EPIPE_EHLO_ERR:	    return -1;	/* non-2xx for pipelined banner or EHLO */
+      case RESP_EHLO_ERR_TLS:	    return -1;	/* TLS first-read error */
 #endif
       }
     }
@@ -3956,17 +3967,19 @@ if (  !(smtp_peer_options & OPTION_CHUNKING)
   int count = smtp_write_command(sx, SCMD_FLUSH, "DATA\r\n");
 
   if (count < 0) goto SEND_FAILED;
+
   switch(sync_responses(sx, count, sx->ok ? +1 : -1))
     {
-    case 3: sx->ok = TRUE;            /* 2xx & 5xx => OK & progress made */
-    case 2: sx->completed_addr = TRUE;    /* 5xx (only) => progress made */
-    break;
+    case RESP_HAD_2_AND_5: sx->ok = TRUE;	/* OK & progress made */
+    case RESP_BIT_HAD_5XX: sx->completed_addr = TRUE; /* progress made */
+			   break;
 
-    case 1: sx->ok = TRUE;            /* 2xx (only) => OK, but if LMTP, */
-    if (!sx->lmtp) sx->completed_addr = TRUE; /* can't tell about progress yet */
-    case 0: break;			/* No 2xx or 5xx, but no probs */
+    case RESP_BIT_HAD_2XX: sx->ok = TRUE;	/* OK, but if LMTP, */
+			   if (!sx->lmtp)	/* can't tell about progress yet */
+			    sx->completed_addr = TRUE;
+    case RESP_NOERROR:	   break;		/* No 2xx or 5xx, but no probs */
 
-    case -1: goto END_OFF;		/* Timeout on RCPT */
+    case RESP_RCPT_TIMEO:  goto END_OFF;
 
 #ifndef DISABLE_PIPE_CONNECT
     case -5:				/* TLS first-read error */
@@ -4196,22 +4209,23 @@ else
     /* Reap any outstanding MAIL & RCPT commands, but not a DATA-go-ahead */
     switch(sync_responses(sx, sx->cmd_count-1, 0))
       {
-      case 3: sx->ok = TRUE;            /* 2xx & 5xx => OK & progress made */
-      case 2: sx->completed_addr = TRUE;    /* 5xx (only) => progress made */
-	      break;
+      case RESP_HAD_2_AND_5: sx->ok = TRUE;		/* OK & progress made */
+      case RESP_BIT_HAD_5XX: sx->completed_addr = TRUE;	/* progress made */
+			     break;
 
-      case 1: sx->ok = TRUE;		/* 2xx (only) => OK, but if LMTP, */
-      if (!sx->lmtp) sx->completed_addr = TRUE; /* can't tell about progress yet */
-      case 0: break;			/* No 2xx or 5xx, but no probs */
+      case RESP_BIT_HAD_2XX: sx->ok = TRUE;		/*  OK, but if LMTP, */
+			     if (!sx->lmtp)		/* can't tell about progress yet */
+			       sx->completed_addr = TRUE;
+      case RESP_NOERROR:     break;			/* No 2xx or 5xx, but no probs */
 
-      case -1: goto END_OFF;		/* Timeout on RCPT */
+      case RESP_RCPT_TIMEO: goto END_OFF;		/* Timeout on RCPT */
 
 #ifndef DISABLE_PIPE_CONNECT
-      case -5:				/* TLS first-read error */
-      case -4:  HDEBUG(D_transport)
+      case RESP_EHLO_ERR_TLS:				/* TLS first-read error */
+      case RESP_EPIPE_EHLO_ERR:  HDEBUG(D_transport)
 		  debug_printf("failed reaping pipelined cmd responses\n");
 #endif
-      default: goto RESPONSE_FAILED;	/* I/O error, or any MAIL/DATA error */
+      default:		     goto RESPONSE_FAILED;	/* I/O error, or any MAIL/DATA error */
       }
     }
 

commit f0ce0ecaa054ad9773a2078bc8f011a59518120a
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Dec 29 22:12:58 2023 +0000

    Tidying: enum for smtp_write_mail_and_rcpt_cmds() retcodes

diff --git a/src/src/macros.h b/src/src/macros.h
index e2c1d0f94..5279f70d0 100644
--- a/src/src/macros.h
+++ b/src/src/macros.h
@@ -1175,4 +1175,14 @@ typedef unsigned mcs_flags;
 #define SR_FINAL	TRUE
 #define SR_NOT_FINAL	FALSE
 
+/* Return codes for smtp_write_mail_and_rcpt_cmds() */
+typedef enum {
+  sw_mrc_ok,	/* good, rcpt results in addr->transport_return (PENDING_OK, DEFER, FAIL) */
+  sw_mrc_bad_mail,		/* MAIL response error */
+  sw_mrc_bad_read,		/* any non-MAIL read i/o error */
+  sw_mrc_nonmail_read_timeo,	/* non-MAIL response timeout */
+  sw_mrc_bad_internal,		/* internal error; channel still usable */
+  sw_mrc_tx_fail,		/* transmit failed */
+} sw_mrc_t;
+
 /* End of macros.h */
diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index 194671272..817d729a7 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -3415,17 +3415,11 @@ if (sx->peer_offered & OPTION_DSN && !(addr->dsn_flags & rf_dsnlasthop))
 
 
 
-/*
-Return:
- 0	good, rcpt results in addr->transport_return (PENDING_OK, DEFER, FAIL)
- -1	MAIL response error
- -2	any non-MAIL read i/o error
- -3	non-MAIL response timeout
- -4	internal error; channel still usable
- -5	transmit failed
+/* Send MAIL FROM and RCPT TO commands.
+See sw_mrc_t definition for return codes.
  */
 
-int
+sw_mrc_t
 smtp_write_mail_and_rcpt_cmds(smtp_context * sx, int * yield)
 {
 address_item * addr;
@@ -3438,7 +3432,7 @@ int rc;
 if (build_mailcmd_options(sx, sx->first_addr) != OK)
   {
   *yield = ERROR;
-  return -4;
+  return sw_mrc_bad_internal;
   }
 
 /* From here until we send the DATA command, we can make use of PIPELINING
@@ -3466,7 +3460,7 @@ sx->pending_MAIL = TRUE;     /* The block starts with MAIL */
       {
       set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, errstr, DEFER, FALSE, &sx->delivery_start);
       *yield = ERROR;
-      return -4;
+      return sw_mrc_bad_internal;
       }
     setflag(sx->addrlist, af_utf8_downcvt);
     }
@@ -3481,7 +3475,7 @@ mail_command = string_copy(big_buffer);  /* Save for later error message */
 switch(rc)
   {
   case -1:                /* Transmission error */
-    return -5;
+    return sw_mrc_bad_mail;
 
   case +1:                /* Cmd was sent */
     if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2',
@@ -3492,7 +3486,7 @@ switch(rc)
 	errno = ERRNO_MAIL4XX;
 	sx->addrlist->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
 	}
-      return -1;
+      return sw_mrc_bad_mail;
       }
     sx->pending_MAIL = FALSE;
     break;
@@ -3567,14 +3561,14 @@ for (addr = sx->first_addr, address_count = 0, pipe_limit = 100;
     {
     /*XXX could we use a per-address errstr here? Not fail the whole send? */
     errno = ERRNO_EXPANDFAIL;
-    return -5;		/*XXX too harsh? */
+    return sw_mrc_tx_fail;		/*XXX too harsh? */
     }
 #endif
 
   cmds_sent = smtp_write_command(sx, no_flush ? SCMD_BUFFER : SCMD_FLUSH,
     "RCPT TO:<%s>%s%s\r\n", rcpt_addr, sx->igquotstr, sx->buffer);
 
-  if (cmds_sent < 0) return -5;
+  if (cmds_sent < 0) return sw_mrc_tx_fail;
   if (cmds_sent > 0)
     {
     switch(sync_responses(sx, cmds_sent, 0))
@@ -3596,17 +3590,17 @@ for (addr = sx->first_addr, address_count = 0, pipe_limit = 100;
 		DEBUG(D_transport) debug_printf("seen 452 too-many-rcpts\n");
 		sx->RCPT_452 = FALSE;
 		/* sx->next_addr has been reset for fast_retry */
-		return 0;
+		return sw_mrc_ok;
 		}
 	      break;
 
-      case RESP_RCPT_TIMEO:	    return -3;	/* Timeout on RCPT */
-      case RESP_RCPT_ERROR:	    return -2;	/* non-MAIL read i/o error */
-      default:			    return -1;	/* any MAIL error */
+      case RESP_RCPT_TIMEO:	    return sw_mrc_nonmail_read_timeo;
+      case RESP_RCPT_ERROR:	    return sw_mrc_bad_read;
+      default:			    return sw_mrc_bad_mail;	/* any MAIL error */
 
 #ifndef DISABLE_PIPE_CONNECT
-      case RESP_EPIPE_EHLO_ERR:	    return -1;	/* non-2xx for pipelined banner or EHLO */
-      case RESP_EHLO_ERR_TLS:	    return -1;	/* TLS first-read error */
+      case RESP_EPIPE_EHLO_ERR:	    return sw_mrc_bad_mail;	/* non-2xx for pipelined banner or EHLO */
+      case RESP_EHLO_ERR_TLS:	    return sw_mrc_bad_mail;	/* TLS first-read error */
 #endif
       }
     }
@@ -3617,7 +3611,7 @@ sx->next_addr = restart_addr ? restart_addr : addr;
 #else
 sx->next_addr = addr;
 #endif
-return 0;
+return sw_mrc_ok;
 }
 
 
@@ -3925,11 +3919,12 @@ else
 
   switch(smtp_write_mail_and_rcpt_cmds(sx, &yield))
     {
-    case 0:		break;
-    case -1: case -2:	goto RESPONSE_FAILED;
-    case -3:		goto END_OFF;
-    case -4:		goto SEND_QUIT;
-    default:		goto SEND_FAILED;
+    case sw_mrc_ok:			break;
+    case sw_mrc_bad_mail:		goto RESPONSE_FAILED;
+    case sw_mrc_bad_read:		goto RESPONSE_FAILED;
+    case sw_mrc_nonmail_read_timeo:	goto END_OFF;
+    case sw_mrc_bad_internal:		goto SEND_QUIT;
+    default:				goto SEND_FAILED;
     }
 
   /* If we are an MUA wrapper, abort if any RCPTs were rejected, either
diff --git a/src/src/transports/smtp.h b/src/src/transports/smtp.h
index 1f888ee0b..ef9135838 100644
--- a/src/src/transports/smtp.h
+++ b/src/src/transports/smtp.h
@@ -225,7 +225,7 @@ typedef struct {
 } smtp_context;
 
 extern int smtp_setup_conn(smtp_context *, BOOL);
-extern int smtp_write_mail_and_rcpt_cmds(smtp_context *, int *);
+extern sw_mrc_t smtp_write_mail_and_rcpt_cmds(smtp_context *, int *);
 extern int smtp_reap_early_pipe(smtp_context *, int *);
 
 
diff --git a/src/src/verify.c b/src/src/verify.c
index 194e9a76a..bde660063 100644
--- a/src/src/verify.c
+++ b/src/src/verify.c
@@ -823,7 +823,7 @@ tls_retry_connection:
       /* Remember when we last did a random test */
       new_domain_record.random_stamp = time(NULL);
 
-      if (smtp_write_mail_and_rcpt_cmds(sx, &yield) == 0)
+      if (smtp_write_mail_and_rcpt_cmds(sx, &yield) == sw_mrc_ok)
 	switch(addr->transport_return)
 	  {
 	  case PENDING_OK:	/* random was accepted, unfortunately */
@@ -891,33 +891,34 @@ tls_retry_connection:
       done = FALSE;
       switch(smtp_write_mail_and_rcpt_cmds(sx, &yield))
 	{
-	case 0:  switch(addr->transport_return)	/* ok so far */
-		    {
-		    case PENDING_OK:  done = TRUE;
-				      new_address_record.result = ccache_accept;
-				      break;
-		    case FAIL:	      done = TRUE;
-				      yield = FAIL;
-				      *failure_ptr = US"recipient";
-				      new_address_record.result = ccache_reject;
-				      break;
-		    default:	      break;
-		    }
-		  break;
+	case sw_mrc_ok:
+	  switch(addr->transport_return)	/* ok so far */
+	    {
+	    case PENDING_OK:  done = TRUE;
+			      new_address_record.result = ccache_accept;
+			      break;
+	    case FAIL:	      done = TRUE;
+			      yield = FAIL;
+			      *failure_ptr = US"recipient";
+			      new_address_record.result = ccache_reject;
+			      break;
+	    default:	      break;
+	    }
+	  break;
 
-	case -1:				/* MAIL response error */
-		  *failure_ptr = US"mail";
-		  if (errno == 0 && sx->buffer[0] == '5')
-		    {
-		    setflag(addr, af_verify_nsfail);
-		    if (from_address[0] == 0)
-		      new_domain_record.result = ccache_reject_mfnull;
-		    }
-		  break;
-						/* non-MAIL read i/o error */
-						/* non-MAIL response timeout */
-						/* internal error; channel still usable */
-	default:  break;			/* transmit failed */
+	case sw_mrc_bad_mail:			/* MAIL response error */
+	  *failure_ptr = US"mail";
+	  if (errno == 0 && sx->buffer[0] == '5')
+	    {
+	    setflag(addr, af_verify_nsfail);
+	    if (from_address[0] == 0)
+	      new_domain_record.result = ccache_reject_mfnull;
+	    }
+	  break;
+					/* non-MAIL read i/o error */
+					/* non-MAIL response timeout */
+					/* internal error; channel still usable */
+	default:  break;		/* transmit failed */
 	}
       }
 
@@ -955,7 +956,7 @@ tls_retry_connection:
 	sx->completed_addr = FALSE;
 	sx->avoid_option = OPTION_SIZE;
 
-	if(  smtp_write_mail_and_rcpt_cmds(sx, &yield) == 0
+	if(  smtp_write_mail_and_rcpt_cmds(sx, &yield) == sw_mrc_ok
 	  && addr->transport_return == PENDING_OK
 	  )
 	  done = TRUE;

commit 690bc2ce8c7f0a76f01d03b5b158b0d64abbc86a
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sat Oct 14 19:35:34 2023 +0100

    fdatasync the spool data file

diff --git a/src/src/exim.h b/src/src/exim.h
index ccf14f0fd..699b39165 100644
--- a/src/src/exim.h
+++ b/src/src/exim.h
@@ -569,7 +569,7 @@ requires various things that are set therein. */
 #endif
 
 #ifdef ENABLE_DISABLE_FSYNC
-# define EXIMfsync(f) (disable_fsync? 0 : fsync(f))
+# define EXIMfsync(f) (disable_fsync ? 0 : fsync(f))
 #else
 # define EXIMfsync(f) fsync(f)
 #endif
diff --git a/src/src/receive.c b/src/src/receive.c
index ae7045068..63aded09f 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -4052,7 +4052,15 @@ else
 
 receive_messagecount++;
 
-if (fflush(spool_data_file))
+if (  fflush(spool_data_file)
+#if _POSIX_C_SOURCE >= 199309L || _XOPEN_SOURCE >= 500
+# ifdef ENABLE_DISABLE_FSYNC
+   || !disable_fsync && fdatasync(data_fd)
+# else
+   || fdatasync(data_fd)
+# endif
+#endif
+   )
   {
   errmsg = string_sprintf("Spool write error: %s", strerror(errno));
   log_write(0, LOG_MAIN, "%s\n", errmsg);

commit 17648b558fc29a488d1e0bc12d2960f892d2838a
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sun Dec 31 19:59:09 2023 +0000

    Rewrites: avoid a split log line when rewiting hits a malformed address.  Bug 3160

diff --git a/src/src/parse.c b/src/src/parse.c
index 29f98e1e2..ecdbe9f8d 100644
--- a/src/src/parse.c
+++ b/src/src/parse.c
@@ -69,30 +69,31 @@ Returns:   pointer past the end of the address
 */
 
 uschar *
-parse_find_address_end(const uschar *s, BOOL nl_ends)
+parse_find_address_end(const uschar * s, BOOL nl_ends)
 {
 BOOL source_routing = *s == '@';
-int no_term = source_routing? 1 : 0;
+int no_term = source_routing ? 1 : 0;
 
-while (*s != 0 && (*s != ',' || no_term > 0) && (*s != '\n' || !nl_ends))
+while (*s && (*s != ',' || no_term > 0) && (*s != '\n' || !nl_ends))
   {
   /* Skip single quoted characters. Strictly these should not occur outside
   quoted strings in RFC 822 addresses, but they can in RFC 821 addresses. Pity
   about the lack of consistency, isn't it? */
 
-  if (*s == '\\' && s[1] != 0) s += 2;
+  if (*s == '\\' && s[1])
+    s += 2;
 
   /* Skip quoted items that are not inside brackets. Note that
   quoted pairs are allowed inside quoted strings. */
 
   else if (*s == '\"')
-    {
-    while (*(++s) != 0 && (*s != '\n' || !nl_ends))
+    while (*++s && (*s != '\n' || !nl_ends))
       {
-      if (*s == '\\' && s[1] != 0) s++;
-        else if (*s == '\"') { s++; break; }
+      if (*s == '\\' && s[1])
+	s++;
+      else if (*s == '\"')
+	{ s++; break; }
       }
-    }
 
   /* Skip comments, which may include nested brackets, but quotes
   are not recognized inside comments, though quoted pairs are. */
@@ -100,12 +101,13 @@ while (*s != 0 && (*s != ',' || no_term > 0) && (*s != '\n' || !nl_ends))
   else if (*s == '(')
     {
     int level = 1;
-    while (*(++s) != 0 && (*s != '\n' || !nl_ends))
-      {
-      if (*s == '\\' && s[1] != 0) s++;
-        else if (*s == '(') level++;
-          else if (*s == ')' && --level <= 0) { s++; break; }
-      }
+    while (*++s && (*s != '\n' || !nl_ends))
+      if (*s == '\\' && s[1])
+	s++;
+      else if (*s == '(')
+	level++;
+      else if (*s == ')' && --level <= 0)
+	{ s++; break; }
     }
 
   /* Non-special character; just advance. Passing the colon in a source
@@ -117,10 +119,12 @@ while (*s != 0 && (*s != ',' || no_term > 0) && (*s != '\n' || !nl_ends))
     if (*s == '<')
       {
       source_routing = s[1] == '@';
-      no_term = source_routing? 2 : 1;
+      no_term = source_routing ? 2 : 1;
       }
-    else if (*s == '>') no_term--;
-    else if (source_routing && *s == ':') no_term--;
+    else if (*s == '>')
+      no_term--;
+    else if (source_routing && *s == ':')
+      no_term--;
     s++;
     }
   }
diff --git a/src/src/receive.c b/src/src/receive.c
index 63aded09f..ae4203e7f 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -3069,7 +3069,7 @@ if (  from_header
 it has already been rewritten as part of verification for SMTP input. */
 
 DEBUG(D_rewrite)
-  { debug_printf("global rewrite rules\n"); acl_level++; }
+  { debug_printf("rewrite rules on sender address\n"); acl_level++; }
 if (global_rewrite_rules && !sender_address_unrewritten && *sender_address)
   {
   /* deconst ok as src was not const */
@@ -3096,7 +3096,7 @@ documented as happening *after* recipient addresses are taken from the headers
 by the -t command line option. An added Sender: gets rewritten here. */
 
 DEBUG(D_rewrite)
-  { debug_printf("rewrite headers\n"); acl_level++; }
+  { debug_printf("qualify and rewrite headers\n"); acl_level++; }
 for (header_line * h = header_list->next, * newh; h; h = h->next)
   if ((newh = rewrite_header(h, NULL, NULL, global_rewrite_rules,
 			      rewrite_existflags, TRUE)))
@@ -3135,9 +3135,11 @@ new Received:) has not yet been set. */
 DEBUG(D_receive)
   {
   debug_printf(">>Headers after rewriting and local additions:\n");
+  acl_level++;
   for (header_line * h = header_list->next; h; h = h->next)
-    debug_printf("%c %s", h->type, h->text);
+    debug_printf_indent("%c %s", h->type, h->text);
   debug_printf("\n");
+  acl_level--;
   }
 
 /* The headers are now complete in store. If we are running in filter
diff --git a/src/src/rewrite.c b/src/src/rewrite.c
index c0d173abf..318cda215 100644
--- a/src/src/rewrite.c
+++ b/src/src/rewrite.c
@@ -450,13 +450,13 @@ rewrite_one_header(header_line *h, int flag,
   rewrite_rule *rewrite_rules, int existflags, BOOL replace)
 {
 int lastnewline = 0;
-header_line *newh = NULL;
+header_line * newh = NULL;
 rmark function_reset_point = store_mark();
-uschar *s = Ustrchr(h->text, ':') + 1;
+uschar * s = Ustrchr(h->text, ':') + 1;
 
 while (isspace(*s)) s++;
 
-DEBUG(D_rewrite)
+DEBUG(D_rewrite)	/* The header text includes the trailing newline */
   debug_printf_indent("rewrite_one_header: type=%c:\n  %s", h->type, h->text);
 
 f.parse_allow_group = TRUE;     /* Allow group syntax */
@@ -470,24 +470,31 @@ We want to avoid keeping store for any intermediate versions. */
 
 while (*s)
   {
-  uschar *sprev;
-  uschar *ss = parse_find_address_end(s, FALSE);
-  uschar *recipient, *new;
+  uschar * sprev = s;
+  uschar * ss = parse_find_address_end(s, FALSE), * ss1 = ss;
+  uschar * recipient, * new;
   rmark loop_reset_point = store_mark();
-  uschar *errmess = NULL;
+  uschar * errmess = NULL;
   BOOL changed = FALSE;
-  int terminator = *ss;
+  uschar terminator = *ss;
   int start, end, domain;
 
+  /* If we hit the end of the header, trim trailing newline and whitespace */
+
+  if (!terminator)
+    {
+    while (ss1 > s && isspace(ss1[-1])) ss1--;
+    terminator = *ss1;
+    }
+
   /* Temporarily terminate the string at this point, and extract the
   operative address within. Then put back the terminator and prepare for
   the next address, saving the start of the old one. */
 
-  *ss = 0;
+  *ss1 = '\0';
   recipient = parse_extract_address(s, &errmess, &start, &end, &domain, FALSE);
-  *ss = terminator;
-  sprev = s;
-  s = ss + (terminator ? 1 : 0);
+  *ss1 = terminator;
+  s = ss + (*ss ? 1 : 0);
   while (isspace(*s)) s++;
 
   /* There isn't much we can do for syntactic disasters at this stage.
@@ -587,7 +594,8 @@ while (*s)
   point, because we may have a rewritten line from a previous time round the
   loop. */
 
-  if (!changed) loop_reset_point = store_reset(loop_reset_point);
+  if (!changed)
+    loop_reset_point = store_reset(loop_reset_point);
 
   /* If the address has changed, create a new header containing the
   rewritten address. We do not need to set the chain pointers at this

commit 12fab4b0af476cf9d4bd389a2c4ebf7ff76e8252
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sun Dec 31 22:12:17 2023 +0000

    Address qualification: ensure maformed addresses are logged.  Bug 3061

diff --git a/src/src/rewrite.c b/src/src/rewrite.c
index 318cda215..70bd79e26 100644
--- a/src/src/rewrite.c
+++ b/src/src/rewrite.c
@@ -510,8 +510,8 @@ while (*s)
     address, "To: undisclosed recpients:;" being the classic case. Ignore
     this one and carry on. */
 
-    if ((rewrite_rules || routed_old) && Ustrcmp(errmess, "empty address") != 0)
-      log_write(0, LOG_MAIN, "rewrite: %s", errmess);
+    if (Ustrcmp(errmess, "empty address") != 0)
+      log_write(0, LOG_MAIN, "qualify/rewrite: %s", errmess);
 
     loop_reset_point = store_reset(loop_reset_point);
     continue;

commit 4f1d6cb9b60f4d26c0654eba8889c04079d046d2
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Thu Jan 4 22:29:59 2024 +0000

    Report two-phase queue run in daemon startup log line

diff --git a/src/src/daemon.c b/src/src/daemon.c
index aff05120a..704ede24e 100644
--- a/src/src/daemon.c
+++ b/src/src/daemon.c
@@ -1654,7 +1654,7 @@ return 0;
 
 
 
-const uschar *
+static const uschar *
 describe_queue_runners(void)
 {
 gstring * g = NULL;
@@ -1664,6 +1664,7 @@ if (!is_multiple_qrun()) return US"no queue runs";
 for (qrunner * q = qrunners; q; q = q->next)
   {
   g = string_catn(g, US"-q", 2);
+  if (q->queue_2stage) g = string_catn(g, US"q", 1);
   if (q->name) g = string_append(g, 3, US"G", q->name, US"/");
   g = string_cat(g, readconf_printtime(q->interval));
   g = string_catn(g, US" ", 1);

commit 77da14136b689daefe37608b4fd77417cb8c17b3
Author: Simon Arlott <sa.me.uk>
Date:   Sun Jan 7 18:04:55 2024 +0000

    DKIM: signing with t but no x timestamp.  Bug 2851

diff --git a/src/src/dkim.c b/src/src/dkim.c
index 59025608f..63b16af39 100644
--- a/src/src/dkim.c
+++ b/src/src/dkim.c
@@ -720,8 +720,12 @@ if (dkim_domain)
       if (!(dkim_timestamps_expanded = expand_string(dkim->dkim_timestamps)))
 	{ errwhen = US"dkim_timestamps"; goto expand_bad; }
       else
-	xval = (tval = (unsigned long) time(NULL))
-	      + strtoul(CCS dkim_timestamps_expanded, NULL, 10);
+        {
+        tval = (unsigned long) time(NULL);
+        xval = strtoul(CCS dkim_timestamps_expanded, NULL, 10);
+        if (xval > 0)
+          xval += tval;
+        }
 
     if (!(sig = pdkim_init_sign(&dkim_sign_ctx, dkim_signing_domain,
 			  dkim_signing_selector,

commit 15851896ef1507c0901a4150fd869cbce6c17e3d
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Mon Jan 8 16:01:55 2024 +0000

    Fix recipient (or source) selection in combination with periodic queue run. Bug 3064
    
    Broken-by: 1e835086d159

diff --git a/src/src/exim.c b/src/src/exim.c
index 4473373b3..c53b98186 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -3688,7 +3688,7 @@ on the second character (the one after '-'), to save some effort. */
     in all cases provided there are no further characters in this
     argument. */
 
-      alloc_onetime_qrunner();
+      if (!qrunners) alloc_onetime_qrunner();
       qrunners->queue_2stage = f.queue_2stage;
       if (*argrest)
 	for (int i = 0; i < nelem(rsopts); i++)

commit 4da37662868dcfd8ec23ad9d7f643ef014b1a42b
Author: Heiko Schlichting <heiko@fu-berlin.de>
Date:   Fri Jan 12 21:43:54 2024 +0000

    Eximon: handle new-format message IDs
    
    Broken-by: 46a36afae4

diff --git a/src/exim_monitor/em_hdr.h b/src/exim_monitor/em_hdr.h
index feffa6bfc..f79686a80 100644
--- a/src/exim_monitor/em_hdr.h
+++ b/src/exim_monitor/em_hdr.h
@@ -190,7 +190,7 @@ typedef struct queue_item {
   int  update_time;
   int  size;
   uschar *sender;
-  uschar name[17];
+  uschar name[MESSAGE_ID_LENGTH + 1];
   uschar seen;
   uschar frozen;
   uschar dir_char;
diff --git a/src/exim_monitor/em_log.c b/src/exim_monitor/em_log.c
index 55925d786..fbcb6b1c0 100644
--- a/src/exim_monitor/em_log.c
+++ b/src/exim_monitor/em_log.c
@@ -108,7 +108,9 @@ length = Ustrlen(buffer);
 #ifdef ANONYMIZE
   {
   uschar *p = buffer + 9;
-  if (p[6] == '-' && p[13] == '-') p += 17;
+  if (  p[MESSAGE_ID_TIME_LEN] == '-'
+     && p[MESSAGE_ID_TIME_LEN + MESSAGE_ID_PID_LEN + 1] == '-')
+      p += MESSAGE_ID_LENGTH + 1;
 
   while (p < buffer + length)
     {

commit 06b43f1b7f7319445d4a71b9a60e3f2f144cf7a2
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sat Jan 13 15:24:12 2024 +0000

    Lookups: log warning for deprecated syntax.  Bug 3068

diff --git a/src/src/lookups/lf_sqlperform.c b/src/src/lookups/lf_sqlperform.c
index ecb0a3221..d5bb8d297 100644
--- a/src/src/lookups/lf_sqlperform.c
+++ b/src/src/lookups/lf_sqlperform.c
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) The Exim Maintainers 2020 - 2022 */
+/* Copyright (c) The Exim Maintainers 2020 - 2024 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 /* SPDX-License-Identifier: GPL-2.0-or-later */
@@ -56,6 +56,8 @@ if (Ustrncmp(query, "servers", 7) == 0)
   const uschar *qserverlist;
   uschar *qserver;
 
+  log_write(0, LOG_MAIN|LOG_CONFIG_IN, "WARNING: obslete syntax used for lookup\n");
+
   s = query + 7;
   skip_whitespace(&s);
   if (*s++ != '=')

commit 17061038519b42b6919af1d3d82d346f2259a114
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Mon Jan 15 12:54:41 2024 +0000

    Exinext: handle new-format message IDs
    
    Broken-by: 46a36afae4

diff --git a/src/src/exinext.src b/src/src/exinext.src
index 1a1d88f6b..882ad5f60 100644
--- a/src/src/exinext.src
+++ b/src/src/exinext.src
@@ -163,6 +163,7 @@ perl - $exim_path "$eximmacdef" $argone $spool_directory $qualify_domain $config
 
   if (scalar(@list) == 0)
     {
+    push(@list, $subject) if $subject =~ /^\w{6}-\w{11}-\w{4}$/;
     push(@list, $subject) if $subject =~ /^\w{6}-\w{6}-\w{2}$/;
 
     if ($subject !~ /\@/ && $subject !~ /\./)

commit fe105877d57ac7e05a4333e0d072f232d212b9fe
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sat Jan 20 12:38:15 2024 +0000

    TLS: fix startup after forced-fail
    
    Broken-by: cf0c61644d7d, ce93c6d840d5, 520ef00f56ce

diff --git a/src/src/expand.c b/src/src/expand.c
index 9a88d38ca..f9813133d 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -8558,13 +8558,12 @@ Returns:  the expanded string, or NULL if expansion failed; if failure was
 const uschar *
 expand_string_2(const uschar * string, BOOL * textonly_p)
 {
+f.expand_string_forcedfail = f.search_find_defer = malformed_header = FALSE;
 if (Ustrpbrk(string, "$\\") != NULL)
   {
   int old_pool = store_pool;
   uschar * s;
 
-  f.search_find_defer = FALSE;
-  malformed_header = FALSE;
   store_pool = POOL_MAIN;
     s = expand_string_internal(string, ESI_HONOR_DOLLAR, NULL, NULL, textonly_p);
   store_pool = old_pool;
diff --git a/src/src/tls.c b/src/src/tls.c
index b6c5d70e1..e6203b768 100644
--- a/src/src/tls.c
+++ b/src/src/tls.c
@@ -105,7 +105,10 @@ expand_check(const uschar * s, const uschar * name,
   uschar ** result, uschar ** errstr)
 {
 if (!s)
+  {
+  f.expand_string_forcedfail = FALSE;
   *result = NULL;
+  }
 else if (  !(*result = expand_string(US s)) /* need to clean up const more */
 	&& !f.expand_string_forcedfail
 	)

commit b4e7527561f1c68b821d5cf25efe29ae63d1d434
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Thu Jan 25 17:48:43 2024 +0000

    Appendfile: release regex-match store every thousand files.  Bug 3047

diff --git a/src/src/exim.c b/src/src/exim.c
index c53b98186..e5b72f11a 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -49,6 +49,8 @@ optimize out the tail recursion and so not make them too expensive. */
 static void *
 function_store_malloc(PCRE2_SIZE size, void * tag)
 {
+if (size > INT_MAX)
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "excessive memory alloc request");
 return store_malloc((int)size);
 }
 
@@ -63,12 +65,15 @@ if (block) store_free(block);
 static void *
 function_store_get(PCRE2_SIZE size, void * tag)
 {
+if (size > INT_MAX)
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "excessive memory alloc request");
 return store_get((int)size, GET_UNTAINTED);	/* loses track of taint */
 }
 
 static void
 function_store_nullfree(void * block, void * tag)
 {
+/* We cannot free memory allocated using store_get() */
 }
 
 
diff --git a/src/src/transports/appendfile.c b/src/src/transports/appendfile.c
index ec41ca035..91b353079 100644
--- a/src/src/transports/appendfile.c
+++ b/src/src/transports/appendfile.c
@@ -153,6 +153,10 @@ static const char *mailbox_formats[] = {
   (!ob->quota_warn_threshold_is_percent || ob->quota_value > 0))
 
 
+/* Free memory allocated by PCRE2 every so often, because a recent version
+is now using 20kB for every match call */
+
+#define RESET_STORE_FILECNT	1000
 
 /*************************************************
 *              Setup entry point                 *
@@ -661,13 +665,14 @@ Returns:        the sum of the sizes of the stattable files
 off_t
 check_dir_size(const uschar * dirname, int * countptr, const pcre2_code * re)
 {
-DIR *dir;
+DIR * dir;
 off_t sum = 0;
-int count = *countptr;
+int count = *countptr, lcount = RESET_STORE_FILECNT;
+rmark reset_point = store_mark();
 
 if (!(dir = exim_opendir(dirname))) return 0;
 
-for (struct dirent *ent; ent = readdir(dir); )
+for (struct dirent * ent; ent = readdir(dir); )
   {
   uschar * path, * name = US ent->d_name;
   struct stat statbuf;
@@ -675,6 +680,11 @@ for (struct dirent *ent; ent = readdir(dir); )
   if (Ustrcmp(name, ".") == 0 || Ustrcmp(name, "..") == 0) continue;
 
   count++;
+  if (--lcount == 0)
+    {
+    store_reset(reset_point); reset_point = store_mark();
+    lcount = RESET_STORE_FILECNT;
+    }
 
   /* If there's a regex, try to find the size using it */
 
@@ -726,6 +736,7 @@ DEBUG(D_transport)
   debug_printf("check_dir_size: dir=%s sum=" OFF_T_FMT " count=%d\n", dirname,
     sum, count);
 
+store_reset(reset_point);
 *countptr = count;
 return sum;
 }

commit 35aacb69f5c839a4b77158464e401d86eb422ed6
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Jan 26 21:58:59 2024 +0000

    ACL: in "regex" condition, release store every thousand lines.  Bug 3047

diff --git a/src/src/acl.c b/src/src/acl.c
index 9223fcec8..ecba07b25 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -3955,11 +3955,11 @@ for (; cb; cb = cb->next)
 	CUSS &recipient_data);
       break;
 
-    #ifdef WITH_CONTENT_SCAN
+#ifdef WITH_CONTENT_SCAN
     case ACLC_REGEX:
       rc = regex(&arg, textonly);
       break;
-    #endif
+#endif
 
     case ACLC_REMOVE_HEADER:
       setup_remove_header(arg);
diff --git a/src/src/macros.h b/src/src/macros.h
index 5279f70d0..8aed335b5 100644
--- a/src/src/macros.h
+++ b/src/src/macros.h
@@ -1185,4 +1185,9 @@ typedef enum {
   sw_mrc_tx_fail,		/* transmit failed */
 } sw_mrc_t;
 
+/* Recent versions of PCRE2 are allocating 20kB per match, rather than the previous 112 B.
+When doing en extended loop of matching, release store periodically. */
+
+#define	REGEX_LOOPCOUNT_STORE_RESET	1000
+
 /* End of macros.h */
diff --git a/src/src/regex.c b/src/src/regex.c
index af425db2d..cda807716 100644
--- a/src/src/regex.c
+++ b/src/src/regex.c
@@ -31,12 +31,11 @@ extern uschar *mime_current_boundary;
 
 
 static pcre_list *
-compile(const uschar * list, BOOL cacheable)
+compile(const uschar * list, BOOL cacheable, int * cntp)
 {
-int sep = 0;
+int sep = 0, cnt = 0;
 uschar * regex_string;
-pcre_list * re_list_head = NULL;
-pcre_list * ri;
+pcre_list * re_list_head = NULL, * ri;
 
 /* precompile our regexes */
 while ((regex_string = string_nextinlist(&list, &sep, NULL, 0)))
@@ -58,7 +57,9 @@ while ((regex_string = string_nextinlist(&list, &sep, NULL, 0)))
     ri->pcre_text = regex_string;
     ri->next = re_list_head;
     re_list_head = ri;
+    cnt++;
     }
+if (cntp) *cntp = cnt;
 return re_list_head;
 }
 
@@ -112,7 +113,8 @@ FILE * mbox_file;
 pcre_list * re_list_head;
 uschar * linebuffer;
 long f_pos = 0;
-int ret = FAIL;
+int ret = FAIL, cnt, lcount = REGEX_LOOPCOUNT_STORE_RESET;
+rmark reset_point;
 
 regex_vars_clear();
 
@@ -136,26 +138,34 @@ else
   mbox_file = mime_stream;
   }
 
-/* precompile our regexes */
-if (!(re_list_head = compile(*listptr, cacheable)))
-  return FAIL;			/* no regexes -> nothing to do */
-
-/* match each line against all regexes */
-linebuffer = store_get(32767, GET_TAINTED);
-while (fgets(CS linebuffer, 32767, mbox_file))
+reset_point = store_mark();
   {
-  if (  mime_stream && mime_current_boundary		/* check boundary */
-     && Ustrncmp(linebuffer, "--", 2) == 0
-     && Ustrncmp((linebuffer+2), mime_current_boundary,
-		  Ustrlen(mime_current_boundary)) == 0)
-      break;						/* found boundary */
-
-  if ((ret = matcher(re_list_head, linebuffer, (int)Ustrlen(linebuffer))) == OK)
-    goto done;
+  /* precompile our regexes */
+  if ((re_list_head = compile(*listptr, cacheable, &cnt)))
+    {
+    /* match each line against all regexes */
+    linebuffer = store_get(32767, GET_TAINTED);
+    while (fgets(CS linebuffer, 32767, mbox_file))
+      {
+      if (  mime_stream && mime_current_boundary		/* check boundary */
+	 && Ustrncmp(linebuffer, "--", 2) == 0
+	 && Ustrncmp((linebuffer+2), mime_current_boundary,
+		      Ustrlen(mime_current_boundary)) == 0)
+	break;						/* found boundary */
+
+      if ((ret = matcher(re_list_head, linebuffer, (int)Ustrlen(linebuffer))) == OK)
+	break;
+
+      if ((lcount -= cnt) <= 0)
+	{
+	store_reset(reset_point); reset_point = store_mark();
+	lcount = REGEX_LOOPCOUNT_STORE_RESET;
+	}
+      }
+    }
   }
-/* no matches ... */
+store_reset(reset_point);
 
-done:
 if (!mime_stream)
   (void)fclose(mbox_file);
 else
@@ -180,14 +190,11 @@ pcre_list * re_list_head = NULL;
 FILE * f;
 uschar * mime_subject = NULL;
 int mime_subject_len = 0;
-int ret;
+int ret = FAIL;
+rmark reset_point;
 
 regex_vars_clear();
 
-/* precompile our regexes */
-if (!(re_list_head = compile(*listptr, cacheable)))
-  return FAIL;			/* no regexes -> nothing to do */
-
 /* check if the file is already decoded */
 if (!mime_decoded_filename)
   {				/* no, decode it first */
@@ -210,12 +217,20 @@ if (!(f = fopen(CS mime_decoded_filename, "rb")))
   return DEFER;
   }
 
-/* get 32k memory, tainted */
-mime_subject = store_get(32767, GET_TAINTED);
+reset_point = store_mark();
+  {
+  /* precompile our regexes */
+  if ((re_list_head = compile(*listptr, cacheable, NULL)))
+    {
+    /* get 32k memory, tainted */
+    mime_subject = store_get(32767, GET_TAINTED);
 
-mime_subject_len = fread(mime_subject, 1, 32766, f);
+    mime_subject_len = fread(mime_subject, 1, 32766, f);
 
-ret = matcher(re_list_head, mime_subject, mime_subject_len);
+    ret = matcher(re_list_head, mime_subject, mime_subject_len);
+    }
+  }
+store_reset(reset_point);
 (void)fclose(f);
 return ret;
 }
diff --git a/src/src/transports/appendfile.c b/src/src/transports/appendfile.c
index 91b353079..ce52cc6ff 100644
--- a/src/src/transports/appendfile.c
+++ b/src/src/transports/appendfile.c
@@ -153,11 +153,6 @@ static const char *mailbox_formats[] = {
   (!ob->quota_warn_threshold_is_percent || ob->quota_value > 0))
 
 
-/* Free memory allocated by PCRE2 every so often, because a recent version
-is now using 20kB for every match call */
-
-#define RESET_STORE_FILECNT	1000
-
 /*************************************************
 *              Setup entry point                 *
 *************************************************/
@@ -667,7 +662,7 @@ check_dir_size(const uschar * dirname, int * countptr, const pcre2_code * re)
 {
 DIR * dir;
 off_t sum = 0;
-int count = *countptr, lcount = RESET_STORE_FILECNT;
+int count = *countptr, lcount = REGEX_LOOPCOUNT_STORE_RESET;
 rmark reset_point = store_mark();
 
 if (!(dir = exim_opendir(dirname))) return 0;
@@ -683,7 +678,7 @@ for (struct dirent * ent; ent = readdir(dir); )
   if (--lcount == 0)
     {
     store_reset(reset_point); reset_point = store_mark();
-    lcount = RESET_STORE_FILECNT;
+    lcount = REGEX_LOOPCOUNT_STORE_RESET;
     }
 
   /* If there's a regex, try to find the size using it */

commit d0e4bb183c038fe38ee249c1bd096f49120e908a
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Jan 26 21:18:01 2024 +0000

        Fix smtp transport response to close after all rcpt fates determined.  Bug 3059

diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index 817d729a7..c205145a7 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -4518,7 +4518,26 @@ if (!sx->ok)
 	break;
 
       case ERRNO_SMTPCLOSED:
-	message_error = Ustrncmp(smtp_command,"end ",4) == 0;
+	/* If the peer closed the TCP connection after end-of-data, but before
+	we could send QUIT, do TLS close, etc - call it a message error.
+	Otherwise, if all the recipients have been dealt with, call a close no
+	error at all; each address_item should have a suitable result already
+	(2xx: PENDING_OK, 4xx: DEFER, 5xx: FAIL) */
+
+	if (!(message_error = Ustrncmp(smtp_command,"end ",4) == 0))
+	  {
+	  address_item * addr;
+	  for (addr = sx->addrlist; addr; addr = addr->next)
+	    if (addr->transport_return == PENDING_DEFER)
+	      break;
+	  if (!addr)	/* all rcpts fates determined */
+	    {
+	    log_write(0, LOG_MAIN, "peer close after all rcpt responses;"
+	      " converting i/o-error to no-error");
+	    sx->ok = TRUE;
+	    goto happy;
+	    }
+	  }
 	break;
 
 #ifndef DISABLE_DKIM
@@ -4542,7 +4561,8 @@ if (!sx->ok)
 	break;
       }
 
-    /* Handle the cases that are treated as message errors. These are:
+    /* Handle the cases that are treated as message errors (as opposed to
+    host-errors). These are:
 
       (a) negative response or timeout after MAIL
       (b) negative response after DATA
@@ -4646,6 +4666,8 @@ connection to a new process. However, not all servers can handle this (Exim
 can), so we do not pass such a connection on if the host matches
 hosts_nopass_tls. */
 
+happy:
+
 DEBUG(D_transport)
   debug_printf("ok=%d send_quit=%d send_rset=%d continue_more=%d "
     "yield=%d first_address is %sNULL\n", sx->ok, sx->send_quit,
@@ -5063,16 +5085,16 @@ Returns:       the first address for this delivery
 */
 
 static address_item *
-prepare_addresses(address_item *addrlist, host_item *host)
+prepare_addresses(address_item * addrlist, host_item * host)
 {
-address_item *first_addr = NULL;
+address_item * first_addr = NULL;
 for (address_item * addr = addrlist; addr; addr = addr->next)
   if (addr->transport_return == DEFER)
     {
     if (!first_addr) first_addr = addr;
     addr->transport_return = PENDING_DEFER;
     addr->basic_errno = 0;
-    addr->more_errno = (host->mx >= 0)? 'M' : 'A';
+    addr->more_errno = host->mx >= 0 ? 'M' : 'A';
     addr->message = NULL;
 #ifndef DISABLE_TLS
     addr->cipher = NULL;
@@ -5104,24 +5126,17 @@ FALSE. */
 
 BOOL
 smtp_transport_entry(
-  transport_instance *tblock,      /* data for this instantiation */
-  address_item *addrlist)          /* addresses we are working on */
+  transport_instance * tblock,      /* data for this instantiation */
+  address_item * addrlist)          /* addresses we are working on */
 {
 int defport;
-int hosts_defer = 0;
-int hosts_fail  = 0;
-int hosts_looked_up = 0;
-int hosts_retry = 0;
-int hosts_serial = 0;
-int hosts_total = 0;
-int total_hosts_tried = 0;
+int hosts_defer = 0, hosts_fail  = 0, hosts_looked_up = 0;
+int hosts_retry = 0, hosts_serial = 0, hosts_total = 0, total_hosts_tried = 0;
 BOOL expired = TRUE;
-uschar *expanded_hosts = NULL;
-uschar *pistring;
-uschar *tid = string_sprintf("%s transport", tblock->name);
-smtp_transport_options_block *ob = SOB tblock->options_block;
-host_item *hostlist = addrlist->host_list;
-host_item *host = NULL;
+uschar * expanded_hosts = NULL, * pistring;
+uschar * tid = string_sprintf("%s transport", tblock->name);
+smtp_transport_options_block * ob = SOB tblock->options_block;
+host_item * hostlist = addrlist->host_list, * host = NULL;
 
 DEBUG(D_transport)
   {
@@ -5161,7 +5176,7 @@ database if the delivery fails temporarily or if we are running with
 queue_smtp or a 2-stage queue run. This gets unset for certain
 kinds of error, typically those that are specific to the message. */
 
-update_waiting =  TRUE;
+update_waiting = TRUE;
 
 /* If a host list is not defined for the addresses - they must all have the
 same one in order to be passed to a single transport - or if the transport has
@@ -5349,16 +5364,12 @@ retry_non_continued:
        && total_hosts_tried < ob->hosts_max_try_hardlimit;
        host = nexthost)
     {
-    int rc;
-    int host_af;
-    BOOL host_is_expired = FALSE;
-    BOOL message_defer = FALSE;
-    BOOL some_deferred = FALSE;
-    address_item *first_addr = NULL;
-    uschar *interface = NULL;
-    uschar *retry_host_key = NULL;
-    uschar *retry_message_key = NULL;
-    uschar *serialize_key = NULL;
+    int rc, host_af;
+    BOOL host_is_expired = FALSE, message_defer = FALSE, some_deferred = FALSE;
+    address_item * first_addr = NULL;
+    uschar * interface = NULL;
+    uschar * retry_host_key = NULL, * retry_message_key = NULL;
+    uschar * serialize_key = NULL;
 
     /* Deal slightly better with a possible Linux kernel bug that results
     in intermittent TFO-conn fails deep into the TCP flow.  Bug 2907 tracks.
@@ -5665,7 +5676,14 @@ retry_non_continued:
     out the result of previous attempts, and finding the first address that
     is still to be delivered. */
 
-    first_addr = prepare_addresses(addrlist, host);
+    if (!(first_addr = prepare_addresses(addrlist, host)))
+      {
+      /* Obscure situation; at least one case (bug 3059, fixed) where
+      a previous host try returned DEFER, but having moved all
+      recipients away from DEFER (the waiting-to-be-done state). */
+      DEBUG(D_transport) debug_printf("no pending recipients\n");
+      goto END_TRANSPORT;
+      }
 
     DEBUG(D_transport) debug_printf("delivering %s to %s [%s] (%s%s)\n",
       message_id, host->name, host->address, addrlist->address,
@@ -5717,7 +5735,7 @@ retry_non_continued:
       {
       host_item * thost;
       /* Make a copy of the host if it is local to this invocation
-       of the transport. */
+      of the transport. */
 
       if (expanded_hosts)
 	{
@@ -5911,20 +5929,14 @@ retry_non_continued:
     if (rc == OK)
       for (address_item * addr = addrlist; addr; addr = addr->next)
         if (addr->transport_return == DEFER)
-          {
-          some_deferred = TRUE;
-          break;
-          }
+          { some_deferred = TRUE; break; }
 
     /* If no addresses deferred or the result was ERROR, return. We do this for
     ERROR because a failing filter set-up or add_headers expansion is likely to
     fail for any host we try. */
 
     if (rc == ERROR || (rc == OK && !some_deferred))
-      {
-      DEBUG(D_transport) debug_printf("Leaving %s transport\n", tblock->name);
-      return TRUE;    /* Each address has its status */
-      }
+      goto END_TRANSPORT;
 
     /* If the result was DEFER or some individual addresses deferred, let
     the loop run to try other hosts with the deferred addresses, except for the
@@ -5944,7 +5956,7 @@ retry_non_continued:
     if ((rc == DEFER || some_deferred) && nexthost)
       {
       BOOL timedout;
-      retry_config *retry = retry_find_config(host->name, NULL, 0, 0);
+      retry_config * retry = retry_find_config(host->name, NULL, 0, 0);
 
       if (retry && retry->rules)
         {

commit cd8cb71deb2d653228cc037cf91ecab980acdcd0
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sat Feb 10 16:44:54 2024 +0000

    Move ESMPT LIMITS extension from experimental to mainline

diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults
index 28d4f13f7..c08f1874d 100644
--- a/src/src/config.h.defaults
+++ b/src/src/config.h.defaults
@@ -51,6 +51,7 @@ Do not put spaces between # and the 'define'.
 #define DISABLE_D_OPTION
 #define DISABLE_DNSSEC
 #define DISABLE_DKIM
+#define DISABLE_ESMTP_LIMITS
 #define DISABLE_EVENT
 #define DISABLE_OCSP
 #define DISABLE_PIPE_CONNECT
@@ -209,7 +210,6 @@ Do not put spaces between # and the 'define'.
 #define EXPERIMENTAL_BRIGHTMAIL
 #define EXPERIMENTAL_DCC
 #define EXPERIMENTAL_DSN_INFO
-#define EXPERIMENTAL_ESMTP_LIMITS
 #define EXPERIMENTAL_QUEUEFILE
 #define EXPERIMENTAL_XCLIENT
 
diff --git a/src/src/exim.c b/src/src/exim.c
index e5b72f11a..1f76f8f14 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -1105,6 +1105,9 @@ g = string_cat(g, US"Support for:");
 #ifndef DISABLE_DNSSEC
   g = string_cat(g, US" DNSSEC");
 #endif
+#ifndef DISABLE_ESMTP_LIMITS
+  g = string_cat(g, US" ESMTP_Limits");
+#endif
 #ifndef DISABLE_EVENT
   g = string_cat(g, US" Event");
 #endif
@@ -1151,9 +1154,6 @@ g = string_cat(g, US"Support for:");
 #ifdef EXPERIMENTAL_DSN_INFO
   g = string_cat(g, US" Experimental_DSN_info");
 #endif
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
-  g = string_cat(g, US" Experimental_ESMTP_Limits");
-#endif
 #ifdef EXPERIMENTAL_QUEUEFILE
   g = string_cat(g, US" Experimental_QUEUEFILE");
 #endif
@@ -3019,7 +3019,7 @@ on the second character (the one after '-'), to save some effort. */
 
 	case 'K': smtp_peer_options |= OPTION_CHUNKING; break;
 
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
     /* -MCL: peer used LIMITS RCPTMAX and/or RCPTDOMAINMAX */
 	case 'L': if (++i < argc) continue_limit_mail = Uatoi(argv[i]);
 		  else badarg = TRUE;
diff --git a/src/src/functions.h b/src/src/functions.h
index ef84e3d7c..77323a5c1 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -632,7 +632,7 @@ extern void    transport_do_pass_socket(const uschar *, const uschar *,
 		 const uschar *, uschar *, int);
 extern void    transport_init(void);
 extern BOOL    transport_pass_socket(const uschar *, const uschar *, const uschar *, uschar *, int
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
 			, unsigned, unsigned, unsigned
 #endif
 			);
diff --git a/src/src/globals.c b/src/src/globals.c
index c535ea121..c483ab182 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -706,7 +706,7 @@ unsigned chunking_data_left    = 0;
 chunking_state_t chunking_state= CHUNKING_NOT_OFFERED;
 const pcre2_code *regex_CHUNKING     = NULL;
 
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
 const pcre2_code *regex_LIMITS        = NULL;
 #endif
 
@@ -746,7 +746,7 @@ uschar *continue_hostname      = NULL;
 uschar *continue_host_address  = NULL;
 int     continue_sequence      = 1;
 uschar *continue_transport     = NULL;
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
 unsigned continue_limit_mail   = 0;
 unsigned continue_limit_rcpt   = 0;
 unsigned continue_limit_rcptdom= 0;
@@ -1029,7 +1029,7 @@ const uschar *letter_digit_hyphen_dot =
     US"abcdefghijklmnopqrstuvwxyz"
       ".-0123456789"
       "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
 uschar *limits_advertise_hosts = US"*";
 #endif
 int     load_average           = -2;
diff --git a/src/src/globals.h b/src/src/globals.h
index ffc7150f2..b5c3a520e 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -449,7 +449,7 @@ 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 */
 extern uschar *continue_transport;     /* Transport for continued delivery */
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
 extern unsigned continue_limit_mail;   /* Peer advertised limit */
 extern unsigned continue_limit_rcpt;
 extern unsigned continue_limit_rcptdom;
@@ -688,7 +688,7 @@ extern int     keep_malformed;         /* Time to keep malformed messages */
 
 extern uschar *eldap_dn;               /* Where LDAP DNs are left */
 extern const uschar *letter_digit_hyphen_dot; /* Legitimate DNS host name chars */
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
 extern uschar *limits_advertise_hosts; /* for banner/EHLO pipelining */
 #endif
 extern int     load_average;           /* Most recently read load average */
@@ -902,7 +902,7 @@ extern const pcre2_code  *regex_check_dns_names; /* For DNS name checking */
 extern const pcre2_code  *regex_From;        /* For recognizing "From_" lines */
 extern const pcre2_code  *regex_CHUNKING;    /* For recognizing CHUNKING (RFC 3030) */
 extern const pcre2_code  *regex_IGNOREQUOTA; /* For recognizing IGNOREQUOTA (LMTP) */
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
 extern const pcre2_code  *regex_LIMITS; /* For recognizing LIMITS */
 #endif
 extern const pcre2_code  *regex_PIPELINING;  /* For recognizing PIPELINING */
diff --git a/src/src/hintsdb_structs.h b/src/src/hintsdb_structs.h
index 0e5853b2b..5da311c2b 100644
--- a/src/src/hintsdb_structs.h
+++ b/src/src/hintsdb_structs.h
@@ -163,7 +163,7 @@ typedef struct {
   unsigned short cleartext_auths;
   unsigned short crypted_auths;
 
-# ifdef EXPERIMENTAL_ESMTP_LIMITS
+# ifndef DISABLE_ESMTP_LIMITS
   unsigned int limit_mail;
   unsigned int limit_rcpt;
   unsigned int limit_rcptdom;
diff --git a/src/src/macro_predef.c b/src/src/macro_predef.c
index bdc587502..9b354d345 100644
--- a/src/src/macro_predef.c
+++ b/src/src/macro_predef.c
@@ -160,6 +160,9 @@ due to conflicts with other common macros. */
 #ifndef DISABLE_DNSSEC
   builtin_macro_create(US"_HAVE_DNSSEC");
 #endif
+#ifndef DISABLE_ESMTP_LIMITS
+  builtin_macro_create(US"_HAVE_ESMTP_LIMITS");
+#endif
 #ifndef DISABLE_EVENT
   builtin_macro_create(US"_HAVE_EVENT");
 #endif
@@ -309,9 +312,6 @@ exp_features(void)
 #ifdef EXPERIMENTAL_DSN_INFO
   builtin_macro_create(US"_EXP_DSNI");
 #endif
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
-  builtin_macro_create(US"_EXP_LIMITS");
-#endif
 #ifdef EXPERIMENTAL_QUEUEFILE
   builtin_macro_create(US"_EXP_QUEUEFILE");
 #endif
diff --git a/src/src/readconf.c b/src/src/readconf.c
index bbcca87f7..8ee6e9fe0 100644
--- a/src/src/readconf.c
+++ b/src/src/readconf.c
@@ -209,7 +209,7 @@ static optionlist optionlist_config[] = {
   { "ldap_start_tls",           opt_bool,        {&eldap_start_tls} },
   { "ldap_version",             opt_int,         {&eldap_version} },
 #endif
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
   { "limits_advertise_hosts", opt_stringptr, {&limits_advertise_hosts} },
 #endif
   { "local_from_check",         opt_bool,        {&local_from_check} },
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index 541288301..a57c9821e 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -4054,7 +4054,7 @@ while (done <= 0)
 	  g = string_catn(g, US"-SIZE\r\n", 7);
 	  }
 
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
 	if (  (smtp_mailcmd_max > 0 || recipients_max)
 	   && verify_check_host(&limits_advertise_hosts) == OK)
 	  {
diff --git a/src/src/transport.c b/src/src/transport.c
index 79a69f941..5729f8e75 100644
--- a/src/src/transport.c
+++ b/src/src/transport.c
@@ -1941,7 +1941,7 @@ const uschar **argv;
 #ifndef DISABLE_TLS
 if (smtp_peer_options & OPTION_TLS) i += 6;
 #endif
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
 if (continue_limit_mail || continue_limit_rcpt || continue_limit_rcptdom)
 				    i += 4;
 #endif
@@ -1983,7 +1983,7 @@ if (smtp_peer_options & OPTION_TLS)
     argv[i++] = US"-MCT";
 #endif
 
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
 if (continue_limit_rcpt || continue_limit_rcptdom)
   {
   argv[i++] = US"-MCL";
@@ -2055,7 +2055,7 @@ Returns:          FALSE if fork fails; TRUE otherwise
 BOOL
 transport_pass_socket(const uschar *transport_name, const uschar *hostname,
   const uschar *hostaddress, uschar *id, int socket_fd
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
   , unsigned peer_limit_mail, unsigned peer_limit_rcpt, unsigned peer_limit_rcptdom
 #endif
   )
@@ -2065,7 +2065,7 @@ int status;
 
 DEBUG(D_transport) debug_printf("transport_pass_socket entered\n");
 
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
 continue_limit_mail = peer_limit_mail;
 continue_limit_rcpt = peer_limit_rcpt;
 continue_limit_rcptdom = peer_limit_rcptdom;
diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index c205145a7..596a328c3 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -277,7 +277,7 @@ struct list
 #ifndef DISABLE_PIPE_CONNECT
     { &regex_EARLY_PIPE,  	US"\\n250[\\s\\-]" EARLY_PIPE_FEATURE_NAME "(\\s|\\n|$)" },
 #endif
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
     { &regex_LIMITS,		US"\\n250[\\s\\-]LIMITS\\s" },
 #endif
   };
@@ -797,7 +797,7 @@ sx->conn_args.have_lbserver = TRUE;
 
 /******************************************************************************/
 
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
 /* If TLS, or TLS not offered, called with the EHLO response in the buffer.
 Check it for a LIMITS keyword and parse values into the smtp context structure.
 
@@ -900,7 +900,7 @@ write_ehlo_cache_entry(smtp_context * sx)
 {
 open_db dbblock, * dbm_file;
 
-# ifdef EXPERIMENTAL_ESMTP_LIMITS
+# ifndef DISABLE_ESMTP_LIMITS
 sx->ehlo_resp.limit_mail = sx->peer_limit_mail;
 sx->ehlo_resp.limit_rcpt = sx->peer_limit_rcpt;
 sx->ehlo_resp.limit_rcptdom = sx->peer_limit_rcptdom;
@@ -912,7 +912,7 @@ if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE)))
   dbdata_ehlo_resp er = { .data = sx->ehlo_resp };
 
   HDEBUG(D_transport)
-# ifdef EXPERIMENTAL_ESMTP_LIMITS
+# ifndef DISABLE_ESMTP_LIMITS
     if (sx->ehlo_resp.limit_mail || sx->ehlo_resp.limit_rcpt || sx->ehlo_resp.limit_rcptdom)
       debug_printf("writing clr %04x/%04x cry %04x/%04x lim %05d/%05d/%05d\n",
 	sx->ehlo_resp.cleartext_features, sx->ehlo_resp.cleartext_auths,
@@ -979,7 +979,7 @@ else
   else
     {
     DEBUG(D_transport)
-# ifdef EXPERIMENTAL_ESMTP_LIMITS
+# ifndef DISABLE_ESMTP_LIMITS
       if (er->data.limit_mail || er->data.limit_rcpt || er->data.limit_rcptdom)
 	debug_printf("EHLO response bits from cache:"
 	  " cleartext 0x%04x/0x%04x crypted 0x%04x/0x%04x lim %05d/%05d/%05d\n",
@@ -994,7 +994,7 @@ else
 	  er->data.crypted_features, er->data.crypted_auths);
 
     sx->ehlo_resp = er->data;
-# ifdef EXPERIMENTAL_ESMTP_LIMITS
+# ifndef DISABLE_ESMTP_LIMITS
     ehlo_cache_limits_apply(sx);
 # endif
     dbfn_close(dbm_file);
@@ -1120,7 +1120,7 @@ if (pending_EHLO)
 	| OPTION_CHUNKING | OPTION_PRDR | OPTION_DSN | OPTION_PIPE | OPTION_SIZE
 	| OPTION_UTF8 | OPTION_EARLY_PIPE
 	);
-# ifdef EXPERIMENTAL_ESMTP_LIMITS
+# ifndef DISABLE_ESMTP_LIMITS
   if (tls_out.active.sock >= 0 || !(peer_offered & OPTION_TLS))
     ehlo_response_limits_read(sx);
 # endif
@@ -1147,7 +1147,7 @@ if (pending_EHLO)
 
     return OK;		/* just carry on */
     }
-# ifdef EXPERIMENTAL_ESMTP_LIMITS
+# ifndef DISABLE_ESMTP_LIMITS
     /* If we are handling LIMITS, compare the actual EHLO LIMITS values with the
     cached values and invalidate cache if different.  OK to carry on with
     connect since values are advisory. */
@@ -2305,7 +2305,7 @@ if (!continue_hostname)
 
   sx->cctx.tls_ctx = NULL;
   sx->inblock.cctx = sx->outblock.cctx = &sx->cctx;
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
   sx->peer_limit_mail = sx->peer_limit_rcpt = sx->peer_limit_rcptdom =
 #endif
   sx->avoid_option = sx->peer_offered = smtp_peer_options = 0;
@@ -2607,7 +2607,7 @@ goto SEND_QUIT;
 	  )
 #endif
 	);
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
       if (tls_out.active.sock >= 0 || !(sx->peer_offered & OPTION_TLS))
 	{
 	ehlo_response_limits_read(sx);
@@ -2672,7 +2672,7 @@ else
   sx->inblock.cctx = sx->outblock.cctx = &sx->cctx;
   smtp_command = big_buffer;
   sx->peer_offered = smtp_peer_options;
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
   /* Limits passed by cmdline over exec. */
   ehlo_limits_apply(sx,
 		    sx->peer_limit_mail = continue_limit_mail,
@@ -3000,7 +3000,7 @@ if (   !continue_hostname
       sx->ehlo_resp.crypted_features = sx->peer_offered;
 #endif
 
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
     if (tls_out.active.sock >= 0 || !(sx->peer_offered & OPTION_TLS))
       {
       ehlo_response_limits_read(sx);
@@ -3423,7 +3423,7 @@ sw_mrc_t
 smtp_write_mail_and_rcpt_cmds(smtp_context * sx, int * yield)
 {
 address_item * addr;
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
 address_item * restart_addr = NULL;
 #endif
 int address_count, pipe_limit;
@@ -3517,7 +3517,7 @@ for (addr = sx->first_addr, address_count = 0, pipe_limit = 100;
   BOOL no_flush;
   const uschar * rcpt_addr;
 
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
   if (  sx->single_rcpt_domain					/* restriction on domains */
      && address_count > 0					/* not first being sent */
      && Ustrcmp(addr->domain, sx->first_addr->domain) != 0	/* dom diff from first */
@@ -3606,7 +3606,7 @@ for (addr = sx->first_addr, address_count = 0, pipe_limit = 100;
     }
   }      /* Loop for next address */
 
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
 sx->next_addr = restart_addr ? restart_addr : addr;
 #else
 sx->next_addr = addr;
@@ -3790,7 +3790,7 @@ uschar *message = NULL;
 uschar new_message_id[MESSAGE_ID_LENGTH + 1];
 smtp_context * sx = store_get(sizeof(*sx), GET_TAINTED);	/* tainted, for the data buffers */
 BOOL pass_message = FALSE;
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
 BOOL mail_limit = FALSE;
 #endif
 #ifdef SUPPORT_DANE
@@ -4674,7 +4674,7 @@ DEBUG(D_transport)
     sx->send_rset, f.continue_more, yield, sx->first_addr ? "not " : "");
 
 if (sx->completed_addr && sx->ok && sx->send_quit)
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
   if (mail_limit = continue_sequence >= sx->max_mail)
     {
     DEBUG(D_transport)
@@ -4735,7 +4735,7 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
 
 	if (sx->first_addr)		/* More addresses still to be sent */
 	  {				/*   for this message              */
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
 	  /* Any that we marked as skipped, reset to do now */
 	  for (address_item * a = sx->first_addr; a; a = a->next)
 	    if (a->transport_return == SKIP)
@@ -4810,7 +4810,7 @@ if (sx->completed_addr && sx->ok && sx->send_quit)
   */
 	if (sx->ok && transport_pass_socket(tblock->name, host->name,
 	      host->address, new_message_id, socket_fd
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
 	      , sx->peer_limit_mail, sx->peer_limit_rcpt, sx->peer_limit_rcptdom
 #endif
 	      ))
@@ -4987,7 +4987,7 @@ if (dane_held)
   }
 #endif
 
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
 if (mail_limit && sx->first_addr)
   {
   /* Reset the sequence count since we closed the connection.  This is flagged
diff --git a/src/src/transports/smtp.h b/src/src/transports/smtp.h
index ef9135838..fc8a6b126 100644
--- a/src/src/transports/smtp.h
+++ b/src/src/transports/smtp.h
@@ -176,7 +176,7 @@ typedef struct {
   BOOL pending_BDAT:1;
   BOOL RCPT_452:1;
   BOOL good_RCPT:1;
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
   BOOL single_rcpt_domain:1;
 #endif
   BOOL completed_addr:1;
@@ -185,7 +185,7 @@ typedef struct {
   BOOL send_tlsclose:1;
 
   unsigned	peer_offered;
-#ifdef EXPERIMENTAL_ESMTP_LIMITS
+#ifndef DISABLE_ESMTP_LIMITS
   unsigned	peer_limit_mail;
   unsigned	peer_limit_rcpt;
   unsigned	peer_limit_rcptdom;

commit 84add256b3467cf6e0d4a2edf72cf5bf16cd9306
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sun Feb 11 13:57:18 2024 +0000

    Use non-releaseable memory for regex match strings. Bug 3047
    
    Broken-by: 35aacb69f5c8

diff --git a/src/src/regex.c b/src/src/regex.c
index cda807716..bb34e5b23 100644
--- a/src/src/regex.c
+++ b/src/src/regex.c
@@ -63,6 +63,13 @@ if (cntp) *cntp = cnt;
 return re_list_head;
 }
 
+
+/* Check list of REs against buffer, returning OK for (first) match,
+else FAIL.  On match return allocated result strings in regex_vars[]. 
+
+We use the perm-pool for that, so that our caller can release
+other allocations.
+*/
 static int
 matcher(pcre_list * re_list_head, uschar * linebuffer, int len)
 {
@@ -75,6 +82,9 @@ for (pcre_list * ri = re_list_head; ri; ri = ri->next)
   /* try matcher on the line */
   if ((n = pcre2_match(ri->re, (PCRE2_SPTR)linebuffer, len, 0, 0, md, pcre_gen_mtc_ctx)) > 0)
     {
+    int save_pool = store_pool;
+    store_pool = POOL_PERM;
+
     Ustrncpy(regex_match_string_buffer, ri->pcre_text,
 	      sizeof(regex_match_string_buffer)-1);
     regex_match_string = regex_match_string_buffer;
@@ -87,6 +97,7 @@ for (pcre_list * ri = re_list_head; ri; ri = ri->next)
       regex_vars[nn-1] = string_copyn(linebuffer + ovec[off], len);
       }
 
+    store_pool = save_pool;
     return OK;
     }
   }

commit 6fcb3173d64ef8a9d70f8adf19f134a0cd9cf6e8
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sun Feb 11 15:04:58 2024 +0000

    use dynamic mem for regex_match_string

diff --git a/src/src/regex.c b/src/src/regex.c
index bb34e5b23..d5dc10258 100644
--- a/src/src/regex.c
+++ b/src/src/regex.c
@@ -24,8 +24,6 @@ typedef struct pcre_list {
   struct pcre_list *	next;
 } pcre_list;
 
-uschar regex_match_string_buffer[1024];
-
 extern FILE *mime_stream;
 extern uschar *mime_current_boundary;
 
@@ -85,9 +83,7 @@ for (pcre_list * ri = re_list_head; ri; ri = ri->next)
     int save_pool = store_pool;
     store_pool = POOL_PERM;
 
-    Ustrncpy(regex_match_string_buffer, ri->pcre_text,
-	      sizeof(regex_match_string_buffer)-1);
-    regex_match_string = regex_match_string_buffer;
+    regex_match_string = string_copy(ri->pcre_text);
 
     for (int nn = 1; nn < n; nn++)
       {

commit a173a4376d168edbf3fe2494dff998c4060bf425
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Tue Feb 13 17:34:19 2024 +0000

    Use non-releasable memory for regex line-buffer
    
    Broken-by: 5aacb69f5c8

diff --git a/src/src/regex.c b/src/src/regex.c
index d5dc10258..642b9cf93 100644
--- a/src/src/regex.c
+++ b/src/src/regex.c
@@ -118,7 +118,6 @@ regex(const uschar ** listptr, BOOL cacheable)
 unsigned long mbox_size;
 FILE * mbox_file;
 pcre_list * re_list_head;
-uschar * linebuffer;
 long f_pos = 0;
 int ret = FAIL, cnt, lcount = REGEX_LOOPCOUNT_STORE_RESET;
 rmark reset_point;
@@ -151,16 +150,15 @@ reset_point = store_mark();
   if ((re_list_head = compile(*listptr, cacheable, &cnt)))
     {
     /* match each line against all regexes */
-    linebuffer = store_get(32767, GET_TAINTED);
-    while (fgets(CS linebuffer, 32767, mbox_file))
+    while (fgets(CS big_buffer, big_buffer_size, mbox_file))
       {
       if (  mime_stream && mime_current_boundary		/* check boundary */
-	 && Ustrncmp(linebuffer, "--", 2) == 0
-	 && Ustrncmp((linebuffer+2), mime_current_boundary,
+	 && Ustrncmp(big_buffer, "--", 2) == 0
+	 && Ustrncmp((big_buffer+2), mime_current_boundary,
 		      Ustrlen(mime_current_boundary)) == 0)
 	break;						/* found boundary */
 
-      if ((ret = matcher(re_list_head, linebuffer, (int)Ustrlen(linebuffer))) == OK)
+      if ((ret = matcher(re_list_head, big_buffer, (int)Ustrlen(big_buffer))) == OK)
 	break;
 
       if ((lcount -= cnt) <= 0)

commit cd19f9a79c20f9c0c9d650a8aa21d9cc54a66620
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sun Feb 11 23:32:34 2024 +0000

    Expand recipients_max

diff --git a/src/src/exim.c b/src/src/exim.c
index 1f76f8f14..10fc98963 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -5339,6 +5339,7 @@ if (expansion_test)
 
   else if (expansion_test_message)
     {
+    uschar * rme = expand_string(recipients_max);
     int save_stdin = dup(0);
     int fd = Uopen(expansion_test_message, O_RDONLY, 0);
     if (fd < 0)
@@ -5347,6 +5348,7 @@ if (expansion_test)
     (void) dup2(fd, 0);
     filter_test = FTEST_USER;      /* Fudge to make it look like filter test */
     message_ended = END_NOTENDED;
+    recipients_max_expanded = atoi(CCS rme);
     read_message_body(receive_msg(extract_recipients));
     message_linecount += body_linecount;
     (void)dup2(save_stdin, 0);
@@ -5753,8 +5755,8 @@ for (BOOL more = TRUE; more; )
     int rc;
     if ((rc = smtp_setup_msg()) > 0)
       {
-      if (real_sender_address != NULL &&
-          !receive_check_set_sender(sender_address))
+      if (   real_sender_address
+	  && !receive_check_set_sender(sender_address))
         {
         sender_address = raw_sender = real_sender_address;
         sender_address_unrewritten = NULL;
@@ -5801,10 +5803,12 @@ for (BOOL more = TRUE; more; )
 
   else
     {
-    int rcount = 0;
-    int count = argc - recipients_arg;
+    uschar * rme = expand_string(recipients_max);
+    int rcount = 0, count = argc - recipients_arg;
     const uschar ** list = argv + recipients_arg;
 
+    recipients_max_expanded = atoi(CCS rme);
+
     /* These options cannot be changed dynamically for non-SMTP messages */
 
     f.active_local_sender_retain = local_sender_retain;
@@ -5831,15 +5835,19 @@ for (BOOL more = TRUE; more; )
       while (*s)
         {
         BOOL finished = FALSE;
-        uschar *recipient;
-        uschar *ss = parse_find_address_end(s, FALSE);
+        uschar * recipient;
+        uschar * ss = parse_find_address_end(s, FALSE);
 
         if (*ss == ',') *ss = 0; else finished = TRUE;
 
         /* Check max recipients - if -t was used, these aren't recipients */
 
-        if (recipients_max > 0 && ++rcount > recipients_max &&
-            !extract_recipients)
+        if (  recipients_max_expanded > 0 && ++rcount > recipients_max_expanded
+	   && !extract_recipients)
+	  {
+	  DEBUG(D_all) debug_printf("excess reipients (max %d)\n",
+	    recipients_max_expanded);
+
           if (error_handling == ERRORS_STDERR)
             {
             fprintf(stderr, "exim: too many recipients\n");
@@ -5849,6 +5857,7 @@ for (BOOL more = TRUE; more; )
             return
               moan_to_sender(ERRMESS_TOOMANYRECIP, NULL, NULL, stdin, TRUE)?
                 errors_sender_rc : EXIT_FAILURE;
+	  }
 
 #ifdef SUPPORT_I18N
 	{
@@ -5873,6 +5882,10 @@ for (BOOL more = TRUE; more; )
           }
 
         if (!recipient)
+	  {
+	  DEBUG(D_all) debug_printf("bad recipient address \"%s\": %s\n",
+	    string_printing(list[i]), errmess);
+
           if (error_handling == ERRORS_STDERR)
             {
             fprintf(stderr, "exim: bad recipient address \"%s\": %s\n",
@@ -5889,6 +5902,7 @@ for (BOOL more = TRUE; more; )
               moan_to_sender(ERRMESS_BADARGADDRESS, &eblock, NULL, stdin, TRUE)?
                 errors_sender_rc : EXIT_FAILURE;
             }
+	  }
 
         receive_add_recipient(string_copy_taint(recipient, GET_TAINTED), -1);
         s = ss;
diff --git a/src/src/globals.c b/src/src/globals.c
index c483ab182..4af8668f0 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -1322,7 +1322,8 @@ uschar *recipient_verify_failure = NULL;
 int     recipients_count       = 0;
 recipient_item  *recipients_list = NULL;
 int     recipients_list_max    = 0;
-int     recipients_max         = 50000;
+uschar *recipients_max         = US"50000";
+int     recipients_max_expanded= 0;
 const pcre2_code *regex_AUTH         = NULL;
 const pcre2_code *regex_check_dns_names = NULL;
 const pcre2_code *regex_From         = NULL;
diff --git a/src/src/globals.h b/src/src/globals.h
index b5c3a520e..427590050 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -895,7 +895,8 @@ extern uschar *recipient_data;         /* lookup data for recipients */
 extern uschar *recipient_unqualified_hosts; /* Permitted unqualified recipients */
 extern uschar *recipient_verify_failure; /* What went wrong */
 extern int     recipients_list_max;    /* Maximum number fitting in list */
-extern int     recipients_max;         /* Max permitted */
+extern uschar *recipients_max;         /* Max permitted */
+extern int     recipients_max_expanded;
 extern BOOL    recipients_max_reject;  /* If TRUE, reject whole message */
 extern const pcre2_code *regex_AUTH;         /* For recognizing AUTH settings */
 extern const pcre2_code  *regex_check_dns_names; /* For DNS name checking */
diff --git a/src/src/moan.c b/src/src/moan.c
index c2e38d4f7..a3c8e0aba 100644
--- a/src/src/moan.c
+++ b/src/src/moan.c
@@ -561,7 +561,7 @@ switch(ident)
 
   case ERRMESS_TOOMANYRECIP:
   log_write(0, LOG_MAIN, "%s: too many recipients (max set to %d)", msg,
-    recipients_max);
+    recipients_max_expanded);
   break;
 
   case ERRMESS_LOCAL_SCAN:
diff --git a/src/src/readconf.c b/src/src/readconf.c
index 8ee6e9fe0..5b486d0b6 100644
--- a/src/src/readconf.c
+++ b/src/src/readconf.c
@@ -293,7 +293,7 @@ static optionlist optionlist_config[] = {
   { "received_header_text",     opt_stringptr,   {&received_header_text} },
   { "received_headers_max",     opt_int,         {&received_headers_max} },
   { "recipient_unqualified_hosts", opt_stringptr, {&recipient_unqualified_hosts} },
-  { "recipients_max",           opt_int,         {&recipients_max} },
+  { "recipients_max",           opt_stringptr,   {&recipients_max} },
   { "recipients_max_reject",    opt_bool,        {&recipients_max_reject} },
 #ifdef LOOKUP_REDIS
   { "redis_servers",            opt_stringptr,   {&redis_servers} },
diff --git a/src/src/receive.c b/src/src/receive.c
index ae4203e7f..990b9c22e 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -1204,6 +1204,8 @@ static void
 give_local_error(int errcode, uschar *text1, uschar *text2, int error_rc,
   FILE *f, header_line *hptr)
 {
+DEBUG(D_all) debug_printf("%s%s\n", text2, text1);
+
 if (error_handling == ERRORS_SENDER)
   {
   error_block eblock;
@@ -2622,12 +2624,12 @@ if (extract_recip)
     if ((h->type == htype_to || h->type == htype_cc || h->type == htype_bcc) &&
         (!contains_resent_headers || strncmpic(h->text, US"resent-", 7) == 0))
       {
-      uschar *s = Ustrchr(h->text, ':') + 1;
+      uschar * s = Ustrchr(h->text, ':') + 1;
       while (isspace(*s)) s++;
 
       f.parse_allow_group = TRUE;          /* Allow address group syntax */
 
-      while (*s != 0)
+      while (*s)
         {
         uschar *ss = parse_find_address_end(s, FALSE);
         uschar *recipient, *errmess, *pp;
@@ -2635,7 +2637,7 @@ if (extract_recip)
 
         /* Check on maximum */
 
-        if (recipients_max > 0 && ++rcount > recipients_max)
+        if (recipients_max_expanded > 0 && ++rcount > recipients_max_expanded)
           give_local_error(ERRMESS_TOOMANYRECIP, US"too many recipients",
             US"message rejected: ", error_rc, stdin, NULL);
           /* Does not return */
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index a57c9821e..1d4c232a5 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -1879,7 +1879,7 @@ while (done <= 0)
       if (recipients_max > 0 && recipients_count + 1 > recipients_max)
 	/* The function moan_smtp_batch() does not return. */
 	moan_smtp_batch(smtp_cmd_buffer, "%s too many recipients",
-	  recipients_max_reject? "552": "452");
+	  recipients_max_reject ? "552": "452");
 
       /* Apply SMTP rewrite, then extract address. Don't allow "<>" as a
       recipient address */
@@ -2540,6 +2540,11 @@ if (!f.sender_host_unknown)
   fl.helo_accept_junk = verify_check_host(&helo_accept_junk_hosts) == OK;
   }
 
+/* Expand recipients_max, if needed */
+ {
+  uschar * rme = expand_string(recipients_max);
+  recipients_max_expanded = atoi(CCS rme);
+ }
 /* For batch SMTP input we are now done. */
 
 if (smtp_batched_input) return TRUE;
@@ -4055,14 +4060,14 @@ while (done <= 0)
 	  }
 
 #ifndef DISABLE_ESMTP_LIMITS
-	if (  (smtp_mailcmd_max > 0 || recipients_max)
+	if (  (smtp_mailcmd_max > 0 || recipients_max_expanded > 0)
 	   && verify_check_host(&limits_advertise_hosts) == OK)
 	  {
 	  g = string_fmt_append(g, "%.3s-LIMITS", smtp_code);
 	  if (smtp_mailcmd_max > 0)
 	    g = string_fmt_append(g, " MAILMAX=%d", smtp_mailcmd_max);
-	  if (recipients_max)
-	    g = string_fmt_append(g, " RCPTMAX=%d", recipients_max);
+	  if (recipients_max > 9)
+	    g = string_fmt_append(g, " RCPTMAX=%d", recipients_max_expanded);
 	  g = string_catn(g, US"\r\n", 2);
 	  }
 #endif
@@ -4900,7 +4905,8 @@ while (done <= 0)
 
       /* Check maximum allowed */
 
-      if (rcpt_count+1 < 0 || rcpt_count > recipients_max && recipients_max > 0)
+      if (  rcpt_count+1 < 0
+         || rcpt_count > recipients_max_expanded && recipients_max_expanded > 0)
 	{
 	if (recipients_max_reject)
 	  {

commit 7502f0cbd3753de473a2279acaae15f9bbdfb5ee
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Wed Feb 14 11:25:52 2024 +0000

    Expand recipients_max, smtp-batch input

diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index 1d4c232a5..2d977b975 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -1876,7 +1876,8 @@ while (done <= 0)
 
       /* Check maximum number allowed */
 
-      if (recipients_max > 0 && recipients_count + 1 > recipients_max)
+      if (  recipients_max_expanded > 0
+	 && recipients_count + 1 > recipients_max_expanded)
 	/* The function moan_smtp_batch() does not return. */
 	moan_smtp_batch(smtp_cmd_buffer, "%s too many recipients",
 	  recipients_max_reject ? "552": "452");
@@ -4066,7 +4067,7 @@ while (done <= 0)
 	  g = string_fmt_append(g, "%.3s-LIMITS", smtp_code);
 	  if (smtp_mailcmd_max > 0)
 	    g = string_fmt_append(g, " MAILMAX=%d", smtp_mailcmd_max);
-	  if (recipients_max > 9)
+	  if (recipients_max_expanded > 0)
 	    g = string_fmt_append(g, " RCPTMAX=%d", recipients_max_expanded);
 	  g = string_catn(g, US"\r\n", 2);
 	  }

commit 44b3172e369435c2c1baa4e9c837252f729d2905
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Thu Feb 15 19:56:40 2024 +0000

    regex: avoid releasing built RE midloop

diff --git a/src/src/regex.c b/src/src/regex.c
index 642b9cf93..285ab8dbd 100644
--- a/src/src/regex.c
+++ b/src/src/regex.c
@@ -120,7 +120,6 @@ FILE * mbox_file;
 pcre_list * re_list_head;
 long f_pos = 0;
 int ret = FAIL, cnt, lcount = REGEX_LOOPCOUNT_STORE_RESET;
-rmark reset_point;
 
 regex_vars_clear();
 
@@ -144,11 +143,11 @@ else
   mbox_file = mime_stream;
   }
 
-reset_point = store_mark();
-  {
   /* precompile our regexes */
   if ((re_list_head = compile(*listptr, cacheable, &cnt)))
     {
+    rmark reset_point = store_mark();
+
     /* match each line against all regexes */
     while (fgets(CS big_buffer, big_buffer_size, mbox_file))
       {
@@ -167,9 +166,9 @@ reset_point = store_mark();
 	lcount = REGEX_LOOPCOUNT_STORE_RESET;
 	}
       }
+
+    store_reset(reset_point);
     }
-  }
-store_reset(reset_point);
 
 if (!mime_stream)
   (void)fclose(mbox_file);

commit 90bd3832bc0ff090ac5e37dfc66b30cabb9cfc1a
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Feb 16 23:16:29 2024 +0000

    Debug: option access for expansion

diff --git a/src/src/daemon.c b/src/src/daemon.c
index 704ede24e..3c63c6368 100644
--- a/src/src/daemon.c
+++ b/src/src/daemon.c
@@ -298,9 +298,10 @@ to provide host-specific limits according to $sender_host address, but because
 this is in the daemon mainline, only fast expansions (such as inline address
 checks) should be used. The documentation is full of warnings. */
 
+GET_OPTION("smtp_accept_max_per_host");
 if (smtp_accept_max_per_host)
   {
-  uschar *expanded = expand_string(smtp_accept_max_per_host);
+  uschar * expanded = expand_string(smtp_accept_max_per_host);
   if (!expanded)
     {
     if (!f.expand_string_forcedfail)
@@ -434,6 +435,7 @@ if (pid == 0)
   likely what it depends on.) */
 
   smtp_active_hostname = primary_hostname;
+  GET_OPTION("smtp_active_hostname");
   if (raw_active_hostname)
     {
     uschar * nah = expand_string(raw_active_hostname);
@@ -1165,6 +1167,7 @@ return offsetof(struct sockaddr_un, sun_path)
 ssize_t
 daemon_notifier_sockname(struct sockaddr_un * sup)
 {
+GET_OPTION("notifier_socket");
 #ifdef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS
 sup->sun_path[0] = 0;  /* Abstract local socket addr - Linux-specific? */
 return offsetof(struct sockaddr_un, sun_path) + 1
@@ -1709,12 +1712,13 @@ time_t last_connection_time = (time_t)0;
 int local_queue_run_max = 0;
 
 if (is_multiple_qrun())
-
+  {
   /* Nuber of runner-tracking structs needed:  If the option queue_run_max has
   no expandable elements then it is the overall maximum; else we assume it
   depends on the queue name, and add them up to get the maximum.
   Evaluate both that and the individual limits. */
 
+  GET_OPTION("queue_run_max");
   if (Ustrchr(queue_run_max, '$') != NULL)
     {
     for (qrunner * q = qrunners; q; q = q->next)
@@ -1731,6 +1735,7 @@ if (is_multiple_qrun())
     for (qrunner * q = qrunners; q; q = q->next)
       q->run_max = local_queue_run_max;
     }
+  }
 
 process_purpose = US"daemon";
 
diff --git a/src/src/deliver.c b/src/src/deliver.c
index 2cef3d731..70f44535f 100644
--- a/src/src/deliver.c
+++ b/src/src/deliver.c
@@ -1858,8 +1858,9 @@ if (tp->gid_set)
   }
 else if (tp->expand_gid)
   {
+  GET_OPTION("group");
   if (!route_find_expanded_group(tp->expand_gid, tp->name, US"transport", gidp,
-    &(addr->message)))
+    &addr->message))
     {
     common_error(FALSE, addr, ERRNO_GIDFAIL, NULL);
     return FALSE;
@@ -1885,6 +1886,7 @@ it does not provide a passwd value from which a gid can be taken. */
 else if (tp->expand_uid)
   {
   struct passwd *pw;
+  GET_OPTION("user");
   if (!route_find_expanded_user(tp->expand_uid, tp->name, US"transport", &pw,
        uidp, &(addr->message)))
     {
@@ -1988,6 +1990,7 @@ check_message_size(transport_instance *tp, address_item *addr)
 int rc = OK;
 int size_limit;
 
+GET_OPTION("message_size_limit");
 deliver_set_expansions(addr);
 size_limit = expand_string_integer(tp->message_size_limit, TRUE);
 deliver_set_expansions(NULL);
@@ -2150,6 +2153,7 @@ if(addr->prop.errors_address)
 else
   return_path = sender_address;
 
+GET_OPTION("return_path");
 if (tp->return_path)
   {
   uschar * new_return_path = expand_string(tp->return_path);
@@ -2179,6 +2183,7 @@ if (!findugid(addr, tp, &uid, &gid, &use_initgroups)) return;
 home directory set in the address may already be expanded; a flag is set to
 indicate that. In other cases we must expand it. */
 
+GET_OPTION("home_directory");
 if (  (deliver_home = tp->home_dir)		/* Set in transport, or */
    || (  (deliver_home = addr->home_dir)	/* Set in address and */
       && !testflag(addr, af_home_expanded)	/*   not expanded */
@@ -2208,6 +2213,7 @@ all users have access. It is necessary to be in a visible directory for some
 operating systems when running pipes, as some commands (e.g. "rm" under Solaris
 2.5) require this. */
 
+GET_OPTION("current_directory");
 working_directory = tp->current_dir ? tp->current_dir : addr->current_dir;
 if (working_directory)
   {
@@ -2605,36 +2611,40 @@ if ((status & 0xffff) != 0)
 
 /* If SPECIAL_WARN is set in the top address, send a warning message. */
 
-if (addr->special_action == SPECIAL_WARN && addr->transport->warn_message)
+if (addr->special_action == SPECIAL_WARN)
   {
-  int fd;
-  uschar *warn_message;
-  pid_t pid;
+  uschar * warn_message = addr->transport->warn_message;
+  GET_OPTION("quota_warn_message");
+  if (warn_message)
+    {
+    int fd;
+    pid_t pid;
 
-  DEBUG(D_deliver) debug_printf("Warning message requested by transport\n");
+    DEBUG(D_deliver) debug_printf("Warning message requested by transport\n");
 
-  if (!(warn_message = expand_string(addr->transport->warn_message)))
-    log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand \"%s\" (warning "
-      "message for %s transport): %s", addr->transport->warn_message,
-      addr->transport->name, expand_string_message);
+    if (!(warn_message = expand_string(warn_message)))
+      log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand \"%s\" (warning "
+	"message for %s transport): %s", addr->transport->warn_message,
+	addr->transport->name, expand_string_message);
 
-  else if ((pid = child_open_exim(&fd, US"tpt-warning-message")) > 0)
-    {
-    FILE *f = fdopen(fd, "wb");
-    if (errors_reply_to && !contains_header(US"Reply-To", warn_message))
-      fprintf(f, "Reply-To: %s\n", errors_reply_to);
-    fprintf(f, "Auto-Submitted: auto-replied\n");
-    if (!contains_header(US"From", warn_message))
-      moan_write_from(f);
-    fprintf(f, "%s", CS warn_message);
+    else if ((pid = child_open_exim(&fd, US"tpt-warning-message")) > 0)
+      {
+      FILE * f = fdopen(fd, "wb");
+      if (errors_reply_to && !contains_header(US"Reply-To", warn_message))
+	fprintf(f, "Reply-To: %s\n", errors_reply_to);
+      fprintf(f, "Auto-Submitted: auto-replied\n");
+      if (!contains_header(US"From", warn_message))
+	moan_write_from(f);
+      fprintf(f, "%s", CS warn_message);
 
-    /* Close and wait for child process to complete, without a timeout. */
+      /* Close and wait for child process to complete, without a timeout. */
 
-    (void)fclose(f);
-    (void)child_close(pid, 0);
-    }
+      (void)fclose(f);
+      (void)child_close(pid, 0);
+      }
 
-  addr->special_action = SPECIAL_NONE;
+    addr->special_action = SPECIAL_NONE;
+    }
   }
 }
 
@@ -2650,6 +2660,7 @@ tpt_parallel_check(transport_instance * tp, address_item * addr, uschar ** key)
 {
 unsigned max_parallel;
 
+GET_OPTION("max_parallel");
 if (!tp->max_parallel) return FALSE;
 
 max_parallel = (unsigned) expand_string_integer(tp->max_parallel, TRUE);
@@ -2773,6 +2784,7 @@ while (addr_local)
     /* Expand the batch_id string for comparison with other addresses.
     Expansion failure suppresses batching. */
 
+    GET_OPTION("batch_id");
     if (tp->batch_id)
       {
       deliver_set_expansions(addr);
@@ -2827,11 +2839,12 @@ while (addr_local)
 
       if (ok && batch_id)
         {
-        uschar *bid;
-        address_item *save_nextnext = next->next;
+        uschar * bid;
+        address_item * save_nextnext = next->next;
         next->next = NULL;            /* Expansion for a single address */
         deliver_set_expansions(next);
         next->next = save_nextnext;
+	GET_OPTION("batch_id");
         bid = expand_string(tp->batch_id);
         deliver_set_expansions(NULL);
         if (!bid)
@@ -4454,9 +4467,10 @@ nonmatch domains
   else
     return_path = sender_address;
 
+  GET_OPTION("return_path");
   if (tp->return_path)
     {
-    uschar *new_return_path = expand_string(tp->return_path);
+    uschar * new_return_path = expand_string(tp->return_path);
     if (new_return_path)
       return_path = new_return_path;
     else if (!f.expand_string_forcedfail)
@@ -5557,18 +5571,18 @@ return actual_time;
 
 static FILE *
 expand_open(const uschar * filename,
-  const uschar * varname, const uschar * reason)
+  const uschar * optname, const uschar * reason)
 {
 const uschar * s = expand_cstring(filename);
 FILE * fp = NULL;
 
 if (!s || !*s)
   log_write(0, LOG_MAIN|LOG_PANIC,
-    "Failed to expand %s: '%s'\n", varname, filename);
+    "Failed to expand %s: '%s'\n", optname, filename);
 else if (*s != '/' || is_tainted(s))
   log_write(0, LOG_MAIN|LOG_PANIC,
     "%s is not %s after expansion: '%s'\n",
-    varname, *s == '/' ? "untainted" : "absolute", s);
+    optname, *s == '/' ? "untainted" : "absolute", s);
 else if (!(fp = Ufopen(s, "rb")))
   log_write(0, LOG_MAIN|LOG_PANIC, "Failed to open %s for %s "
     "message texts: %s", s, reason, strerror(errno));
@@ -5699,6 +5713,7 @@ else
   /* Open a template file if one is provided. Log failure to open, but
   carry on - default texts will be used. */
 
+  GET_OPTION("bounce_message_file");
   if (bounce_message_file)
     emf = expand_open(bounce_message_file,
 	    US"bounce_message_file", US"error");
@@ -6058,6 +6073,7 @@ transport_ctx tctx = {{0}};
 
 if (pid <= 0) return FALSE;
 
+GET_OPTION("warn_message_file");
 if (warn_message_file)
   wmf = expand_open(warn_message_file,
 	  US"warn_message_file", US"warning");
@@ -6757,6 +6773,7 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT)
 
   /* Any error in the filter file causes a delivery to be abandoned. */
 
+  GET_OPTION("system_filter");
   redirect.string = system_filter;
   redirect.isfile = TRUE;
   redirect.check_owner = redirect.check_group = FALSE;
@@ -6936,12 +6953,14 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT)
         if (p->address[0] == '|')
           {
           type = US"pipe";
+	  GET_OPTION("system_filter_pipe_transport");
           tpname = system_filter_pipe_transport;
           address_pipe = p->address;
           }
         else if (p->address[0] == '>')
           {
           type = US"reply";
+	  GET_OPTION("system_filter_reply_transport");
           tpname = system_filter_reply_transport;
           }
         else
@@ -6949,11 +6968,13 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT)
           if (p->address[Ustrlen(p->address)-1] == '/')
             {
             type = US"directory";
+	    GET_OPTION("system_filter_directory_transport");
             tpname = system_filter_directory_transport;
             }
           else
             {
             type = US"file";
+	    GET_OPTION("system_filter_file_transport");
             tpname = system_filter_file_transport;
             }
           address_file = p->address;
@@ -8437,54 +8458,57 @@ else if (addr_defer != (address_item *)(+1))
         || addr_defer->dsn_flags & rf_notify_delay
 	)
      && delay_warning[1] > 0
-     && sender_address[0] != 0
-     && (  !delay_warning_condition
-        || expand_check_condition(delay_warning_condition,
-            US"delay_warning", US"option")
-	)
-     )
+     && sender_address[0] != 0)
     {
-    int count;
-    int show_time;
-    int queue_time = time(NULL) - received_time.tv_sec;
-
-    queue_time = test_harness_fudged_queue_time(queue_time);
-
-    /* See how many warnings we should have sent by now */
+    GET_OPTION("delay_warning_condition");
+    if ( (  !delay_warning_condition
+	    || expand_check_condition(delay_warning_condition,
+		US"delay_warning", US"option")
+	  )
+       )
+      {
+      int count;
+      int show_time;
+      int queue_time = time(NULL) - received_time.tv_sec;
 
-    for (count = 0; count < delay_warning[1]; count++)
-      if (queue_time < delay_warning[count+2]) break;
+      queue_time = test_harness_fudged_queue_time(queue_time);
 
-    show_time = delay_warning[count+1];
+      /* See how many warnings we should have sent by now */
 
-    if (count >= delay_warning[1])
-      {
-      int extra;
-      int last_gap = show_time;
-      if (count > 1) last_gap -= delay_warning[count];
-      extra = (queue_time - delay_warning[count+1])/last_gap;
-      show_time += last_gap * extra;
-      count += extra;
-      }
+      for (count = 0; count < delay_warning[1]; count++)
+	if (queue_time < delay_warning[count+2]) break;
 
-    DEBUG(D_deliver)
-      {
-      debug_printf("time on queue = %s  id %s  addr %s\n",
-	readconf_printtime(queue_time), message_id, addr_defer->address);
-      debug_printf("warning counts: required %d done %d\n", count,
-        warning_count);
-      }
+      show_time = delay_warning[count+1];
 
-    /* We have computed the number of warnings there should have been by now.
-    If there haven't been enough, send one, and up the count to what it should
-    have been. */
+      if (count >= delay_warning[1])
+	{
+	int extra;
+	int last_gap = show_time;
+	if (count > 1) last_gap -= delay_warning[count];
+	extra = (queue_time - delay_warning[count+1])/last_gap;
+	show_time += last_gap * extra;
+	count += extra;
+	}
 
-    if (warning_count < count)
-      if (send_warning_message(recipients, queue_time, show_time))
+      DEBUG(D_deliver)
 	{
-	warning_count = count;
-	update_spool = TRUE;    /* Ensure spool rewritten */
+	debug_printf("time on queue = %s  id %s  addr %s\n",
+	  readconf_printtime(queue_time), message_id, addr_defer->address);
+	debug_printf("warning counts: required %d done %d\n", count,
+	  warning_count);
 	}
+
+      /* We have computed the number of warnings there should have been by now.
+      If there haven't been enough, send one, and up the count to what it should
+      have been. */
+
+      if (warning_count < count)
+	if (send_warning_message(recipients, queue_time, show_time))
+	  {
+	  warning_count = count;
+	  update_spool = TRUE;    /* Ensure spool rewritten */
+	  }
+      }
     }
 
   /* Clear deliver_domain */
diff --git a/src/src/dkim.c b/src/src/dkim.c
index 63b16af39..9e76c51a0 100644
--- a/src/src/dkim.c
+++ b/src/src/dkim.c
@@ -624,6 +624,7 @@ if (dkim->dot_stuffed)
 
 store_pool = POOL_MAIN;
 
+GET_OPTION("dkim_domain");
 if ((s = dkim->dkim_domain) && !(dkim_domain = expand_cstring(s)))
   /* expansion error, do not send message. */
   { errwhen = US"dkim_domain"; goto expand_bad; }
@@ -652,6 +653,7 @@ if (dkim_domain)
   /* Set $dkim_selector expansion variable to each selector in list,
   for this domain. */
 
+  GET_OPTION("dkim_selector");
   if (!(dkim_sel = expand_string(dkim->dkim_selector)))
     { errwhen = US"dkim_selector"; goto expand_bad; }
 
@@ -669,6 +671,7 @@ if (dkim_domain)
 
     /* Get canonicalization to use */
 
+    GET_OPTION("dkim_canon");
     dkim_canon_expanded = dkim->dkim_canon
       ? expand_string(dkim->dkim_canon) : US"relaxed";
     if (!dkim_canon_expanded)	/* expansion error, do not send message. */
@@ -686,6 +689,7 @@ if (dkim_domain)
       pdkim_canon = PDKIM_CANON_RELAXED;
       }
 
+    GET_OPTION("dkim_sign_headers");
     if (  dkim->dkim_sign_headers
        && !(dkim_sign_headers_expanded = expand_string(dkim->dkim_sign_headers)))
       { errwhen = US"dkim_sign_header"; goto expand_bad; }
@@ -693,6 +697,7 @@ if (dkim_domain)
 
     /* Get private key to use. */
 
+    GET_OPTION("dkim_private_key");
     if (!(dkim_private_key_expanded = expand_string(dkim->dkim_private_key)))
       { errwhen = US"dkim_private_key"; goto expand_bad; }
 
@@ -707,15 +712,18 @@ if (dkim_domain)
 	     expand_file_big_buffer(dkim_private_key_expanded)))
       goto bad;
 
+    GET_OPTION("dkim_hash");
     if (!(dkim_hash_expanded = expand_string(dkim->dkim_hash)))
       { errwhen = US"dkim_hash"; goto expand_bad; }
 
+    GET_OPTION("dkim_identity");
     if (dkim->dkim_identity)
       if (!(dkim_identity_expanded = expand_string(dkim->dkim_identity)))
 	{ errwhen = US"dkim_identity"; goto expand_bad; }
       else if (!*dkim_identity_expanded)
 	dkim_identity_expanded = NULL;
 
+    GET_OPTION("dkim_timestamps");
     if (dkim->dkim_timestamps)
       if (!(dkim_timestamps_expanded = expand_string(dkim->dkim_timestamps)))
 	{ errwhen = US"dkim_timestamps"; goto expand_bad; }
diff --git a/src/src/dkim_transport.c b/src/src/dkim_transport.c
index 38f849af6..8c8d9b41a 100644
--- a/src/src/dkim_transport.c
+++ b/src/src/dkim_transport.c
@@ -18,6 +18,7 @@
 static BOOL
 dkt_sign_fail(struct ob_dkim * dkim, int * errp)
 {
+GET_OPTION("dkim_strict");
 if (dkim->dkim_strict)
   {
   uschar * dkim_strict_result = expand_string(dkim->dkim_strict);
diff --git a/src/src/exim.c b/src/src/exim.c
index 10fc98963..c591ee60c 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -5063,6 +5063,8 @@ for (i = 0;;)
         /* If a pattern for matching the gecos field was supplied, apply
         it and then expand the name string. */
 
+	GET_OPTION("gecos_pattern");
+	GET_OPTION("gecos_name");
         if (gecos_pattern && gecos_name)
           {
           const pcre2_code *re;
@@ -5108,6 +5110,7 @@ any setting of unknown_login overrides the actual name. */
 
 if (!originator_login || f.running_in_test_harness)
   {
+  GET_OPTION("unknown_login");
   if (unknown_login)
     {
     originator_login = expand_string(unknown_login);
@@ -5407,17 +5410,18 @@ for hosts that want to play several parts at once. We need to ensure that it is
 set for host checking, and for receiving messages. */
 
 smtp_active_hostname = primary_hostname;
-if (raw_active_hostname != NULL)
+GET_OPTION("smtp_active_hostname");
+if (raw_active_hostname)
   {
-  uschar *nah = expand_string(raw_active_hostname);
-  if (nah == NULL)
+  uschar * nah = expand_string(raw_active_hostname);
+  if (!nah)
     {
     if (!f.expand_string_forcedfail)
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand \"%s\" "
         "(smtp_active_hostname): %s", raw_active_hostname,
         expand_string_message);
     }
-  else if (nah[0] != 0) smtp_active_hostname = nah;
+  else if (nah[0]) smtp_active_hostname = nah;
   }
 
 /* Handle host checking: this facility mocks up an incoming SMTP call from a
@@ -5675,6 +5679,7 @@ if (smtp_input)
 
 else
   {
+  GET_OPTION("message_size_limit");
   thismessage_size_limit = expand_string_integer(message_size_limit, TRUE);
   if (expand_string_message)
     if (thismessage_size_limit == -1)
@@ -5767,14 +5772,18 @@ for (BOOL more = TRUE; more; )
       the very end. The result of the ACL is ignored (as for other non-SMTP
       messages). It is run for its potential side effects. */
 
-      if (smtp_batched_input && acl_not_smtp_start != NULL)
-        {
-        uschar *user_msg, *log_msg;
-        f.enable_dollar_recipients = TRUE;
-        (void)acl_check(ACL_WHERE_NOTSMTP_START, NULL, acl_not_smtp_start,
-          &user_msg, &log_msg);
-        f.enable_dollar_recipients = FALSE;
-        }
+      if (smtp_batched_input)
+	{
+	GET_OPTION("acl_not_smtp_start");
+	if (acl_not_smtp_start)
+	  {
+	  uschar * user_msg, * log_msg;
+	  f.enable_dollar_recipients = TRUE;
+	  (void)acl_check(ACL_WHERE_NOTSMTP_START, NULL, acl_not_smtp_start,
+	    &user_msg, &log_msg);
+	  f.enable_dollar_recipients = FALSE;
+	  }
+	}
 
       /* Now get the data for the message */
 
@@ -5928,9 +5937,10 @@ for (BOOL more = TRUE; more; )
     ignored; rejecting here would just add complication, and it can just as
     well be done later. Allow $recipients to be visible in the ACL. */
 
+    GET_OPTION("acl_not_smtp_start");
     if (acl_not_smtp_start)
       {
-      uschar *user_msg, *log_msg;
+      uschar * user_msg, * log_msg;
       f.enable_dollar_recipients = TRUE;
       (void)acl_check(ACL_WHERE_NOTSMTP_START, NULL, acl_not_smtp_start,
         &user_msg, &log_msg);
diff --git a/src/src/expand.c b/src/src/expand.c
index f9813133d..6be5cca0e 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -8737,12 +8737,14 @@ Returns:     OK     value placed in rvalue
 */
 
 int
-exp_bool(address_item *addr,
-  uschar *mtype, uschar *mname, unsigned dbg_opt,
-  uschar *oname, BOOL bvalue,
-  uschar *svalue, BOOL *rvalue)
+exp_bool(address_item * addr,
+  uschar * mtype, uschar * mname, unsigned dbg_opt,
+  uschar * oname, BOOL bvalue,
+  uschar * svalue, BOOL * rvalue)
 {
-uschar *expanded;
+uschar * expanded;
+
+DEBUG(D_expand) debug_printf("try option %s\n", oname);
 if (!svalue) { *rvalue = bvalue; return OK; }
 
 if (!(expanded = expand_string(svalue)))
diff --git a/src/src/macros.h b/src/src/macros.h
index 8aed335b5..902cdd165 100644
--- a/src/src/macros.h
+++ b/src/src/macros.h
@@ -1190,4 +1190,8 @@ When doing en extended loop of matching, release store periodically. */
 
 #define	REGEX_LOOPCOUNT_STORE_RESET	1000
 
+/* Debug an option access. Use for non-list ones about to be expanded. */
+#define GET_OPTION(name) \
+  DEBUG(D_expand) debug_printf("try option " name "\n");
+
 /* End of macros.h */
diff --git a/src/src/moan.c b/src/src/moan.c
index a3c8e0aba..6fe82b29c 100644
--- a/src/src/moan.c
+++ b/src/src/moan.c
@@ -30,8 +30,9 @@ Returns:    nothing
 void
 moan_write_from(FILE *f)
 {
-uschar * s = expand_string(dsn_from);
-if (!s)
+uschar * s;
+GET_OPTION("dsn_from");
+if (!(s = expand_string(dsn_from)))
   {
   log_write(0, LOG_MAIN|LOG_PANIC,
     "Failed to expand dsn_from (using default): %s", expand_string_message);
@@ -175,6 +176,7 @@ uschar * s, * s2;
 /* For DMARC if there is a specific sender set, expand the variable for the
 header From: and grab the address from that for the envelope FROM. */
 
+GET_OPTION("dmarc_forensic_sender");
 if (  ident == ERRMESS_DMARC_FORENSIC
    && dmarc_forensic_sender
    && (s = expand_string(dmarc_forensic_sender))
@@ -513,9 +515,12 @@ if (check_sender && message_file && f.trusted_caller &&
   {
   uschar *new_sender = NULL;
   if (regex_match_and_setup(regex_From, big_buffer, 0, -1))
+    {
+    GET_OPTION("uucp_from_sender");
     new_sender = expand_string(uucp_from_sender);
+    }
   if (new_sender) sender_address = new_sender;
-    else firstline = big_buffer;
+  else firstline = big_buffer;
   }
 
 /* If viable sender address, send a message */
diff --git a/src/src/receive.c b/src/src/receive.c
index 990b9c22e..abac05c56 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -1574,6 +1574,7 @@ uschar * timestamp = expand_string(US"${tod_full}");
 header_line * received_header= header_list;
 
 if (recipients_count == 1) received_for = recipients_list[0].address;
+GET_OPTION("received_header_text");
 received = expand_string(received_header_text);
 received_for = NULL;
 
@@ -2201,8 +2202,9 @@ OVERSIZE:
     {
     if (!f.sender_address_forced)
       {
-      uschar *uucp_sender = expand_string(uucp_from_sender);
-      if (!uucp_sender)
+      uschar * uucp_sender;
+      GET_OPTION("uucp_from_sender");
+      if (!(uucp_sender = expand_string(uucp_from_sender)))
         log_write(0, LOG_MAIN|LOG_PANIC,
           "expansion of \"%s\" failed after matching "
           "\"From \" line: %s", uucp_from_sender, expand_string_message);
@@ -2821,6 +2823,7 @@ if (  !msgid_header
 
   /* Permit only letters, digits, dots, and hyphens in the domain */
 
+  GET_OPTION("message_id_header_domain");
   if (message_id_domain)
     {
     uschar *new_id_domain = expand_string(message_id_domain);
@@ -2842,6 +2845,7 @@ if (  !msgid_header
   /* Permit all characters except controls and RFC 2822 specials in the
   additional text part. */
 
+  GET_OPTION("message_id_header_text");
   if (message_id_text)
     {
     uschar *new_id_text = expand_string(message_id_text);
@@ -3517,6 +3521,7 @@ else
       dkim_exim_verify_finish();
 
       /* Check if we must run the DKIM ACL */
+      GET_OPTION("acl_smtp_dkim");
       if (acl_smtp_dkim && dkim_verify_signers && *dkim_verify_signers)
         {
         uschar * dkim_verify_signers_expanded =
@@ -3606,11 +3611,14 @@ else
 #endif /* DISABLE_DKIM */
 
 #ifdef WITH_CONTENT_SCAN
-    if (  recipients_count > 0
-       && acl_smtp_mime
-       && !run_mime_acl(acl_smtp_mime, &smtp_yield, &smtp_reply, &blackholed_by)
-       )
-      goto TIDYUP;
+    if (recipients_count > 0)
+      {
+      GET_OPTION("acl_smtp_mime");
+      if (acl_smtp_mime
+	 && !run_mime_acl(acl_smtp_mime, &smtp_yield, &smtp_reply, &blackholed_by)
+	 )
+	goto TIDYUP;
+      }
 #endif /* WITH_CONTENT_SCAN */
 
 #ifdef SUPPORT_DMARC
@@ -3618,67 +3626,73 @@ else
 #endif
 
 #ifndef DISABLE_PRDR
-    if (prdr_requested && recipients_count > 1 && acl_smtp_data_prdr)
+    if (prdr_requested && recipients_count > 1)
       {
-      int all_pass = OK;
-      int all_fail = FAIL;
+      GET_OPTION("acl_smtp_data_prdr");
+      if (acl_smtp_data_prdr)
+	{
+	int all_pass = OK;
+	int all_fail = FAIL;
 
-      smtp_printf("353 PRDR content analysis beginning\r\n", SP_MORE);
-      /* Loop through recipients, responses must be in same order received */
-      for (unsigned int c = 0; recipients_count > c; c++)
-        {
-	const uschar * addr = recipients_list[c].address;
-	uschar * msg= US"PRDR R=<%s> %s";
-	uschar * code;
-        DEBUG(D_receive)
-          debug_printf("PRDR processing recipient %s (%d of %d)\n",
-                       addr, c+1, recipients_count);
-        rc = acl_check(ACL_WHERE_PRDR, addr,
-                       acl_smtp_data_prdr, &user_msg, &log_msg);
-
-        /* If any recipient rejected content, indicate it in final message */
-        all_pass |= rc;
-        /* If all recipients rejected, indicate in final message */
-        all_fail &= rc;
-
-        switch (rc)
-          {
-          case OK: case DISCARD: code = US"250"; break;
-          case DEFER:            code = US"450"; break;
-          default:               code = US"550"; break;
-          }
-	if (user_msg != NULL)
-	  smtp_user_msg(code, user_msg);
-	else
+	smtp_printf("353 PRDR content analysis beginning\r\n", SP_MORE);
+	/* Loop through recipients, responses must be in same order received */
+	for (unsigned int c = 0; recipients_count > c; c++)
 	  {
+	  const uschar * addr = recipients_list[c].address;
+	  uschar * msg= US"PRDR R=<%s> %s";
+	  uschar * code;
+	  DEBUG(D_receive)
+	    debug_printf("PRDR processing recipient %s (%d of %d)\n",
+			 addr, c+1, recipients_count);
+	  rc = acl_check(ACL_WHERE_PRDR, addr,
+			 acl_smtp_data_prdr, &user_msg, &log_msg);
+
+	  /* If any recipient rejected content, indicate it in final message */
+	  all_pass |= rc;
+	  /* If all recipients rejected, indicate in final message */
+	  all_fail &= rc;
+
 	  switch (rc)
-            {
-            case OK: case DISCARD:
-              msg = string_sprintf(CS msg, addr, "acceptance");        break;
-            case DEFER:
-              msg = string_sprintf(CS msg, addr, "temporary refusal"); break;
-            default:
-              msg = string_sprintf(CS msg, addr, "refusal");           break;
-            }
-          smtp_user_msg(code, msg);
-	  }
-	if (log_msg)       log_write(0, LOG_MAIN, "PRDR %s %s", addr, log_msg);
-	else if (user_msg) log_write(0, LOG_MAIN, "PRDR %s %s", addr, user_msg);
-	else               log_write(0, LOG_MAIN, "%s", CS msg);
+	    {
+	    case OK: case DISCARD: code = US"250"; break;
+	    case DEFER:            code = US"450"; break;
+	    default:               code = US"550"; break;
+	    }
+	  if (user_msg != NULL)
+	    smtp_user_msg(code, user_msg);
+	  else
+	    {
+	    switch (rc)
+	      {
+	      case OK: case DISCARD:
+		msg = string_sprintf(CS msg, addr, "acceptance");        break;
+	      case DEFER:
+		msg = string_sprintf(CS msg, addr, "temporary refusal"); break;
+	      default:
+		msg = string_sprintf(CS msg, addr, "refusal");           break;
+	      }
+	    smtp_user_msg(code, msg);
+	    }
+	  if (log_msg)       log_write(0, LOG_MAIN, "PRDR %s %s", addr, log_msg);
+	  else if (user_msg) log_write(0, LOG_MAIN, "PRDR %s %s", addr, user_msg);
+	  else               log_write(0, LOG_MAIN, "%s", CS msg);
 
-	if (rc != OK) { receive_remove_recipient(addr); c--; }
-        }
-      /* Set up final message, used if data acl gives OK */
-      smtp_reply = string_sprintf("%s id=%s message %s",
-		       all_fail == FAIL ? US"550" : US"250",
-		       message_id,
-                       all_fail == FAIL
-		         ? US"rejected for all recipients"
-			 : all_pass == OK
-			   ? US"accepted"
-			   : US"accepted for some recipients");
-      if (recipients_count == 0)
-	goto NOT_ACCEPTED;
+	  if (rc != OK) { receive_remove_recipient(addr); c--; }
+	  }
+	/* Set up final message, used if data acl gives OK */
+	smtp_reply = string_sprintf("%s id=%s message %s",
+			 all_fail == FAIL ? US"550" : US"250",
+			 message_id,
+			 all_fail == FAIL
+			   ? US"rejected for all recipients"
+			   : all_pass == OK
+			     ? US"accepted"
+			     : US"accepted for some recipients");
+	if (recipients_count == 0)
+	  goto NOT_ACCEPTED;
+	}
+      else
+	prdr_requested = FALSE;
       }
     else
       prdr_requested = FALSE;
@@ -3687,6 +3701,7 @@ else
     /* Check the recipients count again, as the MIME ACL might have changed
     them. */
 
+    GET_OPTION("acl_smtp_data");
     if (acl_smtp_data && recipients_count > 0)
       {
       rc = acl_check(ACL_WHERE_DATA, NULL, acl_smtp_data, &user_msg, &log_msg);
@@ -3724,6 +3739,7 @@ else
     {
 
 #ifdef WITH_CONTENT_SCAN
+    GET_OPTION("acl_not_smtp_mime");
     if (  acl_not_smtp_mime
        && !run_mime_acl(acl_not_smtp_mime, &smtp_yield, &smtp_reply,
           &blackholed_by)
@@ -3731,9 +3747,10 @@ else
       goto TIDYUP;
 #endif /* WITH_CONTENT_SCAN */
 
+    GET_OPTION("acl_not_smtp");
     if (acl_not_smtp)
       {
-      uschar *user_msg, *log_msg;
+      uschar * user_msg, * log_msg;
       f.authentication_local = TRUE;
       rc = acl_check(ACL_WHERE_NOTSMTP, NULL, acl_not_smtp, &user_msg, &log_msg);
       if (rc == DISCARD)
diff --git a/src/src/route.c b/src/src/route.c
index 3401f15b4..0fdcdf2ed 100644
--- a/src/src/route.c
+++ b/src/src/route.c
@@ -616,7 +616,7 @@ uschar *check;
 
 if (!s) return OK;
 
-DEBUG(D_route) debug_printf("checking require_files\n");
+DEBUG(D_route|D_expand) debug_printf("checking require_files\n");
 
 listptr = s;
 while ((check = string_nextinlist(&listptr, &sep, NULL, 0)))
@@ -637,7 +637,7 @@ while ((check = string_nextinlist(&listptr, &sep, NULL, 0)))
 
   /* Empty items are just skipped */
 
-  if (*ss == 0) continue;
+  if (!*ss) continue;
 
   /* If there are no slashes in the string, we have a user name or uid, with
   optional group/gid. */
@@ -651,9 +651,9 @@ while ((check = string_nextinlist(&listptr, &sep, NULL, 0)))
     /* If there's a comma, temporarily terminate the user name/number
     at that point. Then set the uid. */
 
-    if (comma != NULL) *comma = 0;
+    if (comma) *comma = 0;
     ok = route_finduser(ss, &pw, &uid);
-    if (comma != NULL) *comma = ',';
+    if (comma) *comma = ',';
 
     if (!ok)
       {
@@ -663,24 +663,22 @@ while ((check = string_nextinlist(&listptr, &sep, NULL, 0)))
 
     /* If there was no comma, the gid is that associated with the user. */
 
-    if (comma == NULL)
-      {
-      if (pw != NULL) gid = pw->pw_gid; else
+    if (!comma)
+      if (pw)
+	gid = pw->pw_gid;
+      else
         {
         *perror = string_sprintf("group missing after numerical uid %d for "
           "require_files", (int)uid);
         goto RETURN_DEFER;
         }
-      }
     else
-      {
       if (!route_findgroup(comma + 1, &gid))
         {
         *perror = string_sprintf("group \"%s\" for require_files not found\n",
           comma + 1);
         goto RETURN_DEFER;
         }
-      }
 
     /* Note that we have values set, and proceed to next item */
 
@@ -974,6 +972,7 @@ check_local_user before any subsequent expansions are done. Otherwise, $home
 could mean different things for different options, which would be extremely
 confusing. */
 
+GET_OPTION("router_home_directory");
 if (r->router_home_directory)
   {
   uschar * router_home = expand_string(r->router_home_directory);
@@ -1018,7 +1017,8 @@ if ((rc = check_files(r->require_files, perror)) != OK)
 
 if (r->condition)
   {
-  DEBUG(D_route) debug_printf("checking \"condition\" \"%.80s\"...\n", r->condition);
+  DEBUG(D_route|D_expand)
+    debug_printf("checking \"condition\" \"%.80s\"...\n", r->condition);
   if (!expand_check_condition(r->condition, r->name, US"router"))
     {
     if (f.search_find_defer)
@@ -1440,6 +1440,7 @@ const uschar * varlist = r->set;
 tree_node ** root = (tree_node **) &addr->prop.variables;
 int sep = ';';
 
+GET_OPTION("set");
 if (!varlist) return OK;
 
 /* Walk the varlist, creating variables */
@@ -1474,6 +1475,7 @@ for (uschar * ele; (ele = string_nextinlist(&varlist, &sep, NULL, 0)); )
 
       /* Expand "more" if necessary; DEFER => an expansion failed */
 
+      GET_OPTION("more");
       yield = exp_bool(addr, US"router", r->name, D_route,
 		      US"more", r->more, r->expand_more, &more);
       if (yield != OK) return yield;
@@ -1754,7 +1756,7 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
 
   if (r->address_data)
     {
-    DEBUG(D_route) debug_printf("processing address_data\n");
+    DEBUG(D_route|D_expand) debug_printf("processing address_data\n");
     if (!(deliver_address_data = expand_string(r->address_data)))
       {
       if (f.expand_string_forcedfail)
@@ -1764,6 +1766,7 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
 
         /* Expand "more" if necessary; DEFER => an expansion failed */
 
+	GET_OPTION("more");
         yield = exp_bool(addr, US"router", r->name, D_route,
 			US"more", r->more, r->expand_more, &more);
         if (yield != OK) goto ROUTE_EXIT;
@@ -1872,6 +1875,7 @@ for (r = addr->start_router ? addr->start_router : routers; r; r = nextr)
     {
     /* Expand "more" if necessary */
 
+    GET_OPTION("more");
     yield = exp_bool(addr, US"router", r->name, D_route,
 		       	US"more", r->more, r->expand_more, &more);
     if (yield != OK) goto ROUTE_EXIT;
@@ -1897,18 +1901,21 @@ if (!r)
   HDEBUG(D_route) debug_printf("no more routers\n");
   if (!addr->message)
     {
-    uschar *message = US"Unrouteable address";
-    if (addr->router && addr->router->cannot_route_message)
+    uschar * message = US"Unrouteable address";
+    if (addr->router)
       {
-      uschar *expmessage = expand_string(addr->router->cannot_route_message);
-      if (!expmessage)
-        {
-        if (!f.expand_string_forcedfail)
-          log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
-            "cannot_route_message in %s router: %s", addr->router->name,
-            expand_string_message);
-        }
-      else message = expmessage;
+      uschar * s = addr->router->cannot_route_message;
+      GET_OPTION("cannot_route_message");
+      if (s)
+	{
+	if ((s = expand_string(s)))
+	  message = s;
+	else
+	  if (!f.expand_string_forcedfail)
+	    log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
+	      "cannot_route_message in %s router: %s", addr->router->name,
+	      expand_string_message);
+	}
       }
     addr->user_message = addr->message = message;
     }
@@ -1950,6 +1957,7 @@ networking, so it is included in the binary only if requested. */
 
 #ifdef SUPPORT_TRANSLATE_IP_ADDRESS
 
+GET_OPTION("translate_ip_address");
 if (r->translate_ip_address)
   {
   int rc;
@@ -2008,6 +2016,7 @@ if (r->translate_ip_address)
 /* See if this is an unseen routing; first expand the option if necessary.
 DEFER can be given if the expansion fails */
 
+GET_OPTION("unseen");
 yield = exp_bool(addr, US"router", r->name, D_route,
 	       	US"unseen", r->unseen, r->expand_unseen, &unseen);
 if (yield != OK) goto ROUTE_EXIT;
diff --git a/src/src/routers/dnslookup.c b/src/src/routers/dnslookup.c
index d27757c7e..671e8c93f 100644
--- a/src/src/routers/dnslookup.c
+++ b/src/src/routers/dnslookup.c
@@ -151,14 +151,11 @@ host_item h;
 int rc;
 int widen_sep = 0;
 int whichrrs = HOST_FIND_BY_MX | HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
-dnslookup_router_options_block *ob =
+dnslookup_router_options_block * ob =
   (dnslookup_router_options_block *)(rblock->options_block);
-uschar *srv_service = NULL;
-uschar *widen = NULL;
-const uschar *pre_widen = addr->domain;
-const uschar *post_widen = NULL;
-const uschar *fully_qualified_name;
-const uschar *listptr;
+uschar * srv_service = NULL, * widen = NULL;
+const uschar * pre_widen = addr->domain, * post_widen = NULL;
+const uschar * fully_qualified_name, * listptr;
 uschar widen_buffer[256];
 
 DEBUG(D_route)
@@ -167,8 +164,8 @@ DEBUG(D_route)
 
 /* If an SRV check is required, expand the service name */
 
+GET_OPTION("check_srv");
 if (ob->check_srv)
-  {
   if (  !(srv_service = expand_string(ob->check_srv))
      && !f.expand_string_forcedfail)
     {
@@ -176,8 +173,8 @@ if (ob->check_srv)
       rblock->name, ob->check_srv, expand_string_message);
     return DEFER;
     }
-  else whichrrs |= HOST_FIND_BY_SRV;
-  }
+  else
+    whichrrs |= HOST_FIND_BY_SRV;
 
 /* Set up the first of any widening domains. The code further down copes with
 either pre- or post-widening, but at present there is no way to turn on
diff --git a/src/src/routers/iplookup.c b/src/src/routers/iplookup.c
index a6dc0fc87..f9966bcc0 100644
--- a/src/src/routers/iplookup.c
+++ b/src/src/routers/iplookup.c
@@ -178,19 +178,17 @@ reply = store_get(256, GET_TAINTED);
 /* Build the query string to send. If not explicitly given, a default of
 "user@domain user@domain" is used. */
 
-if (ob->query == NULL)
+GET_OPTION("query");
+if (!ob->query)
   query = string_sprintf("%s@%s %s@%s", addr->local_part, addr->domain,
     addr->local_part, addr->domain);
 else
-  {
-  query = expand_string(ob->query);
-  if (query == NULL)
+  if (!(query = expand_string(ob->query)))
     {
     addr->message = string_sprintf("%s router: failed to expand %s: %s",
       rblock->name, ob->query, expand_string_message);
     return DEFER;
     }
-  }
 
 query_len = Ustrlen(query);
 DEBUG(D_route) debug_printf("%s router query is \"%s\"\n", rblock->name,
@@ -362,23 +360,24 @@ else
 /* If an explicit rerouting string is specified, expand it. Otherwise, use
 what was sent back verbatim. */
 
-if (ob->reroute != NULL)
+GET_OPTION("reroute");
+if (ob->reroute)
   {
   reroute = expand_string(ob->reroute);
   expand_nmax = -1;
-  if (reroute == NULL)
+  if (!reroute)
     {
     addr->message = string_sprintf("%s router: failed to expand %s: %s",
       rblock->name, ob->reroute, expand_string_message);
     return DEFER;
     }
   }
-else reroute = reply;
+else
+  reroute = reply;
 
 /* We should now have a new address in the form user@domain. */
 
-domain = Ustrchr(reroute, '@');
-if (domain == NULL)
+if (!(domain = Ustrchr(reroute, '@')))
   {
   log_write(0, LOG_MAIN, "%s router: reroute string %s is not of the form "
     "user@domain", rblock->name, reroute);
diff --git a/src/src/routers/manualroute.c b/src/src/routers/manualroute.c
index 160c866b9..200e0206e 100644
--- a/src/src/routers/manualroute.c
+++ b/src/src/routers/manualroute.c
@@ -118,8 +118,8 @@ if (ob->hai_code < 0)
 
 /* One of route_list or route_data must be specified */
 
-if ((ob->route_list == NULL && ob->route_data == NULL) ||
-    (ob->route_list != NULL && ob->route_data != NULL))
+if (  !ob->route_list && !ob->route_data
+   || ob->route_list && ob->route_data)
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n  "
     "route_list or route_data (but not both) must be specified",
     rblock->name);
@@ -291,6 +291,7 @@ string, decline. */
 
 else
   {
+  GET_OPTION("route_data");
   if (!(route_item = rf_expand_data(addr, ob->route_data, &rc)))
     return rc;
   (void) parse_route_item(route_item, NULL, &hostlist, &options);
diff --git a/src/src/routers/queryprogram.c b/src/src/routers/queryprogram.c
index 2a6a73eb6..dd5e24b93 100644
--- a/src/src/routers/queryprogram.c
+++ b/src/src/routers/queryprogram.c
@@ -287,6 +287,7 @@ if (curr_uid != root_uid && (uid != curr_uid || gid != curr_gid))
 
 /* Set up the command to run */
 
+GET_OPTION("command");
 if (!transport_set_up_command(&argvptr, /* anchor for arg list */
     ob->command,                        /* raw command */
     TSUC_EXPAND_ARGS,                   /* arguments expanded but must not be tainted */
diff --git a/src/src/routers/redirect.c b/src/src/routers/redirect.c
index 4c8e30563..6b27c82b0 100644
--- a/src/src/routers/redirect.c
+++ b/src/src/routers/redirect.c
@@ -370,12 +370,11 @@ while (generated)
     in \N...\N to avoid expansion later. In Cygwin, home directories can
     contain $ characters. */
 
-    if (rblock->home_directory != NULL)
+    if (rblock->home_directory)
       next->home_dir = rblock->home_directory;
     else if (rblock->check_local_user)
       next->home_dir = string_sprintf("\\N%s\\N", pw->pw_dir);
-    else if (rblock->router_home_directory != NULL &&
-             testflag(addr, af_home_expanded))
+    else if (rblock->router_home_directory && testflag(addr, af_home_expanded))
       {
       next->home_dir = deliver_home;
       setflag(next, af_home_expanded);
@@ -395,6 +394,7 @@ while (generated)
     if (next->address[0] == '|')
       {
       address_pipe = next->address;
+      GET_OPTION("pipe_transport");
       if (rf_get_transport(ob->pipe_transport_name, &ob->pipe_transport,
           next, rblock->name, US"pipe_transport"))
         next->transport = ob->pipe_transport;
@@ -402,6 +402,7 @@ while (generated)
       }
     else if (next->address[0] == '>')
       {
+      GET_OPTION("reply_transport");
       if (rf_get_transport(ob->reply_transport_name, &ob->reply_transport,
           next, rblock->name, US"reply_transport"))
         next->transport = ob->reply_transport;
@@ -412,15 +413,19 @@ while (generated)
       address_file = next->address;
       if (next->address[len-1] == '/')
         {
+	GET_OPTION("directory_transport");
         if (rf_get_transport(ob->directory_transport_name,
             &(ob->directory_transport), next, rblock->name,
             US"directory_transport"))
           next->transport = ob->directory_transport;
         }
       else
+	{
+	GET_OPTION("file_transport");
         if (rf_get_transport(ob->file_transport_name, &ob->file_transport,
             next, rblock->name, US"file_transport"))
           next->transport = ob->file_transport;
+	}
 
       address_file = NULL;
       }
@@ -566,11 +571,15 @@ address. Otherwise, if a local qualify_domain is provided, set that up. */
 
 if (ob->qualify_preserve_domain)
   qualify_domain_recipient = addr->domain;
-else if (ob->qualify_domain)
+else
   {
-  uschar *new_qdr = rf_expand_data(addr, ob->qualify_domain, &xrc);
-  if (!new_qdr) return xrc;
-  qualify_domain_recipient = new_qdr;
+  GET_OPTION("qualify_domain");
+  if (ob->qualify_domain)
+    {
+    uschar *new_qdr = rf_expand_data(addr, ob->qualify_domain, &xrc);
+    if (!new_qdr) return xrc;
+    qualify_domain_recipient = new_qdr;
+    }
   }
 
 redirect.owners = ob->owners;
diff --git a/src/src/routers/rf_expand_data.c b/src/src/routers/rf_expand_data.c
index 9892567f0..6ba613215 100644
--- a/src/src/routers/rf_expand_data.c
+++ b/src/src/routers/rf_expand_data.c
@@ -31,7 +31,7 @@ uschar *
 rf_expand_data(address_item *addr, uschar *s, int *prc)
 {
 uschar *yield = expand_string(s);
-if (yield != NULL) return yield;
+if (yield) return yield;
 if (f.expand_string_forcedfail)
   {
   DEBUG(D_route) debug_printf("forced failure for expansion of \"%s\"\n", s);
diff --git a/src/src/routers/rf_get_errors_address.c b/src/src/routers/rf_get_errors_address.c
index 222d157d8..792b9b5f3 100644
--- a/src/src/routers/rf_get_errors_address.c
+++ b/src/src/routers/rf_get_errors_address.c
@@ -38,14 +38,13 @@ int
 rf_get_errors_address(address_item * addr, router_instance * rblock,
   int verify, const uschar ** errors_to)
 {
-uschar *s;
+uschar * s;
 
 *errors_to = addr->prop.errors_address;
 if (!rblock->errors_to) return OK;
 
-s = expand_string(rblock->errors_to);
-
-if (s == NULL)
+GET_OPTION("errors_to");
+if (!(s = expand_string(rblock->errors_to)))
   {
   if (f.expand_string_forcedfail)
     {
@@ -60,7 +59,7 @@ if (s == NULL)
 
 /* If the errors_to address is empty, it means "ignore errors" */
 
-if (*s == 0)
+if (!*s)
   {
   addr->prop.ignore_error = TRUE;   /* For locally detected errors */
   *errors_to = US"";                   /* Return path for SMTP */
diff --git a/src/src/routers/rf_get_transport.c b/src/src/routers/rf_get_transport.c
index d54e3c296..d37966865 100644
--- a/src/src/routers/rf_get_transport.c
+++ b/src/src/routers/rf_get_transport.c
@@ -47,6 +47,7 @@ rf_get_transport(uschar *tpname, transport_instance **tpptr, address_item *addr,
 uschar *ss;
 BOOL expandable;
 
+GET_OPTION("transport");
 if (!tpname)
   {
   if (!require_name) return TRUE;
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index 2d977b975..ff768e94c 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -2172,6 +2172,7 @@ lwr_receive_ungetc = NULL;
 
 /* Set up the message size limit; this may be host-specific */
 
+GET_OPTION("message_size_limit");
 thismessage_size_limit = expand_string_integer(message_size_limit, TRUE);
 if (expand_string_message)
   {
@@ -2569,6 +2570,7 @@ if (proxy_protocol_host())
 /* Run the connect ACL if it exists */
 
 user_msg = NULL;
+GET_OPTION("acl_smtp_connect");
 if (acl_smtp_connect)
   {
   int rc;
@@ -2615,16 +2617,20 @@ if (user_msg)
     esclen = codelen - 4;
     }
   }
-else if (!(s = expand_string(smtp_banner)))
+else
   {
-  log_write(0, f.expand_string_forcedfail ? LOG_MAIN : LOG_MAIN|LOG_PANIC_DIE,
-    "Expansion of \"%s\" (smtp_banner) failed: %s",
-    smtp_banner, expand_string_message);
-  /* for force-fail */
-#ifndef DISABLE_TLS
-  if (tls_in.on_connect) tls_close(NULL, TLS_SHUTDOWN_WAIT);
-#endif
-  return FALSE;
+  GET_OPTION("smtp_banner");
+  if (!(s = expand_string(smtp_banner)))
+    {
+    log_write(0, f.expand_string_forcedfail ? LOG_MAIN : LOG_MAIN|LOG_PANIC_DIE,
+      "Expansion of \"%s\" (smtp_banner) failed: %s",
+      smtp_banner, expand_string_message);
+    /* for force-fail */
+  #ifndef DISABLE_TLS
+    if (tls_in.on_connect) tls_close(NULL, TLS_SHUTDOWN_WAIT);
+  #endif
+    return FALSE;
+    }
   }
 
 /* Remove any terminating newlines; might as well remove trailing space too */
@@ -3185,6 +3191,7 @@ fl.smtp_exit_function_called = TRUE;
 
 /* Call the not-QUIT ACL, if there is one, unless no reason is given. */
 
+GET_OPTION("acl_smtp_notquit");
 if (acl_smtp_notquit && reason)
   {
   smtp_notquit_reason = reason;
@@ -3521,6 +3528,7 @@ smtp_quit_handler(uschar ** user_msgp, uschar ** log_msgp)
 HAD(SCH_QUIT);
 f.smtp_in_quit = TRUE;
 incomplete_transaction_log(US"QUIT");
+GET_OPTION("acl_smtp_quit");
 if (  acl_smtp_quit
    && acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, user_msgp, log_msgp)
 	== ERROR)
@@ -3694,6 +3702,7 @@ while (done <= 0)
     for (auth_instance * au = auths; au; au = au->next)
       if (strcmpic(US"tls", au->driver_name) == 0)
 	{
+	GET_OPTION("acl_smtp_auth");
 	if (  acl_smtp_auth
 	   && (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth,
 		      &user_msg, &log_msg)) != OK
@@ -3772,6 +3781,7 @@ while (done <= 0)
 
       /* Check the ACL */
 
+      GET_OPTION("acl_smtp_auth");
       if (  acl_smtp_auth
 	 && (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth,
 		    &user_msg, &log_msg)) != OK
@@ -3966,6 +3976,7 @@ while (done <= 0)
       /* Apply an ACL check if one is defined; afterwards, recheck
       synchronization in case the client started sending in a delay. */
 
+      GET_OPTION("acl_smtp_helo");
       if (acl_smtp_helo)
 	if ((rc = acl_check(ACL_WHERE_HELO, NULL, acl_smtp_helo,
 		  &user_msg, &log_msg)) != OK)
@@ -4097,16 +4108,19 @@ while (done <= 0)
 	/* Advertise ETRN/VRFY/EXPN if there's are ACL checking whether a host is
 	permitted to issue them; a check is made when any host actually tries. */
 
+	GET_OPTION("acl_smtp_etrn");
 	if (acl_smtp_etrn)
 	  {
 	  g = string_catn(g, smtp_code, 3);
 	  g = string_catn(g, US"-ETRN\r\n", 7);
 	  }
+	GET_OPTION("acl_smtp_vrfy");
 	if (acl_smtp_vrfy)
 	  {
 	  g = string_catn(g, smtp_code, 3);
 	  g = string_catn(g, US"-VRFY\r\n", 7);
 	  }
+	GET_OPTION("acl_smtp_expn");
 	if (acl_smtp_expn)
 	  {
 	  g = string_catn(g, smtp_code, 3);
@@ -4502,6 +4516,7 @@ while (done <= 0)
 		  US"invalid data for AUTH");
 		goto COMMAND_LOOP;
 		}
+	      GET_OPTION("acl_smtp_mailauth");
 	      if (!acl_smtp_mailauth)
 		{
 		ignore_msg = US"client not authenticated";
@@ -4698,6 +4713,7 @@ while (done <= 0)
       when pipelining is not advertised, do another sync check in case the ACL
       delayed and the client started sending in the meantime. */
 
+      GET_OPTION("acl_smtp_mail");
       if (acl_smtp_mail)
 	{
 	rc = acl_check(ACL_WHERE_MAIL, NULL, acl_smtp_mail, &user_msg, &log_msg);
@@ -4953,10 +4969,13 @@ while (done <= 0)
       if (f.recipients_discarded)
 	rc = DISCARD;
       else
+	{
+	GET_OPTION("acl_smtp_rcpt");
 	if (  (rc = acl_check(ACL_WHERE_RCPT, recipient, acl_smtp_rcpt, &user_msg,
 		      &log_msg)) == OK
 	   && !f.smtp_in_pipelining_advertised && !check_sync())
 	  goto SYNC_FAILURE;
+	}
 
       /* The ACL was happy */
 
@@ -5119,6 +5138,7 @@ while (done <= 0)
 	since the ACL may have delayed.  To handle cutthrough delivery enforce a
 	dummy call to get the DATA command sent. */
 
+	GET_OPTION("acl_smtp_predata");
 	if (!acl_smtp_predata && cutthrough.cctx.sock < 0)
 	  rc = OK;
 	else
@@ -5177,6 +5197,7 @@ while (done <= 0)
 				    US"verify")))
 	  break;
 
+      GET_OPTION("acl_smtp_vrfy");
       if ((rc = acl_check(ACL_WHERE_VRFY, address, acl_smtp_vrfy,
 		    &user_msg, &log_msg)) != OK)
 	done = smtp_handle_acl_fail(ACL_WHERE_VRFY, rc, user_msg, log_msg);
@@ -5215,6 +5236,7 @@ while (done <= 0)
 
     case EXPN_CMD:
       HAD(SCH_EXPN);
+      GET_OPTION("acl_smtp_expn");
       rc = acl_check(ACL_WHERE_EXPN, NULL, acl_smtp_expn, &user_msg, &log_msg);
       if (rc != OK)
 	done = smtp_handle_acl_fail(ACL_WHERE_EXPN, rc, user_msg, log_msg);
@@ -5244,6 +5266,7 @@ while (done <= 0)
 
       /* Apply an ACL check if one is defined */
 
+      GET_OPTION("acl_smtp_starttls");
       if (  acl_smtp_starttls
 	 && (rc = acl_check(ACL_WHERE_STARTTLS, NULL, acl_smtp_starttls,
 		    &user_msg, &log_msg)) != OK
@@ -5362,6 +5385,7 @@ while (done <= 0)
 	case QUIT_CMD:
 	  f.smtp_in_quit = TRUE;
 	  user_msg = NULL;
+	  GET_OPTION("acl_smtp_quit");
 	  if (  acl_smtp_quit
 	     && ((rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, &user_msg,
 				&log_msg)) == ERROR))
@@ -5481,6 +5505,7 @@ while (done <= 0)
       log_write(L_etrn, LOG_MAIN, "ETRN %s received from %s", smtp_cmd_argument,
 	host_and_ident(FALSE));
 
+      GET_OPTION("acl_smtp_etrn");
       if ((rc = acl_check(ACL_WHERE_ETRN, NULL, acl_smtp_etrn,
 		  &user_msg, &log_msg)) != OK)
 	{
@@ -5497,6 +5522,7 @@ while (done <= 0)
       since that is strictly the only kind of ETRN that can be implemented
       according to the RFC. */
 
+      GET_OPTION("smtp_etrn_command");
       if (smtp_etrn_command)
 	{
 	uschar *error;
diff --git a/src/src/smtp_out.c b/src/src/smtp_out.c
index 62557072e..895b594fc 100644
--- a/src/src/smtp_out.c
+++ b/src/src/smtp_out.c
@@ -278,6 +278,7 @@ if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, US &on, sizeof(on)))
 /* Set DSCP value, if we can. For now, if we fail to set the value, we don't
 bomb out, just log it and continue in default traffic class. */
 
+GET_OPTION("dscp");
 if (dscp && dscp_lookup(dscp, sc->host_af, &dscp_level, &dscp_option, &dscp_value))
   {
   HDEBUG(D_transport|D_acl|D_v)
diff --git a/src/src/spf.c b/src/src/spf.c
index 2e3f861eb..cc36463e4 100644
--- a/src/src/spf.c
+++ b/src/src/spf.c
@@ -253,10 +253,13 @@ if (!(spf_server = SPF_server_new_dns(dc, debug)))
   DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n");
   return FALSE;
   }
-  /* Override the outdated explanation URL.
-  See https://www.mail-archive.com/mailop@mailop.org/msg08019.html
-  Used to work as "Please%_see%_http://www.open-spf.org/Why?id=%{S}&ip=%{C}&receiver=%{R}",
-  but is broken now (May 18th, 2020) */
+
+/* Override the outdated explanation URL.
+See https://www.mail-archive.com/mailop@mailop.org/msg08019.html
+Used to work as "Please%_see%_http://www.open-spf.org/Why?id=%{S}&ip=%{C}&receiver=%{R}",
+but is broken now (May 18th, 2020) */
+
+GET_OPTION("spf_smtp_comment_template");
 if (!(s = expand_string(spf_smtp_comment_template)))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "expansion of spf_smtp_comment_template failed");
 
diff --git a/src/src/transports/appendfile.c b/src/src/transports/appendfile.c
index ce52cc6ff..72c96f0d0 100644
--- a/src/src/transports/appendfile.c
+++ b/src/src/transports/appendfile.c
@@ -178,18 +178,24 @@ static int
 appendfile_transport_setup(transport_instance *tblock, address_item *addrlist,
   transport_feedback *dummy, uid_t uid, gid_t gid, uschar **errmsg)
 {
-appendfile_transport_options_block *ob =
+appendfile_transport_options_block * ob =
   (appendfile_transport_options_block *)(tblock->options_block);
-uschar *q = ob->quota;
+uschar * q;
 double default_value = 0.0;
 
 if (ob->expand_maildir_use_size_file)
-	ob->maildir_use_size_file = expand_check_condition(ob->expand_maildir_use_size_file,
+  {
+  GET_OPTION("maildir_use_size_file");
+  ob->maildir_use_size_file =
+    expand_check_condition(ob->expand_maildir_use_size_file,
 		US"`maildir_use_size_file` in transport", tblock->name);
+  }
 
 /* Loop for quota, quota_filecount, quota_warn_threshold, mailbox_size,
 mailbox_filecount */
 
+GET_OPTION("quota");
+q = ob->quota;
 for (int i = 0; i < 5; i++)
   {
   double d = default_value;
@@ -258,6 +264,7 @@ for (int i = 0; i < 5; i++)
 	which = US"quota";
       ob->quota_value = (off_t)d;
       ob->quota_no_check = no_check;
+      GET_OPTION("quota_filecount");
       q = ob->quota_filecount;
       break;
 
@@ -266,6 +273,7 @@ for (int i = 0; i < 5; i++)
 	which = US"quota_filecount";
       ob->quota_filecount_value = (int)d;
       ob->quota_filecount_no_check = no_check;
+      GET_OPTION("quota_warn_threshold");
       q = ob->quota_warn_threshold;
       break;
 
@@ -273,6 +281,7 @@ for (int i = 0; i < 5; i++)
       if (d >= 2.0*1024.0*1024.0*1024.0 && sizeof(off_t) <= 4)
 	  which = US"quota_warn_threshold";
       ob->quota_warn_threshold_value = (off_t)d;
+      GET_OPTION("mailbox_size");
       q = ob->mailbox_size_string;
       default_value = -1.0;
       break;
@@ -281,6 +290,7 @@ for (int i = 0; i < 5; i++)
       if (d >= 2.0*1024.0*1024.0*1024.0 && sizeof(off_t) <= 4)
 	which = US"mailbox_size";;
       ob->mailbox_size_value = (off_t)d;
+      GET_OPTION("mailbox_filecount");
       q = ob->mailbox_filecount_string;
       break;
 
@@ -399,10 +409,10 @@ if (ob->dirname)
   if (ob->maildir_format && ob->mailstore_format)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s transport:\n  "
       "only one of maildir and mailstore may be specified", tblock->name);
-  if (ob->quota_filecount != NULL && ob->quota == NULL)
+  if (ob->quota_filecount != NULL && !ob->quota)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s transport:\n  "
       "quota must be set if quota_filecount is set", tblock->name);
-  if (ob->quota_directory != NULL && ob->quota == NULL)
+  if (ob->quota_directory != NULL && !ob->quota)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s transport:\n  "
       "quota must be set if quota_directory is set", tblock->name);
   }
@@ -1221,6 +1231,7 @@ if (!fdname)
   {
   if (!(fdname = ob->filename))
     {
+    GET_OPTION("directory");
     fdname = ob->dirname;
     isdirectory = TRUE;
     }
@@ -2234,6 +2245,7 @@ else
 
     /* Use an explicitly configured directory if set */
 
+    GET_OPTION("quota_directory");
     if (ob->quota_directory)
       {
       if (!(check_path = expand_string(ob->quota_directory)))
@@ -2431,6 +2443,7 @@ else
     DEBUG(D_transport)
       debug_printf("delivering in maildir format in %s\n", path);
 
+    GET_OPTION("maildir_tag");
     nametag = ob->maildir_tag;
 
     /* Check that nametag expands successfully; a hard failure causes a panic
@@ -2567,9 +2580,10 @@ else
 
     /* Write the envelope file, then close it. */
 
+    GET_OPTION("mailstore_prefix");
     if (ob->mailstore_prefix)
       {
-      uschar *s = expand_string(ob->mailstore_prefix);
+      uschar * s = expand_string(ob->mailstore_prefix);
       if (!s)
         {
         if (!f.expand_string_forcedfail)
@@ -2595,9 +2609,10 @@ else
     for (address_item * taddr = addr; taddr; taddr = taddr->next)
       fprintf(env_file, "%s@%s\n", taddr->local_part, taddr->domain);
 
+    GET_OPTION("mailstore_suffix");
     if (ob->mailstore_suffix)
       {
-      uschar *s = expand_string(ob->mailstore_suffix);
+      uschar * s = expand_string(ob->mailstore_suffix);
       if (!s)
         {
         if (!f.expand_string_forcedfail)
@@ -2757,18 +2772,21 @@ transport_newlines = 0;
 
 /* Write any configured prefix text first */
 
-if (yield == OK && ob->message_prefix && *ob->message_prefix)
+if (yield == OK)
   {
-  uschar *prefix = expand_string(ob->message_prefix);
-  if (!prefix)
-    {
-    errno = ERRNO_EXPANDFAIL;
-    addr->transport_return = PANIC;
-    addr->message = string_sprintf("Expansion of \"%s\" (prefix for %s "
-      "transport) failed", ob->message_prefix, tblock->name);
-    yield = DEFER;
-    }
-  else if (!transport_write_string(fd, "%s", prefix)) yield = DEFER;
+  uschar * prefix = ob->message_prefix;
+  GET_OPTION("message_prefix");
+  if (prefix && *prefix)
+    if (!(prefix = expand_string(prefix)))
+      {
+      errno = ERRNO_EXPANDFAIL;
+      addr->transport_return = PANIC;
+      addr->message = string_sprintf("Expansion of \"%s\" (prefix for %s "
+	"transport) failed", ob->message_prefix, tblock->name);
+      yield = DEFER;
+      }
+    else if (!transport_write_string(fd, "%s", prefix))
+      yield = DEFER;
   }
 
 /* If the use_bsmtp option is on, we need to write SMTP prefix information. The
@@ -2820,18 +2838,21 @@ if (yield == OK)
 
 /* Now a configured suffix. */
 
-if (yield == OK && ob->message_suffix && *ob->message_suffix)
+if (yield == OK)
   {
-  uschar *suffix = expand_string(ob->message_suffix);
-  if (!suffix)
-    {
-    errno = ERRNO_EXPANDFAIL;
-    addr->transport_return = PANIC;
-    addr->message = string_sprintf("Expansion of \"%s\" (suffix for %s "
-      "transport) failed", ob->message_suffix, tblock->name);
-    yield = DEFER;
-    }
-  else if (!transport_write_string(fd, "%s", suffix)) yield = DEFER;
+  uschar * suffix = ob->message_suffix;
+  GET_OPTION("message_suffix");
+  if (suffix && *suffix)
+    if (!(suffix = expand_string(suffix)))
+      {
+      errno = ERRNO_EXPANDFAIL;
+      addr->transport_return = PANIC;
+      addr->message = string_sprintf("Expansion of \"%s\" (suffix for %s "
+	"transport) failed", ob->message_suffix, tblock->name);
+      yield = DEFER;
+      }
+    else if (!transport_write_string(fd, "%s", suffix))
+      yield = DEFER;
   }
 
 /* If batch smtp, write the terminating dot. */
diff --git a/src/src/transports/autoreply.c b/src/src/transports/autoreply.c
index fa884cec4..3755ba284 100644
--- a/src/src/transports/autoreply.c
+++ b/src/src/transports/autoreply.c
@@ -314,20 +314,21 @@ if (addr->reply)
   }
 else
   {
-  uschar *oncerepeat = ob->once_repeat;
+  uschar * oncerepeat;
 
   DEBUG(D_transport) debug_printf("taking data from transport\n");
-  from = ob->from;
-  reply_to = ob->reply_to;
-  to = ob->to;
-  cc = ob->cc;
-  bcc = ob->bcc;
-  subject = ob->subject;
-  headers = ob->headers;
-  text = ob->text;
-  file = ob->file;
-  logfile = ob->logfile;
-  oncelog = ob->oncelog;
+  GET_OPTION("once_repeat");	oncerepeat = ob->once_repeat;
+  GET_OPTION("from"); 		from = ob->from;
+  GET_OPTION("reply_to");	reply_to = ob->reply_to;
+  GET_OPTION("to");		to = ob->to;
+  GET_OPTION("cc");		cc = ob->cc;
+  GET_OPTION("bcc");		bcc = ob->bcc;
+  GET_OPTION("subject");	subject = ob->subject;
+  GET_OPTION("headers"); 	headers = ob->headers;
+  GET_OPTION("text");		text = ob->text;
+  GET_OPTION("file");		file = ob->file;
+  GET_OPTION("log");		logfile = ob->logfile;
+  GET_OPTION("once");		oncelog = ob->oncelog;
   file_expand = ob->file_expand;
   return_message = ob->return_message;
 
diff --git a/src/src/transports/pipe.c b/src/src/transports/pipe.c
index 64c70e719..157d2368f 100644
--- a/src/src/transports/pipe.c
+++ b/src/src/transports/pipe.c
@@ -315,6 +315,7 @@ argv = *argvptr;
 
 /* If allow_commands is set, see if the command is in the permitted list. */
 
+GET_OPTION("allow_commands");
 if (ob->allow_commands)
   {
   int sep = 0;
@@ -368,10 +369,11 @@ for it. */
 if (argv[0][0] != '/')
   {
   int sep = 0;
-  uschar *p;
-  const uschar *listptr = expand_string(ob->path);
+  uschar * p;
 
-  while ((p = string_nextinlist(&listptr, &sep, NULL, 0)))
+  GET_OPTION("path");
+  for (const uschar * listptr = expand_string(ob->path);
+      p = string_nextinlist(&listptr, &sep, NULL, 0); )
     {
     struct stat statbuf;
     sprintf(CS big_buffer, "%.256s/%.256s", p, argv[0]);
@@ -550,6 +552,7 @@ if (testflag(addr, af_pfr) && addr->local_part[0] == '|')
     {
     /* Enables expansion of $address_pipe into separate arguments */
     setflag(addr, af_force_command);
+    GET_OPTION("commsnd");
     cmd = ob->cmd;
     expand_arguments = TRUE;
     expand_fail = PANIC;
@@ -563,6 +566,7 @@ if (testflag(addr, af_pfr) && addr->local_part[0] == '|')
     }
 else
   {
+  GET_OPTION("commsnd");
   cmd = ob->cmd;
   expand_arguments = TRUE;
   expand_fail = PANIC;
@@ -650,6 +654,7 @@ else if (timezone_string && timezone_string[0])
 
 /* Add any requested items */
 
+GET_OPTION("environment");
 if (envlist)
   if (!(envlist = expand_cstring(envlist)))
     {
@@ -796,9 +801,10 @@ transport_count = 0;
 
 /* First write any configured prefix information */
 
+GET_OPTION("message_prefix");
 if (ob->message_prefix)
   {
-  uschar *prefix = expand_string(ob->message_prefix);
+  uschar * prefix = expand_string(ob->message_prefix);
   if (!prefix)
     {
     addr->transport_return = f.search_find_defer? DEFER : PANIC;
@@ -838,9 +844,10 @@ if (!transport_write_message(&tctx, 0))
 
 /* Now any configured suffix */
 
+GET_OPTION("message_suffix");
 if (ob->message_suffix)
   {
-  uschar *suffix = expand_string(ob->message_suffix);
+  uschar * suffix = expand_string(ob->message_suffix);
   if (!suffix)
     {
     addr->transport_return = f.search_find_defer? DEFER : PANIC;
diff --git a/src/src/transports/queuefile.c b/src/src/transports/queuefile.c
index b6511133c..21c98eeef 100644
--- a/src/src/transports/queuefile.c
+++ b/src/src/transports/queuefile.c
@@ -195,6 +195,7 @@ DEBUG(D_transport)
 # define O_NOFOLLOW 0
 #endif
 
+GET_OPTION("directory");
 if (!(dstdir = expand_string(ob->dirname)))
   {
   addr->message = string_sprintf("%s transport: failed to expand dirname option",
diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index 596a328c3..736592352 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -1088,6 +1088,7 @@ if (pending_BANNER)
   /*XXX EXPERIMENTAL_ESMTP_LIMITS ? */
 
 # ifndef DISABLE_TLS_RESUME
+  GET_OPTION("host_name_extract");
   s = ((smtp_transport_options_block *)sx->conn_args.ob)->host_name_extract;
   if (!s) s = HNE_DEFAULT;
   ehlo_response_lbserver(sx, s);
@@ -1769,6 +1770,7 @@ uschar * local_authenticated_sender = authenticated_sender;
     authenticated_sender, ob->authenticated_sender, f.smtp_authenticated?"Y":"N");
 #endif
 
+GET_OPTION("authenticated_sender");
 if (ob->authenticated_sender)
   {
   uschar * new = expand_string(ob->authenticated_sender);
@@ -2374,6 +2376,7 @@ PIPE_CONNECT_RETRY:
   For early-pipe, we are ok if binding to a local interface; otherwise (if
   $sending_ip_address is seen in helo_data) we disabled early-pipe above. */
 
+  GET_OPTION("helo_data");
   if (sx->helo_data)
     if (!(sx->helo_data = expand_string(sx->helo_data)))
       if (sx->verify)
@@ -2501,6 +2504,7 @@ goto SEND_QUIT;
     an LB.  Call this anyway, so that a dummy host_name_extract option value can
     force resumption attempts. */
 
+    GET_OPTION("host_name_extract");
     if (!(s = ob->host_name_extract)) s = US"never-LB";
     ehlo_response_lbserver(sx, s);
 # endif
@@ -2629,6 +2633,7 @@ goto SEND_QUIT;
 	}
 #endif
 #ifndef DISABLE_TLS_RESUME
+      GET_OPTION("host_name_extract");
       if (!(s = ob->host_name_extract)) s = HNE_DEFAULT;
       ehlo_response_lbserver(sx, s);
 #endif
@@ -2837,13 +2842,17 @@ if (tls_out.active.sock >= 0)
   {
   uschar * greeting_cmd;
 
-  if (!sx->helo_data && !(sx->helo_data = expand_string(ob->helo_data)))
+  if (!sx->helo_data)
     {
-    uschar *message = string_sprintf("failed to expand helo_data: %s",
-      expand_string_message);
-    set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE, &sx->delivery_start);
-    yield = DEFER;
-    goto SEND_QUIT;
+    GET_OPTION("helo_data");
+    if (!(sx->helo_data = expand_string(ob->helo_data)))
+      {
+      uschar *message = string_sprintf("failed to expand helo_data: %s",
+	expand_string_message);
+      set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE, &sx->delivery_start);
+      yield = DEFER;
+      goto SEND_QUIT;
+      }
     }
 
 #ifndef DISABLE_PIPE_CONNECT
@@ -3094,6 +3103,7 @@ if (sx->addrlist->prop.utf8_msg)
   /* If the transport sets a downconversion mode it overrides any set by ACL
   for the message. */
 
+  GET_OPTION("utf8_downconvert");
   if ((s = ob->utf8_downconvert))
     {
     if (!(s = expand_string(s)))
@@ -5565,8 +5575,9 @@ retry_non_continued:
 
     host_af = Ustrchr(host->address, ':') ? AF_INET6 : AF_INET;
       {
-      uschar * s = ob->interface;
-      if (s && *s)
+      uschar * s;
+      GET_OPTION("interface");
+      if ((s = ob->interface) && *s)
 	{
 	if (!smtp_get_interface(s, host_af, addrlist, &interface, tid))
 	  return FALSE;
diff --git a/src/src/transports/smtp_socks.c b/src/src/transports/smtp_socks.c
index 22ee74bd8..ac8d0f2da 100644
--- a/src/src/transports/smtp_socks.c
+++ b/src/src/transports/smtp_socks.c
@@ -229,6 +229,7 @@ blob early_data;
 if (!timeout) timeout = 24*60*60;	/* use 1 day for "indefinite" */
 tmo = time(NULL) + timeout;
 
+GET_OPTION("socks_proxy");
 if (!(proxy_list = expand_string(ob->socks_proxy)))
   {
   log_write(0, LOG_MAIN|LOG_PANIC, "Bad expansion for socks_proxy in %s",
diff --git a/src/src/verify.c b/src/src/verify.c
index bde660063..78923651c 100644
--- a/src/src/verify.c
+++ b/src/src/verify.c
@@ -368,6 +368,7 @@ if (addr->transport == cutthrough.addr.transport)
 
       host_af = Ustrchr(host->address, ':') ? AF_INET6 : AF_INET;
 
+      GET_OPTION("interface");
       if (  !smtp_get_interface(tf->interface, host_af, addr, &interface,
 	      US"callout")
 	 || !smtp_get_port(tf->port, addr, &port, US"callout")
@@ -579,10 +580,14 @@ else
   with a random local part, ensure that such a local part is available. If not,
   log the fact, but carry on without randomising. */
 
-  if (options & vopt_callout_random  &&  callout_random_local_part)
-    if (!(random_local_part = expand_string(callout_random_local_part)))
+  if (options & vopt_callout_random)
+    {
+    GET_OPTION("callout_random_local_part");
+    if (  callout_random_local_part
+       && !(random_local_part = expand_string(callout_random_local_part)))
       log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
         "callout_random_local_part: %s", expand_string_message);
+    }
 
   /* Compile regex' used by client-side smtp */
 
@@ -660,6 +665,7 @@ coding means skipping this whole loop and doing the append separately.  */
     deliver_domain = addr->domain;
     transport_name = addr->transport->name;
 
+    GET_OPTION("interface");
     if (  !smtp_get_interface(tf->interface, host_af, addr, &interface,
             US"callout")
        || !smtp_get_port(tf->port, addr, &port, US"callout")

commit 38965d80d70cacd0c944d32f7107549a3a272b35
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Thu Sep 28 11:10:13 2023 +0100

    Build: avoid compiling code for unused transports, routers, authenticators
    
    The smtp transport is not covered here; there are dependencies in
    other bits of the exim body.

diff --git a/src/src/auths/cram_md5.c b/src/src/auths/cram_md5.c
index 0761b7c6a..8f00ee498 100644
--- a/src/src/auths/cram_md5.c
+++ b/src/src/auths/cram_md5.c
@@ -13,15 +13,17 @@ in the MD5 computation functions, without their own stand-alone main
 program. */
 
 #ifdef STAND_ALONE
-#define CRAM_STAND_ALONE
-#include "md5.c"
+# define CRAM_STAND_ALONE
+# include "md5.c"
 
 
 /* This is the normal, non-stand-alone case */
 
 #else
-#include "../exim.h"
-#include "cram_md5.h"
+# include "../exim.h"
+
+# ifdef AUTH_CRAM_MD5
+#  include "cram_md5.h"
 
 /* Options specific to the cram_md5 authentication mechanism. */
 
@@ -49,7 +51,7 @@ auth_cram_md5_options_block auth_cram_md5_option_defaults = {
 };
 
 
-#ifdef MACRO_PREDEF
+#  ifdef MACRO_PREDEF
 
 /* Dummy values */
 void auth_cram_md5_init(auth_instance *ablock) {}
@@ -57,7 +59,7 @@ int auth_cram_md5_server(auth_instance *ablock, uschar *data) {return 0;}
 int auth_cram_md5_client(auth_instance *ablock, void *sx, int timeout,
     uschar *buffer, int buffsize) {return 0;}
 
-#else	/*!MACRO_PREDEF*/
+#  else	/*!MACRO_PREDEF*/
 
 
 /*************************************************
@@ -81,8 +83,9 @@ if (ob->client_secret != NULL)
   }
 }
 
-#endif	/*!MACRO_PREDEF*/
-#endif  /* STAND_ALONE */
+#  endif	/*!MACRO_PREDEF*/
+# endif		/*AUTH_CRAM_MD5*/
+#endif		/*!STAND_ALONE*/
 
 
 
@@ -154,7 +157,8 @@ md5_end(&base, md5secret, 16, digestptr);
 }
 
 
-#ifndef STAND_ALONE
+# ifndef STAND_ALONE
+#  ifdef AUTH_CRAM_MD5
 
 /*************************************************
 *             Server entry point                 *
@@ -329,7 +333,8 @@ if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", b64encode(CUS big_buffer,
 return smtp_read_response(sx, US buffer, buffsize, '2', timeout)
   ? OK : FAIL;
 }
-#endif  /* STAND_ALONE */
+#  endif  /*AUTH_CRAM_MD5*/
+# endif  /*!STAND_ALONE*/
 
 
 /*************************************************
@@ -338,7 +343,7 @@ return smtp_read_response(sx, US buffer, buffsize, '2', timeout)
 **************************************************
 *************************************************/
 
-#ifdef STAND_ALONE
+# ifdef STAND_ALONE
 
 int main(int argc, char **argv)
 {
@@ -355,7 +360,7 @@ printf("\n");
 return 0;
 }
 
-#endif
+# endif	/*STAND_ALONE*/
 
 #endif	/*!MACRO_PREDEF*/
 /* End of cram_md5.c */
diff --git a/src/src/auths/dovecot.c b/src/src/auths/dovecot.c
index 377c09205..7f437fa0d 100644
--- a/src/src/auths/dovecot.c
+++ b/src/src/auths/dovecot.c
@@ -23,6 +23,8 @@ because using C buffered I/O gives problems on some operating systems. PH */
  */
 
 #include "../exim.h"
+
+#ifdef AUTH_DOVECOT	/* Remainder of file */
 #include "dovecot.h"
 
 #define VERSION_MAJOR  1
@@ -533,4 +535,6 @@ return ret;
 }
 
 
-#endif   /*!MACRO_PREDEF*/
+#endif	/*!MACRO_PREDEF*/
+#endif	/*AUTH_DOVECOT*/
+/* end of auths/dovecot.c */
diff --git a/src/src/auths/external.c b/src/src/auths/external.c
index a89a50486..6ae5833af 100644
--- a/src/src/auths/external.c
+++ b/src/src/auths/external.c
@@ -14,6 +14,8 @@ method defined in RFC 4422 Appendix A.
 
 
 #include "../exim.h"
+
+#ifdef AUTH_EXTERNAL	/* Remainder of file */
 #include "external.h"
 
 /* Options specific to the external authentication mechanism. */
@@ -153,5 +155,6 @@ return OK;
 
 
 
-#endif   /*!MACRO_PREDEF*/
+#endif	/*!MACRO_PREDEF*/
+#endif	/*AUTH_EXTERNAL*/
 /* End of external.c */
diff --git a/src/src/auths/plaintext.c b/src/src/auths/plaintext.c
index 391e629f9..814896047 100644
--- a/src/src/auths/plaintext.c
+++ b/src/src/auths/plaintext.c
@@ -8,6 +8,8 @@
 /* SPDX-License-Identifier: GPL-2.0-or-later */
 
 #include "../exim.h"
+
+#ifdef AUTH_PLAINTEXT	/* Remainder of file */
 #include "plaintext.h"
 
 
@@ -176,5 +178,6 @@ while ((s = string_nextinlist(&text, &sep, NULL, 0)))
 return FAIL;
 }
 
-#endif   /*!MACRO_PREDEF*/
+#endif	/*!MACRO_PREDEF*/
+#endif	/*AUTH_PLAINTEST*/
 /* End of plaintext.c */
diff --git a/src/src/auths/spa.c b/src/src/auths/spa.c
index 0d4262ec5..51418c4ed 100644
--- a/src/src/auths/spa.c
+++ b/src/src/auths/spa.c
@@ -34,6 +34,8 @@ References:
 
 
 #include "../exim.h"
+
+#ifdef AUTH_SPA		/* Remainder of file */
 #include "spa.h"
 
 /* #define DEBUG_SPA */
@@ -372,5 +374,6 @@ if (errno != 0 || buffer[0] != '3')
 return FAIL;
 }
 
-#endif   /*!MACRO_PREDEF*/
+#endif	/*!MACRO_PREDEF*/
+#endif	/*AUTH_SPA*/
 /* End of spa.c */
diff --git a/src/src/auths/tls.c b/src/src/auths/tls.c
index 72ad56c4e..005032f57 100644
--- a/src/src/auths/tls.c
+++ b/src/src/auths/tls.c
@@ -12,6 +12,8 @@ a server to verify a client SSL certificate
 
 
 #include "../exim.h"
+
+#ifdef AUTH_TLS		/* Remainder of file */
 #include "tls.h"
 
 /* Options specific to the tls authentication mechanism. */
@@ -91,5 +93,6 @@ return auth_check_serv_cond(ablock);
 }
 
 
-#endif   /*!MACRO_PREDEF*/
+#endif	/*!MACRO_PREDEF*/
+#endif	/*AUTH_TLS*/
 /* End of tls.c */
diff --git a/src/src/routers/accept.c b/src/src/routers/accept.c
index 8b72bfac1..9766e5e10 100644
--- a/src/src/routers/accept.c
+++ b/src/src/routers/accept.c
@@ -9,6 +9,8 @@
 
 
 #include "../exim.h"
+
+#ifdef ROUTER_ACCEPT		/* Remainder of file */
 #include "rf_functions.h"
 #include "accept.h"
 
@@ -137,4 +139,5 @@ return rf_queue_add(addr, addr_local, addr_remote, rblock, pw)? OK : DEFER;
 }
 
 #endif	/*!MACRO_PREDEF*/
+#endif	/*ROUTER_ACCEPT*/
 /* End of routers/accept.c */
diff --git a/src/src/routers/dnslookup.c b/src/src/routers/dnslookup.c
index 671e8c93f..33382671f 100644
--- a/src/src/routers/dnslookup.c
+++ b/src/src/routers/dnslookup.c
@@ -8,6 +8,8 @@
 /* SPDX-License-Identifier: GPL-2.0-or-later */
 
 #include "../exim.h"
+
+#ifdef ROUTER_DNSLOOKUP		/* Remainder of file */
 #include "rf_functions.h"
 #include "dnslookup.h"
 
@@ -469,7 +471,8 @@ return rf_queue_add(addr, addr_local, addr_remote, rblock, pw)?
   OK : DEFER;
 }
 
-#endif   /*!MACRO_PREDEF*/
+#endif	/*!MACRO_PREDEF*/
+#endif	/*ROUTER_DNSLOOKUP
 /* End of routers/dnslookup.c */
 /* vi: aw ai sw=2
 */
diff --git a/src/src/routers/ipliteral.c b/src/src/routers/ipliteral.c
index 1297b97f2..16e096b40 100644
--- a/src/src/routers/ipliteral.c
+++ b/src/src/routers/ipliteral.c
@@ -9,6 +9,8 @@
 
 
 #include "../exim.h"
+
+#ifdef ROUTER_IPLITERAL		/* Remainder of file */
 #include "rf_functions.h"
 #include "ipliteral.h"
 
@@ -199,5 +201,6 @@ return rf_queue_add(addr, addr_local, addr_remote, rblock, pw)?
   OK : DEFER;
 }
 
-#endif   /*!MACRO_PREDEF*/
+#endif	/*!MACRO_PREDEF*/
+#endif	/*ROUTER_IPLITERAL*/
 /* End of routers/ipliteral.c */
diff --git a/src/src/routers/iplookup.c b/src/src/routers/iplookup.c
index f9966bcc0..b88212e5c 100644
--- a/src/src/routers/iplookup.c
+++ b/src/src/routers/iplookup.c
@@ -9,6 +9,8 @@
 
 
 #include "../exim.h"
+
+#ifdef ROUTER_IPLOOKUP		/* Remainder of file */
 #include "rf_functions.h"
 #include "iplookup.h"
 
@@ -414,5 +416,6 @@ if (rc != OK) return rc;
 return OK;
 }
 
-#endif   /*!MACRO_PREDEF*/
+#endif	/*!MACRO_PREDEF*/
+#endif	/*ROUTER_IPLOOKUP*/
 /* End of routers/iplookup.c */
diff --git a/src/src/routers/manualroute.c b/src/src/routers/manualroute.c
index 200e0206e..45378ce3d 100644
--- a/src/src/routers/manualroute.c
+++ b/src/src/routers/manualroute.c
@@ -9,6 +9,8 @@
 
 
 #include "../exim.h"
+
+#ifdef ROUTER_MANUALROUTE
 #include "rf_functions.h"
 #include "manualroute.h"
 
@@ -488,5 +490,6 @@ addr->transport = transport;
 return OK;
 }
 
-#endif   /*!MACRO_PREDEF*/
+#endif	/*!MACRO_PREDEF*/
+#endif	/*ROUTER_MANUALROUTE*/
 /* End of routers/manualroute.c */
diff --git a/src/src/routers/queryprogram.c b/src/src/routers/queryprogram.c
index dd5e24b93..b1d736f94 100644
--- a/src/src/routers/queryprogram.c
+++ b/src/src/routers/queryprogram.c
@@ -8,6 +8,8 @@
 /* SPDX-License-Identifier: GPL-2.0-or-later */
 
 #include "../exim.h"
+
+#ifdef ROUTER_QUERYPROGRAM	/* Remainder of file */
 #include "rf_functions.h"
 #include "queryprogram.h"
 
@@ -535,5 +537,6 @@ addr->prop = addr_prop;
 return rf_queue_add(addr, addr_local, addr_remote, rblock, pw) ? OK : DEFER;
 }
 
-#endif   /*!MACRO_PREDEF*/
+#endif	/*!MACRO_PREDEF*/
+#endif	/*ROUTER_QUERYPROGRAM*/
 /* End of routers/queryprogram.c */
diff --git a/src/src/routers/redirect.c b/src/src/routers/redirect.c
index 6b27c82b0..37b5f5e24 100644
--- a/src/src/routers/redirect.c
+++ b/src/src/routers/redirect.c
@@ -9,6 +9,8 @@
 
 
 #include "../exim.h"
+
+#ifdef ROUTER_REDIRECT	/* Remainder of file */
 #include "rf_functions.h"
 #include "redirect.h"
 
@@ -807,4 +809,5 @@ return yield;
 }
 
 #endif   /*!MACRO_PREDEF*/
+#endif	/*ROUTER_REDIRECT*/
 /* End of routers/redirect.c */
diff --git a/src/src/transports/appendfile.c b/src/src/transports/appendfile.c
index 72c96f0d0..0279659f2 100644
--- a/src/src/transports/appendfile.c
+++ b/src/src/transports/appendfile.c
@@ -9,6 +9,8 @@
 
 
 #include "../exim.h"
+
+#ifdef TRANSPORT_APPENDFILE	/* Remainder of file */
 #include "appendfile.h"
 
 #ifdef SUPPORT_MAILDIR
@@ -3326,4 +3328,5 @@ ret_panic:
 }
 
 #endif	/*!MACRO_PREDEF*/
+#endif	/*TRANSPORT_APPENDFILE*/
 /* End of transport/appendfile.c */
diff --git a/src/src/transports/autoreply.c b/src/src/transports/autoreply.c
index 3755ba284..2f2fe1e16 100644
--- a/src/src/transports/autoreply.c
+++ b/src/src/transports/autoreply.c
@@ -9,6 +9,8 @@
 
 
 #include "../exim.h"
+
+#ifdef TRANSPORT_AUTOREPLY	/* Remainder of file */
 #include "autoreply.h"
 
 
@@ -820,4 +822,5 @@ return FALSE;
 }
 
 #endif	/*!MACRO_PREDEF*/
+#endif	/*TRANSPORT_AUTOREPOL*/
 /* End of transport/autoreply.c */
diff --git a/src/src/transports/lmtp.c b/src/src/transports/lmtp.c
index f3f908836..eff0dc79d 100644
--- a/src/src/transports/lmtp.c
+++ b/src/src/transports/lmtp.c
@@ -9,6 +9,8 @@
 
 
 #include "../exim.h"
+#ifdef TRANSPORT_LMTP		/* Remainder of file */
+
 #include "lmtp.h"
 
 #define PENDING_OK 256
@@ -807,4 +809,5 @@ MINUS_N:
 }
 
 #endif	/*!MACRO_PREDEF*/
+#endif	/*TRANSPORT_LMTP*/
 /* End of transport/lmtp.c */
diff --git a/src/src/transports/pipe.c b/src/src/transports/pipe.c
index 157d2368f..60a20c8fe 100644
--- a/src/src/transports/pipe.c
+++ b/src/src/transports/pipe.c
@@ -9,6 +9,8 @@
 
 
 #include "../exim.h"
+
+#ifdef TRANSPORT_PIPE	/* Remainder of file */
 #include "pipe.h"
 
 #ifdef HAVE_SETCLASSRESOURCES
@@ -1131,4 +1133,5 @@ return FALSE;
 }
 
 #endif	/*!MACRO_PREDEF*/
+#endif	/*TRASPORT_PIPE*/
 /* End of transport/pipe.c */

commit 46ad960434683b3e15eb17edf3b2d748c191fbc4
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sat Dec 23 17:44:31 2023 +0000

    tidying

diff --git a/src/src/daemon.c b/src/src/daemon.c
index 3c63c6368..88114d648 100644
--- a/src/src/daemon.c
+++ b/src/src/daemon.c
@@ -1493,7 +1493,7 @@ if (is_multiple_qrun())				/* we are managing periodic runs */
       }
     else
 #endif
-      /* Normal periodic runL in order of run priority, find the first queue
+      /* Normal periodic run: in order of run priority, find the first queue
       for which we can start a runner */
 
       for (q = qrunners; q; q = q->next)

commit f2738aab2d72569b6d47b788099f6ebab701b2b2
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Wed Feb 28 13:46:56 2024 +0000

    Lookups: avoid leaking user/passwd from server spec to log.  Bug 3066

diff --git a/src/src/acl.c b/src/src/acl.c
index ecba07b25..f4d65464e 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -1146,9 +1146,9 @@ Returns:         nothing
 */
 
 static void
-acl_warn(int where, uschar *user_message, uschar *log_message)
+acl_warn(int where, uschar * user_message, uschar * log_message)
 {
-if (log_message != NULL && log_message != user_message)
+if (log_message && log_message != user_message)
   {
   uschar *text;
   string_item *logged;
@@ -1159,9 +1159,9 @@ if (log_message != NULL && log_message != user_message)
   /* If a sender verification has failed, and the log message is "sender verify
   failed", add the failure message. */
 
-  if (sender_verified_failed != NULL &&
-      sender_verified_failed->message != NULL &&
-      strcmpic(log_message, US"sender verify failed") == 0)
+  if (  sender_verified_failed
+     && sender_verified_failed->message
+     && strcmpic(log_message, US"sender verify failed") == 0)
     text = string_sprintf("%s: %s", text, sender_verified_failed->message);
 
   /* Search previously logged warnings. They are kept in malloc
diff --git a/src/src/lookups/lf_sqlperform.c b/src/src/lookups/lf_sqlperform.c
index d5bb8d297..0f3984ba7 100644
--- a/src/src/lookups/lf_sqlperform.c
+++ b/src/src/lookups/lf_sqlperform.c
@@ -13,6 +13,15 @@
 
 
 
+static int
+server_len_for_logging(const uschar * server)
+{
+const uschar * s = Ustrchr(server, '/');
+if (!s) return 64;
+if (!(s = Ustrchr(s+1, '/'))) return 64;
+return (int) (s - server);
+}
+
 /*************************************************
 *    Call SQL server(s) to run an actual query   *
 *************************************************/
@@ -30,6 +39,7 @@ Arguments:
   result         where to pass back the result
   errmsg         where to pass back an error message
   do_cache       to be set zero if data is changed
+  opts		 options (which suffixed the lookup name, minus cache-control ones) or NULL
   func           the lookup function to call
 
 Returns:         the return from the lookup function, or DEFER
@@ -42,21 +52,19 @@ lf_sqlperform(const uschar *name, const uschar *optionname,
   int(*fn)(const uschar *, uschar *, uschar **, uschar **, BOOL *, uint *, const uschar *))
 {
 int rc;
-uschar *server;
+uschar * server;
 BOOL defer_break = FALSE;
 
 DEBUG(D_lookup) debug_printf_indent("%s query: \"%s\" opts '%s'\n", name, query, opts);
 
-/* Handle queries that do have server information at the start. */
+/* Handle queries that do have server information at the start (old style). */
 
 if (Ustrncmp(query, "servers", 7) == 0)
   {
   int qsep = 0;
-  const uschar *s, *ss;
-  const uschar *qserverlist;
-  uschar *qserver;
+  const uschar * s, * ss, * qserverlist;
 
-  log_write(0, LOG_MAIN|LOG_CONFIG_IN, "WARNING: obslete syntax used for lookup\n");
+  log_write(0, LOG_MAIN|LOG_CONFIG_IN, "WARNING: obsolete syntax used for lookup");
 
   s = query + 7;
   skip_whitespace(&s);
@@ -83,35 +91,37 @@ if (Ustrncmp(query, "servers", 7) == 0)
     }
 
   qserverlist = string_sprintf("%.*s", (int)(ss - s), s);
+  query = ss + 1;
 
-  while ((qserver = string_nextinlist(&qserverlist, &qsep, NULL, 0)))
+  for (uschar * qsrv; qsrv = string_nextinlist(&qserverlist, &qsep, NULL, 0); )
     {
-    if (Ustrchr(qserver, '/'))
-      server = qserver;
+    if (Ustrchr(qsrv, '/'))
+      server = qsrv;			/* full server spec */
     else
-      {
-      int len = Ustrlen(qserver);
+      {					/* only name; search in option list */
+      int len = Ustrlen(qsrv);
       const uschar * serverlist = optserverlist;
 
       for (int sep = 0; server = string_nextinlist(&serverlist, &sep, NULL, 0);)
-        if (Ustrncmp(server, qserver, len) == 0 && server[len] == '/')
+        if (Ustrncmp(server, qsrv, len) == 0 && server[len] == '/')
           break;
 
       if (!server)
         {
-        *errmsg = string_sprintf("%s server \"%s\" not found in %s", name,
-          qserver, optionname);
+        *errmsg = string_sprintf("%s server \"%.*s\" not found in %s",
+	  name, server_len_for_logging(qsrv), qsrv, optionname);
         return DEFER;
         }
       }
 
     if (is_tainted(server))
       {
-      *errmsg = string_sprintf("%s server \"%s\" is tainted", name, server);
+      *errmsg = string_sprintf("%s server \"%.*s\" is tainted",
+	name, server_len_for_logging(server), server);
       return DEFER;
       }
 
-    rc = (*fn)(ss+1, server, result, errmsg, &defer_break, do_cache, opts);
+    rc = (*fn)(query, server, result, errmsg, &defer_break, do_cache, opts);
     if (rc != DEFER || defer_break) return rc;
     }
   }
@@ -139,7 +149,7 @@ else
     *errmsg = string_sprintf("no %s servers defined (%s option)", name,
       optionname);
   else
-    for (int d = 0; (server = string_nextinlist(&serverlist, &d, NULL, 0)); )
+    for (int d = 0; server = string_nextinlist(&serverlist, &d, NULL, 0); )
       {
       /* If not a full spec assume from options; scan main list for matching
       hostname */
@@ -163,7 +173,8 @@ else
 
       if (is_tainted(server))
         {
-        *errmsg = string_sprintf("%s server \"%s\" is tainted", name, server);
+        *errmsg = string_sprintf("%s server \"%.*s\" is tainted",
+	  name, server_len_for_logging(server), server);
         return DEFER;
         }
 
@@ -176,3 +187,5 @@ return DEFER;
 }
 
 /* End of lf_sqlperform.c */
+/* vi: aw ai sw=2
+*/
diff --git a/src/src/receive.c b/src/src/receive.c
index abac05c56..a56ff473e 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -290,12 +290,11 @@ Returns:       FALSE if there isn't enough space, or if the information cannot
 BOOL
 receive_check_fs(int msg_size)
 {
-int_eximarith_t space;
 int inodes;
 
 if (check_spool_space > 0 || msg_size > 0 || check_spool_inodes > 0)
   {
-  space = receive_statvfs(TRUE, &inodes);
+  int_eximarith_t space = receive_statvfs(TRUE, &inodes);
 
   DEBUG(D_receive)
     debug_printf("spool directory space = " PR_EXIM_ARITH "K inodes = %d "
@@ -313,7 +312,7 @@ if (check_spool_space > 0 || msg_size > 0 || check_spool_inodes > 0)
 
 if (check_log_space > 0 || check_log_inodes > 0)
   {
-  space = receive_statvfs(FALSE, &inodes);
+  int_eximarith_t space = receive_statvfs(FALSE, &inodes);
 
   DEBUG(D_receive)
     debug_printf("log directory space = " PR_EXIM_ARITH "K inodes = %d "
diff --git a/src/src/search.c b/src/src/search.c
index 52dcab2b7..62416da76 100644
--- a/src/src/search.c
+++ b/src/src/search.c
@@ -590,35 +590,48 @@ else
   is either untainted or properly quoted for the lookup type.
 
   XXX Should we this move into lf_sqlperform() ?  The server-taint check is there.
+  Also it already knows about looking for a "servers" spec in the query string.
+  Passing search_type down that far is an issue.
   */
 
   if (  !filename && lookup_list[search_type]->quote
      && is_tainted(keystring) && !is_quoted_like(keystring, search_type))
     {
-    uschar * s = acl_current_verb();
-    if (!s) s = authenticator_current_name();	/* must be before transport */
-    if (!s) s = transport_current_name();	/* must be before router */
-    if (!s) s = router_current_name();	/* GCC ?: would be good, but not in clang */
-    if (!s) s = US"";
+    const uschar * ks = keystring;
+    uschar * loc = acl_current_verb();
+    if (!loc) loc = authenticator_current_name();	/* must be before transport */
+    if (!loc) loc = transport_current_name();		/* must be before router */
+    if (!loc) loc = router_current_name();		/* GCC ?: would be good, but not in clang */
+    if (!loc) loc = US"";
+
+    if (Ustrncmp(ks, "servers", 7) == 0)	/* Avoid logging server/password */
+      if ((ks = Ustrchr(keystring, ';')))
+	while (isspace(*++ks))
+	  ;
+      else
+	ks = US"";
+
 #ifdef enforce_quote_protection_notyet
     search_error_message = string_sprintf(
       "tainted search query is not properly quoted%s: %s%s",
-      s, keystring);
+      loc, ks);
     f.search_find_defer = TRUE;
+    goto out;
 #else
-     {
-      int q = quoter_for_address(keystring);
-      /* If we're called from a transport, no privs to open the paniclog;
-      the logging punts to using stderr - and that seems to stop the debug
-      stream. */
-      log_write(0,
-	transport_name ? LOG_MAIN : LOG_MAIN|LOG_PANIC,
-	"tainted search query is not properly quoted%s: %s", s, keystring);
-
-      DEBUG(D_lookup) debug_printf_indent("search_type %d (%s) quoting %d (%s)\n",
+    /* If we're called from a transport, no privs to open the paniclog;
+    the logging punts to using stderr - and that seems to stop the debug
+    stream. */
+    log_write(0,
+      transport_name ? LOG_MAIN : LOG_MAIN|LOG_PANIC,
+      "tainted search query is not properly quoted%s: %s", loc, ks);
+
+    DEBUG(D_lookup)
+      {
+      int q = quoter_for_address(ks);
+      debug_printf_indent("search_type %d (%s) quoting %d (%s)\n",
 	search_type, lookup_list[search_type]->name,
 	q, is_real_quoter(q) ? lookup_list[q]->name : US"none");
-     }
+      }
 #endif
     }
 
@@ -669,6 +682,7 @@ pointer to NULL here, because we cannot release the store at this stage. */
     }
   }
 
+out:
 DEBUG(D_lookup)
   {
   if (data)

commit 443245384239776d29f5386050f56247c7404c1e
Author: Fabrice Fontaine <fontaine.fabrice@gmail.com>
Date:   Thu Feb 29 19:01:49 2024 +0000

    LibreSSL: fix build for 3.5.0+    Bug 3074

diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c
index 237303ba9..e6a1c6fa4 100644
--- a/src/src/tls-openssl.c
+++ b/src/src/tls-openssl.c
@@ -97,6 +97,9 @@ change this guard and punt the issue for a while longer. */
 #if LIBRESSL_VERSION_NUMBER >= 0x3040000fL
 # define EXIM_HAVE_OPENSSL_CIPHER_GET_ID
 #endif
+#if LIBRESSL_VERSION_NUMBER >= 0x3050000fL
+# define EXIM_HAVE_OPENSSL_OCSP_RESP_GET0_CERTS
+#endif
 
 #if !defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER >= 0x030000000L)
 # define EXIM_HAVE_EXPORT_CHNL_BNGNG

commit f9609304cd7ffe553e3da325287973a0b2a4446c
Author: Heiko Schlichting <heiko@fu-berlin.de>
Date:   Thu Feb 29 19:26:48 2024 +0000

    Fix crash on empty -oMt argument.  Bug 3070

diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index ff768e94c..f62d6fbf2 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -1352,7 +1352,7 @@ if (host_checking)
   g = string_cat(g, hostname);
 
 else if (f.sender_host_unknown || f.sender_host_notsocket)
-  g = string_cat(g, sender_ident);
+  g = string_cat(g, sender_ident ? sender_ident : US"NULL");
 
 else if (f.is_inetd)
   g = string_append(g, 2, hostname, US" (via inetd)");

commit d945e180cbe2b26392fb21f7ae4dd2ccc603a81b
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Mar 8 01:59:34 2024 +0000

    Debug: expansions: refactor ascii-art/UTF8; mark up space & nl

diff --git a/src/src/expand.c b/src/src/expand.c
index 6be5cca0e..a916eee64 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -4496,30 +4496,17 @@ return yield;
 /************************************************/
 static void
 debug_expansion_interim(const uschar * what, const uschar * value, int nchar,
-  BOOL skipping)
+  esi_flags flags)
 {
-DEBUG(D_noutf8)
-  debug_printf_indent("|");
-else
-  debug_printf_indent(UTF8_VERT_RIGHT);
+debug_printf_indent("%V", "K");
 
 for (int fill = 11 - Ustrlen(what); fill > 0; fill--)
-  DEBUG(D_noutf8)
-    debug_printf("-");
-  else
-    debug_printf(UTF8_HORIZ);
+  debug_printf("%V", "-");
 
-debug_printf("%s: %.*s\n", what, nchar, value);
+debug_printf("%s: %.*W\n", what, nchar, value);
 if (is_tainted(value))
-  {
-  DEBUG(D_noutf8)
-    debug_printf_indent("%s     \\__", skipping ? "|     " : "      ");
-  else
-    debug_printf_indent("%s",
-      skipping
-      ? UTF8_VERT "             " : "           " UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ);
-  debug_printf("(tainted)\n");
-  }
+  debug_printf_indent("%V          %V(tainted)\n",
+    flags & ESI_SKIPPING ? "|" : " ", "\\__");
 }
 
 
@@ -4618,17 +4605,10 @@ while (*s)
 
   DEBUG(D_expand)
     {
-    DEBUG(D_noutf8)
-      debug_printf_indent("%c%s: %s\n",
-	first ? '/' : '|',
-	flags & ESI_SKIPPING ? "---scanning" : "considering", s);
-    else
-      debug_printf_indent("%s%s: %s\n",
-	first ? UTF8_DOWN_RIGHT : UTF8_VERT_RIGHT,
-	flags & ESI_SKIPPING
-	? UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ "scanning"
-	: "considering",
-	s);
+    debug_printf_indent("%V%V%s: %W\n",
+      first ? "/" : "K",
+      flags & ESI_SKIPPING ? "---" : "",
+      flags & ESI_SKIPPING ? "scanning" : "considering", s);
     first = FALSE;
     }
 
@@ -4651,21 +4631,20 @@ while (*s)
       for (s = t; *s ; s++) if (*s == '\\' && s[1] == 'N') break;
 
       DEBUG(D_expand)
-	debug_expansion_interim(US"protected", t, (int)(s - t), !!(flags & ESI_SKIPPING));
-      yield = string_catn(yield, t, s - t);
+	debug_expansion_interim(US"protected", t, (int)(s - t), flags);
+      if (!(flags & ESI_SKIPPING))
+	yield = string_catn(yield, t, s - t);
       if (*s) s += 2;
       }
     else
       {
       uschar ch[1];
       DEBUG(D_expand)
-	DEBUG(D_noutf8)
-	  debug_printf_indent("|backslashed: '\\%c'\n", s[1]);
-	else
-	  debug_printf_indent(UTF8_VERT_RIGHT "backslashed: '\\%c'\n", s[1]);
+	debug_printf_indent("%Vbackslashed: '\\%c'\n", "K", s[1]);
       ch[0] = string_interpret_escape(&s);
+      if (!(flags & ESI_SKIPPING))
+	yield = string_catn(yield, ch, 1);
       s++;
-      yield = string_catn(yield, ch, 1);
       }
     continue;
     }
@@ -4682,9 +4661,10 @@ while (*s)
     for (const uschar * t = s+1;
 	*t && *t != '$' && *t != '}' && *t != '\\'; t++) i++;
 
-    DEBUG(D_expand) debug_expansion_interim(US"text", s, i, !!(flags & ESI_SKIPPING));
+    DEBUG(D_expand) debug_expansion_interim(US"text", s, i, flags);
 
-    yield = string_catn(yield, s, i);
+    if (!(flags & ESI_SKIPPING))
+      yield = string_catn(yield, s, i);
     s += i;
     continue;
     }
@@ -4710,15 +4690,16 @@ while (*s)
     /* If this is the first thing to be expanded, release the pre-allocated
     buffer. */
 
-    if (!yield)
-      g = store_get(sizeof(gstring), GET_UNTAINTED);
-    else if (yield->ptr == 0)
-      {
-      if (resetok) reset_point = store_reset(reset_point);
-      yield = NULL;
-      reset_point = store_mark();
-      g = store_get(sizeof(gstring), GET_UNTAINTED);	/* alloc _before_ calling find_variable() */
-      }
+    if (!(flags & ESI_SKIPPING))
+      if (!yield)
+	g = store_get(sizeof(gstring), GET_UNTAINTED);
+      else if (yield->ptr == 0)
+	{
+	if (resetok) reset_point = store_reset(reset_point);
+	yield = NULL;
+	reset_point = store_mark();
+	g = store_get(sizeof(gstring), GET_UNTAINTED);	/* alloc _before_ calling find_variable() */
+	}
 
     /* Header */
 
@@ -4767,16 +4748,17 @@ while (*s)
     reset in the middle of the buffer will make it inaccessible. */
 
     len = Ustrlen(value);
-    DEBUG(D_expand) debug_expansion_interim(US"value", value, len, !!(flags & ESI_SKIPPING));
-    if (!yield && newsize != 0)
-      {
-      yield = g;
-      yield->size = newsize;
-      yield->ptr = len;
-      yield->s = US value; /* known to be in new store i.e. a copy, so deconst safe */
-      }
-    else
-      yield = string_catn(yield, value, len);
+    DEBUG(D_expand) debug_expansion_interim(US"value", value, len, flags);
+    if (!(flags & ESI_SKIPPING))
+      if (!yield && newsize != 0)
+	{
+	yield = g;
+	yield->size = newsize;
+	yield->ptr = len;
+	yield->s = US value; /* known to be in new store i.e. a copy, so deconst safe */
+	}
+      else
+	yield = string_catn(yield, value, len);
 
     continue;
     }
@@ -4787,8 +4769,9 @@ while (*s)
     s = read_cnumber(&n, s);
     if (n >= 0 && n <= expand_nmax)
       {
-      DEBUG(D_expand) debug_expansion_interim(US"value", expand_nstring[n], expand_nlength[n], !!(flags & ESI_SKIPPING));
-      yield = string_catn(yield, expand_nstring[n], expand_nlength[n]);
+      DEBUG(D_expand) debug_expansion_interim(US"value", expand_nstring[n], expand_nlength[n], flags);
+      if (!(flags & ESI_SKIPPING))
+	yield = string_catn(yield, expand_nstring[n], expand_nlength[n]);
       }
     continue;
     }
@@ -4815,8 +4798,9 @@ while (*s)
       }
     if (n >= 0 && n <= expand_nmax)
       {
-      DEBUG(D_expand) debug_expansion_interim(US"value", expand_nstring[n], expand_nlength[n], !!(flags & ESI_SKIPPING));
-      yield = string_catn(yield, expand_nstring[n], expand_nlength[n]);
+      DEBUG(D_expand) debug_expansion_interim(US"value", expand_nstring[n], expand_nlength[n], flags);
+      if (!(flags & ESI_SKIPPING))
+	yield = string_catn(yield, expand_nstring[n], expand_nlength[n]);
       }
     continue;
     }
@@ -4941,9 +4925,9 @@ while (*s)
 
       DEBUG(D_expand)
 	{
-	debug_expansion_interim(US"condition", s, (int)(next_s - s), !!(flags & ESI_SKIPPING));
+	debug_expansion_interim(US"condition", s, (int)(next_s - s), flags);
 	debug_expansion_interim(US"result",
-	  cond ? US"true" : US"false", cond ? 4 : 5, !!(flags & ESI_SKIPPING));
+	  cond ? US"true" : US"false", cond ? 4 : 5, flags);
 	}
 
       s = next_s;
@@ -7162,7 +7146,7 @@ while (*s)
     if (yield && (expansion_start > 0 || *s))
       debug_expansion_interim(US"item-res",
 	  yield->s + expansion_start, yield->ptr - expansion_start,
-	  !!(flags & ESI_SKIPPING));
+	  flags);
   continue;
 
 NOT_ITEM: ;
@@ -8347,27 +8331,13 @@ NOT_ITEM: ;
 	int i = gstring_length(yield) - expansion_start;
 	BOOL tainted = is_tainted(s);
 
-	DEBUG(D_noutf8)
-	  {
-	  debug_printf_indent("|-----op-res: %.*s\n", i, s);
-	  if (tainted)
-	    {
-	    debug_printf_indent("%s     \\__", flags & ESI_SKIPPING ? "|     " : "      ");
-	    debug_print_taint(res);
-	    }
-	  }
-	else
+	debug_printf_indent("%Vop-res: %.*s\n", "K-----", i, s);
+	if (tainted)
 	  {
-	  debug_printf_indent(UTF8_VERT_RIGHT
-	    UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
-	    "op-res: %.*s\n", i, s);
-	  if (tainted)
-	    {
-	    debug_printf_indent("%s",
-	      flags & ESI_SKIPPING
-	      ? UTF8_VERT "             " : "           " UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ);
-	    debug_print_taint(res);
-	    }
+	  debug_printf_indent("%V          %V",
+	    flags & ESI_SKIPPING ? "|" : " ",
+	    "\\__");
+	  debug_print_taint(res);
 	  }
 	}
        continue;
@@ -8459,39 +8429,25 @@ left != NULL, return a pointer to the terminator. */
   DEBUG(D_expand)
     {
     BOOL tainted = is_tainted(res);
-    DEBUG(D_noutf8)
-      {
-      debug_printf_indent("|--expanding: %.*s\n", (int)(s - string), string);
-      debug_printf_indent("%sresult: %s\n",
-	flags & ESI_SKIPPING ? "|-----" : "\\_____", res);
-      if (tainted)
-	{
-	debug_printf_indent("%s     \\__", flags & ESI_SKIPPING ? "|     " : "      ");
-	debug_print_taint(res);
-	}
-      if (flags & ESI_SKIPPING)
-	debug_printf_indent("\\___skipping: result is not used\n");
-      }
+    debug_printf_indent("%Vexpanded: %.*W\n",
+      "K---",
+      (int)(s - string), string);
+    debug_printf_indent("%Vresult: ",
+      flags & ESI_SKIPPING ? "K-----" : "\\_____");
+    if (*res || !(flags & ESI_SKIPPING))
+      debug_printf("%W\n", res);
     else
+      debug_printf(" %Vskipped%V\n", "<", ">");
+    if (tainted)
       {
-      debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ
-	"expanding: %.*s\n",
-	(int)(s - string), string);
-      debug_printf_indent("%s" UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
-	"result: %s\n",
-	flags & ESI_SKIPPING ? UTF8_VERT_RIGHT : UTF8_UP_RIGHT,
-	res);
-      if (tainted)
-	{
-	debug_printf_indent("%s",
-	  flags & ESI_SKIPPING
-	  ? UTF8_VERT "             " : "           " UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ);
-	debug_print_taint(res);
-	}
-      if (flags & ESI_SKIPPING)
-	debug_printf_indent(UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
-	  "skipping: result is not used\n");
+      debug_printf_indent("%V          %V",
+	flags & ESI_SKIPPING ? "|" : " ",
+	"\\__"
+	);
+      debug_print_taint(res);
       }
+    if (flags & ESI_SKIPPING)
+      debug_printf_indent("%Vskipping: result is not used\n", "\\___");
     }
   if (textonly_p) *textonly_p = textonly;
   expand_level--;
@@ -8517,25 +8473,11 @@ EXPAND_FAILED:
 if (left) *left = s;
 DEBUG(D_expand)
   {
-  DEBUG(D_noutf8)
-    {
-    debug_printf_indent("|failed to expand: %s\n", string);
-    debug_printf_indent("%serror message: %s\n",
-      f.expand_string_forcedfail ? "|---" : "\\___", expand_string_message);
-    if (f.expand_string_forcedfail)
-      debug_printf_indent("\\failure was forced\n");
-    }
-  else
-    {
-    debug_printf_indent(UTF8_VERT_RIGHT "failed to expand: %s\n",
-      string);
-    debug_printf_indent("%s" UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
-      "error message: %s\n",
-      f.expand_string_forcedfail ? UTF8_VERT_RIGHT : UTF8_UP_RIGHT,
-      expand_string_message);
-    if (f.expand_string_forcedfail)
-      debug_printf_indent(UTF8_UP_RIGHT "failure was forced\n");
-    }
+  debug_printf_indent("%Vfailed to expand: %s\n", "K", string);
+  debug_printf_indent("%Verror message: %s\n",
+    f.expand_string_forcedfail ? "K---" : "\\___", expand_string_message);
+  if (f.expand_string_forcedfail)
+    debug_printf_indent("%Vfailure was forced\n", "\\");
   }
 if (resetok_p && !resetok) *resetok_p = FALSE;
 expand_level--;
diff --git a/src/src/macros.h b/src/src/macros.h
index 902cdd165..3341f5f41 100644
--- a/src/src/macros.h
+++ b/src/src/macros.h
@@ -107,8 +107,9 @@ don't make the file descriptors two-way. */
 /* Debugging control */
 
 #define LOG_NAME_SIZE 256
-#define DEBUG(x)      if (debug_selector & (x))
-#define HDEBUG(x)     if (host_checking || debug_selector & (x))
+#define IS_DEBUG(x)	(debug_selector & (x))
+#define DEBUG(x)	if (IS_DEBUG(x))
+#define HDEBUG(x)	if (host_checking || IS_DEBUG(x))
 
 /* The default From: text for DSNs */
 
@@ -1057,6 +1058,10 @@ enum { FILTER_UNSET, FILTER_FORWARD, FILTER_EXIM, FILTER_SIEVE };
 #define UTF8_VERT_RIGHT		"\xE2\x94\x9C"
 #define UTF8_UP_RIGHT		"\xE2\x95\xB0"
 #define UTF8_VERT_2DASH		"\xE2\x95\x8E"
+#define UTF8_LEFT_TRIANGLE	"\xE2\x97\x80"
+#define UTF8_RIGHT_TRIANGLE	"\xE2\x96\xB6"
+#define UTF8_LIGHT_SHADE	"\xE2\x96\x91"
+#define UTF8_L_ARROW_HOOK	"\xE2\x86\xA9"
 
 
 /* Options on tls_close */
diff --git a/src/src/os.c b/src/src/os.c
index c6d1e06c2..3b49e4b9e 100644
--- a/src/src/os.c
+++ b/src/src/os.c
@@ -12,7 +12,8 @@
 # include <stdio.h>
 # include <time.h>
 #else
-# define DEBUG(x) if (debug_selector & (x))
+# define IS_DEBUG(x) (debug_selector & (x))
+# define DEBUG(x) if (IS_DEBUG(x))
 #endif
 
 #ifndef CS
diff --git a/src/src/string.c b/src/src/string.c
index 3bf2f1df7..af187c199 100644
--- a/src/src/string.c
+++ b/src/src/string.c
@@ -1348,7 +1348,7 @@ The return value can be NULL to signify overflow.
 Field width:		decimal digits, or *
 Precision:		dot, followed by decimal digits or *
 Length modifiers:	h  L  l  ll  z
-Conversion specifiers:	n d o u x X p f e E g G % c s S T Y D M
+Conversion specifiers:	n d o u x X p f e E g G % c s S T W V Y D M
 
 Returns the possibly-new (if copy for growth or taint-handling was needed)
 string, not nul-terminated.
@@ -1597,11 +1597,77 @@ while (*fp)
     case 'Y':			/* gstring pointer */
       {
       gstring * zg = va_arg(ap, gstring *);
-      if (zg) { s = CS zg->s; slen = zg->ptr;    }
+      if (zg) { s = CS zg->s; slen = gstring_length(zg); }
       else    { s = null;     slen = Ustrlen(s); }
       goto INSERT_GSTRING;
       }
+#ifndef COMPILE_UTILITY
+    case 'V':			/* Maybe convert ascii-art to UTF-8 chars */
+      {
+      gstring * zg = NULL;
+      s = va_arg(ap, char *);
+      if (IS_DEBUG(D_noutf8))
+	for ( ; *s; s++)
+	  zg = string_catn(zg, CUS (*s == 'K' ? "|" : s), 1);
+      else
+	for ( ; *s; s++) switch (*s)
+	  {
+	  case '\\': zg = string_catn(zg, US UTF8_UP_RIGHT,	  3); break;
+	  case '/':  zg = string_catn(zg, US UTF8_DOWN_RIGHT,	  3); break;
+	  case '-':
+	  case '_':  zg = string_catn(zg, US UTF8_HORIZ,	  3); break;
+	  case '|':  zg = string_catn(zg, US UTF8_VERT,		  3); break;
+	  case 'K':  zg = string_catn(zg, US UTF8_VERT_RIGHT,	  3); break;
+	  case '<':  zg = string_catn(zg, US UTF8_LEFT_TRIANGLE,  3); break;
+	  case '>':  zg = string_catn(zg, US UTF8_RIGHT_TRIANGLE, 3); break;
+	  default:   zg = string_catn(zg, CUS s, 1);		      break;
+	  }
 
+      if (!zg)
+	break;
+      s = CS zg->s;
+      slen = gstring_length(zg);
+      goto INSERT_GSTRING;
+      }
+
+    case 'W':			/* Maybe mark up spaces & newlines */
+      s = va_arg(ap, char *);
+      if (Ustrpbrk(s, " \n") && !IS_DEBUG(D_noutf8))
+	{
+	gstring * zg = NULL;
+	int p = precision;
+	for ( ; *s; s++)
+	  {
+	  /* Take a given precision as applying to the input; expand
+	  it for the transformed result */
+
+	  if (p >= 0 && --p < 0) break;
+	  switch (*s)
+	    {
+	    case ' ':
+	      zg = string_catn(zg, CUS UTF8_LIGHT_SHADE, 3);
+	      if (precision >= 0) precision += 2;
+	      break;
+	    case '\n':
+	      zg = string_catn(zg, CUS UTF8_L_ARROW_HOOK "\n", 4);
+	      if (precision >= 0) precision += 3;
+	      break;
+	    default:
+	      zg = string_catn(zg, CUS s, 1);
+	      break;
+	    }
+	  }
+	if (zg) { s = CS zg->s; slen = gstring_length(zg); }
+	else    { s = null;     slen = Ustrlen(s); }
+	}
+      else
+	{
+	if (!s) s = null;
+	slen = Ustrlen(s);
+	}
+      goto INSERT_GSTRING;
+
+#endif
     case 's':
     case 'S':                   /* Forces *lower* case */
     case 'T':                   /* Forces *upper* case */
@@ -1610,7 +1676,7 @@ while (*fp)
       if (!s) s = null;
       slen = Ustrlen(s);
 
-    INSERT_GSTRING:		/* Coome to from %Y above */
+    INSERT_GSTRING:		/* Come to from %Y above */
 
       if (!(flags & SVFMT_TAINT_NOCHK) && is_incompatible(g->s, s))
 	if (flags & SVFMT_REBUFFER)
@@ -1909,3 +1975,5 @@ return 0;
 #endif
 
 /* End of string.c */
+/* vi: aw ai sw=2
+*/

commit d4ff6d2a25234f7558baa99a54ca138c3f004b06
Author: Andrew Aitchison <exim@aitchison.me.uk>
Date:   Fri Mar 8 11:52:10 2024 +0000

    Silence some compiler warnings.  Bug 3076

diff --git a/src/src/routers/dnslookup.c b/src/src/routers/dnslookup.c
index 33382671f..80b12cba2 100644
--- a/src/src/routers/dnslookup.c
+++ b/src/src/routers/dnslookup.c
@@ -472,7 +472,7 @@ return rf_queue_add(addr, addr_local, addr_remote, rblock, pw)?
 }
 
 #endif	/*!MACRO_PREDEF*/
-#endif	/*ROUTER_DNSLOOKUP
+#endif	/*ROUTER_DNSLOOKUP*/
 /* End of routers/dnslookup.c */
 /* vi: aw ai sw=2
 */
diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c
index 56ea93935..3e8ec6d84 100644
--- a/src/src/tls-gnu.c
+++ b/src/src/tls-gnu.c
@@ -1185,6 +1185,8 @@ tls_server_servercerts_cb(gnutls_session_t session, unsigned int htype,
 # ifdef notdef_crashes
 				/*XXX crashes */
 return gnutls_ext_raw_parse(NULL, tls_server_servercerts_ext, msg, 0);
+# else
+return GNUTLS_E_SUCCESS;
 # endif
 }
 #endif /*SUPPORT_GNUTLS_EXT_RAW_PARSE*/
@@ -1233,7 +1235,7 @@ switch (htype)
     return tls_server_ticket_cb(sess, htype, when, incoming, msg);
 # endif
   default:
-    return 0;
+    return GNUTLS_E_SUCCESS;
   }
 }
 #endif

commit 35e7f5dc54c333d744a6b73d5668abddc085e0da
Author: Peter Benie <pjb1008@cam.ac.uk>
Date:   Thu Mar 14 15:56:07 2024 +0000

    Debug: fix transport-wait DB message.  Bug 3082

diff --git a/src/src/transport.c b/src/src/transport.c
index 5729f8e75..3609b78bb 100644
--- a/src/src/transport.c
+++ b/src/src/transport.c
@@ -1506,7 +1506,7 @@ open_db *dbm_file;
 if (!is_new_message_id(message_id))
   {
   DEBUG(D_transport) debug_printf("message_id %s is not new format; "
-    "skipping wait-%s database update\n", tpname);
+    "skipping wait-%s database update\n", message_id, tpname);
   return;
   }
 

commit c71f869820b45b911cc8edbd0de88bbc9621fd5f
Author: Peter Benie <pjb1008@cam.ac.uk>
Date:   Thu Mar 14 16:11:14 2024 +0000

    Rewrites: fix delivery crash from constant errors_to.  Bug 3081
    
    Broken-by: 753739fdef6d

diff --git a/src/src/rewrite.c b/src/src/rewrite.c
index 70bd79e26..9ee5a6794 100644
--- a/src/src/rewrite.c
+++ b/src/src/rewrite.c
@@ -104,7 +104,7 @@ rewrite_one(const uschar *s, int flag, BOOL *whole, BOOL add_header, uschar *nam
 {
 const uschar *yield = s;
 const uschar *subject = s;
-uschar *domain = NULL;
+const uschar *domain = NULL;
 BOOL done = FALSE;
 int rule_number = 1;
 int yield_start = 0, yield_end = 0;
@@ -158,7 +158,7 @@ for (rewrite_rule * rule = rewrite_rules;
 
   else
     {
-    if (!domain) domain = Ustrrchr(subject, '@') + 1;
+    if (!domain) domain = CUstrrchr(subject, '@') + 1;
 
     /* Use the general function for matching an address against a list (here
     just one item, so use the "impossible value" separator UCHAR_MAX+1). */
@@ -182,16 +182,14 @@ for (rewrite_rule * rule = rewrite_rules;
     save_domain = deliver_domain;
 
     /* We have subject pointing to "localpart@domain" and domain pointing to
-    the domain. Temporarily terminate the local part so that it can be
-    set up as an expansion variable */
+    the domain. Split into local part and domain so that it can be set up as 
+    an expansion variable */
 
-    domain[-1] = 0;
-    deliver_localpart = US subject;
+    deliver_localpart = US string_copyn(subject, domain-subject-1);
     deliver_domain = domain;
 
     new = expand_string(rule->replacement);
 
-    domain[-1] = '@';
     deliver_localpart = save_localpart;
     deliver_domain = save_domain;
     }

commit a7e6ad0ba38cf088e841c321042f81966d846b4b
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sat Mar 16 13:50:45 2024 +0000

    Lookups: fix dbmnz crash on zero-length datum.  Bug 3079
    
    Broken-by: 6d2c02560e5c

diff --git a/src/src/dbfn.c b/src/src/dbfn.c
index 3c51162a4..460fd8bb7 100644
--- a/src/src/dbfn.c
+++ b/src/src/dbfn.c
@@ -239,12 +239,13 @@ Returns: a pointer to the retrieved record, or
 */
 
 void *
-dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
+dbfn_read_with_length(open_db * dbblock, const uschar * key, int * length)
 {
-void *yield;
+void * yield;
 EXIM_DATUM key_datum, result_datum;
 int klen = Ustrlen(key) + 1;
 uschar * key_copy = store_get(klen, key);
+unsigned dlen;
 
 memcpy(key_copy, key, klen);
 
@@ -260,9 +261,10 @@ if (!exim_dbget(dbblock->dbptr, &key_datum, &result_datum)) return NULL;
 /* Assume the data store could have been tainted.  Properly, we should
 store the taint status with the data. */
 
-yield = store_get(exim_datum_size_get(&result_datum), GET_TAINTED);
-memcpy(yield, exim_datum_data_get(&result_datum), exim_datum_size_get(&result_datum));
-if (length) *length = exim_datum_size_get(&result_datum);
+dlen = exim_datum_size_get(&result_datum);
+yield = store_get(dlen, GET_TAINTED);
+memcpy(yield, exim_datum_data_get(&result_datum), dlen);
+if (length) *length = dlen;
 
 exim_datum_free(&result_datum);    /* Some DBM libs require freeing */
 return yield;
diff --git a/src/src/exim_dbutil.c b/src/src/exim_dbutil.c
index 3f70c2fd5..4d213773b 100644
--- a/src/src/exim_dbutil.c
+++ b/src/src/exim_dbutil.c
@@ -407,12 +407,13 @@ Returns: a pointer to the retrieved record, or
 */
 
 void *
-dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
+dbfn_read_with_length(open_db * dbblock, const uschar * key, int * length)
 {
-void *yield;
+void * yield;
 EXIM_DATUM key_datum, result_datum;
 int klen = Ustrlen(key) + 1;
 uschar * key_copy = store_get(klen, key);
+unsigned dlen;
 
 memcpy(key_copy, key, klen);
 
@@ -426,9 +427,10 @@ if (!exim_dbget(dbblock->dbptr, &key_datum, &result_datum)) return NULL;
 /* Assume for now that anything stored could have been tainted. Properly
 we should store the taint status along with the data. */
 
-yield = store_get(exim_datum_size_get(&result_datum), GET_TAINTED);
-memcpy(yield, exim_datum_data_get(&result_datum), exim_datum_size_get(&result_datum));
-if (length) *length = exim_datum_size_get(&result_datum);
+dlen = exim_datum_size_get(&result_datum);
+yield = store_get(dlen, GET_TAINTED);
+memcpy(yield, exim_datum_data_get(&result_datum), dlen);
+if (length) *length = dlen;
 
 exim_datum_free(&result_datum);    /* Some DBM libs require freeing */
 return yield;
diff --git a/src/src/lookups/dbmdb.c b/src/src/lookups/dbmdb.c
index aa930e654..96665b6e4 100644
--- a/src/src/lookups/dbmdb.c
+++ b/src/src/lookups/dbmdb.c
@@ -102,7 +102,8 @@ exim_datum_size_set(&key, length);
 
 if (exim_dbget(d, &key, &data))
   {
-  *result = string_copyn(exim_datum_data_get(&data), exim_datum_size_get(&data));
+  unsigned len = exim_datum_size_get(&data);
+  *result = len > 0 ? string_copyn(exim_datum_data_get(&data), len) : US"";
   exim_datum_free(&data);            /* Some DBM libraries need a free() call */
   return OK;
   }
@@ -283,3 +284,5 @@ static lookup_info *_lookup_list[] = { &dbm_lookup_info, &dbmz_lookup_info, &dbm
 lookup_module_info dbmdb_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 3 };
 
 /* End of lookups/dbmdb.c */
+/* vi: aw ai sw=2
+*/

commit 481eeb93961ef9998901c522b498253d75124fb4
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Sat Mar 16 13:54:42 2024 +0000

    Autoreply: Remove Exim version 3 campatibility code

diff --git a/src/src/transports/autoreply.c b/src/src/transports/autoreply.c
index 2f2fe1e16..690ec4a92 100644
--- a/src/src/transports/autoreply.c
+++ b/src/src/transports/autoreply.c
@@ -496,19 +496,7 @@ if (oncelog && *oncelog && to)
     exim_datum_size_set(&key_datum, Ustrlen(to) + 1);
 
     if (exim_dbget(dbm_file, &key_datum, &result_datum))
-      {
-      /* If the datum size is that of a binary time, we are in the new world
-      where messages are sent periodically. Otherwise the file is an old one,
-      where the datum was filled with a tod_log time, which is assumed to be
-      different in size. For that, only one message is ever sent. This change
-      introduced at Exim 3.00. In a couple of years' time the test on the size
-      can be abolished. */
-
-      if (exim_datum_size_get(&result_datum) == sizeof(time_t))
-        memcpy(&then, exim_datum_data_get(&result_datum), sizeof(time_t));
-      else
-        then = now;
-      }
+      memcpy(&then, exim_datum_data_get(&result_datum), sizeof(time_t));
     }
 
   /* Either "then" is set zero, if no message has yet been sent, or it

commit a1caa6da6f72e8c5b8b24678e32c4953a26a2b48
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Thu Mar 21 23:48:32 2024 +0000

    Option for taint when setting variable under -be

diff --git a/src/src/acl.c b/src/src/acl.c
index f4d65464e..29441dfc1 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -800,7 +800,7 @@ return TRUE;
 
 static BOOL
 acl_data_to_cond(const uschar * s, acl_condition_block * cond,
-  const uschar * name, uschar ** error)
+  const uschar * name, BOOL taint, uschar ** error)
 {
 if (*s++ != '=')
   {
@@ -809,7 +809,7 @@ if (*s++ != '=')
   return FALSE;;
   }
 Uskip_whitespace(&s);
-cond->arg = string_copy(s);
+cond->arg = taint ? string_copy_taint(s, GET_TAINTED) : string_copy(s);
 return TRUE;
 }
 
@@ -959,7 +959,7 @@ while ((s = (*func)()))
   "endpass" has no data */
 
   if (c != ACLC_ENDPASS)
-    if (!acl_data_to_cond(s, cond, name, error)) return NULL;
+    if (!acl_data_to_cond(s, cond, name, FALSE, error)) return NULL;
   }
 
 return yield;
@@ -4941,7 +4941,7 @@ fprintf(f, "acl%c %s %d\n%s\n", name[0], name+1, Ustrlen(value), value);
 
 
 uschar *
-acl_standalone_setvar(const uschar * s)
+acl_standalone_setvar(const uschar * s, BOOL taint)
 {
 acl_condition_block * cond = store_get(sizeof(acl_condition_block), GET_UNTAINTED);
 uschar * errstr = NULL, * log_msg = NULL;
@@ -4951,7 +4951,7 @@ int e;
 cond->next = NULL;
 cond->type = ACLC_SET;
 if (!acl_varname_to_cond(&s, cond, &errstr)) return errstr;
-if (!acl_data_to_cond(s, cond, US"'-be'", &errstr)) return errstr;
+if (!acl_data_to_cond(s, cond, US"'-be'", taint, &errstr)) return errstr;
 
 if (acl_check_condition(ACL_WARN, cond, ACL_WHERE_UNKNOWN,
 			    NULL, 0, &endpass_seen, &errstr, &log_msg, &e) != OK)
diff --git a/src/src/exim.c b/src/src/exim.c
index c591ee60c..3170bef98 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -1704,8 +1704,10 @@ if (isupper(big_buffer[0]))
   if (macro_read_assignment(big_buffer))
     printf("Defined macro '%s'\n", mlast->name);
   }
+else if (Ustrncmp(big_buffer, "set,t ", 6) == 0)
+  printf("%s\n", acl_standalone_setvar(big_buffer+6, TRUE));
 else if (Ustrncmp(big_buffer, "set ", 4) == 0)
-  printf("%s\n", acl_standalone_setvar(big_buffer+4));
+  printf("%s\n", acl_standalone_setvar(big_buffer+4, FALSE));
 else
   if ((s = expand_string(big_buffer))) printf("%s\n", CS s);
   else printf("Failed: %s\n", expand_string_message);
diff --git a/src/src/functions.h b/src/src/functions.h
index 77323a5c1..0e657d7c4 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -103,7 +103,7 @@ extern acl_block *acl_read(uschar *(*)(void), uschar **);
 extern int     acl_check(int, const uschar *, uschar *, uschar **, uschar **);
 extern uschar *acl_current_verb(void);
 extern int     acl_eval(int, uschar *, uschar **, uschar **);
-extern uschar *acl_standalone_setvar(const uschar *);
+extern uschar *acl_standalone_setvar(const uschar *, BOOL);
 
 extern tree_node *acl_var_create(uschar *);
 extern void    acl_var_write(uschar *, uschar *, void *);

commit cea451c53287e51175c9bbe1a4fd607d1db7a02a
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Mar 22 21:11:25 2024 +0000

    Debug: more clarity on a ret=key lookup

diff --git a/src/src/search.c b/src/src/search.c
index 62416da76..27c686a5d 100644
--- a/src/src/search.c
+++ b/src/src/search.c
@@ -980,9 +980,15 @@ than the result.  Return a de-tainted version of the key on the grounds that
 it have been validated by the lookup. */
 
 if (yield && ret_key)
+  {
   yield = string_copy_taint(keystring, GET_UNTAINTED);
+  DEBUG(D_lookup)
+    debug_printf_indent("lookup yield replace by key: %s\n", yield);
+  }
 
 return yield;
 }
 
 /* End of search.c */
+/* vi: aw ai sw=2
+*/

commit 64226a31616e631b9e10f7897d603cdf9847a458
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Mar 22 23:05:02 2024 +0000

    Debug: mark up space/nl in lookup results

diff --git a/src/src/search.c b/src/src/search.c
index 27c686a5d..168c25e6a 100644
--- a/src/src/search.c
+++ b/src/src/search.c
@@ -686,7 +686,7 @@ out:
 DEBUG(D_lookup)
   {
   if (data)
-    debug_printf_indent("lookup yielded: %s\n", data);
+    debug_printf_indent("lookup yielded: %W\n", data);
   else if (f.search_find_defer)
     debug_printf_indent("lookup deferred: %s\n", search_error_message);
   else debug_printf_indent("lookup failed\n");

commit c90c2288e88f66ae1d8af198cfa56c401e573ce4
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Mon Mar 25 12:13:09 2024 +0000

    specified-initializers

diff --git a/src/src/globals.c b/src/src/globals.c
index 4af8668f0..e23188b34 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -467,6 +467,9 @@ uschar *acl_smtp_quit          = NULL;
 uschar *acl_smtp_rcpt          = NULL;
 uschar *acl_smtp_starttls      = NULL;
 uschar *acl_smtp_vrfy          = NULL;
+#ifdef EXPERIMENTAL_WELLKNOWN
+uschar *acl_smtp_wellknown     = NULL;
+#endif
 
 tree_node *acl_var_c           = NULL;
 tree_node *acl_var_m           = NULL;
@@ -474,56 +477,49 @@ uschar *acl_verify_message     = NULL;
 string_item *acl_warn_logged   = NULL;
 
 /* Names of SMTP places for use in ACL error messages, and corresponding SMTP
-error codes - keep in step with definitions of ACL_WHERE_xxxx in macros.h. */
-
-uschar *acl_wherenames[]       = { US"RCPT",
-                                   US"MAIL",
-                                   US"PREDATA",
-                                   US"MIME",
-                                   US"DKIM",
-                                   US"DATA",
+error codes (only those used) */
+
+uschar *acl_wherenames[]       = { [ACL_WHERE_RCPT] =		US"RCPT",
+                                   [ACL_WHERE_MAIL] =		US"MAIL",
+                                   [ACL_WHERE_PREDATA] =	US"PREDATA",
+                                   [ACL_WHERE_MIME] =		US"MIME",
+                                   [ACL_WHERE_DKIM] =		US"DKIM",
+                                   [ACL_WHERE_DATA] =		US"DATA",
 #ifndef DISABLE_PRDR
-                                   US"PRDR",
-#endif
-                                   US"non-SMTP",
-                                   US"AUTH",
-                                   US"connection",
-                                   US"ETRN",
-                                   US"EXPN",
-                                   US"EHLO or HELO",
-                                   US"MAILAUTH",
-                                   US"non-SMTP-start",
-                                   US"NOTQUIT",
-                                   US"QUIT",
-                                   US"STARTTLS",
-                                   US"VRFY",
-				   US"delivery",
-				   US"unknown"
+                                   [ACL_WHERE_PRDR] =		US"PRDR",
+#endif
+                                   [ACL_WHERE_NOTSMTP] =	US"non-SMTP",
+                                   [ACL_WHERE_AUTH] =		US"AUTH",
+                                   [ACL_WHERE_CONNECT] =	US"connection",
+                                   [ACL_WHERE_ETRN] =		US"ETRN",
+                                   [ACL_WHERE_EXPN] =		US"EXPN",
+                                   [ACL_WHERE_HELO] =		US"EHLO or HELO",
+                                   [ACL_WHERE_MAILAUTH] =	US"MAILAUTH",
+                                   [ACL_WHERE_NOTSMTP_START] =	US"non-SMTP-start",
+                                   [ACL_WHERE_NOTQUIT] =	US"NOTQUIT",
+                                   [ACL_WHERE_QUIT] =		US"QUIT",
+                                   [ACL_WHERE_STARTTLS] =	US"STARTTLS",
+                                   [ACL_WHERE_VRFY] =		US"VRFY",
+				   [ACL_WHERE_DELIVERY] =	US"delivery",
+				   [ACL_WHERE_UNKNOWN] =	US"unknown"
                                  };
 
-uschar *acl_wherecodes[]       = { US"550",     /* RCPT */
-                                   US"550",     /* MAIL */
-                                   US"550",     /* PREDATA */
-                                   US"550",     /* MIME */
-                                   US"550",     /* DKIM */
-                                   US"550",     /* DATA */
+uschar *acl_wherecodes[]       = { [ACL_WHERE_RCPT] =	US"550",
+                                   [ACL_WHERE_MAIL] =	US"550",
+                                   [ACL_WHERE_PREDATA] = US"550",
+                                   [ACL_WHERE_MIME] =	US"550",
+                                   [ACL_WHERE_DKIM] =	US"550",
+                                   [ACL_WHERE_DATA] =	US"550",
 #ifndef DISABLE_PRDR
-                                   US"550",    /* RCPT PRDR */
-#endif
-                                   US"0",       /* not SMTP; not relevant */
-                                   US"503",     /* AUTH */
-                                   US"550",     /* connect */
-                                   US"458",     /* ETRN */
-                                   US"550",     /* EXPN */
-                                   US"550",     /* HELO/EHLO */
-                                   US"0",       /* MAILAUTH; not relevant */
-                                   US"0",       /* not SMTP; not relevant */
-                                   US"0",       /* NOTQUIT; not relevant */
-                                   US"0",       /* QUIT; not relevant */
-                                   US"550",     /* STARTTLS */
-                                   US"252",     /* VRFY */
-				   US"0",       /* delivery; not relevant */
-				   US"0"        /* unknown; not relevant */
+                                   [ACL_WHERE_PRDR] =	US"550",
+#endif
+                                   [ACL_WHERE_AUTH] =	US"503",
+                                   [ACL_WHERE_CONNECT] = US"550",
+                                   [ACL_WHERE_ETRN] =	US"458",
+                                   [ACL_WHERE_EXPN] =	US"550",
+                                   [ACL_WHERE_HELO] =	US"550",
+                                   [ACL_WHERE_STARTTLS] = US"550",
+                                   [ACL_WHERE_VRFY] =	US"252",
                                  };
 
 uschar *add_environment        = NULL;
@@ -1003,6 +999,9 @@ uschar *hosts_proxy            = NULL;
 #endif
 uschar *hosts_treat_as_local   = NULL;
 uschar *hosts_require_helo     = US"*";
+#ifdef EXPERIMENTAL_WELLKNOWN
+uschar *hosts_wellknown	       = NULL;
+#endif
 #ifdef EXPERIMENTAL_XCLIENT
 uschar *hosts_xclient	       = NULL;
 #endif
@@ -1671,3 +1670,5 @@ const uschar *warnmsg_delay    = NULL;
 const uschar *warnmsg_recipients = NULL;
 
 /*  End of globals.c */
+/* vi: aw ai sw=2
+*/

commit 4687a69c269ee3f2a7f0625e0147a503fd9d3d0b
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Tue Mar 26 17:36:59 2024 +0000

    tidying

diff --git a/src/src/acl.c b/src/src/acl.c
index 29441dfc1..d719a937b 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -1366,7 +1366,7 @@ uschar * target = store_get(TARGET_SIZE, GET_TAINTED);
 client's HELO domain. If the client has not said HELO, use its IP address
 instead. If it's a local client (exim -bs), CSA isn't applicable. */
 
-while (isspace(*domain) && *domain != '\0') ++domain;
+while (isspace(*domain) && *domain) ++domain;
 if (*domain == '\0') domain = sender_helo_name;
 if (!domain) domain = sender_host_address;
 if (!sender_host_address) return CSA_UNKNOWN;
@@ -1867,9 +1867,10 @@ switch(vp->value)
       verify_sender_address = sender_address;
     else
       {
-      while (isspace(*s)) s++;
-      if (*s++ != '=') goto BAD_VERIFY;
-      while (isspace(*s)) s++;
+      if (Uskip_whitespace(&s) != '=')
+	goto BAD_VERIFY;
+      s++;
+      Uskip_whitespace(&s);
       verify_sender_address = string_copy(s);
       }
     }
@@ -1911,13 +1912,13 @@ while ((ss = string_nextinlist(&list, &sep, NULL, 0)))
     callout = CALLOUT_TIMEOUT_DEFAULT;
     if (*(ss += 7))
       {
-      while (isspace(*ss)) ss++;
+      Uskip_whitespace(&ss);
       if (*ss++ == '=')
         {
 	const uschar * sublist = ss;
         int optsep = ',';
 
-        while (isspace(*sublist)) sublist++;
+	Uskip_whitespace(&sublist);
         for (uschar * opt; opt = string_nextinlist(&sublist, &optsep, NULL, 0); )
           {
 	  callout_opt_t * op;
@@ -1931,14 +1932,14 @@ while ((ss = string_nextinlist(&list, &sep, NULL, 0)))
 	  if (op->has_option)
 	    {
 	    opt += Ustrlen(op->name);
-            while (isspace(*opt)) opt++;
+            Uskip_whitespace(&opt);
             if (*opt++ != '=')
               {
               *log_msgptr = string_sprintf("'=' expected after "
                 "\"%s\" in ACL verify condition \"%s\"", op->name, arg);
               return ERROR;
               }
-            while (isspace(*opt)) opt++;
+            Uskip_whitespace(&opt);
 	    }
 	  if (op->timeval && (period = v_period(opt, arg, log_msgptr)) < 0)
 	    return ERROR;
@@ -1981,14 +1982,14 @@ while ((ss = string_nextinlist(&list, &sep, NULL, 0)))
     quota = TRUE;
     if (*(ss += 5))
       {
-      while (isspace(*ss)) ss++;
+      Uskip_whitespace(&ss);
       if (*ss++ == '=')
         {
 	const uschar * sublist = ss;
         int optsep = ',';
 	int period;
 
-        while (isspace(*sublist)) sublist++;
+        Uskip_whitespace(&sublist);
         for (uschar * opt; opt = string_nextinlist(&sublist, &optsep, NULL, 0); )
 	  if (Ustrncmp(opt, "cachepos=", 9) == 0)
 	    if ((period = v_period(opt += 9, arg, log_msgptr)) < 0)
@@ -3892,7 +3893,7 @@ for (; cb; cb = cb->next)
           }
         s++;
         }
-      while (isspace(*s)) s++;
+      Uskip_whitespace(&s);
 
       if (logbits == 0) logbits = LOG_MAIN;
       log_write(0, logbits, "%s", string_printing(s));
@@ -4316,19 +4317,17 @@ if (!s)
 /* At top level, we expand the incoming string. At lower levels, it has already
 been expanded as part of condition processing. */
 
-if (acl_level == 0)
+if (acl_level != 0)
+  ss = s;
+else if (!(ss = expand_string(s)))
   {
-  if (!(ss = expand_string(s)))
-    {
-    if (f.expand_string_forcedfail) return OK;
-    *log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s", s,
-      expand_string_message);
-    return ERROR;
-    }
+  if (f.expand_string_forcedfail) return OK;
+  *log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s", s,
+    expand_string_message);
+  return ERROR;
   }
-else ss = s;
 
-while (isspace(*ss)) ss++;
+Uskip_whitespace(&ss);
 
 /* If we can't find a named ACL, the default is to parse it as an inline one.
 (Unless it begins with a slash; non-existent files give rise to an error.) */
@@ -4617,8 +4616,8 @@ if (!(tmp = string_dequote(&s)) || !(name = expand_string(tmp)))
 
 for (i = 0; i < 9; i++)
   {
-  while (*s && isspace(*s)) s++;
-  if (!*s) break;
+  if (!Uskip_whitespace(&s))
+    break;
   if (!(tmp = string_dequote(&s)) || !(tmp_arg[i] = expand_string(tmp)))
     {
     tmp = name;
diff --git a/src/src/dbfn.c b/src/src/dbfn.c
index 460fd8bb7..13a3c2d66 100644
--- a/src/src/dbfn.c
+++ b/src/src/dbfn.c
@@ -490,7 +490,7 @@ while (Ufgets(buffer, 256, stdin) != NULL)
     {
     count = Uatoi(cmd);
     while (isdigit((uschar)*cmd)) cmd++;
-    while (isspace((uschar)*cmd)) cmd++;
+    Uskip_whitespace(&cmd);
     }
 
   if (Ustrncmp(cmd, "open", 4) == 0)
@@ -498,7 +498,7 @@ while (Ufgets(buffer, 256, stdin) != NULL)
     int i;
     open_db *odb;
     uschar *s = cmd + 4;
-    while (isspace((uschar)*s)) s++;
+    Uskip_whitespace(&s);
 
     for (i = 0; i < max_db; i++)
       if (dbblock[i].dbptr == NULL) break;
@@ -534,8 +534,7 @@ while (Ufgets(buffer, 256, stdin) != NULL)
   else if (Ustrncmp(cmd, "write", 5) == 0)
     {
     int rc = 0;
-    uschar *key = cmd + 5;
-    uschar *data;
+    uschar * key = cmd + 5, * data;
 
     if (current < 0)
       {
@@ -543,11 +542,11 @@ while (Ufgets(buffer, 256, stdin) != NULL)
       continue;
       }
 
-    while (isspace((uschar)*key)) key++;
+    Uskip_whitespace(&key);
     data = key;
-    while (*data != 0 && !isspace((uschar)*data)) data++;
+    while (*data && !isspace((uschar)*data)) data++;
     *data++ = 0;
-    while (isspace((uschar)*data)) data++;
+    Uskip_whitespace(&data);
 
     dbwait = (dbdata_wait *)(&structbuffer);
     Ustrcpy(dbwait->text, data);
@@ -562,13 +561,13 @@ while (Ufgets(buffer, 256, stdin) != NULL)
 
   else if (Ustrncmp(cmd, "read", 4) == 0)
     {
-    uschar *key = cmd + 4;
+    uschar * key = cmd + 4;
     if (current < 0)
       {
       printf("No current database\n");
       continue;
       }
-    while (isspace((uschar)*key)) key++;
+    Uskip_whitespace(&key);
     start = clock();
     while (count-- > 0)
       dbwait = (dbdata_wait *)dbfn_read_with_length(dbblock+ current, key, NULL);
@@ -578,13 +577,13 @@ while (Ufgets(buffer, 256, stdin) != NULL)
 
   else if (Ustrncmp(cmd, "delete", 6) == 0)
     {
-    uschar *key = cmd + 6;
+    uschar * key = cmd + 6;
     if (current < 0)
       {
       printf("No current database\n");
       continue;
       }
-    while (isspace((uschar)*key)) key++;
+    Uskip_whitespace(&key);
     dbfn_delete(dbblock + current, key);
     }
 
@@ -614,8 +613,8 @@ while (Ufgets(buffer, 256, stdin) != NULL)
 
   else if (Ustrncmp(cmd, "close", 5) == 0)
     {
-    uschar *s = cmd + 5;
-    while (isspace((uschar)*s)) s++;
+    uschar * s = cmd + 5;
+    Uskip_whitespace(&s);
     i = Uatoi(s);
     if (i >= max_db || dbblock[i].dbptr == NULL) printf("Not open\n"); else
       {
@@ -629,8 +628,8 @@ while (Ufgets(buffer, 256, stdin) != NULL)
 
   else if (Ustrncmp(cmd, "file", 4) == 0)
     {
-    uschar *s = cmd + 4;
-    while (isspace((uschar)*s)) s++;
+    uschar * s = cmd + 4;
+    Uskip_whitespace(&s);
     i = Uatoi(s);
     if (i >= max_db || dbblock[i].dbptr == NULL) printf("Not open\n");
       else current = i;
@@ -682,3 +681,5 @@ return 0;
 #endif
 
 /* End of dbfn.c */
+/* vi: aw ai sw=2
+*/
diff --git a/src/src/exim.c b/src/src/exim.c
index 3170bef98..5c9e64021 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -2632,14 +2632,11 @@ on the second character (the one after '-'), to save some effort. */
               reset_point = store_mark();
               while (Ufgets(big_buffer, big_buffer_size, trust_list))
                 {
-                uschar *start = big_buffer, *nl;
-                while (*start && isspace(*start))
-                start++;
-                if (*start != '/')
+                uschar * start = big_buffer, * nl;
+                if (Uskip_whitespace(&start) != '/')
                   continue;
-                nl = Ustrchr(start, '\n');
-                if (nl)
-                  *nl = 0;
+                if ((nl = Ustrchr(start, '\n')))
+                  *nl = '\0';
                 trusted_configs[nr_configs++] = string_copy(start);
                 if (nr_configs == nelem(trusted_configs))
                   break;
@@ -2698,7 +2695,7 @@ on the second character (the one after '-'), to save some effort. */
       const uschar * s = argrest;
 
       opt_D_used = TRUE;
-      while (isspace(*s)) s++;
+      Uskip_whitespace(&s);
 
       if (*s < 'A' || *s > 'Z')
         exim_fail("exim: macro name set by -D must start with "
@@ -2711,11 +2708,10 @@ on the second character (the one after '-'), to save some effort. */
         }
       name[ptr] = 0;
       if (ptr == 0) { badarg = TRUE; break; }
-      while (isspace(*s)) s++;
-      if (*s != 0)
+      if (Uskip_whitespace(&s))
         {
         if (*s++ != '=') { badarg = TRUE; break; }
-        while (isspace(*s)) s++;
+        Uskip_whitespace(&s);
         }
 
       for (m = macros_user; m; m = m->next)
@@ -5918,7 +5914,7 @@ for (BOOL more = TRUE; more; )
         receive_add_recipient(string_copy_taint(recipient, GET_TAINTED), -1);
         s = ss;
         if (!finished)
-          while (*(++s) != 0 && (*s == ',' || isspace(*s)));
+          while (*++s && (*s == ',' || isspace(*s)));
         }
       }
 
diff --git a/src/src/expand.c b/src/src/expand.c
index a916eee64..5ea4dc6d4 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -1262,7 +1262,8 @@ while (*s)
 
   while (*s && *s != '=' && !isspace(*s)) s++;
   dkeylength = s - dkey;
-  if (Uskip_whitespace(&s) == '=') while (isspace(*++s));
+  if (Uskip_whitespace(&s) == '=')
+    while (isspace(*++s)) ;
 
   data = string_dequote(&s);
   if (length == dkeylength && strncmpic(key, dkey, length) == 0)
@@ -4114,7 +4115,7 @@ if (!*error)
     if (*s != ')')
       *error = US"expecting closing parenthesis";
     else
-      while (isspace(*++s));
+      while (isspace(*++s)) ;
   else if (*s)
     *error = US"expecting operator";
 *sptr = s;
diff --git a/src/src/filter.c b/src/src/filter.c
index b56b593b8..18567f17c 100644
--- a/src/src/filter.c
+++ b/src/src/filter.c
@@ -276,16 +276,11 @@ nextsigchar(const uschar *ptr, BOOL comment_allowed)
 for (;;)
   {
   while (isspace(*ptr))
-    {
-    if (*ptr == '\n') line_number++;
-    ptr++;
-    }
+    if (*ptr++ == '\n') line_number++;
   if (comment_allowed && *ptr == '#')
-    {
-    while (*(++ptr) != '\n' && *ptr != 0);
-    continue;
-    }
-  else break;
+    while (*++ptr != '\n' && *ptr) ;
+  else
+    break;
   }
 return ptr;
 }
@@ -311,18 +306,19 @@ Returns:    pointer to the next significant character after the word
 static const uschar *
 nextword(const uschar *ptr, uschar *buffer, int size, BOOL bracket)
 {
-uschar *bp = buffer;
-while (*ptr != 0 && !isspace(*ptr) &&
+uschar * bp = buffer;
+while (*ptr && !isspace(*ptr) &&
        (!bracket || (*ptr != '(' && *ptr != ')')))
-  {
-  if (bp - buffer < size - 1) *bp++ = *ptr++; else
+  if (bp - buffer < size - 1)
+    *bp++ = *ptr++;
+  else
     {
     *error_pointer = string_sprintf("word is too long in line %d of "
       "filter file (max = %d chars)", line_number, size);
     break;
     }
-  }
-*bp = 0;
+
+*bp = '\0';
 return nextsigchar(ptr, TRUE);
 }
 
@@ -410,8 +406,8 @@ int value, count;
 if (sscanf(CS s, "%i%n", &value, &count) != 1) return 0;
 if (tolower(s[count]) == 'k') { value *= 1024; count++; }
 if (tolower(s[count]) == 'm') { value *= 1024*1024; count++; }
-while (isspace((s[count]))) count++;
-if (s[count] != 0) return 0;
+while (isspace(s[count])) count++;
+if (s[count]) return 0;
 *ok = TRUE;
 return value;
 }
@@ -2006,8 +2002,7 @@ while (commands)
 
 	if (subtype == TRUE)
 	  {
-	  while (isspace(*s)) s++;
-	  if (*s)
+	  if (Uskip_whitespace(&s))
 	    {
 	    header_add(htype_other, "%s%s", s,
 	      s[Ustrlen(s)-1] == '\n' ? "" : "\n");
@@ -2226,7 +2221,7 @@ while (commands)
 	  gstring * log_addr = NULL;
 
 	  if (!to) to = expand_string(US"$reply_address");
-	  while (isspace(*to)) to++;
+	  Uskip_whitespace(&to);
 
 	  for (tt = to; *tt; tt++)     /* Get rid of newlines */
 	    if (*tt == '\n')
@@ -2298,7 +2293,7 @@ while (commands)
 	    /* Move on past this address */
 
 	    tt = ss + (*ss ? 1 : 0);
-	    while (isspace(*tt)) tt++;
+	    Uskip_whitespace(&tt);
 	    }
 
 	  if (log_addr)
@@ -2612,3 +2607,5 @@ return yield;
 
 
 /* End of filter.c */
+/* vi: aw ai sw=2
+*/
diff --git a/src/src/lookups/dnsdb.c b/src/src/lookups/dnsdb.c
index 050baa3b7..af1ad9dab 100644
--- a/src/src/lookups/dnsdb.c
+++ b/src/src/lookups/dnsdb.c
@@ -154,8 +154,7 @@ gstring * yield = string_get(256);
 /* If the string starts with '>' we change the output separator.
 If it's followed by ';' or ',' we set the TXT output separator. */
 
-while (isspace(*keystring)) keystring++;
-if (*keystring == '>')
+if (Uskip_whitespace(&keystring) == '>')
   {
   outsep = keystring + 1;
   keystring += 2;
@@ -169,7 +168,7 @@ if (*keystring == '>')
     outsep2 = US"";
     keystring++;
     }
-  while (isspace(*keystring)) keystring++;
+  Uskip_whitespace(&keystring);
   }
 
 /* Check for a modifier keyword. */
@@ -234,14 +233,14 @@ for (;;)
   else
     break;
 
-  while (isspace(*keystring)) keystring++;
+  Uskip_whitespace(&keystring);
   if (*keystring++ != ',')
     {
     *errmsg = US"dnsdb modifier syntax error";
     rc = DEFER;
     goto out;
     }
-  while (isspace(*keystring)) keystring++;
+  Uskip_whitespace(&keystring);
   }
 
 /* Figure out the "type" value if it is not T_TXT.
@@ -272,7 +271,7 @@ if ((equals = Ustrchr(keystring, '=')) != NULL)
     }
 
   keystring = equals + 1;
-  while (isspace(*keystring)) keystring++;
+  Uskip_whitespace(&keystring);
   }
 
 /* Initialize the resolver in case this is the first time it has been used. */
diff --git a/src/src/lookups/ldap.c b/src/src/lookups/ldap.c
index b93782cc3..90cde6c86 100644
--- a/src/src/lookups/ldap.c
+++ b/src/src/lookups/ldap.c
@@ -1083,20 +1083,15 @@ control_ldap_search(const uschar *ldap_url, int search_type, uschar **res,
   uschar **errmsg)
 {
 BOOL defer_break = FALSE;
-int timelimit = LDAP_NO_LIMIT;
-int sizelimit = LDAP_NO_LIMIT;
+int timelimit = LDAP_NO_LIMIT, sizelimit = LDAP_NO_LIMIT;
 int tcplimit = 0;
 int sep = 0;
 int dereference = LDAP_DEREF_NEVER;
-void* referrals = LDAP_OPT_ON;
-const uschar *url = ldap_url;
-const uschar *p;
-uschar *user = NULL;
-uschar *password = NULL;
-uschar *local_servers = NULL;
-const uschar *list;
+void * referrals = LDAP_OPT_ON;
+const uschar * url = ldap_url, * p, * list;
+uschar * user = NULL, * password = NULL, * local_servers = NULL;
 
-while (isspace(*url)) url++;
+Uskip_whitespace(&url);
 
 /* Until the string begins "ldap", search for the other parameter settings that
 are recognized. They are of the form NAME=VALUE, with the value being
@@ -1175,7 +1170,7 @@ while (strncmpic(url, US"ldap", 4) != 0)
         DEBUG(D_lookup) debug_printf_indent("LDAP query error: %s\n", *errmsg);
         return DEFER;
         }
-      while (isspace(*url)) url++;
+      Uskip_whitespace(&url);
       continue;
       }
     }
diff --git a/src/src/lookups/lsearch.c b/src/src/lookups/lsearch.c
index 33e8c5157..7b78de916 100644
--- a/src/src/lookups/lsearch.c
+++ b/src/src/lookups/lsearch.c
@@ -291,8 +291,8 @@ for (BOOL this_is_eol, last_was_eol = TRUE;
       this_is_comment = (this_is_comment || (buffer[0] == 0 || buffer[0] == '#'));
       if (this_is_comment) continue;
       if (!isspace((uschar)buffer[0])) break;
-      while (isspace((uschar)*s)) s++;
-      *(--s) = ' ';
+      Uskip_whitespace(&s);
+      *--s = ' ';
       }
     if (this_is_comment) continue;
 
diff --git a/src/src/lookups/oracle.c b/src/src/lookups/oracle.c
index d0604c245..c25b7c0ee 100644
--- a/src/src/lookups/oracle.c
+++ b/src/src/lookups/oracle.c
@@ -408,9 +408,9 @@ while (cda->rc != NO_DATA_FOUND)  /* Loop for each row */
   else for (int i = 0; i < num_fields; i++)
     {
     int slen;
-    uschar *s = US desc[i].buf;
+    uschar * s = US desc[i].buf;
 
-    while (*s != 0 && isspace(*s)) s++;
+    Uskip_whitespace(&s);
     slen = Ustrlen(s);
     while (slen > 0 && isspace(s[slen-1])) slen--;
     result = string_catn(result, s, slen);
diff --git a/src/src/lookups/redis.c b/src/src/lookups/redis.c
index 7b680f086..53b479142 100644
--- a/src/src/lookups/redis.c
+++ b/src/src/lookups/redis.c
@@ -212,7 +212,7 @@ if(sdata[1])
   int siz, ptr, i;
   uschar c;
 
-  while (isspace(*s)) s++;
+  Uskip_whitespace(&s);
 
   for (i = 0; *s && i < nele(argv); i++)
     {
@@ -224,7 +224,7 @@ if(sdata[1])
     argv[i] = string_from_gstring(g);
 
     DEBUG(D_lookup) debug_printf_indent("REDIS: argv[%d] '%s'\n", i, argv[i]);
-    while (isspace(*s)) s++;
+    Uskip_whitespace(&s);
     }
 
   /* Run the command. We use the argv form rather than plain as that parses
diff --git a/src/src/match.c b/src/src/match.c
index 8b1a3bef6..2cb499359 100644
--- a/src/src/match.c
+++ b/src/src/match.c
@@ -599,7 +599,7 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
   if (*ss == '!')
     {
     yield = FAIL;
-    while (isspace((*(++ss))));
+    while (isspace(*++ss)) ;
     }
   else
     yield = OK;
@@ -825,15 +825,14 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
 
     while (Ufgets(filebuffer, sizeof(filebuffer), f) != NULL)
       {
-      uschar *error;
-      uschar *sss = filebuffer;
+      uschar * error, * sss = filebuffer;
 
       while ((ss = Ustrchr(sss, '#')) != NULL)
         {
         if ((type != MCL_ADDRESS && type != MCL_LOCALPART) ||
               ss == filebuffer || isspace(ss[-1]))
           {
-          *ss = 0;
+          *ss = '\0';
           break;
           }
         sss = ss + 1;
@@ -841,20 +840,19 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
 
       ss = filebuffer + Ustrlen(filebuffer);		/* trailing space */
       while (ss > filebuffer && isspace(ss[-1])) ss--;
-      *ss = 0;
+      *ss = '\0';
 
       ss = filebuffer;
-      while (isspace(*ss)) ss++;			/* leading space */
-
-      if (!*ss) continue;				/* ignore empty */
+      if (!Uskip_whitespace(&ss))			/* leading space */
+	continue;					/* ignore empty */
 
       file_yield = yield;				/* positive yield */
       sss = ss;						/* for debugging */
 
       if (*ss == '!')					/* negation */
         {
-        file_yield = (file_yield == OK)? FAIL : OK;
-        while (isspace((*(++ss))));
+        file_yield = file_yield == OK ? FAIL : OK;
+        while (isspace(*++ss)) ;
         }
 
       switch ((func)(arg, ss, valueptr, &error))
@@ -1157,7 +1155,7 @@ if (pattern[0] == '@' && pattern[1] == '@')
       if (*ss == '!')
         {
         local_yield = FAIL;
-        while (isspace((*(++ss))));
+        while (isspace(*++ss)) ;
         }
       else local_yield = OK;
 
diff --git a/src/src/moan.c b/src/src/moan.c
index 6fe82b29c..641540839 100644
--- a/src/src/moan.c
+++ b/src/src/moan.c
@@ -762,13 +762,12 @@ llen = domain++ - recipient;
 
 while ((item = string_nextinlist(&listptr, &sep, NULL, 0)))
   {
-  const uschar *newaddress = item;
-  const uschar *pattern = string_dequote(&newaddress);
+  const uschar * newaddress = item;
+  const uschar * pattern = string_dequote(&newaddress);
 
   /* If no new address found, just skip this item. */
 
-  while (isspace(*newaddress)) newaddress++;
-  if (*newaddress == 0) continue;
+  if (!Uskip_whitespace(&newaddress)) continue;
 
   /* We now have an item to match as an address in item, and the additional
   address in newaddress. If the pattern matches, expand the new address string
diff --git a/src/src/parse.c b/src/src/parse.c
index ecdbe9f8d..4a75d229e 100644
--- a/src/src/parse.c
+++ b/src/src/parse.c
@@ -1363,7 +1363,7 @@ for (;;)
       *error = string_sprintf("\"%.*s\" is not permitted", len, s);
       return FF_ERROR;
       }
-    while (*ss && isspace(*ss)) ss++;	/* skip leading whitespace */
+    Uskip_whitespace(&ss);		/* skip leading whitespace */
     if ((len = Ustrlen(ss)) > 0)	/* ignore trailing newlines */
       for (const uschar * t = ss + len - 1; t >= ss && *t == '\n'; t--) len--;
     *error = string_copyn(ss, len);	/* becomes the error */
diff --git a/src/src/pdkim/pdkim.c b/src/src/pdkim/pdkim.c
index 21e17c61e..565ee068e 100644
--- a/src/src/pdkim/pdkim.c
+++ b/src/src/pdkim/pdkim.c
@@ -679,8 +679,7 @@ for (const uschar * ele = raw_record, * tspec, * end, * val; *ele; ele = end)
     DEBUG(D_acl) debug_printf(" %.*s=%s\n", taglen, tspec, val);
     while (taglen > 1 && isspace(tspec[taglen-1]))
       taglen--;			/* Ignore whitespace before = */
-    while (isspace(*val))
-      val++;			/* Ignore whitespace after = */
+    Uskip_whitespace(&val);	/* Ignore whitespace after = */
     if (isspace(val[ Ustrlen(val)-1 ]))
       {				/* Ignore whitespace after value */
       gstring * g = string_cat(NULL, val);
diff --git a/src/src/proxy.c b/src/src/proxy.c
index fbce11163..e2d3bace0 100644
--- a/src/src/proxy.c
+++ b/src/src/proxy.c
@@ -396,7 +396,7 @@ else if (ret >= 8 && memcmp(hdr.v1.line, "PROXY", 5) == 0)
   /* Step through the string looking for the required fields. Ensure
   strict adherence to required formatting, exit for any error. */
   p += 5;
-  if (!isspace(*(p++)))
+  if (!isspace(*p++))
     {
     DEBUG(D_receive) debug_printf("Missing space after PROXY command\n");
     goto proxyfail;
@@ -417,7 +417,7 @@ else if (ret >= 8 && memcmp(hdr.v1.line, "PROXY", 5) == 0)
     }
 
   p += Ustrlen(iptype);
-  if (!isspace(*(p++)))
+  if (!isspace(*p++))
     {
     DEBUG(D_receive) debug_printf("Missing space after TCP4/6 command\n");
     goto proxyfail;
diff --git a/src/src/readconf.c b/src/src/readconf.c
index 5b486d0b6..3db3bdd69 100644
--- a/src/src/readconf.c
+++ b/src/src/readconf.c
@@ -1050,7 +1050,7 @@ for (;;)
          (Ustrncmp(ss+8, "_if_exists", 10) == 0 && isspace(ss[18]))))
     {
     uschar *t;
-    int include_if_exists = isspace(ss[8])? 0 : 10;
+    int include_if_exists = isspace(ss[8]) ? 0 : 10;
     config_file_item *save;
     struct stat statbuf;
 
@@ -1644,8 +1644,7 @@ uschar name2[EXIM_DRIVERNAME_MAX];
 /* There may be leading spaces; thereafter, we expect an option name starting
 with a letter. */
 
-while (isspace(*s)) s++;
-if (!isalpha(*s))
+if (!isalpha( Uskip_whitespace(&s) ))
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "option setting expected: %s", s);
 
 /* Read the name of the option, and skip any subsequent white space. If
@@ -1660,7 +1659,7 @@ for (int n = 0; n < 2; n++)
     s++;
     }
   name[ptr] = 0;
-  while (isspace(*s)) s++;
+  Uskip_whitespace(&s);
   if (Ustrcmp(name, "hide") != 0) break;
   issecure = opt_secure;
   ptr = 0;
@@ -1720,7 +1719,7 @@ else if (*s && (offset != 0 || *s != '='))
 
 /* Skip white space after = */
 
-if (*s == '=') while (isspace((*(++s))));
+if (*s == '=') while (isspace(*++s));
 
 /* If there is a data block and the opt_public flag is not set, change
 the data block pointer to the private options block. */
@@ -2202,8 +2201,7 @@ switch (type)
 	log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
 	  "absolute value of integer \"%s\" is too large (overflow)", s);
 
-      while (isspace(*endptr)) endptr++;
-      if (*endptr)
+      if (Uskip_whitespace(&endptr))
 	extra_chars_error(endptr, inttype, US"integer value for ", name);
 
       value = (int)lvalue;
@@ -2251,8 +2249,7 @@ switch (type)
     if (errno == ERANGE) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
       "absolute value of integer \"%s\" is too large (overflow)", s);
 
-    while (isspace(*endptr)) endptr++;
-    if (*endptr != 0)
+    if (Uskip_whitespace(&endptr))
       extra_chars_error(endptr, inttype, US"integer value for ", name);
 
     if (data_block)
@@ -2347,7 +2344,7 @@ switch (type)
       list[count+1] = value;
       if (snext == NULL) break;
       s = snext + 1;
-      while (isspace(*s)) s++;
+      Uskip_whitespace(&s);
       }
 
     if (count > list[0] - 2)
@@ -3570,8 +3567,7 @@ if (host_number_string)
         "failed to expand localhost_number \"%s\": %s",
         host_number_string, expand_string_message);
   n = Ustrtol(s, &end, 0);
-  while (isspace(*end)) end++;
-  if (*end)
+  if (Uskip_whitespace(&end))
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
       "localhost_number value is not a number: %s", s);
   if (n > LOCALHOST_MAX)
@@ -4019,10 +4015,9 @@ Returns:    time in seconds or fixed point number * 1000
 */
 
 static int
-retry_arg(const uschar **paddr, int type)
+retry_arg(const uschar ** paddr, int type)
 {
-const uschar *p = *paddr;
-const uschar *pp;
+const uschar * p = *paddr, * pp;
 
 if (*p++ != ',') log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "comma expected");
 
@@ -4030,7 +4025,7 @@ Uskip_whitespace(&p);
 pp = p;
 while (isalnum(*p) || (type == 1 && *p == '.')) p++;
 
-if (*p != 0 && !isspace(*p) && *p != ',' && *p != ';')
+if (*p && !isspace(*p) && *p != ',' && *p != ';')
   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "comma or semicolon expected");
 
 *paddr = p;
@@ -4433,10 +4428,8 @@ for (const config_line_item * i = config_lines; i; i = i->next)
   r = store_mark();
 
   /* skip over to the first non-space */
-  for (current = string_copy(i->line); *current && isspace(*current); ++current)
-    ;
-
-  if (!*current)
+  current = string_copy(i->line);
+  if (!Uskip_whitespace(&current))
     continue;
 
   /* Collapse runs of spaces. We stop this if we encounter one of the
@@ -4444,11 +4437,10 @@ for (const config_line_item * i = config_lines; i; i = i->next)
 
   for (p = current; *p; p++) if (isspace(*p))
     {
-    uschar *next;
+    uschar * next = p;
     if (*p != ' ') *p = ' ';
 
-    for (next = p; isspace(*next); ++next)
-      ;
+    Uskip_whitespace(&p);
 
     if (next - p > 1)
       memmove(p+1, next, Ustrlen(next)+1);
diff --git a/src/src/receive.c b/src/src/receive.c
index a56ff473e..9dbf45df9 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -2251,8 +2251,7 @@ OVERSIZE:
 
     if (isspace(*p)) break;
     while (mac_isgraph(*p) && *p != ':') p++;
-    while (isspace(*p)) p++;
-    if (*p != ':')
+    if (Uskip_whitespace(&p) != ':')
       {
       body_zerocount = had_zero;
       break;
@@ -2453,13 +2452,14 @@ for (header_line * h = header_list->next; h; h = h->next)
 	if (!smtp_input)
 	  {
 	  int len;
-	  uschar *s = Ustrchr(h->text, ':') + 1;
-	  while (isspace(*s)) s++;
+	  uschar * s = Ustrchr(h->text, ':') + 1;
+
+	  Uskip_whitespace(&s);
 	  len = h->slen - (s - h->text) - 1;
 	  if (Ustrlen(originator_login) == len &&
 	      strncmpic(s, originator_login, len) == 0)
 	    {
-	    uschar *name = is_resent? US"Resent-From" : US"From";
+	    uschar * name = is_resent ? US"Resent-From" : US"From";
 	    header_add(htype_from, "%s: %s <%s@%s>\n", name, originator_name,
 	      originator_login, qualify_domain_sender);
 	    from_header = header_last;
@@ -2514,15 +2514,13 @@ for (header_line * h = header_list->next; h; h = h->next)
 
       if (filter_test != FTEST_NONE)
 	{
-	uschar *start = h->text + 12;
-	uschar *end = start + Ustrlen(start);
-	while (isspace(*start)) start++;
+	uschar * start = h->text + 12;
+	uschar * end = start + Ustrlen(start);
+
+	Uskip_whitespace(&start);
 	while (end > start && isspace(end[-1])) end--;
 	if (*start == '<' && end[-1] == '>')
-	  {
-	  start++;
-	  end--;
-	  }
+	  { start++; end--; }
 	return_path = string_copyn(start, end - start);
 	printf("Return-path taken from \"Return-path:\" header line\n");
 	}
@@ -2626,7 +2624,7 @@ if (extract_recip)
         (!contains_resent_headers || strncmpic(h->text, US"resent-", 7) == 0))
       {
       uschar * s = Ustrchr(h->text, ':') + 1;
-      while (isspace(*s)) s++;
+      Uskip_whitespace(&s);
 
       f.parse_allow_group = TRUE;          /* Allow address group syntax */
 
@@ -2706,7 +2704,7 @@ if (extract_recip)
         /* Move on past this address */
 
         s = ss + (*ss ? 1 : 0);
-        while (isspace(*s)) s++;
+        Uskip_whitespace(&s);
         }    /* Next address */
 
       f.parse_allow_group = FALSE;      /* Reset group syntax flags */
diff --git a/src/src/rewrite.c b/src/src/rewrite.c
index 9ee5a6794..ce35c4365 100644
--- a/src/src/rewrite.c
+++ b/src/src/rewrite.c
@@ -452,7 +452,7 @@ header_line * newh = NULL;
 rmark function_reset_point = store_mark();
 uschar * s = Ustrchr(h->text, ':') + 1;
 
-while (isspace(*s)) s++;
+Uskip_whitespace(&s);
 
 DEBUG(D_rewrite)	/* The header text includes the trailing newline */
   debug_printf_indent("rewrite_one_header: type=%c:\n  %s", h->type, h->text);
@@ -493,7 +493,7 @@ while (*s)
   recipient = parse_extract_address(s, &errmess, &start, &end, &domain, FALSE);
   *ss1 = terminator;
   s = ss + (*ss ? 1 : 0);
-  while (isspace(*s)) s++;
+  Uskip_whitespace(&s);
 
   /* There isn't much we can do for syntactic disasters at this stage.
   Pro tem (possibly for ever) ignore them.
diff --git a/src/src/rfc2047.c b/src/src/rfc2047.c
index 18263f9ab..1ab25150a 100644
--- a/src/src/rfc2047.c
+++ b/src/src/rfc2047.c
@@ -313,8 +313,8 @@ while (mimeword)
   mimeword = decode_mimeword(string, lencheck, &q1, &q2, &endword, &dlen, &dptr);
   if (mimeword)
     {
-    uschar *s = string;
-    while (isspace(*s)) s++;
+    uschar * s = string;
+    Uskip_whitespace(&s);
     if (s == mimeword) string = s;
     }
   }
diff --git a/src/src/route.c b/src/src/route.c
index 0fdcdf2ed..afd43d866 100644
--- a/src/src/route.c
+++ b/src/src/route.c
@@ -266,12 +266,12 @@ for (router_instance * r = routers; r; r = r->next)
   else if (Ustrncmp(s, "reroute:", 8) == 0)
     {
     s += 8;
-    while (isspace(*s)) s++;
+    Uskip_whitespace(&s);
     if (Ustrncmp(s, "rewrite:", 8) == 0)
       {
       r->self_rewrite = TRUE;
       s += 8;
-      while (isspace(*s)) s++;
+      Uskip_whitespace(&s);
       }
     r->self = s;
     r->self_code = self_reroute;
@@ -693,13 +693,13 @@ while ((check = string_nextinlist(&listptr, &sep, NULL, 0)))
   if (*ss == '+')
     {
     eacces_code = 1;
-    while (isspace((*(++ss))));
+    while (isspace(*++ss));
     }
 
   if (*ss == '!')
     {
     invert = TRUE;
-    while (isspace((*(++ss))));
+    while (isspace(*++ss));
     }
 
   if (*ss != '/')
@@ -1463,7 +1463,7 @@ for (uschar * ele; (ele = string_nextinlist(&varlist, &sep, NULL, 0)); )
     }
   name += 2;
 
-  while (isspace(*assignment)) assignment++;
+  Uskip_whitespace(&assignment);
 
   if (!(val = expand_string(US assignment)))
     if (f.expand_string_forcedfail)
diff --git a/src/src/routers/manualroute.c b/src/src/routers/manualroute.c
index 45378ce3d..ed497336b 100644
--- a/src/src/routers/manualroute.c
+++ b/src/src/routers/manualroute.c
@@ -156,17 +156,17 @@ static BOOL
 parse_route_item(const uschar *s, const uschar **domain, const uschar **hostlist,
   const uschar **options)
 {
-while (*s != 0 && isspace(*s)) s++;
+Uskip_whitespace(&s);
 
 if (domain)
   {
   if (!*s) return FALSE;            /* missing data */
   *domain = string_dequote(&s);
-  while (*s && isspace(*s)) s++;
+  Uskip_whitespace(&s);
   }
 
 *hostlist = string_dequote(&s);
-while (*s && isspace(*s)) s++;
+Uskip_whitespace(&s);
 *options = s;
 return TRUE;
 }
@@ -333,8 +333,9 @@ lookup_type = LK_DEFAULT;
 while (*options)
   {
   unsigned n;
-  const uschar *s = options;
-  while (*options != 0 && !isspace(*options)) options++;
+  const uschar * s = options;
+
+  while (*options && !isspace(*options)) options++;
   n = options-s;
 
   if (Ustrncmp(s, "randomize", n) == 0) randomize = TRUE;
@@ -368,7 +369,7 @@ while (*options)
   if (*options)
     {
     options++;
-    while (*options != 0 && isspace(*options)) options++;
+    while (*options && isspace(*options)) options++;
     }
   }
 
diff --git a/src/src/routers/queryprogram.c b/src/src/routers/queryprogram.c
index b1d736f94..5f0f50cd9 100644
--- a/src/src/routers/queryprogram.c
+++ b/src/src/routers/queryprogram.c
@@ -363,10 +363,10 @@ buffer[len] = 0;
 DEBUG(D_route) debug_printf("command wrote: %s\n", buffer);
 
 rword = buffer;
-while (isspace(*rword)) rword++;
+Uskip_whitespace(&rword);
 rdata = rword;
 while (*rdata && !isspace(*rdata)) rdata++;
-if (*rdata) *rdata++ = 0;
+if (*rdata) *rdata++ = '\0';
 
 /* The word must be a known yield name. If it is "REDIRECT", the rest of the
 line is redirection data, as for a .forward file. It may not contain filter
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index f62d6fbf2..79176687d 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -1227,7 +1227,7 @@ for (smtp_cmd_list * p = cmd_list; p < cmd_list + nelem(cmd_list); p++)
     follow the sender address. */
 
     smtp_cmd_argument = smtp_cmd_buffer + p->len;
-    while (isspace(*smtp_cmd_argument)) smtp_cmd_argument++;
+    Uskip_whitespace(&smtp_cmd_argument);
     Ustrcpy(smtp_data_buffer, smtp_cmd_argument);
     smtp_cmd_data = smtp_data_buffer;
 
@@ -3807,8 +3807,8 @@ while (done <= 0)
 
       if (*smtp_cmd_data)
 	{
-	*smtp_cmd_data++ = 0;
-	while (isspace(*smtp_cmd_data)) smtp_cmd_data++;
+	*smtp_cmd_data++ = '\0';
+	Uskip_whitespace(&smtp_cmd_data);
 	}
 
       /* Search for an authentication mechanism which is configured for use
@@ -3919,10 +3919,10 @@ while (done <= 0)
       if (!f.sender_host_unknown)
 	{
 	BOOL old_helo_verified = f.helo_verified;
-	uschar *p = smtp_cmd_data;
+	uschar * p = smtp_cmd_data;
 
-	while (*p != 0 && !isspace(*p)) { *p = tolower(*p); p++; }
-	*p = 0;
+	while (*p && !isspace(*p)) { *p = tolower(*p); p++; }
+	*p = '\0';
 
 	/* Force a reverse lookup if HELO quoted something in helo_lookup_domains
 	because otherwise the log can be confusing. */
diff --git a/src/src/smtp_out.c b/src/src/smtp_out.c
index 895b594fc..888a0006e 100644
--- a/src/src/smtp_out.c
+++ b/src/src/smtp_out.c
@@ -704,13 +704,13 @@ if (format)
 
   if (outblock->authenticating)
     {
-    uschar *p = big_buffer;
+    uschar * p = big_buffer;
     if (Ustrncmp(big_buffer, "AUTH ", 5) == 0)
       {
       p += 5;
-      while (isspace(*p)) p++;
+      Uskip_whitespace(&p);
       while (!isspace(*p)) p++;
-      while (isspace(*p)) p++;
+      Uskip_whitespace(&p);
       }
     while (*p) *p++ = '*';
     }
diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c
index e6a1c6fa4..64e8c711a 100644
--- a/src/src/tls-openssl.c
+++ b/src/src/tls-openssl.c
@@ -5170,8 +5170,7 @@ if (!expand_check(option_spec, US"openssl_options", &exp, &end))
 
 for (uschar * s = exp; *s; /**/)
   {
-  while (isspace(*s)) ++s;
-  if (*s == '\0')
+  if (!Uskip_whitespace(&s))
     break;
   if (*s != '+' && *s != '-')
     {
diff --git a/src/src/transports/autoreply.c b/src/src/transports/autoreply.c
index 690ec4a92..67d48a1cb 100644
--- a/src/src/transports/autoreply.c
+++ b/src/src/transports/autoreply.c
@@ -235,13 +235,12 @@ that needs to be removed */
 
 s = newlist + Ustrlen(newlist);
 while (s > newlist && (isspace(s[-1]) || s[-1] == ',')) s--;
-*s = 0;
+*s = '\0';
 
 /* Check to see if there any addresses left; if not, return NULL */
 
 s = newlist;
-while (s && isspace(*s)) s++;
-if (*s)
+if (Uskip_whitespace(&s))
   return newlist;
 
 store_reset(reset_point);
@@ -592,7 +591,7 @@ for (h = header_list; h; h = h->next)
 if (h)
   {
   message_id = Ustrchr(h->text, ':') + 1;
-  while (isspace(*message_id)) message_id++;
+  Uskip_whitespace(&message_id);
   fprintf(fp, "In-Reply-To: %s", message_id);
   }
 
diff --git a/src/src/transports/pipe.c b/src/src/transports/pipe.c
index 60a20c8fe..f8a1427b9 100644
--- a/src/src/transports/pipe.c
+++ b/src/src/transports/pipe.c
@@ -562,7 +562,7 @@ if (testflag(addr, af_pfr) && addr->local_part[0] == '|')
   else
     {
     cmd = addr->local_part + 1;
-    while (isspace(*cmd)) cmd++;
+    Uskip_whitespace(&cmd);
     expand_arguments = testflag(addr, af_expand_pipe);
     expand_fail = FAIL;
     }
diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index 736592352..ed5994241 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -533,7 +533,7 @@ switch(*errno_value)
 
   case ERRNO_SMTPFORMAT:	/* Handle malformed SMTP response */
     s = string_printing(buffer);
-    while (isspace(*s)) s++;
+    Uskip_whitespace(&s);
     *message = *s == 0
       ? string_sprintf("Malformed SMTP reply (an empty line) "
 	  "in response to %s%s", pl, smtp_command)
@@ -815,8 +815,7 @@ uschar * match;
 if (regex_match(regex_LIMITS, sx->buffer, -1, &match))
   for (const uschar * s = sx->buffer + Ustrlen(match); *s; )
     {
-    while (isspace(*s)) s++;
-    if (*s == '\n') break;
+    if (Uskip_whitespace(&s) == '\n') break;
 
     if (strncmpic(s, US"MAILMAX=", 8) == 0)
       {
@@ -1694,8 +1693,8 @@ if (  sx->esmtp
 	{
 	DEBUG(D_transport) debug_printf("skipping %s authenticator: %s\n",
 	  au->name,
-	  (au->client)? "client_condition is false" :
-			"not configured as a client");
+	  au->client ? "client_condition is false"
+		    : "not configured as a client");
 	continue;
 	}
 
@@ -1703,15 +1702,14 @@ if (  sx->esmtp
 
       while (*p)
 	{
-	int len = Ustrlen(au->public_name);
-	int rc;
+	int len = Ustrlen(au->public_name), rc;
 
-	while (isspace(*p)) p++;
+	Uskip_whitespace(&p);
 
 	if (strncmpic(au->public_name, p, len) != 0 ||
-	    (p[len] != 0 && !isspace(p[len])))
+	    (p[len] && !isspace(p[len])))
 	  {
-	  while (*p != 0 && !isspace(*p)) p++;
+	  while (*p && !isspace(*p)) p++;
 	  continue;
 	  }
 
diff --git a/src/src/verify.c b/src/src/verify.c
index 78923651c..4f3bafb04 100644
--- a/src/src/verify.c
+++ b/src/src/verify.c
@@ -2612,12 +2612,12 @@ for (int i = 0; i < 3 && !done; i++)
 
     f.parse_allow_group = TRUE;
 
-    while (*s != 0)
+    while (*s)
       {
-      address_item *vaddr;
+      address_item * vaddr;
 
       while (isspace(*s) || *s == ',') s++;
-      if (*s == 0) break;        /* End of header */
+      if (!*s) break;			/* End of header */
 
       ss = parse_find_address_end(s, FALSE);
 
@@ -2628,7 +2628,7 @@ for (int i = 0; i < 3 && !done; i++)
 
       while (isspace(ss[-1])) ss--;
       terminator = *ss;
-      *ss = 0;
+      *ss = '\0';
 
       HDEBUG(D_verify) debug_printf("verifying %.*s header address %s\n",
         (int)(endname - h->text), h->text, s);
@@ -2873,17 +2873,17 @@ if (sscanf(CS buffer + qlen, "%d , %d%n", &received_sender_port,
   goto END_OFF;
 
 p = buffer + qlen + n;
-while(isspace(*p)) p++;
+Uskip_whitespace(&p);
 if (*p++ != ':') goto END_OFF;
-while(isspace(*p)) p++;
+Uskip_whitespace(&p);
 if (Ustrncmp(p, "USERID", 6) != 0) goto END_OFF;
 p += 6;
-while(isspace(*p)) p++;
+Uskip_whitespace(&p);
 if (*p++ != ':') goto END_OFF;
-while (*p != 0 && *p != ':') p++;
-if (*p++ == 0) goto END_OFF;
-while(isspace(*p)) p++;
-if (*p == 0) goto END_OFF;
+while (*p && *p != ':') p++;
+if (!*p++) goto END_OFF;
+Uskip_whitespace(&p);
+if (!*p) goto END_OFF;
 
 /* The rest of the line is the data we want. We turn it into printing
 characters when we save it, so that it cannot mess up the format of any logging
@@ -3083,7 +3083,7 @@ if (iplookup)
     key = filename;
     while (*key != 0 && !isspace(*key)) key++;
     filename = string_copyn(filename, key - filename);
-    while (isspace(*key)) key++;
+    Uskip_whitespace(&key);
     }
   else if (mac_islookup(search_type, lookup_querystyle))
     {
diff --git a/src/src/xclient.c b/src/src/xclient.c
index 2a8be9b0e..2219cb027 100644
--- a/src/src/xclient.c
+++ b/src/src/xclient.c
@@ -248,7 +248,7 @@ for (state = XCLIENT_SKIP_SPACES; *s; )
       }
 
     case XCLIENT_SKIP_SPACES:
-      while (*s && isspace (*s)) s++;
+      Uskip_whitespace(&s);
       state = XCLIENT_READ_COMMAND;
       break;
 

commit 3f77bd134b64c532748b83c3931df07058268b5e
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Tue Mar 26 19:52:49 2024 +0000

    tidying

diff --git a/src/exim_monitor/em_log.c b/src/exim_monitor/em_log.c
index fbcb6b1c0..49f649d7e 100644
--- a/src/exim_monitor/em_log.c
+++ b/src/exim_monitor/em_log.c
@@ -294,7 +294,7 @@ if (LOG != NULL)
 
     if ((p = Ustrstr(buffer, "==")) != NULL)
       {
-      queue_item *qq = find_queue(id, queue_noop, 0);
+      queue_item * qq = find_queue(id, queue_noop, 0);
       if (qq)
         {
         dest_item *d;
@@ -302,14 +302,12 @@ if (LOG != NULL)
         p += 2;
         while (isspace(*p)) p++;
         q = p;
-        while (*p != 0 && !isspace(*p))
+        while (*p && !isspace(*p))
           {
           if (*p++ != '\"') continue;
-          while (*p != 0)
-            {
+          while (*p)
             if (*p == '\\') p += 2;
-              else if (*p++ == '\"') break;
-            }
+            else if (*p++ == '\"') break;
           }
         *p++ = 0;
         if ((r = strstric(q, qualify_domain, FALSE)) != NULL &&
diff --git a/src/src/auths/cram_md5.c b/src/src/auths/cram_md5.c
index 8f00ee498..5817be7c9 100644
--- a/src/src/auths/cram_md5.c
+++ b/src/src/auths/cram_md5.c
@@ -198,7 +198,7 @@ The former is now the preferred variable; the latter is the original one. Then
 check that the remaining length is 32. */
 
 auth_vars[0] = expand_nstring[1] = clear;
-while (*clear && !isspace(*clear)) clear++;
+Uskip_nonwhite(&clear);
 if (!isspace(*clear)) return FAIL;
 *clear++ = 0;
 
diff --git a/src/src/dbfn.c b/src/src/dbfn.c
index 13a3c2d66..389d1518e 100644
--- a/src/src/dbfn.c
+++ b/src/src/dbfn.c
@@ -544,8 +544,8 @@ while (Ufgets(buffer, 256, stdin) != NULL)
 
     Uskip_whitespace(&key);
     data = key;
-    while (*data && !isspace((uschar)*data)) data++;
-    *data++ = 0;
+    Uskip_nonwhite(&data);
+    *data++ = '\0';
     Uskip_whitespace(&data);
 
     dbwait = (dbdata_wait *)(&structbuffer);
diff --git a/src/src/exim_dbmbuild.c b/src/src/exim_dbmbuild.c
index 9f5f2f042..bd24faa5b 100644
--- a/src/src/exim_dbmbuild.c
+++ b/src/src/exim_dbmbuild.c
@@ -379,7 +379,7 @@ while (Ufgets(line, max_insize, f) != NULL)
     else
       {
       keystart = s;
-      while (*s != 0 && *s != ':' && !isspace(*s)) s++;
+      while (*s && *s != ':' && !isspace(*s)) s++;
       exim_datum_size_set(&key, s - keystart + add_zero);
       }
 
@@ -401,11 +401,11 @@ while (Ufgets(line, max_insize, f) != NULL)
     keybuffer[i] = 0;
     started = 1;
 
-    while (isspace(*s))s++;
+    while (isspace(*s)) s++;
     if (*s == ':')
       {
       s++;
-      while (isspace(*s))s++;
+      while (isspace(*s)) s++;
       }
     if (*s != 0)
       {
diff --git a/src/src/functions.h b/src/src/functions.h
index 0e657d7c4..d811c62a3 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -767,6 +767,12 @@ Return the next char as there's enough places using it to be useful. */
 static inline uschar skip_whitespace(const uschar ** sp)
 { while (isspace(**sp)) (*sp)++; return **sp; }
 
+/* Ditto, non-whitespace */
+
+#define Uskip_nonwhite(sp) skip_nonwhite(CUSS sp)
+static inline uschar skip_nonwhite(const uschar ** sp)
+{ while (**sp && !isspace(**sp)) (*sp)++; return **sp; }
+
 
 /******************************************************************************/
 
diff --git a/src/src/routers/manualroute.c b/src/src/routers/manualroute.c
index ed497336b..ffd6eeaca 100644
--- a/src/src/routers/manualroute.c
+++ b/src/src/routers/manualroute.c
@@ -335,7 +335,7 @@ while (*options)
   unsigned n;
   const uschar * s = options;
 
-  while (*options && !isspace(*options)) options++;
+  Uskip_nonwhite(&options);
   n = options-s;
 
   if (Ustrncmp(s, "randomize", n) == 0) randomize = TRUE;
@@ -369,7 +369,7 @@ while (*options)
   if (*options)
     {
     options++;
-    while (*options && isspace(*options)) options++;
+    Uskip_whitespace(&options);
     }
   }
 
diff --git a/src/src/routers/queryprogram.c b/src/src/routers/queryprogram.c
index 5f0f50cd9..39645cd23 100644
--- a/src/src/routers/queryprogram.c
+++ b/src/src/routers/queryprogram.c
@@ -365,7 +365,7 @@ DEBUG(D_route) debug_printf("command wrote: %s\n", buffer);
 rword = buffer;
 Uskip_whitespace(&rword);
 rdata = rword;
-while (*rdata && !isspace(*rdata)) rdata++;
+Uskip_nonwhite(&rdata);
 if (*rdata) *rdata++ = '\0';
 
 /* The word must be a known yield name. If it is "REDIRECT", the rest of the
diff --git a/src/src/search.c b/src/src/search.c
index 168c25e6a..d0eb84b3b 100644
--- a/src/src/search.c
+++ b/src/src/search.c
@@ -247,7 +247,7 @@ if (mac_islookup(search_type, lookup_absfilequery))
   if (*query == '/')
     {
     uschar * s = query;
-    while (*query && !isspace(*query)) query++;
+    Uskip_nonwhite(&query);
     *fnamep = string_copyn(s, query - s);
     Uskip_whitespace(&query);
     }
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index 79176687d..70e4b95df 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -1946,6 +1946,9 @@ while (done <= 0)
     case HELP_CMD:
     case NOOP_CMD:
     case ETRN_CMD:
+#ifdef EXPERIMENTAL_WELLKNOWN
+    case WELLKNOWN_CMD:
+#endif
       bsmtp_transaction_linecount = receive_linecount;
       break;
 
@@ -2991,8 +2994,8 @@ switch (where)
 
     if (where == ACL_WHERE_AUTH)	/* avoid logging auth creds */
       {
-      uschar * s;
-      for (s = smtp_cmd_data; *s && !isspace(*s); ) s++;
+      uschar * s = smtp_cmd_data;
+      Uskip_nonwhite(&s);
       lim = s - smtp_cmd_data;	/* atop after method */
       }
     what = string_sprintf("%s %.*s", acl_wherenames[where], lim, place);
@@ -5694,7 +5697,7 @@ while (done <= 0)
 
     case TOO_MANY_NONMAIL_CMD:
       s = smtp_cmd_buffer;
-      while (*s && !isspace(*s)) s++;
+      Uskip_nonwhite(&s);
       incomplete_transaction_log(US"too many non-mail commands");
       log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
 	"nonmail commands (last was \"%.*s\")",  host_and_ident(FALSE),
diff --git a/src/src/smtp_out.c b/src/src/smtp_out.c
index 888a0006e..a78b15fbd 100644
--- a/src/src/smtp_out.c
+++ b/src/src/smtp_out.c
@@ -709,7 +709,7 @@ if (format)
       {
       p += 5;
       Uskip_whitespace(&p);
-      while (!isspace(*p)) p++;
+      Uskip_nonwhite(&p);
       Uskip_whitespace(&p);
       }
     while (*p) *p++ = '*';
diff --git a/src/src/string.c b/src/src/string.c
index af187c199..379be2035 100644
--- a/src/src/string.c
+++ b/src/src/string.c
@@ -649,7 +649,7 @@ uschar * t, * yield;
 /* First find the end of the string */
 
 if (*s != '\"')
-  while (*s && !isspace(*s)) s++;
+  Uskip_nonwhite(&s);
 else
   {
   s++;
diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c
index 64e8c711a..34612adea 100644
--- a/src/src/tls-openssl.c
+++ b/src/src/tls-openssl.c
@@ -5179,7 +5179,8 @@ for (uschar * s = exp; *s; /**/)
     return FALSE;
     }
   adding = *s++ == '+';
-  for (end = s; *end && !isspace(*end); ) end++;
+  end = s;
+  Uskip_nonwhite(&end);
   item_parsed = tls_openssl_one_option_parse(string_copyn(s, end-s), &item);
   if (!item_parsed)
     {
diff --git a/src/src/verify.c b/src/src/verify.c
index 4f3bafb04..065deec41 100644
--- a/src/src/verify.c
+++ b/src/src/verify.c
@@ -3081,7 +3081,7 @@ if (iplookup)
     {
     filename = semicolon + 1;
     key = filename;
-    while (*key != 0 && !isspace(*key)) key++;
+    Uskip_nonwhite(&key);
     filename = string_copyn(filename, key - filename);
     Uskip_whitespace(&key);
     }
diff --git a/src/src/xclient.c b/src/src/xclient.c
index 2219cb027..fb9d95c5e 100644
--- a/src/src/xclient.c
+++ b/src/src/xclient.c
@@ -172,7 +172,7 @@ for (state = XCLIENT_SKIP_SPACES; *s; )
       uschar * val;
 
       word = ++s;			/* skip the = */
-      while (*s && !isspace(*s)) s++;
+      Uskip_nonwhite(&s);
       len = s - word;
 
       DEBUG(D_transport) debug_printf(" XCLIENT: \tvalue %.*s\n", len, word);

commit f168d6a2c93ee92d87744ce09c45a0ec666f889c
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Wed Mar 27 15:40:03 2024 +0000

    typoes

diff --git a/src/src/globals.c b/src/src/globals.c
index e23188b34..2885caa23 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -387,7 +387,7 @@ BOOL    mua_wrapper            = FALSE;
 BOOL    preserve_message_logs  = FALSE;
 BOOL    print_topbitchars      = FALSE;
 BOOL    prod_requires_admin    = TRUE;
-#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS) || defined(EXPERIMETAL_XCLIENT)
+#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS) || defined(EXPERIMENTAL_XCLIENT)
 BOOL    proxy_session          = FALSE;
 #endif
 
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index 70e4b95df..f65ab6603 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -2554,7 +2554,7 @@ if (!f.sender_host_unknown)
 
 if (smtp_batched_input) return TRUE;
 
-#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS) || defined(EXPERIMETAL_XCLIENT)
+#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS) || defined(EXPERIMENTAL_XCLIENT)
 proxy_session = FALSE;
 #endif
 

commit 4e19eed51a7f777e1e18b60c636e0c9bfb82c23b
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Wed Mar 27 16:04:50 2024 +0000

    Fix encoding for AUTH= on MAIL FROM

diff --git a/src/src/auths/xtextencode.c b/src/src/auths/xtextencode.c
index c08288831..75be18161 100644
--- a/src/src/auths/xtextencode.c
+++ b/src/src/auths/xtextencode.c
@@ -29,31 +29,14 @@ Returns:      a pointer to the zero-terminated xtext string, which
 uschar *
 auth_xtextencode(uschar *clear, int len)
 {
-uschar *code;
-uschar *p = US clear;
-uschar *pp;
-int c = len;
-int count = 1;
-register int x;
-
-/* We have to do a prepass to find out how many specials there are,
-in order to get the right amount of store. */
-
-while (c -- > 0)
-  count += ((x = *p++) < 33 || x > 127 || x == '+' || x == '=')? 3 : 1;
-
-pp = code = store_get(count, clear);
-
-p = US clear;
-c = len;
-while (c-- > 0)
-  if ((x = *p++) < 33 || x > 127 || x == '+' || x == '=')
-    pp += sprintf(CS pp, "+%.02x", x);   /* There's always room */
-  else
-    *pp++ = x;
-
-*pp = 0;
-return code;
+gstring * g = NULL;
+for(uschar ch; len > 0; len--, clear++)
+  g = (ch = *clear) < 33 || ch > 126 || ch == '+' || ch == '='
+    ? string_fmt_append(g, "+%.02X", ch)
+    : string_catn(g, clear, 1);
+gstring_release_unused(g);
+return string_from_gstring(g);
 }
 
+
 /* End of xtextencode.c */

commit a600ccb4a236549ed1a950d3fc783a1e2e9de2cb
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Mar 29 17:37:05 2024 +0000

    Logging: fix receive-time crash with recipients-logging.
    
    Broken-by: d12746bc15d8

diff --git a/src/src/log.c b/src/src/log.c
index f85823520..1a77f2cdb 100644
--- a/src/src/log.c
+++ b/src/src/log.c
@@ -601,6 +601,22 @@ if (type == lt_debug) unlink(CS debuglog_name);
 
 
 
+/* Append to a gstring, in no-extend (or rebuffer) mode
+and without taint-checking.  Thanks to the no-extend, the
+gstring struct never needs reallocation; we ignore the
+infore that the initial space allocation for the string
+was exceeded, and just leave it potentially truncated. */
+
+static void
+string_fmt_append_noextend(gstring * g, char * fmt, ...)
+{
+va_list ap;
+va_start(ap, fmt);
+/* can return NULL if it truncates; just ignore */
+(void) string_vformat(g, SVFMT_TAINT_NOCHK, fmt, ap);
+va_end(ap);
+}
+
 /*************************************************
 *     Add configuration file info to log line    *
 *************************************************/
@@ -609,24 +625,28 @@ if (type == lt_debug) unlink(CS debuglog_name);
 once for real).
 
 Arguments:
-  ptr         pointer to the end of the line we are building
-  flags       log flags
+  g		extensible-string referring to static buffer,
+		usable after return
+  flags		log flags
 
-Returns:      updated pointer
+Returns:      nothing
 */
 
-static gstring *
+static void
 log_config_info(gstring * g, int flags)
 {
-g = string_cat(g, US"Exim configuration error");
+string_fmt_append_noextend(g, "Exim configuration error");
 
 if (flags & (LOG_CONFIG_FOR & ~LOG_CONFIG))
-  return string_cat(g, US" for ");
-
-if (flags & (LOG_CONFIG_IN & ~LOG_CONFIG))
-  g = string_fmt_append(g, " in line %d of %s", config_lineno, config_filename);
+  string_fmt_append_noextend(g, " for ");
+else
+  {
+  if (flags & (LOG_CONFIG_IN & ~LOG_CONFIG))
+    string_fmt_append_noextend(g, " in line %d of %s",
+				  config_lineno, config_filename);
 
-return string_catn(g, US":\n  ", 4);
+  string_fmt_append_noextend(g, ":\n  ");
+  }
 }
 
 
@@ -813,7 +833,7 @@ log_write(unsigned int selector, int flags, const char *format, ...)
 int paniclogfd;
 ssize_t written_len;
 gstring gs = { .size = LOG_BUFFER_SIZE-2, .ptr = 0, .s = log_buffer };
-gstring * g;
+gstring * g = &gs;
 va_list ap;
 
 /* If panic_recurseflag is set, we have failed to open the panic log. This is
@@ -823,7 +843,7 @@ original log line that caused the problem. Afterwards, expire. */
 
 if (panic_recurseflag)
   {
-  uschar *extra = panic_save_buffer ? panic_save_buffer : US"";
+  uschar * extra = panic_save_buffer ? panic_save_buffer : US"";
   if (debug_file) debug_printf("%s%s", extra, log_buffer);
   if (log_stderr && log_stderr != debug_file)
     fprintf(log_stderr, "%s%s", extra, log_buffer);
@@ -924,45 +944,43 @@ in one go so that it doesn't get split when multi-processing. */
 
 DEBUG(D_any|D_v)
   {
-  int i;
-
-  g = string_catn(&gs, US"LOG:", 4);
+  string_fmt_append_noextend(g, "LOG:");
 
   /* Show the selector that was passed into the call. */
 
-  for (i = 0; i < log_options_count; i++)
+  for (int i = 0; i < log_options_count; i++)
     {
     unsigned int bitnum = log_options[i].bit;
     if (bitnum < BITWORDSIZE && selector == BIT(bitnum))
-      g = string_fmt_append(g, " %s", log_options[i].name);
+      string_fmt_append_noextend(g, " %s", log_options[i].name);
     }
 
-  g = string_fmt_append(g, "%s%s%s%s\n  ",
+  string_fmt_append_noextend(g, "%s%s%s%s\n  ",
     flags & LOG_MAIN ?    " MAIN"   : "",
     flags & LOG_PANIC ?   " PANIC"  : "",
     (flags & LOG_PANIC_DIE) == LOG_PANIC_DIE ? " DIE" : "",
     flags & LOG_REJECT ?  " REJECT" : "");
 
-  if (flags & LOG_CONFIG) g = log_config_info(g, flags);
+  if (flags & LOG_CONFIG) log_config_info(g, flags);
 
   /* We want to be able to log tainted info, but log_buffer is directly
   malloc'd.  So use deliberately taint-nonchecking routines to build into
   it, trusting that we will never expand the results. */
 
   va_start(ap, format);
-  i = g->ptr;
   if (!string_vformat(g, SVFMT_TAINT_NOCHK, format, ap))
     {
-    g->ptr = i;
-    g = string_cat(g, US"**** log string overflowed log buffer ****");
+    uschar * s = US"**** log string overflowed log buffer ****";
+    gstring_trim(g, Ustrlen(s));
+    string_fmt_append_noextend(g, "%s", s);
     }
   va_end(ap);
 
   debug_printf("%Y\n", g);
 
-  gs.size = LOG_BUFFER_SIZE-2;	/* Having used the buffer for debug output, */
-  gs.ptr = 0;			/* reset it for the real use. */
-  gs.s = log_buffer;
+  /* Having used the buffer for debug output, reset it for the real use. */
+
+  gstring_reset(g);
   }
 /* If no log file is specified, we are in a mess. */
 
@@ -986,33 +1004,32 @@ if (!write_rejectlog) flags &= ~LOG_REJECT;
 /* Create the main message in the log buffer. Do not include the message id
 when called by a utility. */
 
-g = string_fmt_append(&gs, "%s ", tod_stamp(tod_log));
+string_fmt_append_noextend(&gs, "%s ", tod_stamp(tod_log));
 
 if (LOGGING(pid))
   {
   if (!syslog_pid) pid_position[0] = g->ptr;		/* remember begin … */
-  g = string_fmt_append(g, "[%d] ", (int)getpid());
+  string_fmt_append_noextend(g, "[%ld] ", (long)getpid());
   if (!syslog_pid) pid_position[1] = g->ptr;		/*  … and end+1 of the PID */
   }
 
 if (f.really_exim && message_id[0])
-  g = string_fmt_append(g, "%s ", message_id);
+  string_fmt_append_noextend(g, "%s ", message_id);
 
 if (flags & LOG_CONFIG)
-  g = log_config_info(g, flags);
+  log_config_info(g, flags);
 
 va_start(ap, format);
   {
-  int i = g->ptr;
-
   /* We want to be able to log tainted info, but log_buffer is directly
   malloc'd.  So use deliberately taint-nonchecking routines to build into
   it, trusting that we will never expand the results. */
 
   if (!string_vformat(g, SVFMT_TAINT_NOCHK, format, ap))
     {
-    g->ptr = i;
-    g = string_cat(g, US"**** log string overflowed log buffer ****\n");
+    uschar * s = US"**** log string overflowed log buffer ****\n";
+    gstring_trim(g, Ustrlen(s));
+    string_fmt_append_noextend(g, "%s", s);
     }
   }
 va_end(ap);
@@ -1020,30 +1037,29 @@ va_end(ap);
 /* Add the raw, unrewritten, sender to the message if required. This is done
 this way because it kind of fits with LOG_RECIPIENTS. */
 
-if (   flags & LOG_SENDER
-   && g->ptr < LOG_BUFFER_SIZE - 10 - Ustrlen(raw_sender))
-  g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " from <%s>", raw_sender);
+if (flags & LOG_SENDER)
+  string_fmt_append_noextend(g, " from <%s>", raw_sender);
 
 /* Add list of recipients to the message if required; the raw list,
 before rewriting, was saved in raw_recipients. There may be none, if an ACL
 discarded them all. */
 
-if (  flags & LOG_RECIPIENTS
-   && g->ptr < LOG_BUFFER_SIZE - 6
-   && raw_recipients_count > 0)
+if (flags & LOG_RECIPIENTS && raw_recipients_count > 0)
   {
-  g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " for", NULL);
+  string_fmt_append_noextend(g, " for");
   for (int i = 0; i < raw_recipients_count; i++)
-    {
-    const uschar * s = raw_recipients[i];
-    if (LOG_BUFFER_SIZE - g->ptr < Ustrlen(s) + 3) break;
-    g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " %s", s);
+    if (!string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " %s", raw_recipients[i]))
+      {
+    uschar * s = US"<trunc>";
+    gstring_trim(g, Ustrlen(s));
+    string_fmt_append_noextend(g, "%s", s);
+    break;
     }
   }
 
 /* actual size, now we are placing the newline (and space for NUL) */
 gs.size = LOG_BUFFER_SIZE;
-g = string_catn(g, US"\n", 1);
+string_fmt_append_noextend(g, "\n");
 string_from_gstring(g);
 
 /* Handle loggable errors when running a utility, or when address testing.
@@ -1117,8 +1133,7 @@ if (  flags & LOG_MAIN
 
     /* Failing to write to the log is disastrous */
 
-    written_len = write_gstring_to_fd_buf(mainlogfd, g);
-    if (written_len != g->ptr)
+    if ((written_len = write_gstring_to_fd_buf(mainlogfd, g)) != g->ptr)
       {
       log_write_failed(US"main log", g->ptr, written_len);
       /* That function does not return */
@@ -1135,53 +1150,39 @@ if (flags & LOG_REJECT)
   {
   if (header_list && LOGGING(rejected_header))
     {
-    gstring * g2;
     int i;
 
     if (recipients_count > 0)
       {
       /* List the sender */
 
-      g2 = string_fmt_append_f(g, SVFMT_TAINT_NOCHK,
+      string_fmt_append_noextend(g,
 			"Envelope-from: <%s>\n", sender_address);
-      if (g2) g = g2;
 
       /* List up to 5 recipients */
 
-      g2 = string_fmt_append_f(g, SVFMT_TAINT_NOCHK,
+      string_fmt_append_noextend(g,
 			"Envelope-to: <%s>\n", recipients_list[0].address);
-      if (g2) g = g2;
 
       for (i = 1; i < recipients_count && i < 5; i++)
-        {
-        g2 = string_fmt_append_f(g, SVFMT_TAINT_NOCHK,
+        string_fmt_append_noextend(g,
 			"    <%s>\n", recipients_list[i].address);
-	if (g2) g = g2;
-        }
 
       if (i < recipients_count)
-        {
-        g2 = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, "    ...\n", NULL);
-	if (g2) g = g2;
-        }
+        string_fmt_append_noextend(g, "    ...\n", NULL);
       }
 
-    /* A header with a NULL text is an unfilled in Received: header */
+    /* A header with a NULL text is an unfilled-in Received: header */
 
     for (header_line * h = header_list; h; h = h->next) if (h->text)
-      {
-      g2 = string_fmt_append_f(g, SVFMT_TAINT_NOCHK,
-			"%c %s", h->type, h->text);
-      if (g2)
-	g = g2;
-      else		/* Buffer is full; truncate */
-        {
-	gstring_trim(g, 100);        /* For message and separator */
+      if (!string_fmt_append_f(g, SVFMT_TAINT_NOCHK,
+			"%c %s", h->type, h->text))
+        {				/* Buffer is full; truncate */
+	gstring_trim(g, 100);		/* space for message and separator */
 	gstring_trim_trailing(g, '\n');
-        g = string_cat(g, US"\n*** truncated ***\n");
+        string_fmt_append_noextend(g, "\n*** truncated ***\n");
         break;
         }
-      }
     }
 
   /* Write to syslog or to a log file */
@@ -1232,8 +1233,7 @@ if (flags & LOG_REJECT)
       if (fstat(rejectlogfd, &statbuf) >= 0) rejectlog_inode = statbuf.st_ino;
       }
 
-    written_len = write_gstring_to_fd_buf(rejectlogfd, g);
-    if (written_len != g->ptr)
+    if ((written_len = write_gstring_to_fd_buf(rejectlogfd, g)) != g->ptr)
       {
       log_write_failed(US"reject log", g->ptr, written_len);
       /* That function does not return */
@@ -1267,8 +1267,7 @@ if (flags & LOG_PANIC)
     if (panic_save_buffer)
       (void) write(paniclogfd, panic_save_buffer, Ustrlen(panic_save_buffer));
 
-    written_len = write_gstring_to_fd_buf(paniclogfd, g);
-    if (written_len != g->ptr)
+    if ((written_len = write_gstring_to_fd_buf(paniclogfd, g)) != g->ptr)
       {
       int save_errno = errno;
       write_syslog(LOG_CRIT, log_buffer);
@@ -1562,3 +1561,5 @@ if (kill) unlink_log(lt_debug);
 
 
 /* End of log.c */
+/* vi: sw ai sw=2
+*/
diff --git a/src/src/string.c b/src/src/string.c
index 379be2035..baa19f426 100644
--- a/src/src/string.c
+++ b/src/src/string.c
@@ -1333,7 +1333,7 @@ Arguments:
 	ap	variable-args pointer
 
 Flags:
-	SVFMT_EXTEND            buffer can be created or exteded as needed
+	SVFMT_EXTEND            buffer can be created or extended as needed
 	SVFMT_REBUFFER          buffer can be recopied to tainted mem as needed
 	SVFMT_TAINT_NOCHK       do not check inputs for taint
 

commit ad64ad24d66f32af7af302b625f7a7be0ac6b988
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Mar 29 20:42:47 2024 +0000

    inlining

diff --git a/src/src/functions.h b/src/src/functions.h
index d811c62a3..b2f948650 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -567,7 +567,6 @@ extern gstring *string_append_listele_n(gstring *, uschar, const uschar *, unsig
 extern gstring *string_append2_listele_n(gstring *, const uschar *, const uschar *, unsigned) WARN_UNUSED_RESULT;
 extern uschar *string_base62_32(unsigned long int);
 extern uschar *string_base62_64(unsigned long int);
-extern gstring *string_cat (gstring *, const uschar *     ) WARN_UNUSED_RESULT;
 extern gstring *string_catn(gstring *, const uschar *, int) WARN_UNUSED_RESULT;
 extern int     string_compare_by_pointer(const void *, const void *);
 extern uschar *string_copy_dnsdomain(uschar *);
@@ -1050,6 +1049,18 @@ if (g) store_release_above_3(g->s + (g->size = g->ptr + 1), file, line);
 }
 
 
+/* plain string append to a growable-string */
+
+static inline gstring * string_cat(gstring * g, const uschar * s)
+ WARN_UNUSED_RESULT;
+
+static inline gstring *
+string_cat(gstring * g, const uschar * s)
+{
+return string_catn(g, s, Ustrlen(s));
+}
+
+
 /* sprintf-append to a growable-string */
 
 #define string_fmt_append(g, fmt, ...) \
@@ -1085,6 +1096,7 @@ g->s = s;
 }
 
 /* Append one gstring to another */
+
 static inline gstring *
 gstring_append(gstring * dest, gstring * item)
 {
diff --git a/src/src/string.c b/src/src/string.c
index baa19f426..cd76e1f1c 100644
--- a/src/src/string.c
+++ b/src/src/string.c
@@ -1234,13 +1234,6 @@ return g;
 }
 
 
-gstring *
-string_cat(gstring * g, const uschar * s)
-{
-return string_catn(g, s, Ustrlen(s));
-}
-
-
 
 /*************************************************
 *        Append strings to another string        *

commit 5e0ccec4397bd2d21cc1473681a62e7c2fb6afbe
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Thu Apr 4 15:53:53 2024 +0100

    Retries: rework DB keys, and fix exinext IPv6. Bug 3086

diff --git a/src/src/deliver.c b/src/src/deliver.c
index 70f44535f..0157743a1 100644
--- a/src/src/deliver.c
+++ b/src/src/deliver.c
@@ -2039,7 +2039,7 @@ static BOOL
 previously_transported(address_item *addr, BOOL testing)
 {
 uschar * s = string_sprintf("%s/%s",
-  addr->unique + (testflag(addr, af_homonym)? 3:0), addr->transport->name);
+  addr->unique + (testflag(addr, af_homonym) ? 3:0), addr->transport->name);
 
 if (tree_search(tree_nonrecipients, s) != 0)
   {
@@ -3142,7 +3142,7 @@ while (addr_local)
     if (result == DEFER || testflag(addr2, af_lt_retry_exists))
       {
       int flags = result == DEFER ? 0 : rf_delete;
-      uschar *retry_key = string_copy(tp->retry_use_local_part
+      uschar * retry_key = string_copy(tp->retry_use_local_part
 	? addr2->address_retry_key : addr2->domain_retry_key);
       *retry_key = 'T';
       retry_add_item(addr2, retry_key, flags);
@@ -7292,7 +7292,7 @@ while (addr_new)           /* Loop until all addresses dealt with */
 
       addr->unique =
         string_sprintf("%s:%s", addr->address, addr->parent->unique +
-          (testflag(addr->parent, af_homonym)? 3:0));
+          (testflag(addr->parent, af_homonym) ? 3:0));
 
       addr->address_retry_key = addr->domain_retry_key =
         string_sprintf("T:%s", addr->unique);
@@ -7729,7 +7729,7 @@ while (addr_new)           /* Loop until all addresses dealt with */
 
     else if (testflag(addr, af_dr_retry_exists))
       {
-      uschar *altkey = string_sprintf("%s:<%s>", addr->address_retry_key,
+      uschar * altkey = string_sprintf("%s:<%s>", addr->address_retry_key,
         sender_address);
       retry_add_item(addr, altkey, rf_delete);
       retry_add_item(addr, addr->address_retry_key, rf_delete);
diff --git a/src/src/exim_dbutil.c b/src/src/exim_dbutil.c
index 4d213773b..c868832b1 100644
--- a/src/src/exim_dbutil.c
+++ b/src/src/exim_dbutil.c
@@ -628,7 +628,7 @@ for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
 	  print_time(retry->first_failed));
 	printf("%s  ", print_time(retry->last_try));
 	printf("%s %s\n", print_time(retry->next_try),
-	  (retry->expired)? "*" : "");
+	  retry->expired ? "*" : "");
 	break;
 
       case type_wait:
diff --git a/src/src/exinext.src b/src/src/exinext.src
index 882ad5f60..5724812c4 100644
--- a/src/src/exinext.src
+++ b/src/src/exinext.src
@@ -195,45 +195,18 @@ perl - $exim_path "$eximmacdef" $argone $spool_directory $qualify_domain $config
         $printed = 1;
         if (/^\s*T:[^:\s]*:/)
           {
+	  # We rely on non-space-containing strings, for parsing
+
           ($key,$error,$error2,$text) = /^\s*T:(\S+)\s+(\S+)\s+(\S+)\s*(.*)$/;
 
-          # Parsing the keys is a nightmare because of IPv6. The design of the
-          # format for the keys is a complete shambles. All my fault (PH). But
-          # I don't want to change it just for this purpose. If they key
-          # contains more than 3 colons, we have an IPv6 address, because
-          # an IPv6 address must contain at least two colons.
-
-          # Deal with IPv4 addresses (3 colons or fewer)
-
-          if ($key !~ /:([^:]*?:){3}/)
-            {
-            ($host,$ip,$port,$msgid) = $key =~
-              /^([^:]*):([^:]*)(?::([^:]*)(?::(\S*)|)|)/;
-            }
-
-          # Deal with IPv6 addresses; sorting out the colons is a complete
-          # mess. We should be able to find the host name and IP address from
-          # further in the message. That seems the easiest escape plan here. We
-          # can use those to match the rest of the key.
-
-          else
-            {
-            ($host,$ip) = $text =~ /host\s(\S+)\s\[([^]]+)\]/;
-            if (defined $host)
-              {
-              ($port,$msgid) = $key =~
-                /^$host:$ip(?::([^:]*)(?::(\S*)|)|)/;
-              }
-
-           # This will probably be wrong...
-
-           else
-             {
-             ($host,$ip) = $key =~ /([^:]*):(.*)/;
-             }
-            }
-
-          printf("Transport: %s [%s]", $host, $ip);
+	  ($host,$ip,$port,$msgid) = $key =~
+	    /^([^:[]*|\[[^]]*\])	# host (could be an ip)
+	      :([^:[]*|\[[^]]*\])	# ip
+	      (?::(\d{1,5}))?		# maybe port
+	      (?::(\S{23}))?		# maybe msgid
+	      $/x;
+
+          printf("Transport: %s %s", $host, $ip);
           print ":$port" if defined $port;
           print " $msgid" if defined $msgid;
           print " error $error: $text\n";
diff --git a/src/src/functions.h b/src/src/functions.h
index b2f948650..9f6396a17 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -454,11 +454,14 @@ extern BOOL    regex_match_and_setup(const pcre2_code *, const uschar *, int, in
 extern const pcre2_code *regex_compile(const uschar *, mcs_flags, uschar **,
 		pcre2_compile_context *);
 extern const pcre2_code *regex_must_compile(const uschar *, mcs_flags, BOOL);
-extern void    retry_add_item(address_item *, uschar *, int);
+
+extern void    retry_add_item(address_item *, const uschar *, int);
 extern BOOL    retry_check_address(const uschar *, host_item *, uschar *, BOOL,
-                 uschar **, uschar **);
+                 const uschar **, const uschar **);
 extern retry_config *retry_find_config(const uschar *, const uschar *, int, int);
-extern BOOL    retry_ultimate_address_timeout(uschar *, const uschar *,
+extern const uschar *retry_host_key_build(const host_item *, BOOL,
+		  const uschar *);
+extern BOOL    retry_ultimate_address_timeout(const uschar *, const uschar *,
                  dbdata_retry *, time_t);
 extern void    retry_update(address_item **, address_item **, address_item **);
 extern const uschar *rewrite_address(const uschar *, BOOL, BOOL, rewrite_rule *, int);
diff --git a/src/src/retry.c b/src/src/retry.c
index 42393d424..6b977dd58 100644
--- a/src/src/retry.c
+++ b/src/src/retry.c
@@ -31,7 +31,7 @@ Returns:        TRUE if the ultimate timeout has been reached
 */
 
 BOOL
-retry_ultimate_address_timeout(uschar *retry_key, const uschar *domain,
+retry_ultimate_address_timeout(const uschar * retry_key, const uschar *domain,
   dbdata_retry *retry_record, time_t now)
 {
 BOOL address_timeout;
@@ -75,6 +75,29 @@ return address_timeout;
 
 
 
+const uschar *
+retry_host_key_build(const host_item * host, BOOL incl_ip,
+  const uschar * portstring)
+{
+const uschar * s = host->name;
+gstring * g = string_is_ip_address(s, NULL)
+  ? string_fmt_append(NULL, "T:[%s]", s)    /* wrap a name which is a bare ip */
+  : string_fmt_append(NULL, "T:%s",   s);
+
+s = host->address;
+if (incl_ip)
+  g = Ustrchr(s, ':')
+    ? string_fmt_append(g, ":[%s]", s)	    /* wrap an ipv6  */
+    : string_fmt_append(g, ":%s",   s);
+
+if (portstring)
+  g = string_cat(g, portstring);
+
+gstring_release_unused(g);
+return string_from_gstring(g);
+}
+
+
 /*************************************************
 *     Set status of a host+address item          *
 *************************************************/
@@ -124,11 +147,12 @@ Returns:    TRUE if the host has expired but is usable because
 
 BOOL
 retry_check_address(const uschar *domain, host_item *host, uschar *portstring,
-  BOOL include_ip_address, uschar **retry_host_key, uschar **retry_message_key)
+  BOOL include_ip_address,
+  const uschar **retry_host_key, const uschar **retry_message_key)
 {
 BOOL yield = FALSE;
 time_t now = time(NULL);
-uschar * host_key, * message_key;
+const uschar * host_key, * message_key;
 open_db dbblock, * dbm_file;
 tree_node * node;
 dbdata_retry * host_retry_record, * message_retry_record;
@@ -143,14 +167,11 @@ if (host->status != hstatus_unknown) return FALSE;
 host->status = hstatus_usable;
 
 /* Generate the host key for the unusable tree and the retry database. Ensure
-host names are lower cased (that's what %S does). */
-
-host_key = include_ip_address
-  ? string_sprintf("T:%S:%s%s", host->name, host->address, portstring)
-  : string_sprintf("T:%S%s", host->name, portstring);
-
-/* Generate the message-specific key */
+host names are lower cased (that's what %S does).
+Generate the message-specific key too.
+Be sure to maintain lack-of-spaces in retry keys; exinext depends on it. */
 
+host_key = retry_host_key_build(host, include_ip_address, portstring);
 message_key = string_sprintf("%s:%s", host_key, message_id);
 
 /* Search the tree of unusable IP addresses. This is filled in when deliveries
@@ -290,7 +311,7 @@ Returns:  nothing
 */
 
 void
-retry_add_item(address_item *addr, uschar *key, int flags)
+retry_add_item(address_item * addr, const uschar * key, int flags)
 {
 retry_item * rti = store_get(sizeof(retry_item), GET_UNTAINTED);
 host_item * host = addr->host_used;
@@ -343,11 +364,11 @@ Returns:       pointer to retry rule, or NULL
 */
 
 retry_config *
-retry_find_config(const uschar *key, const uschar *alternate, int basic_errno,
+retry_find_config(const uschar * key, const uschar * alternate, int basic_errno,
   int more_errno)
 {
-const uschar *colon = Ustrchr(key, ':');
-retry_config *yield;
+const uschar * colon = Ustrchr(key, ':');
+retry_config * yield;
 
 /* If there's a colon in the key, there are two possibilities:
 
@@ -355,7 +376,8 @@ retry_config *yield;
 
       hostname:ip+port
 
-    In this case, we copy the host name.
+    In this case, we copy the host name (which could be an [ip], including
+    being an [ipv6], and we drop the []).
 
 (2) This is a key for a pipe, file, or autoreply delivery, in the format
 
@@ -369,6 +391,8 @@ retry_config *yield;
 if (colon)
   key = isalnum(*key)
     ? string_copyn(key, colon-key)	/* the hostname */
+    : *key == '['
+    ? string_copyn(key+1, Ustrchr(key, ']')-1-key)	/* the ip */
     : Ustrrchr(key, ':') + 1;		/* Take from the last colon */
 
 /* Sort out the keys */
@@ -932,3 +956,5 @@ DEBUG(D_retry) debug_printf("end of retry processing\n");
 }
 
 /* End of retry.c */
+/* vi: aw ai sw=2
+*/
diff --git a/src/src/string.c b/src/src/string.c
index cd76e1f1c..a627611af 100644
--- a/src/src/string.c
+++ b/src/src/string.c
@@ -1698,7 +1698,7 @@ while (*fp)
 	}
 
       /* If a width is not specified and the precision is specified, set
-      the width to the precision, or the string length if shorted. */
+      the width to the precision, or the string length if shorter. */
 
       else if (precision >= 0)
 	width = precision < slen ? precision : slen;
diff --git a/src/src/structs.h b/src/src/structs.h
index b16a8a3f2..8aab54055 100644
--- a/src/src/structs.h
+++ b/src/src/structs.h
@@ -480,7 +480,7 @@ can be tried. */
 
 typedef struct retry_item {
   struct retry_item *next;        /* for chaining */
-  uschar *key;                    /* string identifying host/address/message */
+  const uschar *key;              /* string identifying host/address/message */
   int     basic_errno;            /* error code for this destination */
   int     more_errno;             /* additional error information */
   uschar *message;                /* local error message */
diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index ed5994241..a5f99edaa 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -1306,7 +1306,7 @@ while (count-- > 0)
 
     if (testflag(addr, af_dr_retry_exists))
       {
-      uschar *altkey = string_sprintf("%s:<%s>", addr->address_retry_key,
+      uschar * altkey = string_sprintf("%s:<%s>", addr->address_retry_key,
         sender_address);
       retry_add_item(addr, altkey, rf_delete);
       retry_add_item(addr, addr->address_retry_key, rf_delete);
@@ -1322,7 +1322,7 @@ while (count-- > 0)
 
   else if (errno == ETIMEDOUT)
     {
-    uschar *message = string_sprintf("SMTP timeout after RCPT TO:<%s>",
+    uschar * message = string_sprintf("SMTP timeout after RCPT TO:<%s>",
 		transport_rcpt_address(addr, sx->conn_args.tblock->rcpt_include_affixes));
     set_errno_nohost(sx->first_addr, ETIMEDOUT, message, DEFER, FALSE, &sx->delivery_start);
     retry_add_item(addr, addr->address_retry_key, 0);
@@ -5376,7 +5376,7 @@ retry_non_continued:
     BOOL host_is_expired = FALSE, message_defer = FALSE, some_deferred = FALSE;
     address_item * first_addr = NULL;
     uschar * interface = NULL;
-    uschar * retry_host_key = NULL, * retry_message_key = NULL;
+    const uschar * retry_host_key = NULL, * retry_message_key = NULL;
     uschar * serialize_key = NULL;
 
     /* Deal slightly better with a possible Linux kernel bug that results
@@ -5873,9 +5873,7 @@ retry_non_continued:
 		  ob->expand_retry_include_ip_address, &incl_ip) != OK)
 	  incl_ip = TRUE;	/* error; use most-specific retry record */
 
-        retry_host_key = incl_ip
-	  ? string_sprintf("T:%S:%s%s", host->name, host->address, pistring)
-	  : string_sprintf("T:%S%s", host->name, pistring);
+        retry_host_key = retry_host_key_build(host, incl_ip, pistring);
         }
 
       /* If a delivery of another message over an existing SMTP connection
@@ -5921,10 +5919,8 @@ retry_non_continued:
 		  ob->expand_retry_include_ip_address, &incl_ip) != OK)
 	  incl_ip = TRUE;	/* error; use most-specific retry record */
 
-        retry_message_key = incl_ip
-	  ? string_sprintf("T:%S:%s%s:%s", host->name, host->address, pistring,
-	      message_id)
-	  : string_sprintf("T:%S%s:%s", host->name, pistring, message_id);
+        retry_message_key = string_sprintf("%s:%s",
+	  retry_host_key_build(host, incl_ip, pistring), message_id);
         }
       retry_add_item(addrlist, retry_message_key,
         rf_message | rf_host | delete_flag);
diff --git a/src/src/tree.c b/src/src/tree.c
index eb54829db..2c703583e 100644
--- a/src/src/tree.c
+++ b/src/src/tree.c
@@ -65,6 +65,7 @@ if (!tree_insertnode(&tree_duplicates, node)) store_reset(rpoint);
 
 
 
+#ifndef COMPILE_UTILITY
 /*************************************************
 *    Add entry to unusable addresses tree        *
 *************************************************/
@@ -76,12 +77,11 @@ Returns:     nothing
 */
 
 void
-tree_add_unusable(const host_item *h)
+tree_add_unusable(const host_item * h)
 {
 rmark rpoint = store_mark();
-tree_node *node;
-uschar s[256];
-sprintf(CS s, "T:%.200s:%s", h->name, h->address);
+tree_node * node;
+const uschar * s = retry_host_key_build(h, TRUE, NULL);
 node = store_get(sizeof(tree_node) + Ustrlen(s),
 	    is_tainted(h->name) || is_tainted(h->address) ? GET_TAINTED : GET_UNTAINTED);
 Ustrcpy(node->name, s);
@@ -89,7 +89,7 @@ node->data.val = h->why;
 if (h->status == hstatus_unusable_expired) node->data.val += 256;
 if (!tree_insertnode(&tree_unusable, node)) store_reset(rpoint);
 }
-
+#endif
 
 
 /*************************************************

commit 9ae8613607b12257e1fe59c603119bc5d4cd3474
Author: Daniel Golle <daniel@makrotopia.org>
Date:   Fri Apr 5 00:39:56 2024 +0100

    Debug: use difftime()

diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index a5f99edaa..ddd6ad7e1 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -945,7 +945,8 @@ if (  sx->early_pipe_active
     if (!(er = dbfn_read_enforce_length(dbm_file, ehlo_resp_key, sizeof(dbdata_ehlo_resp))))
       debug_printf("no ehlo-resp record!\n");
     else
-      debug_printf("ehlo-resp record is %d seconds old\n", time(NULL) - er->time_stamp);
+      debug_printf("ehlo-resp record is %.0f seconds old\n",
+		    difftime(time(NULL), er->time_stamp));
     }
 
   dbfn_delete(dbm_file, ehlo_resp_key);

commit 86e5b23559e50f107620aefeb52f4a856f988421
Author: Jeremy Harris <jgh146exb@wizmail.org>
Date:   Fri Apr 5 11:27:10 2024 +0100

    SRS: fix encode for local-part with zero-length quoted element.  Bug 3087

diff --git a/src/src/expand.c b/src/src/expand.c
index 5ea4dc6d4..1d121756d 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -7117,7 +7117,7 @@ while (*s)
 	    gstring_release_unused(h);
 	    s = string_from_gstring(h);
 	    }
-	  g = string_cat(g, s);
+	  if (s) g = string_cat(g, s);
 	  }
 
 	/* Assume that if the original local_part had quotes
