server: include reason in dmarc report

This commit is contained in:
Bill Thiede 2025-08-12 08:16:26 -07:00
parent 01164d6afa
commit 8218fca2ef
2 changed files with 27 additions and 38 deletions

View File

@ -1199,6 +1199,7 @@ pub struct FormattedRecord {
pub disposition: String,
pub dkim: String,
pub spf: String,
pub reason: Vec<String>,
pub auth_results: Option<FormattedAuthResults>,
}
@ -1280,7 +1281,7 @@ pub struct PolicyEvaluated {
pub spf: Option<String>,
pub reason: Option<Vec<Reason>>,
}
#[derive(Debug, serde::Deserialize)]
#[derive(Debug, serde::Deserialize, Clone)]
pub struct Reason {
#[serde(rename = "type")]
pub reason_type: Option<String>,
@ -1325,13 +1326,13 @@ pub fn parse_dmarc_report(xml: &str) -> Result<String, ServerError> {
chrono::LocalResult::Single(d) => Some(d),
_ => None,
}
.map(|d| d.format("%Y-%m-%d").to_string())
.map(|d| d.format("%Y-%m-%d %H:%M:%S").to_string())
.unwrap_or_else(|| "".to_string()),
end: match Utc.timestamp_opt(dr.end.unwrap_or(0) as i64, 0) {
chrono::LocalResult::Single(d) => Some(d),
_ => None,
}
.map(|d| d.format("%Y-%m-%d").to_string())
.map(|d| d.format("%Y-%m-%d %H:%M:%S").to_string())
.unwrap_or_else(|| "".to_string()),
});
FormattedReportMetadata {
@ -1378,39 +1379,23 @@ pub fn parse_dmarc_report(xml: &str) -> Result<String, ServerError> {
});
FormattedRecord {
source_ip: rec
.row
.as_ref()
.and_then(|r| r.source_ip.clone())
.unwrap_or_else(|| "".to_string()),
count: rec
.row
.as_ref()
.and_then(|r| r.count.map(|c| c.to_string()))
.unwrap_or_else(|| "".to_string()),
header_from: rec
.identifiers
.as_ref()
.and_then(|i| i.header_from.clone())
.unwrap_or_else(|| "".to_string()),
disposition: rec
.row
.as_ref()
.and_then(|r| r.policy_evaluated.as_ref())
.and_then(|p| p.disposition.clone())
.unwrap_or_else(|| "".to_string()),
dkim: rec
.row
.as_ref()
.and_then(|r| r.policy_evaluated.as_ref())
.and_then(|p| p.dkim.clone())
.unwrap_or_else(|| "".to_string()),
spf: rec
.row
.as_ref()
.and_then(|r| r.policy_evaluated.as_ref())
.and_then(|p| p.spf.clone())
.unwrap_or_else(|| "".to_string()),
source_ip: rec.row.as_ref().and_then(|r| r.source_ip.clone()).unwrap_or_else(|| "".to_string()),
count: rec.row.as_ref().and_then(|r| r.count.map(|c| c.to_string())).unwrap_or_else(|| "".to_string()),
header_from: rec.identifiers.as_ref().and_then(|i| i.header_from.clone()).unwrap_or_else(|| "".to_string()),
disposition: rec.row.as_ref().and_then(|r| r.policy_evaluated.as_ref()).and_then(|p| p.disposition.clone()).unwrap_or_else(|| "".to_string()),
dkim: rec.row.as_ref().and_then(|r| r.policy_evaluated.as_ref()).and_then(|p| p.dkim.clone()).unwrap_or_else(|| "".to_string()),
spf: rec.row.as_ref().and_then(|r| r.policy_evaluated.as_ref()).and_then(|p| p.spf.clone()).unwrap_or_else(|| "".to_string()),
reason: rec.row.as_ref().and_then(|r| r.policy_evaluated.as_ref()).and_then(|p| p.reason.clone()).unwrap_or_else(|| Vec::new()).into_iter().map(|r| {
let mut s = String::new();
if let Some(reason_type) = r.reason_type {
s.push_str(&format!("Type: {}", reason_type));
}
if let Some(comment) = r.comment {
if !s.is_empty() { s.push_str(", "); }
s.push_str(&format!("Comment: {}", comment));
}
s
}).collect(),
auth_results,
}
})

View File

@ -74,6 +74,10 @@
</span><br>
{% endfor %}
{% for reason in rec.reason %}
<span style="white-space:nowrap;">Reason: {{ reason }}</span><br>
{% endfor %}
{% endif %}
</td>
</tr>