From 38301c6dcd3bf170de1cea7a6f2ff2cb9652da04 Mon Sep 17 00:00:00 2001 From: link2xt Date: Tue, 1 Sep 2026 17:42:58 +0000 Subject: [PATCH 1/2] fix: remove Original-Recipient field from MDNs According to Original-Recipient field values in MDNs MUST NOT be included if the information about original recipient is not available. Original recipient may be obtained from ORCPT parameter of SMTP envelope or from Original-Recipient header which MTAs are expected to convert Original-Recipient to. The way we have been using Original-Recipient field is not correct. Technically we should look for Original-Recipient header on the message when downloading it from IMAP and then copy the value into MDN, but simply assuming it is never there is more correct than always assuming it is the same as our current address. I also grepped for Original-Recipient and removed it together with Reporting-UA from the tests. Orignal-Recipient is now only left in NDN (bounce messages) test data, there it is correct as this field is added by MTAs that have direct access to ORCPT parameter. --- src/mimefactory.rs | 8 +++----- src/mimefactory/mimefactory_tests.rs | 2 -- src/mimeparser/mimeparser_tests.rs | 9 --------- src/receive_imf/receive_imf_tests.rs | 2 -- test-data/message/mdn_without_message_reference.eml | 1 - 5 files changed, 3 insertions(+), 19 deletions(-) diff --git a/src/mimefactory.rs b/src/mimefactory.rs index 6c9eaf6df6..b6308812e3 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -2238,11 +2238,10 @@ impl MimeFactory { // second body part: machine-readable, always REQUIRED by RFC 6522 let message_text2 = format!( - "Original-Recipient: rfc822;{}\r\n\ - Final-Recipient: rfc822;{}\r\n\ + "Final-Recipient: rfc822;{}\r\n\ Original-Message-ID: <{}>\r\n\ Disposition: manual-action/MDN-sent-automatically; displayed\r\n", - self.from_addr, self.from_addr, rfc724_mid + self.from_addr, rfc724_mid ); let extension_fields = if additional_msg_ids.is_empty() { @@ -2527,8 +2526,7 @@ fn keyupdate_body(from_addr: &str) -> MimePart<'static> { message.add_part(MimePart::new( "message/disposition-notification", format!( - "Original-Recipient: rfc822;{from_addr}\r\n\ - Final-Recipient: rfc822;{from_addr}\r\n\ + "Final-Recipient: rfc822;{from_addr}\r\n\ Disposition: automatic-action/MDN-sent-automatically; processed\r\n" ), )); diff --git a/src/mimefactory/mimefactory_tests.rs b/src/mimefactory/mimefactory_tests.rs index 885ab4d408..b59d872b90 100644 --- a/src/mimefactory/mimefactory_tests.rs +++ b/src/mimefactory/mimefactory_tests.rs @@ -269,8 +269,6 @@ async fn test_subject_mdn() { --SNIPP\r\n\ Content-Type: message/disposition-notification\r\n\ \r\n\ - Reporting-UA: Delta Chat 1.28.0\r\n\ - Original-Recipient: rfc822;bob@example.com\r\n\ Final-Recipient: rfc822;bob@example.com\r\n\ Original-Message-ID: <2893@example.com>\r\n\ Disposition: manual-action/MDN-sent-automatically; displayed\r\n\ diff --git a/src/mimeparser/mimeparser_tests.rs b/src/mimeparser/mimeparser_tests.rs index e542c10373..96fd58b7be 100644 --- a/src/mimeparser/mimeparser_tests.rs +++ b/src/mimeparser/mimeparser_tests.rs @@ -570,8 +570,6 @@ This is no guarantee the content was read.\n\ --kJBbU58X1xeWNHgBtTbMk80M5qnV4N\n\ Content-Type: message/disposition-notification\n\ \n\ -Reporting-UA: Delta Chat 1.0.0-beta.22\n\ -Original-Recipient: rfc822;bob@example.org\n\ Final-Recipient: rfc822;bob@example.org\n\ Original-Message-ID: \n\ Disposition: manual-action/MDN-sent-automatically; displayed\n\ @@ -626,8 +624,6 @@ This is no guarantee the content was read.\n\ --kJBbU58X1xeWNHgBtTbMk80M5qnV4N\n\ Content-Type: message/disposition-notification\n\ \n\ -Reporting-UA: Delta Chat 1.0.0-beta.22\n\ -Original-Recipient: rfc822;bob@example.org\n\ Final-Recipient: rfc822;bob@example.org\n\ Original-Message-ID: \n\ Disposition: manual-action/MDN-sent-automatically; displayed\n\ @@ -650,8 +646,6 @@ This is no guarantee the content was read.\n\ --zuOJlsTfZAukyawEPVdIgqWjaM9w2W\n\ Content-Type: message/disposition-notification\n\ \n\ -Reporting-UA: Delta Chat 1.0.0-beta.22\n\ -Original-Recipient: rfc822;bob@example.org\n\ Final-Recipient: rfc822;bob@example.org\n\ Original-Message-ID: \n\ Disposition: manual-action/MDN-sent-automatically; displayed\n\ @@ -697,8 +691,6 @@ This is no guarantee the content was read.\n\ --kJBbU58X1xeWNHgBtTbMk80M5qnV4N\n\ Content-Type: message/disposition-notification\n\ \n\ -Reporting-UA: Delta Chat 1.0.0-beta.22\n\ -Original-Recipient: rfc822;bob@example.org\n\ Final-Recipient: rfc822;bob@example.org\n\ Original-Message-ID: \n\ Disposition: manual-action/MDN-sent-automatically; displayed\n\ @@ -1574,7 +1566,6 @@ async fn test_ignore_read_receipt_to_self() -> Result<()> { --SNIPP\r\n\ Content-Type: message/disposition-notification\r\n\ \r\n\ - Original-Recipient: rfc822;bob@example.com\r\n\ Final-Recipient: rfc822;bob@example.com\r\n\ Original-Message-ID: \r\n\ Disposition: manual-action/MDN-sent-automatically; displayed\r\n\ diff --git a/src/receive_imf/receive_imf_tests.rs b/src/receive_imf/receive_imf_tests.rs index 1c6ad18dd0..1c5c9eca63 100644 --- a/src/receive_imf/receive_imf_tests.rs +++ b/src/receive_imf/receive_imf_tests.rs @@ -251,8 +251,6 @@ async fn test_mdn_and_alias() -> Result<()> { --SNIPP\n\ Content-Type: message/disposition-notification\n\ \n\ - Reporting-UA: Delta Chat 1.28.0\n\ - Original-Recipient: rfc822;bob@example.com\n\ Final-Recipient: rfc822;bob@example.com\n\ Original-Message-ID: <{msg_id}>\n\ Disposition: manual-action/MDN-sent-automatically; displayed\n\ diff --git a/test-data/message/mdn_without_message_reference.eml b/test-data/message/mdn_without_message_reference.eml index 14591ec959..d9e40809d9 100644 --- a/test-data/message/mdn_without_message_reference.eml +++ b/test-data/message/mdn_without_message_reference.eml @@ -15,7 +15,6 @@ Read receipts do not guarantee sth. was read. --SNIPP Content-Type: message/disposition-notification -Original-Recipient: rfc822;bob@example.net Final-Recipient: rfc822;bob@example.net Disposition: automatic-action/MDN-sent-automatically; processed From c1258c21521e6d1a60a936946b882ec02ba2820d Mon Sep 17 00:00:00 2001 From: link2xt Date: Tue, 1 Sep 2026 18:31:24 +0000 Subject: [PATCH 2/2] feat: remove Final-Recipient from MDNs (and keyupdates) This Final-Recipient was not set to the correct value anyway. We could query the database and find out via `imap` table which transport we have received the message on, but it is not worth the effort as the field is not practically used and cannot be relied on as old versions still send incorrect value. This removes one call to get_primary_self_addr() to make it easier to remove the concept of the "primary" address eventually. --- src/mimefactory.rs | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/mimefactory.rs b/src/mimefactory.rs index b6308812e3..e726430367 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -2237,11 +2237,14 @@ impl MimeFactory { ); // second body part: machine-readable, always REQUIRED by RFC 6522 + // + // We do not include the Final-Recipient field. + // According to + // it MUST be present and be the address on which original message was received, + // but practically it is not going to be used. let message_text2 = format!( - "Final-Recipient: rfc822;{}\r\n\ - Original-Message-ID: <{}>\r\n\ + "Original-Message-ID: <{rfc724_mid}>\r\n\ Disposition: manual-action/MDN-sent-automatically; displayed\r\n", - self.from_addr, rfc724_mid ); let extension_fields = if additional_msg_ids.is_empty() { @@ -2513,24 +2516,24 @@ pub(crate) async fn render_symm_encrypted_securejoin_message( /// a `multipart/report` is trashed as an MDN even where unencrypted mail is accepted, /// while a plain text body would end up in a contact request. /// The report deliberately names no original message, see [`crate::keyupdate`]. -fn keyupdate_body(from_addr: &str) -> MimePart<'static> { +fn keyupdate_body() -> MimePart<'static> { // Human-readable first part as RFC 6522 requires, untranslated like in `render_mdn`. let text_part = MimePart::new( "text/plain", "This message updates the sender's encryption key and relay list.", ); - let mut message = MimePart::new( - "multipart/report; report-type=disposition-notification", - vec![text_part], - ); - message.add_part(MimePart::new( + // We do not include the Final-Recipient field. + // Technically it is required for MDNs, but keyupdates + // are sent not in response to any message, + // so we don't have the address on which we received the message either. + let machine_part = MimePart::new( "message/disposition-notification", - format!( - "Final-Recipient: rfc822;{from_addr}\r\n\ - Disposition: automatic-action/MDN-sent-automatically; processed\r\n" - ), - )); - message + "Disposition: automatic-action/MDN-sent-automatically; processed\r\n", + ); + MimePart::new( + "multipart/report; report-type=disposition-notification", + vec![text_part, machine_part], + ) } /// Renders a keyupdate message informing the owners of `recipient_keys` @@ -2545,7 +2548,7 @@ pub(crate) async fn render_keyupdate_message( "Sending keyupdate message to {} recipients.", recipient_keys.len() ); - let message = keyupdate_body(&context.get_primary_self_addr().await?); + let message = keyupdate_body(); let headers = non_chat_headers(context, "Keyupdate").await?; let message = add_headers_to_encrypted_part(message, headers);