pub struct MessageAuthenticator { /* private fields */ }Expand description
Entry point for every verifier and DNS lookup in this crate.
A MessageAuthenticator wraps a DNS resolver and exposes the verifiers
(verify_dkim, verify_dkim2, verify_arc, verify_spf, verify_dmarc,
verify_iprev) together with the DNS lookup helpers
used by them. Cloning is cheap: clones share the underlying resolver.
With the default dns-hickory feature the resolver is a
hickory_resolver::TokioResolver, built with new,
new_system_conf or one of the provider presets
such as new_cloudflare_tls.
§Example
use mail_auth::{AuthenticatedMessage, DkimResult, MessageAuthenticator};
let authenticator = MessageAuthenticator::new_cloudflare_tls().unwrap();
let message = AuthenticatedMessage::parse(raw_message).unwrap();
let results = authenticator.verify_dkim(&message).await;
assert!(results.iter().all(|r| r.result() == &DkimResult::Pass));Implementations§
Source§impl MessageAuthenticator
impl MessageAuthenticator
Sourcepub fn resolver(&self) -> &TokioResolver
pub fn resolver(&self) -> &TokioResolver
Returns the underlying hickory resolver, for issuing DNS queries that this crate does not wrap.
Sourcepub fn with_max_negative_ttl(self, ttl: Duration) -> Self
pub fn with_max_negative_ttl(self, ttl: Duration) -> Self
Sets the longest time a negative answer (NXDOMAIN or NODATA) is
kept in the DNS caches.
Source§impl MessageAuthenticator
impl MessageAuthenticator
Sourcepub async fn verify_dkim<'x, C>(
&self,
params: impl Into<Parameters<'x, &'x AuthenticatedMessage<'x>, C>>,
) -> Vec<DkimOutput<'x>>where
C: DnsCache + 'x,
pub async fn verify_dkim<'x, C>(
&self,
params: impl Into<Parameters<'x, &'x AuthenticatedMessage<'x>, C>>,
) -> Vec<DkimOutput<'x>>where
C: DnsCache + 'x,
Verifies every DKIM-Signature header of a parsed RFC 5322 message
(RFC 6376, Section 6).
params is an AuthenticatedMessage, or Parameters wrapping one
together with a DNS cache. Returns one DkimOutput per signature:
first a neutral output for each header rejected with a DKIM error
(see DkimOutput), then one per parsed signature in header order. A
message without signatures yields an empty vector; this function never
fails.
For each well formed, unexpired signature whose body hash matches, it
looks up the key record, a TXT record at <s>._domainkey.<d>
(DomainKey). When the signature verifies and carries atps= equal
to the domain of a From address, it also looks up the RFC 6541 ATPS
record at <d or base32(hash(d))>._atps.<atps> (AtpsRecord); the
result of that lookup becomes the result of the signature and
DkimOutput::is_atps is set. Finally, for every signature with r=y
that did not pass, it looks up the RFC 6651 report record at
_report._domainkey.<d> (DkimReportRecord) to fill
DkimOutput::report_address.
Result variants:
DkimResult::Pass: the signature verified.DkimResult::Neutral: the header is malformed or unsupported, was ignored because ofl=in strict mode, has expired, or its body hash does not match.DkimResult::Fail: the cryptographic check failed, or thei=domain does not matchd=(honouringt=s).DkimResult::PermError: the key record is missing, invalid, revoked or of an unsupported type (also for a missing ATPS record).DkimResult::TempError: a DNS resolver error occurred.DkimResult::None: never returned by this function.
§Example
use mail_auth::{AuthenticatedMessage, DkimResult, MessageAuthenticator};
let authenticator = MessageAuthenticator::new_system_conf()?;
let message = AuthenticatedMessage::parse(raw_message).ok_or("invalid message")?;
let results = authenticator.verify_dkim(&message).await;
let all_pass = results.iter().all(|output| output.result() == &DkimResult::Pass);Source§impl MessageAuthenticator
impl MessageAuthenticator
Sourcepub async fn verify_dkim2_dsn<'x, R, S, C, A, RT>(
&self,
params: impl Into<Parameters<'x, &'x Dkim2Dsn<'x, R, S>, C>>,
envelope: Envelope<A, RT>,
) -> Result<Dkim2DsnOutput, Dkim2DsnFailure>where
R: AsRef<AuthenticatedMessage<'x>> + 'x,
S: AsRef<AuthenticatedMessage<'x>> + 'x,
C: DnsCache + 'x,
A: AsRef<str> + Clone,
RT: IntoIterator<Item: AsRef<str>> + Clone,
pub async fn verify_dkim2_dsn<'x, R, S, C, A, RT>(
&self,
params: impl Into<Parameters<'x, &'x Dkim2Dsn<'x, R, S>, C>>,
envelope: Envelope<A, RT>,
) -> Result<Dkim2DsnOutput, Dkim2DsnFailure>where
R: AsRef<AuthenticatedMessage<'x>> + 'x,
S: AsRef<AuthenticatedMessage<'x>> + 'x,
C: DnsCache + 'x,
A: AsRef<str> + Clone,
RT: IntoIterator<Item: AsRef<str>> + Clone,
Authenticates an inbound DKIM2-signed delivery status notification (§16.4).
params wraps the Dkim2Dsn, optionally with a DNS cache (see
Parameters). envelope is the SMTP envelope the DSN was received
with, usually with a null MAIL FROM (<>).
Both messages must be DKIM2 signed. A DSN is expected to carry a
single DKIM2-Signature and Message-Instance (§16.2), but a longer
chain is accepted when it verifies. The DSN is verified against
envelope as with
verify_dkim2. The returned
message is verified against the envelope recorded in its most recent
signature; its body hashes are checked only when
Dkim2Dsn::returned_full is true. Finally the DSN must be aligned
with the returned message:
- An
rt=recipient domain of the returned message’s most recent signature relaxed-matches the signing domain of the DSN’si=1signature. - The
d=of that signature relaxed-matches the domain of anenvelopeRCPT TOaddress, which shows that the receiving system made it.
Each chain costs one DNS TXT lookup per s= entry of each signature,
for the public key at <selector>._domainkey.<d>.
On success both fields of Dkim2DsnOutput are
Dkim2Result::Pass.
§Errors
Returns Dkim2DsnFailure::DsnNotSigned or
Dkim2DsnFailure::ReturnedNotSigned if a message has no
DKIM2-Signature header field, Dkim2DsnFailure::DsnChainFailed or
Dkim2DsnFailure::ReturnedChainFailed if a chain does not pass
(including temporary DNS failures), and
Dkim2DsnFailure::NotAligned if the alignment checks fail.
§Example
use mail_auth::{
MessageAuthenticator, Parameters,
dkim2::{Dkim2Dsn, Envelope},
};
let authenticator = MessageAuthenticator::new_cloudflare_tls().unwrap();
let dsn = Dkim2Dsn::parse(raw_dsn).unwrap();
let envelope = Envelope::new("<>", ["sender@example.com"]);
match authenticator
.verify_dkim2_dsn(Parameters::new(&dsn), envelope)
.await
{
Ok(_) => println!("authentic DSN"),
Err(failure) => println!("DSN rejected: {failure}"),
}Source§impl MessageAuthenticator
impl MessageAuthenticator
Sourcepub async fn verify_dkim2<'x, C, A, R>(
&self,
params: impl Into<Parameters<'x, &'x AuthenticatedMessage<'x>, C>>,
envelope: Envelope<A, R>,
) -> Dkim2Output<'x>
pub async fn verify_dkim2<'x, C, A, R>( &self, params: impl Into<Parameters<'x, &'x AuthenticatedMessage<'x>, C>>, envelope: Envelope<A, R>, ) -> Dkim2Output<'x>
Verifies the DKIM2 signature chain of an RFC 5322 message: whether it is a Fully Verified DKIM2 Message (§13 and §14).
params wraps the parsed message, optionally with a DNS cache (see
Parameters); a plain &AuthenticatedMessage also works.
envelope is the SMTP envelope the message was received with. It
must match the most recent signature: MAIL FROM equals its mf=
and every RCPT TO appears in its rt=.
The verifier checks, in order, the syntax and numbering of every
DKIM2-Signature and Message-Instance (§14.2), the signature
timestamps (at most 14 days old and at most one day in the future,
§14.3), the envelope binding, the mf=/d= match of every signature
and the chain of custody (§14.4), the signatures of every
DKIM2-Signature (§14.5 and §14.6), the hashes of every instance,
applying recipes to recreate earlier revisions (§14.7), and that every
From header field, current or recreated, is Authentic (§11 and
§14.8). Signatures and hashes are combined over their , and |
groups: a group passes when all its entries pass, and the tag passes
when any group does. It stops once the result is known. Domains are
compared as A-labels (§18).
For each s= entry it evaluates it performs one DNS TXT lookup for
the public key at <selector>._domainkey.<d>, served from the cache
when one is given. Entries of a group that already failed and groups
after one that passed are not looked up.
The returned Dkim2Output holds one of:
Dkim2Result::Pass: the whole chain verified. The output lists every hop inDkim2Output::chain.Dkim2Result::Fail: verification completed but the message is not a Fully Verified DKIM2 Message: a signature, header hash or body hash did not match, a timestamp is out of range, the envelope or chain of custody does not match, aFromheader field is not Authentic, or a null body recipe prevents checking earlier body hashes (§13).Dkim2Result::PermError: a DKIM2 header field is malformed, incomplete or missing, the numbering has a gap, the chain is longer than 50 hops, a signature or hash algorithm is not implemented, or a public key is missing, malformed, duplicated, revoked or of the wrong type, and no group passed.Dkim2Result::TempError: a public key lookup failed with a temporary DNS error and no group passed.Dkim2Result::None: the message has noDKIM2-Signatureand noMessage-Instanceheader field.
§Example
use mail_auth::{AuthenticatedMessage, Dkim2Result, MessageAuthenticator, dkim2::Envelope};
let authenticator = MessageAuthenticator::new_cloudflare_tls().unwrap();
let message = AuthenticatedMessage::parse(raw_message).unwrap();
let envelope = Envelope::new("sender@example.com", ["recipient@example.org"]);
let output = authenticator.verify_dkim2(&message, envelope).await;
match output.result() {
Dkim2Result::Pass => {
for link in output.chain() {
println!("hop {} signed by {}", link.signature.i, link.signature.d);
}
}
Dkim2Result::None => println!("not DKIM2 signed"),
_ => println!("DKIM2 failed: {:?}", output.error()),
}Sourcepub async fn verify_dkim2_provisionally<'x, C, A, R>(
&self,
params: impl Into<Parameters<'x, &'x AuthenticatedMessage<'x>, C>>,
envelope: Envelope<A, R>,
) -> Dkim2Output<'x>
pub async fn verify_dkim2_provisionally<'x, C, A, R>( &self, params: impl Into<Parameters<'x, &'x AuthenticatedMessage<'x>, C>>, envelope: Envelope<A, R>, ) -> Dkim2Output<'x>
Checks whether a message is Provisionally Verified (§13.2): the initial assessment a receiver may make before accepting delivery, and the precondition for later sending a DSN about the message.
Performs the header field validity, timestamp and envelope checks of
verify_dkim2, then verifies only the most
recent signature and the hashes of the most recent Message-Instance.
It does not check the chain of custody of earlier hops, earlier
signatures or hashes, or whether From header fields are Authentic.
A most recent signature with nd= is a Dkim2Result::Fail (§14.4).
A passing result is reported with Verification::Provisional, its
Dkim2Output::chain holds only the most recent signature, and it is
neither a proof of validity for Dkim2Signer
nor an authenticated identity for DMARC or Authentication-Results.
Source§impl MessageAuthenticator
impl MessageAuthenticator
Sourcepub async fn verify_dmarc<'x, C>(
&self,
params: impl Into<Parameters<'x, DmarcParameters<'x>, C>>,
) -> DmarcOutputwhere
C: DnsCache + 'x,
pub async fn verify_dmarc<'x, C>(
&self,
params: impl Into<Parameters<'x, DmarcParameters<'x>, C>>,
) -> DmarcOutputwhere
C: DnsCache + 'x,
Evaluates the DMARC policy of a message (RFC 9989, Section 4.10).
The Author Domain is taken from the RFC5322.From header. Messages
without a From domain, or whose From header lists several different
domains, are exempt and yield an empty output (result None).
DNS lookups performed:
- A DNS Tree Walk of TXT queries for
_dmarc.<name>, from the Author Domain up to the top-level domain, capped at eight queries. A record withpsd=yorpsd=nstops the walk. The Author Domain’s own record applies if present, otherwise the Organizational Domain’s, otherwise the shortest one found. - An A query for the Author Domain when a record found above it has
npdifferent fromsp, to tell a non-existent subdomain (NXDOMAIN, RFC 8020) from an existing one. - Further Tree Walks to find the Organizational Domain of SPF and DKIM identifiers under relaxed alignment.
A record without a valid p tag is treated as p=none when it has a
valid rua tag; otherwise DMARC does not apply.
The returned DmarcOutput carries per-mechanism results
(DmarcOutput::spf_result, DmarcOutput::dkim_result) and the
overall DmarcOutput::result:
DmarcResult::Pass: SPF or DKIM produced an aligned pass.DmarcResult::Fail: a record applies and no mechanism produced an aligned pass.DmarcResult::TempError: a transient DNS error prevented the evaluation.DmarcResult::PermError: the DMARC record could not be retrieved.DmarcResult::None: DMARC does not apply to the message.
§Example
use mail_auth::{
AuthenticatedMessage, DmarcResult, MessageAuthenticator, dmarc::Policy,
dmarc::verify::DmarcParameters, spf::verify::SpfParameters,
};
let message = AuthenticatedMessage::parse(raw_message).unwrap();
let dkim_output = authenticator.verify_dkim(&message).await;
let spf_output = authenticator
.verify_spf(SpfParameters::mail_from(
"192.0.2.1".parse().unwrap(),
"mx.example.org",
"mx.my-host.org",
"sender@example.org",
))
.await;
let dmarc_output = authenticator
.verify_dmarc(DmarcParameters::new(
&message,
&dkim_output,
"example.org",
&spf_output,
))
.await;
if matches!(dmarc_output.result(), DmarcResult::Fail(_))
&& dmarc_output.policy() == Policy::Reject
{
println!("reject message from {}", dmarc_output.domain());
}Returns the reporting addresses that are authorized to receive DMARC reports for a policy domain (RFC 9989).
Takes a (policy_domain, addresses) pair, optionally wrapped in
Parameters to supply a DNS cache. addresses is usually
DmarcRecord::rua or DmarcRecord::ruf, but any AsRef<str>
holding an email address works. An address is authorized when its
domain is the policy domain or a subdomain of it (internal), or, for
an external destination, when a v=DMARC1 TXT record exists at
<policy_domain>._report._dmarc.<destination_domain>. One TXT lookup
is performed per external address. Addresses are returned in input
order.
§Errors
Returns Error::Dns with DnsError::Resolver when a lookup fails
with a transient resolver error. A missing or invalid authorization
record is not an error: the address is left out.
§Example
use mail_auth::{DmarcOutput, MessageAuthenticator};
if let Some(record) = output.record() {
let authorized = authenticator
.authorized_report_addresses((output.domain(), record.rua()))
.await?;
for uri in authorized {
println!("send aggregate report to {}", uri.uri());
}
}Source§impl MessageAuthenticator
impl MessageAuthenticator
Sourcepub fn new_cloudflare_tls() -> Result<Self, NetError>
pub fn new_cloudflare_tls() -> Result<Self, NetError>
Sourcepub fn new_cloudflare() -> Result<Self, NetError>
pub fn new_cloudflare() -> Result<Self, NetError>
Sourcepub fn new_google() -> Result<Self, NetError>
pub fn new_google() -> Result<Self, NetError>
Sourcepub fn new_quad9_tls() -> Result<Self, NetError>
pub fn new_quad9_tls() -> Result<Self, NetError>
Sourcepub fn new_system_conf() -> Result<Self, NetError>
pub fn new_system_conf() -> Result<Self, NetError>
Sourcepub fn new(
config: ResolverConfig,
options: ResolverOpts,
) -> Result<Self, NetError>
pub fn new( config: ResolverConfig, options: ResolverOpts, ) -> Result<Self, NetError>
Source§impl MessageAuthenticator
impl MessageAuthenticator
Sourcepub async fn txt_raw_lookup(&self, key: impl ToFqdn) -> Result<Vec<u8>>
pub async fn txt_raw_lookup(&self, key: impl ToFqdn) -> Result<Vec<u8>>
Queries the TXT records of key and returns their contents
concatenated into one byte string, without parsing or caching.
§Errors
Returns Error::Dns with DnsError::RecordNotFound
when the name has no records of this type, DnsError::Resolver
when the query fails, or Error::Parse when the name is not a valid
DNS name.
Sourcepub async fn txt_lookup<T: TxtRecordParser + Into<TxtRecord> + UnwrapTxtRecord>(
&self,
key: impl ToFqdn,
cache: Option<&impl ResolverCache<Box<str>, TxtRecord>>,
) -> Result<Arc<T>>
pub async fn txt_lookup<T: TxtRecordParser + Into<TxtRecord> + UnwrapTxtRecord>( &self, key: impl ToFqdn, cache: Option<&impl ResolverCache<Box<str>, TxtRecord>>, ) -> Result<Arc<T>>
Queries the TXT records of key and returns the first one that parses
as T (for example SpfRecord,
DmarcRecord or
TlsRptRecord).
When cache is set, a cached entry is returned without querying, and
the parse outcome (the record or the parse error) is stored after a
query. A negative answer that carries an SOA record is stored as
TxtRecord::Error until its negative TTL expires.
§Errors
Returns Error::Dns with DnsError::RecordNotFound
when the name has no records of this type, DnsError::Resolver
when the query fails, or Error::Parse when the name is not a valid
DNS name.
When no record parses as T, returns the error of the last parse
attempt (usually DnsError::InvalidRecordType),
or InvalidRecordType when the answer holds no TXT data.
Sourcepub async fn mx_lookup(
&self,
key: impl ToFqdn,
cache: Option<&impl ResolverCache<Box<str>, Result<RecordSet<Mx>, Negative>>>,
) -> Result<RecordSet<Mx>>
pub async fn mx_lookup( &self, key: impl ToFqdn, cache: Option<&impl ResolverCache<Box<str>, Result<RecordSet<Mx>, Negative>>>, ) -> Result<RecordSet<Mx>>
Queries the MX records of key (RFC 5321 Section 5.1).
Records are grouped by preference into Mx values sorted by
ascending preference. When cache is set, it is consulted before and
updated after the query; a negative answer that carries an SOA record
is stored as Err(Negative) until its negative TTL expires.
§Errors
Returns Error::Dns with DnsError::RecordNotFound
when the name has no records of this type, DnsError::Resolver
when the query fails, or Error::Parse when the name is not a valid
DNS name.
Sourcepub async fn ipv4_lookup(
&self,
key: impl ToFqdn,
cache: Option<&impl ResolverCache<Box<str>, Result<RecordSet<Ipv4Addr>, Negative>>>,
) -> Result<RecordSet<Ipv4Addr>>
pub async fn ipv4_lookup( &self, key: impl ToFqdn, cache: Option<&impl ResolverCache<Box<str>, Result<RecordSet<Ipv4Addr>, Negative>>>, ) -> Result<RecordSet<Ipv4Addr>>
Queries the A records of key. When cache is set, it is consulted
before and updated after the query; a negative answer that carries an
SOA record is stored as Err(Negative) until its negative TTL
expires.
§Errors
Returns Error::Dns with DnsError::RecordNotFound
when the name has no records of this type, DnsError::Resolver
when the query fails, or Error::Parse when the name is not a valid
DNS name.
Sourcepub async fn ipv4_lookup_raw(
&self,
key: &str,
) -> Result<DnsEntry<Arc<[Ipv4Addr]>>>
pub async fn ipv4_lookup_raw( &self, key: &str, ) -> Result<DnsEntry<Arc<[Ipv4Addr]>>>
Queries the A records of key without caching. key is queried as
given, without conversion to a fully qualified name. Returns the
addresses with their expiry time.
§Errors
Returns Error::Dns with DnsError::RecordNotFound
when the name has no records of this type, DnsError::Resolver
when the query fails, or Error::Parse when the name is not a valid
DNS name.
Sourcepub async fn ipv6_lookup(
&self,
key: impl ToFqdn,
cache: Option<&impl ResolverCache<Box<str>, Result<RecordSet<Ipv6Addr>, Negative>>>,
) -> Result<RecordSet<Ipv6Addr>>
pub async fn ipv6_lookup( &self, key: impl ToFqdn, cache: Option<&impl ResolverCache<Box<str>, Result<RecordSet<Ipv6Addr>, Negative>>>, ) -> Result<RecordSet<Ipv6Addr>>
Queries the AAAA records of key. When cache is set, it is consulted
before and updated after the query; a negative answer that carries an
SOA record is stored as Err(Negative) until its negative TTL
expires.
§Errors
Returns Error::Dns with DnsError::RecordNotFound
when the name has no records of this type, DnsError::Resolver
when the query fails, or Error::Parse when the name is not a valid
DNS name.
Sourcepub async fn ipv6_lookup_raw(
&self,
key: &str,
) -> Result<DnsEntry<Arc<[Ipv6Addr]>>>
pub async fn ipv6_lookup_raw( &self, key: &str, ) -> Result<DnsEntry<Arc<[Ipv6Addr]>>>
Queries the AAAA records of key without caching. key is queried as
given, without conversion to a fully qualified name. Returns the
addresses with their expiry time.
§Errors
Returns Error::Dns with DnsError::RecordNotFound
when the name has no records of this type, DnsError::Resolver
when the query fails, or Error::Parse when the name is not a valid
DNS name.
Sourcepub async fn ip_lookup(
&self,
key: &str,
strategy: IpLookupStrategy,
max_results: usize,
cache_ipv4: Option<&impl ResolverCache<Box<str>, Result<RecordSet<Ipv4Addr>, Negative>>>,
cache_ipv6: Option<&impl ResolverCache<Box<str>, Result<RecordSet<Ipv6Addr>, Negative>>>,
) -> Result<Vec<IpAddr>>
pub async fn ip_lookup( &self, key: &str, strategy: IpLookupStrategy, max_results: usize, cache_ipv4: Option<&impl ResolverCache<Box<str>, Result<RecordSet<Ipv4Addr>, Negative>>>, cache_ipv6: Option<&impl ResolverCache<Box<str>, Result<RecordSet<Ipv6Addr>, Negative>>>, ) -> Result<Vec<IpAddr>>
Resolves key to at most max_results IP addresses, querying A and
AAAA records in the order given by strategy.
With a fallback strategy (IpLookupStrategy::Ipv4thenIpv6 or
IpLookupStrategy::Ipv6thenIpv4), any error from the first query
triggers the second one. The caches are consulted before and updated
after each query.
§Errors
Returns the error of the last query performed. See
ipv4_lookup for the possible errors.
Sourcepub async fn ptr_lookup(
&self,
addr: IpAddr,
cache: Option<&impl ResolverCache<IpAddr, Result<RecordSet<Box<str>>, Negative>>>,
) -> Result<RecordSet<Box<str>>>
pub async fn ptr_lookup( &self, addr: IpAddr, cache: Option<&impl ResolverCache<IpAddr, Result<RecordSet<Box<str>>, Negative>>>, ) -> Result<RecordSet<Box<str>>>
Queries the PTR records of addr (reverse DNS). Host names are
returned lowercased. When cache is set, it is consulted before and
updated after the query; a negative answer that carries an SOA record
is stored as Err(Negative) until its negative TTL expires.
§Errors
Returns Error::Dns with DnsError::RecordNotFound
when the address has no PTR record, or DnsError::Resolver
when the query fails.
Sourcepub async fn exists(
&self,
key: impl ToFqdn,
cache_ipv4: Option<&impl ResolverCache<Box<str>, Result<RecordSet<Ipv4Addr>, Negative>>>,
cache_ipv6: Option<&impl ResolverCache<Box<str>, Result<RecordSet<Ipv6Addr>, Negative>>>,
) -> Result<bool>
pub async fn exists( &self, key: impl ToFqdn, cache_ipv4: Option<&impl ResolverCache<Box<str>, Result<RecordSet<Ipv4Addr>, Negative>>>, cache_ipv6: Option<&impl ResolverCache<Box<str>, Result<RecordSet<Ipv6Addr>, Negative>>>, ) -> Result<bool>
Returns true if key has at least one A or AAAA record, as needed by
the SPF exists mechanism (RFC 7208 Section 5.7).
Returns true without querying when a cache holds an A or AAAA answer
with records, and false without querying when the caches hold a
negative or empty answer for both types. Query results are not added
to the caches.
§Errors
Returns Error::Dns with DnsError::Resolver
when a query fails, or Error::Parse when the name is not a valid
DNS name. A name without records is not an error.
Source§impl MessageAuthenticator
impl MessageAuthenticator
Sourcepub async fn verify_iprev<'x, C>(
&self,
params: impl Into<Parameters<'x, IpAddr, C>>,
) -> IprevOutputwhere
C: DnsCache + 'x,
pub async fn verify_iprev<'x, C>(
&self,
params: impl Into<Parameters<'x, IpAddr, C>>,
) -> IprevOutputwhere
C: DnsCache + 'x,
Verifies the reverse DNS of an SMTP client IP address (RFC 8601 Section 3).
params is the client IP address, either alone or wrapped in
Parameters together with a DNS cache.
The check performs these DNS lookups:
- A PTR query for the IP address.
- For each of the first two names returned, an A query (IPv4 client) or AAAA query (IPv6 client).
The result is:
IprevResult::Passas soon as a forward lookup returns the client address.IprevResult::FailwithError::NotAlignedwhen every forward lookup succeeded but none returned the client address, or when the PTR answer was empty.IprevResult::TempErrororIprevResult::PermErrorwhen the PTR lookup failed, or when no forward lookup matched and at least one of them failed. Resolver errors (DnsError::Resolver) are temporary; all other errors are permanent. The error of the last failed lookup is reported.
IprevOutput::ptr holds the PTR names whenever the PTR lookup
succeeded.
§Example
use mail_auth::{AuthenticationResults, IprevResult, MessageAuthenticator};
use std::net::IpAddr;
let authenticator = MessageAuthenticator::new_cloudflare_tls().unwrap();
let remote_ip: IpAddr = "192.0.2.10".parse().unwrap();
let iprev = authenticator.verify_iprev(remote_ip).await;
if iprev.result() == &IprevResult::Pass {
println!("PTR names: {:?}", iprev.ptr);
}
let header = AuthenticationResults::new("mx.example.org")
.with_iprev_result(&iprev, remote_ip)
.to_string();Source§impl MessageAuthenticator
impl MessageAuthenticator
Sourcepub async fn verify_spf<'x, C>(
&self,
params: impl Into<Parameters<'x, SpfParameters<'x>, C>>,
) -> SpfOutputwhere
C: DnsCache + 'x,
pub async fn verify_spf<'x, C>(
&self,
params: impl Into<Parameters<'x, SpfParameters<'x>, C>>,
) -> SpfOutputwhere
C: DnsCache + 'x,
Verifies the SPF identities of an SMTP session (RFC 7208).
With parameters built by SpfParameters::helo_and_mail_from (or
SpfParameters::new), the HELO identity is checked first. Its output
is returned when the result is fail or the sender is empty;
otherwise the MAIL FROM identity is checked and its output returned.
With parameters built by SpfParameters::helo or
SpfParameters::mail_from, a single check_host is run for that
identity.
The DNS lookups performed are those of check_host, once per
identity checked. See SpfResult for the meaning of each result.
§Example
use mail_auth::{MessageAuthenticator, SpfResult, spf::verify::SpfParameters};
let output = authenticator
.verify_spf(SpfParameters::helo_and_mail_from(
"192.0.2.1".parse().unwrap(),
"mx.example.org",
"mx.my-host.org",
"sender@example.org",
))
.await;
if output.result() == SpfResult::Fail {
println!("rejected: {:?}", output.explanation());
}Sourcepub async fn check_host<'x, C>(
&self,
params: impl Into<Parameters<'x, SpfParameters<'x>, C>>,
) -> SpfOutputwhere
C: DnsCache + 'x,
pub async fn check_host<'x, C>(
&self,
params: impl Into<Parameters<'x, SpfParameters<'x>, C>>,
) -> SpfOutputwhere
C: DnsCache + 'x,
Runs the check_host() function (RFC 7208, Section 4) for a single
identity.
Evaluates the SPF record of the parameters’ domain against the client IP. The DNS lookups performed are:
- TXT for the
v=spf1record of the domain, and of everyinclude:andredirect=target. - A or AAAA (depending on the client IP family) for the
a,mx,ptrandexistsmechanisms. - MX for the
mxmechanism. - PTR for the
ptrmechanism and thepmacro. - TXT for the
exp=explanation, only on afailresult.
The number of DNS-querying mechanisms and modifiers is limited to 10
and the whole evaluation to 20 seconds; an mx mechanism may resolve
at most 10 exchanges. Exceeding a limit yields permerror.
The returned SpfOutput holds one of:
SpfResult::None: the domain is not a valid name or publishes no SPF record.SpfResult::Pass,SpfResult::Fail,SpfResult::SoftFailorSpfResult::Neutral: the qualifier of the first matching directive, orneutralwhen none matches.SpfResult::TempError: a DNS lookup failed transiently.SpfResult::PermError: a record could not be parsed, aninclude:orredirect=target has no record, or a limit was exceeded.
§Example
use mail_auth::{MessageAuthenticator, spf::verify::SpfParameters};
let output = authenticator
.check_host(SpfParameters::new(
"192.0.2.1".parse().unwrap(),
"example.org",
"mx.example.org",
"mx.my-host.org",
"sender@example.org",
))
.await;
println!("spf={} domain={}", output.result(), output.domain());Trait Implementations§
Auto Trait Implementations§
impl !RefUnwindSafe for MessageAuthenticator
impl !UnwindSafe for MessageAuthenticator
impl Freeze for MessageAuthenticator
impl Send for MessageAuthenticator
impl Sync for MessageAuthenticator
impl Unpin for MessageAuthenticator
impl UnsafeUnpin for MessageAuthenticator
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more