use serde::{Deserialize, Serialize};
use crate::error::Result;
pub async fn check_arc(req: &ArcCheckRequest) -> Result<ArcCheckResult> {
let domain = &req.domain;
let mut arc_seals = Vec::new();
let mut arc_aars = Vec::new();
for i in 1..=5 {
let seal_domain = format!("_arc{}.{}", i, domain);
let aar_domain = format!("_dmarc.{}", domain);
let dns_req = crate::api::DnsCheckRequest {
domain: seal_domain.clone(),
record_types: vec!["TXT".to_string()],
timeout_secs: req.timeout_secs,
..Default::default()
};
if let Ok(results) = crate::api::check_dns(&dns_req).await {
for result in results {
for record in &result.answers {
if let crate::api::RecordData::Txt(txt_data) = &record.data {
let txt_value = txt_data.join("");
if txt_value.contains("v=ARC1") {
arc_seals.push(ArcRecord {
index: i,
record_type: "seal".to_string(),
value: Some(txt_value),
valid: true,
});
}
}
}
}
}
}
let arc_present = !arc_seals.is_empty();
let arc_status = if arc_present {
"present".to_string()
} else {
"not-configured".to_string()
};
Ok(ArcCheckResult {
domain: domain.clone(),
arc_present,
arc_status,
seals: arc_seals,
aars: arc_aars,
note: Some(
"ARC validation requires email message headers. Domain-level check only \
verifies DNS record presence, not actual email chain authentication.".to_string()
),
error: None,
})
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArcCheckRequest {
pub domain: String,
#[serde(default = "default_timeout")]
pub timeout_secs: u64,
}
fn default_timeout() -> u64 {
10
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArcCheckResult {
pub domain: String,
pub arc_present: bool,
pub arc_status: String,
pub seals: Vec<ArcRecord>,
pub aars: Vec<ArcRecord>,
pub note: Option<String>,
pub error: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArcRecord {
pub index: usize,
pub record_type: String, pub value: Option<String>,
pub valid: bool,
}