use std::net::IpAddr;
use async_trait::async_trait;
use crate::error::SpfError;
#[async_trait]
pub trait SpfResolver: Send + Sync {
async fn lookup_txt(&self, domain: &str) -> Result<Vec<String>, SpfError>;
async fn lookup_a(&self, domain: &str) -> Result<Vec<IpAddr>, SpfError>;
async fn lookup_aaaa(&self, domain: &str) -> Result<Vec<IpAddr>, SpfError>;
async fn lookup_mx(&self, domain: &str) -> Result<Vec<(u16, String)>, SpfError>;
}
#[cfg(feature = "hickory")]
pub mod hickory {
use super::*;
use hickory_resolver::TokioResolver;
pub struct HickoryResolver {
inner: TokioResolver,
}
impl HickoryResolver {
pub fn new(resolver: TokioResolver) -> Self {
Self { inner: resolver }
}
}
fn is_no_records<E: std::fmt::Display>(e: &E) -> bool {
let s = e.to_string();
s.contains("no record")
|| s.contains("NXDOMAIN")
|| s.contains("no records found")
|| s.contains("NoRecordsFound")
}
#[async_trait]
impl SpfResolver for HickoryResolver {
async fn lookup_txt(&self, domain: &str) -> Result<Vec<String>, SpfError> {
use hickory_resolver::proto::rr::RData;
match self.inner.txt_lookup(domain).await {
Ok(resp) => {
let mut out = Vec::new();
for record in resp.answers() {
if let RData::TXT(txt) = &record.data {
out.push(txt.to_string());
}
}
Ok(out)
}
Err(e) if is_no_records(&e) => Ok(Vec::new()),
Err(e) => Err(SpfError::DnsTempError(e.to_string())),
}
}
async fn lookup_a(&self, domain: &str) -> Result<Vec<IpAddr>, SpfError> {
use hickory_resolver::proto::rr::RData;
match self.inner.ipv4_lookup(domain).await {
Ok(resp) => {
let mut out = Vec::new();
for record in resp.answers() {
if let RData::A(a) = &record.data {
out.push(IpAddr::V4(a.0));
}
}
Ok(out)
}
Err(e) if is_no_records(&e) => Ok(Vec::new()),
Err(e) => Err(SpfError::DnsTempError(e.to_string())),
}
}
async fn lookup_aaaa(&self, domain: &str) -> Result<Vec<IpAddr>, SpfError> {
use hickory_resolver::proto::rr::RData;
match self.inner.ipv6_lookup(domain).await {
Ok(resp) => {
let mut out = Vec::new();
for record in resp.answers() {
if let RData::AAAA(a) = &record.data {
out.push(IpAddr::V6(a.0));
}
}
Ok(out)
}
Err(e) if is_no_records(&e) => Ok(Vec::new()),
Err(e) => Err(SpfError::DnsTempError(e.to_string())),
}
}
async fn lookup_mx(&self, domain: &str) -> Result<Vec<(u16, String)>, SpfError> {
use hickory_resolver::proto::rr::RData;
match self.inner.mx_lookup(domain).await {
Ok(resp) => {
let mut out = Vec::new();
for record in resp.answers() {
if let RData::MX(mx) = &record.data {
out.push((mx.preference, mx.exchange.to_utf8()));
}
}
Ok(out)
}
Err(e) if is_no_records(&e) => Ok(Vec::new()),
Err(e) => Err(SpfError::DnsTempError(e.to_string())),
}
}
}
}