mirror of
https://github.com/chatmail/core.git
synced 2026-05-20 07:16:31 +03:00
Do not treat non-failed DSNs as NDNs
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
- don't squash text parts of NDN into attachments #3497
|
- don't squash text parts of NDN into attachments #3497
|
||||||
|
- do not treat non-failed DSNs as NDNs #3506
|
||||||
|
|
||||||
|
|
||||||
## 1.89.0
|
## 1.89.0
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ use crate::download::DownloadState;
|
|||||||
use crate::ephemeral::{start_ephemeral_timers_msgids, Timer as EphemeralTimer};
|
use crate::ephemeral::{start_ephemeral_timers_msgids, Timer as EphemeralTimer};
|
||||||
use crate::events::EventType;
|
use crate::events::EventType;
|
||||||
use crate::imap::markseen_on_imap_table;
|
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::param::{Param, Params};
|
||||||
use crate::pgp::split_armored_data;
|
use crate::pgp::split_armored_data;
|
||||||
use crate::scheduler::InterruptInfo;
|
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.
|
/// 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(
|
pub(crate) async fn handle_ndn(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
failed: &FailureReport,
|
failed: &DeliveryReport,
|
||||||
error: Option<String>,
|
error: Option<String>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if failed.rfc724_mid.is_empty() {
|
if failed.rfc724_mid.is_empty() {
|
||||||
@@ -1588,7 +1588,7 @@ pub(crate) async fn handle_ndn(
|
|||||||
|
|
||||||
async fn ndn_maybe_add_info_msg(
|
async fn ndn_maybe_add_info_msg(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
failed: &FailureReport,
|
failed: &DeliveryReport,
|
||||||
chat_id: ChatId,
|
chat_id: ChatId,
|
||||||
chat_type: Chattype,
|
chat_type: Chattype,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ pub struct MimeMessage {
|
|||||||
pub(crate) user_avatar: Option<AvatarAction>,
|
pub(crate) user_avatar: Option<AvatarAction>,
|
||||||
pub(crate) group_avatar: Option<AvatarAction>,
|
pub(crate) group_avatar: Option<AvatarAction>,
|
||||||
pub(crate) mdn_reports: Vec<Report>,
|
pub(crate) mdn_reports: Vec<Report>,
|
||||||
pub(crate) failure_report: Option<FailureReport>,
|
pub(crate) delivery_report: Option<DeliveryReport>,
|
||||||
|
|
||||||
/// Standard USENET signature, if any.
|
/// Standard USENET signature, if any.
|
||||||
pub(crate) footer: Option<String>,
|
pub(crate) footer: Option<String>,
|
||||||
@@ -332,7 +332,7 @@ impl MimeMessage {
|
|||||||
webxdc_status_update: None,
|
webxdc_status_update: None,
|
||||||
user_avatar: None,
|
user_avatar: None,
|
||||||
group_avatar: None,
|
group_avatar: None,
|
||||||
failure_report: None,
|
delivery_report: None,
|
||||||
footer: None,
|
footer: None,
|
||||||
is_mime_modified: false,
|
is_mime_modified: false,
|
||||||
decoded_data: Vec::new(),
|
decoded_data: Vec::new(),
|
||||||
@@ -535,7 +535,7 @@ impl MimeMessage {
|
|||||||
self.parse_system_message_headers(context);
|
self.parse_system_message_headers(context);
|
||||||
self.parse_avatar_headers(context).await;
|
self.parse_avatar_headers(context).await;
|
||||||
self.parse_videochat_headers();
|
self.parse_videochat_headers();
|
||||||
if self.failure_report.is_none() {
|
if self.delivery_report.is_none() {
|
||||||
self.squash_attachment_parts();
|
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 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 => {
|
Some("delivery-status") | None => {
|
||||||
if let Some(report) = self.process_delivery_status(context, mail)? {
|
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)
|
// Add all parts (we need another part, preferably text/plain, to show as an error message)
|
||||||
@@ -1280,9 +1280,46 @@ impl MimeMessage {
|
|||||||
&self,
|
&self,
|
||||||
context: &Context,
|
context: &Context,
|
||||||
report: &mailparse::ParsedMail<'_>,
|
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
|
// 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.contains("rfc822")
|
||||||
|| p.ctype.mimetype == "message/global"
|
|| p.ctype.mimetype == "message/global"
|
||||||
|| p.ctype.mimetype == "message/global-headers"
|
|| p.ctype.mimetype == "message/global-headers"
|
||||||
@@ -1303,9 +1340,10 @@ impl MimeMessage {
|
|||||||
None // We do not know which recipient failed
|
None // We do not know which recipient failed
|
||||||
};
|
};
|
||||||
|
|
||||||
return Ok(Some(FailureReport {
|
return Ok(Some(DeliveryReport {
|
||||||
rfc724_mid: original_message_id,
|
rfc724_mid: original_message_id,
|
||||||
failed_recipient: to.map(|s| s.addr),
|
failed_recipient: to.map(|s| s.addr),
|
||||||
|
failure,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1386,7 +1424,7 @@ impl MimeMessage {
|
|||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
if maybe_ndn && self.failure_report.is_none() {
|
if maybe_ndn && self.delivery_report.is_none() {
|
||||||
static RE: Lazy<regex::Regex> =
|
static RE: Lazy<regex::Regex> =
|
||||||
Lazy::new(|| regex::Regex::new(r"Message-ID:(.*)").unwrap());
|
Lazy::new(|| regex::Regex::new(r"Message-ID:(.*)").unwrap());
|
||||||
for captures in self
|
for captures in self
|
||||||
@@ -1400,9 +1438,10 @@ impl MimeMessage {
|
|||||||
if let Ok(Some(_)) =
|
if let Ok(Some(_)) =
|
||||||
message::rfc724_mid_exists(context, &original_message_id).await
|
message::rfc724_mid_exists(context, &original_message_id).await
|
||||||
{
|
{
|
||||||
self.failure_report = Some(FailureReport {
|
self.delivery_report = Some(DeliveryReport {
|
||||||
rfc724_mid: original_message_id,
|
rfc724_mid: original_message_id,
|
||||||
failed_recipient: None,
|
failed_recipient: None,
|
||||||
|
failure: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1440,13 +1479,15 @@ impl MimeMessage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(failure_report) = &self.failure_report {
|
if let Some(delivery_report) = &self.delivery_report {
|
||||||
let error = parts
|
if delivery_report.failure {
|
||||||
.iter()
|
let error = parts
|
||||||
.find(|p| p.typ == Viewtype::Text)
|
.iter()
|
||||||
.map(|p| p.msg.clone());
|
.find(|p| p.typ == Viewtype::Text)
|
||||||
if let Err(e) = message::handle_ndn(context, failure_report, error).await {
|
.map(|p| p.msg.clone());
|
||||||
warn!(context, "Could not handle ndn: {}", e);
|
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)
|
Ok(gossiped_addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Message Disposition Notification (RFC 8098)
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct Report {
|
pub(crate) struct Report {
|
||||||
/// Original-Message-ID header
|
/// Original-Message-ID header
|
||||||
@@ -1537,10 +1579,12 @@ pub(crate) struct Report {
|
|||||||
additional_message_ids: Vec<String>,
|
additional_message_ids: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Delivery Status Notification (RFC 3464, RFC 6533)
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct FailureReport {
|
pub(crate) struct DeliveryReport {
|
||||||
pub rfc724_mid: String,
|
pub rfc724_mid: String,
|
||||||
pub failed_recipient: Option<String>,
|
pub failed_recipient: Option<String>,
|
||||||
|
pub failure: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::indexing_slicing)]
|
#[allow(clippy::indexing_slicing)]
|
||||||
|
|||||||
@@ -497,9 +497,9 @@ async fn add_parts(
|
|||||||
ChatIdBlocked::lookup_by_contact(context, from_id).await?
|
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);
|
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() {
|
if chat_id.is_none() {
|
||||||
@@ -2749,6 +2749,19 @@ mod tests {
|
|||||||
.await;
|
.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
|
// ndn = Non Delivery Notification
|
||||||
async fn test_parse_ndn(
|
async fn test_parse_ndn(
|
||||||
self_addr: &str,
|
self_addr: &str,
|
||||||
@@ -2798,7 +2811,14 @@ mod tests {
|
|||||||
receive_imf(&t, raw_ndn, false).await.unwrap();
|
receive_imf(&t, raw_ndn, false).await.unwrap();
|
||||||
let msg = Message::load_from_db(&t, msg_id).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()));
|
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