server: handle application/zip for google dmarc
This commit is contained in:
parent
43827b4d87
commit
59e35062e7
204
Cargo.lock
generated
204
Cargo.lock
generated
@ -27,6 +27,17 @@ version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.1",
|
||||
"cipher",
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.8"
|
||||
@ -143,6 +154,15 @@ version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33954243bd79057c2de7338850b85983a44588021f8a5fee574a8888c6de4344"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223"
|
||||
dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "1.7.1"
|
||||
@ -580,7 +600,7 @@ checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec 0.5.2",
|
||||
"constant_time_eq",
|
||||
"constant_time_eq 0.1.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -755,6 +775,15 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bzip2"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bea8dcd42434048e4f7a304411d9273a411f647446c1234a65ce0554923f4cff"
|
||||
dependencies = [
|
||||
"libbz2-rs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cacher"
|
||||
version = "0.2.0"
|
||||
@ -889,6 +918,16 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"inout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.43"
|
||||
@ -1035,6 +1074,12 @@ version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
@ -1406,6 +1451,12 @@ version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
|
||||
|
||||
[[package]]
|
||||
name = "deflate64"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b"
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.10"
|
||||
@ -1427,6 +1478,17 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.20"
|
||||
@ -1697,6 +1759,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"libz-rs-sys",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
@ -2918,6 +2981,15 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
|
||||
dependencies = [
|
||||
"generic-array 0.14.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.13"
|
||||
@ -3101,6 +3173,7 @@ dependencies = [
|
||||
"mailparse",
|
||||
"maplit",
|
||||
"memmap",
|
||||
"quick-xml",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"scraper",
|
||||
@ -3115,6 +3188,7 @@ dependencies = [
|
||||
"url",
|
||||
"urlencoding",
|
||||
"xtracing",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3180,6 +3254,12 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c2cdeb66e45e9f36bfad5bbdb4d2384e70936afbee843c6f6543f0c551ebb25"
|
||||
|
||||
[[package]]
|
||||
name = "libbz2-rs-sys"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.174"
|
||||
@ -3198,6 +3278,26 @@ dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "liblzma"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0791ab7e08ccc8e0ce893f6906eb2703ed8739d8e89b57c0714e71bad09024c8"
|
||||
dependencies = [
|
||||
"liblzma-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "liblzma-sys"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01b9596486f6d60c3bbe644c0e1be1aa6ccc472ad630fe8927b456973d7cb736"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.15"
|
||||
@ -3224,6 +3324,15 @@ dependencies = [
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libz-rs-sys"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "172a788537a2221661b480fee8dc5f96c580eb34fa88764d3205dc356c7e4221"
|
||||
dependencies = [
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libz-sys"
|
||||
version = "1.1.22"
|
||||
@ -4071,6 +4180,16 @@ version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pbkdf2"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
|
||||
dependencies = [
|
||||
"digest 0.10.7",
|
||||
"hmac 0.12.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem-rfc7468"
|
||||
version = "0.7.0"
|
||||
@ -4256,6 +4375,12 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppmd-rust"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c834641d8ad1b348c9ee86dec3b9840d805acd5f24daa5f90c788951a52ff59b"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
@ -4387,6 +4512,16 @@ dependencies = [
|
||||
"prost",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.38.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9845d9dccf565065824e69f9f235fafba1587031eda353c1f1561cd6a6be78f4"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn"
|
||||
version = "0.11.8"
|
||||
@ -4923,7 +5058,7 @@ checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"blake2b_simd",
|
||||
"constant_time_eq",
|
||||
"constant_time_eq 0.1.5",
|
||||
"crossbeam-utils 0.8.21",
|
||||
]
|
||||
|
||||
@ -5442,6 +5577,12 @@ dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.1"
|
||||
@ -7637,6 +7778,20 @@ name = "zeroize"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize_derive"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
@ -7671,6 +7826,51 @@ dependencies = [
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "4.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aed4ac33e8eb078c89e6cbb1d5c4c7703ec6d299fc3e7c3695af8f8b423468b"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"arbitrary",
|
||||
"bzip2",
|
||||
"constant_time_eq 0.3.1",
|
||||
"crc32fast",
|
||||
"deflate64",
|
||||
"flate2",
|
||||
"getrandom 0.3.3",
|
||||
"hmac 0.12.1",
|
||||
"indexmap 2.10.0",
|
||||
"liblzma",
|
||||
"memchr",
|
||||
"pbkdf2",
|
||||
"ppmd-rust",
|
||||
"sha1",
|
||||
"time 0.3.41",
|
||||
"zeroize",
|
||||
"zopfli",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zlib-rs"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "626bd9fa9734751fc50d6060752170984d7053f5a39061f524cda68023d4db8a"
|
||||
|
||||
[[package]]
|
||||
name = "zopfli"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"crc32fast",
|
||||
"log",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.13.3"
|
||||
|
||||
@ -34,6 +34,7 @@ lol_html = "2.3.0"
|
||||
mailparse = "0.16.1"
|
||||
maplit = "1.0.2"
|
||||
memmap = "0.7.0"
|
||||
quick-xml = { version = "0.38.1", features = ["serialize"] }
|
||||
regex = "1.11.1"
|
||||
reqwest = { version = "0.12.15", features = ["blocking"] }
|
||||
scraper = "0.23.1"
|
||||
@ -50,6 +51,7 @@ urlencoding = "2.1.3"
|
||||
#xtracing = { git = "http://git-private.h.xinu.tv/wathiede/xtracing.git" }
|
||||
#xtracing = { path = "../../xtracing" }
|
||||
xtracing = { version = "0.3.2", registry = "xinu" }
|
||||
zip = "4.3.0"
|
||||
|
||||
[build-dependencies]
|
||||
build-info-build = "0.0.41"
|
||||
|
||||
233
server/src/nm.rs
233
server/src/nm.rs
@ -1,14 +1,17 @@
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fs::File,
|
||||
io::Cursor,
|
||||
};
|
||||
|
||||
use letterbox_notmuch::Notmuch;
|
||||
use letterbox_shared::{compute_color, Rule};
|
||||
use mailparse::{parse_content_type, parse_mail, MailHeader, MailHeaderMap, ParsedMail};
|
||||
use memmap::MmapOptions;
|
||||
use quick_xml::de::from_str as xml_from_str;
|
||||
use sqlx::{types::Json, PgPool};
|
||||
use tracing::{error, info, info_span, instrument, warn};
|
||||
use zip::ZipArchive;
|
||||
|
||||
use crate::{
|
||||
compute_offset_limit,
|
||||
@ -20,6 +23,7 @@ use crate::{
|
||||
linkify_html, InlineStyle, Query, SanitizeHtml, Transformer,
|
||||
};
|
||||
|
||||
const APPLICATION_ZIP: &'static str = "application/zip";
|
||||
const IMAGE_JPEG: &'static str = "image/jpeg";
|
||||
const IMAGE_PJPEG: &'static str = "image/pjpeg";
|
||||
const IMAGE_PNG: &'static str = "image/png";
|
||||
@ -447,6 +451,42 @@ fn extract_body(m: &ParsedMail, part_addr: &mut Vec<String>) -> Result<Body, Ser
|
||||
MULTIPART_MIXED => extract_mixed(m, part_addr),
|
||||
MULTIPART_ALTERNATIVE => extract_alternative(m, part_addr),
|
||||
MULTIPART_RELATED => extract_related(m, part_addr),
|
||||
APPLICATION_ZIP => {
|
||||
// Try to extract DMARC XML from ZIP
|
||||
if let Ok(zip_bytes) = m.get_body_raw() {
|
||||
if let Ok(mut archive) = ZipArchive::new(Cursor::new(&zip_bytes)) {
|
||||
for i in 0..archive.len() {
|
||||
if let Ok(mut file) = archive.by_index(i) {
|
||||
let name = file.name().to_lowercase();
|
||||
// Google DMARC reports are typically named like "google.com!example.com!...xml"
|
||||
// and may or may not contain "dmarc" in the filename.
|
||||
if name.ends_with(".xml") && (name.contains("dmarc") || name.starts_with("google.com!")) {
|
||||
let mut xml = String::new();
|
||||
use std::io::Read;
|
||||
if file.read_to_string(&mut xml).is_ok() {
|
||||
match parse_dmarc_report(&xml) {
|
||||
Ok(report) => {
|
||||
return Ok(Body::html(format!(
|
||||
"<div class=\"dmarc-report\">Google DMARC report summary:<br>{}</div>",
|
||||
report
|
||||
)));
|
||||
}
|
||||
Err(e) => {
|
||||
return Ok(Body::html(format!(
|
||||
"<div class=\"dmarc-report-error\">Failed to parse DMARC report XML: {}</div>",
|
||||
e
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If no DMARC report found, fall through to unhandled
|
||||
extract_unhandled(m)
|
||||
}
|
||||
_ => extract_unhandled(m),
|
||||
};
|
||||
if let Err(err) = ret {
|
||||
@ -1103,3 +1143,196 @@ fn find_tags<'a, 'b>(rules: &'a [Rule], headers: &'b [MailHeader]) -> (bool, Has
|
||||
}
|
||||
return (matched_rule, add_tags);
|
||||
}
|
||||
|
||||
// Add this helper function to parse the DMARC XML and summarize it.
|
||||
fn parse_dmarc_report(xml: &str) -> Result<String, ServerError> {
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct Feedback {
|
||||
report_metadata: Option<ReportMetadata>,
|
||||
policy_published: Option<PolicyPublished>,
|
||||
record: Option<Vec<Record>>,
|
||||
}
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct ReportMetadata {
|
||||
org_name: Option<String>,
|
||||
email: Option<String>,
|
||||
report_id: Option<String>,
|
||||
date_range: Option<DateRange>,
|
||||
}
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct DateRange {
|
||||
begin: Option<u64>,
|
||||
end: Option<u64>,
|
||||
}
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct PolicyPublished {
|
||||
domain: Option<String>,
|
||||
adkim: Option<String>,
|
||||
aspf: Option<String>,
|
||||
p: Option<String>,
|
||||
sp: Option<String>,
|
||||
pct: Option<String>,
|
||||
}
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct Record {
|
||||
row: Option<Row>,
|
||||
identifiers: Option<Identifiers>,
|
||||
auth_results: Option<AuthResults>,
|
||||
}
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct Row {
|
||||
source_ip: Option<String>,
|
||||
count: Option<u64>,
|
||||
policy_evaluated: Option<PolicyEvaluated>,
|
||||
}
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct PolicyEvaluated {
|
||||
disposition: Option<String>,
|
||||
dkim: Option<String>,
|
||||
spf: Option<String>,
|
||||
reason: Option<Vec<Reason>>,
|
||||
}
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct Reason {
|
||||
#[serde(rename = "type")]
|
||||
reason_type: Option<String>,
|
||||
comment: Option<String>,
|
||||
}
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct Identifiers {
|
||||
header_from: Option<String>,
|
||||
}
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct AuthResults {
|
||||
dkim: Option<Vec<AuthDKIM>>,
|
||||
spf: Option<Vec<AuthSPF>>,
|
||||
}
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct AuthDKIM {
|
||||
domain: Option<String>,
|
||||
result: Option<String>,
|
||||
selector: Option<String>,
|
||||
}
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct AuthSPF {
|
||||
domain: Option<String>,
|
||||
result: Option<String>,
|
||||
scope: Option<String>,
|
||||
}
|
||||
|
||||
let feedback: Feedback = xml_from_str(xml)
|
||||
.map_err(|e| ServerError::StringError(format!("DMARC XML parse error: {e}")))?;
|
||||
let mut summary = String::new();
|
||||
if let Some(meta) = feedback.report_metadata {
|
||||
if let Some(org) = meta.org_name {
|
||||
summary += &format!("<b>Reporter:</b> {}<br>", org);
|
||||
}
|
||||
if let Some(email) = meta.email {
|
||||
summary += &format!("<b>Contact:</b> {}<br>", email);
|
||||
}
|
||||
if let Some(rid) = meta.report_id {
|
||||
summary += &format!("<b>Report ID:</b> {}<br>", rid);
|
||||
}
|
||||
if let Some(dr) = meta.date_range {
|
||||
if let (Some(begin), Some(end)) = (dr.begin, dr.end) {
|
||||
use chrono::{NaiveDateTime, TimeZone, Utc};
|
||||
let begin_dt = Utc.timestamp_opt(begin as i64, 0).single();
|
||||
let end_dt = Utc.timestamp_opt(end as i64, 0).single();
|
||||
summary += &format!("<b>Date range:</b> {} to {}<br>",
|
||||
begin_dt.map(|d| d.format("%Y-%m-%d").to_string()).unwrap_or(begin.to_string()),
|
||||
end_dt.map(|d| d.format("%Y-%m-%d").to_string()).unwrap_or(end.to_string())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(pol) = feedback.policy_published {
|
||||
summary += "<b>Policy Published:</b><ul>";
|
||||
if let Some(domain) = pol.domain {
|
||||
summary += &format!("<li>Domain: {}</li>", domain);
|
||||
}
|
||||
if let Some(adkim) = pol.adkim {
|
||||
summary += &format!("<li>ADKIM: {}</li>", adkim);
|
||||
}
|
||||
if let Some(aspf) = pol.aspf {
|
||||
summary += &format!("<li>ASPF: {}</li>", aspf);
|
||||
}
|
||||
if let Some(p) = pol.p {
|
||||
summary += &format!("<li>Policy: {}</li>", p);
|
||||
}
|
||||
if let Some(sp) = pol.sp {
|
||||
summary += &format!("<li>Subdomain Policy: {}</li>", sp);
|
||||
}
|
||||
if let Some(pct) = pol.pct {
|
||||
summary += &format!("<li>Percent: {}</li>", pct);
|
||||
}
|
||||
summary += "</ul>";
|
||||
}
|
||||
if let Some(records) = feedback.record {
|
||||
summary += "<b>Records:</b><table style=\"border-collapse:collapse;width:100%;font-size:0.95em;\"><thead><tr style=\"background:#f0f0f0;\"><th style=\"border:1px solid #bbb;padding:4px 8px;\">Source IP</th><th style=\"border:1px solid #bbb;padding:4px 8px;\">Count</th><th style=\"border:1px solid #bbb;padding:4px 8px;\">Header From</th><th style=\"border:1px solid #bbb;padding:4px 8px;\">Disposition</th><th style=\"border:1px solid #bbb;padding:4px 8px;\">DKIM</th><th style=\"border:1px solid #bbb;padding:4px 8px;\">SPF</th><th style=\"border:1px solid #bbb;padding:4px 8px;\">Auth Results</th></tr></thead><tbody>";
|
||||
for rec in records {
|
||||
let mut row_html = String::new();
|
||||
let mut source_ip = String::new();
|
||||
let mut count = String::new();
|
||||
let mut header_from = String::new();
|
||||
let mut disposition = String::new();
|
||||
let mut dkim = String::new();
|
||||
let mut spf = String::new();
|
||||
if let Some(r) = &rec.row {
|
||||
if let Some(ref s) = r.source_ip {
|
||||
source_ip = s.clone();
|
||||
}
|
||||
if let Some(c) = r.count {
|
||||
count = c.to_string();
|
||||
}
|
||||
if let Some(ref pe) = r.policy_evaluated {
|
||||
if let Some(ref disp) = pe.disposition {
|
||||
disposition = disp.clone();
|
||||
}
|
||||
if let Some(ref d) = pe.dkim {
|
||||
dkim = d.clone();
|
||||
}
|
||||
if let Some(ref s) = pe.spf {
|
||||
spf = s.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(ids) = &rec.identifiers {
|
||||
if let Some(ref hf) = ids.header_from {
|
||||
header_from = hf.clone();
|
||||
}
|
||||
}
|
||||
row_html += &format!("<tr><td style=\"border:1px solid #bbb;padding:4px 8px;\">{}</td><td style=\"border:1px solid #bbb;padding:4px 8px;\">{}</td><td style=\"border:1px solid #bbb;padding:4px 8px;\">{}</td><td style=\"border:1px solid #bbb;padding:4px 8px;\">{}</td><td style=\"border:1px solid #bbb;padding:4px 8px;\">{}</td><td style=\"border:1px solid #bbb;padding:4px 8px;\">{}</td><td style=\"border:1px solid #bbb;padding:4px 8px;\">",
|
||||
source_ip, count, header_from, disposition, dkim, spf);
|
||||
// Auth Results
|
||||
let mut auths = String::new();
|
||||
if let Some(auth) = &rec.auth_results {
|
||||
if let Some(dkims) = &auth.dkim {
|
||||
for dkimres in dkims {
|
||||
auths += &format!("<span style=\"white-space:nowrap;\">DKIM: domain=<b>{}</b> selector=<b>{}</b> result=<b>{}</b></span><br>",
|
||||
dkimres.domain.as_deref().unwrap_or(""),
|
||||
dkimres.selector.as_deref().unwrap_or(""),
|
||||
dkimres.result.as_deref().unwrap_or("")
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Some(spfs) = &auth.spf {
|
||||
for spfres in spfs {
|
||||
auths += &format!("<span style=\"white-space:nowrap;\">SPF: domain=<b>{}</b> scope=<b>{}</b> result=<b>{}</b></span><br>",
|
||||
spfres.domain.as_deref().unwrap_or(""),
|
||||
spfres.scope.as_deref().unwrap_or(""),
|
||||
spfres.result.as_deref().unwrap_or("")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
row_html += &auths;
|
||||
row_html += "</td></tr>";
|
||||
summary += &row_html;
|
||||
}
|
||||
summary += "</tbody></table>";
|
||||
}
|
||||
if summary.is_empty() {
|
||||
summary = "No DMARC summary found.".to_string();
|
||||
}
|
||||
Ok(summary)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user