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.
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"

View File

@ -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"] }

View File

@ -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(),
}
}
}

View File

@ -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);
}

View File

@ -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<B: Backend>(terminal: &mut Terminal<B>, 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();

View File

@ -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<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) {
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());

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 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<String> {
let mut results = vec![];
for (_, [_, rex2]) in self.re.captures_iter(haystack).map(|c| c.extract()) {
results.push(String::from(rex2.trim()));
}
results
impl Whois for _Whois {
fn new() -> _Whois {
_Whois {
base: WhoisData::new(),
}
}
#[derive(Debug, Clone)]
struct NameServer {
host: String,
fn to_vec(&self) -> Vec<String> {
let mut ret_vec: Vec<String> = vec![];
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 {
fn new(host: String) -> NameServer {
NameServer { host }
}
for nameserver in &self.base.nameservers.clone().unwrap() {
let mut tmp = String::from("Nameserver: ");
tmp.push_str(&nameserver.host);
ret_vec.push(tmp);
}
#[derive(Debug, Clone)]
struct Registrant {
name: String,
org: String,
email: String,
rtype: RegistrantType,
return ret_vec;
}
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<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 {
fn lookup(&mut self, domain: String) -> Vec<String> {
let whois = WhoIs::from_path("servers.json").unwrap();
let result: String = whois
.lookup(WhoIsLookupOptions::from_string(domain).unwrap())
@ -132,86 +101,17 @@ impl WhoisData {
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(
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,
)]),
nameservers: Some(nameservers),
dnssec: Some(dnssec[0].clone()),
eligibility_type: None,
}
}
)]);
self.base.nameservers = Some(nameservers);
self.base.dnssec = Some(dnssec[0].clone());
self.base.registrar = Some(registrar.clone());
pub fn to_vec(&self) -> Vec<String> {
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
self.to_vec()
}
}

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
}
}