diff --git a/src/appstate.rs b/src/appstate.rs index 70b3bb2..b439a18 100644 --- a/src/appstate.rs +++ b/src/appstate.rs @@ -275,7 +275,10 @@ impl AppState { self.help.push( "save_server\nsave the server and password to your system's keyring.\n".to_string(), ); - self.help.push("parse_nessus\nparse a nessus csv to add hosts and ports to the currently selected project\n".to_string()); + self.help.push("parse_nessus\nparse a nessus csv to add hosts and ports to the currently selected project\nexport nessus csv report, only checking the host, protocol, and port boxes.\n".to_string()); + self.help.push( + "Shortcuts:\nCTRL+P = new_project\nCTRL+s = save\nCTRL_+h = select_host\n".to_string(), + ); self.help.push("exit\nquit the tool\n".to_string()); self.initialize_modules(); return Ok(()); @@ -563,9 +566,22 @@ impl AppState { } "new_terminal" | "nt" => { let project = self.projects[self.selected_project].clone(); - if let Some(db) = project.db { - if let Some(cmd) = self.config.get("term_cmd") { + if let Some(cmd) = self.config.get("term_cmd") { + if let Some(db) = project.db { db.launch_terminal(cmd.to_string()); + } else { + if let Some((prog, _)) = cmd.split_once(" ") { + let mut launch_cmd = Command::new(prog); + match launch_cmd.spawn() { + Ok(_) => {} + Err(e) => { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!("[error] launching terminal: {e}"), + ))); + } + } + } } } } @@ -871,10 +887,9 @@ impl AppState { let mut key = String::new(); let mut value = String::new(); if let Some(args) = command_args.clone() { - let args_vec: Vec<&str> = args.split(" ").collect(); - if args_vec.len() == 2 { - key = args_vec[0].to_string(); - value = args_vec[1].to_string(); + if let Some((setting, data)) = args.split_once(" ") { + key = setting.to_string(); + value = data.to_string(); if self.config.contains_key(&key) { ready = true; } else { @@ -1111,7 +1126,14 @@ impl AppState { match args_vec.len() { 1 => { args_vec[0].split(",").into_iter().for_each(|p| { - if let Ok(port) = p.trim().parse::() { + if p.contains("-") { + let (first, last) = p.split_once("-").unwrap(); + if let Ok(start) = first.parse::() { + if let Ok(end) = last.parse::() { + ports.extend(start..=end); + } + } + } else if let Ok(port) = p.trim().parse::() { ports.push(port); } }); @@ -1155,23 +1177,45 @@ impl AppState { rid, format!("scanning {} ports on {} hosts", ports.len(), hosts.len()), ))); + let progress_bar = Arc::new(RwLock::new(ToolProgress { + name: format!("portscan {} - {}", project.org_name, project.name), + total: hosts.len() * ports.len(), + complete: 0, + finished: false, + })); + self.progress_bars.push(progress_bar.clone()); let project_clone = Arc::new(Mutex::new(project.clone())); let id = self.selected_project.clone(); let tx_clone = self.main_tx.clone(); + let pb_clone = progress_bar.clone(); self.workers.spawn(move || { hosts.par_iter_mut().for_each(|h| { - ports.par_iter().for_each(|p| { - let mut host = h.clone(); - match host.check_port(p.clone(), tx_clone.clone()) { - Ok(_) => { - if let Ok(mut lock) = project_clone.lock() { - lock.add_host(host.clone()); - } - } - Err(_) => {} + let rt = tokio::runtime::Runtime::new().unwrap(); + let host = rt.block_on(h.portscan( + ports.clone(), + tx_clone.clone(), + pb_clone.clone(), + )); + if let Ok(mut lock) = project_clone.lock() { + if !lock.hosts.iter().any(|ph| ph.ip == host.ip) { + lock.hosts.push(host); + } else { + lock.hosts + .iter_mut() + .filter(|ph| ph.ip == host.ip) + .for_each(|ph| { + host.open_ports.iter().for_each(|p| { + if !ph.open_ports.contains(p) { + ph.open_ports.push(p.clone()); + } + }); + }); } - }); + } }); + if let Ok(mut lock) = pb_clone.write() { + lock.finished = true; + } if let Ok(lock) = project_clone.lock() { let _ = tx_clone.send(ToolMessage::UpdateProject(id, lock.clone())); @@ -1604,6 +1648,7 @@ impl AppState { }); } } + "exit" => { self.save_all()?; let _ = self.main_tx.send(ToolMessage::AppStateExit); @@ -1866,15 +1911,18 @@ impl AppState { } } } - let db = DistroBox { - name: format!("{}-{}-{}", template_box, org, name), - volumes: vec![ - project_files.display().to_string(), - project_notes.display().to_string(), - tools.to_string(), - ], - created: false, - template: template_box.to_string(), + let mut db = None; + if template_box != &"NONE".to_string() { + db = Some(DistroBox { + name: format!("{}-{}-{}", template_box, org, name), + volumes: vec![ + project_files.display().to_string(), + project_notes.display().to_string(), + tools.to_string(), + ], + created: false, + template: template_box.to_string(), + }); }; let mut new_project = Project { org_name: org, @@ -1884,7 +1932,7 @@ impl AppState { hosts: Vec::new(), config_folder: project_config_file, current: false, - db: Some(db), + db: db, scope: Vec::new(), }; match new_project.save_config() { @@ -2547,4 +2595,34 @@ impl AppState { } Ok(()) } + + pub fn run_cmd(&mut self, cmd: String) { + if let Some(db) = self.projects[self.selected_project].db.clone() { + db.run_command(&cmd); + } else { + if let Some(term_cmd) = self.config.get("term_cmd") { + if let Some((prog, args)) = term_cmd.split_once(" ") { + let mut launch_cmd = Command::new(prog); + args.split(" ").into_iter().for_each(|arg| { + launch_cmd.arg(arg); + }); + launch_cmd.arg(&cmd); + match launch_cmd.spawn() { + Ok(_) => { + let _ = self.main_tx.send(ToolMessage::Output(( + 0, + format!("[success] running {}", cmd), + ))); + } + Err(e) => { + let _ = self.main_tx.send(ToolMessage::Output(( + 0, + format!("[error] running {}, {e}", cmd), + ))); + } + } + } + } + } + } } diff --git a/src/funcs.rs b/src/funcs.rs index e0da816..8b2f86d 100644 --- a/src/funcs.rs +++ b/src/funcs.rs @@ -341,10 +341,7 @@ pub fn run_tui( Style::default().fg(Color::Green), ))); progress_lines.push(Line::from(Span::styled( - format!( - " {}/{} ({}%)", - status.complete, status.total, percentage - ), + format!(" {}/{} complete", status.complete, status.total), Style::default().fg(Color::Green), ))); } else { @@ -356,8 +353,8 @@ pub fn run_tui( ))); progress_lines.push(Line::from(Span::styled( format!( - " {}/{} ({}%)", - status.complete, status.total, percentage + " {}/{} complete", + status.complete, status.total ), Style::default().fg(Color::Cyan), ))); @@ -370,8 +367,8 @@ pub fn run_tui( ))); progress_lines.push(Line::from(Span::styled( format!( - " {}/{} ({}%)", - status.complete, status.total, percentage + " {}/{} complete", + status.complete, status.total ), Style::default().fg(Color::Magenta), ))); @@ -479,6 +476,7 @@ pub fn run_tui( ToolMessage::UpdateProject(index, project) => { if index < state.projects.len() { state.projects[index] = project; + let _ = state.save_all(); } } ToolMessage::RebuildDB => { @@ -894,9 +892,46 @@ pub fn run_tui( history_index = state.history.len(); } } - KeyCode::Char(c) => { + KeyCode::Char(c) + if !key + .modifiers + .contains(crossterm::event::KeyModifiers::CONTROL) => + { state.curent_intput.push(c); } + KeyCode::Char(c) + if key + .modifiers + .contains(crossterm::event::KeyModifiers::CONTROL) => + { + match c { + 'p' => { + state.curent_intput.push_str("new_project "); + let _ = state.main_tx.send(ToolMessage::Output(( + 0, + "type organization name and project name.".to_string(), + ))); + } + 's' => match state.save_all() { + Ok(_) => { + let _ = state.main_tx.send(ToolMessage::Output(( + 0, + format!("[success] Saved!"), + ))); + } + Err(e) => { + let _ = state.main_tx.send(ToolMessage::Output(( + 0, + format!("[error] saving: {e}"), + ))); + } + }, + 'h' => { + state.selecting_host = true; + } + _ => {} + } + } KeyCode::Backspace => { state.curent_intput.pop(); } diff --git a/src/host.rs b/src/host.rs index 7d74f4d..684ac62 100644 --- a/src/host.rs +++ b/src/host.rs @@ -1,5 +1,6 @@ use crate::ShodanData; use crate::ToolMessage; +use crate::ToolProgress; use serde_json; use shodan_rust::ShodanClient; use std::error::Error; @@ -7,9 +8,8 @@ use std::fs::File; use std::io::Write; use std::net::IpAddr; use std::net::SocketAddr; -use std::net::TcpStream; use std::path::PathBuf; -use std::sync::mpsc::Sender; +use std::sync::{Arc, RwLock, mpsc::Sender}; use std::time::Duration; #[derive(Clone, Debug)] @@ -119,24 +119,65 @@ id: {}", Ok(()) } - pub fn check_port(&mut self, port: u16, tx: Sender) -> Result<(), Box> { + pub async fn check_port(&mut self, port: u16, tx: Sender) -> Vec { if let Ok(ip) = self.ip.parse::() { let address = SocketAddr::new(ip, port); - if let Ok(_) = TcpStream::connect_timeout(&address, Duration::from_secs(3)) { - self.add_port(&format!("{}", port)); - let _ = tx.send(ToolMessage::Output(( - 0, - format!("[success] {} is open on {}", port, self.ip), - ))); + if let Ok(res) = tokio::time::timeout( + Duration::from_secs(3), + tokio::net::TcpStream::connect(&address), + ) + .await + { + if let Ok(_) = res { + self.add_port(&format!("{}", port)); + let _ = tx.send(ToolMessage::Output(( + 0, + format!("[success] {} is open on {}", port, self.ip), + ))); + } } } else { let _ = tx.send(ToolMessage::Output(( 0, format!("[error] {} is not a valid ip address!", self.ip), ))); - return Err("not valid IP".into()); } - return Ok(()); + return self.open_ports.clone(); + } + + pub async fn portscan( + &mut self, + ports: Vec, + tx: Sender, + pb: Arc>, + ) -> Host { + let mut handles = Vec::new(); + for port in ports { + let mut self_clone = self.clone(); + let tx_clone = tx.clone(); + let handle = tokio::spawn(async move { self_clone.check_port(port, tx_clone).await }); + handles.push(handle); + } + let mut results = Vec::new(); + for handle in handles { + match handle.await { + Ok(res) => { + results.push(res); + if let Ok(mut lock) = pb.write() { + lock.complete += 1; + } + } + Err(_) => {} + } + } + results.iter().for_each(|ports| { + ports.iter().for_each(|port| { + if !self.open_ports.contains(port) { + self.open_ports.push(port.clone()); + } + }); + }); + return self.clone(); } pub fn get_shodan(&mut self, key: String, tx: Sender) {