Prereq: "3.8.5"
diff -ur --new-file /var/tmp/postfix-3.8.5/src/global/mail_version.h ./src/global/mail_version.h
--- /var/tmp/postfix-3.8.5/src/global/mail_version.h	2024-01-18 18:44:21.000000000 -0500
+++ ./src/global/mail_version.h	2024-03-04 11:51:28.000000000 -0500
@@ -20,8 +20,8 @@
   * Patches change both the patchlevel and the release date. Snapshots have no
   * patchlevel; they change the release date only.
   */
-#define MAIL_RELEASE_DATE	"20240121"
-#define MAIL_VERSION_NUMBER	"3.8.5"
+#define MAIL_RELEASE_DATE	"20240304"
+#define MAIL_VERSION_NUMBER	"3.8.6"
 
 #ifdef SNAPSHOT
 #define MAIL_VERSION_DATE	"-" MAIL_RELEASE_DATE
diff -ur --new-file /var/tmp/postfix-3.8.5/HISTORY ./HISTORY
--- /var/tmp/postfix-3.8.5/HISTORY	2024-01-19 10:26:29.000000000 -0500
+++ ./HISTORY	2024-02-27 16:00:12.000000000 -0500
@@ -27246,3 +27246,83 @@
 	Files: mantools/postlink, proto/postconf.proto,
 	global/mail_params.h, global/smtp_stream.c, global/smtp_stream.h,
 	smtpd/smtpd.c, smtpd/smtpd_check.[hc].
+
+20231102
+
+	Bugfix (defect introduced: Postfix 2.3, date 20051222): the
+	Dovecot auth client did not reset the 'reason' from  a
+	previous Dovecot auth service response, before parsing the
+	next Dovecot auth server response in the same SMTP session.
+	Reported by Stephan Bosch, File: xsasl/xsasl_dovecot_server.c.
+
+20231105
+
+	Cleanup: Postfix SMTP server response with an empty
+	authentication failure reason. File: smtpd/smtpd_sasl_glue.c.
+
+20231208
+
+	Bugfix (defect introduced: Postfix 3.1, date: 20151128):
+	"postqueue -j" produced broken JSON when escaping a control
+	character as \uXXXX. Found during code maintenance. File:
+	postqueue/showq_json.c.
+
+20231211
+
+	Cleanup: posttls-finger certificate match expectations for
+	all TLS security levels, including warnings for levels that
+	don't implement certificate matching. Viktor Dukhovni.
+	File: posttls-finger.c.
+
+20231213
+
+	Bugfix (defect introduced: Postfix 2.3): after prepending
+	a message header with a Postfix access table PREPEND action,
+	a Milter request to delete or update an existing header
+	could have no effect, or it could target the wrong instance
+	of an existing header. Root cause: the fix dated 20141018
+	for the Postfix Milter client was incomplete. The client
+	did correctly hide the first, Postfix-generated, Received:
+	header when sending message header information to a Milter
+	with the smfi_header() application callback function, but
+	it was still hiding the first header (instead of the first
+	Received: header) when handling requests from a Milter to
+	delete or update an existing header. Problem report by
+	Carlos Velasco. This change was verified to have no effect
+	on requests from a Milter to add or insert a header. File:
+	cleanup/cleanup_milter.c.
+
+20240124
+
+	Workaround: tlsmgr logfile spam. Some OS lies under load:
+	it says that a socket is readable, then it says that the
+	socket has unread data, and then it says that read returns
+	EOF, causing Postfix to spam the log with a warning message.
+	File: tlsmgr/tlsmgr.c.
+
+	Bugfix (defect introduced: Postfix 3.4): the SMTP server's
+	BDAT command handler could be tricked to read $message_size_limit
+	bytes into memory. Found during code maintenance. File:
+	smtpd/smtpd.c.
+
+20240209
+
+	Performance: eliminate worst-case behavior where the queue
+	manager defers delivery to all destinations over a specific
+	delivery transport, after only a single delivery agent
+	failure. The scheduler now throttles one destination, and
+	allows deliveries to other destinations to keep making
+	progress. Files: *qmgr/qmgr_deliver.c.
+
+20240226
+
+	Safety: drop and log over-size DNS responses resulting in
+	more than 100 records. This 20x larger than the number of
+	server addresses that the Postfix SMTP client is willing
+	to consider when delivering mail, and is well below the
+	number of records that could cause a tail recursion crash
+	in dns_rr_append() as reported by Toshifumi Sakaguchi. This
+	also limits the number of DNS requests from check_*_*_access
+	restrictions. Files: dns/dns.h, dns/dns_lookup.c, dns/dns_rr.c,
+	dns/test_dns_lookup.c, posttls-finger/posttls-finger.c,
+	smtp/smtp_addr.c, smtpd/smtpd_check.c.
diff -ur --new-file /var/tmp/postfix-3.8.5/src/cleanup/cleanup_milter.c ./src/cleanup/cleanup_milter.c
--- /var/tmp/postfix-3.8.5/src/cleanup/cleanup_milter.c	2023-03-10 09:06:42.000000000 -0500
+++ ./src/cleanup/cleanup_milter.c	2024-02-27 10:55:17.000000000 -0500
@@ -119,6 +119,7 @@
 #include <dsn_util.h>
 #include <xtext.h>
 #include <info_log_addr_form.h>
