Files
Stalwart/crates/main/src/test_data.rs

1408 lines
67 KiB
Rust

/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use common::{
Server,
config::smtp::queue::{QueueExpiry, QueueName},
};
use registry::{
schema::{
enums::{
ArfAuthFailureType, ArfDeliveryResult, ArfFeedbackType, ArfIdentityAlignment,
DkimAuthResult, DmarcAlignment, DmarcDisposition, DmarcResult, SpfAuthResult,
SpfDomainScope, TlsPolicyType, TlsResultType,
},
prelude::ObjectType,
structs::{
ArfExternalReport, ArfFeedbackReport, DmarcDkimResult, DmarcExternalReport,
DmarcInternalReport, DmarcReport, DmarcReportRecord, DmarcSpfResult, TlsExternalReport,
TlsFailureDetails, TlsInternalReport, TlsReport, TlsReportPolicy,
},
},
types::{
EnumImpl, ObjectImpl, datetime::UTCDateTime, float::Float, ipaddr::IpAddr, list::List,
map::Map,
},
};
use smtp::{
queue::{
Error, ErrorDetails, FROM_AUTHENTICATED, FROM_DSN, FROM_REPORT, HostResponse, Message,
MessageWrapper, RCPT_DSN_SENT, RCPT_SPAM_PAYLOAD, Recipient, Schedule, Status,
UnexpectedResponse,
},
reporting::index::{ExternalReportIndex, InternalReportIndex},
};
use smtp_proto::Response;
use std::net::Ipv4Addr;
use store::write::{BatchBuilder, RegistryClass, ValueClass};
use types::blob_hash::BlobHash;
pub async fn insert_test_data(server: &Server) {
let mut hashes = Vec::new();
let future = one_year_from_now_u64();
for message in sample_raw_messages() {
let (hash, _) = server
.put_temporary_blob(u32::MAX, message.as_bytes(), future)
.await
.unwrap();
hashes.push(hash);
}
for message in sample_queued_messages(hashes) {
let qm = MessageWrapper::new(
message,
server.inner.data.queue_id_gen.generate(),
QueueName::default(),
);
assert!(qm.save_changes(server, None).await);
}
for report in sample_tls_internal_reports() {
let object_id = ObjectType::TlsInternalReport.to_id();
let item_id = server.inner.data.queue_id_gen.generate();
let mut batch = BatchBuilder::new();
batch.clear(report.primary_key());
report.write_ops(&mut batch, item_id, true);
let report_bytes = report.to_pickled_vec();
batch.set(
ValueClass::Registry(RegistryClass::Item { object_id, item_id }),
report_bytes,
);
server.store().write(batch.build_all()).await.unwrap();
}
for report in sample_dmarc_internal_reports() {
let object_id = ObjectType::DmarcInternalReport.to_id();
let item_id = server.inner.data.queue_id_gen.generate();
let mut batch = BatchBuilder::new();
batch.clear(report.primary_key());
report.write_ops(&mut batch, item_id, true);
let report_bytes = report.to_pickled_vec();
batch.set(
ValueClass::Registry(RegistryClass::Item { object_id, item_id }),
report_bytes,
);
server.store().write(batch.build_all()).await.unwrap();
}
for report in sample_tls_external_reports() {
let mut batch = BatchBuilder::new();
let item_id = server.inner.data.queue_id_gen.generate();
report.write_ops(&mut batch, item_id, true);
server.store().write(batch.build_all()).await.unwrap();
}
for report in sample_dmarc_external_reports() {
let mut batch = BatchBuilder::new();
let item_id = server.inner.data.queue_id_gen.generate();
report.write_ops(&mut batch, item_id, true);
server.store().write(batch.build_all()).await.unwrap();
}
for report in sample_arf_external_reports() {
let mut batch = BatchBuilder::new();
let item_id = server.inner.data.queue_id_gen.generate();
report.write_ops(&mut batch, item_id, true);
server.store().write(batch.build_all()).await.unwrap();
}
}
fn sample_queued_messages(blob_hashes: Vec<BlobHash>) -> Vec<Message> {
assert!(
blob_hashes.len() >= 3,
"Need at least 3 blob hashes for sample queued messages"
);
let future = one_year_from_now_u64();
let now = store::write::now();
let raw_messages = sample_raw_messages();
vec![
// Message 1: Normal outbound message with two recipients, one scheduled and one completed
Message {
created: now,
blob_hash: blob_hashes[0].clone(),
return_path: "sender@myserver.com".into(),
recipients: vec![
Recipient {
address: "alice@example.com".into(),
retry: Schedule {
due: future,
inner: 0,
},
notify: Schedule {
due: future,
inner: 0,
},
expires: QueueExpiry::Ttl(365 * 24 * 3600),
queue: Default::default(),
status: Status::Scheduled,
flags: 0,
orcpt: None,
},
Recipient {
address: "bob@example.org".into(),
retry: Schedule {
due: future,
inner: 2,
},
notify: Schedule {
due: future,
inner: 1,
},
expires: QueueExpiry::Ttl(365 * 24 * 3600),
queue: Default::default(),
status: Status::Completed(HostResponse {
hostname: "mx.example.org".into(),
response: Response {
code: 250,
esc: [2, 1, 5],
message: "OK".into(),
},
}),
flags: RCPT_DSN_SENT,
orcpt: Some("rfc822;bob@example.org".into()),
},
],
received_from_ip: std::net::IpAddr::V4(Ipv4Addr::new(192, 168, 1, 10)),
received_via_port: 25,
flags: FROM_AUTHENTICATED,
env_id: Some("env-001".into()),
priority: 0,
size: raw_messages[0].len() as u64,
quota_keys: Box::new([]),
},
// Message 2: DSN bounce message with a temporary failure recipient
Message {
created: now,
blob_hash: blob_hashes[1].clone(),
return_path: "".into(),
recipients: vec![Recipient {
address: "postmaster@remote.net".into(),
retry: Schedule {
due: future,
inner: 3,
},
notify: Schedule {
due: future,
inner: 0,
},
expires: QueueExpiry::Ttl(365 * 24 * 3600),
queue: Default::default(),
status: Status::TemporaryFailure(ErrorDetails {
entity: "mx.remote.net".into(),
details: Error::UnexpectedResponse(UnexpectedResponse {
command: "RCPT TO".into(),
response: Response {
code: 450,
esc: [4, 2, 1],
message: "Mailbox temporarily unavailable".into(),
},
}),
}),
flags: 0,
orcpt: None,
}],
received_from_ip: std::net::IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
received_via_port: 587,
flags: FROM_DSN,
env_id: None,
priority: -5,
size: raw_messages[1].len() as u64,
quota_keys: Box::new([]),
},
// Message 3: Report message with a temporary failure recipient
Message {
created: now,
blob_hash: blob_hashes[2].clone(),
return_path: "reports@myserver.com".into(),
recipients: vec![Recipient {
address: "abuse@bigcorp.com".into(),
retry: Schedule {
due: future,
inner: 0,
},
notify: Schedule {
due: future,
inner: 2,
},
expires: QueueExpiry::Ttl(365 * 24 * 3600),
queue: Default::default(),
status: Status::TemporaryFailure(ErrorDetails {
entity: "mx.bigcorp.com".into(),
details: Error::ConnectionError("Rejected by policy".into()),
}),
flags: RCPT_SPAM_PAYLOAD,
orcpt: None,
}],
received_from_ip: std::net::IpAddr::V4(Ipv4Addr::new(172, 16, 0, 5)),
received_via_port: 465,
flags: FROM_REPORT | FROM_AUTHENTICATED,
env_id: Some("env-report-99".into()),
priority: 10,
size: raw_messages[2].len() as u64,
quota_keys: Box::new([]),
},
]
}
fn sample_raw_messages() -> Vec<String> {
vec![
// Raw message 1: Normal outbound email (matches Message 1)
concat!(
"From: sender@myserver.com\r\n",
"To: alice@example.com, bob@example.org\r\n",
"Subject: Quarterly Report Q1 2027\r\n",
"Date: Sat, 12 Apr 2027 09:00:00 +0000\r\n",
"Message-ID: <msg-001@myserver.com>\r\n",
"MIME-Version: 1.0\r\n",
"Content-Type: text/plain; charset=utf-8\r\n",
"\r\n",
"Hi team,\r\n",
"\r\n",
"Please find attached the quarterly report for Q1 2027.\r\n",
"Let me know if you have any questions.\r\n",
"\r\n",
"Best regards,\r\n",
"The Sender\r\n",
)
.to_string(),
// Raw message 2: DSN bounce (matches Message 2)
concat!(
"From: <>\r\n",
"To: postmaster@remote.net\r\n",
"Subject: Delivery Status Notification (Failure)\r\n",
"Date: Sat, 12 Apr 2027 09:05:00 +0000\r\n",
"Message-ID: <dsn-002@myserver.com>\r\n",
"MIME-Version: 1.0\r\n",
"Content-Type: multipart/report; report-type=delivery-status;\r\n",
" boundary=\"boundary-dsn-002\"\r\n",
"\r\n",
"--boundary-dsn-002\r\n",
"Content-Type: text/plain; charset=utf-8\r\n",
"\r\n",
"This is an automatically generated Delivery Status Notification.\r\n",
"Delivery to the following recipient failed temporarily:\r\n",
"\r\n",
" postmaster@remote.net\r\n",
"\r\n",
"The server will retry delivery.\r\n",
"\r\n",
"--boundary-dsn-002\r\n",
"Content-Type: message/delivery-status\r\n",
"\r\n",
"Reporting-MTA: dns; myserver.com\r\n",
"Arrival-Date: Sat, 12 Apr 2027 09:00:00 +0000\r\n",
"\r\n",
"Final-Recipient: rfc822; postmaster@remote.net\r\n",
"Action: delayed\r\n",
"Status: 4.2.1\r\n",
"Remote-MTA: dns; mx.remote.net\r\n",
"Diagnostic-Code: smtp; 450 Mailbox temporarily unavailable\r\n",
"\r\n",
"--boundary-dsn-002--\r\n",
)
.to_string(),
// Raw message 3: Report message (matches Message 3)
concat!(
"From: reports@myserver.com\r\n",
"To: abuse@bigcorp.com\r\n",
"Subject: DMARC Aggregate Report for bigcorp.com\r\n",
"Date: Sat, 12 Apr 2027 09:10:00 +0000\r\n",
"Message-ID: <report-003@myserver.com>\r\n",
"MIME-Version: 1.0\r\n",
"Content-Type: multipart/mixed;\r\n",
" boundary=\"boundary-report-003\"\r\n",
"\r\n",
"--boundary-report-003\r\n",
"Content-Type: text/plain; charset=utf-8\r\n",
"\r\n",
"This is a DMARC aggregate report for the domain bigcorp.com\r\n",
"generated by myserver.com.\r\n",
"\r\n",
"Report period: 2027-04-11T00:00:00Z to 2027-04-12T00:00:00Z\r\n",
"\r\n",
"--boundary-report-003\r\n",
"Content-Type: application/gzip\r\n",
"Content-Disposition: attachment;\r\n",
" filename=\"myserver.com!bigcorp.com!1744329600!1744416000.xml.gz\"\r\n",
"Content-Transfer-Encoding: base64\r\n",
"\r\n",
"H4sIAAAAAAAAA2NgGAWjYBSMglEwCkbBKBgFo2AUDAIAAP//\r\n",
"\r\n",
"--boundary-report-003--\r\n",
)
.to_string(),
]
}
fn sample_tls_internal_reports() -> Vec<TlsInternalReport> {
let future = one_year_from_now();
vec![
// Report 1: Successful TLS sessions, STS policy
TlsInternalReport {
created_at: now(),
deliver_at: future,
domain: "example.com".to_string(),
http_rua: Map::new(vec!["https://example.com/tlsrpt".to_string()]),
mail_rua: Map::new(vec!["mailto:tls-reports@example.com".to_string()]),
policy_identifiers: Map::new(vec![1]),
report: TlsReport {
contact_info: Some("admin@myserver.com".to_string()),
date_range_end: now(),
date_range_start: days_ago(1),
organization_name: Some("My Mail Server".to_string()),
policies: List::from(vec![TlsReportPolicy {
failure_details: List::from(vec![]),
mx_hosts: Map::new(vec!["mx1.example.com".to_string()]),
policy_domain: "example.com".to_string(),
policy_strings: Map::new(vec![
"mode: enforce".to_string(),
"max_age: 86400".to_string(),
]),
policy_type: TlsPolicyType::Sts,
total_failed_sessions: 0,
total_successful_sessions: 150,
}]),
report_id: "tls-int-report-001".to_string(),
},
},
// Report 2: Mixed results with certificate mismatch failures
TlsInternalReport {
created_at: now(),
deliver_at: future,
domain: "secure-mail.org".to_string(),
http_rua: Map::new(vec![]),
mail_rua: Map::new(vec![
"mailto:tlsrpt@secure-mail.org".to_string(),
"mailto:security@secure-mail.org".to_string(),
]),
policy_identifiers: Map::new(vec![2, 3]),
report: TlsReport {
contact_info: Some("postmaster@myserver.com".to_string()),
date_range_end: now(),
date_range_start: days_ago(1),
organization_name: Some("My Mail Server".to_string()),
policies: List::from(vec![TlsReportPolicy {
failure_details: List::from(vec![TlsFailureDetails {
additional_information: Some(
"Certificate CN does not match hostname".to_string(),
),
failed_session_count: 5,
failure_reason_code: Some("certificate-host-mismatch".to_string()),
receiving_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(
203, 0, 113, 10,
)))),
receiving_mx_helo: Some("mx.secure-mail.org".to_string()),
receiving_mx_hostname: Some("mx.secure-mail.org".to_string()),
result_type: TlsResultType::CertificateHostMismatch,
sending_mta_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(
192, 0, 2, 1,
)))),
}]),
mx_hosts: Map::new(vec!["mx.secure-mail.org".to_string()]),
policy_domain: "secure-mail.org".to_string(),
policy_strings: Map::new(vec!["mode: testing".to_string()]),
policy_type: TlsPolicyType::Sts,
total_failed_sessions: 5,
total_successful_sessions: 95,
}]),
report_id: "tls-int-report-002".to_string(),
},
},
// Report 3: DANE/TLSA policy with validation failure
TlsInternalReport {
created_at: now(),
deliver_at: future,
domain: "dane-enabled.net".to_string(),
http_rua: Map::new(vec!["https://dane-enabled.net/tlsrpt".to_string()]),
mail_rua: Map::new(vec![]),
policy_identifiers: Map::new(vec![4]),
report: TlsReport {
contact_info: None,
date_range_end: now(),
date_range_start: days_ago(1),
organization_name: Some("My Mail Server".to_string()),
policies: List::from(vec![TlsReportPolicy {
failure_details: List::from(vec![TlsFailureDetails {
additional_information: None,
failed_session_count: 2,
failure_reason_code: Some("tlsa-invalid".to_string()),
receiving_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(
198, 51, 100, 25,
)))),
receiving_mx_helo: Some("mail.dane-enabled.net".to_string()),
receiving_mx_hostname: Some("mail.dane-enabled.net".to_string()),
result_type: TlsResultType::TlsaInvalid,
sending_mta_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(
192, 0, 2, 1,
)))),
}]),
mx_hosts: Map::new(vec!["mail.dane-enabled.net".to_string()]),
policy_domain: "dane-enabled.net".to_string(),
policy_strings: Map::new(vec![]),
policy_type: TlsPolicyType::Tlsa,
total_failed_sessions: 2,
total_successful_sessions: 48,
}]),
report_id: "tls-int-report-003".to_string(),
},
},
]
}
fn sample_dmarc_internal_reports() -> Vec<DmarcInternalReport> {
let future = one_year_from_now();
vec![
// Report 1: Clean domain with all-pass records
DmarcInternalReport {
created_at: now(),
deliver_at: future,
domain: "trusted-sender.com".to_string(),
policy_identifier: 100,
report: DmarcReport {
date_range_begin: days_ago(1),
date_range_end: now(),
email: "dmarc@myserver.com".to_string(),
errors: Map::new(vec![]),
extensions: List::from(vec![]),
extra_contact_info: Some("https://myserver.com/dmarc".to_string()),
org_name: "My Mail Server".to_string(),
policy_adkim: DmarcAlignment::Relaxed,
policy_aspf: DmarcAlignment::Relaxed,
policy_disposition: DmarcDisposition::None,
policy_domain: "trusted-sender.com".to_string(),
policy_failure_reporting_options: Map::new(vec![]),
policy_subdomain_disposition: DmarcDisposition::Quarantine,
policy_testing_mode: false,
policy_version: Some("DMARC1".to_string()),
records: List::from(vec![DmarcReportRecord {
count: 500,
dkim_results: List::from(vec![DmarcDkimResult {
domain: "trusted-sender.com".to_string(),
human_result: None,
result: DkimAuthResult::Pass,
selector: "selector1".to_string(),
}]),
envelope_from: "trusted-sender.com".to_string(),
envelope_to: Some("myserver.com".to_string()),
evaluated_disposition: Default::default(),
evaluated_dkim: DmarcResult::Pass,
evaluated_spf: DmarcResult::Pass,
extensions: List::from(vec![]),
header_from: "trusted-sender.com".to_string(),
policy_override_reasons: List::from(vec![]),
source_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(
93, 184, 216, 34,
)))),
spf_results: List::from(vec![DmarcSpfResult {
domain: "trusted-sender.com".to_string(),
human_result: None,
result: SpfAuthResult::Pass,
scope: SpfDomainScope::MailFrom,
}]),
}]),
report_id: "dmarc-int-001".to_string(),
version: Float::from(1.0),
},
rua: Map::new(vec!["mailto:dmarc-rua@trusted-sender.com".to_string()]),
},
// Report 2: Domain with DKIM failure and strict alignment
DmarcInternalReport {
created_at: now(),
deliver_at: future,
domain: "strict-domain.org".to_string(),
policy_identifier: 200,
report: DmarcReport {
date_range_begin: days_ago(1),
date_range_end: now(),
email: "dmarc@myserver.com".to_string(),
errors: Map::new(vec!["DKIM signature verification failed".to_string()]),
extensions: List::from(vec![]),
extra_contact_info: None,
org_name: "My Mail Server".to_string(),
policy_adkim: DmarcAlignment::Strict,
policy_aspf: DmarcAlignment::Strict,
policy_disposition: DmarcDisposition::Reject,
policy_domain: "strict-domain.org".to_string(),
policy_failure_reporting_options: Map::new(vec![]),
policy_subdomain_disposition: DmarcDisposition::Reject,
policy_testing_mode: false,
policy_version: Some("DMARC1".to_string()),
records: List::from(vec![DmarcReportRecord {
count: 12,
dkim_results: List::from(vec![DmarcDkimResult {
domain: "strict-domain.org".to_string(),
human_result: Some("signature verification failed".to_string()),
result: DkimAuthResult::Fail,
selector: "dkim2024".to_string(),
}]),
envelope_from: "strict-domain.org".to_string(),
envelope_to: None,
evaluated_disposition: Default::default(),
evaluated_dkim: DmarcResult::Fail,
evaluated_spf: DmarcResult::Pass,
extensions: List::from(vec![]),
header_from: "strict-domain.org".to_string(),
policy_override_reasons: List::from(vec![]),
source_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(
198, 51, 100, 50,
)))),
spf_results: List::from(vec![DmarcSpfResult {
domain: "strict-domain.org".to_string(),
human_result: None,
result: SpfAuthResult::Pass,
scope: SpfDomainScope::MailFrom,
}]),
}]),
report_id: "dmarc-int-002".to_string(),
version: Float::from(1.0),
},
rua: Map::new(vec!["mailto:dmarc@strict-domain.org".to_string()]),
},
// Report 3: Domain in testing mode with multiple record types
DmarcInternalReport {
created_at: now(),
deliver_at: future,
domain: "new-policy.io".to_string(),
policy_identifier: 300,
report: DmarcReport {
date_range_begin: days_ago(1),
date_range_end: now(),
email: "dmarc@myserver.com".to_string(),
errors: Map::new(vec![]),
extensions: List::from(vec![]),
extra_contact_info: None,
org_name: "My Mail Server".to_string(),
policy_adkim: DmarcAlignment::Relaxed,
policy_aspf: DmarcAlignment::Strict,
policy_disposition: DmarcDisposition::Quarantine,
policy_domain: "new-policy.io".to_string(),
policy_failure_reporting_options: Map::new(vec![]),
policy_subdomain_disposition: DmarcDisposition::None,
policy_testing_mode: true,
policy_version: Some("DMARC1".to_string()),
records: List::from(vec![
DmarcReportRecord {
count: 200,
dkim_results: List::from(vec![DmarcDkimResult {
domain: "new-policy.io".to_string(),
human_result: None,
result: DkimAuthResult::Pass,
selector: "sel1".to_string(),
}]),
envelope_from: "new-policy.io".to_string(),
envelope_to: Some("myserver.com".to_string()),
evaluated_disposition: Default::default(),
evaluated_dkim: DmarcResult::Pass,
evaluated_spf: DmarcResult::Pass,
extensions: List::from(vec![]),
header_from: "new-policy.io".to_string(),
policy_override_reasons: List::from(vec![]),
source_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(
203, 0, 113, 5,
)))),
spf_results: List::from(vec![DmarcSpfResult {
domain: "new-policy.io".to_string(),
human_result: None,
result: SpfAuthResult::Pass,
scope: SpfDomainScope::MailFrom,
}]),
},
DmarcReportRecord {
count: 3,
dkim_results: List::from(vec![DmarcDkimResult {
domain: "new-policy.io".to_string(),
human_result: Some("no signature found".to_string()),
result: DkimAuthResult::None,
selector: "".to_string(),
}]),
envelope_from: "spoofed.example".to_string(),
envelope_to: Some("myserver.com".to_string()),
evaluated_disposition: Default::default(),
evaluated_dkim: DmarcResult::Fail,
evaluated_spf: DmarcResult::Fail,
extensions: List::from(vec![]),
header_from: "new-policy.io".to_string(),
policy_override_reasons: List::from(vec![]),
source_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(192, 0, 2, 99)))),
spf_results: List::from(vec![DmarcSpfResult {
domain: "spoofed.example".to_string(),
human_result: Some("SPF record not found".to_string()),
result: SpfAuthResult::None,
scope: SpfDomainScope::MailFrom,
}]),
},
]),
report_id: "dmarc-int-003".to_string(),
version: Float::from(1.0),
},
rua: Map::new(vec![
"mailto:dmarc@new-policy.io".to_string(),
"mailto:dmarc-backup@new-policy.io".to_string(),
]),
},
]
}
fn sample_tls_external_reports() -> Vec<TlsExternalReport> {
let future = one_year_from_now();
vec![
// Report 1: Clean report from a large provider
TlsExternalReport {
expires_at: future,
from: "tls-reports@bigprovider.com".to_string(),
member_tenant_id: None,
received_at: now(),
report: TlsReport {
contact_info: Some("postmaster@bigprovider.com".to_string()),
date_range_end: now(),
date_range_start: days_ago(1),
organization_name: Some("Big Provider Inc.".to_string()),
policies: List::from(vec![
TlsReportPolicy {
failure_details: List::from(vec![]),
mx_hosts: Map::new(vec![
"mx1.myserver.com".to_string(),
"mx2.myserver.com".to_string(),
]),
policy_domain: "myserver.com".to_string(),
policy_strings: Map::new(vec![
"mode: enforce".to_string(),
"max_age: 604800".to_string(),
]),
policy_type: TlsPolicyType::Sts,
total_failed_sessions: 0,
total_successful_sessions: 12500,
},
TlsReportPolicy {
failure_details: List::from(vec![]),
mx_hosts: Map::new(vec!["mx3.myserver.com".to_string()]),
policy_domain: "myserver.com".to_string(),
policy_strings: Map::new(vec![]),
policy_type: TlsPolicyType::Tlsa,
total_failed_sessions: 0,
total_successful_sessions: 3200,
},
TlsReportPolicy {
failure_details: List::from(vec![TlsFailureDetails {
additional_information: Some(
"Fallback to plaintext after STARTTLS failure".to_string(),
),
failed_session_count: 2,
failure_reason_code: Some("starttls-not-supported".to_string()),
receiving_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(
192, 0, 2, 50,
)))),
receiving_mx_helo: Some("backup-mx.myserver.com".to_string()),
receiving_mx_hostname: Some("backup-mx.myserver.com".to_string()),
result_type: TlsResultType::StartTlsNotSupported,
sending_mta_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(
198, 51, 100, 5,
)))),
}]),
mx_hosts: Map::new(vec!["backup-mx.myserver.com".to_string()]),
policy_domain: "myserver.com".to_string(),
policy_strings: Map::new(vec!["mode: testing".to_string()]),
policy_type: TlsPolicyType::Sts,
total_failed_sessions: 2,
total_successful_sessions: 50,
},
]),
report_id: "tls-ext-001-bigprovider".to_string(),
},
subject: "TLS-RPT report for myserver.com".to_string(),
to: Map::new(vec!["tls-rpt@myserver.com".to_string()]),
},
// Report 2: Report with expired certificate failures
TlsExternalReport {
expires_at: future,
from: "noreply@securemail.org".to_string(),
member_tenant_id: None,
received_at: now(),
report: TlsReport {
contact_info: None,
date_range_end: now(),
date_range_start: days_ago(1),
organization_name: Some("SecureMail".to_string()),
policies: List::from(vec![
TlsReportPolicy {
failure_details: List::from(vec![TlsFailureDetails {
additional_information: Some(
"Certificate expired 2 days ago".to_string(),
),
failed_session_count: 30,
failure_reason_code: Some("certificate-expired".to_string()),
receiving_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(
192, 0, 2, 10,
)))),
receiving_mx_helo: Some("mx1.myserver.com".to_string()),
receiving_mx_hostname: Some("mx1.myserver.com".to_string()),
result_type: TlsResultType::CertificateExpired,
sending_mta_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(
198, 51, 100, 1,
)))),
}]),
mx_hosts: Map::new(vec!["mx1.myserver.com".to_string()]),
policy_domain: "myserver.com".to_string(),
policy_strings: Map::new(vec!["mode: enforce".to_string()]),
policy_type: TlsPolicyType::Sts,
total_failed_sessions: 30,
total_successful_sessions: 70,
},
TlsReportPolicy {
failure_details: List::from(vec![TlsFailureDetails {
additional_information: Some(
"CN=old.myserver.com does not match mx2.myserver.com".to_string(),
),
failed_session_count: 15,
failure_reason_code: Some("certificate-host-mismatch".to_string()),
receiving_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(
192, 0, 2, 11,
)))),
receiving_mx_helo: Some("mx2.myserver.com".to_string()),
receiving_mx_hostname: Some("mx2.myserver.com".to_string()),
result_type: TlsResultType::CertificateHostMismatch,
sending_mta_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(
198, 51, 100, 2,
)))),
}]),
mx_hosts: Map::new(vec!["mx2.myserver.com".to_string()]),
policy_domain: "myserver.com".to_string(),
policy_strings: Map::new(vec!["mode: enforce".to_string()]),
policy_type: TlsPolicyType::Sts,
total_failed_sessions: 15,
total_successful_sessions: 85,
},
TlsReportPolicy {
failure_details: List::from(vec![TlsFailureDetails {
additional_information: Some(
"Untrusted CA in certificate chain".to_string(),
),
failed_session_count: 8,
failure_reason_code: Some("certificate-not-trusted".to_string()),
receiving_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(
192, 0, 2, 12,
)))),
receiving_mx_helo: Some("mx3.myserver.com".to_string()),
receiving_mx_hostname: Some("mx3.myserver.com".to_string()),
result_type: TlsResultType::CertificateNotTrusted,
sending_mta_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(
198, 51, 100, 3,
)))),
}]),
mx_hosts: Map::new(vec!["mx3.myserver.com".to_string()]),
policy_domain: "myserver.com".to_string(),
policy_strings: Map::new(vec!["mode: testing".to_string()]),
policy_type: TlsPolicyType::Sts,
total_failed_sessions: 8,
total_successful_sessions: 120,
},
]),
report_id: "tls-ext-002-securemail".to_string(),
},
subject: "SMTP TLS Reporting for myserver.com".to_string(),
to: Map::new(vec!["tls-rpt@myserver.com".to_string()]),
},
// Report 3: DANE report with no policy found
TlsExternalReport {
expires_at: future,
from: "reports@mailhoster.net".to_string(),
member_tenant_id: None,
received_at: now(),
report: TlsReport {
contact_info: Some("abuse@mailhoster.net".to_string()),
date_range_end: now(),
date_range_start: days_ago(7),
organization_name: Some("MailHoster".to_string()),
policies: List::from(vec![
TlsReportPolicy {
failure_details: List::from(vec![TlsFailureDetails {
additional_information: None,
failed_session_count: 10,
failure_reason_code: None,
receiving_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(
203, 0, 113, 25,
)))),
receiving_mx_helo: Some("mx2.myserver.com".to_string()),
receiving_mx_hostname: Some("mx2.myserver.com".to_string()),
result_type: TlsResultType::StartTlsNotSupported,
sending_mta_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(
203, 0, 113, 50,
)))),
}]),
mx_hosts: Map::new(vec!["mx2.myserver.com".to_string()]),
policy_domain: "myserver.com".to_string(),
policy_strings: Map::new(vec![]),
policy_type: TlsPolicyType::NoPolicyFound,
total_failed_sessions: 10,
total_successful_sessions: 0,
},
TlsReportPolicy {
failure_details: List::from(vec![TlsFailureDetails {
additional_information: Some(
"DANE TLSA record invalid for mx1.myserver.com".to_string(),
),
failed_session_count: 20,
failure_reason_code: Some("tlsa-invalid".to_string()),
receiving_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(
203, 0, 113, 26,
)))),
receiving_mx_helo: Some("mx1.myserver.com".to_string()),
receiving_mx_hostname: Some("mx1.myserver.com".to_string()),
result_type: TlsResultType::TlsaInvalid,
sending_mta_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(
203, 0, 113, 51,
)))),
}]),
mx_hosts: Map::new(vec!["mx1.myserver.com".to_string()]),
policy_domain: "myserver.com".to_string(),
policy_strings: Map::new(vec![]),
policy_type: TlsPolicyType::Tlsa,
total_failed_sessions: 20,
total_successful_sessions: 180,
},
TlsReportPolicy {
failure_details: List::from(vec![TlsFailureDetails {
additional_information: Some(
"DNSSEC validation failed for _25._tcp.mx1.myserver.com"
.to_string(),
),
failed_session_count: 5,
failure_reason_code: Some("dnssec-invalid".to_string()),
receiving_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(
203, 0, 113, 27,
)))),
receiving_mx_helo: Some("mx1.myserver.com".to_string()),
receiving_mx_hostname: Some("mx1.myserver.com".to_string()),
result_type: TlsResultType::DnssecInvalid,
sending_mta_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(
203, 0, 113, 52,
)))),
}]),
mx_hosts: Map::new(vec!["mx1.myserver.com".to_string()]),
policy_domain: "myserver.com".to_string(),
policy_strings: Map::new(vec![]),
policy_type: TlsPolicyType::Tlsa,
total_failed_sessions: 5,
total_successful_sessions: 95,
},
]),
report_id: "tls-ext-003-mailhoster".to_string(),
},
subject: "TLS Report: myserver.com".to_string(),
to: Map::new(vec!["tls-rpt@myserver.com".to_string()]),
},
]
}
fn sample_dmarc_external_reports() -> Vec<DmarcExternalReport> {
let future = one_year_from_now();
vec![
// Report 1: Google-style aggregate report, all pass
DmarcExternalReport {
expires_at: future,
from: "noreply-dmarc@google.com".to_string(),
member_tenant_id: None,
received_at: now(),
report: DmarcReport {
date_range_begin: days_ago(1),
date_range_end: now(),
email: "noreply-dmarc@google.com".to_string(),
errors: Map::new(vec![]),
extensions: List::from(vec![]),
extra_contact_info: Some(
"https://support.google.com/a/answer/10032169".to_string(),
),
org_name: "google.com".to_string(),
policy_adkim: DmarcAlignment::Relaxed,
policy_aspf: DmarcAlignment::Relaxed,
policy_disposition: DmarcDisposition::None,
policy_domain: "myserver.com".to_string(),
policy_failure_reporting_options: Map::new(vec![]),
policy_subdomain_disposition: DmarcDisposition::None,
policy_testing_mode: false,
policy_version: Some("DMARC1".to_string()),
records: List::from(vec![
DmarcReportRecord {
count: 1500,
dkim_results: List::from(vec![DmarcDkimResult {
domain: "myserver.com".to_string(),
human_result: None,
result: DkimAuthResult::Pass,
selector: "google".to_string(),
}]),
envelope_from: "myserver.com".to_string(),
envelope_to: None,
evaluated_disposition: Default::default(),
evaluated_dkim: DmarcResult::Pass,
evaluated_spf: DmarcResult::Pass,
extensions: List::from(vec![]),
header_from: "myserver.com".to_string(),
policy_override_reasons: List::from(vec![]),
source_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1)))),
spf_results: List::from(vec![DmarcSpfResult {
domain: "myserver.com".to_string(),
human_result: None,
result: SpfAuthResult::Pass,
scope: SpfDomainScope::MailFrom,
}]),
},
DmarcReportRecord {
count: 320,
dkim_results: List::from(vec![DmarcDkimResult {
domain: "myserver.com".to_string(),
human_result: None,
result: DkimAuthResult::Pass,
selector: "selector2".to_string(),
}]),
envelope_from: "myserver.com".to_string(),
envelope_to: None,
evaluated_disposition: Default::default(),
evaluated_dkim: DmarcResult::Pass,
evaluated_spf: DmarcResult::Pass,
extensions: List::from(vec![]),
header_from: "myserver.com".to_string(),
policy_override_reasons: List::from(vec![]),
source_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(192, 0, 2, 2)))),
spf_results: List::from(vec![DmarcSpfResult {
domain: "myserver.com".to_string(),
human_result: None,
result: SpfAuthResult::Pass,
scope: SpfDomainScope::MailFrom,
}]),
},
DmarcReportRecord {
count: 7,
dkim_results: List::from(vec![DmarcDkimResult {
domain: "sub.myserver.com".to_string(),
human_result: Some("DKIM signature uses subdomain".to_string()),
result: DkimAuthResult::Pass,
selector: "google".to_string(),
}]),
envelope_from: "sub.myserver.com".to_string(),
envelope_to: None,
evaluated_disposition: Default::default(),
evaluated_dkim: DmarcResult::Pass,
evaluated_spf: DmarcResult::Fail,
extensions: List::from(vec![]),
header_from: "myserver.com".to_string(),
policy_override_reasons: List::from(vec![]),
source_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(192, 0, 2, 3)))),
spf_results: List::from(vec![DmarcSpfResult {
domain: "sub.myserver.com".to_string(),
human_result: Some(
"SPF alignment failed: subdomain mismatch".to_string(),
),
result: SpfAuthResult::SoftFail,
scope: SpfDomainScope::MailFrom,
}]),
},
]),
report_id: "dmarc-ext-001-google".to_string(),
version: Float::from(1.0),
},
subject: "Report domain: myserver.com Submitter: google.com".to_string(),
to: Map::new(vec!["dmarc-rua@myserver.com".to_string()]),
},
// Report 2: Report showing spoofing attempts from an unknown source
DmarcExternalReport {
expires_at: future,
from: "dmarc@yahoo.com".to_string(),
member_tenant_id: None,
received_at: now(),
report: DmarcReport {
date_range_begin: days_ago(1),
date_range_end: now(),
email: "dmarc@yahoo.com".to_string(),
errors: Map::new(vec![]),
extensions: List::from(vec![]),
extra_contact_info: None,
org_name: "Yahoo! Inc.".to_string(),
policy_adkim: DmarcAlignment::Strict,
policy_aspf: DmarcAlignment::Strict,
policy_disposition: DmarcDisposition::Reject,
policy_domain: "myserver.com".to_string(),
policy_failure_reporting_options: Map::new(vec![]),
policy_subdomain_disposition: DmarcDisposition::Reject,
policy_testing_mode: false,
policy_version: Some("DMARC1".to_string()),
records: List::from(vec![
DmarcReportRecord {
count: 8,
dkim_results: List::from(vec![DmarcDkimResult {
domain: "myserver.com".to_string(),
human_result: Some("bad signature".to_string()),
result: DkimAuthResult::Fail,
selector: "default".to_string(),
}]),
envelope_from: "attacker.example".to_string(),
envelope_to: None,
evaluated_disposition: Default::default(),
evaluated_dkim: DmarcResult::Fail,
evaluated_spf: DmarcResult::Fail,
extensions: List::from(vec![]),
header_from: "myserver.com".to_string(),
policy_override_reasons: List::from(vec![]),
source_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(
198, 51, 100, 222,
)))),
spf_results: List::from(vec![DmarcSpfResult {
domain: "attacker.example".to_string(),
human_result: Some("domain not found".to_string()),
result: SpfAuthResult::Fail,
scope: SpfDomainScope::MailFrom,
}]),
},
DmarcReportRecord {
count: 15,
dkim_results: List::from(vec![DmarcDkimResult {
domain: "phisher.net".to_string(),
human_result: Some(
"no valid DKIM signature for myserver.com".to_string(),
),
result: DkimAuthResult::None,
selector: "".to_string(),
}]),
envelope_from: "phisher.net".to_string(),
envelope_to: None,
evaluated_disposition: Default::default(),
evaluated_dkim: DmarcResult::Fail,
evaluated_spf: DmarcResult::Fail,
extensions: List::from(vec![]),
header_from: "myserver.com".to_string(),
policy_override_reasons: List::from(vec![]),
source_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(
198, 51, 100, 100,
)))),
spf_results: List::from(vec![DmarcSpfResult {
domain: "phisher.net".to_string(),
human_result: Some("SPF domain mismatch".to_string()),
result: SpfAuthResult::Fail,
scope: SpfDomainScope::MailFrom,
}]),
},
DmarcReportRecord {
count: 2,
dkim_results: List::from(vec![
DmarcDkimResult {
domain: "myserver.com".to_string(),
human_result: Some("signature expired".to_string()),
result: DkimAuthResult::Fail,
selector: "selector1".to_string(),
},
DmarcDkimResult {
domain: "myserver.com".to_string(),
human_result: Some("body hash mismatch".to_string()),
result: DkimAuthResult::Fail,
selector: "selector2".to_string(),
},
]),
envelope_from: "compromised-relay.example".to_string(),
envelope_to: None,
evaluated_disposition: Default::default(),
evaluated_dkim: DmarcResult::Fail,
evaluated_spf: DmarcResult::Fail,
extensions: List::from(vec![]),
header_from: "myserver.com".to_string(),
policy_override_reasons: List::from(vec![]),
source_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(
198, 51, 100, 33,
)))),
spf_results: List::from(vec![DmarcSpfResult {
domain: "compromised-relay.example".to_string(),
human_result: None,
result: SpfAuthResult::PermError,
scope: SpfDomainScope::MailFrom,
}]),
},
]),
report_id: "dmarc-ext-002-yahoo".to_string(),
version: Float::from(1.0),
},
subject: "Report domain: myserver.com Submitter: yahoo.com".to_string(),
to: Map::new(vec!["dmarc-rua@myserver.com".to_string()]),
},
// Report 3: Microsoft report with forwarded mail override
DmarcExternalReport {
expires_at: future,
from: "dmarc-noreply@microsoft.com".to_string(),
member_tenant_id: None,
received_at: now(),
report: DmarcReport {
date_range_begin: days_ago(1),
date_range_end: now(),
email: "dmarc-noreply@microsoft.com".to_string(),
errors: Map::new(vec![]),
extensions: List::from(vec![]),
extra_contact_info: None,
org_name: "Microsoft Corporation".to_string(),
policy_adkim: DmarcAlignment::Relaxed,
policy_aspf: DmarcAlignment::Relaxed,
policy_disposition: DmarcDisposition::Quarantine,
policy_domain: "myserver.com".to_string(),
policy_failure_reporting_options: Map::new(vec![]),
policy_subdomain_disposition: DmarcDisposition::Quarantine,
policy_testing_mode: false,
policy_version: Some("DMARC1".to_string()),
records: List::from(vec![
DmarcReportRecord {
count: 25,
dkim_results: List::from(vec![DmarcDkimResult {
domain: "myserver.com".to_string(),
human_result: None,
result: DkimAuthResult::Pass,
selector: "selector1".to_string(),
}]),
envelope_from: "forwarder.example.com".to_string(),
envelope_to: None,
evaluated_disposition: Default::default(),
evaluated_dkim: DmarcResult::Pass,
evaluated_spf: DmarcResult::Fail,
extensions: List::from(vec![]),
header_from: "myserver.com".to_string(),
policy_override_reasons: List::from(vec![]),
source_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(
203, 0, 113, 100,
)))),
spf_results: List::from(vec![DmarcSpfResult {
domain: "forwarder.example.com".to_string(),
human_result: None,
result: SpfAuthResult::SoftFail,
scope: SpfDomainScope::MailFrom,
}]),
},
DmarcReportRecord {
count: 800,
dkim_results: List::from(vec![DmarcDkimResult {
domain: "myserver.com".to_string(),
human_result: None,
result: DkimAuthResult::Pass,
selector: "selector1".to_string(),
}]),
envelope_from: "myserver.com".to_string(),
envelope_to: None,
evaluated_disposition: Default::default(),
evaluated_dkim: DmarcResult::Pass,
evaluated_spf: DmarcResult::Pass,
extensions: List::from(vec![]),
header_from: "myserver.com".to_string(),
policy_override_reasons: List::from(vec![]),
source_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1)))),
spf_results: List::from(vec![DmarcSpfResult {
domain: "myserver.com".to_string(),
human_result: None,
result: SpfAuthResult::Pass,
scope: SpfDomainScope::MailFrom,
}]),
},
DmarcReportRecord {
count: 40,
dkim_results: List::from(vec![DmarcDkimResult {
domain: "myserver.com".to_string(),
human_result: Some(
"DKIM signature OK but SPF failed via mailing list".to_string(),
),
result: DkimAuthResult::Pass,
selector: "selector1".to_string(),
}]),
envelope_from: "mailinglist.example.org".to_string(),
envelope_to: None,
evaluated_disposition: Default::default(),
evaluated_dkim: DmarcResult::Pass,
evaluated_spf: DmarcResult::Fail,
extensions: List::from(vec![]),
header_from: "myserver.com".to_string(),
policy_override_reasons: List::from(vec![]),
source_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(
203, 0, 113, 200,
)))),
spf_results: List::from(vec![DmarcSpfResult {
domain: "mailinglist.example.org".to_string(),
human_result: Some("Mailing list rewrite".to_string()),
result: SpfAuthResult::Fail,
scope: SpfDomainScope::MailFrom,
}]),
},
]),
report_id: "dmarc-ext-003-msft".to_string(),
version: Float::from(1.0),
},
subject: "Report domain: myserver.com Submitter: microsoft.com".to_string(),
to: Map::new(vec!["dmarc-rua@myserver.com".to_string()]),
},
]
}
fn sample_arf_external_reports() -> Vec<ArfExternalReport> {
let future = one_year_from_now();
vec![
// Report 1: Abuse complaint from a user
ArfExternalReport {
expires_at: future,
from: "fbl@isp-provider.com".to_string(),
member_tenant_id: None,
received_at: now(),
report: ArfFeedbackReport {
arrival_date: Some(days_ago(1)),
auth_failure: ArfAuthFailureType::Unspecified,
authentication_results: Map::new(vec![
"dkim=pass header.d=myserver.com".to_string(),
"spf=pass smtp.mailfrom=myserver.com".to_string(),
]),
delivery_result: ArfDeliveryResult::Delivered,
dkim_adsp_dns: None,
dkim_canonicalized_body: None,
dkim_canonicalized_header: None,
dkim_domain: Some("myserver.com".to_string()),
dkim_identity: None,
dkim_selector: Some("selector1".to_string()),
dkim_selector_dns: None,
feedback_type: ArfFeedbackType::Abuse,
headers: Some(
"From: newsletter@myserver.com\r\nTo: user@isp-provider.com\r\nSubject: Weekly Newsletter\r\nDate: Mon, 10 Apr 2027 10:00:00 +0000"
.to_string(),
),
identity_alignment: ArfIdentityAlignment::DkimSpf,
incidents: 1,
message: Some("User marked this message as spam".to_string()),
original_envelope_id: Some("env-newsletter-001".to_string()),
original_mail_from: Some("newsletter@myserver.com".to_string()),
original_rcpt_to: Some("user@isp-provider.com".to_string()),
reported_domains: Map::new(vec!["myserver.com".to_string()]),
reported_uris: Map::new(vec![]),
reporting_mta: Some("fbl.isp-provider.com".to_string()),
source_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1)))),
source_port: Some(25),
spf_dns: None,
user_agent: Some("ISP-FBL/2.0".to_string()),
version: 1,
},
subject: "FBL report from isp-provider.com".to_string(),
to: Map::new(vec!["abuse@myserver.com".to_string()]),
},
// Report 2: Auth failure report (DKIM)
ArfExternalReport {
expires_at: future,
from: "authfail@receiver.org".to_string(),
member_tenant_id: None,
received_at: now(),
report: ArfFeedbackReport {
arrival_date: Some(days_ago(2)),
auth_failure: ArfAuthFailureType::Signature,
authentication_results: Map::new(vec![
"dkim=fail header.d=myserver.com".to_string(),
]),
delivery_result: ArfDeliveryResult::Reject,
dkim_adsp_dns: None,
dkim_canonicalized_body: Some("base64bodyhash==".to_string()),
dkim_canonicalized_header: Some("base64headerhash==".to_string()),
dkim_domain: Some("myserver.com".to_string()),
dkim_identity: Some("@myserver.com".to_string()),
dkim_selector: Some("selector1".to_string()),
dkim_selector_dns: Some(
"v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUA".to_string(),
),
feedback_type: ArfFeedbackType::AuthFailure,
headers: Some(
"From: info@myserver.com\r\nTo: contact@receiver.org\r\nSubject: Important Update"
.to_string(),
),
identity_alignment: ArfIdentityAlignment::None,
incidents: 3,
message: None,
original_envelope_id: None,
original_mail_from: Some("info@myserver.com".to_string()),
original_rcpt_to: Some("contact@receiver.org".to_string()),
reported_domains: Map::new(vec!["myserver.com".to_string()]),
reported_uris: Map::new(vec![]),
reporting_mta: Some("mx.receiver.org".to_string()),
source_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1)))),
source_port: Some(587),
spf_dns: None,
user_agent: Some("ReceiverMTA/1.0".to_string()),
version: 1,
},
subject: "Authentication failure report for myserver.com".to_string(),
to: Map::new(vec!["abuse@myserver.com".to_string()]),
},
// Report 3: Fraud/phishing report
ArfExternalReport {
expires_at: future,
from: "reports@antiphish.net".to_string(),
member_tenant_id: None,
received_at: now(),
report: ArfFeedbackReport {
arrival_date: Some(days_ago(3)),
auth_failure: ArfAuthFailureType::Dmarc,
authentication_results: Map::new(vec![
"dmarc=fail header.from=myserver.com".to_string(),
"spf=fail smtp.mailfrom=spoofed.example".to_string(),
]),
delivery_result: ArfDeliveryResult::Policy,
dkim_adsp_dns: None,
dkim_canonicalized_body: None,
dkim_canonicalized_header: None,
dkim_domain: None,
dkim_identity: None,
dkim_selector: None,
dkim_selector_dns: None,
feedback_type: ArfFeedbackType::Fraud,
headers: Some(
"From: security@myserver.com\r\nTo: victim@antiphish.net\r\nSubject: Urgent: Verify your account"
.to_string(),
),
identity_alignment: ArfIdentityAlignment::None,
incidents: 50,
message: Some("Phishing attempt impersonating myserver.com".to_string()),
original_envelope_id: None,
original_mail_from: Some("spoofer@spoofed.example".to_string()),
original_rcpt_to: Some("victim@antiphish.net".to_string()),
reported_domains: Map::new(vec![
"myserver.com".to_string(),
"spoofed.example".to_string(),
]),
reported_uris: Map::new(vec![
"https://evil-site.example/phish".to_string(),
]),
reporting_mta: Some("gateway.antiphish.net".to_string()),
source_ip: Some(IpAddr(std::net::IpAddr::V4(Ipv4Addr::new(
198, 51, 100, 77,
)))),
source_port: Some(25),
spf_dns: Some("v=spf1 -all".to_string()),
user_agent: Some("AntiPhish/3.0".to_string()),
version: 1,
},
subject: "Fraud report: phishing attempt using myserver.com".to_string(),
to: Map::new(vec![
"abuse@myserver.com".to_string(),
"security@myserver.com".to_string(),
]),
},
]
}
fn one_year_from_now_u64() -> u64 {
store::write::now() + 365 * 24 * 3600
}
fn one_year_from_now() -> UTCDateTime {
UTCDateTime::from_timestamp(store::write::now().cast_signed() + 365 * 24 * 3600)
}
fn now() -> UTCDateTime {
UTCDateTime::from_timestamp(store::write::now().cast_signed())
}
fn days_ago(days: i64) -> UTCDateTime {
UTCDateTime::from_timestamp(store::write::now().cast_signed() - days * 24 * 3600)
}