//
// pandora: Syd's Dump Inspector & Profile Writer
// dns.rs: Reverse DNS support
//
// Copyright (c) 2023, 2024, 2025, 2026 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

#![forbid(unsafe_code)]

use std::net::IpAddr;
#[cfg(feature = "rust-dns")]
use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6, UdpSocket};
#[cfg(feature = "rust-dns")]
use std::{fs::File, io::Read, time::Duration};

use nix::errno::Errno;

#[cfg(any(feature = "rust-dns", feature = "system-dns"))]
use crate::err2no;

pub(crate) fn dial_ip(key: &str) -> Option<&str> {
    let mut it = key.split('!');
    let first = it.next()?;
    match (it.next(), it.next()) {
        (None, _) => Some(first),
        (Some(_), None) => Some(first),
        (Some(ip), Some(_)) => Some(ip),
    }
}

pub(crate) fn lookup_addr(addr: IpAddr) -> Result<String, Errno> {
    #[cfg(not(any(feature = "rust-dns", feature = "system-dns")))]
    {
        let _ = addr;
        Err(Errno::ENOSYS)
    }

    #[cfg(feature = "system-dns")]
    {
        dns_lookup::lookup_addr(&addr).map_err(err2no)
    }

    #[cfg(feature = "rust-dns")]
    {
        // Read system DNS configuration (max 4KB).
        let f = File::open("/etc/resolv.conf").map_err(err2no)?;
        let mut buf = Vec::with_capacity(4096);
        f.take(4096).read_to_end(&mut buf).map_err(err2no)?;
        let conf = resolv_conf::Config::parse(&buf).map_err(|_| Errno::EINVAL)?;

        // Pick first nameserver (IPv4/IPv6).
        let ns = conf
            .nameservers
            .iter()
            .map(|ns| -> SocketAddr {
                match ns {
                    resolv_conf::ScopedIp::V4(ipv4) => SocketAddr::V4(SocketAddrV4::new(*ipv4, 53)),
                    resolv_conf::ScopedIp::V6(ipv6, _scope) => {
                        SocketAddr::V6(SocketAddrV6::new(*ipv6, 53, 0, 0))
                    }
                }
            })
            .next()
            .ok_or(Errno::ENOENT)?;

        // Construct reverse pointer name.
        let ptr_name = match addr {
            IpAddr::V4(ip) => {
                let octets = ip.octets();
                format!(
                    "{}.{}.{}.{}.in-addr.arpa",
                    octets[3], octets[2], octets[1], octets[0]
                )
            }
            IpAddr::V6(ip) => {
                let octets = ip.octets();
                let mut s = String::with_capacity(72);
                use std::fmt::Write;
                for octet in octets.iter().rev() {
                    let _ = write!(s, "{:x}.{:x}.", octet & 0x0f, (octet >> 4) & 0x0f);
                }
                s + "ip6.arpa"
            }
        };

        // Send Query using simple-dns.
        let qname = simple_dns::Name::new(&ptr_name).map_err(|_| Errno::EINVAL)?;
        let question = simple_dns::Question::new(
            qname.clone(),
            simple_dns::QTYPE::try_from(12).expect("QTYPE::PTR"),
            simple_dns::QCLASS::try_from(1).expect("QCLASS::IN"),
            false,
        );
        let mut packet = simple_dns::Packet::new_query(0);
        packet.questions.push(question);

        // Set Recursion Desired (RD) bit (Byte 2, Bit 0).
        let mut packet_bytes = packet.build_bytes_vec().map_err(|_| Errno::EINVAL)?;
        if packet_bytes.len() > 2 {
            packet_bytes[2] |= 1;
        }

        let socket = UdpSocket::bind("0.0.0.0:0").map_err(err2no)?;
        socket
            .set_read_timeout(Some(Duration::from_secs(2)))
            .map_err(err2no)?;
        socket.connect(ns).map_err(err2no)?;
        socket.send(&packet_bytes).map_err(err2no)?;

        let mut recv_buf = [0u8; 1024];
        let amt = socket.recv(&mut recv_buf).map_err(err2no)?;

        let response = simple_dns::Packet::parse(&recv_buf[..amt]).map_err(|_| Errno::EIO)?;

        if response.answers.is_empty() {
            return Err(Errno::ENOENT);
        }

        // Extract PTR record.
        for answer in response.answers {
            match answer.rdata {
                simple_dns::rdata::RData::PTR(ptr) => return Ok(ptr.0.to_string()),
                _ => continue,
            }
        }

        Err(Errno::ENOENT)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_dial_ip_1() {
        assert_eq!(dial_ip("127.0.0.1!80"), Some("127.0.0.1"));
        assert_eq!(dial_ip("::1!443"), Some("::1"));
        assert_eq!(dial_ip("tcp!127.0.0.1!80"), Some("127.0.0.1"));
        assert_eq!(dial_ip("udp!9.9.9.9!53"), Some("9.9.9.9"));
        assert_eq!(dial_ip("tcp!::1!443"), Some("::1"));
    }

    #[test]
    fn test_dial_ip_2() {
        assert_eq!(dial_ip("127.0.0.1"), Some("127.0.0.1"));
        assert_eq!(dial_ip("::1"), Some("::1"));
        assert_eq!(dial_ip("10.0.0.0/8"), Some("10.0.0.0/8"));
    }

    #[test]
    fn test_dial_ip_3() {
        assert_eq!(dial_ip("tcp!10.0.0.0/8!22,80,443"), Some("10.0.0.0/8"));
        assert_eq!(dial_ip("net6!::1!1024-65535"), Some("::1"));
        assert_eq!(dial_ip("udp4!0.0.0.0/0!*"), Some("0.0.0.0/0"));
    }

    #[test]
    fn test_dial_ip_4() {
        assert_eq!(dial_ip(""), Some(""));
        assert_eq!(dial_ip("tcp!"), Some("tcp"));
        assert_eq!(dial_ip("!"), Some(""));
    }

    #[test]
    fn test_dial_ip_5() {
        assert_eq!(dial_ip("icmp!127.0.0.1!0"), Some("127.0.0.1"));
        assert_eq!(dial_ip("icmp4!127.0.0.1!0"), Some("127.0.0.1"));
        assert_eq!(dial_ip("icmp6!::1!0"), Some("::1"));
    }
}