+#include <header_opts.h>
 
 /* Application-specific. */
 
@@ -754,14 +755,26 @@
      */
 }
 
+/* hidden_header - respect milter header hiding protocol */
+
+static int hidden_header(VSTRING *buf, ARGV *auto_hdrs, int *hide_done)
+{
+    char  **cpp;
+    int     mask;
+
+    for (cpp = auto_hdrs->argv, mask = 1; *cpp; cpp++, mask <<= 1)
+	if ((*hide_done & mask) == 0 && strncmp(*cpp, STR(buf), LEN(buf)) == 0)
+	    return (*hide_done |= mask);
+    return (0);
+}
+
 /* cleanup_find_header_start - find specific header instance */
 
 static off_t cleanup_find_header_start(CLEANUP_STATE *state, ssize_t index,
 				               const char *header_label,
 				               VSTRING *buf,
 				               int *prec_type,
-				               int allow_ptr_backup,
-				               int skip_headers)
+				               int allow_ptr_backup)
 {
     const char *myname = "cleanup_find_header_start";
     off_t   curr_offset;		/* offset after found record */
@@ -770,7 +783,7 @@
     int     rec_type = REC_TYPE_ERROR;
     int     last_type;
     ssize_t len;
-    int     hdr_count = 0;
+    int     hide_done = 0;
 
     if (msg_verbose)
 	msg_info("%s: index %ld name \"%s\"",
@@ -912,11 +925,10 @@
 	    break;
 	}
 	/* This the start of a message header. */
-	else if (hdr_count++ < skip_headers)
-	     /* Reset the saved PTR record and update last_type. */ ;
 	else if ((header_label == 0
 		  || (strncasecmp(header_label, STR(buf), len) == 0
-		      && (strlen(header_label) == len)))
+		      && strlen(header_label) == len
+		      && !hidden_header(buf, state->auto_hdrs, &hide_done)))
 		 && --index == 0) {
 	    /* If we have a saved PTR record, it points to start of header. */
 	    break;
@@ -1182,15 +1194,12 @@
      */
 #define NO_HEADER_NAME	((char *) 0)
 #define ALLOW_PTR_BACKUP	1
-#define SKIP_ONE_HEADER		1
-#define DONT_SKIP_HEADERS	0
 
     if (index < 1)
 	index = 1;
     old_rec_offset = cleanup_find_header_start(state, index, NO_HEADER_NAME,
 					       old_rec_buf, &old_rec_type,
-					       ALLOW_PTR_BACKUP,
-					       DONT_SKIP_HEADERS);
+					       ALLOW_PTR_BACKUP);
     if (old_rec_offset == CLEANUP_FIND_HEADER_IOERROR)
 	/* Warning and errno->error mapping are done elsewhere. */
 	CLEANUP_INS_HEADER_RETURN(cleanup_milter_error(state, 0));
@@ -1270,8 +1279,7 @@
     rec_buf = vstring_alloc(100);
     old_rec_offset = cleanup_find_header_start(state, index, new_hdr_name,
 					       rec_buf, &last_type,
-					       NO_PTR_BACKUP,
-					       SKIP_ONE_HEADER);
+					       NO_PTR_BACKUP);
     if (old_rec_offset == CLEANUP_FIND_HEADER_IOERROR)
 	/* Warning and errno->error mapping are done elsewhere. */
 	CLEANUP_UPD_HEADER_RETURN(cleanup_milter_error(state, 0));
