1use std::{
2 collections::HashMap,
3 fs::File,
4 io::{self, Read, Write},
5 net::{SocketAddr, TcpStream, ToSocketAddrs},
6 path::Path,
7 str::FromStr,
8 sync::LazyLock,
9 time::Duration,
10};
11
12use hickory_client::{
13 client::{Client, SyncClient},
14 op::DnsResponse,
15 rr::{DNSClass, Name, RData, Record, RecordType},
16 udp::UdpClientConnection,
17};
18use regex::Regex;
19use serde_json::{Map, Value};
20#[cfg(feature = "tokio")]
21use tokio::io::{AsyncReadExt, AsyncWriteExt};
22use validators::{models::Host, prelude::*};
23
24use crate::{WhoIsError, WhoIsLookupOptions, WhoIsServerValue};
25
26const DEFAULT_WHOIS_HOST_PORT: u16 = 43;
27const DEFAULT_WHOIS_HOST_QUERY: &str = "$addr\r\n";
28
29static RE_SERVER: LazyLock<Regex> = LazyLock::new(|| {
30 Regex::new(r"(ReferralServer|Registrar Whois|Whois Server|WHOIS Server|Registrar WHOIS Server):[^\S\n]*(r?whois://)?(.*)").unwrap()
31});
32
33fn extract_referral_host(query_result: &str) -> Option<&str> {
35 let host = RE_SERVER.captures(query_result)?.get(3)?.as_str().trim();
36
37 if host.is_empty() { None } else { Some(host) }
38}
39
40#[derive(Debug, Clone)]
42pub struct WhoIs {
43 map: HashMap<String, WhoIsServerValue>,
44 ip: WhoIsServerValue,
45}
46
47impl WhoIs {
48 pub fn from_host<T: AsRef<str>>(host: T) -> Result<WhoIs, WhoIsError> {
50 Ok(Self {
51 map: HashMap::new(), ip: WhoIsServerValue::from_string(host)?
52 })
53 }
54
55 #[inline]
57 pub fn from_path<P: AsRef<Path>>(path: P) -> Result<WhoIs, WhoIsError> {
58 let path = path.as_ref();
59
60 let file = File::open(path)?;
61
62 let map: Map<String, Value> = serde_json::from_reader(file)?;
63
64 Self::from_inner(map)
65 }
66
67 #[cfg(feature = "tokio")]
68 #[inline]
70 pub async fn from_path_async<P: AsRef<Path>>(path: P) -> Result<WhoIs, WhoIsError> {
71 let file = tokio::fs::read(path).await?;
72
73 let map: Map<String, Value> = serde_json::from_slice(file.as_slice())?;
74
75 Self::from_inner(map)
76 }
77
78 #[inline]
80 pub fn from_string<S: AsRef<str>>(string: S) -> Result<WhoIs, WhoIsError> {
81 let string = string.as_ref();
82
83 let map: Map<String, Value> = serde_json::from_str(string)?;
84
85 Self::from_inner(map)
86 }
87
88 fn from_inner(mut map: Map<String, Value>) -> Result<WhoIs, WhoIsError> {
89 let ip = match map.remove("_") {
90 Some(server) => {
91 if let Value::Object(server) = server {
92 match server.get("ip") {
93 Some(server) => {
94 if server.is_null() {
95 return Err(WhoIsError::MapError(
96 "`ip` in the `_` object in the server list is null.",
97 ));
98 }
99
100 WhoIsServerValue::from_value(server)?
101 },
102 None => {
103 return Err(WhoIsError::MapError(
104 "Cannot find `ip` in the `_` object in the server list.",
105 ));
106 },
107 }
108 } else {
109 return Err(WhoIsError::MapError("`_` in the server list is not an object."));
110 }
111 },
112 None => return Err(WhoIsError::MapError("Cannot find `_` in the server list.")),
113 };
114
115 let mut new_map: HashMap<String, WhoIsServerValue> = HashMap::with_capacity(map.len());
116
117 for (k, v) in map {
118 if !v.is_null() {
119 let server_value = WhoIsServerValue::from_value(&v)?;
120 new_map.insert(k, server_value);
121 }
122 }
123
124 Ok(WhoIs {
125 map: new_map,
126 ip,
127 })
128 }
129}
130
131impl WhoIs {
132 pub fn can_find_server_for_tld<T: AsRef<str>, D: AsRef<str>>(
134 &mut self,
135 tld: T,
136 dns_server: D,
137 ) -> Result<bool, WhoIsError> {
138 let mut tld = tld.as_ref();
139 let dns_server = dns_server.as_ref();
140
141 let address = match dns_server.parse() {
142 Ok(address) => address,
143 Err(_error) => {
144 return Err(WhoIsError::MapError("The DNS server address is incorrect."));
145 },
146 };
147 let conn = UdpClientConnection::new(address).map_err(io::Error::other)?;
148 let client = SyncClient::new(conn);
149
150 loop {
151 if self.map.contains_key(tld) {
152 return Ok(true);
153 }
154
155 match tld.find('.') {
156 Some(index) => {
157 tld = &tld[index + 1..];
158 },
159 None => {
160 tld = "";
161 },
162 }
163
164 if tld.is_empty() {
165 return Ok(false);
166 }
167
168 let name =
169 Name::from_str(&format!("_nicname._tcp.{tld}.")).map_err(io::Error::other)?;
170 let response: DnsResponse =
171 client.query(&name, DNSClass::IN, RecordType::SRV).map_err(io::Error::other)?;
172 let answers: &[Record] = response.answers();
173
174 for record in answers {
175 if let Some(RData::SRV(record)) = record.data() {
176 let target = record.target().to_string();
177 let new_server =
178 match WhoIsServerValue::from_string(target.trim_end_matches('.')) {
179 Ok(new_server) => new_server,
180 Err(_error) => continue,
181 };
182
183 self.map.insert(tld.to_string(), new_server);
184
185 return Ok(true);
186 }
187 }
188 }
189 }
190
191 fn get_server_by_tld(&self, mut tld: &str) -> Option<&WhoIsServerValue> {
192 let mut server;
193
194 loop {
195 server = self.map.get(tld);
196
197 if server.is_some() {
198 break;
199 }
200
201 if tld.is_empty() {
202 break;
203 }
204
205 match tld.find('.') {
206 Some(index) => {
207 tld = &tld[index + 1..];
208 },
209 None => {
210 tld = "";
211 },
212 }
213 }
214
215 server
216 }
217
218 fn lookup_once(
219 server: &WhoIsServerValue,
220 text: &str,
221 timeout: Option<Duration>,
222 ) -> Result<(String, String), WhoIsError> {
223 let addr = server.host.to_addr_string(DEFAULT_WHOIS_HOST_PORT);
224
225 let mut client = if let Some(timeout) = timeout {
226 let socket_addrs: Vec<SocketAddr> = addr.to_socket_addrs()?.collect();
227
228 if socket_addrs.is_empty() {
229 return Err(io::Error::new(
230 io::ErrorKind::AddrNotAvailable,
231 "the host is not resolved to any socket address",
232 )
233 .into());
234 }
235
236 let mut client = None;
237
238 for socket_addr in socket_addrs.iter().take(socket_addrs.len() - 1) {
239 if let Ok(c) = TcpStream::connect_timeout(socket_addr, timeout) {
240 client = Some(c);
241 break;
242 }
243 }
244
245 let client = if let Some(client) = client {
246 client
247 } else {
248 let socket_addr = &socket_addrs[socket_addrs.len() - 1];
249 TcpStream::connect_timeout(socket_addr, timeout)?
250 };
251
252 client.set_read_timeout(Some(timeout))?;
253 client.set_write_timeout(Some(timeout))?;
254 client
255 } else {
256 TcpStream::connect(&addr)?
257 };
258
259 if let Some(query) = &server.query {
260 client.write_all(query.replace("$addr", text).as_bytes())?;
261 } else {
262 client.write_all(DEFAULT_WHOIS_HOST_QUERY.replace("$addr", text).as_bytes())?;
263 }
264
265 client.flush()?;
266
267 let mut query_result = String::new();
268
269 client.read_to_string(&mut query_result)?;
270
271 Ok((server.host.host.to_string(), query_result))
273 }
274
275 fn lookup_inner(
276 server: &WhoIsServerValue,
277 text: &str,
278 timeout: Option<Duration>,
279 mut follow: u16,
280 ) -> Result<String, WhoIsError> {
281 let mut query_result = Self::lookup_once(server, text, timeout)?;
282
283 while follow > 0 {
284 if let Some(h) = extract_referral_host(&query_result.1)
285 && !h.eq_ignore_ascii_case(&query_result.0)
286 && let Ok(server) = WhoIsServerValue::from_string(h)
287 {
288 query_result = Self::lookup_once(&server, text, timeout)?;
289
290 follow -= 1;
291
292 continue;
293 }
294
295 break;
296 }
297
298 Ok(query_result.1)
299 }
300
301 pub fn lookup(&self, options: WhoIsLookupOptions) -> Result<String, WhoIsError> {
303 match &options.target.0 {
304 Host::IPv4(_) | Host::IPv6(_) => {
305 let server = match &options.server {
306 Some(server) => server,
307 None => &self.ip,
308 };
309
310 Self::lookup_inner(
311 server,
312 options.target.to_uri_authority_string().as_ref(),
313 options.timeout,
314 options.follow,
315 )
316 },
317 Host::Domain(domain) => {
318 let server = match &options.server {
319 Some(server) => server,
320 None => match self.get_server_by_tld(domain.as_str()) {
321 Some(server) => server,
322 None => {
323 return Err(WhoIsError::MapError(
324 "No whois server is known for this kind of object.",
325 ));
326 },
327 },
328 };
329
330 let text = server.encode_domain(domain);
332
333 Self::lookup_inner(server, text.as_ref(), options.timeout, options.follow)
334 },
335 }
336 }
337}
338
339#[cfg(feature = "tokio")]
340impl WhoIs {
341 async fn lookup_once_async(
342 server: &WhoIsServerValue,
343 text: &str,
344 timeout: Option<Duration>,
345 ) -> Result<(String, String), WhoIsError> {
346 let addr = server.host.to_addr_string(DEFAULT_WHOIS_HOST_PORT);
347
348 if let Some(timeout) = timeout {
349 let socket_addrs: Vec<SocketAddr> = addr.to_socket_addrs()?.collect();
350
351 if socket_addrs.is_empty() {
352 return Err(io::Error::new(
353 io::ErrorKind::AddrNotAvailable,
354 "the host is not resolved to any socket address",
355 )
356 .into());
357 }
358
359 let mut client = None;
360
361 for socket_addr in socket_addrs.iter().take(socket_addrs.len() - 1) {
362 if let Ok(c) =
363 tokio::time::timeout(timeout, tokio::net::TcpStream::connect(&socket_addr))
364 .await?
365 {
366 client = Some(c);
367 break;
368 }
369 }
370
371 let mut client = if let Some(client) = client {
372 client
373 } else {
374 let socket_addr = &socket_addrs[socket_addrs.len() - 1];
375 tokio::time::timeout(timeout, tokio::net::TcpStream::connect(socket_addr)).await??
376 };
377
378 if let Some(query) = &server.query {
379 tokio::time::timeout(
380 timeout,
381 client.write_all(query.replace("$addr", text).as_bytes()),
382 )
383 .await??;
384 } else {
385 tokio::time::timeout(
386 timeout,
387 client.write_all(DEFAULT_WHOIS_HOST_QUERY.replace("$addr", text).as_bytes()),
388 )
389 .await??;
390 }
391
392 tokio::time::timeout(timeout, client.flush()).await??;
393
394 let mut query_result = String::new();
395
396 tokio::time::timeout(timeout, client.read_to_string(&mut query_result)).await??;
397
398 Ok((server.host.host.to_string(), query_result))
400 } else {
401 let mut client = tokio::net::TcpStream::connect(&addr).await?;
402
403 if let Some(query) = &server.query {
404 client.write_all(query.replace("$addr", text).as_bytes()).await?;
405 } else {
406 client
407 .write_all(DEFAULT_WHOIS_HOST_QUERY.replace("$addr", text).as_bytes())
408 .await?;
409 }
410
411 client.flush().await?;
412
413 let mut query_result = String::new();
414
415 client.read_to_string(&mut query_result).await?;
416
417 Ok((server.host.host.to_string(), query_result))
419 }
420 }
421
422 async fn lookup_inner_async<'a>(
423 server: &'a WhoIsServerValue,
424 text: &'a str,
425 timeout: Option<Duration>,
426 mut follow: u16,
427 ) -> Result<String, WhoIsError> {
428 let mut query_result = Self::lookup_once_async(server, text, timeout).await?;
429
430 while follow > 0 {
431 if let Some(h) = extract_referral_host(&query_result.1)
432 && !h.eq_ignore_ascii_case(&query_result.0)
433 && let Ok(server) = WhoIsServerValue::from_string(h)
434 {
435 query_result = Self::lookup_once_async(&server, text, timeout).await?;
436
437 follow -= 1;
438
439 continue;
440 }
441
442 break;
443 }
444
445 Ok(query_result.1)
446 }
447
448 pub async fn lookup_async(&self, options: WhoIsLookupOptions) -> Result<String, WhoIsError> {
450 match &options.target.0 {
451 Host::IPv4(_) | Host::IPv6(_) => {
452 let server = match &options.server {
453 Some(server) => server,
454 None => &self.ip,
455 };
456
457 Self::lookup_inner_async(
458 server,
459 options.target.to_uri_authority_string().as_ref(),
460 options.timeout,
461 options.follow,
462 )
463 .await
464 },
465 Host::Domain(domain) => {
466 let server = match &options.server {
467 Some(server) => server,
468 None => match self.get_server_by_tld(domain.as_str()) {
469 Some(server) => server,
470 None => {
471 return Err(WhoIsError::MapError(
472 "No whois server is known for this kind of object.",
473 ));
474 },
475 },
476 };
477
478 let text = server.encode_domain(domain);
480
481 Self::lookup_inner_async(server, text.as_ref(), options.timeout, options.follow)
482 .await
483 },
484 }
485 }
486}
487
488#[cfg(test)]
489mod tests {
490 use super::extract_referral_host;
491
492 #[test]
493 fn extract_referral_host_trims_trailing_cr() {
494 let body = "Domain: example.com\r\nReferralServer: whois://whois.arin.net\r\n";
495
496 assert_eq!(Some("whois.arin.net"), extract_referral_host(body));
497 }
498}