From c6d9b775bc44d4f7c6e56f2776874bb1d571f653 Mon Sep 17 00:00:00 2001 From: Benjamyn Love Date: Mon, 1 Apr 2024 18:04:30 +1100 Subject: [PATCH] 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