@@ -1333,8 +1341,7 @@
 
     rec_buf = vstring_alloc(100);
     header_offset = cleanup_find_header_start(state, index, hdr_name, rec_buf,
-					      &last_type, NO_PTR_BACKUP,
-					      SKIP_ONE_HEADER);
+					      &last_type, NO_PTR_BACKUP);
     if (header_offset == CLEANUP_FIND_HEADER_IOERROR)
 	/* Warning and errno->error mapping are done elsewhere. */
 	CLEANUP_DEL_HEADER_RETURN(cleanup_milter_error(state, 0));
diff -ur --new-file /var/tmp/postfix-3.8.5/src/dns/dns.h ./src/dns/dns.h
--- /var/tmp/postfix-3.8.5/src/dns/dns.h	2023-04-02 16:20:25.000000000 -0400
+++ ./src/dns/dns.h	2024-02-29 11:51:22.000000000 -0500
@@ -161,12 +161,18 @@
     unsigned short pref;		/* T_MX and T_SRV record related */
     unsigned short weight;		/* T_SRV related, defined in rfc2782 */
     unsigned short port;		/* T_SRV related, defined in rfc2782 */
+    /* Assume that flags lives in what was previously padding */
+    unsigned short flags;		/* DNS_RR_FLAG_XX, see below */
     struct DNS_RR *next;		/* linkage */
     size_t  data_len;			/* actual data size */
     char    *data;			/* a bunch of data */
      /* Add new fields at the end, for ABI forward compatibility. */
 } DNS_RR;
 
+#define DNS_RR_FLAG_TRUNCATED	(1<<0)
+
+#define DNS_RR_IS_TRUNCATED(rr)	((rr)->flags & DNS_RR_FLAG_TRUNCATED)
+
  /*
   * dns_strerror.c
   */
@@ -215,6 +221,7 @@
 extern int dns_rr_compare_pref(DNS_RR *, DNS_RR *);
 extern DNS_RR *dns_rr_shuffle(DNS_RR *);
 extern DNS_RR *dns_rr_remove(DNS_RR *, DNS_RR *);
+extern int var_dns_rr_list_limit;
 
  /*
   * dns_rr_to_pa.c
diff -ur --new-file /var/tmp/postfix-3.8.5/src/dns/dns_lookup.c ./src/dns/dns_lookup.c
--- /var/tmp/postfix-3.8.5/src/dns/dns_lookup.c	2023-08-31 14:57:22.000000000 -0400
+++ ./src/dns/dns_lookup.c	2024-02-27 11:14:58.000000000 -0500
@@ -978,6 +978,8 @@
 		    resource_found++;
 		    rr->dnssec_valid = *maybe_secure ? reply->dnssec_ad : 0;
 		    *rrlist = dns_rr_append(*rrlist, rr);
+		    if (DNS_RR_IS_TRUNCATED(*rrlist))
+			break;
 		} else if (status == DNS_NULLMX || status == DNS_NULLSRV) {
 		    CORRUPT(status);		/* TODO: use better name */
 		} else if (not_found_status != DNS_RETRY)
@@ -1208,8 +1210,11 @@
 		     name, dns_strtype(type), dns_str_resflags(flags));
 	status = dns_lookup_x(name, type, flags, rrlist ? &rr : (DNS_RR **) 0,
 			      fqdn, why, rcode, lflags);
-	if (rrlist && rr)
+	if (rrlist && rr) {
 	    *rrlist = dns_rr_append(*rrlist, rr);
+	    if (DNS_RR_IS_TRUNCATED(*rrlist))
+		break;
+	}
 	if (status == DNS_OK) {
 	    if (lflags & DNS_REQ_FLAG_STOP_OK)
 		break;
@@ -1260,8 +1265,11 @@
 		     name, dns_strtype(type), dns_str_resflags(flags));
 	status = dns_lookup_x(name, type, flags, rrlist ? &rr : (DNS_RR **) 0,
 			      fqdn, why, rcode, lflags);
