Changed whois system to trait based system

This commit is contained in:
Benjamyn Love 2024-04-21 17:17:22 +10:00
parent 9d71ba17a5
commit 4515f35181
11 changed files with 351 additions and 179 deletions

47
Cargo.lock generated
View File

@ -2,6 +2,17 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 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]] [[package]]
name = "addr2line" name = "addr2line"
version = "0.21.0" version = "0.21.0"
@ -308,6 +319,7 @@ checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
name = "dns_lookup_project" name = "dns_lookup_project"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"addr",
"chrono", "chrono",
"configparser", "configparser",
"crossterm", "crossterm",
@ -563,6 +575,16 @@ dependencies = [
"cc", "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]] [[package]]
name = "idna" name = "idna"
version = "0.4.0" version = "0.4.0"
@ -857,6 +879,21 @@ dependencies = [
"unicode-ident", "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]] [[package]]
name = "ptr_meta" name = "ptr_meta"
version = "0.1.4" version = "0.1.4"
@ -877,6 +914,16 @@ dependencies = [
"syn 1.0.109", "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]] [[package]]
name = "quick-error" name = "quick-error"
version = "1.2.3" version = "1.2.3"

View File

@ -6,12 +6,13 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
configparser = "3.0.4" configparser = "*"
crossterm = "0.27.0" crossterm = "*"
hickory-resolver = { version = "0.24.0" } hickory-resolver = { version = "0.24.0" }
ratatui = "0.26.1" ratatui = "*"
regex = "1.10.3" regex = "*"
whois-rust = "1.6.0" whois-rust = "*"
serde = { version = "*", features = ["derive"] } serde = { version = "*", features = ["derive"] }
serde_json = "*" serde_json = "*"
chrono = "0.4.37" chrono = "*"
addr = { version = "0.15.6", features = ["publicsuffix"] }

View File

@ -25,7 +25,6 @@ pub struct App {
pub current_state: CurrentState, pub current_state: CurrentState,
pub menu_state: MenuState, pub menu_state: MenuState,
pub config: Config, pub config: Config,
pub state: ListState,
} }
impl App { impl App {
@ -38,7 +37,6 @@ impl App {
current_state: CurrentState::Lookup, current_state: CurrentState::Lookup,
menu_state: MenuState::Main, menu_state: MenuState::Main,
config: Config::from_file("test.ini".to_string()), config: Config::from_file("test.ini".to_string()),
state: ListState::default(),
} }
} }
} }

View File

