Skip to main content

web_analyzer/
domain_dns.rs

1use serde::{Deserialize, Serialize};
2use std::time::Instant;
3use tokio::process::Command;
4
5// ── Structs ─────────────────────────────────────────────────────────────────
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct DnsRecords {
9    pub a: Vec<String>,
10    pub aaaa: Vec<String>,
11    pub mx: Vec<String>,
12    pub ns: Vec<String>,
13    pub soa: Vec<String>,
14    pub txt: Vec<String>,
15    pub cname: Vec<String>,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct DomainDnsResult {
20    pub timestamp: String,
21    pub domain: String,
22    pub records: DnsRecords,
23    pub response_time_ms: u128,
24}
25
26// ── DNS resolution via dig ──────────────────────────────────────────────────
27
28async fn resolve_record(domain: &str, record_type: &str) -> Vec<String> {
29    if let Ok(output) = Command::new("dig")
30        .arg("+short")
31        .arg(record_type)
32        .arg(domain)
33        .output()
34        .await
35    {
36        if let Ok(text) = String::from_utf8(output.stdout) {
37            return text
38                .lines()
39                .filter_map(|s| {
40                    let s = s.trim();
41                    if !s.is_empty() && !s.starts_with(';') {
42                        Some(s.to_string())
43                    } else {
44                        None
45                    }
46                })
47                .collect();
48        }
49    }
50    vec![]
51}
52
53// ── Main function ───────────────────────────────────────────────────────────
54
55pub async fn get_dns_records(
56    domain: &str,
57    progress_tx: Option<tokio::sync::mpsc::Sender<crate::ScanProgress>>,
58) -> Result<DomainDnsResult, Box<dyn std::error::Error + Send + Sync>> {
59    let start_time = Instant::now();
60
61    if let Some(t) = &progress_tx { let _ = t.send(crate::ScanProgress { module: "Domain DNS".into(), percentage: 5.0, message: "Starting parallel DNS resolution...".into(), status: "Info".into() }).await; }
62
63    // Run all 7 record types concurrently
64    let (a, aaaa, mx, ns, soa, txt, cname) = tokio::join!(
65        async {
66            let res = resolve_record(domain, "A").await;
67            if let Some(t) = &progress_tx { let _ = t.send(crate::ScanProgress { module: "Domain DNS".into(), percentage: 15.0, message: "Resolved A records".into(), status: "Success".into() }).await; }
68            res
69        },
70        async {
71            let res = resolve_record(domain, "AAAA").await;
72            if let Some(t) = &progress_tx { let _ = t.send(crate::ScanProgress { module: "Domain DNS".into(), percentage: 30.0, message: "Resolved AAAA records".into(), status: "Success".into() }).await; }
73            res
74        },
75        async {
76            let res = resolve_record(domain, "MX").await;
77            if let Some(t) = &progress_tx { let _ = t.send(crate::ScanProgress { module: "Domain DNS".into(), percentage: 45.0, message: "Resolved MX records".into(), status: "Success".into() }).await; }
78            res
79        },
80        async {
81            let res = resolve_record(domain, "NS").await;
82            if let Some(t) = &progress_tx { let _ = t.send(crate::ScanProgress { module: "Domain DNS".into(), percentage: 60.0, message: "Resolved NS records".into(), status: "Success".into() }).await; }
83            res
84        },
85        async {
86            let res = resolve_record(domain, "SOA").await;
87            if let Some(t) = &progress_tx { let _ = t.send(crate::ScanProgress { module: "Domain DNS".into(), percentage: 75.0, message: "Resolved SOA records".into(), status: "Success".into() }).await; }
88            res
89        },
90        async {
91            let res = resolve_record(domain, "TXT").await;
92            if let Some(t) = &progress_tx { let _ = t.send(crate::ScanProgress { module: "Domain DNS".into(), percentage: 90.0, message: "Resolved TXT records".into(), status: "Success".into() }).await; }
93            res
94        },
95        async {
96            let res = resolve_record(domain, "CNAME").await;
97            if let Some(t) = &progress_tx { let _ = t.send(crate::ScanProgress { module: "Domain DNS".into(), percentage: 100.0, message: "Resolved CNAME records".into(), status: "Success".into() }).await; }
98            res
99        },
100    );
101
102    let duration = start_time.elapsed().as_millis();
103
104    Ok(DomainDnsResult {
105        timestamp: chrono::Utc::now().to_rfc3339(),
106        domain: domain.to_string(),
107        records: DnsRecords {
108            a,
109            aaaa,
110            mx,
111            ns,
112            soa,
113            txt,
114            cname,
115        },
116        response_time_ms: duration,
117    })
118}