Unit tests part 7

This commit is contained in:
Mauro D 2023-01-24 18:19:51 +00:00
parent 71b9912f17
commit cbae2e22d6
49 changed files with 2050 additions and 378 deletions

6
.gitignore vendored
View file

@ -1,6 +1,4 @@
# Generated by Cargo
# will have compiled files and executables
/target/
# These are backup files generated by rustfmt
**/*.rs.bk
resources/config/stalwart.toml
run.sh

31
Cargo.lock generated
View file

@ -1091,6 +1091,20 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "nix"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a"
dependencies = [
"bitflags",
"cfg-if",
"libc",
"memoffset",
"pin-utils",
"static_assertions",
]
[[package]]
name = "nom"
version = "7.1.3"
@ -1354,6 +1368,16 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "privdrop"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81ed9e5437d82d5f2cde999a21571474c5f09b3d76e33eab94bf0e8e42a4fd96"
dependencies = [
"libc",
"nix",
]
[[package]]
name = "proc-macro2"
version = "1.0.50"
@ -1792,6 +1816,7 @@ dependencies = [
"mail-send",
"num_cpus",
"parking_lot",
"privdrop",
"rand 0.8.5",
"rayon",
"regex",
@ -1835,6 +1860,12 @@ dependencies = [
"der",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "subtle"
version = "2.4.1"

View file

@ -30,6 +30,7 @@ reqwest = { version = "0.11", default-features = false, features = ["rustls-tls"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
num_cpus = "1.15.0"
privdrop = "0.5.3"
[dev-dependencies]
mail-auth = { path = "/home/vagrant/code/mail-auth", features = ["test"] }

View file

@ -2,7 +2,6 @@
Stalwart SMTP Server
# TODO
- Dashmap cleanup
- RBL
- Sieve
- Spam filter

View file

@ -1,27 +1,28 @@
[server]
hostname = "mx.example.org"
greeting = "Stalwart SMTP at your service"
hostname = "%%HOST%%"
#greeting = "Stalwart SMTP at your service"
protocol = "smtp"
[server.listener."smtp"]
bind = ["0.0.0.0:9025"]
tls.implicit = false
[server.run-as]
user = "stalwart-smtp"
group = "stalwart-smtp"
[server.listener."smtps"]
bind = ["0.0.0.0:9465"]
#tls.sni = [{subject = "domain.org", pki = "abc"}]
#socket.backlog = 1024
[server.listener."smtp"]
bind = ["0.0.0.0:25"]
[server.listener."submission"]
bind = ["0.0.0.0:9587"]
#tls = {implicit = true}
bind = ["0.0.0.0:587"]
[server.listener."submissions"]
bind = ["0.0.0.0:465"]
tls.implicit = true
[server.tls]
enable = true
implicit = true
implicit = false
timeout = "1m"
certificate = "default"
#sni = [{subject = "domain.org", pki = "abc"}]
#sni = [{subject = "", certificate = ""}]
#protocols = ["TLSv1.2", TLSv1.3"]
#ciphers = []
ignore-client-order = true
@ -39,11 +40,12 @@ backlog = 1024
[global]
log-level = "trace"
concurrency = 1024
throttle-map = {shard = 32, capacity = 10}
shared-map = {shard = 32, capacity = 10}
#thread-pool = 8
[session]
timeout = "5m"
transfer-limit = 5000000
transfer-limit = 262144000 # 250 MB
duration = "10m"
[session.connect]
@ -58,21 +60,22 @@ pipelining = true
chunking = true
requiretls = true
no-soliciting = ""
dsn = true
future-release = [
{ if = "listener", eq = "submission", then = "5d"},
{ else = false }
]
deliver-by = false
mt-priority = false
dsn = [ { if = "authenticated-as", ne = "", then = true},
{ else = false } ]
future-release = [ { if = "authenticated-as", ne = "", then = "7d"},
{ else = false } ]
deliver-by = [ { if = "authenticated-as", ne = "", then = "15d"},
{ else = false } ]
mt-priority = [ { if = "authenticated-as", ne = "", then = "mixer"},
{ else = false } ]
[session.auth]
mechanisms = [
{ if = "listener", eq = "submission", then = ["plain", "login"]},
{ else = false }
]
lookup = [ { if = "listener", eq = "submission", then = "local-addresses" },
mechanisms = [ { if = "listener", ne = "smtp", then = ["plain", "login"]},
{ else = [] } ]
lookup = [ { if = "listener", ne = "smtp", then = "authentication" },
{ else = false } ]
require = [ { if = "listener", ne = "smtp", then = true},
{ else = false } ]
[session.auth.errors]
total = 3
@ -80,13 +83,12 @@ wait = "5s"
[session.mail]
#script = mail-from.sieve
timeout = 10
[session.rcpt]
#script = rcpt-to.sieve
relay = [ { if = "authenticated-as", ne = "", then = true },
{ else = false } ]
max-recipients = 100
{ else = false } ]
max-recipients = 25
[session.rcpt.lookup]
domains = "local-domains"
@ -97,7 +99,7 @@ expn = [ { if = "authenticated-as", ne = "", then = "local-addresses" },
{ else = false } ]
[session.rcpt.errors]
total = 3
total = 5
wait = "5s"
[session.data]
@ -105,222 +107,245 @@ wait = "5s"
[session.data.limits]
messages = 10
size = 100000
size = 104857600
received-headers = 50
mime-parts = 50
nested-messages = 3
[session.data.add-headers]
received = true
received-spf = true
return-path = true
auth-results = true
message-id = true
date = true
received = [ { if = "listener", eq = "smtp", then = true },
{ else = false } ]
received-spf = [ { if = "listener", eq = "smtp", then = true },
{ else = false } ]
auth-results = [ { if = "listener", eq = "smtp", then = true },
{ else = false } ]
message-id = [ { if = "listener", eq = "smtp", then = false },
{ else = true } ]
date = [ { if = "listener", eq = "smtp", then = false },
{ else = true } ]
return-path = false
[[session.throttle]]
match = {if = "remote-ip", eq = "127.0.0.1"}
key = ["remote-ip", "authenticated-as"]
concurrency = 100
rate = "50/30s"
#match = {if = "remote-ip", eq = "10.0.0.1"}
key = ["remote-ip"]
concurrency = 5
#rate = "5/1h"
[[session.throttle]]
key = "sender-domain"
concurrency = 10000
key = ["sender-domain", "rcpt"]
rate = "25/1h"
[auth.iprev]
verify = "strict"
verify = [ { if = "listener", eq = "smtp", then = "relaxed" },
{ else = "disable" } ]
[auth.dkim]
verify = "strict"
sign = true
verify = "relaxed"
sign = [ { if = "listener", ne = "smtp", then = ["rsa"] },
{ else = [] } ]
[signature."default"]
public-key = "cert-name"
private-key = "cert-name"
domain = "example.org"
selector = ""
headers = ["From", "To", "Date", "Subject", "Message-ID"]
algorithm = "rsa-sha256"
canonicalization = "simple/relaxed"
expire = "10d"
third-party = ""
third-party-algo = ""
auid = ""
set-body-length = false
report = true
[auth.spf]
verify.ehlo = "relaxed"
verify.mail-from = "relaxed"
[auth.spf.verify]
ehlo = [ { if = "listener", eq = "smtp", then = "relaxed" },
{ else = "disable" } ]
mail-from = [ { if = "listener", eq = "smtp", then = "relaxed" },
{ else = "disable" } ]
[auth.arc]
verify = "strict"
seal = true
verify = "relaxed"
seal = ["rsa"]
[auth.dmarc]
verify = "strict"
[remote."lmtp"]
address = 192.168.0.1
port = 25
protocol = "lmtp"
[remote."lmtp".auth]
username = "hello"
secret = "world"
[remote."lmtp".cache]
entries = 1000
ttl = {positive = 10, negative = 5}
[remote."lmtp".tls]
implicit = true
allow-invalid-certs = true
verify = [ { if = "listener", eq = "smtp", then = "relaxed" },
{ else = "disable" } ]
[queue]
path = "/var/spool/queue"
hash = 123
path = "%%QUEUE_DIR%%"
hash = 64
[queue.schedule]
retry = ["0m", "2m", "5m", "10m", "15m", "30m", "1h", "2h"]
retry = ["2m", "5m", "10m", "15m", "30m", "1h", "2h"]
notify = ["1d", "3d"]
expire = "5d"
[queue.outbound]
#hostname = mx.domain.org
next-hop = "lmtp"
next-hop = [ { if = "rcpt-domain", in-list = "local-domains", then = "lmtp" },
{ else = false } ]
ip-strategy = "ipv4-then-ipv6"
[queue.outbound.tls]
dane = require
mta-sts = disabled
starttls = optional
dane = "optional"
mta-sts = "optional"
starttls = "require"
[queue.outbound.source-ip]
v4 = ["192.168.0.2", "162.168.0.1"]
v6 = ["192.168.0.2", "162.168.0.1"]
#[queue.outbound.source-ip]
#v4 = ["10.0.0.10", "10.0.0.11"]
#v6 = ["a::b", "a::c"]
[queue.outbound.limits]
mx = 5
mx = 7
multihomed = 2
[queue.outbound.timeouts]
connect = "1m"
greeting = "1m"
tls = "1m"
ehlo = "1m"
mail-from = "1m"
rcpt-to = "1m"
data = "5m"
mta-sts = "1m"
connect = "3m"
greeting = "3m"
tls = "2m"
ehlo = "3m"
mail-from = "3m"
rcpt-to = "3m"
data = "10m"
mta-sts = "2m"
[[queue.quota]]
match = {if = "remote-ip", eq = "127.0.0.1"}
key = [""]
messages = 10000
size = 1000000
#match = {if = "remote-ip", eq = "10.0.0.1"}
#key = ["rcpt"]
messages = 100000
size = 10737418240 # 10gb
[[queue.throttle]]
rate = "1/60s"
concurrency = 1000
key = ["remote-ip"]
key = ["rcpt-domain"]
#rate = "100/1h"
concurrency = 5
[resolver]
type = "cloudflare"
strategy = "ipv6"
dnssec = true
preserve-intermediates = true
type = "cloudflare-tls"
#preserve-intermediates = true
concurrency = 2
timeout = 100
attempts = 3
timeout = "5s"
attempts = 2
try-tcp-on-error = true
[resolver.cache]
a = 1000
mx = 9393
txt = 3233
tlsa = 333
txt = 2048
mx = 1024
ipv4 = 1024
ipv6 = 1024
ptr = 1024
tlsa = 1024
mta-sts = 1024
[report]
path = "%%QUEUE_DIR%%"
hash = 64
#submitter = "mx.domain.org"
[scripts]
[report.analysis]
addresses = ["dmarc@*", "abuse@*"]
forward = true
#store = "/var/spool/report"
[scripts]
ehlo = "this is my script"
[report.dsn]
from-name = "Mail Delivery Subsystem"
from-address = "MAILER-DAEMON@%%DOMAIN%%"
sign = ["rsa"]
[report.dkim]
from-name = "Report Subsystem"
from-address = "noreply-dkim@%%DOMAIN%%"
subject = "DKIM Authentication Failure Report"
sign = ["rsa"]
send = "1/1d"
[report.spf]
from-name = "Report Subsystem"
from-address = "noreply-spf@%%DOMAIN%%"
subject = "SPF Authentication Failure Report"
send = "1/1d"
sign = ["rsa"]
[report.dmarc]
from-name = "Report Subsystem"
from-address = "noreply-dmarc@%%DOMAIN%%"
subject = "DMARC Authentication Failure Report"
send = "1/1d"
sign = ["rsa"]
[report.dmarc.aggregate]
from-name = "DMARC Report"
from-address = "noreply-dmarc@%%DOMAIN%%"
org-name = "%%DOMAIN%%"
#contact-info = ""
send = "daily"
max-size = 26214400 # 25mb
sign = ["rsa"]
[report.tls.aggregate]
from-name = "TLS Report"
from-address = "noreply-tls@%%DOMAIN%%"
org-name = "%%DOMAIN%%"
#contact-info = ""
send = "daily"
max-size = 26214400 # 25 mb
sign = ["rsa"]
[signature."rsa"]
public-key = "%%DKIM_PUBLIC%%"
private-key = "%%DKIM_PRIVATE%%"
domain = "%%DOMAIN%%"
selector = "%%DKIM_SELECTOR%%"
headers = ["From", "To", "Date", "Subject", "Message-ID"]
algorithm = "rsa-sha256"
canonicalization = "relaxed/relaxed"
#expire = "10d"
#third-party = ""
#third-party-algo = ""
#auid = ""
set-body-length = false
report = true
[remote."lmtp"]
address = "%LMTP_HOST%"
port = 25
protocol = "lmtp"
timeout = "1m"
[remote."lmtp".cache]
entries = 1000
ttl = {positive = "1d", negative = "1h"}
[remote."lmtp".tls]
implicit = false
allow-invalid-certs = true
#[remote."lmtp".auth]
#username = ""
#secret = ""
[remote."lmtp".limits]
errors = 3
requests = 50
[remote."imap"]
address = "%IMAP_HOST%"
port = 143
protocol = "imap"
timeout = "1m"
[remote."imap".cache]
entries = 1000
ttl = {positive = "1d", negative = "1h"}
[remote."imap".tls]
implicit = false
allow-invalid-certs = true
#[scripts]
#ehlo = "this is my script"
[list."local-domains"]
type = "inline"
items = ["example.org", "*.example.net"]
items = ["%%DOMAIN%%"]
[list."local-addresses"]
type = "remote"
host = "lmtp"
[list."authentication"]
type = "remote"
host = "imap"
#[list."local-users"]
#type = "file"
#path = "/tmp/file.txt"
[report]
path = "/var/spool/report"
hash = 16
submitter = "mx.domain.org"
[report.analysis]
addresses = ["dmarc@*", "abuse@*"]
forward = true
store = "/var/spool/report"
[report.dsn]
from-name = "Mail Delivery Subsystem"
from-address = "MAILER-DAEMON@domain.org"
subject = "Delivery Status Notification"
sign = []
[report.dkim]
from-name = "Autentication Report"
from-address = "noreply-auth-failure"
subject = "SPF Authentication Failure Report"
send = "1/20d"
[report.spf]
from-name = "Autentication Report"
from-address = "noreply-auth-failure"
subject = "SPF Authentication Failure Report"
sign = []
send = "1/20d"
[report.dmarc]
from-name = "DMARC report"
from-address = "noreply-dmarc"
subject = "DMARC Failure Report"
send = "1/20d"
sign = []
[report.dmarc.aggregate]
from-name = "DMARC report"
from-address = "noreply-dmarc"
subject = "DMARC aggregate report for $1"
org-name = "Mycorp"
contact-info = ""
send = never
max-size = 10000
sign = []
[report.tls.aggregate]
from-name = "Autentication Report"
from-address = "noreply-auth-failure"
subject = "TLS Failure Report"
org-name = "Mycorp"
contact-info = ""
send = never
max-size = 10000
sign = []
[servers."relay".dmarc]
send-reports = true
#report-frequency = requested, 86400
incoming-address = "dmarc@*"
[certificate]
[certificate."default"]
cert = '''
-----BEGIN CERTIFICATE-----

View file

@ -0,0 +1,44 @@
From: <abusedesk@example.com>
Date: Thu, 8 Mar 2005 17:40:36 EDT
Subject: FW: Earn money
To: <abuse@example.net>
MIME-Version: 1.0
Content-Type: multipart/report; report-type=feedback-report;
boundary="part1_13d.2e68ed54_boundary"
--part1_13d.2e68ed54_boundary
Content-Type: text/plain; charset="US-ASCII"
Content-Transfer-Encoding: 7bit
This is an email abuse report for an email message received from IP
192.0.2.1 on Thu, 8 Mar 2005 14:00:00 EDT. For more information
about this format please see http://www.mipassoc.org/arf/.
--part1_13d.2e68ed54_boundary
Content-Type: message/feedback-report
Feedback-Type: abuse
User-Agent: SomeGenerator/1.0
Version: 1
--part1_13d.2e68ed54_boundary
Content-Type: message/rfc822
Content-Disposition: inline
Received: from mailserver.example.net
(mailserver.example.net [192.0.2.1])
by example.com with ESMTP id M63d4137594e46;
Thu, 08 Mar 2005 14:00:00 -0400
From: <somespammer@example.net>
To: <Undisclosed Recipients>
Subject: Earn money
MIME-Version: 1.0
Content-type: text/plain
Message-ID: 8787KJKJ3K4J3K4J3K4J3.mail@example.net
Date: Thu, 02 Sep 2004 12:31:03 -0500
Spam Spam Spam
Spam Spam Spam
Spam Spam Spam
Spam Spam Spam
--part1_13d.2e68ed54_boundary--

View file

@ -0,0 +1,55 @@
From: <abusedesk@example.com>
Date: Thu, 8 Mar 2005 17:40:36 EDT
Subject: FW: Earn money
To: <abuse@example.net>
MIME-Version: 1.0
Content-Type: multipart/report; report-type=feedback-report;
boundary="part1_13d.2e68ed54_boundary"
--part1_13d.2e68ed54_boundary
Content-Type: text/plain; charset="US-ASCII"
Content-Transfer-Encoding: 7bit
This is an email abuse report for an email message received from IP
192.0.2.1 on Thu, 8 Mar 2005 14:00:00 EDT. For more information
about this format please see http://www.mipassoc.org/arf/.
--part1_13d.2e68ed54_boundary
Content-Type: message/feedback-report
Feedback-Type: abuse
User-Agent: SomeGenerator/1.0
Version: 1
Original-Mail-From: <somespammer@example.net>
Original-Rcpt-To: <user@example.com>
Arrival-Date: Thu, 8 Mar 2005 14:00:00 EDT
Reporting-MTA: dns; mail.example.com
Source-IP: 192.0.2.1
Authentication-Results: mail.example.com;
spf=fail smtp.mail=somespammer@example.com
Reported-Domain: example.net
Reported-Uri: http://example.net/earn_money.html
Reported-Uri: mailto:user@example.com
Removal-Recipient: user@example.com
--part1_13d.2e68ed54_boundary
Content-Type: message/rfc822
Content-Disposition: inline
From: <somespammer@example.net>
Received: from mailserver.example.net (mailserver.example.net
[192.0.2.1]) by example.com with ESMTP id M63d4137594e46;
Thu, 08 Mar 2005 14:00:00 -0400
To: <Undisclosed Recipients>
Subject: Earn money
MIME-Version: 1.0
Content-type: text/plain
Message-ID: 8787KJKJ3K4J3K4J3K4J3.mail@example.net
Date: Thu, 02 Sep 2004 12:31:03 -0500
Spam Spam Spam
Spam Spam Spam
Spam Spam Spam
Spam Spam Spam
--part1_13d.2e68ed54_boundary--

View file

@ -0,0 +1,58 @@
From: arf-daemon@example.com
To: recipient@example.net
Subject: This is a test
Date: Wed, 14 Apr 2010 12:17:45 -0700 (PDT)
MIME-Version: 1.0
Content-Type: multipart/report; report-type=feedback-report;
boundary="part1_13d.2e68ed54_boundary"
--part1_13d.2e68ed54_boundary
Content-Type: text/plain; charset="US-ASCII"
Content-Transfer-Encoding: 7bit
This is an email abuse report for an email message received
from IP 192.0.2.1 on Wed, 14 Apr 2010 12:15:31 PDT. For more
information about this format please see
http://www.mipassoc.org/arf/.
--part1_13d.2e68ed54_boundary
Content-Type: message/feedback-report
Feedback-Type: auth-failure
User-Agent: SomeDKIMFilter/1.0
Version: 1
Original-Mail-From: <randomuser@example.net>
Original-Rcpt-To: <user@example.com>
Received-Date: Wed, 14 Apr 2010 12:15:31 -0700 (PDT)
Source-IP: 192.0.2.1
Authentication-Results: mail.example.com; dkim=fail
header.d=example.net
Reported-Domain: example.net
DKIM-Domain: example.net
Auth-Failure: bodyhash
--part1_13d.2e68ed54_boundary
Content-Type: message/rfc822
DKIM-Signature: v=1; c=relaxed/simple; a=rsa-sha256;
s=testkey; d=example.net; h=From:To:Subject:Date;
bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;
b=AuUoFEfDxTDkHlLXSZEpZj79LICEps6eda7W3deTVFOk4yAUoqOB
4nujc7YopdG5dWLSdNg6xNAZpOPr+kHxt1IrE+NahM6L/LbvaHut
KVdkLLkpVaVVQPzeRDI009SO2Il5Lu7rDNH6mZckBdrIx0orEtZV
4bmp/YzhwvcubU4=
Received: from smtp-out.example.net by mail.example.com
with SMTP id o3F52gxO029144;
Wed, 14 Apr 2010 12:15:31 -0700 (PDT)
Received: from internal-client-001.example.com
by mail.example.com
with SMTP id o3F3BwdY028431;
Wed, 14 Apr 2010 12:12:09 -0700 (PDT)
From: randomuser@example.net
To: user@example.com
Date: Wed, 14 Apr 2010 12:12:09 -0700 (PDT)
Subject: This is a test
Hi, just making sure DKIM is working!
--part1_13d.2e68ed54_boundary--

View file

@ -0,0 +1,73 @@
Return-Path: <opendmarc@box.mydomain.name>
Received: by box.mydomain.name (Postfix, from userid 116)
id CF8FA658E0; Tue, 5 Oct 2021 17:37:02 +1300 (NZDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=box.mydomain.name;
s=mail; t=1633408622;
bh=yDlkGfe4dFwlsFeoKaIHG6xiRgQs2/PqPnLtiPm5ewk=;
h=From:To:Date:Subject:From;
b=TwXvJJFoFwJDcb6IKMKsxp2BiRDsrjLOESyQPh/Cc4tRZltVAud/k6f0XP4l5a/T8
kh0iDOGImc0O1WZNFt0MUcwLsfW4qbYjCBtthQDnbPApvv6MJDASwau+wipu5Nrkjc
flg+nMaD97pVgR0LevMoVIWoiy1f5PNC/z0xkY2wnvyoGn91WuDsdocOqyoPo4RmIT
A/f3M4CjOv/QmMEAWBIsa7kAZwf+rNmzDahFOtp2vFLqHt0iZi5vs40fa6O/I0snTM
fRkv2GMZAug7NMU8MN/MhuO87FV6ATZXvB0Kxvsy9z0zZYK7tM1OYHjiCYot45erG3
dlKrYiXsfd3BQ==
From: OpenDMARC Filter <opendmarc@box.mydomain.name>
To: postmaster@vericty.interpublication.org
Date: Tue, 5 Oct 2021 17:37:02 +1300 (NZDT)
Subject: FW: Wir kaufen dein Auto!
MIME-Version: 1.0
Content-Type: multipart/report;
report-type=feedback-report;
boundary="box.mydomain.name:8BE2660E72"
Message-Id: <20211005043702.CF8FA658E0@box.mydomain.name>
--box.mydomain.name:8BE2660E72
Content-Type: text/plain
This is an authentication failure report for an email message received from IP
148.163.85.135 on Tue, 5 Oct 2021 17:37:02 +1300 (NZDT).
--box.mydomain.name:8BE2660E72
Content-Type: message/feedback-report
Feedback-Type: auth-failure
Version: 1
User-Agent: OpenDMARC-Filter/1.3.2
Auth-Failure: dmarc
Authentication-Results: box.mydomain.name; dmarc=fail header.from=interpublication.org
Original-Envelope-Id: 8BE2660E72
Original-Mail-From: info@interpublication.org
Source-IP: 148.163.85.135 (sainay.interpublication.org)
Reported-Domain: interpublication.org
--box.mydomain.name:8BE2660E72
Content-Type: text/rfc822-headers
Authentication-Results: box.mydomain.name;
dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=interpublication.org header.i=@interpublication.org header.b="PrsTNnuH";
dkim-atps=neutral
Received: from dslb-002-202-150-127.002.202.pools.vodafone-ip.de (dslb-188-099-080-029.188.099.pools.vodafone-ip.de [188.99.80.29])
by sainay.interpublication.org (Postfix) with ESMTPA id 6BB23A2D3
for <address@myotherdomain.name>; Tue, 5 Oct 2021 00:36:52 -0400 (EDT)
DKIM-Filter: OpenDKIM Filter v2.11.0 sainay.interpublication.org 6BB23A2D3
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=interpublication.org; s=default; t=1633408612;
bh=q1/OPSn+VXteY2+DHXqOIgs5LsNCJisEcQIKVW9it6I=;
h=From:Subject:To:Reply-To:Date:From;
b=PrsTNnuH8D0Ch3gcWqGmXiYc2Kvu1CHGJBsqS521uYazd3G/urp7MHQvmNwK0r1gS
DR3A3KwGejI5uuqzxDCqz28Mq6AkdTkOjFyXw65MLlsKTQddWTgciVnoqJempa6yzw
PSM5550XqVFqqkNxEcYBUBYEwUdy1tY8rc4zhq8cIrsonQVxJJSbc3cdonICM1kLBV
WASv16p3376ZBcKqFLc8UQ58YQKaFm51VZGEjtabfmWbgOQ7VikFFECDG3aRt8fZa6
D03MrzUSngwPUdcRQZuqS/sApW/a9N2YwdbR51OFzPBr4ypUEIw/qprgBG4BfQQKeS
1PhinNvVtgQpQ==
From: "Rolf Bader" <info@interpublication.org>
Subject: Wir kaufen dein Auto!
To: "address" <address@myotherdomain.name>
Content-Type: multipart/alternative; boundary="TD6gM3Blv=_XBZYNFT7dCsH1DHHOKUuSyA"
MIME-Version: 1.0
Reply-To: "Rolf Bader" <auto24-export@gmx.de>
Organization: AutoTEAM24
Date: Tue, 5 Oct 2021 06:36:51 +0200
--box.mydomain.name:8BE2660E72--

View file

@ -0,0 +1,87 @@
Message-ID: <433689.81121.example@mta.mail.receiver.example>
From: "SomeISP Antispam Feedback" <feedback@mail.receiver.example>
To: arf-failure@sender.example
Subject: FW: You have a new bill from your bank
Date: Sat, 8 Oct 2011 15:15:59 -0500 (CDT)
MIME-Version: 1.0
Content-Type: multipart/report;
boundary="------------Boundary-00=_3BCR4Y7kX93yP9uUPRhg";
report-type=feedback-report
Content-Transfer-Encoding: 7bit
--------------Boundary-00=_3BCR4Y7kX93yP9uUPRhg
Content-Type: text/plain; charset="us-ascii"
Content-Disposition: inline
Content-Transfer-Encoding: 7bit
This is an authentication failure report for an email message
received from a.sender.example on 8 Oct 2011 20:15:58 +0000 (GMT).
For more information about this format, please see [RFC6591].
--------------Boundary-00=_3BCR4Y7kX93yP9uUPRhg
Content-Type: message/feedback-report
Content-Transfer-Encoding: 7bit
Feedback-Type: auth-failure
User-Agent: Someisp!Mail-Feedback/1.0
Version: 1
Original-Mail-From: anexample.reply@a.sender.example
Original-Envelope-Id: o3F52gxO029144
Authentication-Results: mta1011.mail.tp2.receiver.example;
dkim=fail (bodyhash) header.d=sender.example
Auth-Failure: bodyhash
DKIM-Canonicalized-Body: VGhpcyBpcyBhIG1lc3NhZ2UgYm9keSB0
aGF0IGdvdCBtb2RpZmllZCBpbiB0cmFuc2l0LgoKQXQgdGhlIHNhbWU
gdGltZSB0aGF0IHRoZSBib2R5aGFzaCBmYWlscyB0byB2ZXJpZnksIH
RoZQptZXNzYWdlIGNvbnRlbnQgaXMgY2xlYXJseSBhYnVzaXZlIG9yI
HBoaXNoeSwgYXMgdGhlClN1YmplY3QgYWxyZWFkeSBoaW50cy4gIElu
ZGVlZCwgdGhpcyBib2R5IGFsc28gY29udGFpbnMKdGhlIGZvbGxvd2l
uZyB0ZXh0OgoKICAgUGxlYXNlIGVudGVyIHlvdXIgZnVsbCBiYW5rIG
NyZWRlbnRpYWxzIGF0CiAgIGh0dHA6Ly93d3cuc2VuZGVyLmV4YW1wb
GUvCgpXZSBhcmUgaW1wbHlpbmcgdGhhdCwgYWx0aG91Z2ggbXVsdGlw
bGUgZmFpbHVyZXMKcmVxdWlyZSBtdWx0aXBsZSByZXBvcnRzLCBhIHN
pbmdsZSBmYWlsdXJlIGNhbiBiZQpyZXBvcnRlZCBhbG9uZyB3aXRoIH
BoaXNoaW5nIGluIGEgc2luZ2xlIHJlcG9ydC4K
DKIM-Domain: sender.example
DKIM-Identity: @sender.example
DKIM-Selector: testkey
Arrival-Date: 8 Oct 2011 20:15:58 +0000 (GMT)
Source-IP: 192.0.2.1
Reported-Domain: a.sender.example
Reported-URI: http://www.sender.example/
--------------Boundary-00=_3BCR4Y7kX93yP9uUPRhg
Content-Type: text/rfc822-headers
Content-Transfer-Encoding: 7bit
Authentication-Results: mta1011.mail.tp2.receiver.example;
dkim=fail (bodyhash) header.d=sender.example;
spf=pass smtp.mailfrom=anexample.reply@a.sender.example
Received: from smtp-out.sender.example
by mta1011.mail.tp2.receiver.example
with SMTP id oB85W8xV000169;
Sat, 08 Oct 2011 13:15:58 -0700 (PDT)
DKIM-Signature: v=1; c=relaxed/simple; a=rsa-sha256;
s=testkey; d=sender.example; h=From:To:Subject:Date;
bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;
b=AuUoFEfDxTDkHlLXSZEpZj79LICEps6eda7W3deTVFOk4yAUoqOB
4nujc7YopdG5dWLSdNg6xNAZpOPr+kHxt1IrE+NahM6L/LbvaHut
KVdkLLkpVaVVQPzeRDI009SO2Il5Lu7rDNH6mZckBdrIx0orEtZV
4bmp/YzhwvcubU4=
Received: from mail.sender.example
by smtp-out.sender.example
with SMTP id o3F52gxO029144;
Sat, 08 Oct 2011 13:15:31 -0700 (PDT)
Received: from internal-client-001.sender.example
by mail.sender.example
with SMTP id o3F3BwdY028431;
Sat, 08 Oct 2011 13:15:24 -0700 (PDT)
Date: Sat, 8 Oct 2011 16:15:24 -0400 (EDT)
Reply-To: anexample.reply@a.sender.example
From: anexample@a.sender.example
To: someuser@receiver.example
Subject: You have a new bill from your bank
Message-ID: <87913910.1318094604546@out.sender.example>
--------------Boundary-00=_3BCR4Y7kX93yP9uUPRhg--

View file

@ -0,0 +1,66 @@
Received: from mail.stalw.art ([mail.stalw.art])
by 127.0.0.1 (Stalwart JMAP) with LMTP;
Mon, 28 Nov 2022 10:51:56 +0000
Received: from mail-qv1-xf4a.google.com (mail-qv1-xf4a.google.com [IPv6:2607:f8b0:4864:20::f4a])
(using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits)
key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256)
(No client certificate requested)
by mail.stalw.art (Postfix) with ESMTPS id 1145E7CC0B
for <domains@stalw.art>; Mon, 28 Nov 2022 10:51:53 +0000 (UTC)
Received: by mail-qv1-xf4a.google.com with SMTP id 71-20020a0c804d000000b004b2fb260447so12985969qva.10
for <domains@stalw.art>; Mon, 28 Nov 2022 02:51:52 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=google.com; s=20210112;
h=content-transfer-encoding:content-disposition:to:from:subject
:message-id:date:mime-version:from:to:cc:subject:date:message-id
:reply-to;
bh=sMF/38UFRhmUYFRJST4vLBu/U1BXgsdCUE02HF8nXx8=;
b=I7WONP7tMsULp4eKjJeeKtM+nDYqMSIYMxqNHqCP1bTsnUiW2xM278I2+F8EjtFNYf
XOgusNn8kqbSnA4w1+q4G87zTF4K3tGnxNpuUMQ7GzcofBKtr7VPv9XFqvTPJ+N8YSwe
926ec7xi71BpSHAgqp5Wqocj8ruIVjcCZ37hYrG0C4s+FVBtbaU3EeyPpkESaaY2vE5y
Qa2KsrMsyJXlbyW/sFJ7AGDDuXwyGkTa+btP/xIiQM2HlBKy7vNOFZKkxInOuQsXJgZy
3H7ivlpD3hMrszwU77o5jBArVwN0RIkUSosAPQf+pzgvRlkseRlDrmzKQutvYWIaTP3/
FHPA==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=1e100.net; s=20210112;
h=content-transfer-encoding:content-disposition:to:from:subject
:message-id:date:mime-version:x-gm-message-state:from:to:cc:subject
:date:message-id:reply-to;
bh=sMF/38UFRhmUYFRJST4vLBu/U1BXgsdCUE02HF8nXx8=;
b=D3oClvT5AKcTpEjjffHQqPPQ9j5mmtExiviSq7iBYkoq+322LtR2hGqGxtvlAwRDsQ
VIfuKVExygw3c9bckjzKtJYX128HGK35gHnmsrzqvCC93JlRaC/55kcM9Bhks0xJnl7i
yNFHPZ0DY/jasdUdQ1QqnI+8qiPy+/12JvD+/TGlaDuS+RWYFU4/ky46S3vMXwXmRt6D
IGggXoW7snSaM4s88DzMUl0U7DH823UPQrUxnA5Oxscwn9M1ENJUWD/3EJo5ZEUMw0ll
Y8AlyhjWFgVqs1Y4V/LVWeXdF10fpm78+jm8QyIZYZJjh4I33AekdsWVM71ZNNYGL0+8
+GDg==
X-Gm-Message-State: ANoB5pn0zRZSWXdFXd9G0tawbSeUYuhxToVkIYoLf8OJzoBLcIc0wKcB
AfU9Coz5vAuiM1mASWJhbg==
X-Google-Smtp-Source: AA0mqf6WWsnqMD4cHE40jB89/zblmT7yNKeHKlsvvCmlYANKmpKLTQTaCm5qCA0mVmxR/PTQogsNndWH/qe0ug==
MIME-Version: 1.0
X-Received: by 2002:ac8:5182:0:b0:39c:cb6a:300b with SMTP id
c2-20020ac85182000000b0039ccb6a300bmr48409299qtn.181.1669632711968; Mon, 28
Nov 2022 02:51:51 -0800 (PST)
Date: Sun, 27 Nov 2022 15:59:59 -0800
Message-ID: <5264580628977113351@google.com>
Subject: Report domain: stalw.art Submitter: google.com Report-ID: 5264580628977113351
From: noreply-dmarc-support@google.com
To: domains@stalw.art
Content-Type: application/zip;
name="google.com!stalw.art!1669507200!1669593599.zip"
Content-Disposition: attachment;
filename="google.com!stalw.art!1669507200!1669593599.zip"
Content-Transfer-Encoding: base64
UEsDBAoAAAAIAHFUfFWAeOSU8QEAAKkEAAAuAAAAZ29vZ2xlLmNvbSFzdGFsdy5hcnQhMTY2OTUw
NzIwMCExNjY5NTkzNTk5LnhtbKVUwZKjIBC9z1ekck9Qk5hoMcye9gt2zxbB1lBBoACTmb9fHNCw
ma257El83f2632sUv70PYnUDY7mSr+t8m61XIJlquexf179//dyc1qs38oI7gPZM2ZW8rFbYgFbG
NQM42lJHJ8yjyvSNpAOQXqlewJapAaMFDDkwUC6IVJ5BfGzagRq2saOe6H6kZSEv1rw7QxumpKPM
NVx2ilyc07ZGKJZuH6WIIirtHQwq9mV5OGWe62t9II4yeEsORbn3uWVxqo7HPN/tDjlGj3BI91Kh
MVT2UYyHztBzSfKyrA7Zsch8s4DMcZBtiFa7Q1X5UeRMhv5mW7qlnmKtBGcfjR7PgtsLLIMo744k
1lFx31LjPFlAQpi2Vz4Qg1E4RNDq7hObngHSfg8SMNLx3c6AnRHNHMknVdPhc8p/TeR9ZMrMwxl1
X+RbNRoGDdekoFle77uqZlme1+f9jtW1t/iRMJcwNUrfFKNwmOHYF25UjN64dg5MbnCrleXOX+A4
f4okeZMZmlrrExZfovAuBhZzEq1PPf2mZoWYtyAd77j/fJayC9AWTNMZNaQbSuHI86Ua09FdGgN2
FO5B+DTs98uP93piiJLiS6IWBDCnDLmB4FdujaayKLz2GV8MSDvjxJr/niIx2t/IJ9FTcrhPGD3+
On8AUEsBAgoACgAAAAgAcVR8VYB45JTxAQAAqQQAAC4AAAAAAAAAAAAAAAAAAAAAAGdvb2dsZS5j
b20hc3RhbHcuYXJ0ITE2Njk1MDcyMDAhMTY2OTU5MzU5OS54bWxQSwUGAAAAAAEAAQBcAAAAPQIA
AAAA

View file

@ -0,0 +1,68 @@
Received: from mail.stalw.art ([mail.stalw.art])
by 127.0.0.1 (Stalwart JMAP) with LMTP;
Thu, 10 Nov 2022 03:27:19 +0000
Received: from mx0.backschues.net (lnxs001.backschues.net [85.183.142.13])
(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256)
(No client certificate requested)
by mail.stalw.art (Postfix) with ESMTPS id 6DD117CC0B
for <domains@stalw.art>; Thu, 10 Nov 2022 03:27:16 +0000 (UTC)
Received: from mx0.backschues.net (localhost [127.0.0.1])
by mx0.backschues.net with SMTP id 4N76hg4lNgz9ryP
for <domains@stalw.art>; Thu, 10 Nov 2022 04:27:15 +0100 (CET)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=backschues.net;
s=mail-2014-01; t=1668050835;
h=from:from:reply-to:subject:subject:date:date:message-id:message-id:
to:to:cc:mime-version:mime-version:content-type:content-type;
bh=LTj1tdFz9JQFL/mVJASN0b9hGcolcCtY5v0bhnChJYY=;
b=AtRegYc51PTYqDOy/6fB4xETTWAbVc2ivf8AfF4ygu3+6+oqBPyloTuOnEt7xYmjLFnll/
SMZFFpRETsMlkiVg/1O0VpPRpIpiTbh4dwtUrRyo1Uw/cDJv5auz4rBMxcRNnDKypHwUKs
BUahHWsVKH/TL5SzV79kqyjlYAs1HdJvS+wRINYBaptkeT6UeHGZakL21NnQUdOGt0fj4y
eJvWVtCYHZ5DUJ8K8h2W1NlTAWP8nTBoQVVDQrI5Zi1AEvnUWw+H7E8d/q2cF756/IBYso
rT56D3PYo2iuSt3aIBth1wL7/GJwc6N4JHcNpJ9XPV6xQbt+lm2b3+W59osL0Q==
DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=backschues.net;
s=ed25519-mail-2018-10; t=1668050835;
h=from:from:reply-to:subject:subject:date:date:message-id:message-id:
to:to:cc:mime-version:mime-version:content-type:content-type;
bh=LTj1tdFz9JQFL/mVJASN0b9hGcolcCtY5v0bhnChJYY=;
b=y7d79OWWCrDX40k91FoBdGnUcrjN7xvWYyqskPfQmMoaSqFNSlTHH8gMXC/vXwiYIP3Oxp
d/hVvEuIIQBlwMDQ==
From: "DMARC Aggregate Report" <noreply-dmarc-support@backschues.net>
To: domains@stalw.art
Subject: Report Domain: stalw.art
Submitter: backschues.net
Report-ID: stalw.art.1667948400.1668034800
Date: Thu, 10 Nov 2022 03:27:02 GMT
MIME-Version: 1.0
Message-ID: <afe541eab3ec091f@backschues.net>
Content-Type: multipart/mixed;
boundary="----=_NextPart_84e1fdd0-b285-4922-9fc7-88b070204303"
This is a multipart message in MIME format.
------=_NextPart_84e1fdd0-b285-4922-9fc7-88b070204303
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
This is an aggregate report from backschues.net.
Report domain: stalw.art
Submitter: backschues.net
Report ID: stalw.art.1667948400.1668034800
------=_NextPart_84e1fdd0-b285-4922-9fc7-88b070204303
Content-Type: application/gzip
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
filename="backschues.net!stalw.art!1667948400!1668034800.xml.gz"
H4sIAAAAAAAAA5VUsXLbMAzd/RU6D9ksSo6b2heG6dKOndJZR5OQzYtEsiSVNH9fUqQoqXWHT
gIfgAfgASf8/KvvijcwVij5tK3LaluAZIoLeXna/nj5tjtui2eywS0AP1P2SjZFgQ1oZVzTg6
OcOhowjypzaSTtgdz9HJR7DNGWXQew5fevLxhld4yGnoqOSOW5uo8d76lhOzvoQPxlkSrBYRR
jY16qLTixjnbvJTWurB8ePp8Ox0NVBfNY3R+OVYXRHBpTfa/QGCovqQcPneEiJJnzMYrI5AfJ
yZIyvCMZWrPlaktRsFadYB+NHs6dsFfIjSg/kJwH8GQRiW7KX0VPDEbRSKDV7YiFb4S0l08CR
jq97QTYCdHMkTr0HYyxy7878ooyZXhcrHqfuNRgGDRCk09Vud/fl/X+VNangyfPnhjJ1CB9FY
yikQrHMvBGu8HrxLOgXFitrHD+3FKzSyRHhblbv3TvzhKME7YJnlVAt2r5dcRRsOAgnWiFP/G
UcAXKwTStUf1yBUt4ZPgjE9PBXRsDdujcRLVqLu1QgGtLf+zrpY6XG1KJptaGaxkf0y0Fnpts
/7iRmS7KcYNukxX7zwbjWtaMicaf30qEEBaPB6P8h/gNNHLX4VQEAAA=
------=_NextPart_84e1fdd0-b285-4922-9fc7-88b070204303--

View file

@ -0,0 +1,52 @@
Received: from mail.stalw.art ([mail.stalw.art])
by 127.0.0.1 (Stalwart JMAP) with LMTP;
Tue, 08 Nov 2022 23:26:41 +0000
Received: from relay7.m.smailru.net (relay7.m.smailru.net [94.100.178.51])
(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))
(No client certificate requested)
by mail.stalw.art (Postfix) with ESMTPS id DD4337CC09
for <domains@stalw.art>; Tue, 8 Nov 2022 23:26:38 +0000 (UTC)
DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=corp.mail.ru; s=mail4;
h=Date:Message-ID:To:From:Subject:MIME-Version:Content-Type:From:Subject:Content-Type:Content-Transfer-Encoding:To:Cc; bh=fooa0+RBCZvyV2mP8Nx/UsLQ5RhazFg+SPGNtxZrCX0=;
t=1667950001;x=1668040001;
b=J9aMEkY9eVdOxjkNxaPFJ2Yk+/NCux9uOZl3iJXI0hEFaeYj9g7l+WtmXczk+YvgH3yhVhtvONUEYFsValRWWCAfmePm429N3mSuclVktk7t6RPJ4O5EcMjwrD9882vmX1xpI7ecPOzd5AD67HPt5SIA1RIa5injaOI5CWUXBBa5c0zDfmciyANAiDw0gm1axEMK4AUc61txPsX7H1qRq/FxGNITnnpYdqkkT2lR8sTl5HPwTjEsw4sYGKr5SiMpROhhbLZTM8RpojkP73bmw3UBZ9FI8iKApJUFB8i9tu0hjzHkev4uoDXgOXFYs/RAI1JkCWEp2Rjb3LpTSHT6cA==;
Received: from [10.161.4.115] (port=60844 helo=60)
by relay7.m.smailru.net with esmtp (envelope-from <dmarc_support@corp.mail.ru>)
id 1osXzK-0007VC-BD
for domains@stalw.art; Wed, 09 Nov 2022 02:26:38 +0300
Content-Type: multipart/mixed; boundary="===============5640625649776607409=="
MIME-Version: 1.0
Subject: Report Domain: stalw.art; Submitter: Mail.Ru;
Report-ID: 28551467700969547611667865600
From: dmarc_support@corp.mail.ru
To: domains@stalw.art
Message-ID: <dmarc-1667949998@corp.mail.ru>
Date: Wed, 09 Nov 2022 02:26:38 +0300
Auto-Submitted: auto-generated
Authentication-Results: relay7.m.smailru.net; auth=pass smtp.auth=dmarc_support@corp.mail.ru smtp.mailfrom=dmarc_support@corp.mail.ru; iprev=pass policy.iprev=10.161.4.115
--===============5640625649776607409==
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: base64
VGhpcyBpcyBhbiBhZ2dyZWdhdGUgcmVwb3J0IGZyb20gTWFpbC5SdS4=
--===============5640625649776607409==
Content-Type: application/gzip
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
filename="mail.ru!stalw.art!1667865600!1667952000.xml.gz"
H4sICK7lamMC/21haWwucnUhc3RhbHcuYXJ0ITE2Njc4NjU2MDAhMTY2Nzk1MjAwMC54bWwAdVNB
cqMwELzvK3LzKQhYg01qouwHctkPULIYjMogqSThJL/fEQSC18kFzbRmWt0jAS/vQ/9wReeV0c+7
LEl3D6ilaZQ+P+/G0D4edy/8F7SIzUnICweH1rhQDxhEI4LgYNy51mJA/ipUn/wdga0I4EAYbwbh
ZO1HGzv/SONsEvHEUe1cAfgenKil0UHIUCvdGt6FYJ8Y67Bfy1lcHyNCjfcdizbV8PxYFNm+PBzS
tCqrYn8os6wsD8eyKNMU2FchkAmsndBnknvCs9J8WzgjgLqZ4KrI0wjHHNi2ld3NxZpeyY/ajqde
+Q7jUYb0a+6D6N8S4QIxzAiI5qIG7oDNAQhv2ymNK1iujUZgloNfYgrAysCzKCcG9L070CENO67m
jVrN6CTWyvIiTfL8d5LlVZJVe+Jad0CaURMpsDlYTOBV9CO5jSaUt8arQO/lU8oWgUl/S9dE+GQl
OpjzyQu7Z2STPNWgDqpV9BQ5dCgadHXrzLAd1xYGdtMhxtDVDv3YB/+pYpm3wtAm9Ca/xu2xRxmM
m7bI7JrDzMCt8D5e6ZQsTm5Iv7nEleWKvboo76zQef4N+zyO/9in6fysWBqLfIjOiXBKftA6T/l2
HGx5CGz9j/8BQWPZIPkDAAA=
--===============5640625649776607409==--

View file

@ -0,0 +1,126 @@
Received: from mail.stalw.art ([mail.stalw.art]) by 127.0.0.1 (Stalwart JMAP) with LMTP; Tue, 25 Oct 2022 04:08:22 +0000
Received: from NAM12-MW2-obe.outbound.protection.outlook.com (mail-mw2nam12on2073.outbound.protection.outlook.com [40.107.244.73])
(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))
(No client certificate requested)
by mail.stalw.art (Postfix) with ESMTPS id A24107CC0A
for <domains@stalw.art>; Tue, 25 Oct 2022 04:08:22 +0000 (UTC)
ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none;
b=SvolQ1oIEgdfCI6dbwmJ1jS0ovWmprW6kT3q9NgrbX+CMhIsdrqyS3Q1sO16KT2wCQAyNofiEZ5tKY0e1PzzMqeR29jUWvEye9T43fCfUeLFx9b45YrfkGYwqLeDIq0Ywl+ggVmsm7X83XqI6+9EC6qMukCb0cbLazu3rW/Rbyc6d5+fq6QTFZovATGRvHz71H9t7e//hYI23XjU5Q3Enw0Qq3xPSyusWDi3t7CfGXn9i2120XlNLnPxef5PCmwy4E+OTJ5qC5WtMthOskKKuFvx8onOYmc/JjJ3VrtZwALx9C+ulzix5US6H7pFvZ2jtDbMnW4U7ir/hp5xn5adFw==
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com;
s=arcselector9901;
h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1;
bh=65M39Uvc5w8zbmK6TxEdoblSMXlIyqTXJHNalJ80wk4=;
b=PN7QPeXJLr6tmH2CxydbDQjHqBtFKNN9HjGimeUHaIeSr82WHf4R295QbVX7gxw6sFE7Z9lZMTrMSqbRVI7rhbx+SEkxCfAothf9207FDX6t37Zt0wd/5EwR6dzfbcNJBL+U0/iG4J03L5b1geWY+e68mHKYH4/ybGcr+SBKuv/LgfZNtOfbQ3ioiKvFcpSDqd/qGUs4U9l2tVlXgbcKkct04sCuPciqgLEuIGirPLLbDUaBRJc51ZZB6CeporySRdHp6uFXyy3VBvvLVuwDNnnPrW4BUL05AuutzK7rc8ZQEpWf7r0gUEg2ArSrvs6Znnfe97oRa01L2SeFwuZsMA==
ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=none; dmarc=none
action=none header.from=microsoft.com; dkim=none (message not signed);
arc=none
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=notification.microsoft.com; s=selector1;
h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck;
bh=65M39Uvc5w8zbmK6TxEdoblSMXlIyqTXJHNalJ80wk4=;
b=NjqsA7D6sEq1WgCZ/E1f5/B+XUXe5F4uv6CF2KQYVuyRnxItdox09LCWqZQ+fNQ6BbJ4Ne05Cb1BPbPP9yvb8Y6B1s2QvuxkUb69UFbAhoFgsRT6A4K76ykKQQyiPoYpxlO6FEyy+gel4y7c9XRLiWW6OxMIBcjBGB5ziP7mGFaJx4qXJ2mROfO7uZfrCu5pzOimkjPw6extWv4i0Kl3XKvBtXZnsr9eoC10mJvEAp7E2cpnaZnP46RQc9cmXzlmvhKPvCQCUWipJN9f1BTTvFjJ9ff6ehmN9RSzCckj3SZGw9XAnd0WYqh4evt6Y1RxQ4iQDSaZHNRpyMOtmkWc/w==
Authentication-Results: dkim=none (message not signed)
header.d=none;dmarc=none action=none header.from=microsoft.com;
Received: from BN9PR03CA0046.namprd03.prod.outlook.com (2603:10b6:408:fb::21)
by SJ0PR18MB3916.namprd18.prod.outlook.com (2603:10b6:a03:2c9::21) with
Microsoft SMTP Server (version=TLS1_2,
cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.5746.21; Tue, 25 Oct
2022 04:08:19 +0000
Received: from BN7NAM10FT048.eop-nam10.prod.protection.outlook.com
(2603:10b6:408:fb:cafe::d7) by BN9PR03CA0046.outlook.office365.com
(2603:10b6:408:fb::21) with Microsoft SMTP Server (version=TLS1_2,
cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.5746.27 via Frontend
Transport; Tue, 25 Oct 2022 04:08:19 +0000
Received: from nam10.map.protection.outlook.com (2a01:111:f400:fe53::30) by
BN7NAM10FT048.mail.protection.outlook.com (2a01:111:e400:7e8f::199) with
Microsoft SMTP Server (version=TLS1_2,
cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.5746.16 via Frontend
Transport; Tue, 25 Oct 2022 04:08:19 +0000
Message-ID: <725cbfbe133940149987cfc528387235@microsoft.com>
X-Sender: <dmarcreport@microsoft.com> XATTRDIRECT=Originating XATTRORGID=xorgid:96f9e21d-a1c4-44a3-99e4-37191ac61848
MIME-Version: 1.0
From: "DMARC Aggregate Report" <dmarcreport@microsoft.com>
To: <domains@stalw.art>
Subject: =?utf-8?B?UmVwb3J0IERvbWFpbjogc3RhbHcuYXJ0IFN1Ym1pdHRlcjogcHJvdGVjdGlvbi5vdXRsb29rLmNvbSBSZXBvcnQtSUQ6IDcyNWNiZmJlMTMzOTQwMTQ5OTg3Y2ZjNTI4Mzg3MjM1?=
Content-Type: multipart/mixed;
boundary="_mpm_a4bcd9a515b44b9d8eceb05d7333675fpiotk5m200exchangecorpm_"
Date: Tue, 25 Oct 2022 04:08:19 +0000
X-EOPAttributedMessage: 0
X-MS-PublicTrafficType: Email
X-MS-TrafficTypeDiagnostic: BN7NAM10FT048:EE_|SJ0PR18MB3916:EE_
X-MS-Office365-Filtering-Correlation-Id: 7f843e40-ccc6-4c17-1ce7-08dab63e8cd1
X-MS-Exchange-SenderADCheck: 2
X-MS-Exchange-AntiSpam-Relay: 0
X-Microsoft-Antispam: BCL:0;
X-Microsoft-Antispam-Message-Info:
PSiI3L4DKj/cyRBl8/bmbyQMrr1DvsYEB1+aTn/3Y39oHnyJ5HXcxu6jNUl32WcPW6Gfqmhc6P1RFE5L/9ev0cWnqh4GgIs2qmHicLexPmMjP8viPdjb1N7TSSOv1hhXMT+gVLx889X5sltd4qpfIAWhoxNonjQpVIgt4VOVnbCWTu1hyOjOVplq0rKqIF04BQGHZnBRfkcD1No+mZrvx8RLWIwInU3fpPeGz77Wn3TIvHtzypR/d22WpZ8eHk3aIxxdjwp5WLg4unpiJaieyQN7BRhD/v6b3pLFVJP8Ii2+FGjTsKASczEL4dHnIoIrHYE0wwaFFPcSNzovLhzYguDV42EGS8Fm7soiew4ch+hICM0LPNTGTZIDe7wm2eSwhN2tkJK4QCfh1DON39jXninVr88ZlzMcDXnXpgvWHHiur8az7Gvs9zHH/1tFMsPVSh7BS+8fHEcBYpdtihrP22GcjbOd98IiTAs/dVzSy0TUg6WEgJO6oUklGjqVbi99CrNZI1BtLP4vH4aSlz9JYg4et6SxiJlKyoSzqUr2NN9/pyFdQ//5d/EEjKJz8CAcQmCjjPEObGFttT3maY2+zsa2THodZgpfMyDbA3WUKxE=
X-Forefront-Antispam-Report:
CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:nam10.map.protection.outlook.com;PTR:;CAT:NONE;SFS:(13230022)(396003)(39860400002)(346002)(34036004)(366004)(376002)(136003)(47540400005)(451199015)(2616005)(52230400001)(121820200001)(83380400001)(166002)(86362001)(41300700001)(2906002)(4001150100001)(8936002)(316002)(5660300002)(235185007)(41320700001)(508600001)(6486002)(6512007)(6506007)(24736004)(108616005)(68406010)(85236043)(8676002)(10290500003)(6916009)(36736006)(36756003)(66899015);DIR:OUT;SFP:1101;
X-OriginatorOrg: dmarcrep.onmicrosoft.com
X-MS-Exchange-CrossTenant-OriginalArrivalTime: 25 Oct 2022 04:08:19.1682
(UTC)
X-MS-Exchange-CrossTenant-Network-Message-Id: 7f843e40-ccc6-4c17-1ce7-08dab63e8cd1
X-MS-Exchange-CrossTenant-AuthSource: BN7NAM10FT048.eop-nam10.prod.protection.outlook.com
X-MS-Exchange-CrossTenant-AuthAs: Internal
X-MS-Exchange-CrossTenant-Id: 96f9e21d-a1c4-44a3-99e4-37191ac61848
X-MS-Exchange-CrossTenant-FromEntityHeader: Internet
X-MS-Exchange-Transport-CrossTenantHeadersStamped: SJ0PR18MB3916
This is a multi-part message in MIME format.
--_mpm_a4bcd9a515b44b9d8eceb05d7333675fpiotk5m200exchangecorpm_
Content-Type: multipart/related;
boundary="_rv_a4bcd9a515b44b9d8eceb05d7333675fpiotk5m200exchangecorpm_"
--_rv_a4bcd9a515b44b9d8eceb05d7333675fpiotk5m200exchangecorpm_
Content-Type: multipart/alternative;
boundary="_av_a4bcd9a515b44b9d8eceb05d7333675fpiotk5m200exchangecorpm_"
--_av_a4bcd9a515b44b9d8eceb05d7333675fpiotk5m200exchangecorpm_
--_av_a4bcd9a515b44b9d8eceb05d7333675fpiotk5m200exchangecorpm_
Content-Type: text/html; charset=us-ascii
Content-Transfer-Encoding: base64
PGRpdiBzdHlsZSA9ImZvbnQtZmFtaWx5OlNlZ29lIFVJOyBmb250LXNpemU6MTRweDsiPlRoaXMgaX
MgYSBETUFSQyBhZ2dyZWdhdGUgcmVwb3J0IGZyb20gTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBGb3Ig
RW1haWxzIHJlY2VpdmVkIGJldHdlZW4gMjAyMi0xMC0yMyAwMDowMDowMCBVVEMgdG8gMjAyMi0xMC
0yNCAwMDowMDowMCBVVEMuPC8gZGl2PjxiciAvPjxiciAvPllvdSdyZSByZWNlaXZpbmcgdGhpcyBl
bWFpbCBiZWNhdXNlIHlvdSBoYXZlIGluY2x1ZGVkIHlvdXIgZW1haWwgYWRkcmVzcyBpbiB0aGUgJ3
J1YScgdGFnIG9mIHlvdXIgRE1BUkMgcmVjb3JkIGluIEROUyBmb3Igc3RhbHcuYXJ0LiBQbGVhc2Ug
cmVtb3ZlIHlvdXIgZW1haWwgYWRkcmVzcyBmcm9tIHRoZSAncnVhJyB0YWcgaWYgeW91IGRvbid0IH
dhbnQgdG8gcmVjZWl2ZSB0aGlzIGVtYWlsLjxiciAvPjxiciAvPjxkaXYgc3R5bGUgPSJmb250LWZh
bWlseTpTZWdvZSBVSTsgZm9udC1zaXplOjEycHg7IGNvbG9yOiM2NjY2NjY7Ij5QbGVhc2UgZG8gbm
90IHJlc3BvbmQgdG8gdGhpcyBlLW1haWwuIFRoaXMgbWFpbGJveCBpcyBub3QgbW9uaXRvcmVkIGFu
ZCB5b3Ugd2lsbCBub3QgcmVjZWl2ZSBhIHJlc3BvbnNlLiBGb3IgYW55IGZlZWRiYWNrL3N1Z2dlc3
Rpb25zLCBraW5kbHkgbWFpbCB0byBkbWFyY3JlcG9ydGZlZWRiYWNrQG1pY3Jvc29mdC5jb20uPGJy
IC8+PGJyIC8+TWljcm9zb2Z0IHJlc3BlY3RzIHlvdXIgcHJpdmFjeS4gUmV2aWV3IG91ciBPbmxpbm
UgU2VydmljZXMgPGEgaHJlZiA9Imh0dHBzOi8vcHJpdmFjeS5taWNyb3NvZnQuY29tL2VuLXVzL3By
aXZhY3lzdGF0ZW1lbnQiPlByaXZhY3kgU3RhdGVtZW50PC9hPi48YnIgLz5PbmUgTWljcm9zb2Z0IF
dheSwgUmVkbW9uZCwgV0EsIFVTQSA5ODA1Mi48LyBkaXYgPg==
--_av_a4bcd9a515b44b9d8eceb05d7333675fpiotk5m200exchangecorpm_--
--_rv_a4bcd9a515b44b9d8eceb05d7333675fpiotk5m200exchangecorpm_--
--_mpm_a4bcd9a515b44b9d8eceb05d7333675fpiotk5m200exchangecorpm_
Content-Type: application/gzip
Content-Transfer-Encoding: base64
Content-ID: <3ff45643-7977-4f3c-a97d-14b9e7faa5e7>
Content-Description: protection.outlook.com!stalw.art!1666483200!1666569600.xml.gz
Content-Disposition: attachment; filename="protection.outlook.com!stalw.art!1666483200!1666569600.xml.gz";
H4sIAAAAAAAEAM1VzY7bIBi8V+o7RLnXxHZ+VyzbB2jVQy+9WQTjBMUGBDjZvn0/G4JJsu3usZcE5h
vzDcNg45fXrp2dubFCyed5ni3mL+TzJ9xwXu8pO82gLO3Tq62f50fn9BNCl8slu5SZMgdULBY5+vX9
20925B2dR7J4n/xFSOuoZHwO7WYzHCQQUIDRdTJWDNfKuKrjjtbU0REEGJasJO04+dG7VqlTxlSHUU
QDCzqJltQdNcyv87UTzCirGucf8ITADq1ETTbFiu2bPc/Lcrdc5MvdbrthDVsV23K7KcoVRhM3PAzi
eGWoPFybA7bnBwF7Wq/Xy20JBmDkkUjgsh7Lq/VuPZSHeVgP3S0YW944gbVqBftd6X7fCnvkkxwFO5
METG4vGTUO1vNIqNP6JDpiMPKDK2p1M4LDf8A0kUpyjPQVsFfERkgzR/JhA8MgYI0iAMCvV/+mULCc
KRNFG3WZvLGqN4xXQpPVIiuKMsuLXZbvltA3ViKZqV6CBIz8IOKhKz/Ttgc/61gZLBJWKyvcEDW/oR
RJiYNDDQQFGJNZwYsmVCbHkt3e94VDjFvEoubSiUZA2tNEnHmrNK+cIiqNdlp4ZDdGdURw1wx3LSGP
eKQfOa258WCSjBS+6nwUh2nvjpXhtm9dIvjekRCzSctN7rxpvOXMKTOS4MziPOH4PkRTa4fkj5PJ3p
um/7GEv12/Ww1wVuIkrNFUFsU/tfiovaMlTeIH3WCQFdINAYD24+TDPiRvCvSQkIEfLji8CsJHhfwB
wJC79XYGAAA=
--_mpm_a4bcd9a515b44b9d8eceb05d7333675fpiotk5m200exchangecorpm_--

View file

@ -0,0 +1,54 @@
Received: from mail.stalw.art ([mail.stalw.art]) by 127.0.0.1 (Stalwart JMAP) with LMTP; Tue, 20 Sep 2022 10:28:19 +0000
Received: from a14-92.smtp-out.amazonses.com (a14-92.smtp-out.amazonses.com [54.240.14.92])
(using TLSv1.2 with cipher ECDHE-RSA-AES128-SHA256 (128/128 bits))
(No client certificate requested)
by mail.stalw.art (Postfix) with ESMTPS id 1337D7E19D
for <domains@stalw.art>; Tue, 20 Sep 2022 10:28:18 +0000 (UTC)
DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/simple;
s=a66wkfbz3zwxdt2n5p6d7lj2ja7sdwuc; d=amazonses.com; t=1663669697;
h=From:To:Message-ID:Subject:MIME-Version:Content-Type:Date;
bh=h9v7dueDYfUxVokuKSLTqLuOwisdgdDRQ6TLwJOzXes=;
b=dHR5EJhoY9s8g2/Y4K4rHdz44k67r7fyC4wr2AWZmemrVBoxYHJPwa295S2VJQtY
kxTxppN2GEcNxhUMw8TXBrRwNKdoOLU38ZtrAN1a4hWVxmlwky1dtjXETQ/qJ257Nzg
bsXkAo4S1RABFmkQQJ0zSPZGkMW+lpZTBCDzlOHU=
DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/simple;
s=6gbrjpgwjskckoa6a5zn6fwqkn67xbtw; d=amazonses.com; t=1663669697;
h=From:To:Message-ID:Subject:MIME-Version:Content-Type:Date:Feedback-ID;
bh=h9v7dueDYfUxVokuKSLTqLuOwisdgdDRQ6TLwJOzXes=;
b=UDIvc6rvbihyGbzGRsmSSSzVNFgpfb3V3j0UivcNjlX2y63vjLinol463Z/+3Xh3
BmxAOiLHF/DbVnqqNg5ygdxsa7MBHXEJ5we3W8vQr37xNk5DqhV7HPBSFttWP5sy0dg
rdjyfMIjqJ1J/2+aM4opFA/6EWif7TGmjo7N1KKM=
From: postmaster@amazonses.com
To: domains@stalw.art
Message-ID: <010001835a70fc8d-a3d7eff5-7adb-41cc-87bd-a646d9776a69-000000@email.amazonses.com>
Subject: Dmarc Aggregate Report Domain: {stalw.art} Submitter: {Amazon SES}
Date: {2022-09-19} Report-ID: {6b06c366-0631-4ca0-8337-f5aecf137918}
MIME-Version: 1.0
Content-Type: multipart/mixed;
boundary="----=_Part_42492_694130218.1663669697673"
Date: Tue, 20 Sep 2022 10:28:17 +0000
Feedback-ID: 1.us-east-1.CTa/CO4t1eWkL0VlHBu5/eINCZhxZraAIsQC/FZHIgk=:AmazonSES
X-SES-Outgoing: 2022.09.20-54.240.14.92
------=_Part_42492_694130218.1663669697673
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
This MIME email was sent through Amazon SES.
------=_Part_42492_694130218.1663669697673
Content-Type: application/octet-stream;
name=amazonses.com!stalw.art!1663545600!1663632000.xml.gz
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
filename=amazonses.com!stalw.art!1663545600!1663632000.xml.gz
H4sIAAAAAAAAAG1TwXLbIBA9O1/RyV1CWLHszlDSHHJMe8itFw1GK5uJBAwgp+3XlwXJVjK9SOzb
1b59vBV7/D0OXy7gvDL62z0tq/tHfsd6gO4o5Bu/27A5yauSMrIEEXdgjQvtCEF0IogIbZhxp1aL
EfjTy9Ovnz+K1+dXRq4gVsAo1MCt8WEUPoD7Lkbx12gPvpRmZCTnsXLurzreHKtG1k1TVE1Niwcp
quJQ1/ui3wmQPa33X+mBkVs9fh1HgtYJfUq0G3aEk9KcNk29e9g1VcVIRlISdJdSTb2tMIUxNiEf
ulwpVpKZNYOSf1o7HQflzzCTm6hCcx/E8F4KF2KjjGBSdG9q5I6RfEiQt31C8I2A5dpoYMSmyC+h
z7GVgVOcEw8I9IbHKD5xyP9MFO9SGpdnc+Y9i/ZmchJaZfm22pd0T0t6OJRb7HtLpUppJh0ZGcmH
hM0scBHDFC8p9UblykdvVcAdyTOvkbkGZffR5picbyCJ7GdwvoSblA8k0YWsgKkOdFC9iiu52HiB
wVhoe2dGnher1AP6uU6k2jOIDlwGVj6t4UT2iYSJKZxbB34awsy6jHu1fUV8sz0tNH7FrfAeVykF
WediO/nUHcuycdHd5Zf8BxMenbqzAwAA
------=_Part_42492_694130218.1663669697673--

View file

@ -0,0 +1,41 @@
From: tlsrpt@mail.sender.example.com
Date: Fri, May 09 2017 16:54:30 -0800
To: mts-sts-tlsrpt@example.net
Subject: Report Domain: example.net
Submitter: mail.sender.example.com
Report-ID: <735ff.e317+bf22029@example.net>
TLS-Report-Domain: example.net
TLS-Report-Submitter: mail.sender.example.com
MIME-Version: 1.0
Content-Type: multipart/report; report-type="tlsrpt";
boundary="----=_NextPart_000_024E_01CC9B0A.AFE54C00"
Content-Language: en-us
This is a multipart message in MIME format.
------=_NextPart_000_024E_01CC9B0A.AFE54C00
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
This is an aggregate TLS report from mail.sender.example.com
------=_NextPart_000_024E_01CC9B0A.AFE54C00
Content-Type: application/tlsrpt+gzip
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
filename="mail.sender.example!example.com!1013662812!1013749130.json.gz"
H4sICCpFtWMAA3JwdDAxLmpzb24uMQCtVVtr2zAYfe+vEN7bmFzZjt3EMLbRhu1hdCUJI+soRpGU
VMy2jCSHZCX/fZIvzVLHpc0WDDHSOfqOznfxwxkwP0fIFc75b6y5yGGOM+bEwLkUWYHzLZw772oU
xZpBifOV3X6o1qp1pbHU0O5qXlN95EUQDSDyZgjF1XPbnFIxWE778H4QhyPz3DoVfNfEJiLXmGjI
86WwDKUVlKwQUvN89ZE0Ujcu2+CsSFkruYZATi0nRFE48C8I9AMawMEFwXARMQRHg4hhxIZksHgk
FiLlhDNleD8fde/vvMdsD7x4sgf1tmCN3L/u/xSltDS3OAh1AFszqUxmYjCdTdfekYMqVCYoi4Fm
ylrSC9rE4K2bYZ66rWnbJ6Z1OXiT4JU5exgNEHI6oLv+m1FhQuXWgZdEM+rgvVC634le6V1RByu7
w2COKrMMy57caaFxClVJCFNqWZpX8287g4gyt+LCwI+OqK95SyOwlKxDClDwrKSWR5k2b+qoB12x
FVUyVab6sdgIM12x5MS2K9sUXDLal1plOtFUC8w0hryoWxF5MV0MY7wg1PSt58dxb8lJRhhfVwfU
mWtnR7bxXllk9vqMdlzzEOrgd90jXmZMNah0qmAutMlvYWfDP3kTnOaN/0pv9ke1OgIXuZ4XuGH0
Sj/NFXoImFJu578pYTtkZVZ9DWy4e60LFZ+f18NUuZ1p2+wklgc+AE7Be9AMW0AABH4AaPAGTBv7
r4WetuaDbueenN41Tjmtv2FNM708td5o6Iaea8rNjfwT8jA8oQzgApNfZfF/GiV4Bm7HimRYVXBa
RZ+HaJR8T8aTSXIz+Tb/kdx8mn1Jvo6vP5u/8fxyPL4aXx3JzcHKfsbW63dnuz+8byfQUQgAAA==
------=_NextPart_000_024E_01CC9B0A.AFE54C00--

View file

@ -0,0 +1,64 @@
From: tlsrpt@mail.sender.example.com
Date: Fri, May 09 2017 16:54:30 -0800
To: mts-sts-tlsrpt@example.net
Subject: Report Domain: example.net
Submitter: mail.sender.example.com
Report-ID: <735ff.e317+bf22029@example.net>
TLS-Report-Domain: example.net
TLS-Report-Submitter: mail.sender.example.com
MIME-Version: 1.0
Content-Type: multipart/report; report-type="tlsrpt";
boundary="----=_NextPart_000_024E_01CC9B0A.AFE54C00"
Content-Language: en-us
This is a multipart message in MIME format.
------=_NextPart_000_024E_01CC9B0A.AFE54C00
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
This is an aggregate TLS report from mail.sender.example.com
------=_NextPart_000_024E_01CC9B0A.AFE54C00
Content-Type: application/tlsrpt
Content-Disposition: attachment;
filename="mail.sender.example!example.com!1013662812!1013749130.json"
{
"report-id": "2020-01-01T00:00:00Z_example.com",
"date-range": {
"start-datetime": "2020-01-01T00:00:00Z",
"end-datetime": "2020-01-07T23:59:59Z"
},
"organization-name": "Google Inc.",
"contact-info": "smtp-tls-reporting@google.com",
"policies": [
{
"policy": {
"policy-type": "sts",
"policy-string": [
"version: STSv1",
"mode: enforce",
"mx: demo.example.com",
"max_age: 604800"
],
"policy-domain": "example.com"
},
"summary": {
"total-successful-session-count": 23,
"total-failure-session-count": 1
},
"failure-details": [
{
"result-type": "certificate-host-mismatch",
"sending-mta-ip": "123.123.123.123",
"receiving-ip": "234.234.234.234",
"receiving-mx-hostname": "demo.example.com",
"failed-session-count": 1
}
]
}
]
}
------=_NextPart_000_024E_01CC9B0A.AFE54C00--

View file

@ -80,14 +80,14 @@ impl Config {
&String::from_utf8(self.file_contents((
"signature",
id,
"public-key",
"private-key",
))?)
.unwrap_or_default(),
)
.map_err(|err| {
format!(
"Failed to build RSA key for {}: {}",
("signature", id, "public-key",).as_key(),
("signature", id, "private-key",).as_key(),
err
)
})?;
@ -95,14 +95,14 @@ impl Config {
&String::from_utf8(self.file_contents((
"signature",
id,
"public-key",
"private-key",
))?)
.unwrap_or_default(),
)
.map_err(|err| {
format!(
"Failed to build RSA key for {}: {}",
("signature", id, "public-key",).as_key(),
("signature", id, "private-key",).as_key(),
err
)
})?;

View file

@ -259,6 +259,7 @@ pub struct Auth {
pub script: IfBlock<Option<Arc<Script>>>,
pub lookup: IfBlock<Option<Arc<List>>>,
pub mechanisms: IfBlock<u64>,
pub require: IfBlock<bool>,
pub errors_max: IfBlock<usize>,
pub errors_wait: IfBlock<Duration>,
}
@ -397,7 +398,6 @@ pub struct Dsn {
pub struct AggregateReport {
pub name: IfBlock<String>,
pub address: IfBlock<String>,
pub subject: IfBlock<String>,
pub org_name: IfBlock<Option<String>>,
pub contact_info: IfBlock<Option<String>>,
pub send: IfBlock<AggregateFrequency>,

View file

@ -1,4 +1,4 @@
use std::{fs, time::Duration};
use std::time::Duration;
use mail_send::Credentials;
@ -103,10 +103,10 @@ impl Config {
.parse_if_block("queue.outbound.tls.dane", ctx, &mx_envelope_keys)?
.unwrap_or_else(|| IfBlock::new(RequireOptional::Optional)),
mta_sts: self
.parse_if_block("queue.outbound.tls.mta_sts", ctx, &rcpt_envelope_keys)?
.parse_if_block("queue.outbound.tls.mta-sts", ctx, &rcpt_envelope_keys)?
.unwrap_or_else(|| IfBlock::new(RequireOptional::Optional)),
start: self
.parse_if_block("queue.outbound.tls.tls", ctx, &mx_envelope_keys)?
.parse_if_block("queue.outbound.tls.starttls", ctx, &mx_envelope_keys)?
.unwrap_or_else(|| IfBlock::new(RequireOptional::Optional)),
},
throttle: self.parse_queue_throttle(ctx)?,
@ -430,11 +430,10 @@ impl ParseValue for PathBuf {
fn parse_value(_key: impl utils::AsKey, value: &str) -> super::Result<Self> {
let path = PathBuf::from(value);
if !path.exists() {
fs::create_dir(&path)
.map_err(|err| format!("Failed to create spool directory {:?}: {}", path, err))?;
if path.exists() {
Ok(path)
} else {
Err(format!("Directory {} does not exist.", path.display()))
}
Ok(path)
}
}

View file

@ -38,7 +38,7 @@ impl Config {
.unwrap_or(Duration::from_secs(86400)),
cache_ttl_negative: self
.property(("remote", id, "cache.ttl.positive"))?
.unwrap_or(Duration::from_secs(86400)),
.unwrap_or(Duration::from_secs(3600)),
timeout: self
.property(("remote", id, "timeout"))?
.unwrap_or(Duration::from_secs(60)),

View file

@ -111,15 +111,6 @@ impl Config {
&rcpt_envelope_keys,
)?
.unwrap_or_else(|| IfBlock::new(format!("noreply-{}@{}", id, default_hostname))),
subject: self
.parse_if_block(
("report", id, "aggregate.subject"),
ctx,
&rcpt_envelope_keys,
)?
.unwrap_or_else(|| {
IfBlock::new(format!("{} Aggregage Report", id.to_ascii_uppercase()))
}),
org_name: self
.parse_if_block(
("report", id, "aggregate.org-name"),

View file

@ -35,10 +35,12 @@ impl Config {
if let Some(preserve) = self.property("resolver.preserve-intermediates")? {
opts.preserve_intermediates = preserve;
}
if let Some(try_tcp_on_error) = self.property("resolver.try-tcp-on-error")? {
opts.try_tcp_on_error = try_tcp_on_error;
}
if let Some(attempts) = self.property("resolver.attempts")? {
opts.attempts = attempts;
}
// Prepare DNSSEC resolver options
let config_dnssec = config.clone();

View file

@ -9,6 +9,36 @@ use super::{
impl Config {
pub fn parse_session_config(&self, ctx: &ConfigContext) -> super::Result<SessionConfig> {
let available_keys = [
EnvelopeKey::Listener,
EnvelopeKey::RemoteIp,
EnvelopeKey::LocalIp,
];
Ok(SessionConfig {
duration: self
.parse_if_block("session.duration", ctx, &available_keys)?
.unwrap_or_else(|| IfBlock::new(Duration::from_secs(15 * 60))),
transfer_limit: self
.parse_if_block("session.transfer-limit", ctx, &available_keys)?
.unwrap_or_else(|| IfBlock::new(250 * 1024 * 1024)),
timeout: self
.parse_if_block::<Option<Duration>>("session.timeout", ctx, &available_keys)?
.unwrap_or_else(|| IfBlock::new(Some(Duration::from_secs(5 * 60))))
.try_unwrap("session.timeout")
.unwrap_or_else(|_| IfBlock::new(Duration::from_secs(5 * 60))),
throttle: self.parse_session_throttle(ctx)?,
connect: self.parse_session_connect(ctx)?,
ehlo: self.parse_session_ehlo(ctx)?,
auth: self.parse_session_auth(ctx)?,
mail: self.parse_session_mail(ctx)?,
rcpt: self.parse_session_rcpt(ctx)?,
data: self.parse_session_data(ctx)?,
extensions: self.parse_extensions(ctx)?,
})
}
fn parse_session_throttle(&self, ctx: &ConfigContext) -> super::Result<SessionThrottle> {
// Parse throttle
let mut throttle = SessionThrottle {
connect: Vec::new(),
@ -78,33 +108,7 @@ impl Config {
}
}
let available_keys = [
EnvelopeKey::Listener,
EnvelopeKey::RemoteIp,
EnvelopeKey::LocalIp,
];
Ok(SessionConfig {
duration: self
.parse_if_block("session.duration", ctx, &available_keys)?
.unwrap_or_else(|| IfBlock::new(Duration::from_secs(15 * 60))),
transfer_limit: self
.parse_if_block("session.transfer-limit", ctx, &available_keys)?
.unwrap_or_else(|| IfBlock::new(500 * 1024 * 1024)),
timeout: self
.parse_if_block::<Option<Duration>>("session.timeout", ctx, &available_keys)?
.unwrap_or_else(|| IfBlock::new(Some(Duration::from_secs(5 * 60))))
.try_unwrap("session.timeout")
.unwrap_or_else(|_| IfBlock::new(Duration::from_secs(5 * 60))),
throttle,
connect: self.parse_session_connect(ctx)?,
ehlo: self.parse_session_ehlo(ctx)?,
auth: self.parse_session_auth(ctx)?,
mail: self.parse_session_mail(ctx)?,
rcpt: self.parse_session_rcpt(ctx)?,
data: self.parse_session_data(ctx)?,
extensions: self.parse_extensions(ctx)?,
})
Ok(throttle)
}
fn parse_session_connect(&self, ctx: &ConfigContext) -> super::Result<Connect> {
@ -212,6 +216,9 @@ impl Config {
.into_iter()
.fold(0, |acc, m| acc | m.mechanism),
},
require: self
.parse_if_block("session.auth.require", ctx, &available_keys)?
.unwrap_or_else(|| IfBlock::new(false)),
errors_max: self
.parse_if_block("session.auth.errors.max", ctx, &available_keys)?
.unwrap_or_else(|| IfBlock::new(3)),

View file

@ -161,6 +161,7 @@ pub struct SessionParameters {
// Auth parameters
pub auth_script: Option<Arc<Script>>,
pub auth_lookup: Option<Arc<List>>,
pub auth_require: bool,
pub auth_errors_max: usize,
pub auth_errors_wait: Duration,

View file

@ -22,6 +22,7 @@ impl<T: AsyncRead + AsyncWrite> Session<T> {
let ac = &self.core.session.config.auth;
self.params.auth_script = ac.script.eval(self).await.clone();
self.params.auth_lookup = ac.lookup.eval(self).await.clone();
self.params.auth_require = *ac.require.eval(self).await;
self.params.auth_errors_max = *ac.errors_max.eval(self).await;
self.params.auth_errors_wait = *ac.errors_wait.eval(self).await;

View file

@ -76,6 +76,10 @@ impl RateLimiter {
)
}
pub fn elapsed(&self) -> Duration {
self.limiter.0.elapsed()
}
pub fn reset(&mut self) {
self.limiter = (Instant::now(), self.max_requests);
}

View file

@ -1,3 +1,5 @@
use std::sync::{atomic::Ordering, Arc};
use tokio::sync::oneshot;
use super::Core;
@ -26,4 +28,33 @@ impl Core {
}
}
}
fn cleanup(&self) {
for throttle in [&self.session.throttle, &self.queue.throttle] {
throttle.retain(|_, v| {
v.concurrency
.as_ref()
.map_or(false, |c| c.concurrent.load(Ordering::Relaxed) > 0)
|| v.rate
.as_ref()
.map_or(false, |r| r.elapsed().as_secs_f64() < r.max_interval)
});
}
self.queue.quota.retain(|_, v| {
v.messages.load(Ordering::Relaxed) > 0 || v.size.load(Ordering::Relaxed) > 0
});
}
}
pub trait SpawnCleanup {
fn spawn_cleanup(&self);
}
impl SpawnCleanup for Arc<Core> {
fn spawn_cleanup(&self) {
let core = self.clone();
self.worker_pool.spawn(move || {
core.cleanup();
});
}
}

View file

@ -20,6 +20,10 @@ impl<T: AsyncWrite + AsyncRead + Unpin> Session<T> {
return self
.write(b"503 5.5.1 Multiple MAIL commands not allowed.\r\n")
.await;
} else if self.params.auth_require && self.data.authenticated_as.is_empty() {
return self
.write(b"503 5.5.1 You must authenticate first.\r\n")
.await;
} else if self.data.iprev.is_none() && self.params.iprev.verify() {
let iprev = self
.core

View file

@ -33,10 +33,6 @@ impl Server {
for listener_config in self.listeners {
// Bind socket
let local_ip = listener_config.addr.ip();
listener_config
.socket
.bind(listener_config.addr)
.map_err(|err| format!("Failed to bind to {}: {}", listener_config.addr, err))?;
let listener = listener_config
.socket
.listen(listener_config.backlog.unwrap_or(1024))

View file

@ -68,40 +68,43 @@ async fn main() -> std::io::Result<()> {
),
throttle: DashMap::with_capacity_and_hasher_and_shard_amount(
config
.property("global.throttle-map.capacity")
.failed("Failed to parse throttle map capacity")
.property("global.shared-map.capacity")
.failed("Failed to parse shared map capacity")
.unwrap_or(2),
ThrottleKeyHasherBuilder::default(),
config
.property("global.throttle-map.shard")
.failed("Failed to parse throttle map shard amount")
.unwrap_or(32),
.property::<u64>("global.shared-map.shard")
.failed("Failed to parse shared map shard amount")
.unwrap_or(32)
.next_power_of_two() as usize,
),
},
queue: QueueCore {
config: queue_config,
throttle: DashMap::with_capacity_and_hasher_and_shard_amount(
config
.property("global.throttle-map.capacity")
.failed("Failed to parse throttle map capacity")
.property("global.shared-map.capacity")
.failed("Failed to parse shared map capacity")
.unwrap_or(2),
ThrottleKeyHasherBuilder::default(),
config
.property("global.throttle-map.shard")
.failed("Failed to parse throttle map shard amount")
.unwrap_or(32),
.property::<u64>("global.shared-map.shard")
.failed("Failed to parse shared map shard amount")
.unwrap_or(32)
.next_power_of_two() as usize,
),
id_seq: 0.into(),
quota: DashMap::with_capacity_and_hasher_and_shard_amount(
config
.property("global.throttle-map.capacity")
.failed("Failed to parse throttle map capacity")
.property("global.shared-map.capacity")
.failed("Failed to parse shared map capacity")
.unwrap_or(2),
ThrottleKeyHasherBuilder::default(),
config
.property("global.throttle-map.shard")
.failed("Failed to parse throttle map shard amount")
.unwrap_or(32),
.property::<u64>("global.shared-map.shard")
.failed("Failed to parse shared map shard amount")
.unwrap_or(32)
.next_power_of_two() as usize,
),
tx: queue_tx,
connectors: TlsConnectors {
@ -116,6 +119,25 @@ async fn main() -> std::io::Result<()> {
mail_auth: mail_auth_config,
});
// Bind ports before dropping privileges
for server in &config_context.servers {
for listener in &server.listeners {
listener
.socket
.bind(listener.addr)
.failed(&format!("Failed to bind to {}", listener.addr));
}
}
// Drop privileges
if let Some(run_as_user) = config.value("server.run-as.user") {
let mut pd = privdrop::PrivDrop::default().user(run_as_user);
if let Some(run_as_group) = config.value("server.run-as.group") {
pd = pd.group(run_as_group);
}
pd.apply().failed("Failed to drop privileges");
}
// Enable logging
tracing::subscriber::set_global_default(
tracing_subscriber::FmtSubscriber::builder()
@ -139,6 +161,13 @@ async fn main() -> std::io::Result<()> {
// Spawn report manager
report_rx.spawn(core.clone(), core.report.read_reports().await);
// Spawn remote hosts
for host in config_context.hosts.into_values() {
if host.ref_count != 0 {
host.spawn(&config);
}
}
// Spawn listeners
let (shutdown_tx, shutdown_rx) = watch::channel(false);
for server in config_context.servers {

View file

@ -56,21 +56,20 @@ pub trait RemoteLookup: Clone {
}
impl Host {
pub fn spawn(self, config: &Config) -> mpsc::Sender<Event> {
pub fn spawn(self, config: &Config) -> LookupChannel {
// Create channel
let (tx, rx) = mpsc::channel(1024);
let local_host = config
.value("server.hostname")
.unwrap_or("[127.0.0.1]")
.to_string();
let tx_ = tx.clone();
let tx_ = self.channel_tx.clone();
tokio::spawn(async move {
// Prepare builders
match self.protocol {
ServerProtocol::Smtp | ServerProtocol::Lmtp => {
RemoteHost {
tx,
tx: self.channel_tx,
host: Arc::new(SmtpClientBuilder {
builder: mail_send::SmtpClientBuilder {
addr: format!("{}:{}", self.address, self.port),
@ -87,7 +86,7 @@ impl Host {
}),
}
.run(
rx,
self.channel_rx,
self.cache_entries,
self.cache_ttl_positive,
self.cache_ttl_negative,
@ -97,7 +96,7 @@ impl Host {
}
ServerProtocol::Imap => {
RemoteHost {
tx,
tx: self.channel_tx,
host: Arc::new(
ImapAuthClientBuilder::new(
format!("{}:{}", self.address, self.port),
@ -111,7 +110,7 @@ impl Host {
),
}
.run(
rx,
self.channel_rx,
self.cache_entries,
self.cache_ttl_positive,
self.cache_ttl_negative,
@ -122,7 +121,7 @@ impl Host {
}
});
tx_
LookupChannel { tx: tx_ }
}
}

View file

@ -45,6 +45,7 @@ impl AnalyzeReport for Arc<Core> {
let message = if let Some(message) = Message::parse(&message) {
message
} else {
tracing::debug!(context = "report", "Failed to parse message.");
return;
};
let from = match message.from() {
@ -114,7 +115,9 @@ impl AnalyzeReport for Arc<Core> {
("xml", _) => Format::Dmarc,
("tlsrpt", _) | (_, "json") => Format::Tls,
_ => {
if attachment_name.map_or(false, |n| n.contains(".xml")) {
if attachment_name
.map_or(false, |n| n.contains(".xml") || n.contains('!'))
{
Format::Dmarc
} else {
continue;

View file

@ -29,10 +29,10 @@ use super::{
};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
struct DmarcFormat {
rua: Vec<URI>,
policy: PolicyPublished,
records: Vec<Record>,
pub struct DmarcFormat {
pub rua: Vec<URI>,
pub policy: PolicyPublished,
pub records: Vec<Record>,
}
impl<T: AsyncWrite + AsyncRead + Unpin> Session<T> {
@ -412,7 +412,7 @@ impl Scheduler {
Entry::Occupied(e) => (None, e.into_mut().dmarc_path()),
Entry::Vacant(e) => {
let domain = e.key().domain_name().to_string();
let created = event.interval.from_timestamp();
let created = event.interval.to_timestamp();
let deliver_at = created + event.interval.as_secs();
self.main.push(Schedule {
@ -452,62 +452,7 @@ impl Scheduler {
}
} else if path.size < *max_size {
// Append to existing report
path.size += json_append(&path.path, &event.report_record).await;
path.size += json_append(&path.path, &event.report_record, *max_size - path.size).await;
}
}
}
#[cfg(test)]
mod tests {
use mail_auth::{
dmarc::URI,
report::{Alignment, Disposition, PolicyPublished, Record},
};
use crate::reporting::dmarc::DmarcFormat;
#[test]
fn strip_json() {
let mut d = DmarcFormat {
rua: vec![
URI {
uri: "hello".to_string(),
max_size: 0,
},
URI {
uri: "world".to_string(),
max_size: 0,
},
],
policy: PolicyPublished {
domain: "example.org".to_string(),
version_published: None,
adkim: Alignment::Relaxed,
aspf: Alignment::Strict,
p: Disposition::Quarantine,
sp: Disposition::Reject,
testing: false,
fo: None,
},
records: vec![Record::default()
.with_count(1)
.with_envelope_from("domain.net")
.with_envelope_to("other.org")],
};
let mut s = serde_json::to_string(&d).unwrap();
s.truncate(s.len() - 2);
let r = Record::default()
.with_count(2)
.with_envelope_from("otherdomain.net")
.with_envelope_to("otherother.org");
let rs = serde_json::to_string(&r).unwrap();
d.records.push(r);
assert_eq!(
serde_json::from_str::<DmarcFormat>(&format!("{},{}]}}", s, rs)).unwrap(),
d
);
}
}

View file

@ -167,12 +167,15 @@ impl Message {
}
impl AggregateFrequency {
pub fn from_timestamp(&self) -> u64 {
let mut dt = DateTime::from_timestamp(
pub fn to_timestamp(&self) -> u64 {
self.to_timestamp_(DateTime::from_timestamp(
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map_or(0, |d| d.as_secs()) as i64,
);
))
}
pub fn to_timestamp_(&self, mut dt: DateTime) -> u64 {
(match self {
AggregateFrequency::Hourly => {
dt.minute = 0;
@ -190,7 +193,7 @@ impl AggregateFrequency {
dt.hour = 0;
dt.minute = 0;
dt.second = 0;
dt.to_timestamp() - (86400 * 7 * dow as i64)
dt.to_timestamp() - (86400 * dow as i64)
}
AggregateFrequency::Never => dt.to_timestamp(),
}) as u64
@ -251,3 +254,53 @@ impl From<(&Option<Arc<Policy>>, &Option<Arc<Tlsa>>)> for PolicyType {
}
}
}
#[cfg(test)]
mod tests {
use mail_parser::DateTime;
use crate::config::AggregateFrequency;
#[test]
fn aggregate_to_timestamp() {
for (freq, date, expected) in [
(
AggregateFrequency::Hourly,
"2023-01-24T09:10:40Z",
"2023-01-24T09:00:00Z",
),
(
AggregateFrequency::Daily,
"2023-01-24T09:10:40Z",
"2023-01-24T00:00:00Z",
),
(
AggregateFrequency::Weekly,
"2023-01-24T09:10:40Z",
"2023-01-22T00:00:00Z",
),
(
AggregateFrequency::Weekly,
"2023-01-28T23:59:59Z",
"2023-01-22T00:00:00Z",
),
(
AggregateFrequency::Weekly,
"2023-01-22T23:59:59Z",
"2023-01-22T00:00:00Z",
),
] {
assert_eq!(
DateTime::from_timestamp(
freq.to_timestamp_(DateTime::parse_rfc3339(date).unwrap()) as i64
)
.to_rfc3339(),
expected,
"failed for {:?} {} {}",
freq,
date,
expected
);
}
}
}

View file

@ -23,7 +23,7 @@ use tokio::{
use crate::{
config::AggregateFrequency,
core::{Core, ReportCore},
core::{worker::SpawnCleanup, Core, ReportCore},
queue::{InstantFromTimestamp, Schedule},
};
@ -39,12 +39,13 @@ pub struct Scheduler {
pub reports: AHashMap<ReportKey, ReportValue>,
}
#[derive(Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ReportType<T, U> {
Dmarc(T),
Tls(U),
}
#[derive(Debug, PartialEq, Eq)]
pub struct ReportPath<T> {
pub path: T,
pub size: usize,
@ -52,7 +53,7 @@ pub struct ReportPath<T> {
pub deliver_at: AggregateFrequency,
}
#[derive(Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ReportPolicy<T> {
pub inner: T,
pub policy: u64,
@ -61,6 +62,8 @@ pub struct ReportPolicy<T> {
impl SpawnReport for mpsc::Receiver<Event> {
fn spawn(mut self, core: Arc<Core>, mut scheduler: Scheduler) {
tokio::spawn(async move {
let mut last_cleanup = Instant::now();
loop {
match tokio::time::timeout(scheduler.wake_up_time(), self.recv()).await {
Ok(Some(event)) => match event {
@ -85,6 +88,12 @@ impl SpawnReport for mpsc::Receiver<Event> {
_ => unreachable!(),
}
}
// Cleanup expired throttles
if last_cleanup.elapsed().as_secs() >= 86400 {
last_cleanup = Instant::now();
core.spawn_cleanup();
}
}
}
}
@ -101,8 +110,8 @@ impl Core {
interval: AggregateFrequency,
) -> PathBuf {
let (ext, domain) = match domain {
ReportType::Dmarc(domain) => ("t", domain),
ReportType::Tls(domain) => ("d", domain),
ReportType::Dmarc(domain) => ("d", domain),
ReportType::Tls(domain) => ("t", domain),
};
// Build base path
@ -153,7 +162,7 @@ impl ReportCore {
Ok(Some(file)) => {
let file = file.path();
if file.is_dir() {
match tokio::fs::read_dir(path).await {
match tokio::fs::read_dir(&file).await {
Ok(mut dir) => {
let file_ = file;
loop {
@ -399,24 +408,26 @@ pub async fn json_write(path: &PathBuf, entry: &impl Serialize) -> usize {
}
}
pub async fn json_append(path: &PathBuf, entry: &impl Serialize) -> usize {
pub async fn json_append(path: &PathBuf, entry: &impl Serialize, bytes_left: usize) -> usize {
let mut bytes = Vec::with_capacity(128);
bytes.push(b',');
if serde_json::to_writer(&mut bytes, entry).is_ok() {
let err = match OpenOptions::new().append(true).open(&path).await {
Ok(mut file) => match file.write_all(&bytes).await {
Ok(_) => return bytes.len() + 1,
if bytes.len() <= bytes_left {
let err = match OpenOptions::new().append(true).open(&path).await {
Ok(mut file) => match file.write_all(&bytes).await {
Ok(_) => return bytes.len(),
Err(err) => err,
},
Err(err) => err,
},
Err(err) => err,
};
tracing::error!(
context = "report",
event = "error",
"Failed to append report to {}: {}",
path.display(),
err
);
};
tracing::error!(
context = "report",
event = "error",
"Failed to append report to {}: {}",
path.display(),
err
);
}
}
0
}

View file

@ -37,7 +37,7 @@ pub struct TlsRptOptions {
pub interval: AggregateFrequency,
}
#[derive(Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize)]
struct TlsFormat {
rua: Vec<ReportUri>,
policy: PolicyDetails,
@ -131,6 +131,7 @@ impl GenerateTlsReport for Arc<Core> {
event = "empty-report",
"No policies found in report"
);
path.cleanup_blocking();
return;
}
@ -161,6 +162,15 @@ impl GenerateTlsReport for Arc<Core> {
.timeout(Duration::from_secs(2 * 60))
.build()
{
#[cfg(test)]
if uri == "https://127.0.0.1/tls" {
crate::tests::reporting::tls::TLS_HTTP_REPORT
.lock()
.extend_from_slice(&json);
path.cleanup_blocking();
return;
}
match client
.post(uri)
.header(CONTENT_TYPE, "application/tlsrpt+gzip")
@ -169,6 +179,15 @@ impl GenerateTlsReport for Arc<Core> {
{
Ok(response) => {
if response.status().is_success() {
tracing::info!(
parent: &span,
context = "http",
event = "success",
url = uri,
);
path.cleanup_blocking();
return;
} else {
tracing::debug!(
parent: &span,
context = "http",
@ -176,7 +195,6 @@ impl GenerateTlsReport for Arc<Core> {
url = uri,
status = %response.status()
);
return;
}
}
Err(err) => {
@ -271,7 +289,7 @@ impl Scheduler {
}
}
Entry::Vacant(e) => {
let created = event.interval.from_timestamp();
let created = event.interval.to_timestamp();
let deliver_at = created + event.interval.as_secs();
self.main.push(Schedule {
@ -366,7 +384,7 @@ impl Scheduler {
let bytes_written = json_write(&path.path[pos].inner, &entry).await;
if bytes_written > 0 {
path.size = bytes_written;
path.size += bytes_written;
} else {
// Something went wrong, remove record
if let Entry::Occupied(mut e) = self
@ -383,7 +401,8 @@ impl Scheduler {
}
} else if path.size < *max_size {
// Append to existing report
path.size += json_append(&path.path[pos].inner, &event.failure).await;
path.size +=
json_append(&path.path[pos].inner, &event.failure, *max_size - path.size).await;
}
}
}

View file

@ -23,6 +23,9 @@ async fn auth() {
let mut config = &mut core.session.config.auth;
config.require = r"[{if = 'remote-ip', eq = '10.0.0.1', then = true},
{else = false}]"
.parse_if(&ctx);
config.lookup = r"[{if = 'remote-ip', eq = '10.0.0.1', then = 'plain'},
{else = false}]"
.parse_if::<Option<String>>(&ctx)
@ -77,12 +80,17 @@ async fn auth() {
.unwrap_err();
session.response().assert_code("421 4.3.0");
// Should not be able to send without authenticating
session.state = State::default();
session.mail_from("bill@foobar.org", "503 5.5.1").await;
// Successful PLAIN authentication
session.data.auth_errors = 0;
session.state = State::default();
session
.cmd("AUTH PLAIN AGpvaG4Ac2VjcmV0", "235 2.7.0")
.await;
session.mail_from("bill@foobar.org", "250").await;
session.data.mail_from.take();
// Should not be able to authenticate twice
session

View file

@ -99,7 +99,7 @@ async fn data() {
.await;
assert_eq!(
qr.read_event().await.unwrap_message().read_message(),
load_test_message("no_msgid")
load_test_message("no_msgid", "messages")
);
// Maximum one message per session is allowed for 10.0.0.1

View file

@ -17,7 +17,7 @@ use crate::{
const SIGNATURES: &str = "
[signature.rsa]
public-key = '''
private-key = '''
-----BEGIN RSA PRIVATE KEY-----
MIICXwIBAAKBgQDwIRP/UC3SBsEmGqZ9ZJW3/DkMoGeLnQg1fWn7/zYtIxN2SnFC
jxOCKG9v3b4jYfcTNh5ijSsq631uBItLa7od+v/RtdC2UzJ1lWT947qR+Rcac2gb

View file

@ -30,6 +30,7 @@ pub mod inbound;
pub mod outbound;
pub mod queue;
pub mod remote;
pub mod reporting;
pub mod session;
pub trait ParseTestConfig {
@ -173,6 +174,7 @@ impl SessionConfig {
script: IfBlock::new(None),
lookup: IfBlock::new(None),
mechanisms: IfBlock::new(AUTH_PLAIN | AUTH_LOGIN),
require: IfBlock::new(false),
errors_max: IfBlock::new(10),
errors_wait: IfBlock::new(Duration::from_secs(1)),
},
@ -353,7 +355,6 @@ impl AggregateReport {
Self {
name: IfBlock::default(),
address: IfBlock::default(),
subject: IfBlock::default(),
org_name: IfBlock::default(),
contact_info: IfBlock::default(),
send: IfBlock::default(),

View file

@ -12,7 +12,7 @@ use tokio_rustls::TlsAcceptor;
use crate::{
config::{Config, ConfigContext},
core::throttle::{ConcurrencyLimiter, InFlight},
remote::lookup::{Item, LookupChannel, LookupResult},
remote::lookup::{Item, LookupResult},
};
use super::dummy_tls_acceptor;
@ -54,9 +54,7 @@ async fn remote_imap() {
let mut ctx = ConfigContext::default();
let config = Config::parse(REMOTE).unwrap();
config.parse_remote_hosts(&mut ctx).unwrap();
let lookup = LookupChannel {
tx: ctx.hosts.remove("imap").unwrap().spawn(&config),
};
let lookup = ctx.hosts.remove("imap").unwrap().spawn(&config);
// Basic lookup
let tests = vec![

View file

@ -12,7 +12,7 @@ use tokio_rustls::TlsAcceptor;
use crate::{
config::{Config, ConfigContext},
core::throttle::{ConcurrencyLimiter, InFlight},
remote::lookup::{Item, LookupChannel, LookupResult},
remote::lookup::{Item, LookupResult},
};
use super::dummy_tls_acceptor;
@ -46,9 +46,7 @@ async fn remote_smtp() {
let mut ctx = ConfigContext::default();
let config = Config::parse(REMOTE).unwrap();
config.parse_remote_hosts(&mut ctx).unwrap();
let lookup = LookupChannel {
tx: ctx.hosts.remove("lmtp").unwrap().spawn(&config),
};
let lookup = ctx.hosts.remove("lmtp").unwrap().spawn(&config);
// Basic lookup
let tests = vec![

View file

@ -0,0 +1,74 @@
use std::{fs, sync::Arc, time::Duration};
use crate::{
config::{AddressMatch, IfBlock},
core::{Core, Session},
tests::make_temp_dir,
};
#[tokio::test]
async fn report_analyze() {
let mut core = Core::test();
// Create temp dir for queue
let mut qr = core.init_test_queue("smtp_analyze_report_test");
let report_dir = make_temp_dir("smtp_report_incoming", true);
let mut config = &mut core.session.config.rcpt;
config.relay = IfBlock::new(true);
let mut config = &mut core.session.config.data;
config.max_messages = IfBlock::new(1024);
let mut config = &mut core.report.config.analysis;
config.addresses = vec![
AddressMatch::StartsWith("reports@".to_string()),
AddressMatch::EndsWith("@dmarc.foobar.org".to_string()),
AddressMatch::Equals("feedback@foobar.org".to_string()),
];
config.forward = false;
config.store = report_dir.temp_dir.clone().into();
// Create test message
let core = Arc::new(core);
let mut session = Session::test(core.clone());
session.data.remote_ip = "10.0.0.1".parse().unwrap();
session.eval_session_params().await;
session.ehlo("mx.test.org").await;
let addresses = [
"reports@foobar.org",
"rep@dmarc.foobar.org",
"feedback@foobar.org",
];
let mut ac = 0;
let mut total_reports_received = 0;
for (test, num_tests) in [("arf", 5), ("dmarc", 5), ("tls", 2)] {
for num_test in 1..=num_tests {
total_reports_received += 1;
session
.send_message(
"john@test.org",
&[addresses[ac % addresses.len()]],
&format!("report:{}{}", test, num_test),
"250",
)
.await;
qr.assert_empty_queue();
ac += 1;
}
}
tokio::time::sleep(Duration::from_millis(200)).await;
let mut total_reports = 0;
for entry in fs::read_dir(&report_dir.temp_dir).unwrap() {
let path = entry.unwrap().path();
assert_ne!(fs::metadata(&path).unwrap().len(), 0);
total_reports += 1;
}
assert_eq!(total_reports, total_reports_received);
// Test delivery to non-report addresses
session
.send_message("john@test.org", &["bill@foobar.org"], "test:no_dkim", "250")
.await;
qr.read_event().await.unwrap_message();
}

View file

@ -0,0 +1,160 @@
use std::{
net::IpAddr,
sync::Arc,
time::{Duration, Instant},
};
use mail_auth::{
common::parse::TxtRecordParser,
dmarc::Dmarc,
report::{ActionDisposition, Disposition, DmarcResult, Record, Report},
};
use crate::{
config::{AggregateFrequency, ConfigContext, IfBlock},
core::Core,
reporting::{
dmarc::GenerateDmarcReport,
scheduler::{ReportType, Scheduler},
DmarcEvent,
},
tests::{make_temp_dir, session::VerifyResponse, ParseTestConfig},
};
#[tokio::test]
async fn report_dmarc() {
tracing::subscriber::set_global_default(
tracing_subscriber::FmtSubscriber::builder()
.with_max_level(tracing::Level::DEBUG)
.finish(),
)
.unwrap();
// Create scheduler
let mut core = Core::test();
let ctx = ConfigContext::default().parse_signatures();
let temp_dir = make_temp_dir("smtp_report_dmarc_test", true);
let config = &mut core.report.config;
config.path = IfBlock::new(temp_dir.temp_dir.clone());
config.hash = IfBlock::new(16);
config.dmarc_aggregate.sign = "['rsa']"
.parse_if::<Vec<String>>(&ctx)
.map_if_block(&ctx.signers, "", "")
.unwrap();
config.dmarc_aggregate.max_size = IfBlock::new(4096);
config.submitter = IfBlock::new("mx.example.org".to_string());
config.dmarc_aggregate.address = IfBlock::new("reports@example.org".to_string());
config.dmarc_aggregate.org_name = IfBlock::new("Foobar, Inc.".to_string().into());
config.dmarc_aggregate.contact_info =
IfBlock::new("https://foobar.org/contact".to_string().into());
let mut scheduler = Scheduler::default();
// Authorize external report for foobar.org
core.resolvers.dns.txt_add(
"foobar.org._report._dmarc.foobar.net",
Dmarc::parse(b"v=DMARC1;").unwrap(),
Instant::now() + Duration::from_secs(10),
);
// Create temp dir for queue
let mut qr = core.init_test_queue("smtp_report_dmarc_test");
let core = Arc::new(core);
// Schedule two events with a same policy and another one with a different policy
let dmarc_record = Arc::new(
Dmarc::parse(
b"v=DMARC1; p=quarantine; rua=mailto:reports@foobar.net,mailto:reports@example.net",
)
.unwrap(),
);
assert_eq!(dmarc_record.rua().len(), 2);
for _ in 0..2 {
scheduler
.schedule_dmarc(
Box::new(DmarcEvent {
domain: "foobar.org".to_string(),
report_record: Record::new()
.with_source_ip("192.168.1.2".parse().unwrap())
.with_action_disposition(ActionDisposition::Pass)
.with_dmarc_dkim_result(DmarcResult::Pass)
.with_dmarc_spf_result(DmarcResult::Fail)
.with_envelope_from("hello@example.org")
.with_envelope_to("other@example.org")
.with_header_from("bye@example.org"),
dmarc_record: dmarc_record.clone(),
interval: AggregateFrequency::Weekly,
}),
&core,
)
.await;
}
scheduler
.schedule_dmarc(
Box::new(DmarcEvent {
domain: "foobar.org".to_string(),
report_record: Record::new()
.with_source_ip("a:b:c::e:f".parse().unwrap())
.with_action_disposition(ActionDisposition::Reject)
.with_dmarc_dkim_result(DmarcResult::Fail)
.with_dmarc_spf_result(DmarcResult::Pass),
dmarc_record: dmarc_record.clone(),
interval: AggregateFrequency::Weekly,
}),
&core,
)
.await;
assert_eq!(scheduler.reports.len(), 1);
let report_path;
match scheduler.reports.into_iter().next().unwrap() {
(ReportType::Dmarc(domain), ReportType::Dmarc(path)) => {
report_path = path.path.clone();
core.generate_dmarc_report(domain, path);
}
_ => unreachable!(),
}
// Expect report
let message = qr.read_event().await.unwrap_message();
qr.assert_empty_queue();
assert_eq!(message.recipients.len(), 1);
assert_eq!(
message.recipients.last().unwrap().address,
"reports@foobar.net"
);
assert_eq!(message.return_path, "reports@example.org");
message
.read_lines()
.assert_contains("DKIM-Signature: v=1; a=rsa-sha256; s=rsa; d=example.com;")
.assert_contains("To: <reports@foobar.net>")
.assert_contains("Report Domain: foobar.org")
.assert_contains("Submitter: mx.example.org");
// Verify generated report
let report = Report::parse_rfc5322(message.read_message().as_bytes()).unwrap();
assert_eq!(report.domain(), "foobar.org");
assert_eq!(report.email(), "reports@example.org");
assert_eq!(report.org_name(), "Foobar, Inc.");
assert_eq!(
report.extra_contact_info().unwrap(),
"https://foobar.org/contact"
);
assert_eq!(report.p(), Disposition::Quarantine);
assert_eq!(report.records().len(), 2);
for record in report.records() {
let source_ip = record.source_ip().unwrap();
if source_ip == "192.168.1.2".parse::<IpAddr>().unwrap() {
assert_eq!(record.count(), 2);
assert_eq!(record.action_disposition(), ActionDisposition::Pass);
assert_eq!(record.envelope_from(), "hello@example.org");
assert_eq!(record.header_from(), "bye@example.org");
assert_eq!(record.envelope_to().unwrap(), "other@example.org");
} else if source_ip == "a:b:c::e:f".parse::<IpAddr>().unwrap() {
assert_eq!(record.count(), 1);
assert_eq!(record.action_disposition(), ActionDisposition::Reject);
} else {
panic!("unexpected ip {}", source_ip);
}
}
assert!(!report_path.exists());
}

View file

@ -0,0 +1,4 @@
pub mod analyze;
pub mod dmarc;
pub mod scheduler;
pub mod tls;

View file

@ -0,0 +1,251 @@
use std::sync::Arc;
use mail_auth::{
common::parse::TxtRecordParser,
dmarc::{Dmarc, URI},
mta_sts::TlsRpt,
report::{ActionDisposition, Alignment, Disposition, DmarcResult, PolicyPublished, Record},
};
use tokio::fs;
use crate::{
config::{AggregateFrequency, IfBlock},
core::Core,
reporting::{
dmarc::DmarcFormat,
scheduler::{ReportType, Scheduler},
DmarcEvent, PolicyType, TlsEvent,
},
tests::make_temp_dir,
};
#[tokio::test]
async fn report_scheduler() {
tracing::subscriber::set_global_default(
tracing_subscriber::FmtSubscriber::builder()
.with_max_level(tracing::Level::DEBUG)
.finish(),
)
.unwrap();
// Create scheduler
let mut core = Core::test();
let temp_dir = make_temp_dir("smtp_report_scheduler_test", true);
let config = &mut core.report.config;
config.path = IfBlock::new(temp_dir.temp_dir.clone());
config.hash = IfBlock::new(16);
config.dmarc_aggregate.max_size = IfBlock::new(500);
config.tls.max_size = IfBlock::new(550);
let mut scheduler = Scheduler::default();
// Schedule two events with a same policy and another one with a different policy
let dmarc_record =
Arc::new(Dmarc::parse(b"v=DMARC1; p=quarantine; rua=mailto:dmarc@foobar.org").unwrap());
scheduler
.schedule_dmarc(
Box::new(DmarcEvent {
domain: "foobar.org".to_string(),
report_record: Record::new()
.with_source_ip("192.168.1.2".parse().unwrap())
.with_action_disposition(ActionDisposition::Pass)
.with_dmarc_dkim_result(DmarcResult::Pass)
.with_dmarc_spf_result(DmarcResult::Fail)
.with_envelope_from("hello@example.org")
.with_envelope_to("other@example.org")
.with_header_from("bye@example.org"),
dmarc_record: dmarc_record.clone(),
interval: AggregateFrequency::Weekly,
}),
&core,
)
.await;
// No records should be added once the 550 bytes max size is reached
for _ in 0..10 {
scheduler
.schedule_dmarc(
Box::new(DmarcEvent {
domain: "foobar.org".to_string(),
report_record: Record::new()
.with_source_ip("192.168.1.2".parse().unwrap())
.with_action_disposition(ActionDisposition::Pass)
.with_dmarc_dkim_result(DmarcResult::Pass)
.with_dmarc_spf_result(DmarcResult::Fail)
.with_envelope_from("hello@example.org")
.with_envelope_to("other@example.org")
.with_header_from("bye@example.org"),
dmarc_record: dmarc_record.clone(),
interval: AggregateFrequency::Weekly,
}),
&core,
)
.await;
}
let dmarc_record =
Arc::new(Dmarc::parse(b"v=DMARC1; p=reject; rua=mailto:dmarc@foobar.org").unwrap());
scheduler
.schedule_dmarc(
Box::new(DmarcEvent {
domain: "foobar.org".to_string(),
report_record: Record::new()
.with_source_ip("a:b:c::e:f".parse().unwrap())
.with_action_disposition(ActionDisposition::Reject)
.with_dmarc_dkim_result(DmarcResult::Fail)
.with_dmarc_spf_result(DmarcResult::Pass),
dmarc_record: dmarc_record.clone(),
interval: AggregateFrequency::Weekly,
}),
&core,
)
.await;
// Schedule TLS event
let tls_record = Arc::new(TlsRpt::parse(b"v=TLSRPTv1;rua=mailto:reports@foobar.org").unwrap());
scheduler
.schedule_tls(
Box::new(TlsEvent {
domain: "foobar.org".to_string(),
policy: PolicyType::Tlsa(None),
failure: None,
tls_record: tls_record.clone(),
interval: AggregateFrequency::Daily,
}),
&core,
)
.await;
scheduler
.schedule_tls(
Box::new(TlsEvent {
domain: "foobar.org".to_string(),
policy: PolicyType::Tlsa(None),
failure: None,
tls_record: tls_record.clone(),
interval: AggregateFrequency::Daily,
}),
&core,
)
.await;
scheduler
.schedule_tls(
Box::new(TlsEvent {
domain: "foobar.org".to_string(),
policy: PolicyType::Sts(None),
failure: None,
tls_record: tls_record.clone(),
interval: AggregateFrequency::Daily,
}),
&core,
)
.await;
scheduler
.schedule_tls(
Box::new(TlsEvent {
domain: "foobar.org".to_string(),
policy: PolicyType::None,
failure: None,
tls_record: tls_record.clone(),
interval: AggregateFrequency::Daily,
}),
&core,
)
.await;
// Verify sizes and counts
let mut total_tls = 0;
let mut total_tls_policies = 0;
let mut total_dmarc_policies = 0;
for report in scheduler.reports.values() {
match report {
ReportType::Dmarc(r) => {
assert!(r.size <= 550, "{}", r.size);
assert_eq!(fs::metadata(&r.path).await.unwrap().len() as usize, r.size);
assert_eq!(r.deliver_at, AggregateFrequency::Weekly);
total_dmarc_policies += 1;
}
ReportType::Tls(r) => {
total_tls += 1;
total_tls_policies += r.path.len();
assert!(r.size <= 550);
assert_eq!(r.deliver_at, AggregateFrequency::Daily);
let mut sizes = 0;
for p in &r.path {
sizes += fs::metadata(&p.inner).await.unwrap().len() as usize;
}
assert_eq!(r.size, sizes);
}
}
}
assert_eq!(total_tls, 1);
assert_eq!(total_tls_policies, 3);
assert_eq!(total_dmarc_policies, 2);
// Verify deserialized report queue
let mut scheduler_deser = core.report.read_reports().await;
for (key, value) in scheduler.reports {
let a = Some(value);
let b = scheduler_deser.reports.remove(&key);
match (&a, &b) {
(Some(ReportType::Tls(a)), Some(ReportType::Tls(b))) => {
assert_eq!(a.created, b.created);
assert_eq!(a.size, b.size);
assert_eq!(a.deliver_at, b.deliver_at);
assert_eq!(a.path.len(), b.path.len());
for p in &a.path {
assert!(b.path.contains(&p));
}
for p in &b.path {
assert!(a.path.contains(&p));
}
}
_ => {
assert_eq!(a, b, "failed for {:?}", key);
}
}
}
assert_eq!(scheduler.main.len(), scheduler_deser.main.len());
}
#[test]
fn report_strip_json() {
let mut d = DmarcFormat {
rua: vec![
URI {
uri: "hello".to_string(),
max_size: 0,
},
URI {
uri: "world".to_string(),
max_size: 0,
},
],
policy: PolicyPublished {
domain: "example.org".to_string(),
version_published: None,
adkim: Alignment::Relaxed,
aspf: Alignment::Strict,
p: Disposition::Quarantine,
sp: Disposition::Reject,
testing: false,
fo: None,
},
records: vec![Record::default()
.with_count(1)
.with_envelope_from("domain.net")
.with_envelope_to("other.org")],
};
let mut s = serde_json::to_string(&d).unwrap();
s.truncate(s.len() - 2);
let r = Record::default()
.with_count(2)
.with_envelope_from("otherdomain.net")
.with_envelope_to("otherother.org");
let rs = serde_json::to_string(&r).unwrap();
d.records.push(r);
assert_eq!(
serde_json::from_str::<DmarcFormat>(&format!("{},{}]}}", s, rs)).unwrap(),
d
);
}

237
src/tests/reporting/tls.rs Normal file
View file

@ -0,0 +1,237 @@
use std::{io::Read, sync::Arc, time::Duration};
use mail_auth::{
common::parse::TxtRecordParser,
flate2::read::GzDecoder,
mta_sts::TlsRpt,
report::tlsrpt::{FailureDetails, PolicyType, ResultType, TlsReport},
};
use parking_lot::Mutex;
use crate::{
config::{AggregateFrequency, ConfigContext, IfBlock},
core::Core,
reporting::{
scheduler::{ReportType, Scheduler},
tls::GenerateTlsReport,
TlsEvent,
},
tests::{make_temp_dir, session::VerifyResponse, ParseTestConfig},
};
pub static TLS_HTTP_REPORT: Mutex<Vec<u8>> = Mutex::new(Vec::new());
#[tokio::test]
async fn report_tls() {
tracing::subscriber::set_global_default(
tracing_subscriber::FmtSubscriber::builder()
.with_max_level(tracing::Level::DEBUG)
.finish(),
)
.unwrap();
// Create scheduler
let mut core = Core::test();
let ctx = ConfigContext::default().parse_signatures();
let temp_dir = make_temp_dir("smtp_report_tls_test", true);
let config = &mut core.report.config;
config.path = IfBlock::new(temp_dir.temp_dir.clone());
config.hash = IfBlock::new(16);
config.tls.sign = "['rsa']"
.parse_if::<Vec<String>>(&ctx)
.map_if_block(&ctx.signers, "", "")
.unwrap();
config.tls.max_size = IfBlock::new(4096);
config.submitter = IfBlock::new("mx.example.org".to_string());
config.tls.address = IfBlock::new("reports@example.org".to_string());
config.tls.org_name = IfBlock::new("Foobar, Inc.".to_string().into());
config.tls.contact_info = IfBlock::new("https://foobar.org/contact".to_string().into());
let mut scheduler = Scheduler::default();
// Create temp dir for queue
let mut qr = core.init_test_queue("smtp_report_tls_test");
let core = Arc::new(core);
// Schedule TLS reports to be delivered via email
let tls_record = Arc::new(TlsRpt::parse(b"v=TLSRPTv1;rua=mailto:reports@foobar.org").unwrap());
for _ in 0..2 {
// Add two successful records
scheduler
.schedule_tls(
Box::new(TlsEvent {
domain: "foobar.org".to_string(),
policy: crate::reporting::PolicyType::None,
failure: None,
tls_record: tls_record.clone(),
interval: AggregateFrequency::Daily,
}),
&core,
)
.await;
}
for (policy, rt) in [
(
crate::reporting::PolicyType::None,
ResultType::CertificateExpired,
),
(
crate::reporting::PolicyType::Tlsa(None),
ResultType::TlsaInvalid,
),
(
crate::reporting::PolicyType::Sts(None),
ResultType::StsPolicyFetchError,
),
(
crate::reporting::PolicyType::Sts(None),
ResultType::StsPolicyInvalid,
),
] {
scheduler
.schedule_tls(
Box::new(TlsEvent {
domain: "foobar.org".to_string(),
policy,
failure: FailureDetails::new(rt).into(),
tls_record: tls_record.clone(),
interval: AggregateFrequency::Daily,
}),
&core,
)
.await;
}
// Wait for flush
tokio::time::sleep(Duration::from_millis(200)).await;
assert_eq!(scheduler.reports.len(), 1);
let mut report_path = Vec::new();
match scheduler.reports.into_iter().next().unwrap() {
(ReportType::Tls(domain), ReportType::Tls(path)) => {
for p in &path.path {
report_path.push(p.inner.clone());
}
core.generate_tls_report(domain, path);
}
_ => unreachable!(),
}
// Expect report
let message = qr.read_event().await.unwrap_message();
assert_eq!(
message.recipients.last().unwrap().address,
"reports@foobar.org"
);
assert_eq!(message.return_path, "reports@example.org");
message
.read_lines()
.assert_contains("DKIM-Signature: v=1; a=rsa-sha256; s=rsa; d=example.com;")
.assert_contains("To: <reports@foobar.org>")
.assert_contains("Report Domain: foobar.org")
.assert_contains("Submitter: mx.example.org");
// Verify generated report
let report = TlsReport::parse_rfc5322(message.read_message().as_bytes()).unwrap();
assert_eq!(report.organization_name.unwrap(), "Foobar, Inc.");
assert_eq!(report.contact_info.unwrap(), "https://foobar.org/contact");
assert_eq!(report.policies.len(), 3);
let mut seen = [false; 3];
for policy in report.policies {
match policy.policy.policy_type {
PolicyType::Tlsa => {
seen[0] = true;
assert_eq!(policy.summary.total_failure, 1);
assert_eq!(policy.summary.total_success, 0);
assert_eq!(policy.policy.policy_domain, "foobar.org");
assert_eq!(policy.failure_details.len(), 1);
assert_eq!(
policy.failure_details.first().unwrap().result_type,
ResultType::TlsaInvalid
);
}
PolicyType::Sts => {
seen[1] = true;
assert_eq!(policy.summary.total_failure, 2);
assert_eq!(policy.summary.total_success, 0);
assert_eq!(policy.policy.policy_domain, "foobar.org");
assert_eq!(policy.failure_details.len(), 2);
assert!(policy
.failure_details
.iter()
.any(|d| d.result_type == ResultType::StsPolicyFetchError));
assert!(policy
.failure_details
.iter()
.any(|d| d.result_type == ResultType::StsPolicyInvalid));
}
PolicyType::NoPolicyFound => {
seen[2] = true;
assert_eq!(policy.summary.total_failure, 1);
assert_eq!(policy.summary.total_success, 2);
assert_eq!(policy.policy.policy_domain, "foobar.org");
assert_eq!(policy.failure_details.len(), 1);
assert_eq!(
policy.failure_details.first().unwrap().result_type,
ResultType::CertificateExpired
);
}
PolicyType::Other => unreachable!(),
}
}
assert!(seen[0]);
assert!(seen[1]);
assert!(seen[2]);
for path in report_path {
assert!(!path.exists());
}
// Schedule TLS reports to be delivered via https
let mut scheduler = Scheduler::default();
let tls_record = Arc::new(TlsRpt::parse(b"v=TLSRPTv1;rua=https://127.0.0.1/tls").unwrap());
for _ in 0..2 {
// Add two successful records
scheduler
.schedule_tls(
Box::new(TlsEvent {
domain: "foobar.org".to_string(),
policy: crate::reporting::PolicyType::None,
failure: None,
tls_record: tls_record.clone(),
interval: AggregateFrequency::Daily,
}),
&core,
)
.await;
}
let mut report_path = Vec::new();
match scheduler.reports.into_iter().next().unwrap() {
(ReportType::Tls(domain), ReportType::Tls(path)) => {
for p in &path.path {
report_path.push(p.inner.clone());
}
core.generate_tls_report(domain, path);
}
_ => unreachable!(),
}
tokio::time::sleep(Duration::from_millis(200)).await;
// Uncompress report
let gz_report = TLS_HTTP_REPORT.lock();
let mut file = GzDecoder::new(&gz_report[..]);
let mut buf = Vec::new();
file.read_to_end(&mut buf).unwrap();
let report = TlsReport::parse_json(&buf).unwrap();
assert_eq!(report.organization_name.unwrap(), "Foobar, Inc.");
assert_eq!(report.contact_info.unwrap(), "https://foobar.org/contact");
assert_eq!(report.policies.len(), 1);
for path in report_path {
assert!(!path.exists());
}
}

View file

@ -157,7 +157,11 @@ impl Session<DummyIo> {
self.ingest(b"DATA\r\n").await.unwrap();
self.response().assert_code("354");
if let Some(file) = data.strip_prefix("test:") {
self.ingest(load_test_message(file).as_bytes())
self.ingest(load_test_message(file, "messages").as_bytes())
.await
.unwrap();
} else if let Some(file) = data.strip_prefix("report:") {
self.ingest(load_test_message(file, "reports").as_bytes())
.await
.unwrap();
} else {
@ -176,11 +180,11 @@ impl Session<DummyIo> {
}
}
pub fn load_test_message(file: &str) -> String {
pub fn load_test_message(file: &str, test: &str) -> String {
let mut test_file = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
test_file.push("resources");
test_file.push("tests");
test_file.push("messages");
test_file.push(test);
test_file.push(format!("{}.eml", file));
std::fs::read_to_string(test_file).unwrap()
}