-	if (rrlist && rr)
+	if (rrlist && rr) {
 	    *rrlist = dns_rr_append(*rrlist, rr);
+	    if (DNS_RR_IS_TRUNCATED(*rrlist))
+		break;
+	}
 	if (status == DNS_OK) {
 	    if (lflags & DNS_REQ_FLAG_STOP_OK)
 		break;
diff -ur --new-file /var/tmp/postfix-3.8.5/src/dns/dns_rr.c ./src/dns/dns_rr.c
--- /var/tmp/postfix-3.8.5/src/dns/dns_rr.c	2023-04-07 15:50:27.000000000 -0400
+++ ./src/dns/dns_rr.c	2024-02-27 11:14:58.000000000 -0500
@@ -54,6 +54,8 @@
 /*
 /*	DNS_RR	*dns_srv_rr_sort(list)
 /*	DNS_RR	*list;
+/*
+/*	int	var_dns_rr_list_limit;
 /* AUXILIARY FUNCTIONS
 /*	DNS_RR	*dns_rr_create_nopref(qname, rname, type, class, ttl,
 /*					data, data_len)
@@ -95,9 +97,17 @@
 /*
 /*	dns_rr_copy() makes a copy of a resource record.
 /*
-/*	dns_rr_append() appends a resource record to a (list of) resource
-/*	record(s).
-/*	A null input list is explicitly allowed.
+/*	dns_rr_append() appends an input resource record list to
+/*	an output list. Null arguments are explicitly allowed.
+/*	When the result would be longer than var_dns_rr_list_limit
+/*	(default: 100), dns_rr_append() logs a warning, flags the
+/*	output list as truncated, and discards the excess elements.
+/*	Once an output list is flagged as truncated (test with
+/*	DNS_RR_IS_TRUNCATED()), the caller is expected to stop
+/*	trying to append records to that list. Note: the 'truncated'
+/*	flag is transitive, i.e. when appending a input list that
+/*	was flagged as truncated to an output list, the output list
+/*	will also be flagged as truncated.
 /*
 /*	dns_rr_sort() sorts a list of resource records into ascending
 /*	order according to a user-specified criterion. The result is the
@@ -150,6 +160,16 @@
 
 #include "dns.h"
 
+ /*
+  * A generous safety limit for the number of DNS resource records that the
+  * Postfix DNS client library will admit into a list. The default value 100
+  * is 20x the default limit on the number address records that the Postfix
+  * SMTP client is willing to consider.
+  * 
+  * Mutable, to make code testable.
+  */
+int     var_dns_rr_list_limit = 100;
+
 /* dns_rr_create - fill in resource record structure */
 
 DNS_RR *dns_rr_create(const char *qname, const char *rname,
@@ -181,6 +201,7 @@
     }
     rr->data_len = data_len;
     rr->next = 0;
+    rr->flags = 0;
     return (rr);
 }
 
@@ -218,14 +239,58 @@
     return (dst);
 }
 
