diff --git a/Cargo.lock b/Cargo.lock index 37f2362..1a2b0ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,17 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a93b8a41dbe230ad5087cc721f8d41611de654542180586b315d9f4cf6b72bef" +dependencies = [ + "psl", + "psl-types", + "publicsuffix", +] + [[package]] name = "addr2line" version = "0.21.0" @@ -308,6 +319,7 @@ checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" name = "dns_lookup_project" version = "0.1.0" dependencies = [ + "addr", "chrono", "configparser", "crossterm", @@ -563,6 +575,16 @@ dependencies = [ "cc", ] +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "idna" version = "0.4.0" @@ -857,6 +879,21 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "psl" +version = "2.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe63c1c8bb438205c1245719638a0b14fe6e3b33f4406ac36b4770a706655345" +dependencies = [ + "psl-types", +] + +[[package]] +name = "psl-types" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" + [[package]] name = "ptr_meta" version = "0.1.4" @@ -877,6 +914,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "publicsuffix" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96a8c1bda5ae1af7f99a2962e49df150414a43d62404644d98dd5c3a93d07457" +dependencies = [ + "idna 0.3.0", + "psl-types", +] + [[package]] name = "quick-error" version = "1.2.3" diff --git a/Cargo.toml b/Cargo.toml index 3a2b120..0fc7535 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,12 +6,13 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -configparser = "3.0.4" -crossterm = "0.27.0" +configparser = "*" +crossterm = "*" hickory-resolver = { version = "0.24.0" } -ratatui = "0.26.1" -regex = "1.10.3" -whois-rust = "1.6.0" +ratatui = "*" +regex = "*" +whois-rust = "*" serde = { version = "*", features = ["derive"] } serde_json = "*" -chrono = "0.4.37" +chrono = "*" +addr = { version = "0.15.6", features = ["publicsuffix"] } diff --git a/src/app.rs b/src/app.rs index ea4bbc1..3c93a43 100644 --- a/src/app.rs +++ b/src/app.rs @@ -25,7 +25,6 @@ pub struct App { pub current_state: CurrentState, pub menu_state: MenuState, pub config: Config, - pub state: ListState, } impl App { @@ -38,7 +37,6 @@ impl App { current_state: CurrentState::Lookup, menu_state: MenuState::Main, config: Config::from_file("test.ini".to_string()), - state: ListState::default(), } } } diff --git a/src/domain.rs b/src/domain.rs index 24dab9e..8ad3cc6 100644 --- a/src/domain.rs +++ b/src/domain.rs @@ -53,9 +53,9 @@ impl fmt::Display for Domain { } impl Domain { - pub fn new(domain: String) -> Domain { + pub fn new(domain: &String) -> Domain { Domain { - domain_name: domain, + domain_name: String::from(domain), subdomains: vec![], a_records: vec![], aaaa_records: vec![], @@ -116,7 +116,7 @@ impl Domain { let mut new_domain = String::from(subdomain); new_domain.push_str("."); new_domain.push_str(&self.domain_name); - let subdomain = Domain::new(new_domain); + let subdomain = Domain::new(&new_domain); self.subdomains.push(subdomain); // println!("Added: {}", new_domain); } diff --git a/src/main.rs b/src/main.rs index cd9621e..fa38db6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,9 +4,6 @@ use crate::domain::Domain; mod config; use crate::config::Config; -mod whois; -use crate::whois::WhoisData; - mod app; use crate::app::*; @@ -16,6 +13,10 @@ use crate::ui::*; mod fsutil; use crate::fsutil::*; +mod whois; +use whois::default; +use whois::selector::*; + use crossterm::event::{ self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind, }; @@ -78,12 +79,19 @@ fn run_app(terminal: &mut Terminal, app: &mut App) -> io::Result< // Ignore empty input continue; } - let mut domain = Domain::new(app.domain_input.clone()); + let mut domain = Domain::new(&app.domain_input); domain.apply_config(&app.config); domain.lookup_all_records(); app.dns_info = domain.to_vec(); - let whois = WhoisData::new(app.domain_input.clone()); - app.whois_info = whois.to_vec(); + let whois_server = select_whois_server(app.domain_input.clone()); + match whois_server { + Ok(mut whois_data) => { + app.whois_info = whois_data.lookup(app.domain_input.clone()); + } + Err(_) => {} + } + // let whois = WhoisData::new(app.domain_input.clone()); + // app.whois_info = whois.to_vec(); } KeyCode::Delete => { app.domain_input = String::new(); diff --git a/src/ui.rs b/src/ui.rs index e96609c..699bf96 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,9 +1,10 @@ use ratatui::{ layout::{Constraint, Direction, Layout, Rect}, + prelude::*, style::{Color, Modifier, Style, Styled}, text::{Line, Span, Text}, widgets::{ - Block, Borders, Clear, HighlightSpacing, List, ListDirection, ListItem, ListState, + Block, Borders, Clear, HighlightSpacing, List, ListDirection, ListItem, ListState, Padding, Paragraph, Wrap, }, Frame, @@ -14,6 +15,61 @@ use crate::{ CurrentState, DomainData, MenuState, }; +struct StatefulList<'a> { + state: ListState, + items: Vec>, + last_selected: Option, +} + +impl StatefulList<'_> { + fn with_items<'a>(items: Vec) -> StatefulList<'a> { + let mut veclist = Vec::::new(); + for item in items { + veclist.push(ListItem::new(Line::from(item))); + } + StatefulList { + state: ListState::default(), + items: veclist, + last_selected: None, + } + } + + fn next(&mut self) { + let i = match self.state.selected() { + Some(i) => { + if i >= self.items.len() - 1 { + 0 + } else { + i + 1 + } + } + None => self.last_selected.unwrap_or(0), + }; + self.state.select(Some(i)); + } + + fn previous(&mut self) { + let i = match self.state.selected() { + Some(i) => { + if i == 0 { + self.items.len() - 1 + } else { + i - 1 + } + } + None => self.last_selected.unwrap_or(0), + }; + self.state.select(Some(i)); + } + + fn unselect(&mut self) { + let offset = self.state.offset(); + self.last_selected = self.state.selected(); + self.state.select(None); + *self.state.offset_mut() = offset; + } +} + pub fn ui(f: &mut Frame, app: &mut App) { if f.size().width > 100 { app.render_direction = AppRenderDir::Horizontal; @@ -44,11 +100,12 @@ pub fn ui(f: &mut Frame, app: &mut App) { let title_block = Block::default() .borders(Borders::ALL) - .style(Style::default()); + .style(Style::default()) + .padding(Padding::horizontal(2)); let title = Paragraph::new(Span::styled( "Dns Lookup tool", - Style::default().fg(Color::White), + Style::default().fg(Color::Yellow), )) .block(title_block); @@ -277,7 +334,7 @@ pub fn ui(f: &mut Frame, app: &mut App) { .highlight_symbol(">>") .highlight_spacing(HighlightSpacing::Always) .repeat_highlight_symbol(true); - f.render_stateful_widget(items, chunks[1], &mut app.state); + // f.render_stateful_widget(items, chunks[1], &mut app.state); let footer_block = Block::new().borders(Borders::ALL).style(Style::default()); diff --git a/src/whois/au.rs b/src/whois/au.rs new file mode 100644 index 0000000..bc2606e --- /dev/null +++ b/src/whois/au.rs @@ -0,0 +1,23 @@ +use crate::whois::whois_base::{ + NameServer, RegexQuery, Registrant, RegistrantType, Whois, WhoisData, +}; +use whois_rust::{WhoIs, WhoIsLookupOptions}; +pub struct _Whois { + base: WhoisData, + elegibility: String, +} + +impl Whois for _Whois { + fn new() -> Self { + _Whois { + base: WhoisData::new(), + elegibility: String::new(), + } + } + fn to_vec(&self) -> Vec { + vec![String::new()] + } + fn lookup(&mut self, domain: String) -> Vec { + self.to_vec() + } +} diff --git a/src/whois.rs b/src/whois/default.rs similarity index 51% rename from src/whois.rs rename to src/whois/default.rs index 3584089..9a197c7 100644 --- a/src/whois.rs +++ b/src/whois/default.rs @@ -1,168 +1,34 @@ -use core::fmt; -use hickory_resolver::proto::{ - rr::{domain, rdata::name}, - xfer::dns_handle, +use crate::whois::whois_base::{ + NameServer, RegexQuery, Registrant, RegistrantType, Whois, WhoisData, }; -use regex::Regex; use whois_rust::{WhoIs, WhoIsLookupOptions}; - -#[derive(Debug, Clone)] -enum RegistrantType { - Registrant, - Admin, - Tech, - Billing, +pub struct _Whois { + base: WhoisData, } -struct RegexQuery { - re: Regex, -} - -impl RegexQuery { - fn new(expression: String) -> RegexQuery { - let re = Regex::new(&expression).unwrap(); - RegexQuery { re } - } - - fn get_matches(&self, haystack: &str) -> Vec { - let mut results = vec![]; - for (_, [_, rex2]) in self.re.captures_iter(haystack).map(|c| c.extract()) { - results.push(String::from(rex2.trim())); - } - results - } -} - -#[derive(Debug, Clone)] -struct NameServer { - host: String, -} - -impl NameServer { - fn new(host: String) -> NameServer { - NameServer { host } - } -} - -#[derive(Debug, Clone)] -struct Registrant { - name: String, - org: String, - email: String, - rtype: RegistrantType, -} - -impl fmt::Display for Registrant { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "") - } -} - -impl Registrant { - fn new(name: String, org: String, email: String, rtype: RegistrantType) -> Registrant { - Registrant { - name, - org, - email, - rtype, - } - } -} - -struct Eligibility { - e_type: String, - id: String, - name: String, -} - -pub struct WhoisData { - registrar: Option, - domain_status: Option, - registrant: Option>, - nameservers: Option>, - dnssec: Option, - eligibility_type: Option, -} - -impl fmt::Display for WhoisData { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "Registrar: {}\nStatus: {}\nRegistrant: {:?}\nNameservers: {:?}\nDNSSEC: {}", - self.registrar.clone().unwrap(), - self.domain_status.clone().unwrap(), - self.registrant.clone().unwrap(), - self.nameservers.clone().unwrap(), - self.dnssec.clone().unwrap() - ) - } -} - -impl WhoisData { - pub fn new(domain: String) -> WhoisData { - let whois = WhoIs::from_path("servers.json").unwrap(); - let result: String = whois - .lookup(WhoIsLookupOptions::from_string(domain).unwrap()) - .unwrap(); - let registrar_regex = - RegexQuery::new(String::from(r"(?i)(.*registrar:|registrar *name:)(.*)")); - let domain_status_regex = - RegexQuery::new(String::from(r"(?i)(.*domain status:|.*status:)(.*)")); - // TODO: Capture the registrant info for each type - let registrant_name_regex = RegexQuery::new(String::from(r"(?i)(registrant.*name:)(.*)")); - let registrant_org_regex = - RegexQuery::new(String::from(r"(?i)(registrant org.*:|registrant:)(.*)")); - let registrant_email_regex = RegexQuery::new(String::from(r"(?i)(registrant email:)(.*)")); - let nameserver_regex = - RegexQuery::new(String::from(r"(?i)(nameservers*:|name servers*:)(.*)")); - let dnssec_regex = RegexQuery::new(String::from(r"(?i)(.*dnssec:)(.*)")); - - let registrar = WhoisData::return_regex(registrar_regex.get_matches(&result), 0); - let domain_status = WhoisData::return_regex(domain_status_regex.get_matches(&result), 0); - let reg_name = WhoisData::return_regex(registrant_name_regex.get_matches(&result), 0); - let reg_org = WhoisData::return_regex(registrant_org_regex.get_matches(&result), 0); - let reg_email = WhoisData::return_regex(registrant_email_regex.get_matches(&result), 0); - - let mut nameservers = vec![]; - for nameserver in nameserver_regex.get_matches(&result) { - nameservers.push(NameServer::new(nameserver)); - } - let mut dnssec = dnssec_regex.get_matches(&result); - if dnssec.len() == 0 { - dnssec = vec![String::from("Failed to get DNSSEC")]; - } - // println!("{:?}", registrar[0]); - WhoisData { - registrar: Some(registrar.clone()), - domain_status: Some(domain_status.clone()), - registrant: Some(vec![Registrant::new( - reg_name.clone(), - reg_org.clone(), - reg_email, - RegistrantType::Registrant, - )]), - nameservers: Some(nameservers), - dnssec: Some(dnssec[0].clone()), - eligibility_type: None, +impl Whois for _Whois { + fn new() -> _Whois { + _Whois { + base: WhoisData::new(), } } - pub fn to_vec(&self) -> Vec { + fn to_vec(&self) -> Vec { let mut ret_vec: Vec = vec![]; let mut registrar = String::from("Registrar: "); - registrar.push_str(&self.registrar.clone().unwrap()); + registrar.push_str(&self.base.registrar.clone().unwrap()); ret_vec.push(registrar); let mut domain_status = String::from("Status: "); - domain_status.push_str(&self.domain_status.clone().unwrap()); + domain_status.push_str(&self.base.domain_status.clone().unwrap()); ret_vec.push(domain_status); let mut dnssec = String::from("DNSSEC: "); - dnssec.push_str(&self.dnssec.clone().unwrap()); + dnssec.push_str(&self.base.dnssec.clone().unwrap()); ret_vec.push(dnssec); let mut registrant_type = String::new(); - for registrant in &self.registrant.clone().unwrap() { + for registrant in &self.base.registrant.clone().unwrap() { match registrant.rtype { RegistrantType::Admin => { registrant_type.push_str("Admin "); @@ -193,7 +59,7 @@ impl WhoisData { ret_vec.push(email); } - for nameserver in &self.nameservers.clone().unwrap() { + for nameserver in &self.base.nameservers.clone().unwrap() { let mut tmp = String::from("Nameserver: "); tmp.push_str(&nameserver.host); ret_vec.push(tmp); @@ -202,16 +68,50 @@ impl WhoisData { return ret_vec; } - fn return_regex(caps: Vec, index: usize) -> String { - let data: String; - match caps.get(index) { - Some(tmp) => { - data = tmp.to_string(); - } - None => { - data = String::from("None"); - } + fn lookup(&mut self, domain: String) -> Vec { + let whois = WhoIs::from_path("servers.json").unwrap(); + let result: String = whois + .lookup(WhoIsLookupOptions::from_string(domain).unwrap()) + .unwrap(); + let registrar_regex = + RegexQuery::new(String::from(r"(?i)(.*registrar:|registrar *name:)(.*)")); + let domain_status_regex = + RegexQuery::new(String::from(r"(?i)(.*domain status:|.*status:)(.* )")); + // TODO: Capture the registrant info for each type + let registrant_name_regex = RegexQuery::new(String::from(r"(?i)(registrant.*name:)(.*)")); + let registrant_org_regex = + RegexQuery::new(String::from(r"(?i)(registrant org.*:|registrant:)(.*)")); + let registrant_email_regex = RegexQuery::new(String::from(r"(?i)(registrant email:)(.*)")); + let nameserver_regex = + RegexQuery::new(String::from(r"(?i)(nameservers*:|name servers*:)(.*)")); + let dnssec_regex = RegexQuery::new(String::from(r"(?i)(.*dnssec:)(.*)")); + + let registrar = WhoisData::return_regex(registrar_regex.get_matches(&result), 0); + let domain_status = WhoisData::return_regex(domain_status_regex.get_matches(&result), 0); + let reg_name = WhoisData::return_regex(registrant_name_regex.get_matches(&result), 0); + let reg_org = WhoisData::return_regex(registrant_org_regex.get_matches(&result), 0); + let reg_email = WhoisData::return_regex(registrant_email_regex.get_matches(&result), 0); + + let mut nameservers = vec![]; + for nameserver in nameserver_regex.get_matches(&result) { + nameservers.push(NameServer::new(nameserver)); } - data + let mut dnssec = dnssec_regex.get_matches(&result); + if dnssec.len() == 0 { + dnssec = vec![String::from("Failed to get DNSSEC")]; + } + // println!("{:?}", registrar[0]); + self.base.domain_status = Some(domain_status.clone()); + self.base.registrant = Some(vec![Registrant::new( + reg_name.clone(), + reg_org.clone(), + reg_email, + RegistrantType::Registrant, + )]); + self.base.nameservers = Some(nameservers); + self.base.dnssec = Some(dnssec[0].clone()); + self.base.registrar = Some(registrar.clone()); + + self.to_vec() } } diff --git a/src/whois/mod.rs b/src/whois/mod.rs new file mode 100644 index 0000000..0a50a90 --- /dev/null +++ b/src/whois/mod.rs @@ -0,0 +1,4 @@ +pub mod au; +pub mod default; +pub mod selector; +pub mod whois_base; diff --git a/src/whois/selector.rs b/src/whois/selector.rs new file mode 100644 index 0000000..50678a3 --- /dev/null +++ b/src/whois/selector.rs @@ -0,0 +1,30 @@ +use crate::whois::default; +use addr::parse_domain_name; + +use super::{au, whois_base::Whois}; + +pub fn select_whois_server(domain: String) -> Result, String> { + let domain = parse_domain_name(&domain.as_str()); + match domain { + Ok(val) => match val.suffix() { + ".com.au" => Ok(Box::new(au::_Whois::new())), + _ => Ok(Box::new(default::_Whois::new())), + }, + Err(_) => Err(String::from("Failed to select whois server")), + } +} + +// impl RegexQuery { +// pub fn new(expression: String) -> RegexQuery { +// let re = Regex::new(&expression).unwrap(); +// RegexQuery { re } +// } + +// pub fn get_matches(&self, haystack: &str) -> Vec { +// let mut results = vec![]; +// for (_, [_, rex2]) in self.re.captures_iter(haystack).map(|c| c.extract()) { +// results.push(String::from(rex2.trim())); +// } +// results +// } +// } diff --git a/src/whois/whois_base.rs b/src/whois/whois_base.rs new file mode 100644 index 0000000..33ca6b6 --- /dev/null +++ b/src/whois/whois_base.rs @@ -0,0 +1,104 @@ +use core::fmt; +use regex::Regex; + +pub trait Whois { + fn new() -> Self + where + Self: Sized; + fn lookup(&mut self, domain: String) -> Vec; + fn to_vec(&self) -> Vec; +} + +#[derive(Debug, Clone)] +pub enum RegistrantType { + Registrant, + Admin, + Tech, + Billing, +} + +pub struct RegexQuery { + re: Regex, +} + +impl RegexQuery { + pub fn new(expression: String) -> RegexQuery { + let re = Regex::new(&expression).unwrap(); + RegexQuery { re } + } + + pub fn get_matches(&self, haystack: &str) -> Vec { + let mut results = vec![]; + for (_, [_, rex2]) in self.re.captures_iter(haystack).map(|c| c.extract()) { + results.push(String::from(rex2.trim())); + } + results + } +} + +#[derive(Debug, Clone)] +pub struct NameServer { + pub host: String, +} + +impl NameServer { + pub fn new(host: String) -> NameServer { + NameServer { host } + } +} + +#[derive(Debug, Clone)] +pub struct Registrant { + pub name: String, + pub org: String, + pub email: String, + pub rtype: RegistrantType, +} + +impl fmt::Display for Registrant { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "") + } +} + +impl Registrant { + pub fn new(name: String, org: String, email: String, rtype: RegistrantType) -> Registrant { + Registrant { + name, + org, + email, + rtype, + } + } +} +pub struct WhoisData { + pub registrar: Option, + pub domain_status: Option, + pub registrant: Option>, + pub nameservers: Option>, + pub dnssec: Option, +} + +impl WhoisData { + pub fn new() -> WhoisData { + WhoisData { + registrar: None, + domain_status: None, + registrant: None, + nameservers: None, + dnssec: None, + } + } + pub fn return_regex(caps: Vec, index: usize) -> String { + let data: String; + match caps.get(index) { + Some(tmp) => { + data = tmp.to_string(); + } + None => { + data = String::from("None"); + } + } + data + } +}