#ifndef __DMARCChecker_h__ #define __DMARCChecker_h__ #include "DKIMVerifier.h" #include "SPFChecker.h" #include #include namespace mail { /// /// DMARCChecker implements RFC 7489: a message is DMARC-authenticated /// if EITHER SPF or DKIM passed AND is "aligned" with the RFC 5322 /// From: header's domain (the identity a human actually sees) - SPF /// alone checks the envelope sender, DKIM alone checks whatever /// domain signed it, and neither by itself stops a message whose /// From: header claims a domain that had nothing to do with either /// check. Requires both SPFChecker's result and DKIMVerifier's /// results as input, since DMARC is defined entirely in terms of /// those two other mechanisms' outcomes plus the alignment check. /// /// Not implemented: the RFC 7489 Public Suffix List-based /// organizational-domain derivation for relaxed alignment - the /// PSL is a several-thousand-entry external data file this codebase /// has no copy of and no fetch mechanism for. Approximated instead /// by comparing the last two dot-separated labels (e.g. treating /// "mail.example.com" and "example.com" as aligned via /// "example.com"), which is correct for ordinary domains but wrong /// for registrations directly under a multi-label public suffix /// (e.g. "example.co.uk" and "other.co.uk" would incorrectly be /// treated as aligned via "co.uk"). Exact (strict) alignment, /// requested via adkim=s/aspf=s, is unaffected by this and is exact /// either way. /// class DMARCChecker { public: enum class Result { Pass, Fail, None }; struct Policy { bool found = false; std::string policy = "none"; // p= std::string subdomainPolicy; // sp= (defaults to policy if absent) bool strictDKIM = false; // adkim=s (default r, relaxed) bool strictSPF = false; // aspf=s (default r, relaxed) int percent = 100; // pct= }; /// /// fromDomain: the RFC 5322 From: header's domain (what a human /// reading the message actually sees as the sender). /// spfDomain/spfResult: the identity SPF was actually evaluated /// against (RFC 7208 2.4 - the MAIL FROM domain, or HELO's for a /// null reverse-path) and its result. /// dkimResults: every DKIM-Signature DKIMVerifier found on this /// message, verified or not. /// appliedPolicy is filled in with the policy that was actually /// looked up (found==false if none was published at all - the /// domain isn't participating in DMARC, so this always resolves /// to Result::None regardless of alignment). /// static Result evaluate(const std::string &fromDomain, const std::string &spfDomain, SPFChecker::Result spfResult, const std::vector &dkimResults, Policy &appliedPolicy); static std::string resultName(Result result); /// /// A DMARC-aware Authentication-Results header (RFC 8601) - /// summarizes spf=/dkim=/dmarc= together, the modern convention /// downstream filtering actually looks for (Received-SPF alone, /// which __SMTP_MAIL.cpp already adds, predates DMARC and doesn't /// record dkim/dmarc results at all). /// static std::string authenticationResultsHeader(SPFChecker::Result spfResult, const std::string &spfDomain, const std::vector &dkimResults, Result dmarcResult, const std::string &fromDomain, const std::string &receivingHost); private: static bool lookupPolicy(const std::string &domain, Policy &policyOut); static std::string organizationalDomain(const std::string &domain); static bool aligned(const std::string &identityDomain, const std::string &fromDomain, bool strict); }; } #endif