-/* dns_rr_append - append resource record to list */
+/* dns_rr_append_with_limit - append resource record to limited list */
+
+static void dns_rr_append_with_limit(DNS_RR *list, DNS_RR *rr, int limit)
+{
+
+    /*
+     * Pre: list != 0, all lists are concatenated with dns_rr_append().
+     * 
+     * Post: all elements have the DNS_RR_FLAG_TRUNCATED flag value set, or all
+     * elements have it cleared, so that there is no need to update code in
+     * legacy stable releases that deletes or reorders elements.
+     */
+    if (limit <= 1) {
+	if (list->next || rr) {
+	    msg_warn("DNS record count limit (%d) exceeded -- dropping"
+		     " excess record(s) after qname=%s qtype=%s",
+		     var_dns_rr_list_limit, list->qname,
+		     dns_strtype(list->type));
+	    list->flags |= DNS_RR_FLAG_TRUNCATED;
+	    dns_rr_free(list->next);
+	    dns_rr_free(rr);
+	    list->next = 0;
+	}
+    } else {
+	if (list->next == 0 && rr) {
+	    list->next = rr;
+	    rr = 0;
+	}
+	if (list->next) {
+	    dns_rr_append_with_limit(list->next, rr, limit - 1);
+	    list->flags |= list->next->flags;
+	}
+    }
+}
+
+/* dns_rr_append - append resource record(s) to list, or discard */
 
 DNS_RR *dns_rr_append(DNS_RR *list, DNS_RR *rr)
 {
-    if (list == 0) {
-	list = rr;
+
+    /*
+     * Note: rr is not length checked; when multiple lists are concatenated,
+     * the output length may be a small multiple of var_dns_rr_list_limit.
+     */
+    if (rr == 0)
+	return (list);
+    if (list == 0)
+	return (rr);
+    if (!DNS_RR_IS_TRUNCATED(list)) {
+	dns_rr_append_with_limit(list, rr, var_dns_rr_list_limit);
     } else {
-	list->next = dns_rr_append(list->next, rr);
+	dns_rr_free(rr);
     }
     return (list);
 }
diff -ur --new-file /var/tmp/postfix-3.8.5/src/dns/test_dns_lookup.c ./src/dns/test_dns_lookup.c
--- /var/tmp/postfix-3.8.5/src/dns/test_dns_lookup.c	2021-04-04 11:40:41.000000000 -0400
+++ ./src/dns/test_dns_lookup.c	2024-02-27 11:14:58.000000000 -0500
@@ -121,9 +121,11 @@
 	    vstream_printf("%s: fqdn: %s\n", name, vstring_str(fqdn));
 	    buf = vstring_alloc(100);
 	    print_rr(buf, rr);
+	    vstream_fflush(VSTREAM_OUT);
+	    if (DNS_RR_IS_TRUNCATED(rr))
+		msg_warn("one or more excess DNS_RR records were dropped");
 	    dns_rr_free(rr);
 	    vstring_free(buf);
-	    vstream_fflush(VSTREAM_OUT);
 	}
     }
     myfree((void *) types);
diff -ur --new-file /var/tmp/postfix-3.8.5/src/oqmgr/qmgr_deliver.c ./src/oqmgr/qmgr_deliver.c
--- /var/tmp/postfix-3.8.5/src/oqmgr/qmgr_deliver.c	2021-08-06 20:04:05.000000000 -0400
+++ ./src/oqmgr/qmgr_deliver.c	2024-02-27 11:12:55.000000000 -0500
@@ -283,6 +283,7 @@
      * The queue itself won't go away before we dispose of the current queue
      * entry.
      */
+#if 0
     if (status == DELIVER_STAT_CRASH) {
 	message->flags |= DELIVER_STAT_DEFER;
 #if 0
@@ -317,6 +318,7 @@
 	qmgr_defer_transport(transport, &dsb->dsn);
 	return;
     }
+#endif
 
     /*
      * This message must be tried again.
@@ -331,7 +333,9 @@
      */
 #define SUSPENDED	"delivery temporarily suspended: "
 
-    if (status == DELIVER_STAT_DEFER) {
+    if (status == DELIVER_STAT_CRASH)
+	DSN_SIMPLE(&dsb->dsn, "4.3.0", "unknown mail transport error");
+    if (status == DELIVER_STAT_CRASH || status == DELIVER_STAT_DEFER) {
 	message->flags |= DELIVER_STAT_DEFER;
 	if (VSTRING_LEN(dsb->status)) {
 	    /* Sanitize the DSN status/reason from the delivery agent. */
diff -ur --new-file /var/tmp/postfix-3.8.5/src/postqueue/showq_json.c ./src/postqueue/showq_json.c
--- /var/tmp/postfix-3.8.5/src/postqueue/showq_json.c	2021-10-27 19:33:07.000000000 -0400
+++ ./src/postqueue/showq_json.c	2024-02-27 10:55:17.000000000 -0500
@@ -96,7 +96,7 @@
 		VSTRING_ADDCH(result, 't');
 		break;
 	    default:
-		vstring_sprintf(result, "\\u%04X", ch);
+		vstring_sprintf_append(result, "\\u%04X", ch);
 		break;
 	    }
 	} else {
diff -ur --new-file /var/tmp/postfix-3.8.5/src/posttls-finger/posttls-finger.c ./src/posttls-finger/posttls-finger.c
--- /var/tmp/postfix-3.8.5/src/posttls-finger/posttls-finger.c	2023-05-16 17:55:54.000000000 -0400
+++ ./src/posttls-finger/posttls-finger.c	2024-02-27 11:14:58.000000000 -0500
@@ -1260,6 +1260,8 @@
 		    msg_fatal("host %s: conversion error for address family %d: %m",
 		    host, ((struct sockaddr *) (res0->ai_addr))->sa_family);
 		addr_list = dns_rr_append(addr_list, addr);
+		if (DNS_RR_IS_TRUNCATED(addr_list))
+		    break;
 	    }
 	    freeaddrinfo(res0);
 	    if (found == 0) {
@@ -1297,6 +1299,8 @@
 	    msg_panic("%s: bad resource type: %d", myname, rr->type);
 	addr_list = addr_one(state, addr_list, (char *) rr->data, res_opt,
 			     rr->pref, rr->port);
+	if (addr_list && DNS_RR_IS_TRUNCATED(addr_list))
+	    break;
     }
     return (addr_list);
 }
