server: fix is_dmarc check

This commit is contained in:
Bill Thiede 2025-08-12 16:28:28 -07:00
parent 5c42d04598
commit 1f75627fd2

View File

@ -27,6 +27,8 @@ use crate::{
linkify_html, InlineStyle, Query, SanitizeHtml, Transformer, linkify_html, InlineStyle, Query, SanitizeHtml, Transformer,
}; };
const APPLICATION_GZIP: &'static str = "application/gzip";
const APPLICATION_ZIP: &'static str = "application/zip"; const APPLICATION_ZIP: &'static str = "application/zip";
const IMAGE_JPEG: &'static str = "image/jpeg"; const IMAGE_JPEG: &'static str = "image/jpeg";
const IMAGE_PJPEG: &'static str = "image/pjpeg"; const IMAGE_PJPEG: &'static str = "image/pjpeg";
@ -351,9 +353,7 @@ pub async fn thread(
for i in 0..archive.len() { for i in 0..archive.len() {
if let Ok(mut file) = archive.by_index(i) { if let Ok(mut file) = archive.by_index(i) {
let name = file.name().to_lowercase(); let name = file.name().to_lowercase();
if name.ends_with(".xml") if is_dmarc_report_filename(&name) {
&& (name.contains("dmarc") || name.starts_with("google.com!"))
{
let mut xml = String::new(); let mut xml = String::new();
use std::io::Read; use std::io::Read;
if file.read_to_string(&mut xml).is_ok() { if file.read_to_string(&mut xml).is_ok() {
@ -564,8 +564,6 @@ fn extract_body(m: &ParsedMail, part_addr: &mut Vec<String>) -> Result<Body, Ser
ret ret
} }
const APPLICATION_GZIP: &'static str = "application/gzip";
fn extract_zip(m: &ParsedMail) -> Result<Body, ServerError> { fn extract_zip(m: &ParsedMail) -> Result<Body, ServerError> {
if let Ok(zip_bytes) = m.get_body_raw() { if let Ok(zip_bytes) = m.get_body_raw() {
if let Ok(mut archive) = ZipArchive::new(Cursor::new(&zip_bytes)) { if let Ok(mut archive) = ZipArchive::new(Cursor::new(&zip_bytes)) {
@ -574,16 +572,14 @@ fn extract_zip(m: &ParsedMail) -> Result<Body, ServerError> {
let name = file.name().to_lowercase(); let name = file.name().to_lowercase();
// Google DMARC reports are typically named like "google.com!example.com!...xml" // Google DMARC reports are typically named like "google.com!example.com!...xml"
// and may or may not contain "dmarc" in the filename. // and may or may not contain "dmarc" in the filename.
if name.ends_with(".xml") if is_dmarc_report_filename(&name) {
&& (name.contains("dmarc") || name.starts_with("google.com!"))
{
let mut xml = String::new(); let mut xml = String::new();
use std::io::Read; use std::io::Read;
if file.read_to_string(&mut xml).is_ok() { if file.read_to_string(&mut xml).is_ok() {
match parse_dmarc_report(&xml) { match parse_dmarc_report(&xml) {
Ok(report) => { Ok(report) => {
return Ok(Body::html(format!( return Ok(Body::html(format!(
"<div class=\"dmarc-report\">Google DMARC report summary:<br>{}</div>", "<div class=\"dmarc-report\">DMARC report summary:<br>{}</div>",
report report
))); )));
} }
@ -605,23 +601,30 @@ fn extract_zip(m: &ParsedMail) -> Result<Body, ServerError> {
} }
fn extract_gzip(m: &ParsedMail) -> Result<Body, ServerError> { fn extract_gzip(m: &ParsedMail) -> Result<Body, ServerError> {
if let Ok(gz_bytes) = m.get_body_raw() { let pcd = m.get_content_disposition();
let mut decoder = flate2::read::GzDecoder::new(&gz_bytes[..]); let filename = pcd.params.get("filename").map(|s| s.to_lowercase());
let mut xml = String::new();
use std::io::Read; let is_dmarc_xml_file = filename.map_or(false, |name| is_dmarc_report_filename(&name));
if decoder.read_to_string(&mut xml).is_ok() {
match parse_dmarc_report(&xml) { if is_dmarc_xml_file {
Ok(report) => { if let Ok(gz_bytes) = m.get_body_raw() {
return Ok(Body::html(format!( let mut decoder = flate2::read::GzDecoder::new(&gz_bytes[..]);
"<div class=\"dmarc-report\">Microsoft DMARC report summary:<br>{}</div>", let mut xml = String::new();
report use std::io::Read;
))); if decoder.read_to_string(&mut xml).is_ok() {
} match parse_dmarc_report(&xml) {
Err(e) => { Ok(report) => {
return Ok(Body::html(format!( return Ok(Body::html(format!(
"<div class=\"dmarc-report-error\">Failed to parse DMARC report XML: {}</div>", "<div class=\"dmarc-report\">DMARC report summary:<br>{}</div>",
e report
))); )));
}
Err(e) => {
return Ok(Body::html(format!(
"<div class=\"dmarc-report-error\">Failed to parse DMARC report XML: {}</div>",
e
)));
}
} }
} }
} }
@ -752,6 +755,10 @@ fn extract_unhandled(m: &ParsedMail) -> Result<Body, ServerError> {
})) }))
} }
fn is_dmarc_report_filename(name: &str) -> bool {
(name.ends_with(".xml.gz") || name.ends_with(".xml")) && name.contains("!")
}
// multipart/alternative defines multiple representations of the same message, and clients should // multipart/alternative defines multiple representations of the same message, and clients should
// show the fanciest they can display. For this program, the priority is text/html, text/plain, // show the fanciest they can display. For this program, the priority is text/html, text/plain,
// then give up. // then give up.