use std::io::{Read, Write};
use std::net::TcpStream;
use std::time::Duration;
use dns_lookup;
pub fn lookup(domain: &str) -> Result<String, String> {
let tld = domain.split('.').last().unwrap_or("com");
let whois_server = match tld {
"com" | "net" => "whois.verisign-grs.com",
"org" => "whois.pir.org",
"io" => "whois.nic.io",
"co" => "whois.nic.co",
"uk" | "co.uk" | "org.uk" => "whois.nic.uk",
"de" => "whois.denic.de",
"ru" => "whois.tcinet.ru",
"info" => "whois.afilias.net",
"biz" => "whois.nic.biz",
_ => return fallback_lookup(domain),
};
let response = query_server(whois_server, domain)?;
Ok(response)
}
fn query_server(server: &str, domain: &str) -> Result<String, String> {
let resolved = dns_lookup::lookup_host(server)
.map_err(|e| format!("DNS resolution failed for {}: {}", server, e))?;
let addr = resolved
.iter()
.next()
.ok_or_else(|| format!("No addresses found for {}", server))?;
let sock_addr = std::net::SocketAddr::new(*addr, 43);
let mut stream = TcpStream::connect_timeout(
&sock_addr,
Duration::from_secs(10),
)
.map_err(|e| format!("Failed to connect to {}: {}", server, e))?;
stream
.set_read_timeout(Some(Duration::from_secs(10)))
.map_err(|e| format!("Failed to set timeout: {}", e))?;
stream
.set_write_timeout(Some(Duration::from_secs(10)))
.map_err(|e| format!("Failed to set timeout: {}", e))?;
let query = format!("{}\r\n", domain);
stream
.write_all(query.as_bytes())
.map_err(|e| format!("Failed to send query: {}", e))?;
let mut response = String::new();
stream
.read_to_string(&mut response)
.map_err(|e| format!("Failed to read response: {}", e))?;
Ok(response)
}
fn fallback_lookup(domain: &str) -> Result<String, String> {
let iana = query_server("whois.iana.org", domain)?;
for line in iana.lines() {
let trimmed = line.trim().to_lowercase();
if trimmed.starts_with("whois:") {
let server = trimmed
.strip_prefix("whois:")
.unwrap_or("")
.trim();
if !server.is_empty() && server != "whois.iana.org" {
return query_server(server, domain);
}
}
}
Ok(iana)
}