@@ -2114,7 +2118,19 @@
 #ifdef USE_TLS
     int     smtp_mode = 1;
 
+    /*
+     * DANE match names are configured late, once the TLSA records are in
+     * hand. For now, prepare to fall back to "secure".
+     */
     switch (state->level) {
+    default:
+	state->match = 0;
+	if (*argv)
+	    msg_warn("TLS level '%s' does not implement certificate matching",
+		     str_tls_level(state->level));
+	break;
+    case TLS_LEV_DANE:
+    case TLS_LEV_DANE_ONLY:
     case TLS_LEV_SECURE:
 	state->match = argv_alloc(2);
 	while (*argv)
@@ -2135,11 +2151,6 @@
 	    tls_dane_add_fpt_digests((TLS_DANE *) state->dane, *argv++, "",
 				     smtp_mode);
 	break;
-    case TLS_LEV_DANE:
-    case TLS_LEV_DANE_ONLY:
-	state->match = argv_alloc(2);
-	argv_add(state->match, "nexthop", "hostname", ARGV_END);
-	break;
     }
 #endif
 }
diff -ur --new-file /var/tmp/postfix-3.8.5/src/qmgr/qmgr_deliver.c ./src/qmgr/qmgr_deliver.c
--- /var/tmp/postfix-3.8.5/src/qmgr/qmgr_deliver.c	2021-08-06 20:04:05.000000000 -0400
+++ ./src/qmgr/qmgr_deliver.c	2024-02-27 11:12:55.000000000 -0500
@@ -288,6 +288,7 @@
      * The queue itself won't go away before we dispose of the current queue
      * entry.
      */
+#if 0
     if (status == DELIVER_STAT_CRASH) {
 	message->flags |= DELIVER_STAT_DEFER;
 #if 0
@@ -322,6 +323,7 @@
 	qmgr_defer_transport(transport, &dsb->dsn);
 	return;
     }
+#endif
 
     /*
      * This message must be tried again.
@@ -336,7 +338,9 @@
      */
 #define SUSPENDED	"delivery temporarily suspended: "
 
