From d932bb234b640e5a61b3a8bf1b716662170226d0 Mon Sep 17 00:00:00 2001 From: Benjamyn Love Date: Fri, 29 Mar 2024 19:29:04 +1100 Subject: [PATCH 01/16] Added UI base code --- Cargo.lock | 256 +++++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 2 + src/app.rs | 29 ++++++ src/main.rs | 123 +++++++++++++++++++------ src/ui.rs | 126 ++++++++++++++++++++++++++ 5 files changed, 507 insertions(+), 29 deletions(-) create mode 100644 src/app.rs create mode 100644 src/ui.rs diff --git a/Cargo.lock b/Cargo.lock index 1e072e4..1a05bec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,6 +28,18 @@ dependencies = [ "version_check", ] +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -37,6 +49,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + [[package]] name = "arrayvec" version = "0.7.4" @@ -81,6 +99,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + [[package]] name = "bitvec" version = "1.0.1" @@ -156,6 +180,21 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" +dependencies = [ + "rustversion", +] + [[package]] name = "cc" version = "1.0.90" @@ -174,12 +213,50 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "compact_str" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "ryu", + "static_assertions", +] + [[package]] name = "configparser" version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec6d3da8e550377a85339063af6e3735f4b1d9392108da4e083a1b3b9820288" +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags 2.5.0", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "data-encoding" version = "2.5.0" @@ -191,7 +268,9 @@ name = "dns_lookup_project" version = "0.1.0" dependencies = [ "configparser", + "crossterm", "hickory-resolver", + "ratatui", "regex", "whois-rust", ] @@ -208,6 +287,12 @@ dependencies = [ "syn 2.0.53", ] +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + [[package]] name = "endian-type" version = "0.1.2" @@ -330,7 +415,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash", + "ahash 0.7.8", ] [[package]] @@ -338,6 +423,10 @@ name = "hashbrown" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash 0.8.11", + "allocator-api2", +] [[package]] name = "heck" @@ -437,6 +526,12 @@ dependencies = [ "hashbrown 0.14.3", ] +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + [[package]] name = "ipconfig" version = "0.3.2" @@ -455,6 +550,15 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.10" @@ -483,6 +587,21 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "lru" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" +dependencies = [ + "hashbrown 0.14.3", +] + [[package]] name = "lru-cache" version = "0.1.2" @@ -520,6 +639,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", + "log", "wasi", "windows-sys 0.48.0", ] @@ -590,6 +710,12 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -736,13 +862,33 @@ dependencies = [ "getrandom", ] +[[package]] +name = "ratatui" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcb12f8fbf6c62614b0d56eb352af54f6a22410c3b079eb53ee93c7b97dd31d8" +dependencies = [ + "bitflags 2.5.0", + "cassowary", + "compact_str", + "crossterm", + "indoc", + "itertools", + "lru", + "paste", + "stability", + "strum", + "unicode-segmentation", + "unicode-width", +] + [[package]] name = "redox_syscall" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -844,6 +990,12 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "ryu" version = "1.0.17" @@ -899,6 +1051,36 @@ dependencies = [ "serde", ] +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + [[package]] name = "simdutf8" version = "0.1.4" @@ -930,6 +1112,44 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "stability" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd1b177894da2a2d9120208c3386066af06a488255caabc5de8ddca22dbc3ce" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strum" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.53", +] + [[package]] name = "syn" version = "1.0.109" @@ -1134,6 +1354,18 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + [[package]] name = "url" version = "2.5.0" @@ -1397,3 +1629,23 @@ checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ "tap", ] + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.53", +] diff --git a/Cargo.toml b/Cargo.toml index e8aa0fd..82d2b7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,8 @@ edition = "2021" [dependencies] configparser = "3.0.4" +crossterm = "0.27.0" hickory-resolver = { version = "0.24.0" } +ratatui = "0.26.1" regex = "1.10.3" whois-rust = "1.6.0" diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..83e20d9 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,29 @@ +pub enum AppRenderDir { + Vertical, + Horizontal, +} + +pub enum CurrentState { + Lookup, + Menu, +} + +pub struct App { + pub domain_input: String, + pub whois_info: Vec, + pub dns_info: Vec, + pub render_direction: AppRenderDir, + pub current_state: CurrentState, +} + +impl App { + pub fn new() -> App { + App { + domain_input: String::new(), + whois_info: vec![], + dns_info: vec![], + render_direction: AppRenderDir::Horizontal, + current_state: CurrentState::Lookup, + } + } +} diff --git a/src/main.rs b/src/main.rs index 19e738b..94b639c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,38 +7,107 @@ use crate::config::Config; mod whois; use crate::whois::WhoisData; -use std::io; -use std::io::Write; +mod app; +use crate::app::*; -fn main() { - // let mut running = true; +mod ui; +use crate::ui::*; + +use crossterm::event::{ + self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind, +}; +use crossterm::execute; +use crossterm::terminal::{ + disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, +}; +use ratatui::backend::{Backend, CrosstermBackend}; +use ratatui::Terminal; +use std::error::Error; +use std::io::Write; +use std::io::{self, stderr}; + +fn main() -> Result<(), Box> { + enable_raw_mode()?; + let mut stderr = stderr(); + execute!(stderr, EnterAlternateScreen, EnableMouseCapture)?; + + let backend = CrosstermBackend::new(stderr); + let mut terminal = Terminal::new(backend)?; + + // Initialise the App and config + let mut app = App::new(); let config = Config::from_file("test.ini".to_string()); + + // Run the app + let res = run_app(&mut terminal, &mut app, &config); + + disable_raw_mode()?; + execute!( + terminal.backend_mut(), + LeaveAlternateScreen, + DisableMouseCapture + )?; + terminal.show_cursor()?; + // Clear screen + // print!("{}[2J", 27 as char); + // + // loop { + // let mut domain = String::new(); + // print!("Enter domain name: "); + // io::stdout().flush().expect("Failed to flush buffer"); + // match io::stdin().read_line(&mut domain) { + // Ok(_n) => { + // domain = String::from(domain.trim()); + // match domain.as_ref() { + // "q" => break, + // "" => continue, + // _ => { + // let mut dns = Domain::new(domain.clone()); + // dns.apply_config(&config); + // dns.lookup_all_records(); + // let whois = WhoisData::new(domain); + // println!("DNS: {}\n\n\nWhois: {}", dns, whois); // Replace this with fancy UI render update command + // } + // } + // } + // Err(err) => { + // println!("{}", err); + // } + // } + // } + Ok(()) +} + +fn run_app( + terminal: &mut Terminal, + app: &mut App, + config: &Config, +) -> io::Result { loop { - let mut domain = String::new(); - print!("Enter domain name: "); - io::stdout().flush().expect("Failed to flush buffer"); - match io::stdin().read_line(&mut domain) { - Ok(_n) => { - if domain.trim() == "q" { - break; - } - let mut dns = Domain::new(String::from(domain.trim())); - dns.apply_config(&config); - dns.lookup_all_records(); - let whois = WhoisData::new(String::from(domain.trim())); - println!("DNS: {}\n\n\nWhois: {}", dns, whois); + terminal.draw(|f| ui(f, app))?; + + if let Event::Key(key) = event::read()? { + if key.kind == event::KeyEventKind::Release { + continue; } - Err(err) => { - println!("{}", err); + match app.current_state { + CurrentState::Lookup => match key.code { + KeyCode::Esc => return Ok(false), + KeyCode::Backspace => { + app.domain_input.pop(); + } + KeyCode::Char(char) => { + app.domain_input.push(char); + } + KeyCode::Enter => { + // This will do the lookup and populate the UI with the info + let mut domain = Domain::new(app.domain_input.clone()); + domain.lookup_all_records(); + } + _ => {} + }, + CurrentState::Menu => {} } } } - // let mut test = Domain::new("ventraip.com.au".to_string()); - // let config = Config::from_file("test.ini".to_string()); - // test.apply_config(&config); - - // test.lookup_all_records(); - // let whois = WhoisData::new(test.domain_name.clone()); - - // println!("Domain: {}\n{}", test.domain_name.clone(), whois); } diff --git a/src/ui.rs b/src/ui.rs new file mode 100644 index 0000000..f37367c --- /dev/null +++ b/src/ui.rs @@ -0,0 +1,126 @@ +use ratatui::{ + layout::{Constraint, Direction, Layout, Rect}, + style::{Color, Style, Styled}, + text::{Line, Span, Text}, + widgets::{Block, Borders, Clear, List, ListItem, Paragraph, Wrap}, + Frame, +}; + +use crate::{ + app::{App, AppRenderDir}, + CurrentState, +}; + +pub fn ui(f: &mut Frame, app: &App) { + match app.current_state { + CurrentState::Lookup => { + match app.render_direction { + /* + Initial contianer is vertical but we have the large center Rect that we will split horizontally + Constraint 1: 30px title bar + Constraint 2: Rest of unused space + Constraint 3: 30px input / key hints + + Constraint 2.a: 50% of usable space for whois + Constraint 2.b: 50% of usable space for dns + */ + AppRenderDir::Horizontal => { + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(3), + Constraint::Min(1), + Constraint::Length(3), + ]) + .split(f.size()); + + let title_block = Block::default() + .borders(Borders::ALL) + .style(Style::default()); + + let title = Paragraph::new(Span::styled( + "Dns Lookup tool", + Style::default().fg(Color::White), + )) + .block(title_block); + + f.render_widget(title, chunks[0]); + + let data_layout = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) + .split(chunks[1]); + + let whois_block = Block::default() + .borders(Borders::RIGHT) + .style(Style::default()); + + let whois_text = Paragraph::new(Text::styled( + "Whois data in +style +with +multiline support", + Style::default().fg(Color::Green), + )) + .block(whois_block); + f.render_widget(whois_text, data_layout[0]); + + let dns_block = Block::default().style(Style::default()); + let dns_text = Paragraph::new(Text::styled( + "Dns records +also +with +multiline +support", + Style::default().fg(Color::Red), + )) + .block(dns_block); + f.render_widget(dns_text, data_layout[1]); + + let footer_chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) + .split(chunks[2]); + + let domain_block = Block::new().borders(Borders::ALL).style(Style::default()); + let mut domain_string = String::from("Domain: "); + domain_string.push_str(&app.domain_input); + let domain_text = Paragraph::new(Text::styled( + domain_string, + Style::default().fg(Color::Blue), + )) + .block(domain_block); + f.render_widget(domain_text, footer_chunks[0]); + + let key_hint_block = Block::default() + .borders(Borders::ALL) + .style(Style::default()); + let key_hint = Paragraph::new(Text::styled( + "[ESC] Quit / [Enter] Check domain", + Style::default().fg(Color::Red), + )) + .block(key_hint_block); + f.render_widget(key_hint, footer_chunks[1]); + } + /* + Constraint 1: 30px title bar + Constraint 2: 50% of unused space for whois + Constraint 3: 50% of unused space for dns + Constraint 4: 30px input / key hints + */ + AppRenderDir::Vertical => { + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Max(30), + Constraint::Percentage(50), + Constraint::Percentage(50), + Constraint::Max(30), + ]) + .split(f.size()); + } + } + } + CurrentState::Menu => {} + } +} -- 2.43.5 From 067112ae2d5abe0659cc1e6143fb5b845c92e2cf Mon Sep 17 00:00:00 2001 From: Benjamyn Love Date: Fri, 29 Mar 2024 23:39:39 +1100 Subject: [PATCH 02/16] Base UI implemented --- src/domain.rs | 45 +++++++++++++++++++++++++++++++++++++++ src/main.rs | 4 ++++ src/ui.rs | 57 +++++++++++++++++++++++++++++-------------------- src/whois.rs | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 142 insertions(+), 23 deletions(-) diff --git a/src/domain.rs b/src/domain.rs index c286838..24dab9e 100644 --- a/src/domain.rs +++ b/src/domain.rs @@ -67,6 +67,51 @@ impl Domain { } } + pub fn to_vec(&self) -> Vec { + let mut ret_vec: Vec = vec![]; + for rec in &self.a_records { + let mut tmp = String::from("A: "); + tmp.push_str(rec.to_string().as_ref()); + ret_vec.push(tmp); + } + for rec in &self.aaaa_records { + let mut tmp = String::from("AAAA: "); + tmp.push_str(rec.to_string().as_ref()); + ret_vec.push(tmp); + } + for rec in &self.mx_records { + let mut tmp = String::from("MX: "); + tmp.push_str(rec.to_string().as_ref()); + ret_vec.push(tmp); + } + for rec in &self.txt_records { + let mut tmp = String::from("TXT: "); + tmp.push_str(rec.to_string().as_ref()); + ret_vec.push(tmp); + } + for rec in &self.ns_records { + let mut tmp = String::from("NS: "); + tmp.push_str(rec.to_string().as_ref()); + ret_vec.push(tmp); + } + for rec in &self.soa_records { + let mut tmp = String::from("SOA: "); + tmp.push_str(rec.to_string().as_ref()); + ret_vec.push(tmp); + } + for subdomain in &self.subdomains { + let mut subdomain_name = String::from(subdomain.domain_name.clone()); + subdomain_name.push_str("\t"); + for rec in &subdomain.a_records { + let mut tmp = String::from(&subdomain_name); + tmp.push_str(" A: "); + tmp.push_str(rec.to_string().as_ref()); + ret_vec.push(tmp); + } + } + return ret_vec; + } + pub fn append_subdomain(&mut self, subdomain: String) { let mut new_domain = String::from(subdomain); new_domain.push_str("."); diff --git a/src/main.rs b/src/main.rs index 94b639c..38a70d0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -102,7 +102,11 @@ fn run_app( KeyCode::Enter => { // This will do the lookup and populate the UI with the info let mut domain = Domain::new(app.domain_input.clone()); + domain.apply_config(&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(); } _ => {} }, diff --git a/src/ui.rs b/src/ui.rs index f37367c..a78656c 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -51,31 +51,42 @@ pub fn ui(f: &mut Frame, app: &App) { .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) .split(chunks[1]); - let whois_block = Block::default() - .borders(Borders::RIGHT) - .style(Style::default()); + // let whois_block = Block::default() + // .borders(Borders::RIGHT) + // .style(Style::default()); - let whois_text = Paragraph::new(Text::styled( - "Whois data in -style -with -multiline support", - Style::default().fg(Color::Green), - )) - .block(whois_block); - f.render_widget(whois_text, data_layout[0]); + // let whois_text = Paragraph::new(Text::styled( + // "Whois data in + // style + // with + // multiline support", + // Style::default().fg(Color::Green), + // )) + // .block(whois_block); - let dns_block = Block::default().style(Style::default()); - let dns_text = Paragraph::new(Text::styled( - "Dns records -also -with -multiline -support", - Style::default().fg(Color::Red), - )) - .block(dns_block); - f.render_widget(dns_text, data_layout[1]); + // Whois list + let mut whois_list_items = Vec::::new(); + for record in &app.whois_info { + whois_list_items.push(ListItem::new(Line::from(Span::styled( + record, + Style::default(), + )))) + } + let whois_list = List::new(whois_list_items); + + f.render_widget(whois_list, data_layout[0]); + + // DNS list + let mut dns_list_items = Vec::::new(); + for record in &app.dns_info { + dns_list_items.push(ListItem::new(Line::from(Span::styled( + record, + Style::default(), + )))) + } + let dns_list = List::new(dns_list_items); + + f.render_widget(dns_list, data_layout[1]); let footer_chunks = Layout::default() .direction(Direction::Horizontal) diff --git a/src/whois.rs b/src/whois.rs index 2687331..8749291 100644 --- a/src/whois.rs +++ b/src/whois.rs @@ -1,4 +1,8 @@ use core::fmt; +use hickory_resolver::proto::{ + rr::{domain, rdata::name}, + xfer::dns_handle, +}; use regex::Regex; use whois_rust::{WhoIs, WhoIsLookupOptions}; @@ -137,6 +141,61 @@ impl WhoisData { } } + pub fn to_vec(&self) -> Vec { + let mut ret_vec: Vec = vec![]; + let mut registrar = String::from("Registrar: "); + registrar.push_str(&self.registrar); + ret_vec.push(registrar); + + let mut domain_status = String::from("Status: "); + domain_status.push_str(&self.domain_status); + ret_vec.push(domain_status); + + let mut dnssec = String::from("DNSSEC: "); + dnssec.push_str(&self.dnssec); + ret_vec.push(dnssec); + + let mut registrant_type = String::new(); + for registrant in &self.registrant { + 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(®istrant.name); + ret_vec.push(name); + + let mut org = registrant_type.clone(); + org.push_str("Organisation: "); + org.push_str(®istrant.org); + ret_vec.push(org); + + let mut email = registrant_type.clone(); + email.push_str("Email: "); + email.push_str(®istrant.email); + ret_vec.push(email); + } + + for nameserver in &self.nameservers { + let mut tmp = String::from("Nameserver: "); + tmp.push_str(&nameserver.host); + ret_vec.push(tmp); + } + + return ret_vec; + } + fn return_regex(caps: Vec, index: usize) -> String { let data: String; match caps.get(index) { -- 2.43.5 From b7aa022bfd8637c32c981793c7f80880ee685e37 Mon Sep 17 00:00:00 2001 From: Benjamyn Love Date: Fri, 29 Mar 2024 23:45:40 +1100 Subject: [PATCH 03/16] Removed comments --- src/main.rs | 30 +----------------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/src/main.rs b/src/main.rs index 38a70d0..213d83f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,7 +23,6 @@ use crossterm::terminal::{ use ratatui::backend::{Backend, CrosstermBackend}; use ratatui::Terminal; use std::error::Error; -use std::io::Write; use std::io::{self, stderr}; fn main() -> Result<(), Box> { @@ -39,7 +38,7 @@ fn main() -> Result<(), Box> { let config = Config::from_file("test.ini".to_string()); // Run the app - let res = run_app(&mut terminal, &mut app, &config); + let _res = run_app(&mut terminal, &mut app, &config); disable_raw_mode()?; execute!( @@ -48,33 +47,6 @@ fn main() -> Result<(), Box> { DisableMouseCapture )?; terminal.show_cursor()?; - // Clear screen - // print!("{}[2J", 27 as char); - // - // loop { - // let mut domain = String::new(); - // print!("Enter domain name: "); - // io::stdout().flush().expect("Failed to flush buffer"); - // match io::stdin().read_line(&mut domain) { - // Ok(_n) => { - // domain = String::from(domain.trim()); - // match domain.as_ref() { - // "q" => break, - // "" => continue, - // _ => { - // let mut dns = Domain::new(domain.clone()); - // dns.apply_config(&config); - // dns.lookup_all_records(); - // let whois = WhoisData::new(domain); - // println!("DNS: {}\n\n\nWhois: {}", dns, whois); // Replace this with fancy UI render update command - // } - // } - // } - // Err(err) => { - // println!("{}", err); - // } - // } - // } Ok(()) } -- 2.43.5 From 7c6a4bf70ee675a9402f7961f16aaa13ac276492 Mon Sep 17 00:00:00 2001 From: Benjamyn Love Date: Fri, 29 Mar 2024 23:45:55 +1100 Subject: [PATCH 04/16] Removed more comments --- src/ui.rs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/ui.rs b/src/ui.rs index a78656c..25a1ed6 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -51,19 +51,6 @@ pub fn ui(f: &mut Frame, app: &App) { .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) .split(chunks[1]); - // let whois_block = Block::default() - // .borders(Borders::RIGHT) - // .style(Style::default()); - - // let whois_text = Paragraph::new(Text::styled( - // "Whois data in - // style - // with - // multiline support", - // Style::default().fg(Color::Green), - // )) - // .block(whois_block); - // Whois list let mut whois_list_items = Vec::::new(); for record in &app.whois_info { -- 2.43.5 From 80fd57810e36b9ff67d07686050c923c966894e4 Mon Sep 17 00:00:00 2001 From: Benjamyn Love Date: Sat, 30 Mar 2024 20:26:48 +1100 Subject: [PATCH 05/16] Added vertical UI Added reactivity --- src/app.rs | 2 +- src/main.rs | 3 +++ src/ui.rs | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 76 insertions(+), 5 deletions(-) diff --git a/src/app.rs b/src/app.rs index 83e20d9..7459853 100644 --- a/src/app.rs +++ b/src/app.rs @@ -22,7 +22,7 @@ impl App { domain_input: String::new(), whois_info: vec![], dns_info: vec![], - render_direction: AppRenderDir::Horizontal, + render_direction: AppRenderDir::Vertical, current_state: CurrentState::Lookup, } } diff --git a/src/main.rs b/src/main.rs index 213d83f..ef3541f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -80,6 +80,9 @@ fn run_app( let whois = WhoisData::new(app.domain_input.clone()); app.whois_info = whois.to_vec(); } + KeyCode::Delete => { + app.domain_input = String::new(); + } _ => {} }, CurrentState::Menu => {} diff --git a/src/ui.rs b/src/ui.rs index 25a1ed6..79b82b8 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -11,7 +11,12 @@ use crate::{ CurrentState, }; -pub fn ui(f: &mut Frame, app: &App) { +pub fn ui(f: &mut Frame, app: &mut App) { + if f.size().width > 100 { + app.render_direction = AppRenderDir::Horizontal; + } else { + app.render_direction = AppRenderDir::Vertical; + } match app.current_state { CurrentState::Lookup => { match app.render_direction { @@ -65,6 +70,7 @@ pub fn ui(f: &mut Frame, app: &App) { // DNS list let mut dns_list_items = Vec::::new(); + for record in &app.dns_info { dns_list_items.push(ListItem::new(Line::from(Span::styled( record, @@ -93,8 +99,9 @@ pub fn ui(f: &mut Frame, app: &App) { let key_hint_block = Block::default() .borders(Borders::ALL) .style(Style::default()); + let key_hint = Paragraph::new(Text::styled( - "[ESC] Quit / [Enter] Check domain", + "[ESC] Quit / [Enter] Check domain / [Del] Clear Input", Style::default().fg(Color::Red), )) .block(key_hint_block); @@ -110,12 +117,73 @@ pub fn ui(f: &mut Frame, app: &App) { let chunks = Layout::default() .direction(Direction::Vertical) .constraints([ - Constraint::Max(30), + Constraint::Length(3), Constraint::Percentage(50), Constraint::Percentage(50), - Constraint::Max(30), + Constraint::Length(3), ]) .split(f.size()); + + let title_block = Block::default() + .borders(Borders::ALL) + .style(Style::default()); + + let title = Paragraph::new(Span::styled( + "Dns Lookup tool", + Style::default().fg(Color::White), + )) + .block(title_block); + + f.render_widget(title, chunks[0]); + + // Whois list + let mut whois_list_items = Vec::::new(); + for record in &app.whois_info { + whois_list_items.push(ListItem::new(Line::from(Span::styled( + record, + Style::default(), + )))) + } + let whois_list = List::new(whois_list_items); + + f.render_widget(whois_list, chunks[1]); + + // DNS list + let mut dns_list_items = Vec::::new(); + for record in &app.dns_info { + dns_list_items.push(ListItem::new(Line::from(Span::styled( + record, + Style::default(), + )))) + } + let dns_list = List::new(dns_list_items); + + f.render_widget(dns_list, chunks[2]); + let footer_chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) + .split(chunks[3]); + + let domain_block = Block::new().borders(Borders::ALL).style(Style::default()); + let mut domain_string = String::from("Domain: "); + domain_string.push_str(&app.domain_input); + let domain_text = Paragraph::new(Text::styled( + domain_string, + Style::default().fg(Color::Blue), + )) + .block(domain_block); + f.render_widget(domain_text, footer_chunks[0]); + + let key_hint_block = Block::default() + .borders(Borders::ALL) + .style(Style::default()); + + let key_hint = Paragraph::new(Text::styled( + "[ESC] Quit / [Enter] Check domain", + Style::default().fg(Color::Red), + )) + .block(key_hint_block); + f.render_widget(key_hint, footer_chunks[1]); } } } -- 2.43.5 From c6d9b775bc44d4f7c6e56f2776874bb1d571f653 Mon Sep 17 00:00:00 2001 From: Benjamyn Love Date: Mon, 1 Apr 2024 18:04:30 +1100 Subject: [PATCH 06/16] Added Menu and save logic --- Cargo.lock | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 3 ++ error.log | 87 +++++++++++++++++++++++++++++++ src/config.rs | 15 ++++-- src/fs.rs | 46 +++++++++++++++++ src/main.rs | 28 +++++++++- src/ui.rs | 60 +++++++++++++++++++++- src/whois.rs | 50 ++++++++++-------- test.ini | 3 +- 9 files changed, 400 insertions(+), 31 deletions(-) create mode 100644 error.log create mode 100644 src/fs.rs diff --git a/Cargo.lock b/Cargo.lock index 1a05bec..37f2362 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,6 +55,21 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "arrayvec" version = "0.7.4" @@ -141,6 +156,12 @@ dependencies = [ "syn_derive", ] +[[package]] +name = "bumpalo" +version = "3.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" + [[package]] name = "byte-unit" version = "5.1.4" @@ -213,6 +234,20 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "chrono" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.4", +] + [[package]] name = "compact_str" version = "0.7.1" @@ -232,6 +267,12 @@ version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec6d3da8e550377a85339063af6e3735f4b1d9392108da4e083a1b3b9820288" +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + [[package]] name = "crossterm" version = "0.27.0" @@ -267,11 +308,14 @@ checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" name = "dns_lookup_project" version = "0.1.0" dependencies = [ + "chrono", "configparser", "crossterm", "hickory-resolver", "ratatui", "regex", + "serde", + "serde_json", "whois-rust", ] @@ -496,6 +540,29 @@ dependencies = [ "winapi", ] +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "idna" version = "0.4.0" @@ -565,6 +632,15 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "libc" version = "0.2.153" @@ -1429,6 +1505,60 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.53", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.53", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + [[package]] name = "whois-rust" version = "1.6.0" @@ -1470,6 +1600,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.4", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index 82d2b7b..3a2b120 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,6 @@ hickory-resolver = { version = "0.24.0" } ratatui = "0.26.1" regex = "1.10.3" whois-rust = "1.6.0" +serde = { version = "*", features = ["derive"] } +serde_json = "*" +chrono = "0.4.37" diff --git a/error.log b/error.log new file mode 100644 index 0000000..8f98f67 --- /dev/null +++ b/error.log @@ -0,0 +1,87 @@ + Compiling dns_lookup_project v0.1.0 (/home/ben/Documents/Projects/rust/dns_lookup_project) +warning: unused imports: `domain`, `rdata::name`, `xfer::dns_handle` + --> src/whois.rs:3:10 + | +3 | rr::{domain, rdata::name}, + | ^^^^^^ ^^^^^^^^^^^ +4 | xfer::dns_handle, + | ^^^^^^^^^^^^^^^^ + | + = note: `#[warn(unused_imports)]` on by default + +warning: unused imports: `Clear`, `Rect`, `Wrap` + --> src/ui.rs:2:45 + | +2 | layout::{Constraint, Direction, Layout, Rect}, + | ^^^^ +... +5 | widgets::{Block, Borders, Clear, List, ListItem, Paragraph, Wrap}, + | ^^^^^ ^^^^ + +warning: unused import: `KeyEventKind` + --> src/main.rs:17:68 + | +17 | self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind, + | ^^^^^^^^^^^^ + +warning: unused import: `stderr` + --> src/main.rs:26:21 + | +26 | use std::io::{self, stderr, stdout}; + | ^^^^^^ + +warning: unused import: `Styled` + --> src/ui.rs:3:27 + | +3 | style::{Color, Style, Styled}, + | ^^^^^^ + +warning: variants `Admin`, `Tech`, and `Billing` are never constructed + --> src/whois.rs:12:5 + | +10 | enum RegistrantType { + | -------------- variants in this enum +11 | Registrant, +12 | Admin, + | ^^^^^ +13 | Tech, + | ^^^^ +14 | Billing, + | ^^^^^^^ + | + = note: `RegistrantType` has derived impls for the traits `Clone` and `Debug`, but these are intentionally ignored during dead code analysis + = note: `#[warn(dead_code)]` on by default + +warning: fields `e_type`, `id`, and `name` are never read + --> src/whois.rs:73:5 + | +72 | struct Eligibility { + | ----------- fields in this struct +73 | e_type: String, + | ^^^^^^ +74 | id: String, + | ^^ +75 | name: String, + | ^^^^ + +warning: field `eligibility_type` is never read + --> src/whois.rs:84:5 + | +78 | pub struct WhoisData { + | --------- field in this struct +... +84 | eligibility_type: Option, + | ^^^^^^^^^^^^^^^^ + +warning: variant `Menu` is never constructed + --> src/app.rs:8:5 + | +6 | pub enum CurrentState { + | ------------ variant in this enum +7 | Lookup, +8 | Menu, + | ^^^^ + +warning: `dns_lookup_project` (bin "dns_lookup_project") generated 9 warnings (run `cargo fix --bin "dns_lookup_project"` to apply 4 suggestions) + Finished dev [unoptimized + debuginfo] target(s) in 0.79s + Running `target/debug/dns_lookup_project` diff --git a/src/config.rs b/src/config.rs index c6bd528..421381e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,11 +4,16 @@ use core::fmt; pub struct Config { pub subdomains: Vec, pub wildcard_test: String, + pub data_dir: String, } impl fmt::Display for Config { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Subdomains: {:?}\nWildcard Test: {}", self.subdomains, self.wildcard_test) + write!( + f, + "Subdomains: {:?}\nWildcard Test: {}", + self.subdomains, self.wildcard_test + ) } } @@ -23,11 +28,13 @@ impl Config { for x in subdomains.split(',') { subvec.push(x.to_string()); } - + let wildcard_test = config.get("General", "wildcard_test"); + let datadir = config.get("General", "data_directory"); Config { subdomains: subvec, - wildcard_test: wildcard_test.unwrap() + wildcard_test: wildcard_test.unwrap(), + data_dir: datadir.unwrap(), } } -} \ No newline at end of file +} diff --git a/src/fs.rs b/src/fs.rs new file mode 100644 index 0000000..2b73cb0 --- /dev/null +++ b/src/fs.rs @@ -0,0 +1,46 @@ +use chrono::Local; +use serde::{Deserialize, Serialize}; +use std::error::Error; +use std::fs; +use std::path::Path; + +use crate::config::Config; + +#[derive(Serialize)] +pub struct DomainData { + name: String, + dns_records: Vec, + whois_info: Vec, +} + +impl DomainData { + pub fn new(domain: String, dns: Vec, whois_data: Vec) -> DomainData { + DomainData { + name: domain, + dns_records: dns, + whois_info: whois_data, + } + } + + pub fn save_lookup(&self, config: &Config) -> Result<(), Box> { + let output = serde_json::to_string(&self).unwrap(); + let date = Local::now(); + let filepath = format!( + "{}/lookups/{}-{}.json", + config.data_dir, + date.format("%Y-%m-%d_%H-%M-%S"), + self.name + ); + let data_dir = Path::new(&config.data_dir.clone()).exists(); + if data_dir != true { + match fs::create_dir_all(format!("{}/lookups", config.data_dir)) { + Ok(_val) => {} + Err(_err) => {} + } + } + fs::write(filepath, output)?; + + Ok(()) + // println!("{}", output); + } +} diff --git a/src/main.rs b/src/main.rs index ef3541f..b208e0c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,9 @@ use crate::app::*; mod ui; use crate::ui::*; +mod fs; +use crate::fs::*; + use crossterm::event::{ self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind, }; @@ -64,7 +67,9 @@ fn run_app( } match app.current_state { CurrentState::Lookup => match key.code { - KeyCode::Esc => return Ok(false), + KeyCode::Esc => { + app.current_state = CurrentState::Menu; + } KeyCode::Backspace => { app.domain_input.pop(); } @@ -73,6 +78,10 @@ fn run_app( } KeyCode::Enter => { // This will do the lookup and populate the UI with the info + if &app.domain_input == "" { + // Ignore empty input + continue; + } let mut domain = Domain::new(app.domain_input.clone()); domain.apply_config(&config); domain.lookup_all_records(); @@ -85,7 +94,22 @@ fn run_app( } _ => {} }, - CurrentState::Menu => {} + CurrentState::Menu => match key.code { + KeyCode::Esc => { + app.current_state = CurrentState::Lookup; + } + KeyCode::Char('s') => { + let domain_data = DomainData::new( + app.domain_input.clone(), + app.dns_info.clone(), + app.whois_info.clone(), + ); + domain_data.save_lookup(&config).unwrap(); + app.current_state = CurrentState::Lookup; + } + KeyCode::Char('q') => return Ok(false), + _ => {} + }, } } } diff --git a/src/ui.rs b/src/ui.rs index 79b82b8..1a5c9e9 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -101,7 +101,7 @@ pub fn ui(f: &mut Frame, app: &mut App) { .style(Style::default()); let key_hint = Paragraph::new(Text::styled( - "[ESC] Quit / [Enter] Check domain / [Del] Clear Input", + "[ESC] Menu / [Enter] Check domain / [Del] Clear Input", Style::default().fg(Color::Red), )) .block(key_hint_block); @@ -187,6 +187,62 @@ pub fn ui(f: &mut Frame, app: &mut App) { } } } - CurrentState::Menu => {} + CurrentState::Menu => { + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(3), + Constraint::Min(1), + Constraint::Length(3), + ]) + .split(f.size()); + + let title_block = Block::new().borders(Borders::ALL).style(Style::default()); + + let title = Paragraph::new(Text::styled( + "**** Menu ****", + Style::default().fg(Color::Cyan), + )) + .block(title_block); + f.render_widget(title, chunks[0]); + + let mut options_list = Vec::::new(); + options_list.push(ListItem::new(Line::from(Span::styled( + "ESC) Return to lookup", + Style::default(), + )))); + + // TODO: Add code to save lookup + options_list.push(ListItem::new(Line::from(Span::styled( + "S) Save lookup", + Style::default(), + )))); + + // TODO: Add code to load and UI to list saved lookups + options_list.push(ListItem::new(Line::from(Span::styled( + "L) List previous lookups", + Style::default(), + )))); + + // TODO: Add code to read lookup and display + options_list.push(ListItem::new(Line::from(Span::styled( + "V) View previous lookup", + Style::default(), + )))); + + options_list.push(ListItem::new(Line::from(Span::styled( + "Q) Quit", + Style::default(), + )))); + + let options = List::new(options_list); + f.render_widget(options, chunks[1]); + + let footer_block = Block::new().borders(Borders::ALL).style(Style::default()); + + let footer = Paragraph::new(Text::styled("[q] Quit", Style::default().fg(Color::Red))) + .block(footer_block); + f.render_widget(footer, chunks[2]); + } } } diff --git a/src/whois.rs b/src/whois.rs index 8749291..3584089 100644 --- a/src/whois.rs +++ b/src/whois.rs @@ -6,7 +6,7 @@ use hickory_resolver::proto::{ use regex::Regex; use whois_rust::{WhoIs, WhoIsLookupOptions}; -#[derive(Debug)] +#[derive(Debug, Clone)] enum RegistrantType { Registrant, Admin, @@ -33,7 +33,7 @@ impl RegexQuery { } } -#[derive(Debug)] +#[derive(Debug, Clone)] struct NameServer { host: String, } @@ -44,7 +44,7 @@ impl NameServer { } } -#[derive(Debug)] +#[derive(Debug, Clone)] struct Registrant { name: String, org: String, @@ -76,11 +76,11 @@ struct Eligibility { } pub struct WhoisData { - registrar: String, - domain_status: String, - registrant: Vec, - nameservers: Vec, - dnssec: String, + registrar: Option, + domain_status: Option, + registrant: Option>, + nameservers: Option>, + dnssec: Option, eligibility_type: Option, } @@ -89,7 +89,11 @@ impl fmt::Display for WhoisData { write!( f, "Registrar: {}\nStatus: {}\nRegistrant: {:?}\nNameservers: {:?}\nDNSSEC: {}", - self.registrar, self.domain_status, self.registrant, self.nameservers, self.dnssec + self.registrar.clone().unwrap(), + self.domain_status.clone().unwrap(), + self.registrant.clone().unwrap(), + self.nameservers.clone().unwrap(), + self.dnssec.clone().unwrap() ) } } @@ -123,20 +127,22 @@ impl WhoisData { for nameserver in nameserver_regex.get_matches(&result) { nameservers.push(NameServer::new(nameserver)); } - let dnssec = dnssec_regex.get_matches(&result); - + 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: registrar.clone(), - domain_status: domain_status.clone(), - registrant: vec![Registrant::new( + 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: nameservers, - dnssec: dnssec[0].clone(), + )]), + nameservers: Some(nameservers), + dnssec: Some(dnssec[0].clone()), eligibility_type: None, } } @@ -144,19 +150,19 @@ impl WhoisData { pub fn to_vec(&self) -> Vec { let mut ret_vec: Vec = vec![]; let mut registrar = String::from("Registrar: "); - registrar.push_str(&self.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); + 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); + dnssec.push_str(&self.dnssec.clone().unwrap()); ret_vec.push(dnssec); let mut registrant_type = String::new(); - for registrant in &self.registrant { + for registrant in &self.registrant.clone().unwrap() { match registrant.rtype { RegistrantType::Admin => { registrant_type.push_str("Admin "); @@ -187,7 +193,7 @@ impl WhoisData { ret_vec.push(email); } - for nameserver in &self.nameservers { + for nameserver in &self.nameservers.clone().unwrap() { let mut tmp = String::from("Nameserver: "); tmp.push_str(&nameserver.host); ret_vec.push(tmp); diff --git a/test.ini b/test.ini index 19886b3..4b94f9a 100644 --- a/test.ini +++ b/test.ini @@ -1,3 +1,4 @@ [General] wildcard_test=dfjgnkdfjngkdfngjkd -subdomains=www,ftp,mail,files \ No newline at end of file +subdomains=www,ftp,mail,files +data_directory=/home/ben/.config/dnslookup \ No newline at end of file -- 2.43.5 From c6c7808876f94b62896ab114b57f16e9d6b79434 Mon Sep 17 00:00:00 2001 From: Benjamyn Love Date: Mon, 1 Apr 2024 18:26:44 +1100 Subject: [PATCH 07/16] Removed log file --- .gitignore | 1 + error.log | 87 ------------------------------------------------------ 2 files changed, 1 insertion(+), 87 deletions(-) delete mode 100644 error.log diff --git a/.gitignore b/.gitignore index ea8c4bf..4b1d890 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +*.log \ No newline at end of file diff --git a/error.log b/error.log deleted file mode 100644 index 8f98f67..0000000 --- a/error.log +++ /dev/null @@ -1,87 +0,0 @@ - Compiling dns_lookup_project v0.1.0 (/home/ben/Documents/Projects/rust/dns_lookup_project) -warning: unused imports: `domain`, `rdata::name`, `xfer::dns_handle` - --> src/whois.rs:3:10 - | -3 | rr::{domain, rdata::name}, - | ^^^^^^ ^^^^^^^^^^^ -4 | xfer::dns_handle, - | ^^^^^^^^^^^^^^^^ - | - = note: `#[warn(unused_imports)]` on by default - -warning: unused imports: `Clear`, `Rect`, `Wrap` - --> src/ui.rs:2:45 - | -2 | layout::{Constraint, Direction, Layout, Rect}, - | ^^^^ -... -5 | widgets::{Block, Borders, Clear, List, ListItem, Paragraph, Wrap}, - | ^^^^^ ^^^^ - -warning: unused import: `KeyEventKind` - --> src/main.rs:17:68 - | -17 | self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind, - | ^^^^^^^^^^^^ - -warning: unused import: `stderr` - --> src/main.rs:26:21 - | -26 | use std::io::{self, stderr, stdout}; - | ^^^^^^ - -warning: unused import: `Styled` - --> src/ui.rs:3:27 - | -3 | style::{Color, Style, Styled}, - | ^^^^^^ - -warning: variants `Admin`, `Tech`, and `Billing` are never constructed - --> src/whois.rs:12:5 - | -10 | enum RegistrantType { - | -------------- variants in this enum -11 | Registrant, -12 | Admin, - | ^^^^^ -13 | Tech, - | ^^^^ -14 | Billing, - | ^^^^^^^ - | - = note: `RegistrantType` has derived impls for the traits `Clone` and `Debug`, but these are intentionally ignored during dead code analysis - = note: `#[warn(dead_code)]` on by default - -warning: fields `e_type`, `id`, and `name` are never read - --> src/whois.rs:73:5 - | -72 | struct Eligibility { - | ----------- fields in this struct -73 | e_type: String, - | ^^^^^^ -74 | id: String, - | ^^ -75 | name: String, - | ^^^^ - -warning: field `eligibility_type` is never read - --> src/whois.rs:84:5 - | -78 | pub struct WhoisData { - | --------- field in this struct -... -84 | eligibility_type: Option, - | ^^^^^^^^^^^^^^^^ - -warning: variant `Menu` is never constructed - --> src/app.rs:8:5 - | -6 | pub enum CurrentState { - | ------------ variant in this enum -7 | Lookup, -8 | Menu, - | ^^^^ - -warning: `dns_lookup_project` (bin "dns_lookup_project") generated 9 warnings (run `cargo fix --bin "dns_lookup_project"` to apply 4 suggestions) - Finished dev [unoptimized + debuginfo] target(s) in 0.79s - Running `target/debug/dns_lookup_project` -- 2.43.5 From 9d71ba17a5170700a5cf3cd16e6dbc376b718484 Mon Sep 17 00:00:00 2001 From: Benjamyn Love Date: Tue, 2 Apr 2024 14:39:45 +1100 Subject: [PATCH 08/16] added fs stuff --- src/app.rs | 15 +++++ src/{fs.rs => fsutil.rs} | 19 ++++++ src/main.rs | 55 ++++++++------- src/ui.rs | 142 ++++++++++++++++++++++++++------------- 4 files changed, 159 insertions(+), 72 deletions(-) rename src/{fs.rs => fsutil.rs} (68%) diff --git a/src/app.rs b/src/app.rs index 7459853..ea4bbc1 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,8 +1,17 @@ +use ratatui::widgets::ListState; + +use crate::config::*; + pub enum AppRenderDir { Vertical, Horizontal, } +pub enum MenuState { + Main, + List, +} + pub enum CurrentState { Lookup, Menu, @@ -14,6 +23,9 @@ pub struct App { pub dns_info: Vec, pub render_direction: AppRenderDir, pub current_state: CurrentState, + pub menu_state: MenuState, + pub config: Config, + pub state: ListState, } impl App { @@ -24,6 +36,9 @@ impl App { dns_info: vec![], render_direction: AppRenderDir::Vertical, current_state: CurrentState::Lookup, + menu_state: MenuState::Main, + config: Config::from_file("test.ini".to_string()), + state: ListState::default(), } } } diff --git a/src/fs.rs b/src/fsutil.rs similarity index 68% rename from src/fs.rs rename to src/fsutil.rs index 2b73cb0..e272f9b 100644 --- a/src/fs.rs +++ b/src/fsutil.rs @@ -4,6 +4,12 @@ use std::error::Error; use std::fs; use std::path::Path; +use ratatui::{ + style::{Modifier, Style}, + text::{Line, Span}, + widgets::ListItem, +}; + use crate::config::Config; #[derive(Serialize)] @@ -43,4 +49,17 @@ impl DomainData { Ok(()) // println!("{}", output); } + + pub fn list_lookups(config: &Config) -> Vec { + let mut items: Vec = vec![]; + for item in fs::read_dir(format!("{}/lookups", config.data_dir)).unwrap() { + let item_name = item.unwrap(); + items.push(ListItem::new(Line::from(Span::styled( + String::from(item_name.path().to_string_lossy().clone()), + Style::default(), + )))); + } + + return items; + } } diff --git a/src/main.rs b/src/main.rs index b208e0c..cd9621e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,8 +13,8 @@ use crate::app::*; mod ui; use crate::ui::*; -mod fs; -use crate::fs::*; +mod fsutil; +use crate::fsutil::*; use crossterm::event::{ self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind, @@ -41,7 +41,7 @@ fn main() -> Result<(), Box> { let config = Config::from_file("test.ini".to_string()); // Run the app - let _res = run_app(&mut terminal, &mut app, &config); + let _res = run_app(&mut terminal, &mut app); disable_raw_mode()?; execute!( @@ -53,11 +53,7 @@ fn main() -> Result<(), Box> { Ok(()) } -fn run_app( - terminal: &mut Terminal, - app: &mut App, - config: &Config, -) -> io::Result { +fn run_app(terminal: &mut Terminal, app: &mut App) -> io::Result { loop { terminal.draw(|f| ui(f, app))?; @@ -83,7 +79,7 @@ fn run_app( continue; } let mut domain = Domain::new(app.domain_input.clone()); - domain.apply_config(&config); + domain.apply_config(&app.config); domain.lookup_all_records(); app.dns_info = domain.to_vec(); let whois = WhoisData::new(app.domain_input.clone()); @@ -94,21 +90,32 @@ fn run_app( } _ => {} }, - CurrentState::Menu => match key.code { - KeyCode::Esc => { - app.current_state = CurrentState::Lookup; - } - KeyCode::Char('s') => { - let domain_data = DomainData::new( - app.domain_input.clone(), - app.dns_info.clone(), - app.whois_info.clone(), - ); - domain_data.save_lookup(&config).unwrap(); - app.current_state = CurrentState::Lookup; - } - KeyCode::Char('q') => return Ok(false), - _ => {} + CurrentState::Menu => match app.menu_state { + MenuState::Main => match key.code { + KeyCode::Esc => { + app.current_state = CurrentState::Lookup; + } + KeyCode::Char('s') => { + let domain_data = DomainData::new( + app.domain_input.clone(), + app.dns_info.clone(), + app.whois_info.clone(), + ); + domain_data.save_lookup(&app.config).unwrap(); + app.current_state = CurrentState::Lookup; + } + KeyCode::Char('l') => { + app.menu_state = MenuState::List; + } + KeyCode::Char('q') => return Ok(false), + _ => {} + }, + MenuState::List => match key.code { + KeyCode::Esc => { + app.menu_state = MenuState::Main; + } + _ => {} + }, }, } } diff --git a/src/ui.rs b/src/ui.rs index 1a5c9e9..e96609c 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,14 +1,17 @@ use ratatui::{ layout::{Constraint, Direction, Layout, Rect}, - style::{Color, Style, Styled}, + style::{Color, Modifier, Style, Styled}, text::{Line, Span, Text}, - widgets::{Block, Borders, Clear, List, ListItem, Paragraph, Wrap}, + widgets::{ + Block, Borders, Clear, HighlightSpacing, List, ListDirection, ListItem, ListState, + Paragraph, Wrap, + }, Frame, }; use crate::{ app::{App, AppRenderDir}, - CurrentState, + CurrentState, DomainData, MenuState, }; pub fn ui(f: &mut Frame, app: &mut App) { @@ -188,61 +191,104 @@ pub fn ui(f: &mut Frame, app: &mut App) { } } CurrentState::Menu => { - let chunks = Layout::default() - .direction(Direction::Vertical) - .constraints([ - Constraint::Length(3), - Constraint::Min(1), - Constraint::Length(3), - ]) - .split(f.size()); + match app.menu_state { + MenuState::Main => { + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(3), + Constraint::Min(1), + Constraint::Length(3), + ]) + .split(f.size()); - let title_block = Block::new().borders(Borders::ALL).style(Style::default()); + let title_block = Block::new().borders(Borders::ALL).style(Style::default()); - let title = Paragraph::new(Text::styled( - "**** Menu ****", - Style::default().fg(Color::Cyan), - )) - .block(title_block); - f.render_widget(title, chunks[0]); + let title = Paragraph::new(Text::styled( + "**** Menu ****", + Style::default().fg(Color::Cyan), + )) + .block(title_block); + f.render_widget(title, chunks[0]); - let mut options_list = Vec::::new(); - options_list.push(ListItem::new(Line::from(Span::styled( - "ESC) Return to lookup", - Style::default(), - )))); + let mut options_list = Vec::::new(); + options_list.push(ListItem::new(Line::from(Span::styled( + "ESC) Return to lookup", + Style::default(), + )))); - // TODO: Add code to save lookup - options_list.push(ListItem::new(Line::from(Span::styled( - "S) Save lookup", - Style::default(), - )))); + // TODO: Add code to save lookup + options_list.push(ListItem::new(Line::from(Span::styled( + "S) Save lookup", + Style::default(), + )))); - // TODO: Add code to load and UI to list saved lookups - options_list.push(ListItem::new(Line::from(Span::styled( - "L) List previous lookups", - Style::default(), - )))); + // TODO: Add code to load and UI to list saved lookups + options_list.push(ListItem::new(Line::from(Span::styled( + "L) List previous lookups", + Style::default(), + )))); - // TODO: Add code to read lookup and display - options_list.push(ListItem::new(Line::from(Span::styled( - "V) View previous lookup", - Style::default(), - )))); + // TODO: Add code to read lookup and display + options_list.push(ListItem::new(Line::from(Span::styled( + "V) View previous lookup", + Style::default(), + )))); - options_list.push(ListItem::new(Line::from(Span::styled( - "Q) Quit", - Style::default(), - )))); + options_list.push(ListItem::new(Line::from(Span::styled( + "Q) Quit", + Style::default(), + )))); - let options = List::new(options_list); - f.render_widget(options, chunks[1]); + let options = List::new(options_list); + f.render_widget(options, chunks[1]); - let footer_block = Block::new().borders(Borders::ALL).style(Style::default()); + let footer_block = Block::new().borders(Borders::ALL).style(Style::default()); - let footer = Paragraph::new(Text::styled("[q] Quit", Style::default().fg(Color::Red))) - .block(footer_block); - f.render_widget(footer, chunks[2]); + let footer = + Paragraph::new(Text::styled("[q] Quit", Style::default().fg(Color::Red))) + .block(footer_block); + f.render_widget(footer, chunks[2]); + } + MenuState::List => { + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(3), + Constraint::Min(1), + Constraint::Length(3), + ]) + .split(f.size()); + + let title_block = Block::new().borders(Borders::ALL).style(Style::default()); + + let title = Paragraph::new(Text::styled( + "**** List ****", + Style::default().fg(Color::Cyan), + )) + .block(title_block); + f.render_widget(title, chunks[0]); + + let item_list = DomainData::list_lookups(&app.config); + let items = List::new(item_list) + .block(Block::default().title("Lookups").borders(Borders::ALL)) + .style(Style::default()) + .highlight_style(Style::default().add_modifier(Modifier::BOLD)) + .highlight_symbol(">>") + .highlight_spacing(HighlightSpacing::Always) + .repeat_highlight_symbol(true); + f.render_stateful_widget(items, chunks[1], &mut app.state); + + let footer_block = Block::new().borders(Borders::ALL).style(Style::default()); + + let footer = Paragraph::new(Text::styled( + "[ESC] Return to menu", + Style::default().fg(Color::Red), + )) + .block(footer_block); + f.render_widget(footer, chunks[2]); + } + } } } } -- 2.43.5 From 4515f35181bbb5ceff85ce52fb04023249f0887c Mon Sep 17 00:00:00 2001 From: Benjamyn Love Date: Sun, 21 Apr 2024 17:17:22 +1000 Subject: [PATCH 09/16] Changed whois system to trait based system --- Cargo.lock | 47 +++++++ Cargo.toml | 13 +- src/app.rs | 2 - src/domain.rs | 6 +- src/main.rs | 20 ++- src/ui.rs | 65 ++++++++- src/whois/au.rs | 23 +++ src/{whois.rs => whois/default.rs} | 216 ++++++++--------------------- src/whois/mod.rs | 4 + src/whois/selector.rs | 30 ++++ src/whois/whois_base.rs | 104 ++++++++++++++ 11 files changed, 351 insertions(+), 179 deletions(-) create mode 100644 src/whois/au.rs rename src/{whois.rs => whois/default.rs} (51%) create mode 100644 src/whois/mod.rs create mode 100644 src/whois/selector.rs create mode 100644 src/whois/whois_base.rs 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 + } +} -- 2.43.5 From 26588b6de77c855ae046e8dbeb24704e6fc4fa44 Mon Sep 17 00:00:00 2001 From: Benjamyn Love Date: Mon, 22 Apr 2024 09:11:49 +1000 Subject: [PATCH 10/16] Updated --- src/main.rs | 8 +-- src/whois/au.rs | 151 +++++++++++++++++++++++++++++++++++++++++- src/whois/mod.rs | 1 + src/whois/selector.rs | 9 ++- src/whois/uk.rs | 62 +++++++++++++++++ 5 files changed, 219 insertions(+), 12 deletions(-) create mode 100644 src/whois/uk.rs diff --git a/src/main.rs b/src/main.rs index fa38db6..439f387 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,7 +32,7 @@ use std::io::{self, stderr}; fn main() -> Result<(), Box> { enable_raw_mode()?; let mut stderr = stderr(); - execute!(stderr, EnterAlternateScreen, EnableMouseCapture)?; + execute!(stderr, EnterAlternateScreen)?; let backend = CrosstermBackend::new(stderr); let mut terminal = Terminal::new(backend)?; @@ -45,11 +45,7 @@ fn main() -> Result<(), Box> { let _res = run_app(&mut terminal, &mut app); disable_raw_mode()?; - execute!( - terminal.backend_mut(), - LeaveAlternateScreen, - DisableMouseCapture - )?; + execute!(terminal.backend_mut(), LeaveAlternateScreen)?; terminal.show_cursor()?; Ok(()) } diff --git a/src/whois/au.rs b/src/whois/au.rs index bc2606e..f99ddeb 100644 --- a/src/whois/au.rs +++ b/src/whois/au.rs @@ -2,22 +2,167 @@ use crate::whois::whois_base::{ NameServer, RegexQuery, Registrant, RegistrantType, Whois, WhoisData, }; use whois_rust::{WhoIs, WhoIsLookupOptions}; + +struct Eligibility { + e_type: Option, + e_name: Option, + e_id: Option, + r_id: Option, +} + +impl Eligibility { + fn new() -> Eligibility { + Eligibility { + e_id: Some(String::new()), + e_name: Some(String::new()), + e_type: Some(String::new()), + r_id: Some(String::new()), + } + } +} + pub struct _Whois { base: WhoisData, - elegibility: String, + eligibility: Eligibility, } impl Whois for _Whois { fn new() -> Self { _Whois { base: WhoisData::new(), - elegibility: String::new(), + eligibility: Eligibility::new(), } } fn to_vec(&self) -> Vec { - vec![String::new()] + let mut ret_vec: Vec = 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(®istrant.name); + ret_vec.push(name); + + let mut org = registrant_type.clone(); + org.push_str("Organisation: "); + org.push_str(®istrant.org); + ret_vec.push(org); + + let mut email = registrant_type.clone(); + email.push_str("Email: "); + email.push_str(®istrant.email); + ret_vec.push(email); + } + + for nameserver in &self.base.nameservers.clone().unwrap() { + let mut tmp = String::from("Nameserver: "); + tmp.push_str(&nameserver.host); + ret_vec.push(tmp); + } + + let mut eligibility_name = String::from("Eligibility Name: "); + eligibility_name.push_str(&self.eligibility.e_name.clone().unwrap()); + ret_vec.push(eligibility_name); + + let mut eligibility_type = String::from("Eligibility Type: "); + eligibility_type.push_str(&self.eligibility.e_type.clone().unwrap()); + ret_vec.push(eligibility_type); + + let mut eligibility_id = String::from("Eligibility ID: "); + eligibility_id.push_str(&self.eligibility.e_id.clone().unwrap()); + ret_vec.push(eligibility_id); + + let mut registrant_id = String::from("Registrant ID: "); + registrant_id.push_str(&self.eligibility.r_id.clone().unwrap()); + ret_vec.push(registrant_id); + + return ret_vec; } 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 eligebility_name_regex = + RegexQuery::new(String::from(r"(?i)(.*eligibility name:)(.*)")); + let eligebility_type_regex = + RegexQuery::new(String::from(r"(?i)(.*eligibility type:)(.*)")); + let eligebility_id_regex = RegexQuery::new(String::from(r"(?i)(.*eligibility id:)(.*)")); + let registrant_id_regex = RegexQuery::new(String::from(r"(?i)(.*registrant id:)(.*)")); + + 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")]; + } + let eligebility_name = + WhoisData::return_regex(eligebility_name_regex.get_matches(&result), 0); + let eligebility_type = + WhoisData::return_regex(eligebility_type_regex.get_matches(&result), 0); + let eligebility_id = WhoisData::return_regex(eligebility_id_regex.get_matches(&result), 0); + let registrant_id = WhoisData::return_regex(registrant_id_regex.get_matches(&result), 0); + + // 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.eligibility.e_name = Some(eligebility_name.clone()); + self.eligibility.e_type = Some(eligebility_type.clone()); + self.eligibility.e_id = Some(eligebility_id.clone()); + self.eligibility.r_id = Some(registrant_id.clone()); self.to_vec() } } diff --git a/src/whois/mod.rs b/src/whois/mod.rs index 0a50a90..ae9ab09 100644 --- a/src/whois/mod.rs +++ b/src/whois/mod.rs @@ -1,4 +1,5 @@ pub mod au; pub mod default; pub mod selector; +pub mod uk; pub mod whois_base; diff --git a/src/whois/selector.rs b/src/whois/selector.rs index 50678a3..725c059 100644 --- a/src/whois/selector.rs +++ b/src/whois/selector.rs @@ -1,13 +1,16 @@ -use crate::whois::default; use addr::parse_domain_name; -use super::{au, whois_base::Whois}; +use super::{au, default, uk, 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())), + "com.au" => Ok(Box::new(au::_Whois::new())), + "org.au" => Ok(Box::new(au::_Whois::new())), + "net.au" => Ok(Box::new(au::_Whois::new())), + "co.uk" => Ok(Box::new(uk::_Whois::new())), + "au" => Ok(Box::new(au::_Whois::new())), _ => Ok(Box::new(default::_Whois::new())), }, Err(_) => Err(String::from("Failed to select whois server")), diff --git a/src/whois/uk.rs b/src/whois/uk.rs new file mode 100644 index 0000000..b3bf726 --- /dev/null +++ b/src/whois/uk.rs @@ -0,0 +1,62 @@ +use crate::whois::whois_base::{ + NameServer, RegexQuery, Registrant, RegistrantType, Whois, WhoisData, +}; +use regex::Regex; +use whois_rust::{WhoIs, WhoIsLookupOptions}; + +// (.*registrar:\n)(.*) + +// (.*name servers:\n)((.*\n)+?^\n) + +pub struct _Whois { + base: WhoisData, +} + +impl Whois for _Whois { + fn new() -> Self + where + Self: Sized, + { + _Whois { + base: WhoisData::new(), + } + } + fn to_vec(&self) -> Vec { + let mut ret_vec: Vec = vec![]; + let mut registrar = String::from("Registrar: "); + registrar.push_str(&self.base.registrar.clone().unwrap()); + ret_vec.push(registrar); + + return ret_vec; + } + 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"(?mi)(.*registrar:.*\n)(.*?\[)")); + let nameserver_regex = + RegexQuery::new(String::from(r"(?mi)(.*name servers:\r\n)((.*\r\n))")); + + let registrar = WhoisData::return_regex(registrar_regex.get_matches(&result), 0); + // let nameserver_regex = WhoisData::return_regex(self.get_matches(&result), 0); + + self.base.registrar = Some(registrar.clone()); + + self.to_vec() + } +} + +impl _Whois { + pub fn get_matches(&self, haystack: &str, expression: String) -> Vec { + let re = Regex::new(&expression).unwrap(); + let mut results = vec![]; + for (_, [_, rex2]) in re.captures_iter(haystack).map(|c| c.extract()) { + results.push(String::from(rex2.trim())); + println!("{:?}", rex2.trim()) + } + println!("{:?}", results); + results + } +} -- 2.43.5 From 19d1cc0db094ec7f432ae3ec8ddb37e7243b45a6 Mon Sep 17 00:00:00 2001 From: Benjamyn Love Date: Tue, 23 Apr 2024 09:55:08 +1000 Subject: [PATCH 11/16] Added basic logger --- src/logger.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/logger.rs diff --git a/src/logger.rs b/src/logger.rs new file mode 100644 index 0000000..5f6c1c6 --- /dev/null +++ b/src/logger.rs @@ -0,0 +1,33 @@ +use std::fs::OpenOptions; +use std::io::prelude::*; +use std::path::Path; + +enum LoggerType { + Local(&'static str), +} + +pub struct Logger { + l_type: LoggerType, +} + +impl Logger { + pub fn new(path: &'static str) -> Logger { + Logger { + l_type: LoggerType::Local(path), + } + } + + pub fn write_log_line(&self, data: String) { + match self.l_type { + LoggerType::Local(path) => { + let mut file = OpenOptions::new() + .write(true) + .append(true) + .create(true) + .open(path) + .unwrap(); + writeln!(file, "{}", data).unwrap(); + } + } + } +} -- 2.43.5 From 39a2590be8a1393c54798adfb3ea373d777caef4 Mon Sep 17 00:00:00 2001 From: Benjamyn Love Date: Tue, 23 Apr 2024 09:55:24 +1000 Subject: [PATCH 12/16] Updated whois module, added .co.uk nameservers --- src/main.rs | 10 ++++++---- src/whois/uk.rs | 14 +++++++++++--- src/whois/whois_base.rs | 19 +++++++++++++++++++ 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index 439f387..c9a1881 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,8 @@ mod whois; use whois::default; use whois::selector::*; +mod logger; + use crossterm::event::{ self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind, }; @@ -27,14 +29,14 @@ use crossterm::terminal::{ use ratatui::backend::{Backend, CrosstermBackend}; use ratatui::Terminal; use std::error::Error; -use std::io::{self, stderr}; +use std::io::{self, stdout}; fn main() -> Result<(), Box> { enable_raw_mode()?; - let mut stderr = stderr(); - execute!(stderr, EnterAlternateScreen)?; + let mut stdout = stdout(); + execute!(stdout, EnterAlternateScreen)?; - let backend = CrosstermBackend::new(stderr); + let backend = CrosstermBackend::new(stdout); let mut terminal = Terminal::new(backend)?; // Initialise the App and config diff --git a/src/whois/uk.rs b/src/whois/uk.rs index b3bf726..35dfe52 100644 --- a/src/whois/uk.rs +++ b/src/whois/uk.rs @@ -27,6 +27,12 @@ impl Whois for _Whois { registrar.push_str(&self.base.registrar.clone().unwrap()); ret_vec.push(registrar); + for nameserver in &self.base.nameservers.clone().unwrap() { + let mut tmp = String::from("Nameserver: "); + tmp.push_str(&nameserver.host); + ret_vec.push(tmp); + } + return ret_vec; } fn lookup(&mut self, domain: String) -> Vec { @@ -35,14 +41,16 @@ impl Whois for _Whois { .lookup(WhoIsLookupOptions::from_string(domain).unwrap()) .unwrap(); - let registrar_regex = RegexQuery::new(String::from(r"(?mi)(.*registrar:.*\n)(.*?\[)")); + let registrar_regex = RegexQuery::new(String::from(r"(?mi)(.*registrar:.*\n)(.*)")); let nameserver_regex = - RegexQuery::new(String::from(r"(?mi)(.*name servers:\r\n)((.*\r\n))")); + RegexQuery::new(String::from(r"(?miR)(.*name servers:\r\n)((.+\.+.+)+)")); let registrar = WhoisData::return_regex(registrar_regex.get_matches(&result), 0); - // let nameserver_regex = WhoisData::return_regex(self.get_matches(&result), 0); + let nameservers = nameserver_regex.captures(&result); + // println!("{:?}", nameservers); self.base.registrar = Some(registrar.clone()); + self.base.nameservers = Some(nameservers.clone()); self.to_vec() } diff --git a/src/whois/whois_base.rs b/src/whois/whois_base.rs index 33ca6b6..2e376af 100644 --- a/src/whois/whois_base.rs +++ b/src/whois/whois_base.rs @@ -1,3 +1,4 @@ +use crate::logger::Logger; use core::fmt; use regex::Regex; @@ -34,6 +35,24 @@ impl RegexQuery { } results } + pub fn captures(&self, haystack: &str) -> Vec { + // let logger = Logger::new("log.txt"); + let mut results: Vec = vec![]; + match self.re.captures(haystack) { + Some(rex) => { + for x in 1..rex.len() { + if x == 1 { + continue; + }; + results.push(NameServer::new(String::from( + rex.get(x).unwrap().as_str().trim(), + ))); + } + } + None => {} + } + results + } } #[derive(Debug, Clone)] -- 2.43.5 From 618779102bfd5903a4f957d262f8d7fe9deb4ccb Mon Sep 17 00:00:00 2001 From: Benjamyn Love Date: Tue, 11 Jun 2024 13:36:05 +1000 Subject: [PATCH 13/16] List testing --- src/app.rs | 2 ++ src/fsutil.rs | 13 +++++++------ src/ui.rs | 11 +++++++++++ 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/app.rs b/src/app.rs index 3c93a43..ea4bbc1 100644 --- a/src/app.rs +++ b/src/app.rs @@ -25,6 +25,7 @@ pub struct App { pub current_state: CurrentState, pub menu_state: MenuState, pub config: Config, + pub state: ListState, } impl App { @@ -37,6 +38,7 @@ 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/fsutil.rs b/src/fsutil.rs index e272f9b..f2cf425 100644 --- a/src/fsutil.rs +++ b/src/fsutil.rs @@ -50,14 +50,15 @@ impl DomainData { // println!("{}", output); } - pub fn list_lookups(config: &Config) -> Vec { - let mut items: Vec = vec![]; + pub fn list_lookups(config: &Config) -> Vec { + let mut items: Vec = vec![]; for item in fs::read_dir(format!("{}/lookups", config.data_dir)).unwrap() { let item_name = item.unwrap(); - items.push(ListItem::new(Line::from(Span::styled( - String::from(item_name.path().to_string_lossy().clone()), - Style::default(), - )))); + // items.push(ListItem::new(Line::from(Span::styled( + // String::from(item_name.path().to_string_lossy().clone()), + // Style::default(), + // )))); + items.push(String::from(item_name.path().to_string_lossy().clone())) } return items; diff --git a/src/ui.rs b/src/ui.rs index 699bf96..5a213f5 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -336,6 +336,17 @@ pub fn ui(f: &mut Frame, app: &mut App) { .repeat_highlight_symbol(true); // f.render_stateful_widget(items, chunks[1], &mut app.state); + // let t_list = StatefulList::with_items(item_list); + // f.render_stateful_widget(t_list, chunks[1], t_list.state); + let items = ["Item 1", "Item 2", "Item 3"]; + let list = List::new(items) + .block(Block::default().title("List").borders(Borders::ALL)) + .highlight_style(Style::new().add_modifier(Modifier::REVERSED)) + .highlight_symbol(">>") + .repeat_highlight_symbol(true); + + f.render_stateful_widget(list, chunks[1], &mut app.state); + let footer_block = Block::new().borders(Borders::ALL).style(Style::default()); let footer = Paragraph::new(Text::styled( -- 2.43.5 From 42c78a3a8b8793afb3dc6c64cf64ccbfa1597996 Mon Sep 17 00:00:00 2001 From: Benjamyn Love Date: Wed, 19 Jun 2024 13:26:48 +1000 Subject: [PATCH 14/16] Fixed config loading and default config generation --- .vscode/launch.json | 7 +++++ src/app.rs | 3 +- src/config.rs | 77 ++++++++++++++++++++++++++++++++++++++------- src/fsutil.rs | 3 +- src/main.rs | 3 +- 5 files changed, 77 insertions(+), 16 deletions(-) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..5c7247b --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,7 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [] +} \ No newline at end of file diff --git a/src/app.rs b/src/app.rs index ea4bbc1..6556f3c 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,6 +1,7 @@ use ratatui::widgets::ListState; use crate::config::*; +use crate::logger::*; pub enum AppRenderDir { Vertical, @@ -37,7 +38,7 @@ impl App { render_direction: AppRenderDir::Vertical, current_state: CurrentState::Lookup, menu_state: MenuState::Main, - config: Config::from_file("test.ini".to_string()), + config: Config::load(), state: ListState::default(), } } diff --git a/src/config.rs b/src/config.rs index 421381e..479e532 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,7 @@ use configparser::ini::Ini; use core::fmt; +use std::env; +use std::fs; pub struct Config { pub subdomains: Vec, @@ -20,21 +22,72 @@ impl fmt::Display for Config { impl Config { pub fn from_file(file_path: String) -> Config { let mut config = Ini::new(); - config.load(file_path).unwrap(); + match config.load(file_path) { + Ok(_) => { + let subdomains = config.get("General", "subdomains").unwrap(); + let mut subvec: Vec = vec![]; - let subdomains = config.get("General", "subdomains").unwrap(); - let mut subvec: Vec = vec![]; + for x in subdomains.split(',') { + subvec.push(x.to_string()); + } - for x in subdomains.split(',') { - subvec.push(x.to_string()); - } + let wildcard_test = config.get("General", "wildcard"); + let datadir = config.get("General", "data_directory"); - let wildcard_test = config.get("General", "wildcard_test"); - let datadir = config.get("General", "data_directory"); - Config { - subdomains: subvec, - wildcard_test: wildcard_test.unwrap(), - data_dir: datadir.unwrap(), + Config { + subdomains: subvec, + wildcard_test: wildcard_test.unwrap(), + data_dir: datadir.unwrap(), + } + } + Err(err) => { + println!("{:?}", err); + Config::new() + } } } + + pub fn new() -> Config { + let mut config = Ini::new(); + let mut default_path = env::var_os("HOME").unwrap(); + default_path.push("/.config/dnslookup"); + + // Create the directories if they don't exist + match fs::create_dir_all(&default_path) { + Ok(_) => {} + Err(err) => { + println!("Unable to create directories, got {}", err) + } + } + + config.set( + "General", + "wildcard", + Some(String::from("dfjgnkdfjngkdfngjkd")), + ); + config.set( + "General", + "subdomains", + Some(String::from("www,ftp,mail,files")), + ); + config.set( + "General", + "data_directory", + Some(String::from(default_path.to_str().unwrap())), + ); + + let mut conf_path = default_path.clone(); + conf_path.push("/config.ini"); + println!("{:?}", conf_path); + config.write(&conf_path.clone()).unwrap(); + Config::from_file(conf_path.into_string().unwrap()) + } + + pub fn load() -> Config { + // Default path is $HOME/.config/dnslookup/config.ini + let mut conf_path = env::var_os("HOME").unwrap(); + conf_path.push("/.config/dnslookup/config.ini"); + + Config::from_file(conf_path.into_string().unwrap()) + } } diff --git a/src/fsutil.rs b/src/fsutil.rs index f2cf425..5f63650 100644 --- a/src/fsutil.rs +++ b/src/fsutil.rs @@ -37,7 +37,8 @@ impl DomainData { date.format("%Y-%m-%d_%H-%M-%S"), self.name ); - let data_dir = Path::new(&config.data_dir.clone()).exists(); + let path = format!("{}/lookups", config.data_dir); + let data_dir = Path::new(&path).exists(); if data_dir != true { match fs::create_dir_all(format!("{}/lookups", config.data_dir)) { Ok(_val) => {} diff --git a/src/main.rs b/src/main.rs index c9a1881..8e5b6b1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -39,9 +39,8 @@ fn main() -> Result<(), Box> { let backend = CrosstermBackend::new(stdout); let mut terminal = Terminal::new(backend)?; - // Initialise the App and config + // Initialise the App let mut app = App::new(); - let config = Config::from_file("test.ini".to_string()); // Run the app let _res = run_app(&mut terminal, &mut app); -- 2.43.5 From 8784823fc539a267ce7b8846e38b4cbe80eb201e Mon Sep 17 00:00:00 2001 From: Benjamyn Love Date: Wed, 19 Jun 2024 13:28:21 +1000 Subject: [PATCH 15/16] Removed debug line --- src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index 479e532..2d1900b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -78,7 +78,7 @@ impl Config { let mut conf_path = default_path.clone(); conf_path.push("/config.ini"); - println!("{:?}", conf_path); + config.write(&conf_path.clone()).unwrap(); Config::from_file(conf_path.into_string().unwrap()) } -- 2.43.5 From 6a136eb57a2878e44d533384c401bc3dc6675cda Mon Sep 17 00:00:00 2001 From: Benjamyn Love Date: Fri, 12 Jul 2024 14:24:26 +1000 Subject: [PATCH 16/16] Added file for name suggestions --- change_name.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 change_name.md diff --git a/change_name.md b/change_name.md new file mode 100644 index 0000000..71ae829 --- /dev/null +++ b/change_name.md @@ -0,0 +1 @@ +HelpMeDig -- 2.43.5