mirror of
https://github.com/chatmail/core.git
synced 2026-04-28 10:56:29 +03:00
Do not treat non-failed DSNs as NDNs
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
|
||||
### Fixes
|
||||
- don't squash text parts of NDN into attachments #3497
|
||||
- do not treat non-failed DSNs as NDNs #3506
|
||||
|
||||
|
||||
## 1.89.0
|
||||
|
||||
@@ -19,7 +19,7 @@ use crate::download::DownloadState;
|
||||
use crate::ephemeral::{start_ephemeral_timers_msgids, Timer as EphemeralTimer};
|
||||
use crate::events::EventType;
|
||||
use crate::imap::markseen_on_imap_table;
|
||||
use crate::mimeparser::{parse_message_id, FailureReport, SystemMessage};
|
||||
use crate::mimeparser::{parse_message_id, DeliveryReport, SystemMessage};
|
||||
use crate::param::{Param, Params};
|
||||
use crate::pgp::split_armored_data;
|
||||
use crate::scheduler::InterruptInfo;
|
||||
@@ -1532,7 +1532,7 @@ pub async fn handle_mdn(
|
||||
/// Where appropriate, also adds an info message telling the user which of the recipients of a group message failed.
|
||||
pub(crate) async fn handle_ndn(
|
||||
context: &Context,
|
||||
failed: &FailureReport,
|
||||
failed: &DeliveryReport,
|
||||
error: Option<String>,
|
||||
) -> Result<()> {
|
||||
if failed.rfc724_mid.is_empty() {
|
||||
@@ -1588,7 +1588,7 @@ pub(crate) async fn handle_ndn(
|
||||
|
||||
async fn ndn_maybe_add_info_msg(
|
||||
context: &Context,
|
||||
failed: &FailureReport,
|
||||
failed: &DeliveryReport,
|
||||
chat_id: ChatId,
|
||||
chat_type: Chattype,
|
||||
) -> Result<()> {
|
||||
|
||||
@@ -73,7 +73,7 @@ pub struct MimeMessage {
|
||||
pub(crate) user_avatar: Option<AvatarAction>,
|
||||
pub(crate) group_avatar: Option<AvatarAction>,
|
||||
pub(crate) mdn_reports: Vec<Report>,
|
||||
pub(crate) failure_report: Option<FailureReport>,
|
||||
pub(crate) delivery_report: Option<DeliveryReport>,
|
||||
|
||||
/// Standard USENET signature, if any.
|
||||
pub(crate) footer: Option<String>,
|
||||
@@ -332,7 +332,7 @@ impl MimeMessage {
|
||||
webxdc_status_update: None,
|
||||
user_avatar: None,
|
||||
group_avatar: None,
|
||||
failure_report: None,
|
||||
delivery_report: None,
|
||||
footer: None,
|
||||
is_mime_modified: false,
|
||||
decoded_data: Vec::new(),
|
||||
@@ -535,7 +535,7 @@ impl MimeMessage {
|
||||
self.parse_system_message_headers(context);
|
||||
self.parse_avatar_headers(context).await;
|
||||
self.parse_videochat_headers();
|
||||
if self.failure_report.is_none() {
|
||||
if self.delivery_report.is_none() {
|
||||
self.squash_attachment_parts();
|
||||
}
|
||||
|
||||
@@ -869,7 +869,7 @@ impl MimeMessage {
|
||||
// Some providers, e.g. Tiscali, forget to set the report-type. So, if it's None, assume that it might be delivery-status
|
||||
Some("delivery-status") | None => {
|
||||
if let Some(report) = self.process_delivery_status(context, mail)? {
|
||||
self.failure_report = Some(report);
|
||||
self.delivery_report = Some(report);
|
||||
}
|
||||
|
||||
// Add all parts (we need another part, preferably text/plain, to show as an error message)
|
||||
@@ -1280,9 +1280,46 @@ impl MimeMessage {
|
||||
&self,
|
||||
context: &Context,
|
||||
report: &mailparse::ParsedMail<'_>,
|
||||
) -> Result<Option<FailureReport>> {
|
||||
) -> Result<Option<DeliveryReport>> {
|
||||
// Assume failure.
|
||||
let mut failure = true;
|
||||
|
||||
if let Some(status_part) = report.subparts.get(1) {
|
||||
// RFC 3464 defines `message/delivery-status`
|
||||
// RFC 6533 defines `message/global-delivery-status`
|
||||
if status_part.ctype.mimetype != "message/delivery-status"
|
||||
&& status_part.ctype.mimetype != "message/global-delivery-status"
|
||||
{
|
||||
warn!(context, "Second part of Delivery Status Notification is not message/delivery-status or message/global-delivery-status, ignoring");
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let status_body = status_part.get_body_raw()?;
|
||||
|
||||
// Skip per-message fields.
|
||||
let (_, sz) = mailparse::parse_headers(&status_body)?;
|
||||
|
||||
// Parse first set of per-recipient fields
|
||||
if let Some(status_body) = status_body.get(sz..) {
|
||||
let (status_fields, _) = mailparse::parse_headers(status_body)?;
|
||||
if let Some(action) = status_fields.get_first_value("action") {
|
||||
if action != "failed" {
|
||||
info!(context, "DSN with {:?} action", action);
|
||||
failure = false;
|
||||
}
|
||||
} else {
|
||||
warn!(context, "DSN without action");
|
||||
}
|
||||
} else {
|
||||
warn!(context, "DSN without per-recipient fields");
|
||||
}
|
||||
} else {
|
||||
// No message/delivery-status part.
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// parse as mailheaders
|
||||
if let Some(original_msg) = report.subparts.iter().find(|p| {
|
||||
if let Some(original_msg) = report.subparts.get(2).filter(|p| {
|
||||
p.ctype.mimetype.contains("rfc822")
|
||||
|| p.ctype.mimetype == "message/global"
|
||||
|| p.ctype.mimetype == "message/global-headers"
|
||||
@@ -1303,9 +1340,10 @@ impl MimeMessage {
|
||||
None // We do not know which recipient failed
|
||||
};
|
||||
|
||||
return Ok(Some(FailureReport {
|
||||
return Ok(Some(DeliveryReport {
|
||||
rfc724_mid: original_message_id,
|
||||
failed_recipient: to.map(|s| s.addr),
|
||||
failure,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -1386,7 +1424,7 @@ impl MimeMessage {
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if maybe_ndn && self.failure_report.is_none() {
|
||||
if maybe_ndn && self.delivery_report.is_none() {
|
||||
static RE: Lazy<regex::Regex> =
|
||||
Lazy::new(|| regex::Regex::new(r"Message-ID:(.*)").unwrap());
|
||||
for captures in self
|
||||
@@ -1400,9 +1438,10 @@ impl MimeMessage {
|
||||
if let Ok(Some(_)) =
|
||||
message::rfc724_mid_exists(context, &original_message_id).await
|
||||
{
|
||||
self.failure_report = Some(FailureReport {
|
||||
self.delivery_report = Some(DeliveryReport {
|
||||
rfc724_mid: original_message_id,
|
||||
failed_recipient: None,
|
||||
failure: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1440,13 +1479,15 @@ impl MimeMessage {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(failure_report) = &self.failure_report {
|
||||
let error = parts
|
||||
.iter()
|
||||
.find(|p| p.typ == Viewtype::Text)
|
||||
.map(|p| p.msg.clone());
|
||||
if let Err(e) = message::handle_ndn(context, failure_report, error).await {
|
||||
warn!(context, "Could not handle ndn: {}", e);
|
||||
if let Some(delivery_report) = &self.delivery_report {
|
||||
if delivery_report.failure {
|
||||
let error = parts
|
||||
.iter()
|
||||
.find(|p| p.typ == Viewtype::Text)
|
||||
.map(|p| p.msg.clone());
|
||||
if let Err(e) = message::handle_ndn(context, delivery_report, error).await {
|
||||
warn!(context, "Could not handle ndn: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1526,6 +1567,7 @@ async fn update_gossip_peerstates(
|
||||
Ok(gossiped_addr)
|
||||
}
|
||||
|
||||
/// Message Disposition Notification (RFC 8098)
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Report {
|
||||
/// Original-Message-ID header
|
||||
@@ -1537,10 +1579,12 @@ pub(crate) struct Report {
|
||||
additional_message_ids: Vec<String>,
|
||||
}
|
||||
|
||||
/// Delivery Status Notification (RFC 3464, RFC 6533)
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct FailureReport {
|
||||
pub(crate) struct DeliveryReport {
|
||||
pub rfc724_mid: String,
|
||||
pub failed_recipient: Option<String>,
|
||||
pub failure: bool,
|
||||
}
|
||||
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
|
||||
@@ -497,9 +497,9 @@ async fn add_parts(
|
||||
ChatIdBlocked::lookup_by_contact(context, from_id).await?
|
||||
};
|
||||
|
||||
if chat_id.is_none() && mime_parser.failure_report.is_some() {
|
||||
if chat_id.is_none() && mime_parser.delivery_report.is_some() {
|
||||
chat_id = Some(DC_CHAT_ID_TRASH);
|
||||
info!(context, "Message belongs to an NDN (TRASH)",);
|
||||
info!(context, "Message is a DSN (TRASH)",);
|
||||
}
|
||||
|
||||
if chat_id.is_none() {
|
||||
@@ -2749,6 +2749,19 @@ mod tests {
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Test that DSN is not treated as NDN if Action: is not "failed"
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_parse_dsn_relayed() {
|
||||
test_parse_ndn(
|
||||
"anon_1@posteo.de",
|
||||
"anon_2@gmx.at",
|
||||
"8b7b1a9d0c8cc588c7bcac47f5687634@posteo.de",
|
||||
include_bytes!("../test-data/message/dsn_relayed.eml"),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
// ndn = Non Delivery Notification
|
||||
async fn test_parse_ndn(
|
||||
self_addr: &str,
|
||||
@@ -2798,7 +2811,14 @@ mod tests {
|
||||
receive_imf(&t, raw_ndn, false).await.unwrap();
|
||||
let msg = Message::load_from_db(&t, msg_id).await.unwrap();
|
||||
|
||||
assert_eq!(msg.state, MessageState::OutFailed);
|
||||
assert_eq!(
|
||||
msg.state,
|
||||
if error_msg.is_some() {
|
||||
MessageState::OutFailed
|
||||
} else {
|
||||
MessageState::OutDelivered
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(msg.error(), error_msg.map(|error| error.to_string()));
|
||||
}
|
||||
|
||||
109
test-data/message/dsn_relayed.eml
Normal file
109
test-data/message/dsn_relayed.eml
Normal file
@@ -0,0 +1,109 @@
|
||||
Return-Path: <>
|
||||
Delivered-To: anon_1@posteo.de
|
||||
Received: from proxy02.posteo.name ([127.0.0.1])
|
||||
by dovecot16.posteo.name (Dovecot) with LMTP id 4LJTJKBpxGClSAAAchYRkQ
|
||||
for <anon_1@posteo.de>; Sat, 12 Jun 2021 10:42:09 +0200
|
||||
Received: from proxy02.posteo.de ([127.0.0.1])
|
||||
by proxy02.posteo.name (Dovecot) with LMTP id 0NENMHNXxGDI4AIAGFAyLg
|
||||
; Sat, 12 Jun 2021 10:42:09 +0200
|
||||
Received: from mailin02.posteo.de (unknown [10.0.0.62])
|
||||
by proxy02.posteo.de (Postfix) with ESMTPS id 4G2B686dbVz11xc
|
||||
for <anon_1@posteo.de>; Sat, 12 Jun 2021 10:42:08 +0200 (CEST)
|
||||
Received: from mx01.posteo.de (mailin02.posteo.de [127.0.0.1])
|
||||
by mailin02.posteo.de (Postfix) with ESMTPS id AC2472152F
|
||||
for <anon_1@posteo.de>; Sat, 12 Jun 2021 10:42:08 +0200 (CEST)
|
||||
X-Virus-Scanned: amavisd-new at posteo.de
|
||||
X-Spam-Flag: NO
|
||||
X-Spam-Score: -1
|
||||
X-Spam-Level:
|
||||
X-Spam-Status: No, score=-1 tagged_above=-1000 required=7
|
||||
tests=[ALL_TRUSTED=-1] autolearn=disabled
|
||||
X-Posteo-Antispam-Signature: v=1; e=base64; a=aes-256-gcm; d=7/8PYiypR3F6lmk8rQGNxZgmuPRJI9wU2IwnCWX1fg/nFdbPrDu9pCFSVsnrK1SjAWJJ9HtJVYECbeMxMhq9tOMxZf1nSN2cM/XXzeH6ELaaQfOWfQbBff3ZIe+rix/CF1uWX164
|
||||
Authentication-Results: posteo.de; dmarc=none (p=none dis=none) header.from=mout02.posteo.de
|
||||
X-Posteo-TLS-Received-Status: TLSv1.3
|
||||
Received: from mout02.posteo.de (mout02.posteo.de [185.67.36.66])
|
||||
by mx01.posteo.de (Postfix) with ESMTPS id 4G2B676wGBz10Wt
|
||||
for <anon_1@posteo.at>; Sat, 12 Jun 2021 10:42:07 +0200 (CEST)
|
||||
Received: by mout02.posteo.de (Postfix)
|
||||
id A9F481A0089; Sat, 12 Jun 2021 10:42:07 +0200 (CEST)
|
||||
Date: Sat, 12 Jun 2021 10:42:07 +0200 (CEST)
|
||||
From: MAILER-DAEMON@mout02.posteo.de (Mail Delivery System)
|
||||
Subject: Successful Mail Delivery Report
|
||||
To: anon_1@posteo.at
|
||||
Auto-Submitted: auto-replied
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/report; report-type=delivery-status;
|
||||
boundary="56E6D1A007F.1623487327/mout02.posteo.de"
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Message-Id: <20210612084207.A9F481A0089@mout02.posteo.de>
|
||||
|
||||
This is a MIME-encapsulated message.
|
||||
|
||||
--56E6D1A007F.1623487327/mout02.posteo.de
|
||||
Content-Description: Notification
|
||||
Content-Type: text/plain; charset=us-ascii
|
||||
|
||||
This is the mail system at host mout02.posteo.de.
|
||||
|
||||
Your message was successfully delivered to the destination(s)
|
||||
listed below. If the message was delivered to mailbox you will
|
||||
receive no further notifications. Otherwise you may still receive
|
||||
notifications of mail delivery errors from other systems.
|
||||
|
||||
The mail system
|
||||
|
||||
<anon_2@gmx.at>: delivery via mx00.emig.gmx.net[212.227.15.9]:25: 250
|
||||
Requested mail action okay, completed: id=1M9ohD-1lvXys2NFd-005r3O
|
||||
|
||||
--56E6D1A007F.1623487327/mout02.posteo.de
|
||||
Content-Description: Delivery report
|
||||
Content-Type: message/delivery-status
|
||||
|
||||
Reporting-MTA: dns; mout02.posteo.de
|
||||
X-Postfix-Queue-ID: 56E6D1A007F
|
||||
X-Postfix-Sender: rfc822; anon_1@posteo.at
|
||||
Arrival-Date: Sat, 12 Jun 2021 10:42:07 +0200 (CEST)
|
||||
|
||||
Final-Recipient: rfc822; anon_2@gmx.at
|
||||
Original-Recipient: rfc822;anon_2@gmx.at
|
||||
Action: relayed
|
||||
Status: 2.0.0
|
||||
Remote-MTA: dns; mx00.emig.gmx.net
|
||||
Diagnostic-Code: smtp; 250 Requested mail action okay, completed:
|
||||
id=1M9ohD-1lvXys2NFd-005r3O
|
||||
|
||||
--56E6D1A007F.1623487327/mout02.posteo.de
|
||||
Content-Description: Message Headers
|
||||
Content-Type: text/rfc822-headers
|
||||
|
||||
Return-Path: <anon_1@posteo.at>
|
||||
Received: from mout02.posteo.de (unknown [10.0.0.66])
|
||||
by mout02.posteo.de (Postfix) with ESMTPS id 56E6D1A007F
|
||||
for <anon_2@gmx.at>; Sat, 12 Jun 2021 10:42:07 +0200 (CEST)
|
||||
Received: from submission-encrypt01.posteo.de (unknown [10.0.0.75])
|
||||
by mout02.posteo.de (Postfix) with ESMTPS id 1C39E2400FD
|
||||
for <anon_2@gmx.at>; Sat, 12 Jun 2021 10:42:07 +0200 (CEST)
|
||||
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=posteo.at; s=2017;
|
||||
t=1623487327; bh=+ZIKEoFCh8N5xYBj6tMbfqiyHmay76uM4H4bfme6VyU=;
|
||||
h=Date:From:To:Subject:From;
|
||||
b=QK6HwDU2YEzzTgHN2PRT2lPaf5uwC7ZJ1Y0QMSUrEyvJxwPj6+z6OoEqRDcgQcGVo
|
||||
biAO2aKyBX+YCFwM5a6CaJotv8DaL+hn/XLk3RKqxGKTu5cBLQXJc0gjfRMel7LnBg
|
||||
i0UxTeOqoTw2anWTonH2GnseUPtVAhi23UICVD6gC6DchuNYF/YloMltns5HMGthQh
|
||||
z279J05txneSKgpbU/R3fN2v5ACEve7X6GoxM0hDZRNmAur0HAxAREc9xIaHwQ3zXM
|
||||
dEGFyO53s+UzLlOFnY4vhGVI3AiyOZUProq6vX40g9e4TkrIJMGd1pyKG4NdajauuY
|
||||
KTIwbUiR5Y2Xw==
|
||||
Received: from customer (localhost [127.0.0.1])
|
||||
by submission (posteo.de) with ESMTPSA id 4G2B665xBPz6tmH
|
||||
for <anon_2@gmx.at>; Sat, 12 Jun 2021 10:42:06 +0200 (CEST)
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/alternative;
|
||||
boundary="=_d0190a7dc3b70a1dcf12785779aad292"
|
||||
Date: Sat, 12 Jun 2021 08:42:06 +0000
|
||||
From: Anon_1 <anon_1@posteo.at>
|
||||
To: Anon_2 <anon_2@gmx.at>
|
||||
Subject: Hallo
|
||||
Message-ID: <8b7b1a9d0c8cc588c7bcac47f5687634@posteo.de>
|
||||
Posteo-User: anon_1@posteo.de
|
||||
Posteo-Dkim: ok
|
||||
|
||||
--56E6D1A007F.1623487327/mout02.posteo.de--
|
||||
Reference in New Issue
Block a user