use super::{Flag, MessageInstance, Signature};
use crate::{AuthenticatedMessage, DnsError, Error, headers::Header};
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Dkim2Result {
Pass,
Fail(crate::Error),
PermError(crate::Error),
TempError(crate::Error),
None,
}
impl From<Error> for Dkim2Result {
fn from(err: Error) -> Self {
if matches!(&err, Error::Dns(DnsError::Resolver(_))) {
Dkim2Result::TempError(err)
} else {
Dkim2Result::PermError(err)
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
pub enum Verification {
#[default]
Full,
Provisional,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Dkim2Output<'x> {
pub(crate) result: Dkim2Result,
pub(crate) chain: Vec<ChainLink<'x>>,
pub(crate) verification: Verification,
pub(crate) raw_message: &'x [u8],
pub(crate) signature_fields: &'x [Header<'x, Signature>],
pub(crate) instance_fields: &'x [Header<'x, MessageInstance>],
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ChainLink<'x> {
pub signature: &'x Signature,
pub instance: Option<&'x MessageInstance>,
pub result: Dkim2Result,
pub custody_ok: bool,
}
impl<'x> Dkim2Output<'x> {
pub fn result(&self) -> &Dkim2Result {
&self.result
}
pub fn verification(&self) -> Verification {
self.verification
}
pub fn chain(&self) -> &[ChainLink<'x>] {
&self.chain
}
pub fn error(&self) -> Option<&crate::Error> {
match &self.result {
Dkim2Result::Fail(err) | Dkim2Result::PermError(err) | Dkim2Result::TempError(err) => {
Some(err)
}
Dkim2Result::Pass | Dkim2Result::None => None,
}
}
pub fn feedback_requested(&self) -> bool {
self.chain
.iter()
.any(|link| link.signature.flags.contains(&Flag::Feedback))
}
pub fn feedback_domains(&self) -> impl Iterator<Item = &str> {
self.chain
.iter()
.filter(|link| link.signature.flags.contains(&Flag::Feedback))
.map(|link| link.signature.d.as_str())
}
pub fn feedback_relay(&self) -> Option<&str> {
self.chain
.iter()
.filter(|link| link.signature.flags.contains(&Flag::FeedHere))
.max_by_key(|link| link.signature.i)
.map(|link| link.signature.d.as_str())
}
pub(crate) fn is_full_pass(&self) -> bool {
matches!(self.result, Dkim2Result::Pass) && self.verification == Verification::Full
}
pub(crate) fn is_proof_for(&self, message: &AuthenticatedMessage<'_>) -> bool {
self.is_full_pass()
&& !message.has_dkim2_errors
&& same_values(self.signature_fields, &message.dkim2_signatures)
&& same_values(self.instance_fields, &message.dkim2_instances)
}
pub(crate) fn covers(&self, raw_message: &[u8]) -> bool {
!self.raw_message.is_empty() && std::ptr::eq(self.raw_message, raw_message)
}
}
fn same_values<T>(proof: &[Header<'_, T>], message: &[Header<'_, T>]) -> bool {
proof.len() == message.len()
&& proof
.iter()
.zip(message)
.all(|(proof, message)| proof.value == message.value)
}
impl From<Dkim2Result> for Dkim2Output<'_> {
fn from(result: Dkim2Result) -> Self {
Dkim2Output {
result,
chain: Vec::new(),
verification: Verification::Full,
raw_message: &[],
signature_fields: &[],
instance_fields: &[],
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::dkim2::{Flag, Signature};
fn signed(i: u32, domain: &str, flags: Vec<Flag>) -> Signature {
Signature {
i,
d: domain.to_string(),
flags,
..Default::default()
}
}
#[test]
fn feedback_accessors() {
let sig1 = signed(1, "a.example", vec![Flag::Feedback]);
let sig2 = signed(2, "b.example", vec![Flag::Feedback, Flag::FeedHere]);
let link = |signature| ChainLink {
signature,
instance: None,
result: Dkim2Result::Pass,
custody_ok: true,
};
let output = Dkim2Output {
chain: vec![link(&sig1), link(&sig2)],
..Dkim2Output::from(Dkim2Result::Pass)
};
assert!(output.feedback_requested());
assert_eq!(
output.feedback_domains().collect::<Vec<_>>(),
vec!["a.example", "b.example"]
);
assert_eq!(output.feedback_relay(), Some("b.example"));
let none = Dkim2Output::from(Dkim2Result::Pass);
assert!(!none.feedback_requested());
assert!(none.feedback_domains().next().is_none());
assert_eq!(none.feedback_relay(), None);
}
}