use super::{DmarcRecord, FailureOptions, Policy};
use crate::{DnsError, Error};
use std::{fmt::Display, sync::Arc};
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct DmarcOutput {
pub(crate) spf_result: DmarcResult,
pub(crate) dkim_result: DmarcResult,
pub(crate) domain: String,
pub(crate) policy: Policy,
pub(crate) record: Option<Arc<DmarcRecord>>,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum DmarcResult {
Pass,
Fail(crate::Error),
TempError(crate::Error),
PermError(crate::Error),
None,
}
impl From<Error> for DmarcResult {
fn from(err: Error) -> Self {
if matches!(&err, Error::Dns(DnsError::Resolver(_))) {
DmarcResult::TempError(err)
} else {
DmarcResult::PermError(err)
}
}
}
impl Default for DmarcOutput {
fn default() -> Self {
Self {
domain: String::new(),
policy: Policy::None,
record: None,
spf_result: DmarcResult::None,
dkim_result: DmarcResult::None,
}
}
}
impl DmarcOutput {
pub(crate) fn with_domain(mut self, domain: &str) -> Self {
self.domain = domain.to_string();
self
}
pub(crate) fn with_spf_result(mut self, result: DmarcResult) -> Self {
self.spf_result = result;
self
}
pub(crate) fn with_dkim_result(mut self, result: DmarcResult) -> Self {
self.dkim_result = result;
self
}
pub(crate) fn with_record(mut self, record: Arc<DmarcRecord>) -> Self {
self.record = record.into();
self
}
pub fn domain(&self) -> &str {
&self.domain
}
pub fn into_domain(self) -> String {
self.domain
}
pub fn policy(&self) -> Policy {
self.policy
}
pub fn dkim_result(&self) -> &DmarcResult {
&self.dkim_result
}
pub fn spf_result(&self) -> &DmarcResult {
&self.spf_result
}
pub fn result(&self) -> DmarcResult {
match self.mechanism_result() {
Some(result) => result.clone(),
None if self.record.is_some() => DmarcResult::Fail(Error::NotAligned),
None => DmarcResult::None,
}
}
pub(crate) fn mechanism_result(&self) -> Option<&DmarcResult> {
[&self.spf_result, &self.dkim_result]
.into_iter()
.filter_map(|result| {
let rank = match result {
DmarcResult::Pass => 0,
DmarcResult::TempError(_) => 1,
DmarcResult::PermError(_) => 2,
DmarcResult::Fail(_) => 3,
DmarcResult::None => return None,
};
Some((rank, result))
})
.min_by_key(|(rank, _)| *rank)
.map(|(_, result)| result)
}
pub fn record(&self) -> Option<&Arc<DmarcRecord>> {
self.record.as_ref()
}
pub fn requests_reports(&self) -> bool {
self.record
.as_ref()
.is_some_and(|r| !r.rua.is_empty() || !r.ruf.is_empty())
}
pub fn failure_report(&self) -> Option<FailureOptions> {
match &self.record {
Some(record)
if !record.ruf.is_empty()
&& !matches!(self.mechanism_result(), Some(DmarcResult::TempError(_)))
&& ((self.dkim_result != DmarcResult::Pass
&& matches!(
record.fo,
FailureOptions::Any | FailureOptions::Dkim | FailureOptions::DkimSpf
))
|| (self.spf_result != DmarcResult::Pass
&& matches!(
record.fo,
FailureOptions::Any | FailureOptions::Spf | FailureOptions::DkimSpf
))
|| (self.dkim_result != DmarcResult::Pass
&& self.spf_result != DmarcResult::Pass
&& record.fo == FailureOptions::All)) =>
{
Some(record.fo.clone())
}
_ => None,
}
}
}
impl Display for DmarcResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DmarcResult::Pass => f.write_str("pass"),
DmarcResult::Fail(err) => write!(f, "fail; {err}"),
DmarcResult::TempError(err) => write!(f, "temp error; {err}"),
DmarcResult::PermError(err) => write!(f, "perm error; {err}"),
DmarcResult::None => f.write_str("none"),
}
}
}