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;
