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/.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/Cargo.lock b/Cargo.lock index 1e072e4..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" @@ -28,6 +39,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 +60,27 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +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" @@ -81,6 +125,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" @@ -117,6 +167,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" @@ -156,6 +212,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 +245,70 @@ 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" +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 = "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" +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" @@ -190,9 +319,15 @@ checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" name = "dns_lookup_project" version = "0.1.0" dependencies = [ + "addr", + "chrono", "configparser", + "crossterm", "hickory-resolver", + "ratatui", "regex", + "serde", + "serde_json", "whois-rust", ] @@ -208,6 +343,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 +471,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 +479,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" @@ -407,6 +552,39 @@ 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.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" @@ -437,6 +615,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,12 +639,30 @@ 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" 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" @@ -483,6 +685,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 +737,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", + "log", "wasi", "windows-sys 0.48.0", ] @@ -590,6 +808,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" @@ -655,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" @@ -675,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" @@ -736,13 +985,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 +1113,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 +1174,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 +1235,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 +1477,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" @@ -1197,6 +1552,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" @@ -1238,6 +1647,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" @@ -1397,3 +1815,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..0fc7535 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +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" +configparser = "*" +crossterm = "*" hickory-resolver = { version = "0.24.0" } -regex = "1.10.3" -whois-rust = "1.6.0" +ratatui = "*" +regex = "*" +whois-rust = "*" +serde = { version = "*", features = ["derive"] } +serde_json = "*" +chrono = "*" +addr = { version = "0.15.6", features = ["publicsuffix"] } 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 diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..6556f3c --- /dev/null +++ b/src/app.rs @@ -0,0 +1,45 @@ +use ratatui::widgets::ListState; + +use crate::config::*; +use crate::logger::*; + +pub enum AppRenderDir { + Vertical, + Horizontal, +} + +pub enum MenuState { + Main, + List, +} + +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, + pub menu_state: MenuState, + pub config: Config, + pub state: ListState, +} + +impl App { + pub fn new() -> App { + App { + domain_input: String::new(), + whois_info: vec![], + dns_info: vec![], + render_direction: AppRenderDir::Vertical, + current_state: CurrentState::Lookup, + menu_state: MenuState::Main, + config: Config::load(), + state: ListState::default(), + } + } +} diff --git a/src/config.rs b/src/config.rs index c6bd528..2d1900b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,33 +1,93 @@ use configparser::ini::Ini; use core::fmt; +use std::env; +use std::fs; 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 + ) } } 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_test"); - Config { - subdomains: subvec, - wildcard_test: wildcard_test.unwrap() + let wildcard_test = config.get("General", "wildcard"); + let datadir = config.get("General", "data_directory"); + + Config { + subdomains: subvec, + wildcard_test: wildcard_test.unwrap(), + data_dir: datadir.unwrap(), + } + } + Err(err) => { + println!("{:?}", err); + Config::new() + } } } -} \ No newline at end of file + + 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"); + + 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/domain.rs b/src/domain.rs index c286838..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![], @@ -67,11 +67,56 @@ 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("."); 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/fsutil.rs b/src/fsutil.rs new file mode 100644 index 0000000..5f63650 --- /dev/null +++ b/src/fsutil.rs @@ -0,0 +1,67 @@ +use chrono::Local; +use serde::{Deserialize, Serialize}; +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)] +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 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) => {} + Err(_err) => {} + } + } + fs::write(filepath, output)?; + + 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(), + // )))); + items.push(String::from(item_name.path().to_string_lossy().clone())) + } + + return items; + } +} 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(); + } + } + } +} diff --git a/src/main.rs b/src/main.rs index 19e738b..8e5b6b1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,41 +4,125 @@ use crate::domain::Domain; mod config; use crate::config::Config; +mod app; +use crate::app::*; + +mod ui; +use crate::ui::*; + +mod fsutil; +use crate::fsutil::*; + mod whois; -use crate::whois::WhoisData; +use whois::default; +use whois::selector::*; -use std::io; -use std::io::Write; +mod logger; -fn main() { - // let mut running = true; - let config = Config::from_file("test.ini".to_string()); +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::{self, stdout}; + +fn main() -> Result<(), Box> { + enable_raw_mode()?; + let mut stdout = stdout(); + execute!(stdout, EnterAlternateScreen)?; + + let backend = CrosstermBackend::new(stdout); + let mut terminal = Terminal::new(backend)?; + + // Initialise the App + let mut app = App::new(); + + // Run the app + let _res = run_app(&mut terminal, &mut app); + + disable_raw_mode()?; + execute!(terminal.backend_mut(), LeaveAlternateScreen)?; + terminal.show_cursor()?; + Ok(()) +} + +fn run_app(terminal: &mut Terminal, app: &mut App) -> 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 => { + app.current_state = CurrentState::Menu; + } + 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 + if &app.domain_input == "" { + // Ignore empty input + continue; + } + 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_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(); + } + _ => {} + }, + 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; + } + _ => {} + }, + }, } } } - // 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..5a213f5 --- /dev/null +++ b/src/ui.rs @@ -0,0 +1,362 @@ +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, Padding, + Paragraph, Wrap, + }, + Frame, +}; + +use crate::{ + app::{App, AppRenderDir}, + 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; + } else { + app.render_direction = AppRenderDir::Vertical; + } + 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()) + .padding(Padding::horizontal(2)); + + let title = Paragraph::new(Span::styled( + "Dns Lookup tool", + Style::default().fg(Color::Yellow), + )) + .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]); + + // 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) + .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] Menu / [Enter] Check domain / [Del] Clear Input", + 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::Length(3), + Constraint::Percentage(50), + Constraint::Percentage(50), + 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]); + } + } + } + CurrentState::Menu => { + 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 = 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]); + } + 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 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( + "[ESC] Return to menu", + Style::default().fg(Color::Red), + )) + .block(footer_block); + f.render_widget(footer, chunks[2]); + } + } + } + } +} diff --git a/src/whois.rs b/src/whois.rs deleted file mode 100644 index 2687331..0000000 --- a/src/whois.rs +++ /dev/null @@ -1,152 +0,0 @@ -use core::fmt; -use regex::Regex; -use whois_rust::{WhoIs, WhoIsLookupOptions}; - -#[derive(Debug)] -enum RegistrantType { - Registrant, - Admin, - Tech, - Billing, -} - -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)] -struct NameServer { - host: String, -} - -impl NameServer { - fn new(host: String) -> NameServer { - NameServer { host } - } -} - -#[derive(Debug)] -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: String, - domain_status: String, - registrant: Vec, - nameservers: Vec, - dnssec: String, - 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, self.domain_status, self.registrant, self.nameservers, self.dnssec - ) - } -} - -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 dnssec = dnssec_regex.get_matches(&result); - - // println!("{:?}", registrar[0]); - WhoisData { - registrar: registrar.clone(), - domain_status: domain_status.clone(), - registrant: vec![Registrant::new( - reg_name.clone(), - reg_org.clone(), - reg_email, - RegistrantType::Registrant, - )], - nameservers: nameservers, - dnssec: dnssec[0].clone(), - eligibility_type: None, - } - } - - 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 - } -} diff --git a/src/whois/au.rs b/src/whois/au.rs new file mode 100644 index 0000000..f99ddeb --- /dev/null +++ b/src/whois/au.rs @@ -0,0 +1,168 @@ +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, + eligibility: Eligibility, +} + +impl Whois for _Whois { + fn new() -> Self { + _Whois { + base: WhoisData::new(), + eligibility: Eligibility::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); + + 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/default.rs b/src/whois/default.rs new file mode 100644 index 0000000..9a197c7 --- /dev/null +++ b/src/whois/default.rs @@ -0,0 +1,117 @@ +use crate::whois::whois_base::{ + NameServer, RegexQuery, Registrant, RegistrantType, Whois, WhoisData, +}; +use whois_rust::{WhoIs, WhoIsLookupOptions}; +pub struct _Whois { + base: WhoisData, +} + +impl Whois for _Whois { + fn new() -> _Whois { + _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); + + 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); + } + + 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 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]); + 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..ae9ab09 --- /dev/null +++ b/src/whois/mod.rs @@ -0,0 +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 new file mode 100644 index 0000000..725c059 --- /dev/null +++ b/src/whois/selector.rs @@ -0,0 +1,33 @@ +use addr::parse_domain_name; + +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())), + "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")), + } +} + +// 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/uk.rs b/src/whois/uk.rs new file mode 100644 index 0000000..35dfe52 --- /dev/null +++ b/src/whois/uk.rs @@ -0,0 +1,70 @@ +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); + + 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 { + 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"(?miR)(.*name servers:\r\n)((.+\.+.+)+)")); + + let registrar = WhoisData::return_regex(registrar_regex.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() + } +} + +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 + } +} diff --git a/src/whois/whois_base.rs b/src/whois/whois_base.rs new file mode 100644 index 0000000..2e376af --- /dev/null +++ b/src/whois/whois_base.rs @@ -0,0 +1,123 @@ +use crate::logger::Logger; +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 + } + 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)] +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 + } +} 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