-    if (status == DELIVER_STAT_DEFER) {
+    if (status == DELIVER_STAT_CRASH)
+	DSN_SIMPLE(&dsb->dsn, "4.3.0", "unknown mail transport error");
+    if (status == DELIVER_STAT_CRASH || status == DELIVER_STAT_DEFER) {
 	message->flags |= DELIVER_STAT_DEFER;
 	if (VSTRING_LEN(dsb->status)) {
 	    /* Sanitize the DSN status/reason from the delivery agent. */
diff -ur --new-file /var/tmp/postfix-3.8.5/src/smtp/smtp_addr.c ./src/smtp/smtp_addr.c
--- /var/tmp/postfix-3.8.5/src/smtp/smtp_addr.c	2023-03-09 13:06:45.000000000 -0500
+++ ./src/smtp/smtp_addr.c	2024-02-28 14:44:02.000000000 -0500
@@ -179,10 +179,10 @@
 	    if ((addr = dns_sa_to_rr(host, pref, res0->ai_addr)) == 0)
 		msg_fatal("host %s: conversion error for address family "
 			  "%d: %m", host, res0->ai_addr->sa_family);
-	    addr_list = dns_rr_append(addr_list, addr);
 	    addr->pref = pref;
 	    addr->port = port;
-	    if (msg_verbose)
+	    addr_list = dns_rr_append(addr_list, addr);
+	    if (msg_verbose && !DNS_RR_IS_TRUNCATED(addr_list))
 		msg_info("%s: using numerical host %s", myname, host);
 	    freeaddrinfo(res0);
 	    return (addr_list);
@@ -262,6 +262,8 @@
 		    msg_fatal("host %s: conversion error for address family "
 			      "%d: %m", host, res0->ai_addr->sa_family);
 		addr_list = dns_rr_append(addr_list, addr);
+		if (DNS_RR_IS_TRUNCATED(addr_list))
+		    break;
 		if (msg_verbose) {
 		    MAI_HOSTADDR_STR hostaddr_str;
 
@@ -327,6 +329,8 @@
 	    msg_panic("smtp_addr_list: bad resource type: %d", rr->type);
 	addr_list = smtp_addr_one(addr_list, (char *) rr->data, res_opt,
 				  rr->pref, rr->port, why);
+	if (addr_list && DNS_RR_IS_TRUNCATED(addr_list))
+	    break;
     }
     return (addr_list);
 }
@@ -421,6 +425,13 @@
      */
 
     /*
+     * Ensure that dns_rr_append() won't interfere with the protocol
+     * balancing goals.
+     */
+    if (addr_limit > var_dns_rr_list_limit)
+	addr_limit = var_dns_rr_list_limit;
+
+    /*
      * Count the number of IPv6 and IPv4 addresses.
      */
     for (v4_count = v6_count = 0, rr = addr_list; rr != 0; rr = rr->next) {
diff -ur --new-file /var/tmp/postfix-3.8.5/src/smtpd/smtpd.c ./src/smtpd/smtpd.c
--- /var/tmp/postfix-3.8.5/src/smtpd/smtpd.c	2024-01-18 19:00:24.000000000 -0500
+++ ./src/smtpd/smtpd.c	2024-02-27 11:06:01.000000000 -0500
@@ -4129,14 +4129,31 @@
 	/*
 	 * Read lines from the fragment. The last line may continue in the
 	 * next fragment, or in the next chunk.
+	 * 
+	 * If smtp_get_noexcept() stopped after var_line_limit bytes and did not
+	 * emit a queue file record, then that means smtp_get_noexcept()
+	 * stopped after CR and hit EOF as it tried to find out if the next
+	 * byte is LF. In that case, read the first byte from the next
+	 * fragment or chunk, and if that first byte is LF, then
+	 * smtp_get_noexcept() strips off the trailing CRLF and returns '\n'
+	 * as it always does after reading a complete line.
 	 */
 	do {
+	    int     can_read = var_line_limit - LEN(state->bdat_get_buffer);
+
 	    if (smtp_get_noexcept(state->bdat_get_buffer,
 				  state->bdat_get_stream,
-				  var_line_limit,
+				  can_read > 0 ? can_read : 1,	/* Peek one */
 				  SMTP_GET_FLAG_APPEND) == '\n') {
 		/* Stopped at end-of-line. */
 		curr_rec_type = REC_TYPE_NORM;
+	    } else if (LEN(state->bdat_get_buffer) > var_line_limit) {
+		/* Undo peeking, and output the buffer as REC_TYPE_CONT. */
+		vstream_ungetc(state->bdat_get_stream,
+			       vstring_end(state->bdat_get_buffer)[-1]);
+		vstring_truncate(state->bdat_get_buffer,
+				 LEN(state->bdat_get_buffer) - 1);
+		curr_rec_type = REC_TYPE_CONT;
 	    } else if (!vstream_feof(state->bdat_get_stream)) {
 		/* Stopped at var_line_limit. */
 		curr_rec_type = REC_TYPE_CONT;
diff -ur --new-file /var/tmp/postfix-3.8.5/src/smtpd/smtpd_check.c ./src/smtpd/smtpd_check.c
--- /var/tmp/postfix-3.8.5/src/smtpd/smtpd_check.c	2024-01-18 18:39:04.000000000 -0500
+++ ./src/smtpd/smtpd_check.c	2024-02-27 11:14:58.000000000 -0500
@@ -2994,6 +2994,7 @@
     struct addrinfo *res;
     int     status;
     const INET_PROTO_INFO *proto_info;
+    int     server_addr_count = 0;
 
     /*
      * Sanity check.
@@ -3145,6 +3146,15 @@
 	    msg_info("%s: %s host address check: %s",
 		     myname, dns_strtype(type), (char *) server->data);
 	for (res = res0; res != 0; res = res->ai_next) {
+	    server_addr_count += 1;
+	    if (server_addr_count > var_dns_rr_list_limit) {
+		msg_warn("%s: %s server address count limit (%d) exceeded"
+			 " for %s %s -- ignoring the remainder", myname,
+			 dns_strtype(type), var_dns_rr_list_limit,
+			 reply_class, reply_name);
+		freeaddrinfo(res0);
+		CHECK_SERVER_RETURN(SMTPD_CHECK_DUNNO);
+	    }
 	    if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) {
 		if (msg_verbose)
 		    msg_info("skipping address family %d for host %s",
diff -ur --new-file /var/tmp/postfix-3.8.5/src/smtpd/smtpd_sasl_glue.c ./src/smtpd/smtpd_sasl_glue.c
--- /var/tmp/postfix-3.8.5/src/smtpd/smtpd_sasl_glue.c	2023-10-30 19:16:11.000000000 -0400
+++ ./src/smtpd/smtpd_sasl_glue.c	2024-02-27 10:55:17.000000000 -0500
@@ -340,18 +340,20 @@
 	}
     }
     if (status != XSASL_AUTH_DONE) {
+	const char *reason = (*STR(state->sasl_reply) ? STR(state->sasl_reply) :
+			      "(reason unavailable)");
+
 	sasl_username = xsasl_server_get_username(state->sasl_server);
 	msg_warn("%s: SASL %.100s authentication failed: %s, sasl_username=%.100s",
-		 state->namaddr, sasl_method, *STR(state->sasl_reply) ?
-		 STR(state->sasl_reply) : "(reason unavailable)",
+		 state->namaddr, sasl_method, reason,
 		 sasl_username ? sasl_username : "(unavailable)");
 	/* RFC 4954 Section 6. */
 	if (status == XSASL_AUTH_TEMP)
 	    smtpd_chat_reply(state, "454 4.7.0 Temporary authentication failure: %s",
-			     STR(state->sasl_reply));
+			     reason);
 	else
 	    smtpd_chat_reply(state, "535 5.7.8 Error: authentication failed: %s",
-			     STR(state->sasl_reply));
+			     reason);
 	return (-1);
     }
     /* RFC 4954 Section 6. */
diff -ur --new-file /var/tmp/postfix-3.8.5/src/tlsmgr/tlsmgr.c ./src/tlsmgr/tlsmgr.c
--- /var/tmp/postfix-3.8.5/src/tlsmgr/tlsmgr.c	2021-12-19 17:04:54.000000000 -0500
+++ ./src/tlsmgr/tlsmgr.c	2024-02-27 11:03:39.000000000 -0500
@@ -819,6 +819,23 @@
     }
 
     /*
+     * Workaround: some OS lies under load. It tells the Postfix event
+     * handler that a server socket is readable, then it tells peekfd() that
+     * the socket has unread data, and then it tells vstring_get_null() that
+     * there is none, causing Postfix to spam the log with warning messages.
+     * Close the stream to stop such nonsense; the client can reconnect if it
+     * still wants to talk to us.
+     * 
+     * XXX Why is this problem not reported for the other five
+     * multi_server-based Postfix services?
+     */
+    else if (vstream_ferror(client_stream) || vstream_feof(client_stream)) {
+	multi_server_disconnect(client_stream);
+	return;
+	/* Note: client_stream is now a dangling pointer. */
+    }
+
+    /*
      * Protocol error.
      */
     else {
diff -ur --new-file /var/tmp/postfix-3.8.5/src/xsasl/xsasl_dovecot_server.c ./src/xsasl/xsasl_dovecot_server.c
--- /var/tmp/postfix-3.8.5/src/xsasl/xsasl_dovecot_server.c	2022-01-02 18:25:27.000000000 -0500
+++ ./src/xsasl/xsasl_dovecot_server.c	2024-02-27 10:55:17.000000000 -0500
@@ -543,6 +543,8 @@
 	myfree(server->username);
 	server->username = 0;
     }
+    VSTRING_RESET(reply);
+    VSTRING_TERMINATE(reply);
 
     /*
      * Note: TAB is part of the Dovecot protocol and must not appear in