diff options
Diffstat (limited to 'src/lookup.rs')
| -rw-r--r-- | src/lookup.rs | 505 |
1 files changed, 365 insertions, 140 deletions
diff --git a/src/lookup.rs b/src/lookup.rs index f5b3177..694f559 100644 --- a/src/lookup.rs +++ b/src/lookup.rs @@ -6,6 +6,10 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::Duration; +// Dont ask me about the spaghetti code found in here god may know but I dont remember what unholy sins have been created at like 5am here. + + + #[cfg(feature = "builtin-whois")] use tokio::io::{AsyncReadExt, AsyncWriteExt}; #[cfg(feature = "builtin-whois")] @@ -23,7 +27,10 @@ pub struct RdapBootstrap { impl RdapBootstrap { pub async fn fetch(client: &reqwest::Client, verbose: bool) -> Result<Self, String> { if verbose { - eprintln!("[verbose] Fetching RDAP bootstrap from {}", RDAP_BOOTSTRAP_URL); + eprintln!( + "[verbose] Fetching RDAP bootstrap from {}", + RDAP_BOOTSTRAP_URL + ); } let resp = client @@ -47,15 +54,24 @@ impl RdapBootstrap { let tld_map = Self::parse_bootstrap_json(&json); if verbose { - eprintln!("[verbose] RDAP bootstrap loaded, {} TLDs mapped", tld_map.len()); + eprintln!( + "[verbose] RDAP bootstrap loaded, {} TLDs mapped", + tld_map.len() + ); } - Ok(Self { tld_map, raw_json: Some(body) }) + Ok(Self { + tld_map, + raw_json: Some(body), + }) } pub fn load_cached(cache_path: &Path, verbose: bool) -> Result<Self, String> { if verbose { - eprintln!("[verbose] Loading cached RDAP bootstrap from {}", cache_path.display()); + eprintln!( + "[verbose] Loading cached RDAP bootstrap from {}", + cache_path.display() + ); } let body = std::fs::read_to_string(cache_path) .map_err(|e| format!("Could not read cached bootstrap: {}", e))?; @@ -63,9 +79,15 @@ impl RdapBootstrap { .map_err(|e| format!("Could not parse cached bootstrap: {}", e))?; let tld_map = Self::parse_bootstrap_json(&json); if verbose { - eprintln!("[verbose] Cached RDAP bootstrap loaded, {} TLDs mapped", tld_map.len()); + eprintln!( + "[verbose] Cached RDAP bootstrap loaded, {} TLDs mapped", + tld_map.len() + ); } - Ok(Self { tld_map, raw_json: Some(body) }) + Ok(Self { + tld_map, + raw_json: Some(body), + }) } pub fn save_cache(&self, cache_path: &Path) -> Result<(), String> { @@ -126,7 +148,10 @@ pub async fn lookup_domain( None => { // no RDAP server for this TLD, fall back to WHOIS if verbose { - eprintln!("[verbose] No RDAP server for {}, falling back to WHOIS", tld); + eprintln!( + "[verbose] No RDAP server for {}, falling back to WHOIS", + tld + ); } return whois_lookup(whois_overrides, name, tld, verbose).await; } @@ -149,10 +174,14 @@ pub async fn lookup_domain( } else { ErrorKind::Unknown }; - return DomainResult::new(name, tld, DomainStatus::Error { - kind, - message: "unknown error".to_string(), - }); + return DomainResult::new( + name, + tld, + DomainStatus::Error { + kind, + message: "unknown error".to_string(), + }, + ); } }; @@ -169,34 +198,50 @@ pub async fn lookup_domain( // 400 = probably invalid query if status_code == 400 { - return DomainResult::new(name, tld, DomainStatus::Error { - kind: ErrorKind::InvalidTld, - message: "invalid tld".to_string(), - }); + return DomainResult::new( + name, + tld, + DomainStatus::Error { + kind: ErrorKind::InvalidTld, + message: "invalid tld".to_string(), + }, + ); } // 429 = rate limited if status_code == 429 { - return DomainResult::new(name, tld, DomainStatus::Error { - kind: ErrorKind::RateLimit, - message: "rate limited".to_string(), - }); + return DomainResult::new( + name, + tld, + DomainStatus::Error { + kind: ErrorKind::RateLimit, + message: "rate limited".to_string(), + }, + ); } // 403 = forbidden (some registries block queries) if status_code == 403 { - return DomainResult::new(name, tld, DomainStatus::Error { - kind: ErrorKind::Forbidden, - message: "forbidden".to_string(), - }); + return DomainResult::new( + name, + tld, + DomainStatus::Error { + kind: ErrorKind::Forbidden, + message: "forbidden".to_string(), + }, + ); } - // anything else thats not success + // anything else thats not success is le bad if !status_code.is_success() { - return DomainResult::new(name, tld, DomainStatus::Error { - kind: ErrorKind::Unknown, - message: format!("HTTP {}", status_code), - }); + return DomainResult::new( + name, + tld, + DomainStatus::Error { + kind: ErrorKind::Unknown, + message: format!("HTTP {}", status_code), + }, + ); } // 200 = domain exists, try to parse expiry from RDAP json @@ -229,16 +274,30 @@ fn extract_expiry(json: &serde_json::Value) -> Option<String> { // -- No whois feature: just return an error -- #[cfg(not(any(feature = "system-whois", feature = "builtin-whois")))] -async fn whois_lookup(_whois_overrides: &WhoisOverrides, name: &str, tld: &str, _verbose: bool) -> DomainResult { - DomainResult::new(name, tld, DomainStatus::Error { - kind: ErrorKind::InvalidTld, - message: "no RDAP server (whois disabled)".to_string(), - }) +async fn whois_lookup( + _whois_overrides: &WhoisOverrides, + name: &str, + tld: &str, + _verbose: bool, +) -> DomainResult { + DomainResult::new( + name, + tld, + DomainStatus::Error { + kind: ErrorKind::InvalidTld, + message: "no RDAP server (whois disabled)".to_string(), + }, + ) } // -- System whois: shells out to the systems whois binary -- #[cfg(feature = "system-whois")] -async fn whois_lookup(_whois_overrides: &WhoisOverrides, name: &str, tld: &str, verbose: bool) -> DomainResult { +async fn whois_lookup( + _whois_overrides: &WhoisOverrides, + name: &str, + tld: &str, + verbose: bool, +) -> DomainResult { let full = format!("{}.{}", name, tld); let whois_cmd = env!("HOARDOM_WHOIS_CMD"); let whois_flags = env!("HOARDOM_WHOIS_FLAGS"); @@ -247,7 +306,10 @@ async fn whois_lookup(_whois_overrides: &WhoisOverrides, name: &str, tld: &str, if whois_flags.is_empty() { eprintln!("[verbose] System WHOIS: {} {}", whois_cmd, full); } else { - eprintln!("[verbose] System WHOIS: {} {} {}", whois_cmd, whois_flags, full); + eprintln!( + "[verbose] System WHOIS: {} {} {}", + whois_cmd, whois_flags, full + ); } } @@ -260,35 +322,44 @@ async fn whois_lookup(_whois_overrides: &WhoisOverrides, name: &str, tld: &str, } cmd.arg(&full); - let output = match tokio::time::timeout( - Duration::from_secs(15), - cmd.output(), - ).await { + let output = match tokio::time::timeout(Duration::from_secs(15), cmd.output()).await { Ok(Ok(out)) => out, Ok(Err(e)) => { if verbose { eprintln!("[verbose] System whois error for {}: {}", full, e); } - return DomainResult::new(name, tld, DomainStatus::Error { - kind: ErrorKind::Unknown, - message: format!("whois command failed: {}", e), - }); + return DomainResult::new( + name, + tld, + DomainStatus::Error { + kind: ErrorKind::Unknown, + message: format!("whois command failed: {}", e), + }, + ); } Err(_) => { if verbose { eprintln!("[verbose] System whois timeout for {}", full); } - return DomainResult::new(name, tld, DomainStatus::Error { - kind: ErrorKind::Timeout, - message: "whois timeout".to_string(), - }); + return DomainResult::new( + name, + tld, + DomainStatus::Error { + kind: ErrorKind::Timeout, + message: "whois timeout".to_string(), + }, + ); } }; let response_str = String::from_utf8_lossy(&output.stdout); if verbose { - eprintln!("[verbose] WHOIS response for {} ({} bytes)", full, output.stdout.len()); + eprintln!( + "[verbose] WHOIS response for {} ({} bytes)", + full, + output.stdout.len() + ); } if !output.status.success() { @@ -300,26 +371,34 @@ async fn whois_lookup(_whois_overrides: &WhoisOverrides, name: &str, tld: &str, if !response_str.is_empty() { return parse_whois_response(name, tld, &response_str); } - return DomainResult::new(name, tld, DomainStatus::Error { - kind: ErrorKind::Unknown, - message: "whois command returned error".to_string(), - }); + return DomainResult::new( + name, + tld, + DomainStatus::Error { + kind: ErrorKind::Unknown, + message: "whois command returned error".to_string(), + }, + ); } parse_whois_response(name, tld, &response_str) } -// -- Builtin whois: raw TCP to whois servers directly -- +// -- Builtin whois: rawdogs whois server violently over TCP directly-- + -/// try a whois server, returns the response string or errors out + +/// try a whois server returns the response string or errors out #[cfg(feature = "builtin-whois")] -async fn try_whois_server(server: &str, domain: &str, verbose: bool) -> Result<String, &'static str> { +async fn try_whois_server( + server: &str, + domain: &str, + verbose: bool, +) -> Result<String, &'static str> { let addr = format!("{}:43", server); - let stream = match tokio::time::timeout( - Duration::from_secs(4), - TcpStream::connect(&addr), - ).await { + let stream = match tokio::time::timeout(Duration::from_secs(4), TcpStream::connect(&addr)).await + { Ok(Ok(s)) => s, Ok(Err(_)) => return Err("connect error"), Err(_) => return Err("connect timeout"), @@ -337,10 +416,7 @@ async fn try_whois_server(server: &str, domain: &str, verbose: bool) -> Result<S } let mut response = Vec::new(); - match tokio::time::timeout( - Duration::from_secs(8), - reader.read_to_end(&mut response), - ).await { + match tokio::time::timeout(Duration::from_secs(8), reader.read_to_end(&mut response)).await { Ok(Ok(_)) => {} Ok(Err(_)) => return Err("read error"), Err(_) => return Err("read timeout"), @@ -361,7 +437,12 @@ fn whois_candidates(tld: &str) -> Vec<String> { } #[cfg(feature = "builtin-whois")] -async fn whois_lookup(whois_overrides: &WhoisOverrides, name: &str, tld: &str, verbose: bool) -> DomainResult { +async fn whois_lookup( + whois_overrides: &WhoisOverrides, + name: &str, + tld: &str, + verbose: bool, +) -> DomainResult { let full = format!("{}.{}", name, tld); // if Lists.toml has an explicit server ("tld:server"), use ONLY that one @@ -371,14 +452,26 @@ async fn whois_lookup(whois_overrides: &WhoisOverrides, name: &str, tld: &str, v } return match try_whois_server(server, &full, verbose).await { Ok(resp) if !resp.is_empty() => parse_whois_response(name, tld, &resp), - Ok(_) => DomainResult::new(name, tld, DomainStatus::Error { - kind: ErrorKind::Unknown, - message: "empty whois response".to_string(), - }), - Err(e) => DomainResult::new(name, tld, DomainStatus::Error { - kind: if e.contains("timeout") { ErrorKind::Timeout } else { ErrorKind::Unknown }, - message: format!("whois {}: {}", server, e), - }), + Ok(_) => DomainResult::new( + name, + tld, + DomainStatus::Error { + kind: ErrorKind::Unknown, + message: "empty whois response".to_string(), + }, + ), + Err(e) => DomainResult::new( + name, + tld, + DomainStatus::Error { + kind: if e.contains("timeout") { + ErrorKind::Timeout + } else { + ErrorKind::Unknown + }, + message: format!("whois {}: {}", server, e), + }, + ), }; } @@ -386,7 +479,11 @@ async fn whois_lookup(whois_overrides: &WhoisOverrides, name: &str, tld: &str, v let candidates = whois_candidates(tld); if verbose { - eprintln!("[verbose] WHOIS probing {} candidates for .{}", candidates.len(), tld); + eprintln!( + "[verbose] WHOIS probing {} candidates for .{}", + candidates.len(), + tld + ); } for server in &candidates { @@ -408,10 +505,14 @@ async fn whois_lookup(whois_overrides: &WhoisOverrides, name: &str, tld: &str, v } // nothing worked - DomainResult::new(name, tld, DomainStatus::Error { - kind: ErrorKind::Unknown, - message: "no whois server reachable".to_string(), - }) + DomainResult::new( + name, + tld, + DomainStatus::Error { + kind: ErrorKind::Unknown, + message: "no whois server reachable".to_string(), + }, + ) } fn parse_whois_response(name: &str, tld: &str, response: &str) -> DomainResult { @@ -483,6 +584,8 @@ fn extract_whois_expiry(response: &str) -> Option<String> { None } + + pub async fn lookup_with_retry( client: &reqwest::Client, bootstrap: &RdapBootstrap, @@ -503,13 +606,19 @@ pub async fn lookup_with_retry( if let DomainStatus::Error { kind, .. } = &result.status { if noretry.contains(kind) { if verbose { - eprintln!("[verbose] Not retrying {}.{} (error kind in noretry list)", name, tld); + eprintln!( + "[verbose] Not retrying {}.{} (error kind in noretry list)", + name, tld + ); } break; } } if verbose { - eprintln!("[verbose] Retry {}/{} for {}.{}", attempt, retries, name, tld); + eprintln!( + "[verbose] Retry {}/{} for {}.{}", + attempt, retries, name, tld + ); } tokio::time::sleep(Duration::from_millis(500)).await; result = lookup_domain(client, bootstrap, whois_overrides, name, tld, verbose).await; @@ -554,7 +663,17 @@ pub async fn lookup_all( let mut results = Vec::with_capacity(total); let delay = Duration::from_secs_f64(delay_secs); for (i, tld) in tlds.iter().enumerate() { - let result = lookup_with_retry(&client, &bootstrap, whois_overrides, name, tld, retries, noretry, verbose).await; + let result = lookup_with_retry( + &client, + &bootstrap, + whois_overrides, + name, + tld, + retries, + noretry, + verbose, + ) + .await; results.push(result); on_progress(i + 1, total); if delay_secs > 0.0 && i + 1 < total { @@ -579,7 +698,17 @@ pub async fn lookup_all( let name = name_owned.clone(); let tld = tld.to_string(); async move { - let result = lookup_with_retry(&client, &bootstrap, &whois_overrides, &name, &tld, retries, &noretry, verbose).await; + let result = lookup_with_retry( + &client, + &bootstrap, + &whois_overrides, + &name, + &tld, + retries, + &noretry, + verbose, + ) + .await; (i, result) } }) @@ -604,7 +733,10 @@ pub async fn refresh_cache(cache_path: &Path, verbose: bool) -> Result<(), Strin let client = build_client(); let bootstrap = RdapBootstrap::fetch(&client, verbose).await?; bootstrap.save_cache(cache_path)?; - eprintln!("RDAP bootstrap cache refreshed ({} TLDs)", bootstrap.tld_map.len()); + eprintln!( + "RDAP bootstrap cache refreshed ({} TLDs)", + bootstrap.tld_map.len() + ); Ok(()) } @@ -642,33 +774,37 @@ async fn resolve_bootstrap( match cached { Some(b) => Some(b), - None => { - match RdapBootstrap::fetch(client, verbose).await { - Ok(b) => { - if let Some(cp) = cache_path { - if let Err(e) = b.save_cache(cp) { - if verbose { - eprintln!("[verbose] Failed to save cache: {}", e); - } - } else if verbose { - eprintln!("[verbose] RDAP bootstrap cached to {}", cp.display()); + None => match RdapBootstrap::fetch(client, verbose).await { + Ok(b) => { + if let Some(cp) = cache_path { + if let Err(e) = b.save_cache(cp) { + if verbose { + eprintln!("[verbose] Failed to save cache: {}", e); } + } else if verbose { + eprintln!("[verbose] RDAP bootstrap cached to {}", cp.display()); } - Some(b) - } - Err(e) => { - eprintln!("Error: {}", e); - eprintln!("Cannot perform lookups without RDAP bootstrap data."); - None } + Some(b) } - } + Err(e) => { + eprintln!("Error: {}", e); + eprintln!("Cannot perform lookups without RDAP bootstrap data."); + None + } + }, } } pub enum StreamMsg { - Result { result: DomainResult, sort_index: usize }, - Progress { current: usize, total: usize }, + Result { + result: DomainResult, + sort_index: usize, + }, + Progress { + current: usize, + total: usize, + }, Error(String), Done, } @@ -698,19 +834,19 @@ pub fn lookup_streaming( let handle = tokio::spawn(async move { let client = build_client(); - let bootstrap = match resolve_bootstrap( - &client, - cache_path.as_deref(), - force_refresh, - verbose, - ).await { - Some(b) => b, - None => { - let _ = tx.send(StreamMsg::Error("Failed to load RDAP bootstrap".to_string())).await; - let _ = tx.send(StreamMsg::Done).await; - return; - } - }; + let bootstrap = + match resolve_bootstrap(&client, cache_path.as_deref(), force_refresh, verbose).await { + Some(b) => b, + None => { + let _ = tx + .send(StreamMsg::Error( + "Failed to load RDAP bootstrap".to_string(), + )) + .await; + let _ = tx.send(StreamMsg::Done).await; + return; + } + }; let total = tlds.len(); let concurrent = (jobs as usize).max(1); @@ -718,9 +854,29 @@ pub fn lookup_streaming( if concurrent <= 1 { let delay = Duration::from_secs_f64(delay_secs); for (i, tld) in tlds.iter().enumerate() { - let result = lookup_with_retry(&client, &bootstrap, &whois_overrides, &name, tld, retries, &noretry, verbose).await; - let _ = tx.send(StreamMsg::Result { result, sort_index: i }).await; - let _ = tx.send(StreamMsg::Progress { current: i + 1, total }).await; + let result = lookup_with_retry( + &client, + &bootstrap, + &whois_overrides, + &name, + tld, + retries, + &noretry, + verbose, + ) + .await; + let _ = tx + .send(StreamMsg::Result { + result, + sort_index: i, + }) + .await; + let _ = tx + .send(StreamMsg::Progress { + current: i + 1, + total, + }) + .await; if delay_secs > 0.0 && i + 1 < total { tokio::time::sleep(delay).await; } @@ -740,7 +896,17 @@ pub fn lookup_streaming( let noretry = Arc::clone(&noretry); let name = name.clone(); async move { - let result = lookup_with_retry(&client, &bootstrap, &whois_overrides, &name, &tld, retries, &noretry, verbose).await; + let result = lookup_with_retry( + &client, + &bootstrap, + &whois_overrides, + &name, + &tld, + retries, + &noretry, + verbose, + ) + .await; (idx, result) } }) @@ -749,8 +915,18 @@ pub fn lookup_streaming( let mut done_count = 0usize; while let Some((idx, result)) = stream.next().await { done_count += 1; - let _ = tx2.send(StreamMsg::Result { result, sort_index: idx }).await; - let _ = tx2.send(StreamMsg::Progress { current: done_count, total }).await; + let _ = tx2 + .send(StreamMsg::Result { + result, + sort_index: idx, + }) + .await; + let _ = tx2 + .send(StreamMsg::Progress { + current: done_count, + total, + }) + .await; } } @@ -776,7 +952,18 @@ pub fn lookup_many_streaming( ) -> LookupStream { if batches.len() == 1 { let (name, tlds) = batches.into_iter().next().unwrap(); - return lookup_streaming(name, tlds, delay_secs, retries, verbose, cache_path, force_refresh, jobs, whois_overrides, noretry); + return lookup_streaming( + name, + tlds, + delay_secs, + retries, + verbose, + cache_path, + force_refresh, + jobs, + whois_overrides, + noretry, + ); } let (tx, rx) = tokio::sync::mpsc::channel(64); @@ -784,19 +971,19 @@ pub fn lookup_many_streaming( let handle = tokio::spawn(async move { let client = build_client(); - let bootstrap = match resolve_bootstrap( - &client, - cache_path.as_deref(), - force_refresh, - verbose, - ).await { - Some(b) => b, - None => { - let _ = tx.send(StreamMsg::Error("Failed to load RDAP bootstrap".to_string())).await; - let _ = tx.send(StreamMsg::Done).await; - return; - } - }; + let bootstrap = + match resolve_bootstrap(&client, cache_path.as_deref(), force_refresh, verbose).await { + Some(b) => b, + None => { + let _ = tx + .send(StreamMsg::Error( + "Failed to load RDAP bootstrap".to_string(), + )) + .await; + let _ = tx.send(StreamMsg::Done).await; + return; + } + }; let total: usize = batches.iter().map(|(_, tlds)| tlds.len()).sum(); let concurrent = (jobs as usize).max(1); @@ -807,9 +994,24 @@ pub fn lookup_many_streaming( let mut global_idx = 0usize; for (name, tlds) in batches { for tld in tlds { - let result = lookup_with_retry(&client, &bootstrap, &whois_overrides, &name, &tld, retries, &noretry, verbose).await; + let result = lookup_with_retry( + &client, + &bootstrap, + &whois_overrides, + &name, + &tld, + retries, + &noretry, + verbose, + ) + .await; current += 1; - let _ = tx.send(StreamMsg::Result { result, sort_index: global_idx }).await; + let _ = tx + .send(StreamMsg::Result { + result, + sort_index: global_idx, + }) + .await; let _ = tx.send(StreamMsg::Progress { current, total }).await; if delay_secs > 0.0 && current < total { tokio::time::sleep(delay).await; @@ -839,7 +1041,17 @@ pub fn lookup_many_streaming( let whois_overrides = Arc::clone(&whois_overrides); let noretry = Arc::clone(&noretry); async move { - let result = lookup_with_retry(&client, &bootstrap, &whois_overrides, &name, &tld, retries, &noretry, verbose).await; + let result = lookup_with_retry( + &client, + &bootstrap, + &whois_overrides, + &name, + &tld, + retries, + &noretry, + verbose, + ) + .await; (idx, result) } }) @@ -848,13 +1060,26 @@ pub fn lookup_many_streaming( let mut done_count = 0usize; while let Some((idx, result)) = stream.next().await { done_count += 1; - let _ = tx2.send(StreamMsg::Result { result, sort_index: idx }).await; - let _ = tx2.send(StreamMsg::Progress { current: done_count, total }).await; + let _ = tx2 + .send(StreamMsg::Result { + result, + sort_index: idx, + }) + .await; + let _ = tx2 + .send(StreamMsg::Progress { + current: done_count, + total, + }) + .await; } } let _ = tx.send(StreamMsg::Done).await; }); - LookupStream { receiver: rx, handle } + LookupStream { + receiver: rx, + handle, + } } |