@ -53,9 +53,9 @@ impl fmt::Display for Domain {
} }
impl Domain { impl Domain {
pub fn new(domain: String) -> Domain { pub fn new(domain: &String) -> Domain {
Domain { Domain {
domain_name: domain, domain_name: String::from(domain),
subdomains: vec![], subdomains: vec![],
a_records: vec![], a_records: vec![],
aaaa_records: vec![], aaaa_records: vec![],
@ -116,7 +116,7 @@ impl Domain {
let mut new_domain = String::from(subdomain); let mut new_domain = String::from(subdomain);
new_domain.push_str("."); new_domain.push_str(".");
new_domain.push_str(&self.domain_name); new_domain.push_str(&self.domain_name);
let subdomain = Domain::new(new_domain); let subdomain = Domain::new(&new_domain);
self.subdomains.push(subdomain); self.subdomains.push(subdomain);
// println!("Added: {}", new_domain); // println!("Added: {}", new_domain);
} }

View File

@ -4,9 +4,6 @@ use crate::domain::Domain;
mod config; mod config;
use crate::config::Config; use crate::config::Config;
mod whois;
use crate::whois::WhoisData;
mod app; mod app;
use crate::app::*; use crate::app::*;
@ -16,6 +13,10 @@ use crate::ui::*;
mod fsutil; mod fsutil;
use crate::fsutil::*; use crate::fsutil::*;
mod whois;
use whois::default;
use whois::selector::*;
use crossterm::event::{ use crossterm::event::{
self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind, self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind,
}; };
@ -78,12 +79,19 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>, app: &mut App) -> io::Result<
// Ignore empty input // Ignore empty input
continue; continue;
} }
let mut domain = Domain::new(app.domain_input.clone()); let mut domain = Domain::new(&app.domain_input);
domain.apply_config(&app.config); domain.apply_config(&app.config);
domain.lookup_all_records(); domain.lookup_all_records();
app.dns_info = domain.to_vec(); app.dns_info = domain.to_vec();
let whois = WhoisData::new(app.domain_input.clone()); let whois_server = select_whois_server(app.domain_input.clone());
app.whois_info = whois.to_vec(); 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 => { KeyCode::Delete => {
app.domain_input = String::new(); app.domain_input = String::new();

View File

@ -1,9 +1,10 @@
use ratatui::{ use ratatui::{
layout::{Constraint, Direction, Layout, Rect}, layout::{Constraint, Direction, Layout, Rect},
prelude::*,
style::{Color, Modifier, Style, Styled}, style::{Color, Modifier, Style, Styled},
text::{Line, Span, Text}, text::{Line, Span, Text},
widgets::{ widgets::{
Block, Borders, Clear, HighlightSpacing, List, ListDirection, ListItem, ListState, Block, Borders, Clear, HighlightSpacing, List, ListDirection, ListItem, ListState, Padding,
Paragraph, Wrap, Paragraph, Wrap,
}, },
Frame, Frame,
@ -14,6 +15,61 @@ use crate::{
CurrentState, DomainData, MenuState, CurrentState, DomainData, MenuState,
}; };
struct StatefulList<'a> {
state: ListState,
items: Vec<ListItem<'a>>,
last_selected: Option<usize>,
}
impl StatefulList<'_> {
fn with_items<'a>(items: Vec<String>) -> StatefulList<'a> {
let mut veclist = Vec::<ListItem>::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) { pub fn ui(f: &mut Frame, app: &mut App) {
if f.size().width > 100 { if f.size().width > 100 {
app.render_direction = AppRenderDir::Horizontal; app.render_direction = AppRenderDir::Horizontal;
@ -44,11 +100,12 @@ pub fn ui(f: &mut Frame, app: &mut App) {
let title_block = Block::default() let title_block = Block::default()
.borders(Borders::ALL) .borders(Borders::ALL)
.style(Style::default()); .style(Style::default())
.padding(Padding::horizontal(2));
let title = Paragraph::new(Span::styled( let title = Paragraph::new(Span::styled(
"Dns Lookup tool", "Dns Lookup tool",
Style::default().fg(Color::White), Style::default().fg(Color::Yellow),
)) ))
.block(title_block); .block(title_block);
@ -277,7 +334,7 @@ pub fn ui(f: &mut Frame, app: &mut App) {
.highlight_symbol(">>") .highlight_symbol(">>")
.highlight_spacing(HighlightSpacing::Always) .highlight_spacing(HighlightSpacing::Always)
.repeat_highlight_symbol(true); .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()); let footer_block = Block::new().borders(Borders::ALL).style(Style::default());

23
src/whois/au.rs Normal file
View File

@ -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<String> {
vec![String::new()]
}
fn lookup(&mut self, domain: String) -> Vec<String> {
self.to_vec()
}
}

View File

@ -1,105 +1,74 @@
use core::fmt; use crate::whois::whois_base::{
use hickory_resolver::proto::{ NameServer, RegexQuery, Registrant, RegistrantType, Whois, WhoisData,
rr::{domain, rdata::name},
xfer::dns_handle,
}; };
use regex::Regex;
use whois_rust::{WhoIs, WhoIsLookupOptions}; use whois_rust::{WhoIs, WhoIsLookupOptions};
pub struct _Whois {
#[derive(Debug, Clone)] base: WhoisData,
enum RegistrantType {
Registrant,
Admin,
Tech,
Billing,
} }
struct RegexQuery { impl Whois for _Whois {
re: Regex, fn new() -> _Whois {
} _Whois {
base: WhoisData::new(),
impl RegexQuery {
fn new(expression: String) -> RegexQuery {
let re = Regex::new(&expression).unwrap();
RegexQuery { re }
}
fn get_matches(&self, haystack: &str) -> Vec<String> {
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)] fn to_vec(&self) -> Vec<String> {
struct NameServer { let mut ret_vec: Vec<String> = vec![];
host: String, let mut registrar = String::from("Registrar: ");
registrar.push_str(&self.base.registrar.clone().unwrap());
ret_vec.push(registrar);
let mut domain_status = String::from("Status: ");
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.base.dnssec.clone().unwrap());
ret_vec.push(dnssec);
let mut registrant_type = String::new();
for registrant in &self.base.registrant.clone().unwrap() {
match registrant.rtype {
RegistrantType::Admin => {
registrant_type.push_str("Admin ");
}
RegistrantType::Billing => {
registrant_type.push_str("Billing ");
}
RegistrantType::Tech => {
registrant_type.push_str("Tech ");
}
RegistrantType::Registrant => {
registrant_type.push_str("Registrant ");
}
}
let mut name = registrant_type.clone();
name.push_str("Name: ");
name.push_str(&registrant.name);
ret_vec.push(name);
let mut org = registrant_type.clone();
org.push_str("Organisation: ");
org.push_str(&registrant.org);
ret_vec.push(org);
let mut email = registrant_type.clone();
email.push_str("Email: ");
email.push_str(&registrant.email);
ret_vec.push(email);
} }
impl NameServer { for nameserver in &self.base.nameservers.clone().unwrap() {
fn new(host: String) -> NameServer { let mut tmp = String::from("Nameserver: ");
NameServer { host } tmp.push_str(&nameserver.host);
} ret_vec.push(tmp);
} }
#[derive(Debug, Clone)] return ret_vec;
struct Registrant {
name: String,
org: String,
email: String,
rtype: RegistrantType,
} }
impl fmt::Display for Registrant { fn lookup(&mut self, domain: String) -> Vec<String> {
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<String>,
domain_status: Option<String>,
registrant: Option<Vec<Registrant>>,
nameservers: Option<Vec<NameServer>>,
dnssec: Option<String>,
eligibility_type: Option<Eligibility>,
}
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 whois = WhoIs::from_path("servers.json").unwrap();
let result: String = whois let result: String = whois
.lookup(WhoIsLookupOptions::from_string(domain).unwrap()) .lookup(WhoIsLookupOptions::from_string(domain).unwrap())
@ -132,86 +101,17 @@ impl WhoisData {
dnssec = vec![String::from("Failed to get DNSSEC")]; dnssec = vec![String::from("Failed to get DNSSEC")];
} }
// println!("{:?}", registrar[0]); // println!("{:?}", registrar[0]);
WhoisData { self.base.domain_status = Some(domain_status.clone());
registrar: Some(registrar.clone()), self.base.registrant = Some(vec![Registrant::new(
domain_status: Some(domain_status.clone()),
registrant: Some(vec![Registrant::new(
reg_name.clone(), reg_name.clone(),
reg_org.clone(), reg_org.clone(),
reg_email, reg_email,
RegistrantType::Registrant, RegistrantType::Registrant,
)]), )]);
nameservers: Some(nameservers), self.base.nameservers = Some(nameservers);
dnssec: Some(dnssec[0].clone()), self.base.dnssec = Some(dnssec[0].clone());
eligibility_type: None, self.base.registrar = Some(registrar.clone());
}
}
pub fn to_vec(&self) -> Vec<String> { self.to_vec()
let mut ret_vec: Vec<String> = vec![];
let mut registrar = String::from("Registrar: ");
registrar.push_str(&self.registrar.clone().unwrap());
ret_vec.push(registrar);
let mut domain_status = String::from("Status: ");
domain_status.push_str(&self.domain_status.clone().unwrap());
ret_vec.push(domain_status);
let mut dnssec = String::from("DNSSEC: ");
dnssec.push_str(&self.dnssec.clone().unwrap());
ret_vec.push(dnssec);
let mut registrant_type = String::new();
for registrant in &self.registrant.clone().unwrap() {
match registrant.rtype {
RegistrantType::Admin => {
registrant_type.push_str("Admin ");
}
RegistrantType::Billing => {
registrant_type.push_str("Billing ");
}
RegistrantType::Tech => {
registrant_type.push_str("Tech ");
}
RegistrantType::Registrant => {
registrant_type.push_str("Registrant ");
}
}
let mut name = registrant_type.clone();
name.push_str("Name: ");
name.push_str(&registrant.name);
ret_vec.push(name);
let mut org = registrant_type.clone();
org.push_str("Organisation: ");
org.push_str(&registrant.org);
ret_vec.push(org);
let mut email = registrant_type.clone();
email.push_str("Email: ");
email.push_str(&registrant.email);
ret_vec.push(email);
}
for nameserver in &self.nameservers.clone().unwrap() {
let mut tmp = String::from("Nameserver: ");
tmp.push_str(&nameserver.host);
ret_vec.push(tmp);
}
return ret_vec;
}
fn return_regex(caps: Vec<String>, index: usize) -> String {
let data: String;
match caps.get(index) {
Some(tmp) => {
data = tmp.to_string();
}
None => {
data = String::from("None");
}
}
data
} }
} }

4
src/whois/mod.rs Normal file
View File

@ -0,0 +1,4 @@
pub mod au;
pub mod default;
pub mod selector;
pub mod whois_base;

30
src/whois/selector.rs Normal file
View File

@ -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<Box<dyn Whois>, 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<String> {
// let mut results = vec![];
// for (_, [_, rex2]) in self.re.captures_iter(haystack).map(|c| c.extract()) {
// results.push(String::from(rex2.trim()));
// }
// results
// }
// }

104
src/whois/whois_base.rs Normal file
View File

@ -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<String>;
fn to_vec(&self) -> Vec<String>;
}
#[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<String> {
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<String>,
pub domain_status: Option<String>,
pub registrant: Option<Vec<Registrant>>,
pub nameservers: Option<Vec<NameServer>>,
pub dnssec: Option<String>,
}
impl WhoisData {
pub fn new() -> WhoisData {
WhoisData {
registrar: None,
domain_status: None,
registrant: None,
nameservers: None,
dnssec: None,
}
}
pub fn return_regex(caps: Vec<String>, index: usize) -> String {
let data: String;
match caps.get(index) {
Some(tmp) => {
data = tmp.to_string();
}
None => {
data = String::from("None");
}
}
data
}
}