diff --git a/src/appstate.rs b/src/appstate.rs new file mode 100644 index 0000000..70b3bb2 --- /dev/null +++ b/src/appstate.rs @@ -0,0 +1,2550 @@ +use crate::Project; +use crate::Prompt; +use crate::PromptQuery; +use crate::ToolMessage; +use crate::ToolProgress; +use crate::client::Server; +use crate::host::Host; +use crate::modules::ModuleLoader; +use crate::project::DistroBox; +use crate::rust_tools::rustwitness; +use crate::rust_tools::*; +use crate::victim; +use clipboard::ClipboardContext; +use clipboard::ClipboardProvider; +use dns_lookup::lookup_host; +use fs_extra::dir::{CopyOptions, copy}; +use ipnet::IpNet; +use keyring::Entry; +use rayon::prelude::*; +use rhai::{Dynamic, Scope}; +use std::collections::HashMap; +use std::env; +use std::error::Error; +use std::fs::OpenOptions; +use std::fs::{self, File, create_dir_all, read_dir, read_to_string, remove_dir, remove_dir_all}; +use std::io::Write; +use std::net::IpAddr; +use std::path::PathBuf; +use std::process::Command; +use std::sync::RwLock; +use std::sync::{Arc, Mutex, mpsc::Sender, mpsc::channel}; +use std::thread; +use std::time::Duration; +use std::time::{self, Instant}; +use walkdir::WalkDir; + +pub struct AppState { + pub projects: Vec, + pub servers: Vec>>, + pub config: HashMap, + pub config_file: PathBuf, + pub workers: rayon::ThreadPool, + pub worker_txes: Vec>, + pub main_tx: Sender, + pub history: Vec, + pub log: Vec, + pub output: Vec, + pub selected_server: usize, + pub selected_project: usize, + pub selected_host: usize, + pub curent_intput: String, + pub module_loader: ModuleLoader, + pub output_scroll: u16, + pub output_follow: bool, + pub prompt: Prompt, + pub info_scroll: u16, + pub server_broker_running: bool, + pub app_state_running: bool, + pub remoting: bool, + pub help: Vec, + pub name: String, + pub progress_bars: Vec>>, + pub selecting_host: bool, +} + +impl AppState { + pub fn new() -> Self { + let (main_tx, _) = channel(); + Self { + projects: Vec::new(), + servers: Vec::new(), + config: HashMap::new(), + config_file: PathBuf::new(), + workers: rayon::ThreadPoolBuilder::new() + .num_threads(4) + .build() + .unwrap(), + worker_txes: Vec::new(), + main_tx, + history: Vec::new(), + log: Vec::new(), + output: Vec::new(), + selected_server: 0, + selected_project: 0, + selected_host: 0, + curent_intput: String::new(), + module_loader: ModuleLoader::new(), + output_scroll: 0, + output_follow: true, + prompt: Prompt { + action: None, + responses: Vec::new(), + execute_command: String::new(), + num_responses: 0, + prompts: Vec::new(), + last_prompted: None, + }, + info_scroll: 0, + server_broker_running: true, + app_state_running: true, + remoting: false, + help: Vec::new(), + name: String::new(), + progress_bars: Vec::new(), + selecting_host: false, + } + } + + pub fn load_config(&mut self, file: PathBuf, display: bool) -> Result<(), Box> { + self.config_file = file.clone(); + let config_contents = read_to_string(file)?; + for line in config_contents.lines() { + let parts: Vec<&str> = line.split(": ").collect(); + if parts.len() == 2 { + match parts[0].trim() { + "servers" => { + for server_data in parts[1].trim().split(", ").collect::>() { + if let Some((address, name)) = server_data.split_once("|") { + let new_server = Server { + address: address.trim().to_string(), + connected: false, + timer: Duration::from_secs(60), + last_check: time::Instant::now(), + message_que: Vec::new(), + action_que: Vec::new(), + client_id: 0, + selected_client: 0, + config: None, + logged_in: false, + password: String::new(), + name: name.to_string(), + cert_text: String::new(), + }; + self.servers.push(Arc::new(Mutex::new(new_server))); + } + } + self.config + .insert("servers".to_string(), parts[1].trim().to_string()); + } + _ => { + if parts[0].len() > 1 { + self.config + .insert(parts[0].trim().to_string(), parts[1].trim().to_string()); + } + } + } + } + } + let mut projet_folder_path = self.config_file.clone(); + projet_folder_path.pop(); + projet_folder_path.push("projects"); + let project_dir_reses = read_dir(projet_folder_path)?; + for res in project_dir_reses { + if let Ok(project_folder) = res { + let project_folder_file_reses = read_dir(project_folder.path())?; + for res in project_folder_file_reses { + if let Ok(conf_file) = res { + if conf_file.file_name().to_string_lossy() == "project.conf".to_string() { + let mut new_project = Project::new(); + new_project.config_folder(conf_file.path()); + new_project.load_config(display)?; + let template_box = self.config.get("template_box").unwrap(); + let tools = self.config.get("tools").unwrap(); + let db = DistroBox { + name: format!( + "{}-{}-{}", + template_box, + new_project.org_name.clone(), + new_project.name.clone() + ), + volumes: vec![ + new_project.files.display().to_string(), + new_project.notes.display().to_string(), + tools.clone(), + ], + created: false, + template: template_box.clone(), + }; + new_project.db = Some(db); + self.projects.push(new_project); + } + } + } + } + } + let mut server_folder_path = self.config_file.clone(); + server_folder_path.pop(); + server_folder_path.push("client_servers"); + if server_folder_path.exists() { + let server_confs = read_dir(server_folder_path)?; + server_confs.into_iter().for_each(|entry| { + if let Ok(entry) = entry { + if let Ok(text) = read_to_string(entry.path()) { + let mut name = String::new(); + let mut address = String::new(); + let mut cert = String::new(); + text.lines().into_iter().for_each(|line| { + if let Some((setting, data)) = line.split_once(": ") { + match setting.trim() { + "name" => name = data.trim().to_string(), + "address" => address = data.trim().to_string(), + "cert" => cert = data.trim().to_string(), + _ => {} + } + } + }); + let new_server = Server { + address, + connected: false, + timer: Duration::from_secs(90), + config: None, + last_check: Instant::now(), + message_que: Vec::new(), + action_que: Vec::new(), + client_id: 0, + selected_client: 0, + logged_in: false, + password: String::new(), + name, + cert_text: cert, + }; + self.servers.push(Arc::new(Mutex::new(new_server))); + } + } + }); + } + self.help.push("help\nThat's MEEEEEEE\n".to_string()); + self.help.push("new_project\nadd a project to the upcoming project pool and create default notes based on templates\nusage:\nnew_project organization_name, project_name\n".to_string()); + self.help.push( + "current_project\nshow information on the currently selected project\n".to_string(), + ); + self.help + .push("promote_project\npromote a project from upcoming to current\n".to_string()); + self.help.push( + "remove_project\nremove a project, delete its folders, and destroy its distrobox\n" + .to_string(), + ); + self.help.push("new_terminal\nopen a new terminal in the distrobox of the currently selected project\n".to_string()); + self.help + .push("new_server\ncreate a new server object\n".to_string()); + self.help + .push("list_servers\nlist all servers currently tracked by the tool\n".to_string()); + self.help + .push("connect_server\nconnect to a tracked server\n".to_string()); + self.help + .push("save_all\nsave all projects and settings\n".to_string()); + self.help + .push("save_projects\nsave just the projects\n".to_string()); + self.help + .push("test_server\ntest the server connection\n".to_string()); + self.help.push( + "add_scope\nadd a host, cidr range, or ip address to the current project's scope\n" + .to_string(), + ); + self.help.push("parse_scope\nparse the projects general.md notes file for the scope copied from the workbook\n".to_string()); + self.help.push("parse_portscan | pps\nParse a services.tsv file exported from cobalt stirke in your current project's files directory\n".to_string()); + self.help + .push("stop_db\nrestart this project's distrobox".to_string()); + self.help.push("rustwitness\nRun a scan on the hosts in the urls.txt file in yoru project files directory that captures screenshots of those urls, optionally through a proxy.\n".to_string()); + self.help.push("portscan\nRun a simple TCP portscan on either the scope for the project, or a given list of ips.\nportscan ip,ip,ip,ip port,port,port,port or portscan port,port,port\n".to_string()); + self.help.push("rustbuster\nRun the Subdomain/Subdirectory bruteforcetool RustBuster (included in tetanus now!) against a given domain name or url.\nrustbuster target,target,target subs=/path/to/subwordlist dirs=/path/to/dirwordlist\n".to_string()); + self.help + .push("clear_operations\nClear the completed operation statuses.\n".to_string()); + self.help + .push("shodan\nGet the shodan info for the currently selected host\n".to_string()); + self.help + .push("select_host\nSelect a host in the current project.\n".to_string()); + self.help.push( + "shodan_all\nGet the shodan info for all hosts in the current project.\n".to_string(), + ); + self.help.push( + "set_shodan_key\nSet your shodan api key, and save it to your system's keyring.\n" + .to_string(), + ); + 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("exit\nquit the tool\n".to_string()); + self.initialize_modules(); + return Ok(()); + } + + pub fn initialize_modules(&mut self) { + if let Some(base_path_str) = self.config.get("module_path") { + let base_path = PathBuf::from(base_path_str); + + println!("Loading Tetanus modules from: {:?}", base_path); + if let Err(e) = self.module_loader.load_all(&base_path) { + eprintln!("Failed to load modules: {}", e); + } else { + for module in &self.module_loader.commands { + self.help.push(format!( + "{}\n{}\n", + module.1.name.clone(), + module.1.help.clone() + )); + } + println!( + "Successfully loaded {} modules.", + self.module_loader.commands.len() + ); + } + } else { + eprintln!("Warning: 'module_path' is missing from AppState config map!"); + } + } + pub fn execute_command( + &mut self, + command_name: &str, + command_args: Option, + rid: usize, + ) -> Result<(), Box> { + let tx = self.main_tx.clone(); + match command_name { + "remote" => { + if let Some(server) = self.servers.get(self.selected_server) { + if let Ok(mut server) = server.lock() { + if server.selected_client != 0 { + if let Some(args) = command_args.clone() { + let args = args.trim(); + if args.contains(" ") { + let (cmd, iargs) = args.split_once(" ").unwrap(); + server.message_que.push(format!("CMD|{} {}", cmd, iargs)); + } else { + server.message_que.push(format!("CMD|{}", args)); + } + let _ = self.main_tx.send(ToolMessage::Output(( + 0, + format!("Tasked {} to run {}", server.selected_client, args), + ))); + } + } + } + } + } + "new_project" | "np" => { + let args = command_args.unwrap(); + let (org, name) = args.split_once(" ").unwrap(); + if let Err(e) = self.new_project(org.to_string(), name.to_string(), rid) { + tx.send(ToolMessage::Output(( + rid, + format!("[error] making {}-{}: {e}", org, name), + )))?; + } else { + tx.send(ToolMessage::Output(( + rid, + format!("[success] {}-{} created!", org, name), + )))?; + } + } + "generate_victim" => { + let mut success = false; + if let Some(args) = command_args { + if args.split(" ").into_iter().count() == 1 { + let mut address = String::new(); + let mut password = String::new(); + let config_file = self.config_file.clone(); + let files = self.projects[self.selected_project].files.clone(); + let mut cert_text = String::new(); + if let Ok(lock) = self.servers[self.selected_server].lock() { + address = lock.address.clone(); + password = lock.password.clone(); + cert_text = lock.cert_text.clone(); + } + match victim::generate_code( + address, + password, + config_file, + files, + args, + self.main_tx.clone(), + cert_text, + ) { + Ok(_) => { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + "[Success] Victim code generated! Compilation thread spawned!" + .to_string(), + ))); + success = true; + } + Err(e) => { + let _ = self.main_tx.send(ToolMessage::Output(( + 0, + format!("[error] generating victim code: {e}"), + ))); + } + } + } + } + if !success { + let _ = self.main_tx.send(ToolMessage::Output(( + 0, + format!("Usage: generate_victim (windows/linux)"), + ))); + } + } + "current_project" | "cp" => { + let mut out_vec = Vec::new(); + out_vec.push(format!( + "org: {}", + &self.projects[self.selected_project].org_name + )); + out_vec.push(format!( + "name: {}", + &self.projects[self.selected_project].name + )); + out_vec.push(format!( + "files: {}", + &self.projects[self.selected_project].files.display() + )); + out_vec.push(format!( + "notes: {}", + &self.projects[self.selected_project].notes.display() + )); + out_vec.push(format!( + "config_folder: {}", + &self.projects[self.selected_project].config_folder.display() + )); + if let Some(db) = self.projects[self.selected_project].db.clone() { + out_vec.push(format!("distrobox: {}", db.name)); + } + if self.projects[self.selected_project].current { + out_vec.push(format!("status: current")); + } else { + out_vec.push(format!("status: upcoming")); + } + out_vec.push(format!( + "Num hosts: {}", + &self.projects[self.selected_project].hosts.iter().count() + )); + for line in out_vec { + let _ = self.main_tx.send(ToolMessage::Output((rid, line))); + } + } + "rebuild_distro_box" | "rdb" => { + if let Some(db) = self.projects[self.selected_project].db.clone() { + if db.created { + let _ = self.main_tx.send(ToolMessage::DestroyDB(db.clone())); + } + let _ = self.main_tx.send(ToolMessage::RebuildDB); + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + "[success] Distorbox made!".to_string(), + ))); + } + } + "promote_project" | "pp" => { + self.main_tx.send(ToolMessage::Output(( + rid, + String::from("promote_project command recieved!"), + )))?; + match self.promote_project(rid) { + Ok(_) => { + self.main_tx.send(ToolMessage::Output(( + rid, + String::from("[success] project promoted!"), + )))?; + } + Err(e) => { + self.main_tx.send(ToolMessage::Output(( + rid, + format!("[error] promoting project: {}", e), + )))?; + } + } + } + "remove_project_confirm" => { + if self.prompt.responses.len() > 0 { + if self.prompt.responses[0] + .response + .to_lowercase() + .contains("y") + { + let project = self.projects[self.selected_project].clone(); + let mut config_file = project.config_folder.clone(); + config_file.pop(); + if let Err(e) = remove_dir_all(config_file) { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!( + "failed to delete config: {e} on {}", + &project.config_folder.display() + ), + ))); + } + if let Err(e) = remove_dir_all(&project.notes) { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!( + "failed to delete notes: {e} on {}", + &project.notes.display() + ), + ))); + } else if let Some(parent) = project.notes.parent() { + if let Ok(mut entries) = read_dir(parent) { + if entries.next().is_none() { + if let Err(e) = remove_dir(parent) { + let _ = self.main_tx.send(ToolMessage::Output((rid, format!("failed to delete the empty client notes folder: {e} on {}", parent.display())))); + } + } + } + } + if let Err(e) = remove_dir_all(&project.files) { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!( + "failed to delete files: {e} on {}", + &project.files.display() + ), + ))); + } else if let Some(parent) = project.files.parent() { + if let Ok(mut entries) = read_dir(parent) { + if entries.next().is_none() { + if let Err(e) = remove_dir(parent) { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!( + "failed to delete empty files parent: {e} on {}", + parent.display() + ), + ))); + } + } + } + } + let remove_id = self.selected_project.clone(); + let project = self.projects[remove_id].clone(); + if let Some(db) = project.db { + self.main_tx.send(ToolMessage::DestroyDB(db))?; + } + self.selected_project -= 1; + self.projects.remove(remove_id); + self.prompt.responses.clear(); + self.prompt.action = None; + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!( + "{}-{} was sucessfully removed!", + project.org_name, project.name + ), + ))); + } + } + } + "remove_project" | "rp" => { + let project = self.projects[self.selected_project].clone(); + self.prompt.action = Some(ToolMessage::RemoveProject); + self.prompt.num_responses = 1; + self.prompt.execute_command = String::from("remove_project_confirm"); + self.prompt.prompts.push(PromptQuery { + id: 0, + query: format!( + "{}, {} and all contentes wil lbe deleted. Contineue? (y/N", + project.files.display(), + project.notes.display() + ), + }); + let _ = self + .main_tx + .send(ToolMessage::Input((rid, "prompt".to_string()))); + } + "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") { + db.launch_terminal(cmd.to_string()); + } + } + } + "new_server" | "ns" => { + self.prompt.num_responses = 2; + self.prompt.action = Some(ToolMessage::AddServer); + self.prompt.execute_command = String::from("add_server"); + self.prompt.prompts.push(PromptQuery { + id: 0, + query: "What is the IP of the server?".to_string(), + }); + self.prompt.prompts.push(PromptQuery { + id: 1, + query: "What is the port for the server?".to_string(), + }); + let _ = self + .main_tx + .send(ToolMessage::Input((0, "prompt".to_string()))); + } + "add_server" => { + let mut ip = String::new(); + let mut port = String::new(); + self.prompt.responses.iter().for_each(|res| { + if res.query.id == 0 { + ip = res.response.clone(); + } else if res.query.id == 1 { + port = res.response.clone(); + } + }); + let new_server = Server { + address: format!("{}:{}", ip, port), + connected: false, + timer: Duration::from_secs(5), + last_check: time::Instant::now(), + message_que: Vec::new(), + action_que: Vec::new(), + client_id: 0, + selected_client: 0, + config: None, + logged_in: false, + password: String::new(), + name: String::new(), + cert_text: String::new(), + }; + self.servers.push(Arc::new(Mutex::new(new_server))); + self.save_all()?; + self.prompt.reset(); + } + "connect_server" => { + let _ = self.main_tx.send(ToolMessage::ConnectServer); + } + "prompt" => { + if self.prompt.responses.len() == self.prompt.num_responses { + let _ = self + .main_tx + .send(ToolMessage::Input((0, self.prompt.execute_command.clone()))); + let _ = self.main_tx.send(ToolMessage::EndPrompt); + } else { + for query in &self.prompt.prompts { + if !self + .prompt + .responses + .iter() + .any(|res| res.query.id == query.id) + { + self.prompt.last_prompted = Some(query.clone()); + let _ = self + .main_tx + .send(ToolMessage::Output((rid, query.query.clone()))); + break; + } + } + } + } + "list_servers" => { + self.servers.iter().enumerate().for_each(|(id, m)| { + if let Ok(s) = m.lock() { + if id == self.selected_server { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!( + "Server ID:{} Name:{} Address:{} ClientID:{} Sleep:{} Last:{} Loggedin: {} - Currently Selected", + id, s.name, s.address, s.client_id, s.timer.as_secs(), s.last_check.elapsed().as_secs(), s.logged_in + ), + ))); + } else { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!( + "Server ID:{} Name:{} Address:{} ClientID:{} Sleep:{} Last:{}, loggedin: {}", + id, s.name, s.address, s.client_id, s.timer.as_secs(), s.last_check.elapsed().as_secs(), s.logged_in + ), + ))); + } + } + }); + } + "remove_server" => { + let _ = self.main_tx.send(ToolMessage::DisconnectServer); + } + "server_remove_confim" => { + let args = command_args.unwrap(); + let id = args.trim().parse::().unwrap(); + self.servers.remove(id); + if self.selected_server == id && self.selected_server >= 1 { + let _ = self.main_tx.send(ToolMessage::Output(( + 0, + format!("[success] server {} removed!", self.selected_server), + ))); + self.selected_server = self.selected_server - 1; + } + } + "select_server" => { + self.prompt.action = Some(ToolMessage::SelectServer); + self.prompt.num_responses = 1; + self.prompt.prompts.push(PromptQuery { + id: 0, + query: String::from("Selection?"), + }); + self.prompt.execute_command = String::from("server_selected"); + self.servers.iter().enumerate().for_each(|(id, servermut)| { + if let Ok(server) = servermut.lock() { + if id == self.selected_server { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!("{}:{} - Currently Selected", id, server.address.clone()), + ))); + } else { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!("{}: {}", id, server.address.clone()), + ))); + } + } + }); + } + "server_selected" => { + if self.prompt.responses.len() == 1 { + if let Ok(id) = self.prompt.responses[0].response.parse::() { + self.selected_server = id; + if let Ok(server) = self.servers[id].lock() { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!("{} Selected!", server.address), + ))); + } + } else { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + String::from("Invalid selection."), + ))); + } + } else { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + String::from("[error] selecting server: no prompt responses found!"), + ))); + } + let _ = self.main_tx.send(ToolMessage::EndPrompt); + } + "test_server" => { + if let Some(server) = self.servers.get(self.selected_server) { + if let Ok(mut server) = server.lock() { + server.message_que.push("TEST|NONE".to_string()); + } else { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + "[error] getting server lock!".to_string(), + ))); + } + } else { + self.output + .push(String::from("[error] getting selected server!")); + } + } + "server_sleep" => { + if let Some(args) = command_args { + if let Ok(seconds) = args.trim().parse::() { + if let Some(server) = self.servers.get(self.selected_server) { + if let Ok(mut server) = server.lock() { + server.timer = Duration::from_secs(seconds); + self.output + .push(format!("Server timer set to {} seconds", seconds)); + } else { + self.output + .push(String::from("[error] locking server object!")); + } + } else { + self.output + .push(String::from("[error] not a valid server selected!")); + } + } else { + self.output + .push(String::from("invalid second count provided!")); + } + } else { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + "[error] no seconds provided! please use server_sleep number_of_seconds" + .to_string(), + ))); + } + } + "save_all" => { + self.save_all()?; + } + "save_projects" => { + self.save_projects()?; + } + "list_clients" => { + if let Some(server) = self.servers.get(self.selected_server) { + if let Ok(mut server) = server.lock() { + server.message_que.push("LIST_CLIENTS|NONE".to_string()); + } else { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + "[error] getting server lock!".to_string(), + ))); + } + } else { + self.output + .push(String::from("[error] getting selected server!")); + } + } + "select_client" => { + if let Some(arg) = command_args { + if let Some(server) = self.servers.get(self.selected_server) { + if let Ok(mut server) = server.lock() { + if let Ok(id) = arg.parse::() { + server.message_que.push(format!("STOP_CONTROL")); + server.message_que.push(format!("CONTROL|{}", id.clone())); + server.selected_client = id; + let _ = self.main_tx.send(ToolMessage::Output(( + 0, + format!("client {} selected!", id), + ))); + } else { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!("[error] parsing client id from arg: {}", arg), + ))); + } + } + } + } else { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!("No client identified. Usage: select_client client_id"), + ))); + } + } + "deselect_client" => { + if let Some(server) = self.servers.get(self.selected_server) { + if let Ok(mut server) = server.lock() { + server.message_que.push(format!("STOP_CONTROL")); + server.selected_client = 0; + let _ = self + .main_tx + .send(ToolMessage::Output((rid, format!("client deselected!")))); + } + } + } + "add_scope" => { + if let Some(args) = command_args { + args.split_whitespace().into_iter().for_each(|host| { + self.projects[self.selected_project].add_scope(host.to_string()); + }); + } + } + "parse_scope" => { + let mut general_path = self.projects[self.selected_project].notes.clone(); + general_path.push("general.md"); + let general_string = read_to_string(general_path)?; + if let Some((_, scope_section)) = general_string.split_once("# Scope") { + if let Some((scope_section, _)) = scope_section.split_once("#") { + scope_section.lines().into_iter().for_each(|line| { + if line.contains("|") { + if !line.contains("Subnet") { + if !line.contains("--") { + let host = + line.split("|").collect::>()[1].to_string(); + self.projects[self.selected_project].add_scope(host); + } + } + } + }); + } + } + } + "show_config" | "sc" => { + let _ = self + .main_tx + .send(ToolMessage::Output((rid, "Current Config:".to_string()))); + self.config.iter().for_each(|setting| { + let _ = self.main_tx.send(ToolMessage::Output(( + 0, + format!("{}:{}", setting.0, setting.1), + ))); + }); + } + "set" => { + let mut ready = false; + 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 self.config.contains_key(&key) { + ready = true; + } else { + let _ = self.main_tx.send(ToolMessage::Output(( + 0, + "[error] Unknown setting! {key}, {value}".to_string(), + ))); + return Ok(()); + } + } + } + if ready { + self.config.insert(key, value); + self.save_config()?; + let _ = self.main_tx.send(ToolMessage::Output(( + 0, + "[success]Setting changed!".to_string(), + ))); + } else { + let _ = self.main_tx.send(ToolMessage::Output(( + 0, + "[error] malformed command!".to_string(), + ))); + let _ = self.main_tx.send(ToolMessage::Output(( + 0, + "Usag: set setting value".to_string(), + ))); + } + } + "list_modules" => { + self.module_loader.commands.iter().for_each(|module| { + let _ = self + .main_tx + .send(ToolMessage::Output((rid, format!("{}", module.0)))); + }); + } + "server_login" => { + if let Some(pass) = command_args { + let pass = pass.trim().to_string(); + if let Some(server) = self.servers.get(self.selected_server) { + if let Ok(mut lock) = server.lock() { + lock.login(pass)?; + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + "[success] Logged into Server!".to_string(), + ))); + } + } + } + } + "new_config" => { + self.prompt.num_responses = 8; + self.prompt.action = Some(ToolMessage::Input((0, "add_config".to_string()))); + self.prompt.execute_command = String::from("add_config"); + self.prompt.prompts.push(PromptQuery { + id: 0, + query: "name for the new config?".to_string(), + }); + self.prompt.prompts.push(PromptQuery { + id: 1, + query: "Path to save the config folder?".to_string(), + }); + self.prompt.prompts.push(PromptQuery { + id: 2, + query: "Path for upcoming project files (not notes)?".to_string(), + }); + self.prompt.prompts.push(PromptQuery { + id: 3, + query: "Path for current project files (not notes)?".to_string(), + }); + self.prompt.prompts.push(PromptQuery { + id: 4, + query: "Path for upcoming notes?".to_string(), + }); + self.prompt.prompts.push(PromptQuery { + id: 5, + query: "Path for current notes?".to_string(), + }); + self.prompt.prompts.push(PromptQuery { + id: 6, + query: "Path to tool directory?".to_string(), + }); + self.prompt.prompts.push(PromptQuery { + id: 7, + query: "Template box name?".to_string(), + }); + let _ = self + .main_tx + .send(ToolMessage::Input((0, "prompt".to_string()))); + } + "add_config" => { + self.prompt.responses.iter().for_each(|res| { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!("{} | {}", res.query.query, res.response), + ))); + }); + self.new_config()?; + self.save_config()?; + } + "list_configs" => { + if let Some(config_line) = self.config.get("alternate_configs") { + config_line.split(",").into_iter().for_each(|entry| { + if let Some((name, path)) = entry.split_once("|") { + let _ = self + .main_tx + .send(ToolMessage::Output((rid, format!("{}: {}", name, path)))); + } + }); + } + } + "collab_with" => { + if let Some(args) = command_args { + if let Ok(id) = args.trim().parse::() { + if let Some(server) = self.servers.get(self.selected_server) { + if let Ok(mut lock) = server.lock() { + lock.action_que.push(format!("COLLAB|{}", id)); + } + } + } + } + } + "collab" => { + if let Some(args) = command_args { + if let Some(server) = self.servers.get(self.selected_server) { + if let Ok(mut lock) = server.lock() { + lock.action_que.push(format!("CMD|{}", args)); + } + } + } + } + "parse_port_scan" | "pps" => { + self.parse_cs_portscan()?; + } + "stop_db" => { + if let Some(project) = self.projects.get_mut(self.selected_project) { + if let Some(db) = project.db.as_mut() { + db.stop(self.main_tx.clone(), rid)?; + } + } + } + "rustwitness" => { + if let Some(project) = self.projects.get(self.selected_project) { + if let Some(args) = command_args { + if let Some((outfile, proxy)) = args.split_once(" ") { + let outfile = format!("{}/{}", project.files.display(), outfile); + let mut proxy_string = None; + if let Some((ip, port)) = proxy.split_once(" ") { + proxy_string = Some(format!("socks5://{}:{}", ip, port)); + } + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!( + "[success] rustwitness running will save screenshots to {}", + outfile + ), + ))); + let mut urls = Vec::new(); + WalkDir::new(&project.files) + .into_iter() + .filter(|res| res.is_ok()) + .for_each(|res| { + if let Ok(entry) = res { + if entry.file_name().to_string_lossy().contains("urls.txt") + { + if let Ok(input) = read_to_string(entry.path()) { + input.lines().into_iter().for_each(|url| { + urls.push(url.to_string()); + }); + } + } + } + }); + let proxy_string_clone = proxy_string.clone(); + let out_folder = PathBuf::from(outfile); + let tx_clone = self.main_tx.clone(); + self.workers.spawn(move || { + urls.par_iter().for_each(|url| { + if url.contains("://"){ + match rustwitness( + url.clone(), + out_folder.clone(), + proxy_string_clone.clone(), + ) { + Ok(_) => { + let _ = tx_clone.send(ToolMessage::Output(( + rid, + format!("[success] {} captured!", url), + ))); + } + Err(e) => { + let _ = tx_clone.send(ToolMessage::Output(( + rid, + format!( + "[error] capturing screenshot of {url}: {e}" + ), + ))); + } + } + } + else{ + let _ = tx_clone.send(ToolMessage::Output(( + rid, + format!( + "[error] capturing screenshot of {url}: not a valid url." + ), + ))); + } + }); + let _ = tx_clone.send(ToolMessage::Output(( + rid, + "[success] all urls scanned for screenshots!".to_string(), + ))); + }); + } + } else { + let _ = self.main_tx.send(ToolMessage::Output(( + 0, + "[error] incorrect command usage!".to_string(), + ))); + let _ = self.main_tx.send(ToolMessage::Output(( + 0, + "usage: rustwitness output_file_name optional_proxy_ip optional_proxy_port".to_string(), + ))); + } + } + } + "portscan" => { + if let Some(project) = self.projects.get(self.selected_project) { + let mut ports = Vec::new(); + let mut hosts = project.hosts.clone(); + if let Some(args) = command_args { + let args_vec: Vec<&str> = args.split_whitespace().collect(); + match args_vec.len() { + 1 => { + args_vec[0].split(",").into_iter().for_each(|p| { + if let Ok(port) = p.trim().parse::() { + ports.push(port); + } + }); + } + 2 => { + args_vec[0].split(",").into_iter().for_each(|ip| { + if let Ok(ip) = ip.parse::() { + let mut new_host = Host::new(); + new_host.ip = ip.to_string(); + hosts.push(new_host); + } else if let Ok(net) = ip.parse::() { + for host_addr in net.hosts() { + let mut new_host = Host::new(); + new_host.ip = host_addr.to_string(); + hosts.push(new_host); + } + } + }); + args_vec[1].split(",").into_iter().for_each(|p| { + if p.contains("-") { + if let Some((start, end)) = p.split_once("-") { + if let Ok(start) = start.parse::() { + if let Ok(end) = end.parse::() { + for range in [start..end] { + range.into_iter().for_each(|port| { + ports.push(port.clone()); + }); + } + } + } + } + } else if let Ok(port) = p.trim().parse::() { + ports.push(port); + } + }); + } + _ => {} + } + if !ports.is_empty() && !hosts.is_empty() { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!("scanning {} ports on {} hosts", ports.len(), hosts.len()), + ))); + let project_clone = Arc::new(Mutex::new(project.clone())); + let id = self.selected_project.clone(); + let tx_clone = self.main_tx.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(_) => {} + } + }); + }); + if let Ok(lock) = project_clone.lock() { + let _ = + tx_clone.send(ToolMessage::UpdateProject(id, lock.clone())); + let _ = tx_clone.send(ToolMessage::Output(( + 0, + "[success] portscan finished!".to_string(), + ))); + } + }); + } + } + } + } + "rustbuster" => { + if let Some(args) = command_args { + let args: Vec<&str> = args.split(" ").collect(); + let mut dirs = Vec::new(); + let mut subs = Vec::new(); + let mut targets = Vec::new(); + if args + .iter() + .any(|a| a.contains("subs=") || a.contains("dirs=")) + { + args.iter().for_each(|arg| { + if arg.contains("subs=") { + if let Some((_, sub_path)) = arg.split_once("=") { + if let Ok(subtext) = read_to_string(sub_path) { + subtext.lines().into_iter().for_each(|sub| { + subs.push(sub.to_string()); + }); + } + } + } + if arg.contains("dirs=") { + if let Some((_, dir_path)) = arg.split_once("=") { + if let Ok(dir_text) = read_to_string(dir_path) { + dir_text.lines().into_iter().for_each(|dir| { + dirs.push(dir.to_string()); + }); + } + } + } else { + if arg.contains(",") { + arg.split(",").into_iter().for_each(|t| { + targets.push(t.to_string()); + }); + } else if !arg.contains("=") { + targets.push(arg.to_string()); + } + } + }); + let mut num_dir = 0; + let mut num_sub = 0; + let tx_clone = self.main_tx.clone(); + let out_path = self.projects[self.selected_project].files.clone(); + let project_clone = + Arc::new(Mutex::new(self.projects[self.selected_project].clone())); + let mut buster_targets = Vec::new(); + for target in targets { + if target.contains("://") { + for dir in &dirs { + num_dir += 1; + buster_targets + .push(BusterTarget::Dir(format!("{}/{}", target, dir))); + } + } else if target.contains(".") { + for sub in &subs { + num_sub += 1; + buster_targets + .push(BusterTarget::Sub(format!("{}.{}", sub, target))); + } + } + } + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!("{} targets found scanning...", num_dir + num_sub), + ))); + let new_progress = Arc::new(RwLock::new(ToolProgress { + name: format!( + "rustbuster - {} {}", + self.projects[self.selected_project].org_name.clone(), + self.projects[self.selected_project].name.clone() + ), + total: num_dir + num_sub, + complete: 0, + finished: false, + })); + self.progress_bars.push(new_progress.clone()); + self.workers.spawn(move || { + buster_targets.par_iter().for_each(|t| match t { + BusterTarget::Dir(_) => { + if let Some(res) = rustbuster(t.clone()) { + let mut url_path = out_path.clone(); + url_path.push("urls.txt"); + if let Ok(mut url_file) = OpenOptions::new() + .write(true) + .append(true) + .create(true) + .open(&url_path) + { + if let Err(e) = writeln!( + url_file, + "{}", + format!("[success] {}", res) + ) { + let _ = tx_clone.send(ToolMessage::Output(( + rid, + format!( + "[error] saving url to {}: {e}", + url_path.display() + ), + ))); + } + } + let _ = tx_clone.send(ToolMessage::Output(( + rid, + format!("[success] {}", res), + ))); + } + if let Ok(mut lock) = new_progress.write() { + lock.complete += 1; + } + } + BusterTarget::Sub(_) => { + if let Some(res) = rustbuster(t.clone()) { + let mut url_path = out_path.clone(); + url_path.push("domains.txt"); + if let Ok(mut url_file) = OpenOptions::new() + .write(true) + .append(true) + .create(true) + .open(&url_path) + { + if let Err(e) = + writeln!(url_file, "{}", format!("{}", res)) + { + let _ = tx_clone.send(ToolMessage::Output(( + rid, + format!( + "[error] saving domain to {}: {e}", + url_path.display() + ), + ))); + } + } + let _ = tx_clone.send(ToolMessage::Output(( + rid, + format!("[success] {}", res), + ))); + if let Some((domain, ip)) = res.split_once(":") { + let mut new_host = Host::new(); + new_host.ip = ip.to_string(); + new_host.hostname = domain.to_string(); + if let Ok(mut project_lock) = project_clone.lock() { + project_lock.add_host(new_host); + } + } + } + if let Ok(mut lock) = new_progress.write() { + lock.complete += 1; + } + } + }); + if let Ok(mut lock) = new_progress.write() { + lock.finished = true; + } + drop(project_clone); + }); + } else { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + "[error] no sub or dir wordlist provided!".to_string(), + ))); + let _ = self + .main_tx + .send(ToolMessage::Output((rid, "USAGE:".to_string()))); + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + "rustbuster target,target,target subs=/path/to/subwordlist dirs=/path/to/dirwordlist".to_string(), + ))); + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + "be sure to include the subs= or dirs= part of the command above!" + .to_string(), + ))); + } + } else { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + "[error] no arguments provided!".to_string(), + ))); + let _ = self + .main_tx + .send(ToolMessage::Output((rid, "USAGE:".to_string()))); + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + "rustbuster target,target,target subs=/path/to/subwordlist dirs=/path/to/dirwordlist".to_string(), + ))); + } + } + "clear_operations" => { + self.progress_bars.retain(|pb| { + let lock = pb.read().unwrap(); + !lock.finished + }); + } + "shodan" => { + if let Some(project) = self.projects.get(self.selected_project) { + if let Some(host) = project.hosts.get(self.selected_host) { + if let Ok(entry) = Entry::new("tetanus", "shodan_key") { + if let Ok(key) = entry.get_password() { + let mut host_clone = host.clone(); + let tx_clone = self.main_tx.clone(); + thread::spawn(move || host_clone.get_shodan(key, tx_clone)); + } else { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!("[error] couldn't get shodan key from keyring!"), + ))); + } + } else { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!("[error] couldn't find shodan key in keyring!"), + ))); + } + } + } + } + "shodan_all" => { + if let Some(project) = self.projects.get(self.selected_project) { + if !project.hosts.is_empty() { + if let Ok(entry) = Entry::new("tetanus", "shodan_key") { + if let Ok(key) = entry.get_password() { + project.hosts.iter().for_each(|h| { + let mut host_clone = h.clone(); + let tx_clone = self.main_tx.clone(); + let key_clone = key.clone(); + thread::spawn(move || { + host_clone.get_shodan(key_clone, tx_clone) + }); + }); + } else { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!("[error] couldn't get shodan key from keyring!"), + ))); + } + } else { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!("[error] couldn't find shodan key in keyring!"), + ))); + } + } + } + } + "set_shodan_key" => { + if let Some(args) = command_args { + if let Ok(_) = Entry::store_status() { + if let Ok(entry) = Entry::new("tetanus", "shodan_key") { + if let Err(e) = entry.set_password(args.trim()) { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!("[error] couldn't set shodan apikey!: {e}"), + ))); + } + } else { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!("[error] couldn't create new entry!"), + ))); + } + } else { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!("[error] store status bad!"), + ))); + } + } + } + "save_server" => { + if let Some(server) = self.servers.get(self.selected_server) { + let mut path = self.config_file.clone(); + path.pop(); + path.push("client_servers"); + if !path.exists() { + create_dir_all(&path).unwrap(); + } + if let Ok(mut lock) = server.lock() { + match lock.save(path) { + Ok(_) => { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!("[success] {} saved!", lock.address), + ))); + } + Err(e) => { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!("[error] saving server: {e}"), + ))); + } + } + } + } + } + "parse_nessus" => { + if let Some(arg) = command_args { + let path = PathBuf::from(arg); + let csv_text = read_to_string(path)?; + let mut parsed_hosts: Vec = Vec::new(); + csv_text + .lines() + .into_iter() + .filter(|line| !line.contains("Host,Protocol,Port") && line.contains(",")) + .for_each(|line| { + let parts: Vec<&str> = line.split(",").collect(); + let name = parts[0].replace("\"", "").trim().to_string(); + let port = parts[2].replace("\"", "").trim().to_string(); + if let Ok(_) = name.parse::() { + if !parsed_hosts.iter().any(|h| h.ip == name) { + let mut new_host = Host::new(); + new_host.ip = name.clone(); + new_host.add_port(&port); + parsed_hosts.push(new_host); + } else { + parsed_hosts.iter_mut().filter(|h| h.ip == name).for_each( + |h| { + h.add_port(&port); + }, + ); + } + } else { + if let Ok(ips) = lookup_host(name.as_str()) { + ips.for_each(|ip| { + if !parsed_hosts.iter().any(|h| h.ip == ip.to_string()) { + let mut new_host = Host::new(); + new_host.ip = ip.to_string().clone(); + new_host.hostname = name.clone(); + new_host.add_port(&port); + parsed_hosts.push(new_host) + } else { + parsed_hosts + .iter_mut() + .filter(|h| h.ip == ip.to_string()) + .for_each(|h| { + if h.hostname != name { + h.hostname = name.clone(); + } + if port != "0" { + h.add_port(&port); + } + }); + } + }); + } + } + }); + parsed_hosts.iter().for_each(|h| { + if !self.projects[self.selected_project] + .hosts + .iter() + .any(|ph| ph.ip == h.ip) + { + self.projects[self.selected_project].add_host(h.clone()); + } else { + self.projects[self.selected_project] + .hosts + .iter_mut() + .filter(|ph| ph.ip == h.ip) + .for_each(|ph| { + h.open_ports.iter().for_each(|p| { + ph.add_port(&format!("{}", p)); + }); + if ph.hostname != h.hostname { + ph.hostname = h.hostname.clone(); + } + }); + } + }); + self.save_all()?; + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + "[success] Project hosts saved!".to_string(), + ))); + let mut host_notes_folder = self.projects[self.selected_project].notes.clone(); + host_notes_folder.push("host_notes.md"); + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!("writing to {}...", host_notes_folder.display()), + ))); + let mut host_notes_file = OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(host_notes_folder)?; + writeln!( + host_notes_file, + "#{} #external #host_notes", + self.projects[self.selected_project].org_name.clone() + )?; + self.projects[self.selected_project] + .hosts + .iter() + .filter(|h| !h.open_ports.is_empty()) + .for_each(|h| { + let _ = writeln!(host_notes_file, "# {}", h.ip); + let _ = writeln!(host_notes_file, "hostname: {}", h.hostname); + let _ = writeln!(host_notes_file, "ports:\n"); + let _ = writeln!(host_notes_file, "| port | service | vulns |",); + let _ = writeln!(host_notes_file, "| ---- | ------- | ----- |",); + h.open_ports.iter().for_each(|p| { + let _ = writeln!(host_notes_file, "| {} | | |", p); + }); + let _ = writeln!(host_notes_file, "\n\n",); + let _ = writeln!(host_notes_file, "---",); + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!("[success] notes for {} written!", h.ip), + ))); + }); + let _ = writeln!(host_notes_file, "# Hosts with no ports open:\n"); + self.projects[self.selected_project] + .hosts + .iter() + .filter(|h| h.open_ports.is_empty()) + .for_each(|h| { + let _ = writeln!(host_notes_file, "- {}:{}", h.ip, h.hostname); + }); + } + } + "exit" => { + self.save_all()?; + let _ = self.main_tx.send(ToolMessage::AppStateExit); + let _ = self + .main_tx + .send(ToolMessage::Output((rid, String::from("Good bye!")))); + } + _ => { + if self.module_loader.asts.get(command_name).is_none() { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!("[error] command not found: {}", command_name), + ))); + return Ok(()); + } + let ast = self + .module_loader + .asts + .get(command_name) + .ok_or_else(|| format!("[error] Command not found: {}", command_name))? + .clone(); + let engine = Arc::clone(&self.module_loader.engine); + let cmd_meta = self + .module_loader + .commands + .get(command_name) + .ok_or_else(|| format!("Metadata not found for command: {}", command_name))? + .clone(); + let mut scope = Scope::new(); + for arg_requirement in &cmd_meta.args { + match arg_requirement.trim() { + "project" => { + scope.push("project", self.projects[self.selected_project].clone()); + } + "projects" => { + let mut arr = rhai::Array::new(); + for proj in &self.projects { + arr.push(rhai::Dynamic::from(proj.clone())); + } + scope.push("projects", arr); + } + "host" => { + scope.push("host", "todo"); + } + "hosts" => { + scope.push("hosts", self.projects[self.selected_project].clone()); + } + "config" => { + let mut map = rhai::Map::new(); + for (k, v) in &self.config { + map.insert(k.clone().into(), v.clone().into()); + } + scope.push("config", map); + } + "distrobox" => { + if let Some(db) = self.projects[self.selected_project].db.clone() { + scope.push("distrobox", db); + } + } + _ if arg_requirement.starts_with("string") => { + let input_val = command_args + .clone() + .unwrap_or_else(|| self.curent_intput.clone()); + scope.push("input_string", input_val); + } + _ if arg_requirement.starts_with("bool") => { + let mut input_val = false; + if command_args + .clone() + .unwrap_or_else(|| self.curent_intput.clone()) + .contains("true") + { + input_val = true; + } + scope.push("input_bool", input_val); + } + _ => {} + } + } + let worker_tx = self.main_tx.clone(); + self.workers.spawn(move || { + match engine.eval_ast_with_scope::(&mut scope, &ast) { + Ok(result) => { + let result_str = if result.is_array() { + if let Ok(arr) = result.into_array() { + let lines: Vec = arr + .into_iter() + .map(|item| { + item.into_string().unwrap_or_else(|_| "".into()) + }) + .collect(); + lines.join("\n") + } else { + "failed to process array output".into() + } + } else if result.is_string() { + let out_string = result + .into_string() + .unwrap_or_else(|_| "failed to parse string".into()); + if cmd_meta.calls == "clipboard" { + let mut ctx: ClipboardContext = + ClipboardProvider::new().unwrap(); + ctx.set_contents(out_string.clone()).unwrap(); + } else if cmd_meta.calls != "none" { + let _ = + worker_tx.send(ToolMessage::Input((0, out_string.clone()))); + } + out_string + } else { + format!("{:?}", result) + }; + if result_str.contains("\n") { + result_str.lines().into_iter().for_each(|line| { + let _ = tx.send(ToolMessage::Output((rid, line.to_string()))); + }); + } else { + let _ = tx.send(ToolMessage::Output((rid, result_str))); + } + } + Err(err) => { + let _ = tx.send(ToolMessage::Output(( + rid, + format!("[error] in script execution: {}", err), + ))); + } + } + }); + } + } + Ok(()) + } + + pub fn edit_config(&mut self, key: String, value: String) { + if self.config.contains_key(&key) { + self.config.insert(key.clone(), value.clone()); + } + let _ = self.main_tx.send(ToolMessage::Output(( + 0, + format!("Setting saved! {key}:{value}"), + ))); + } + + pub fn new_project( + &mut self, + org: String, + name: String, + rid: usize, + ) -> Result<(), Box> { + let mut main_config_file = self.config_file.clone(); + main_config_file.pop(); + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!("{} is the main config folder.", main_config_file.display()), + ))); + let template_box = self.config.get("template_box").unwrap(); + let project_files = PathBuf::from(self.config.get("upcoming_files").unwrap()) + .join(format!("{}/{}", org, name)); + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!("{} is the project_files folder", project_files.display()), + ))); + let project_notes = PathBuf::from(self.config.get("upcoming_notes").unwrap()) + .join(format!("{}/{}", org, name)); + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!("{} is the project_notes folder", project_notes.display()), + ))); + let mut template_path = main_config_file.clone(); + let mut project_conf_folder = main_config_file.clone(); + let tools = self.config.get("tools").unwrap(); + template_path.push("note_templates"); + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!("{} is the note template folder", template_path.display()), + ))); + project_conf_folder.push(format!("projects/{}-{}", org, name)); + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!( + "{} is the project config folder", + project_conf_folder.display() + ), + ))); + let mut options = CopyOptions::new(); + options.overwrite = true; + if !project_files.exists() { + match create_dir_all(&project_files) { + Ok(_) => {} + Err(e) => { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!( + "[error] making project files! {e} on {}", + project_files.display() + ), + ))); + } + } + } + if !project_notes.exists() {} + match create_dir_all(&project_notes) { + Ok(_) => {} + Err(e) => { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!( + "[error] making project notes! {e} on {}", + project_notes.display() + ), + ))); + } + } + if !project_conf_folder.exists() { + match create_dir_all(&project_conf_folder) { + Ok(_) => {} + Err(e) => { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!( + "[error] making config folder! {e} on {}", + project_conf_folder.display() + ), + ))); + } + } + } + let mut project_config_file = project_conf_folder.clone(); + project_config_file.push("project.conf"); + for entry in read_dir(template_path)? { + let entry = entry?; + let template_name_os = entry.file_name(); + let template_name = template_name_os.to_string_lossy(); + if name.clone().contains(template_name.as_ref()) { + for entry in read_dir(entry.path())? { + let entry = entry?; + let file_name_os = entry.file_name(); + let file_name = file_name_os.to_string_lossy(); + if entry.file_type()?.is_dir() { + let dest = project_notes.join(file_name.as_ref()); + match copy(entry.path(), dest, &options) { + Ok(_) => {} + Err(e) => { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!("[error] copying note template! {e}"), + ))); + } + } + } else { + match fs::copy(entry.path(), project_notes.join(file_name.as_ref())) { + Ok(_) => {} + Err(e) => { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!("[error] copying note template! {e}"), + ))); + } + } + } + } + } + } + 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 new_project = Project { + org_name: org, + name: name, + notes: project_notes, + files: project_files, + hosts: Vec::new(), + config_folder: project_config_file, + current: false, + db: Some(db), + scope: Vec::new(), + }; + match new_project.save_config() { + Ok(_) => {} + Err(e) => { + let _ = self.main_tx.send(ToolMessage::Output(( + 0, + format!("[error] saving project! {e}"), + ))); + } + } + new_project.save_config()?; + self.projects.push(new_project); + return Ok(()); + } + + pub fn promote_project(&mut self, rid: usize) -> Result> { + self.main_tx.send(ToolMessage::Output(( + rid, + String::from("promoting project..."), + )))?; + let return_string = String::from("[success] Project promoted!"); + let mut new_project = self.projects[self.selected_project].clone(); + let mut new_files_path = PathBuf::from(self.config.get("current_files").unwrap()) + .join(format!("{}", new_project.org_name)); + let mut new_notes_path = PathBuf::from(self.config.get("current_notes").unwrap()) + .join(format!("{}", new_project.org_name)); + let mut options = CopyOptions::new(); + create_dir_all(&new_files_path)?; + create_dir_all(&new_notes_path)?; + options.overwrite = true; + copy(new_project.files.clone(), new_files_path.clone(), &options)?; + copy(new_project.notes.clone(), new_notes_path.clone(), &options)?; + self.main_tx.send(ToolMessage::Output(( + rid, + String::from("[success] folders copied!"), + )))?; + remove_dir_all(&new_project.files)?; + remove_dir_all(&new_project.notes)?; + new_files_path.push(&new_project.name); + new_notes_path.push(&new_project.name); + new_project.files(new_files_path.clone()); + new_project.notes(new_notes_path.clone()); + new_project.current = true; + self.main_tx.send(ToolMessage::Output(( + rid, + String::from("upcoming folders cleaned up!"), + )))?; + self.main_tx.send(ToolMessage::Output(( + rid, + String::from("creating distrobox..."), + )))?; + if let Some(mut db) = new_project.db { + db.volumes = vec![ + new_files_path.display().to_string(), + new_notes_path.display().to_string(), + self.config.get("tools").unwrap().to_string(), + ]; + new_project.db = Some(db); + let _ = self.main_tx.send(ToolMessage::RebuildDB); + self.main_tx.send(ToolMessage::Output(( + rid, + String::from("distrobox sent to rebuild function..."), + )))?; + } + new_project.save_config()?; + self.projects[self.selected_project] = new_project; + return Ok(return_string); + } + + pub fn save_projects(&mut self) -> Result<(), Box> { + for mut project in self.projects.clone() { + project.save_config()?; + } + return Ok(()); + } + + pub fn save_config(&mut self) -> Result<(), Box> { + let mut config_vec = Vec::new(); + for key in self.config.keys() { + if let Some(value) = self.config.get(key) { + config_vec.push(format!("{}: {}", key, value)); + } + } + if config_vec.len() > 0 { + fs::write(self.config_file.clone(), config_vec.join("\n"))?; + } else { + return Err("No config loaded!".into()); + } + return Ok(()); + } + + pub fn save_all(&mut self) -> Result<(), Box> { + self.save_config()?; + self.save_projects()?; + return Ok(()); + } + + pub fn new_server(&mut self) -> Result<(), Box> { + let mut ip = String::new(); + let mut port = String::new(); + self.prompt.responses.iter().for_each(|res| { + if res.query.id == 0 { + ip = res.response.clone(); + } else if res.query.id == 1 { + port = res.response.clone(); + } + }); + let new_server = Server { + address: format!("{}:{}", ip, port), + connected: false, + timer: Duration::from_secs(60), + last_check: time::Instant::now(), + message_que: Vec::new(), + action_que: Vec::new(), + client_id: 0, + selected_client: 0, + config: None, + logged_in: false, + password: String::new(), + name: String::new(), + cert_text: String::new(), + }; + self.servers.push(Arc::new(Mutex::new(new_server))); + self.prompt.reset(); + Ok(()) + } + + pub fn new_config(&mut self) -> Result<(), Box> { + let mut name = String::new(); + let mut config_path = PathBuf::new(); + let mut upcoming_files_path = PathBuf::new(); + let mut current_files_path = PathBuf::new(); + let mut upcoming_notes_path = PathBuf::new(); + let mut current_notes_path = PathBuf::new(); + let mut tools_folder = PathBuf::new(); + let mut template_box = String::new(); + let term_cmd = self.config.get("term_cmd").unwrap().clone(); + self.prompt + .responses + .iter() + .for_each(|res| match res.query.id { + 0 => name = res.response.clone(), + 1 => config_path = PathBuf::from(res.response.clone()), + 2 => upcoming_files_path = PathBuf::from(res.response.clone()), + 3 => current_files_path = PathBuf::from(res.response.clone()), + 4 => upcoming_notes_path = PathBuf::from(res.response.clone()), + 5 => current_notes_path = PathBuf::from(res.response.clone()), + 6 => tools_folder = PathBuf::from(res.response.clone()), + 7 => template_box = res.response.clone(), + _ => {} + }); + let mut existing = false; + if let Some(line) = self.config.get("alternate_configs") { + line.split(",").into_iter().for_each(|line| { + if let Some((ename, _)) = line.split_once("|") { + if ename.trim().to_string() == name { + existing = true; + } + } + }); + } + if existing { + let _ = self.main_tx.send(ToolMessage::Output(( + 0, + format!("{} already exists!", name), + ))); + return Ok(()); + } + let root_config_folder_path = config_path.clone(); + let mut projects_path = config_path.clone(); + projects_path.push("projects"); + let mut module_path = config_path.clone(); + module_path.push("modules"); + let mut note_templates_path = config_path.clone(); + note_templates_path.push("note_templates"); + create_dir_all(&module_path)?; + config_path.push("client.conf"); + let client_conf_path = config_path.clone(); + projects_path.push("default"); + match create_dir_all(&projects_path) { + Ok(_) => { + let _ = self.main_tx.send(ToolMessage::Output(( + 0, + format!( + "[success] create projects path at {}", + projects_path.display() + ), + ))); + } + Err(e) => { + let _ = self.main_tx.send(ToolMessage::Output(( + 0, + format!( + "[error] couldn't create projects path at {}: {e}", + projects_path.display() + ), + ))); + return Ok(()); + } + } + projects_path.push("project.conf"); + let mut client_config_file = File::create(&config_path)?; + client_config_file.write(format!("projects: {}\n", projects_path.display()).as_bytes())?; + client_config_file.write("servers: 127.0.0.1:31337\n".as_bytes())?; + let mut default_project_file = File::create(projects_path)?; + config_path.pop(); + let mut server_path = config_path.clone(); + server_path.push("server"); + create_dir_all(&server_path)?; + server_path.push("server.conf"); + let mut server_config_file = File::create(&server_path)?; + server_config_file.write("address: 127.0.0.1:31337\n".as_bytes())?; + server_config_file.write("running: false\n".as_bytes())?; + default_project_file.write("org_name: default\n".as_bytes())?; + default_project_file.write("name: default\n".as_bytes())?; + default_project_file + .write(format!("notes: {}\n", current_files_path.display()).as_bytes())?; + default_project_file + .write(format!("files: {}\n", current_notes_path.display()).as_bytes())?; + default_project_file.write("stage: current".as_bytes())?; + client_config_file + .write(format!("current_files: {}\n", current_files_path.display()).as_bytes())?; + client_config_file + .write(format!("current_notes: {}\n", current_notes_path.display()).as_bytes())?; + client_config_file + .write(format!("upcoming_files: {}\n", upcoming_files_path.display()).as_bytes())?; + client_config_file + .write(format!("upcoming_notes: {}\n", upcoming_notes_path.display()).as_bytes())?; + client_config_file.write(format!("module_path: {}\n", module_path.display()).as_bytes())?; + client_config_file.write(format!("template_box: {}\n", template_box).as_bytes())?; + client_config_file.write(format!("term_cmd: {}\n", term_cmd).as_bytes())?; + client_config_file.write(format!("tools: {}\n", tools_folder.display()).as_bytes())?; + create_dir_all("./temp")?; + env::set_current_dir("./temp")?; + let git_clone_output = Command::new("git") + .arg("clone") + .arg("https://git.pyro.monster/pyro/tetanus.git") + .output()?; + let git_clone_status = git_clone_output.status.success(); + if git_clone_status { + let mut options = CopyOptions::new(); + options.overwrite = true; + options.copy_inside = true; + copy( + "./tetanus/note_templates", + root_config_folder_path.clone(), + &options, + )?; + copy( + "./tetanus/default-modules", + module_path.join("default"), + &options, + )?; + copy( + "./tetanus/victim_templates", + root_config_folder_path.join("victim"), + &options, + )?; + env::set_current_dir("../")?; + remove_dir_all("./temp")?; + let _ = self.main_tx.send(ToolMessage::Output(( + 0, + "New config structure created!".to_string(), + ))); + } + if let Some(original_value) = self.config.get("alternate_configs") { + let new_value = format!( + "{}|{},{}", + name, + client_conf_path.display().to_string(), + original_value, + ); + self.config + .insert("alternate_configs".to_string(), new_value); + } else { + self.config.insert( + "alternate_configs".to_string(), + format!("{}|{},", name, client_conf_path.display().to_string()), + ); + } + self.prompt.responses.clear(); + self.prompt.prompts.clear(); + self.prompt.action = None; + self.prompt.execute_command.clear(); + Ok(()) + } + + pub fn parse_cs_portscan(&mut self) -> Result<(), Box> { + if let Some(project) = self.projects.get_mut(self.selected_project) { + let walk = WalkDir::new(project.files.clone()); + walk.into_iter().for_each(|res| { + if let Ok(entry) = res { + if entry.file_name().to_string_lossy().contains("services.tsv") { + if let Ok(scan) = read_to_string(entry.path()) { + let mut url_lines = Vec::new(); + let mut web_host_lines = Vec::new(); + let mut rdp_host_lines = Vec::new(); + let mut ftp_host_lines = Vec::new(); + let mut telnet_host_lines = Vec::new(); + let mut winrm_host_lines = Vec::new(); + let mut mysql_host_lines = Vec::new(); + let mut mssql_host_lines = Vec::new(); + let mut snmp_host_lines = Vec::new(); + let mut kerberos_host_lines = Vec::new(); + let mut ldap_host_lines = Vec::new(); + let mut smb_host_lines = Vec::new(); + let mut unknonw_host_lines = Vec::new(); + let mut ssh_host_lines = Vec::new(); + scan.lines().into_iter().for_each(|line| { + if line.len() > 0 { + let data: Vec<&str> = line.split_whitespace().collect(); + match data[1].trim() { + "80" | "443" | "8080" | "8443" | "8000" | "4433" => { + let http_line = + format!("http://{}:{}", data[0], data[1]); + let https_line = + format!("https://{}:{}", data[0], data[1]); + url_lines.push(http_line); + url_lines.push(https_line); + web_host_lines.push(data[0].to_string()); + let mut existing = false; + project + .hosts + .iter_mut() + .filter(|h| h.ip == data[0].to_string()) + .for_each(|h| { + h.add_port(data[1]); + existing = true; + }); + if !existing { + let mut new_host = Host::new(); + new_host.ip = data[0].trim().to_string(); + new_host.add_port(data[1]); + project.add_host(new_host); + } + } + "3389" => { + rdp_host_lines.push(data[0].to_string()); + let mut existing = false; + project + .hosts + .iter_mut() + .filter(|h| h.ip == data[0].to_string()) + .for_each(|h| { + h.add_port(data[1]); + existing = true; + }); + if !existing { + let mut new_host = Host::new(); + new_host.ip = data[0].trim().to_string(); + new_host.add_port(data[1]); + project.add_host(new_host); + } + } + "21" => { + ftp_host_lines.push(data[0].to_string()); + let mut existing = false; + project + .hosts + .iter_mut() + .filter(|h| h.ip == data[0].to_string()) + .for_each(|h| { + h.add_port(data[1]); + existing = true; + }); + if !existing { + let mut new_host = Host::new(); + new_host.ip = data[0].trim().to_string(); + new_host.add_port(data[1]); + project.add_host(new_host); + } + } + "23" => { + telnet_host_lines.push(data[0].to_string()); + let mut existing = false; + project + .hosts + .iter_mut() + .filter(|h| h.ip == data[0].to_string()) + .for_each(|h| { + h.add_port(data[1]); + existing = true; + }); + if !existing { + let mut new_host = Host::new(); + new_host.ip = data[0].trim().to_string(); + new_host.add_port(data[1]); + project.add_host(new_host); + } + } + "22" => { + ssh_host_lines.push(data[0].to_string()); + let mut existing = false; + project + .hosts + .iter_mut() + .filter(|h| h.ip == data[0].to_string()) + .for_each(|h| { + h.add_port(data[1]); + existing = true; + }); + if !existing { + let mut new_host = Host::new(); + new_host.ip = data[0].trim().to_string(); + new_host.add_port(data[1]); + project.add_host(new_host); + } + } + "5985" | "5986" => { + winrm_host_lines.push(data[0].to_string()); + let mut existing = false; + project + .hosts + .iter_mut() + .filter(|h| h.ip == data[0].to_string()) + .for_each(|h| { + h.add_port(data[1]); + existing = true; + }); + if !existing { + let mut new_host = Host::new(); + new_host.ip = data[0].trim().to_string(); + new_host.add_port(data[1]); + project.add_host(new_host); + } + } + "3306" => { + mysql_host_lines.push(data[0].to_string()); + let mut existing = false; + project + .hosts + .iter_mut() + .filter(|h| h.ip == data[0].to_string()) + .for_each(|h| { + h.add_port(data[1]); + existing = true; + }); + if !existing { + let mut new_host = Host::new(); + new_host.ip = data[0].trim().to_string(); + new_host.add_port(data[1]); + project.add_host(new_host); + } + } + "1433" => { + mssql_host_lines.push(data[0].to_string()); + let mut existing = false; + project + .hosts + .iter_mut() + .filter(|h| h.ip == data[0].to_string()) + .for_each(|h| { + h.add_port(data[1]); + existing = true; + }); + if !existing { + let mut new_host = Host::new(); + new_host.ip = data[0].trim().to_string(); + new_host.add_port(data[1]); + project.add_host(new_host); + } + } + "161" => { + snmp_host_lines.push(data[0].to_string()); + let mut existing = false; + project + .hosts + .iter_mut() + .filter(|h| h.ip == data[0].to_string()) + .for_each(|h| { + h.add_port(data[1]); + existing = true; + }); + if !existing { + let mut new_host = Host::new(); + new_host.ip = data[0].trim().to_string(); + new_host.add_port(data[1]); + project.add_host(new_host); + } + } + "88" => { + kerberos_host_lines.push(data[0].to_string()); + let mut existing = false; + project + .hosts + .iter_mut() + .filter(|h| h.ip == data[0].to_string()) + .for_each(|h| { + h.add_port(data[1]); + existing = true; + }); + if !existing { + let mut new_host = Host::new(); + new_host.ip = data[0].trim().to_string(); + new_host.add_port(data[1]); + project.add_host(new_host); + } + } + "389" | "636" => { + ldap_host_lines.push(data[0].to_string()); + let mut existing = false; + project + .hosts + .iter_mut() + .filter(|h| h.ip == data[0].to_string()) + .for_each(|h| { + h.add_port(data[1]); + existing = true; + }); + if !existing { + let mut new_host = Host::new(); + new_host.ip = data[0].trim().to_string(); + new_host.add_port(data[1]); + project.add_host(new_host); + } + } + "445" | "139" => { + smb_host_lines.push(data[0].to_string()); + let mut existing = false; + project + .hosts + .iter_mut() + .filter(|h| h.ip == data[0].to_string()) + .for_each(|h| { + h.add_port(data[1]); + existing = true; + }); + if !existing { + let mut new_host = Host::new(); + new_host.ip = data[0].trim().to_string(); + new_host.add_port(data[1]); + project.add_host(new_host); + } + } + _ => { + unknonw_host_lines.push(data[0].to_string()); + let mut existing = false; + project + .hosts + .iter_mut() + .filter(|h| h.ip == data[0].to_string()) + .for_each(|h| { + h.add_port(data[1]); + existing = true; + }); + if !existing { + let mut new_host = Host::new(); + new_host.ip = data[0].trim().to_string(); + new_host.add_port(data[1]); + project.add_host(new_host); + } + } + } + } + }); + if !url_lines.is_empty() { + let mut file_path = project.files.clone(); + file_path.push("urls.txt"); + if let Ok(mut file) = File::create(file_path) { + let _ = write!(file, "{}", url_lines.join("\n")); + } + } + if !web_host_lines.is_empty() { + let mut file_path = project.files.clone(); + file_path.push("web_hosts.txt"); + if let Ok(mut file) = File::create(file_path) { + let _ = write!(file, "{}", web_host_lines.join("\n")); + } + } + if !rdp_host_lines.is_empty() { + let mut file_path = project.files.clone(); + file_path.push("rdp_hosts.txt"); + if let Ok(mut file) = File::create(file_path) { + let _ = write!(file, "{}", rdp_host_lines.join("\n")); + } + } + if !ftp_host_lines.is_empty() { + let mut file_path = project.files.clone(); + file_path.push("ftp_hosts.txt"); + if let Ok(mut file) = File::create(file_path) { + let _ = write!(file, "{}", ftp_host_lines.join("\n")); + } + } + if !telnet_host_lines.is_empty() { + let mut file_path = project.files.clone(); + file_path.push("telnet_hosts.txt"); + if let Ok(mut file) = File::create(file_path) { + let _ = write!(file, "{}", telnet_host_lines.join("\n")); + } + } + if !winrm_host_lines.is_empty() { + let mut file_path = project.files.clone(); + file_path.push("winrm_hosts.txt"); + if let Ok(mut file) = File::create(file_path) { + let _ = write!(file, "{}", winrm_host_lines.join("\n")); + } + } + if !mysql_host_lines.is_empty() { + let mut file_path = project.files.clone(); + file_path.push("mysql_hosts.txt"); + if let Ok(mut file) = File::create(file_path) { + let _ = write!(file, "{}", mysql_host_lines.join("\n")); + } + } + if !mssql_host_lines.is_empty() { + let mut file_path = project.files.clone(); + file_path.push("mssql_hosts.txt"); + if let Ok(mut file) = File::create(file_path) { + let _ = write!(file, "{}", mssql_host_lines.join("\n")); + } + } + if !snmp_host_lines.is_empty() { + let mut file_path = project.files.clone(); + file_path.push("snmp_hosts.txt"); + if let Ok(mut file) = File::create(file_path) { + let _ = write!(file, "{}", snmp_host_lines.join("\n")); + } + } + if !kerberos_host_lines.is_empty() { + let mut file_path = project.files.clone(); + file_path.push("kerberos_hosts.txt"); + if let Ok(mut file) = File::create(file_path) { + let _ = write!(file, "{}", kerberos_host_lines.join("\n")); + } + } + if !ldap_host_lines.is_empty() { + let mut file_path = project.files.clone(); + file_path.push("ldap_hosts.txt"); + if let Ok(mut file) = File::create(file_path) { + let _ = write!(file, "{}", ldap_host_lines.join("\n")); + } + } + if !smb_host_lines.is_empty() { + let mut file_path = project.files.clone(); + file_path.push("smb_hosts.txt"); + if let Ok(mut file) = File::create(file_path) { + let _ = write!(file, "{}", smb_host_lines.join("\n")); + } + } + if !unknonw_host_lines.is_empty() { + let mut file_path = project.files.clone(); + file_path.push("unknown_hosts.txt"); + if let Ok(mut file) = File::create(file_path) { + let _ = write!(file, "{}", unknonw_host_lines.join("\n")); + } + } + if !ssh_host_lines.is_empty() { + let mut file_path = project.files.clone(); + file_path.push("ssh_hosts.txt"); + if let Ok(mut file) = File::create(file_path) { + let _ = write!(file, "{}", ssh_host_lines.join("\n")); + } + } + } + } + } + }); + project.save_config()?; + } + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 8913323..436358a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,38 +1,20 @@ -use crate::client::Server; +use crate::appstate::AppState; use crate::host::Host; use crate::host::User; -use crate::modules::ModuleLoader; use crate::project::DistroBox; -use crate::rust_tools::rustwitness; -use clipboard::ClipboardContext; -use clipboard::ClipboardProvider; -use dns_lookup::lookup_host; -use fs_extra::dir::{CopyOptions, copy}; -use ipnet::IpNet; -use keyring::Entry; use project::Project; use ratatui::crossterm::event; -use rayon::prelude::*; -use rhai::{Dynamic, Scope}; -use rust_tools::{BusterTarget, rustbuster}; use serde_derive::Deserialize; use std::collections::HashMap; -use std::env; use std::error::Error; -use std::fs::OpenOptions; -use std::fs::{self, File, create_dir_all, read_dir, read_to_string, remove_dir, remove_dir_all}; +use std::fs::{self, File, create_dir_all, read_to_string}; use std::io::{self, Write}; -use std::net::IpAddr; use std::path::PathBuf; use std::process::Command; -use std::sync::RwLock; use std::sync::mpsc::Receiver; use std::sync::{Arc, Mutex, mpsc::Sender, mpsc::channel}; -use std::thread; -use std::time::Duration; -use std::time::{self, Instant}; -use walkdir::WalkDir; +pub mod appstate; pub mod client; pub mod funcs; pub mod host; @@ -88,2523 +70,8 @@ pub enum ToolArg { Path(PathBuf), } -pub struct AppState { - pub projects: Vec, - pub servers: Vec>>, - pub config: HashMap, - pub config_file: PathBuf, - pub workers: rayon::ThreadPool, - pub worker_txes: Vec>, - pub main_tx: Sender, - pub history: Vec, - pub log: Vec, - pub output: Vec, - pub selected_server: usize, - pub selected_project: usize, - pub selected_host: usize, - pub curent_intput: String, - pub module_loader: ModuleLoader, - pub output_scroll: u16, - pub output_follow: bool, - prompt: Prompt, - pub info_scroll: u16, - pub server_broker_running: bool, - pub app_state_running: bool, - pub remoting: bool, - pub help: Vec, - pub name: String, - pub progress_bars: Vec>>, - pub selecting_host: bool, -} - -impl AppState { - pub fn new() -> Self { - let (main_tx, _) = channel(); - Self { - projects: Vec::new(), - servers: Vec::new(), - config: HashMap::new(), - config_file: PathBuf::new(), - workers: rayon::ThreadPoolBuilder::new() - .num_threads(4) - .build() - .unwrap(), - worker_txes: Vec::new(), - main_tx, - history: Vec::new(), - log: Vec::new(), - output: Vec::new(), - selected_server: 0, - selected_project: 0, - selected_host: 0, - curent_intput: String::new(), - module_loader: ModuleLoader::new(), - output_scroll: 0, - output_follow: true, - prompt: Prompt { - action: None, - responses: Vec::new(), - execute_command: String::new(), - num_responses: 0, - prompts: Vec::new(), - last_prompted: None, - }, - info_scroll: 0, - server_broker_running: true, - app_state_running: true, - remoting: false, - help: Vec::new(), - name: String::new(), - progress_bars: Vec::new(), - selecting_host: false, - } - } - - pub fn load_config(&mut self, file: PathBuf, display: bool) -> Result<(), Box> { - self.config_file = file.clone(); - let config_contents = read_to_string(file)?; - for line in config_contents.lines() { - let parts: Vec<&str> = line.split(": ").collect(); - if parts.len() == 2 { - match parts[0].trim() { - "servers" => { - for server_data in parts[1].trim().split(", ").collect::>() { - if let Some((address, name)) = server_data.split_once("|") { - let new_server = Server { - address: address.trim().to_string(), - connected: false, - timer: Duration::from_secs(60), - last_check: time::Instant::now(), - message_que: Vec::new(), - action_que: Vec::new(), - client_id: 0, - selected_client: 0, - config: None, - logged_in: false, - password: String::new(), - name: name.to_string(), - cert_text: String::new(), - }; - self.servers.push(Arc::new(Mutex::new(new_server))); - } - } - self.config - .insert("servers".to_string(), parts[1].trim().to_string()); - } - _ => { - if parts[0].len() > 1 { - self.config - .insert(parts[0].trim().to_string(), parts[1].trim().to_string()); - } - } - } - } - } - let mut projet_folder_path = self.config_file.clone(); - projet_folder_path.pop(); - projet_folder_path.push("projects"); - let project_dir_reses = read_dir(projet_folder_path)?; - for res in project_dir_reses { - if let Ok(project_folder) = res { - let project_folder_file_reses = read_dir(project_folder.path())?; - for res in project_folder_file_reses { - if let Ok(conf_file) = res { - if conf_file.file_name().to_string_lossy() == "project.conf".to_string() { - let mut new_project = Project::new(); - new_project.config_folder(conf_file.path()); - new_project.load_config(display)?; - let template_box = self.config.get("template_box").unwrap(); - let tools = self.config.get("tools").unwrap(); - let db = DistroBox { - name: format!( - "{}-{}-{}", - template_box, - new_project.org_name.clone(), - new_project.name.clone() - ), - volumes: vec![ - new_project.files.display().to_string(), - new_project.notes.display().to_string(), - tools.clone(), - ], - created: false, - template: template_box.clone(), - }; - new_project.db = Some(db); - self.projects.push(new_project); - } - } - } - } - } - let mut server_folder_path = self.config_file.clone(); - server_folder_path.pop(); - server_folder_path.push("client_servers"); - if server_folder_path.exists() { - let server_confs = read_dir(server_folder_path)?; - server_confs.into_iter().for_each(|entry| { - if let Ok(entry) = entry { - if let Ok(text) = read_to_string(entry.path()) { - let mut name = String::new(); - let mut address = String::new(); - let mut cert = String::new(); - text.lines().into_iter().for_each(|line| { - if let Some((setting, data)) = line.split_once(": ") { - match setting.trim() { - "name" => name = data.trim().to_string(), - "address" => address = data.trim().to_string(), - "cert" => cert = data.trim().to_string(), - _ => {} - } - } - }); - let new_server = Server { - address, - connected: false, - timer: Duration::from_secs(90), - config: None, - last_check: Instant::now(), - message_que: Vec::new(), - action_que: Vec::new(), - client_id: 0, - selected_client: 0, - logged_in: false, - password: String::new(), - name, - cert_text: cert, - }; - self.servers.push(Arc::new(Mutex::new(new_server))); - } - } - }); - } - self.help.push("help\nThat's MEEEEEEE\n".to_string()); - self.help.push("new_project\nadd a project to the upcoming project pool and create default notes based on templates\nusage:\nnew_project organization_name, project_name\n".to_string()); - self.help.push( - "current_project\nshow information on the currently selected project\n".to_string(), - ); - self.help - .push("promote_project\npromote a project from upcoming to current\n".to_string()); - self.help.push( - "remove_project\nremove a project, delete its folders, and destroy its distrobox\n" - .to_string(), - ); - self.help.push("new_terminal\nopen a new terminal in the distrobox of the currently selected project\n".to_string()); - self.help - .push("new_server\ncreate a new server object\n".to_string()); - self.help - .push("list_servers\nlist all servers currently tracked by the tool\n".to_string()); - self.help - .push("connect_server\nconnect to a tracked server\n".to_string()); - self.help - .push("save_all\nsave all projects and settings\n".to_string()); - self.help - .push("save_projects\nsave just the projects\n".to_string()); - self.help - .push("test_server\ntest the server connection\n".to_string()); - self.help.push( - "add_scope\nadd a host, cidr range, or ip address to the current project's scope\n" - .to_string(), - ); - self.help.push("parse_scope\nparse the projects general.md notes file for the scope copied from the workbook\n".to_string()); - self.help.push("parse_portscan | pps\nParse a services.tsv file exported from cobalt stirke in your current project's files directory\n".to_string()); - self.help - .push("stop_db\nrestart this project's distrobox".to_string()); - self.help.push("rustwitness\nRun a scan on the hosts in the urls.txt file in yoru project files directory that captures screenshots of those urls, optionally through a proxy.\n".to_string()); - self.help.push("portscan\nRun a simple TCP portscan on either the scope for the project, or a given list of ips.\nportscan ip,ip,ip,ip port,port,port,port or portscan port,port,port\n".to_string()); - self.help.push("rustbuster\nRun the Subdomain/Subdirectory bruteforcetool RustBuster (included in tetanus now!) against a given domain name or url.\nrustbuster target,target,target subs=/path/to/subwordlist dirs=/path/to/dirwordlist\n".to_string()); - self.help - .push("clear_operations\nClear the completed operation statuses.\n".to_string()); - self.help - .push("shodan\nGet the shodan info for the currently selected host\n".to_string()); - self.help - .push("select_host\nSelect a host in the current project.\n".to_string()); - self.help.push( - "shodan_all\nGet the shodan info for all hosts in the current project.\n".to_string(), - ); - self.help.push( - "set_shodan_key\nSet your shodan api key, and save it to your system's keyring.\n" - .to_string(), - ); - 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("exit\nquit the tool\n".to_string()); - self.initialize_modules(); - return Ok(()); - } - - pub fn initialize_modules(&mut self) { - if let Some(base_path_str) = self.config.get("module_path") { - let base_path = PathBuf::from(base_path_str); - - println!("Loading Tetanus modules from: {:?}", base_path); - if let Err(e) = self.module_loader.load_all(&base_path) { - eprintln!("Failed to load modules: {}", e); - } else { - for module in &self.module_loader.commands { - self.help.push(format!( - "{}\n{}\n", - module.1.name.clone(), - module.1.help.clone() - )); - } - println!( - "Successfully loaded {} modules.", - self.module_loader.commands.len() - ); - } - } else { - eprintln!("Warning: 'module_path' is missing from AppState config map!"); - } - } - pub fn execute_command( - &mut self, - command_name: &str, - command_args: Option, - rid: usize, - ) -> Result<(), Box> { - let tx = self.main_tx.clone(); - match command_name { - "remote" => { - if let Some(server) = self.servers.get(self.selected_server) { - if let Ok(mut server) = server.lock() { - if server.selected_client != 0 { - if let Some(args) = command_args.clone() { - let args = args.trim(); - if args.contains(" ") { - let (cmd, iargs) = args.split_once(" ").unwrap(); - server.message_que.push(format!("CMD|{} {}", cmd, iargs)); - } else { - server.message_que.push(format!("CMD|{}", args)); - } - let _ = self.main_tx.send(ToolMessage::Output(( - 0, - format!("Tasked {} to run {}", server.selected_client, args), - ))); - } - } - } - } - } - "new_project" | "np" => { - let args = command_args.unwrap(); - let (org, name) = args.split_once(" ").unwrap(); - if let Err(e) = self.new_project(org.to_string(), name.to_string(), rid) { - tx.send(ToolMessage::Output(( - rid, - format!("[error] making {}-{}: {e}", org, name), - )))?; - } else { - tx.send(ToolMessage::Output(( - rid, - format!("[success] {}-{} created!", org, name), - )))?; - } - } - "generate_victim" => { - let mut success = false; - if let Some(args) = command_args { - if args.split(" ").into_iter().count() == 1 { - let mut address = String::new(); - let mut password = String::new(); - let config_file = self.config_file.clone(); - let files = self.projects[self.selected_project].files.clone(); - let mut cert_text = String::new(); - if let Ok(lock) = self.servers[self.selected_server].lock() { - address = lock.address.clone(); - password = lock.password.clone(); - cert_text = lock.cert_text.clone(); - } - match victim::generate_code( - address, - password, - config_file, - files, - args, - self.main_tx.clone(), - cert_text, - ) { - Ok(_) => { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - "[Success] Victim code generated! Compilation thread spawned!" - .to_string(), - ))); - success = true; - } - Err(e) => { - let _ = self.main_tx.send(ToolMessage::Output(( - 0, - format!("[error] generating victim code: {e}"), - ))); - } - } - } - } - if !success { - let _ = self.main_tx.send(ToolMessage::Output(( - 0, - format!("Usage: generate_victim (windows/linux)"), - ))); - } - } - "current_project" | "cp" => { - let mut out_vec = Vec::new(); - out_vec.push(format!( - "org: {}", - &self.projects[self.selected_project].org_name - )); - out_vec.push(format!( - "name: {}", - &self.projects[self.selected_project].name - )); - out_vec.push(format!( - "files: {}", - &self.projects[self.selected_project].files.display() - )); - out_vec.push(format!( - "notes: {}", - &self.projects[self.selected_project].notes.display() - )); - out_vec.push(format!( - "config_folder: {}", - &self.projects[self.selected_project].config_folder.display() - )); - if let Some(db) = self.projects[self.selected_project].db.clone() { - out_vec.push(format!("distrobox: {}", db.name)); - } - if self.projects[self.selected_project].current { - out_vec.push(format!("status: current")); - } else { - out_vec.push(format!("status: upcoming")); - } - out_vec.push(format!( - "Num hosts: {}", - &self.projects[self.selected_project].hosts.iter().count() - )); - for line in out_vec { - let _ = self.main_tx.send(ToolMessage::Output((rid, line))); - } - } - "rebuild_distro_box" | "rdb" => { - if let Some(db) = self.projects[self.selected_project].db.clone() { - if db.created { - let _ = self.main_tx.send(ToolMessage::DestroyDB(db.clone())); - } - let _ = self.main_tx.send(ToolMessage::RebuildDB); - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - "[success] Distorbox made!".to_string(), - ))); - } - } - "promote_project" | "pp" => { - self.main_tx.send(ToolMessage::Output(( - rid, - String::from("promote_project command recieved!"), - )))?; - match self.promote_project(rid) { - Ok(_) => { - self.main_tx.send(ToolMessage::Output(( - rid, - String::from("[success] project promoted!"), - )))?; - } - Err(e) => { - self.main_tx.send(ToolMessage::Output(( - rid, - format!("[error] promoting project: {}", e), - )))?; - } - } - } - "remove_project_confirm" => { - if self.prompt.responses.len() > 0 { - if self.prompt.responses[0] - .response - .to_lowercase() - .contains("y") - { - let project = self.projects[self.selected_project].clone(); - let mut config_file = project.config_folder.clone(); - config_file.pop(); - if let Err(e) = remove_dir_all(config_file) { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!( - "failed to delete config: {e} on {}", - &project.config_folder.display() - ), - ))); - } - if let Err(e) = remove_dir_all(&project.notes) { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!( - "failed to delete notes: {e} on {}", - &project.notes.display() - ), - ))); - } else if let Some(parent) = project.notes.parent() { - if let Ok(mut entries) = read_dir(parent) { - if entries.next().is_none() { - if let Err(e) = remove_dir(parent) { - let _ = self.main_tx.send(ToolMessage::Output((rid, format!("failed to delete the empty client notes folder: {e} on {}", parent.display())))); - } - } - } - } - if let Err(e) = remove_dir_all(&project.files) { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!( - "failed to delete files: {e} on {}", - &project.files.display() - ), - ))); - } else if let Some(parent) = project.files.parent() { - if let Ok(mut entries) = read_dir(parent) { - if entries.next().is_none() { - if let Err(e) = remove_dir(parent) { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!( - "failed to delete empty files parent: {e} on {}", - parent.display() - ), - ))); - } - } - } - } - let remove_id = self.selected_project.clone(); - let project = self.projects[remove_id].clone(); - if let Some(db) = project.db { - self.main_tx.send(ToolMessage::DestroyDB(db))?; - } - self.selected_project -= 1; - self.projects.remove(remove_id); - self.prompt.responses.clear(); - self.prompt.action = None; - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!( - "{}-{} was sucessfully removed!", - project.org_name, project.name - ), - ))); - } - } - } - "remove_project" | "rp" => { - let project = self.projects[self.selected_project].clone(); - self.prompt.action = Some(ToolMessage::RemoveProject); - self.prompt.num_responses = 1; - self.prompt.execute_command = String::from("remove_project_confirm"); - self.prompt.prompts.push(PromptQuery { - id: 0, - query: format!( - "{}, {} and all contentes wil lbe deleted. Contineue? (y/N", - project.files.display(), - project.notes.display() - ), - }); - let _ = self - .main_tx - .send(ToolMessage::Input((rid, "prompt".to_string()))); - } - "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") { - db.launch_terminal(cmd.to_string()); - } - } - } - "new_server" | "ns" => { - self.prompt.num_responses = 2; - self.prompt.action = Some(ToolMessage::AddServer); - self.prompt.execute_command = String::from("add_server"); - self.prompt.prompts.push(PromptQuery { - id: 0, - query: "What is the IP of the server?".to_string(), - }); - self.prompt.prompts.push(PromptQuery { - id: 1, - query: "What is the port for the server?".to_string(), - }); - let _ = self - .main_tx - .send(ToolMessage::Input((0, "prompt".to_string()))); - } - "add_server" => { - let mut ip = String::new(); - let mut port = String::new(); - self.prompt.responses.iter().for_each(|res| { - if res.query.id == 0 { - ip = res.response.clone(); - } else if res.query.id == 1 { - port = res.response.clone(); - } - }); - let new_server = Server { - address: format!("{}:{}", ip, port), - connected: false, - timer: Duration::from_secs(5), - last_check: time::Instant::now(), - message_que: Vec::new(), - action_que: Vec::new(), - client_id: 0, - selected_client: 0, - config: None, - logged_in: false, - password: String::new(), - name: String::new(), - cert_text: String::new(), - }; - self.servers.push(Arc::new(Mutex::new(new_server))); - self.save_all()?; - self.prompt.reset(); - } - "connect_server" => { - let _ = self.main_tx.send(ToolMessage::ConnectServer); - } - "prompt" => { - if self.prompt.responses.len() == self.prompt.num_responses { - let _ = self - .main_tx - .send(ToolMessage::Input((0, self.prompt.execute_command.clone()))); - let _ = self.main_tx.send(ToolMessage::EndPrompt); - } else { - for query in &self.prompt.prompts { - if !self - .prompt - .responses - .iter() - .any(|res| res.query.id == query.id) - { - self.prompt.last_prompted = Some(query.clone()); - let _ = self - .main_tx - .send(ToolMessage::Output((rid, query.query.clone()))); - break; - } - } - } - } - "list_servers" => { - self.servers.iter().enumerate().for_each(|(id, m)| { - if let Ok(s) = m.lock() { - if id == self.selected_server { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!( - "Server ID:{} Name:{} Address:{} ClientID:{} Sleep:{} Last:{} Loggedin: {} - Currently Selected", - id, s.name, s.address, s.client_id, s.timer.as_secs(), s.last_check.elapsed().as_secs(), s.logged_in - ), - ))); - } else { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!( - "Server ID:{} Name:{} Address:{} ClientID:{} Sleep:{} Last:{}, loggedin: {}", - id, s.name, s.address, s.client_id, s.timer.as_secs(), s.last_check.elapsed().as_secs(), s.logged_in - ), - ))); - } - } - }); - } - "remove_server" => { - let _ = self.main_tx.send(ToolMessage::DisconnectServer); - } - "server_remove_confim" => { - let args = command_args.unwrap(); - let id = args.trim().parse::().unwrap(); - self.servers.remove(id); - if self.selected_server == id && self.selected_server >= 1 { - let _ = self.main_tx.send(ToolMessage::Output(( - 0, - format!("[success] server {} removed!", self.selected_server), - ))); - self.selected_server = self.selected_server - 1; - } - } - "select_server" => { - self.prompt.action = Some(ToolMessage::SelectServer); - self.prompt.num_responses = 1; - self.prompt.prompts.push(PromptQuery { - id: 0, - query: String::from("Selection?"), - }); - self.prompt.execute_command = String::from("server_selected"); - self.servers.iter().enumerate().for_each(|(id, servermut)| { - if let Ok(server) = servermut.lock() { - if id == self.selected_server { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!("{}:{} - Currently Selected", id, server.address.clone()), - ))); - } else { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!("{}: {}", id, server.address.clone()), - ))); - } - } - }); - } - "server_selected" => { - if self.prompt.responses.len() == 1 { - if let Ok(id) = self.prompt.responses[0].response.parse::() { - self.selected_server = id; - if let Ok(server) = self.servers[id].lock() { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!("{} Selected!", server.address), - ))); - } - } else { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - String::from("Invalid selection."), - ))); - } - } else { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - String::from("[error] selecting server: no prompt responses found!"), - ))); - } - let _ = self.main_tx.send(ToolMessage::EndPrompt); - } - "test_server" => { - if let Some(server) = self.servers.get(self.selected_server) { - if let Ok(mut server) = server.lock() { - server.message_que.push("TEST|NONE".to_string()); - } else { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - "[error] getting server lock!".to_string(), - ))); - } - } else { - self.output - .push(String::from("[error] getting selected server!")); - } - } - "server_sleep" => { - if let Some(args) = command_args { - if let Ok(seconds) = args.trim().parse::() { - if let Some(server) = self.servers.get(self.selected_server) { - if let Ok(mut server) = server.lock() { - server.timer = Duration::from_secs(seconds); - self.output - .push(format!("Server timer set to {} seconds", seconds)); - } else { - self.output - .push(String::from("[error] locking server object!")); - } - } else { - self.output - .push(String::from("[error] not a valid server selected!")); - } - } else { - self.output - .push(String::from("invalid second count provided!")); - } - } else { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - "[error] no seconds provided! please use server_sleep number_of_seconds" - .to_string(), - ))); - } - } - "save_all" => { - self.save_all()?; - } - "save_projects" => { - self.save_projects()?; - } - "list_clients" => { - if let Some(server) = self.servers.get(self.selected_server) { - if let Ok(mut server) = server.lock() { - server.message_que.push("LIST_CLIENTS|NONE".to_string()); - } else { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - "[error] getting server lock!".to_string(), - ))); - } - } else { - self.output - .push(String::from("[error] getting selected server!")); - } - } - "select_client" => { - if let Some(arg) = command_args { - if let Some(server) = self.servers.get(self.selected_server) { - if let Ok(mut server) = server.lock() { - if let Ok(id) = arg.parse::() { - server.message_que.push(format!("STOP_CONTROL")); - server.message_que.push(format!("CONTROL|{}", id.clone())); - server.selected_client = id; - let _ = self.main_tx.send(ToolMessage::Output(( - 0, - format!("client {} selected!", id), - ))); - } else { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!("[error] parsing client id from arg: {}", arg), - ))); - } - } - } - } else { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!("No client identified. Usage: select_client client_id"), - ))); - } - } - "deselect_client" => { - if let Some(server) = self.servers.get(self.selected_server) { - if let Ok(mut server) = server.lock() { - server.message_que.push(format!("STOP_CONTROL")); - server.selected_client = 0; - let _ = self - .main_tx - .send(ToolMessage::Output((rid, format!("client deselected!")))); - } - } - } - "add_scope" => { - if let Some(args) = command_args { - args.split_whitespace().into_iter().for_each(|host| { - self.projects[self.selected_project].add_scope(host.to_string()); - }); - } - } - "parse_scope" => { - let mut general_path = self.projects[self.selected_project].notes.clone(); - general_path.push("general.md"); - let general_string = read_to_string(general_path)?; - if let Some((_, scope_section)) = general_string.split_once("# Scope") { - if let Some((scope_section, _)) = scope_section.split_once("#") { - scope_section.lines().into_iter().for_each(|line| { - if line.contains("|") { - if !line.contains("Subnet") { - if !line.contains("--") { - let host = - line.split("|").collect::>()[1].to_string(); - self.projects[self.selected_project].add_scope(host); - } - } - } - }); - } - } - } - "show_config" | "sc" => { - let _ = self - .main_tx - .send(ToolMessage::Output((rid, "Current Config:".to_string()))); - self.config.iter().for_each(|setting| { - let _ = self.main_tx.send(ToolMessage::Output(( - 0, - format!("{}:{}", setting.0, setting.1), - ))); - }); - } - "set" => { - let mut ready = false; - 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 self.config.contains_key(&key) { - ready = true; - } else { - let _ = self.main_tx.send(ToolMessage::Output(( - 0, - "[error] Unknown setting! {key}, {value}".to_string(), - ))); - return Ok(()); - } - } - } - if ready { - self.config.insert(key, value); - self.save_config()?; - let _ = self.main_tx.send(ToolMessage::Output(( - 0, - "[success]Setting changed!".to_string(), - ))); - } else { - let _ = self.main_tx.send(ToolMessage::Output(( - 0, - "[error] malformed command!".to_string(), - ))); - let _ = self.main_tx.send(ToolMessage::Output(( - 0, - "Usag: set setting value".to_string(), - ))); - } - } - "list_modules" => { - self.module_loader.commands.iter().for_each(|module| { - let _ = self - .main_tx - .send(ToolMessage::Output((rid, format!("{}", module.0)))); - }); - } - "server_login" => { - if let Some(pass) = command_args { - let pass = pass.trim().to_string(); - if let Some(server) = self.servers.get(self.selected_server) { - if let Ok(mut lock) = server.lock() { - lock.login(pass)?; - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - "[success] Logged into Server!".to_string(), - ))); - } - } - } - } - "new_config" => { - self.prompt.num_responses = 8; - self.prompt.action = Some(ToolMessage::Input((0, "add_config".to_string()))); - self.prompt.execute_command = String::from("add_config"); - self.prompt.prompts.push(PromptQuery { - id: 0, - query: "name for the new config?".to_string(), - }); - self.prompt.prompts.push(PromptQuery { - id: 1, - query: "Path to save the config folder?".to_string(), - }); - self.prompt.prompts.push(PromptQuery { - id: 2, - query: "Path for upcoming project files (not notes)?".to_string(), - }); - self.prompt.prompts.push(PromptQuery { - id: 3, - query: "Path for current project files (not notes)?".to_string(), - }); - self.prompt.prompts.push(PromptQuery { - id: 4, - query: "Path for upcoming notes?".to_string(), - }); - self.prompt.prompts.push(PromptQuery { - id: 5, - query: "Path for current notes?".to_string(), - }); - self.prompt.prompts.push(PromptQuery { - id: 6, - query: "Path to tool directory?".to_string(), - }); - self.prompt.prompts.push(PromptQuery { - id: 7, - query: "Template box name?".to_string(), - }); - let _ = self - .main_tx - .send(ToolMessage::Input((0, "prompt".to_string()))); - } - "add_config" => { - self.prompt.responses.iter().for_each(|res| { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!("{} | {}", res.query.query, res.response), - ))); - }); - self.new_config()?; - self.save_config()?; - } - "list_configs" => { - if let Some(config_line) = self.config.get("alternate_configs") { - config_line.split(",").into_iter().for_each(|entry| { - if let Some((name, path)) = entry.split_once("|") { - let _ = self - .main_tx - .send(ToolMessage::Output((rid, format!("{}: {}", name, path)))); - } - }); - } - } - "collab_with" => { - if let Some(args) = command_args { - if let Ok(id) = args.trim().parse::() { - if let Some(server) = self.servers.get(self.selected_server) { - if let Ok(mut lock) = server.lock() { - lock.action_que.push(format!("COLLAB|{}", id)); - } - } - } - } - } - "collab" => { - if let Some(args) = command_args { - if let Some(server) = self.servers.get(self.selected_server) { - if let Ok(mut lock) = server.lock() { - lock.action_que.push(format!("CMD|{}", args)); - } - } - } - } - "parse_port_scan" | "pps" => { - self.parse_cs_portscan()?; - } - "stop_db" => { - if let Some(project) = self.projects.get_mut(self.selected_project) { - if let Some(db) = project.db.as_mut() { - db.stop(self.main_tx.clone(), rid)?; - } - } - } - "rustwitness" => { - if let Some(project) = self.projects.get(self.selected_project) { - if let Some(args) = command_args { - if let Some((outfile, proxy)) = args.split_once(" ") { - let outfile = format!("{}/{}", project.files.display(), outfile); - let mut proxy_string = None; - if let Some((ip, port)) = proxy.split_once(" ") { - proxy_string = Some(format!("socks5://{}:{}", ip, port)); - } - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!( - "[success] rustwitness running will save screenshots to {}", - outfile - ), - ))); - let mut urls = Vec::new(); - WalkDir::new(&project.files) - .into_iter() - .filter(|res| res.is_ok()) - .for_each(|res| { - if let Ok(entry) = res { - if entry.file_name().to_string_lossy().contains("urls.txt") - { - if let Ok(input) = read_to_string(entry.path()) { - input.lines().into_iter().for_each(|url| { - urls.push(url.to_string()); - }); - } - } - } - }); - let proxy_string_clone = proxy_string.clone(); - let out_folder = PathBuf::from(outfile); - let tx_clone = self.main_tx.clone(); - self.workers.spawn(move || { - urls.par_iter().for_each(|url| { - if url.contains("://"){ - match rustwitness( - url.clone(), - out_folder.clone(), - proxy_string_clone.clone(), - ) { - Ok(_) => { - let _ = tx_clone.send(ToolMessage::Output(( - rid, - format!("[success] {} captured!", url), - ))); - } - Err(e) => { - let _ = tx_clone.send(ToolMessage::Output(( - rid, - format!( - "[error] capturing screenshot of {url}: {e}" - ), - ))); - } - } - } - else{ - let _ = tx_clone.send(ToolMessage::Output(( - rid, - format!( - "[error] capturing screenshot of {url}: not a valid url." - ), - ))); - } - }); - let _ = tx_clone.send(ToolMessage::Output(( - rid, - "[success] all urls scanned for screenshots!".to_string(), - ))); - }); - } - } else { - let _ = self.main_tx.send(ToolMessage::Output(( - 0, - "[error] incorrect command usage!".to_string(), - ))); - let _ = self.main_tx.send(ToolMessage::Output(( - 0, - "usage: rustwitness output_file_name optional_proxy_ip optional_proxy_port".to_string(), - ))); - } - } - } - "portscan" => { - if let Some(project) = self.projects.get(self.selected_project) { - let mut ports = Vec::new(); - let mut hosts = project.hosts.clone(); - if let Some(args) = command_args { - let args_vec: Vec<&str> = args.split_whitespace().collect(); - match args_vec.len() { - 1 => { - args_vec[0].split(",").into_iter().for_each(|p| { - if let Ok(port) = p.trim().parse::() { - ports.push(port); - } - }); - } - 2 => { - args_vec[0].split(",").into_iter().for_each(|ip| { - if let Ok(ip) = ip.parse::() { - let mut new_host = Host::new(); - new_host.ip = ip.to_string(); - hosts.push(new_host); - } else if let Ok(net) = ip.parse::() { - for host_addr in net.hosts() { - let mut new_host = Host::new(); - new_host.ip = host_addr.to_string(); - hosts.push(new_host); - } - } - }); - args_vec[1].split(",").into_iter().for_each(|p| { - if p.contains("-") { - if let Some((start, end)) = p.split_once("-") { - if let Ok(start) = start.parse::() { - if let Ok(end) = end.parse::() { - for range in [start..end] { - range.into_iter().for_each(|port| { - ports.push(port.clone()); - }); - } - } - } - } - } else if let Ok(port) = p.trim().parse::() { - ports.push(port); - } - }); - } - _ => {} - } - if !ports.is_empty() && !hosts.is_empty() { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!("scanning {} ports on {} hosts", ports.len(), hosts.len()), - ))); - let project_clone = Arc::new(Mutex::new(project.clone())); - let id = self.selected_project.clone(); - let tx_clone = self.main_tx.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(_) => {} - } - }); - }); - if let Ok(lock) = project_clone.lock() { - let _ = - tx_clone.send(ToolMessage::UpdateProject(id, lock.clone())); - let _ = tx_clone.send(ToolMessage::Output(( - 0, - "[success] portscan finished!".to_string(), - ))); - } - }); - } - } - } - } - "rustbuster" => { - if let Some(args) = command_args { - let args: Vec<&str> = args.split(" ").collect(); - let mut dirs = Vec::new(); - let mut subs = Vec::new(); - let mut targets = Vec::new(); - if args - .iter() - .any(|a| a.contains("subs=") || a.contains("dirs=")) - { - args.iter().for_each(|arg| { - if arg.contains("subs=") { - if let Some((_, sub_path)) = arg.split_once("=") { - if let Ok(subtext) = read_to_string(sub_path) { - subtext.lines().into_iter().for_each(|sub| { - subs.push(sub.to_string()); - }); - } - } - } - if arg.contains("dirs=") { - if let Some((_, dir_path)) = arg.split_once("=") { - if let Ok(dir_text) = read_to_string(dir_path) { - dir_text.lines().into_iter().for_each(|dir| { - dirs.push(dir.to_string()); - }); - } - } - } else { - if arg.contains(",") { - arg.split(",").into_iter().for_each(|t| { - targets.push(t.to_string()); - }); - } else if !arg.contains("=") { - targets.push(arg.to_string()); - } - } - }); - let mut num_dir = 0; - let mut num_sub = 0; - let tx_clone = self.main_tx.clone(); - let out_path = self.projects[self.selected_project].files.clone(); - let project_clone = - Arc::new(Mutex::new(self.projects[self.selected_project].clone())); - let mut buster_targets = Vec::new(); - for target in targets { - if target.contains("://") { - for dir in &dirs { - num_dir += 1; - buster_targets - .push(BusterTarget::Dir(format!("{}/{}", target, dir))); - } - } else if target.contains(".") { - for sub in &subs { - num_sub += 1; - buster_targets - .push(BusterTarget::Sub(format!("{}.{}", sub, target))); - } - } - } - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!("{} targets found scanning...", num_dir + num_sub), - ))); - let new_progress = Arc::new(RwLock::new(ToolProgress { - name: format!( - "rustbuster - {} {}", - self.projects[self.selected_project].org_name.clone(), - self.projects[self.selected_project].name.clone() - ), - total: num_dir + num_sub, - complete: 0, - finished: false, - })); - self.progress_bars.push(new_progress.clone()); - self.workers.spawn(move || { - buster_targets.par_iter().for_each(|t| match t { - BusterTarget::Dir(_) => { - if let Some(res) = rustbuster(t.clone()) { - let mut url_path = out_path.clone(); - url_path.push("urls.txt"); - if let Ok(mut url_file) = OpenOptions::new() - .write(true) - .append(true) - .create(true) - .open(&url_path) - { - if let Err(e) = writeln!( - url_file, - "{}", - format!("[success] {}", res) - ) { - let _ = tx_clone.send(ToolMessage::Output(( - rid, - format!( - "[error] saving url to {}: {e}", - url_path.display() - ), - ))); - } - } - let _ = tx_clone.send(ToolMessage::Output(( - rid, - format!("[success] {}", res), - ))); - } - if let Ok(mut lock) = new_progress.write() { - lock.complete += 1; - } - } - BusterTarget::Sub(_) => { - if let Some(res) = rustbuster(t.clone()) { - let mut url_path = out_path.clone(); - url_path.push("domains.txt"); - if let Ok(mut url_file) = OpenOptions::new() - .write(true) - .append(true) - .create(true) - .open(&url_path) - { - if let Err(e) = - writeln!(url_file, "{}", format!("{}", res)) - { - let _ = tx_clone.send(ToolMessage::Output(( - rid, - format!( - "[error] saving domain to {}: {e}", - url_path.display() - ), - ))); - } - } - let _ = tx_clone.send(ToolMessage::Output(( - rid, - format!("[success] {}", res), - ))); - if let Some((domain, ip)) = res.split_once(":") { - let mut new_host = Host::new(); - new_host.ip = ip.to_string(); - new_host.hostname = domain.to_string(); - if let Ok(mut project_lock) = project_clone.lock() { - project_lock.add_host(new_host); - } - } - } - if let Ok(mut lock) = new_progress.write() { - lock.complete += 1; - } - } - }); - if let Ok(mut lock) = new_progress.write() { - lock.finished = true; - } - drop(project_clone); - }); - } else { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - "[error] no sub or dir wordlist provided!".to_string(), - ))); - let _ = self - .main_tx - .send(ToolMessage::Output((rid, "USAGE:".to_string()))); - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - "rustbuster target,target,target subs=/path/to/subwordlist dirs=/path/to/dirwordlist".to_string(), - ))); - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - "be sure to include the subs= or dirs= part of the command above!" - .to_string(), - ))); - } - } else { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - "[error] no arguments provided!".to_string(), - ))); - let _ = self - .main_tx - .send(ToolMessage::Output((rid, "USAGE:".to_string()))); - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - "rustbuster target,target,target subs=/path/to/subwordlist dirs=/path/to/dirwordlist".to_string(), - ))); - } - } - "clear_operations" => { - self.progress_bars.retain(|pb| { - let lock = pb.read().unwrap(); - !lock.finished - }); - } - "shodan" => { - if let Some(project) = self.projects.get(self.selected_project) { - if let Some(host) = project.hosts.get(self.selected_host) { - if let Ok(entry) = Entry::new("tetanus", "shodan_key") { - if let Ok(key) = entry.get_password() { - let mut host_clone = host.clone(); - let tx_clone = self.main_tx.clone(); - thread::spawn(move || host_clone.get_shodan(key, tx_clone)); - } else { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!("[error] couldn't get shodan key from keyring!"), - ))); - } - } else { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!("[error] couldn't find shodan key in keyring!"), - ))); - } - } - } - } - "shodan_all" => { - if let Some(project) = self.projects.get(self.selected_project) { - if !project.hosts.is_empty() { - if let Ok(entry) = Entry::new("tetanus", "shodan_key") { - if let Ok(key) = entry.get_password() { - project.hosts.iter().for_each(|h| { - let mut host_clone = h.clone(); - let tx_clone = self.main_tx.clone(); - let key_clone = key.clone(); - thread::spawn(move || { - host_clone.get_shodan(key_clone, tx_clone) - }); - }); - } else { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!("[error] couldn't get shodan key from keyring!"), - ))); - } - } else { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!("[error] couldn't find shodan key in keyring!"), - ))); - } - } - } - } - "set_shodan_key" => { - if let Some(args) = command_args { - if let Ok(_) = Entry::store_status() { - if let Ok(entry) = Entry::new("tetanus", "shodan_key") { - if let Err(e) = entry.set_password(args.trim()) { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!("[error] couldn't set shodan apikey!: {e}"), - ))); - } - } else { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!("[error] couldn't create new entry!"), - ))); - } - } else { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!("[error] store status bad!"), - ))); - } - } - } - "save_server" => { - if let Some(server) = self.servers.get(self.selected_server) { - let mut path = self.config_file.clone(); - path.pop(); - path.push("client_servers"); - if !path.exists() { - create_dir_all(&path).unwrap(); - } - if let Ok(mut lock) = server.lock() { - match lock.save(path) { - Ok(_) => { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!("[success] {} saved!", lock.address), - ))); - } - Err(e) => { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!("[error] saving server: {e}"), - ))); - } - } - } - } - } - "parse_nessus" => { - if let Some(arg) = command_args { - let path = PathBuf::from(arg); - let csv_text = read_to_string(path)?; - let mut parsed_hosts: Vec = Vec::new(); - csv_text - .lines() - .into_iter() - .filter(|line| !line.contains("Host,Protocol,Port") && line.contains(",")) - .for_each(|line| { - let parts: Vec<&str> = line.split(",").collect(); - let name = parts[0].replace("\"", "").trim().to_string(); - let port = parts[2].replace("\"", "").trim().to_string(); - if let Ok(_) = name.parse::() { - if !parsed_hosts.iter().any(|h| h.ip == name) { - let mut new_host = Host::new(); - new_host.ip = name.clone(); - new_host.add_port(&port); - parsed_hosts.push(new_host); - } else { - parsed_hosts.iter_mut().filter(|h| h.ip == name).for_each( - |h| { - h.add_port(&port); - }, - ); - } - } else { - if let Ok(ips) = lookup_host(name.as_str()) { - ips.for_each(|ip| { - if !parsed_hosts.iter().any(|h| h.ip == ip.to_string()) { - let mut new_host = Host::new(); - new_host.ip = ip.to_string().clone(); - new_host.hostname = name.clone(); - new_host.add_port(&port); - parsed_hosts.push(new_host) - } else { - parsed_hosts - .iter_mut() - .filter(|h| h.ip == ip.to_string()) - .for_each(|h| { - if h.hostname != name { - h.hostname = name.clone(); - } - if port != "0" { - h.add_port(&port); - } - }); - } - }); - } - } - }); - parsed_hosts.iter().for_each(|h| { - if !self.projects[self.selected_project] - .hosts - .iter() - .any(|ph| ph.ip == h.ip) - { - self.projects[self.selected_project].add_host(h.clone()); - } else { - self.projects[self.selected_project] - .hosts - .iter_mut() - .filter(|ph| ph.ip == h.ip) - .for_each(|ph| { - h.open_ports.iter().for_each(|p| { - ph.add_port(&format!("{}", p)); - }); - if ph.hostname != h.hostname { - ph.hostname = h.hostname.clone(); - } - }); - } - }); - self.save_all()?; - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - "[success] Project hosts saved!".to_string(), - ))); - let mut host_notes_folder = self.projects[self.selected_project].notes.clone(); - host_notes_folder.push("host_notes.md"); - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!("writing to {}...", host_notes_folder.display()), - ))); - let mut host_notes_file = OpenOptions::new() - .create(true) - .truncate(true) - .write(true) - .open(host_notes_folder)?; - writeln!( - host_notes_file, - "#{} #external #host_notes", - self.projects[self.selected_project].org_name.clone() - )?; - self.projects[self.selected_project] - .hosts - .iter() - .filter(|h| !h.open_ports.is_empty()) - .for_each(|h| { - let _ = writeln!(host_notes_file, "# {}", h.ip); - let _ = writeln!(host_notes_file, "hostname: {}", h.hostname); - let _ = writeln!(host_notes_file, "ports:\n"); - let _ = writeln!(host_notes_file, "| port | service | vulns |",); - let _ = writeln!(host_notes_file, "| ---- | ------- | ----- |",); - h.open_ports.iter().for_each(|p| { - let _ = writeln!(host_notes_file, "| {} | | |", p); - }); - let _ = writeln!(host_notes_file, "\n\n",); - let _ = writeln!(host_notes_file, "---",); - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!("[success] notes for {} written!", h.ip), - ))); - }); - let _ = writeln!(host_notes_file, "# Hosts with no ports open:\n"); - self.projects[self.selected_project] - .hosts - .iter() - .filter(|h| h.open_ports.is_empty()) - .for_each(|h| { - let _ = writeln!(host_notes_file, "- {}:{}", h.ip, h.hostname); - }); - } - } - "exit" => { - self.save_all()?; - let _ = self.main_tx.send(ToolMessage::AppStateExit); - let _ = self - .main_tx - .send(ToolMessage::Output((rid, String::from("Good bye!")))); - } - _ => { - if self.module_loader.asts.get(command_name).is_none() { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!("[error] command not found: {}", command_name), - ))); - return Ok(()); - } - let ast = self - .module_loader - .asts - .get(command_name) - .ok_or_else(|| format!("[error] Command not found: {}", command_name))? - .clone(); - let engine = Arc::clone(&self.module_loader.engine); - let cmd_meta = self - .module_loader - .commands - .get(command_name) - .ok_or_else(|| format!("Metadata not found for command: {}", command_name))? - .clone(); - let mut scope = Scope::new(); - for arg_requirement in &cmd_meta.args { - match arg_requirement.trim() { - "project" => { - scope.push("project", self.projects[self.selected_project].clone()); - } - "projects" => { - let mut arr = rhai::Array::new(); - for proj in &self.projects { - arr.push(rhai::Dynamic::from(proj.clone())); - } - scope.push("projects", arr); - } - "host" => { - scope.push("host", "todo"); - } - "hosts" => { - scope.push("hosts", self.projects[self.selected_project].clone()); - } - "config" => { - let mut map = rhai::Map::new(); - for (k, v) in &self.config { - map.insert(k.clone().into(), v.clone().into()); - } - scope.push("config", map); - } - "distrobox" => { - if let Some(db) = self.projects[self.selected_project].db.clone() { - scope.push("distrobox", db); - } - } - _ if arg_requirement.starts_with("string") => { - let input_val = command_args - .clone() - .unwrap_or_else(|| self.curent_intput.clone()); - scope.push("input_string", input_val); - } - _ if arg_requirement.starts_with("bool") => { - let mut input_val = false; - if command_args - .clone() - .unwrap_or_else(|| self.curent_intput.clone()) - .contains("true") - { - input_val = true; - } - scope.push("input_bool", input_val); - } - _ => {} - } - } - let worker_tx = self.main_tx.clone(); - self.workers.spawn(move || { - match engine.eval_ast_with_scope::(&mut scope, &ast) { - Ok(result) => { - let result_str = if result.is_array() { - if let Ok(arr) = result.into_array() { - let lines: Vec = arr - .into_iter() - .map(|item| { - item.into_string().unwrap_or_else(|_| "".into()) - }) - .collect(); - lines.join("\n") - } else { - "failed to process array output".into() - } - } else if result.is_string() { - let out_string = result - .into_string() - .unwrap_or_else(|_| "failed to parse string".into()); - if cmd_meta.calls == "clipboard" { - let mut ctx: ClipboardContext = - ClipboardProvider::new().unwrap(); - ctx.set_contents(out_string.clone()).unwrap(); - } else if cmd_meta.calls != "none" { - let _ = - worker_tx.send(ToolMessage::Input((0, out_string.clone()))); - } - out_string - } else { - format!("{:?}", result) - }; - if result_str.contains("\n") { - result_str.lines().into_iter().for_each(|line| { - let _ = tx.send(ToolMessage::Output((rid, line.to_string()))); - }); - } else { - let _ = tx.send(ToolMessage::Output((rid, result_str))); - } - } - Err(err) => { - let _ = tx.send(ToolMessage::Output(( - rid, - format!("[error] in script execution: {}", err), - ))); - } - } - }); - } - } - Ok(()) - } - - pub fn edit_config(&mut self, key: String, value: String) { - if self.config.contains_key(&key) { - self.config.insert(key.clone(), value.clone()); - } - let _ = self.main_tx.send(ToolMessage::Output(( - 0, - format!("Setting saved! {key}:{value}"), - ))); - } - - pub fn new_project( - &mut self, - org: String, - name: String, - rid: usize, - ) -> Result<(), Box> { - let mut main_config_file = self.config_file.clone(); - main_config_file.pop(); - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!("{} is the main config folder.", main_config_file.display()), - ))); - let template_box = self.config.get("template_box").unwrap(); - let project_files = PathBuf::from(self.config.get("upcoming_files").unwrap()) - .join(format!("{}/{}", org, name)); - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!("{} is the project_files folder", project_files.display()), - ))); - let project_notes = PathBuf::from(self.config.get("upcoming_notes").unwrap()) - .join(format!("{}/{}", org, name)); - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!("{} is the project_notes folder", project_notes.display()), - ))); - let mut template_path = main_config_file.clone(); - let mut project_conf_folder = main_config_file.clone(); - let tools = self.config.get("tools").unwrap(); - template_path.push("note_templates"); - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!("{} is the note template folder", template_path.display()), - ))); - project_conf_folder.push(format!("projects/{}-{}", org, name)); - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!( - "{} is the project config folder", - project_conf_folder.display() - ), - ))); - let mut options = CopyOptions::new(); - options.overwrite = true; - if !project_files.exists() { - match create_dir_all(&project_files) { - Ok(_) => {} - Err(e) => { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!( - "[error] making project files! {e} on {}", - project_files.display() - ), - ))); - } - } - } - if !project_notes.exists() {} - match create_dir_all(&project_notes) { - Ok(_) => {} - Err(e) => { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!( - "[error] making project notes! {e} on {}", - project_notes.display() - ), - ))); - } - } - if !project_conf_folder.exists() { - match create_dir_all(&project_conf_folder) { - Ok(_) => {} - Err(e) => { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!( - "[error] making config folder! {e} on {}", - project_conf_folder.display() - ), - ))); - } - } - } - let mut project_config_file = project_conf_folder.clone(); - project_config_file.push("project.conf"); - for entry in read_dir(template_path)? { - let entry = entry?; - let template_name_os = entry.file_name(); - let template_name = template_name_os.to_string_lossy(); - if name.clone().contains(template_name.as_ref()) { - for entry in read_dir(entry.path())? { - let entry = entry?; - let file_name_os = entry.file_name(); - let file_name = file_name_os.to_string_lossy(); - if entry.file_type()?.is_dir() { - let dest = project_notes.join(file_name.as_ref()); - match copy(entry.path(), dest, &options) { - Ok(_) => {} - Err(e) => { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!("[error] copying note template! {e}"), - ))); - } - } - } else { - match fs::copy(entry.path(), project_notes.join(file_name.as_ref())) { - Ok(_) => {} - Err(e) => { - let _ = self.main_tx.send(ToolMessage::Output(( - rid, - format!("[error] copying note template! {e}"), - ))); - } - } - } - } - } - } - 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 new_project = Project { - org_name: org, - name: name, - notes: project_notes, - files: project_files, - hosts: Vec::new(), - config_folder: project_config_file, - current: false, - db: Some(db), - scope: Vec::new(), - }; - match new_project.save_config() { - Ok(_) => {} - Err(e) => { - let _ = self.main_tx.send(ToolMessage::Output(( - 0, - format!("[error] saving project! {e}"), - ))); - } - } - new_project.save_config()?; - self.projects.push(new_project); - return Ok(()); - } - - pub fn promote_project(&mut self, rid: usize) -> Result> { - self.main_tx.send(ToolMessage::Output(( - rid, - String::from("promoting project..."), - )))?; - let return_string = String::from("[success] Project promoted!"); - let mut new_project = self.projects[self.selected_project].clone(); - let mut new_files_path = PathBuf::from(self.config.get("current_files").unwrap()) - .join(format!("{}", new_project.org_name)); - let mut new_notes_path = PathBuf::from(self.config.get("current_notes").unwrap()) - .join(format!("{}", new_project.org_name)); - let mut options = CopyOptions::new(); - create_dir_all(&new_files_path)?; - create_dir_all(&new_notes_path)?; - options.overwrite = true; - copy(new_project.files.clone(), new_files_path.clone(), &options)?; - copy(new_project.notes.clone(), new_notes_path.clone(), &options)?; - self.main_tx.send(ToolMessage::Output(( - rid, - String::from("[success] folders copied!"), - )))?; - remove_dir_all(&new_project.files)?; - remove_dir_all(&new_project.notes)?; - new_files_path.push(&new_project.name); - new_notes_path.push(&new_project.name); - new_project.files(new_files_path.clone()); - new_project.notes(new_notes_path.clone()); - new_project.current = true; - self.main_tx.send(ToolMessage::Output(( - rid, - String::from("upcoming folders cleaned up!"), - )))?; - self.main_tx.send(ToolMessage::Output(( - rid, - String::from("creating distrobox..."), - )))?; - if let Some(mut db) = new_project.db { - db.volumes = vec![ - new_files_path.display().to_string(), - new_notes_path.display().to_string(), - self.config.get("tools").unwrap().to_string(), - ]; - new_project.db = Some(db); - let _ = self.main_tx.send(ToolMessage::RebuildDB); - self.main_tx.send(ToolMessage::Output(( - rid, - String::from("distrobox sent to rebuild function..."), - )))?; - } - new_project.save_config()?; - self.projects[self.selected_project] = new_project; - return Ok(return_string); - } - - pub fn save_projects(&mut self) -> Result<(), Box> { - for mut project in self.projects.clone() { - project.save_config()?; - } - return Ok(()); - } - - pub fn save_config(&mut self) -> Result<(), Box> { - let mut config_vec = Vec::new(); - for key in self.config.keys() { - if let Some(value) = self.config.get(key) { - config_vec.push(format!("{}: {}", key, value)); - } - } - if config_vec.len() > 0 { - fs::write(self.config_file.clone(), config_vec.join("\n"))?; - } else { - return Err("No config loaded!".into()); - } - return Ok(()); - } - - pub fn save_all(&mut self) -> Result<(), Box> { - self.save_config()?; - self.save_projects()?; - return Ok(()); - } - - pub fn new_server(&mut self) -> Result<(), Box> { - let mut ip = String::new(); - let mut port = String::new(); - self.prompt.responses.iter().for_each(|res| { - if res.query.id == 0 { - ip = res.response.clone(); - } else if res.query.id == 1 { - port = res.response.clone(); - } - }); - let new_server = Server { - address: format!("{}:{}", ip, port), - connected: false, - timer: Duration::from_secs(60), - last_check: time::Instant::now(), - message_que: Vec::new(), - action_que: Vec::new(), - client_id: 0, - selected_client: 0, - config: None, - logged_in: false, - password: String::new(), - name: String::new(), - cert_text: String::new(), - }; - self.servers.push(Arc::new(Mutex::new(new_server))); - self.prompt.reset(); - Ok(()) - } - - pub fn new_config(&mut self) -> Result<(), Box> { - let mut name = String::new(); - let mut config_path = PathBuf::new(); - let mut upcoming_files_path = PathBuf::new(); - let mut current_files_path = PathBuf::new(); - let mut upcoming_notes_path = PathBuf::new(); - let mut current_notes_path = PathBuf::new(); - let mut tools_folder = PathBuf::new(); - let mut template_box = String::new(); - let term_cmd = self.config.get("term_cmd").unwrap().clone(); - self.prompt - .responses - .iter() - .for_each(|res| match res.query.id { - 0 => name = res.response.clone(), - 1 => config_path = PathBuf::from(res.response.clone()), - 2 => upcoming_files_path = PathBuf::from(res.response.clone()), - 3 => current_files_path = PathBuf::from(res.response.clone()), - 4 => upcoming_notes_path = PathBuf::from(res.response.clone()), - 5 => current_notes_path = PathBuf::from(res.response.clone()), - 6 => tools_folder = PathBuf::from(res.response.clone()), - 7 => template_box = res.response.clone(), - _ => {} - }); - let mut existing = false; - if let Some(line) = self.config.get("alternate_configs") { - line.split(",").into_iter().for_each(|line| { - if let Some((ename, _)) = line.split_once("|") { - if ename.trim().to_string() == name { - existing = true; - } - } - }); - } - if existing { - let _ = self.main_tx.send(ToolMessage::Output(( - 0, - format!("{} already exists!", name), - ))); - return Ok(()); - } - let root_config_folder_path = config_path.clone(); - let mut projects_path = config_path.clone(); - projects_path.push("projects"); - let mut module_path = config_path.clone(); - module_path.push("modules"); - let mut note_templates_path = config_path.clone(); - note_templates_path.push("note_templates"); - create_dir_all(&module_path)?; - config_path.push("client.conf"); - let client_conf_path = config_path.clone(); - projects_path.push("default"); - match create_dir_all(&projects_path) { - Ok(_) => { - let _ = self.main_tx.send(ToolMessage::Output(( - 0, - format!( - "[success] create projects path at {}", - projects_path.display() - ), - ))); - } - Err(e) => { - let _ = self.main_tx.send(ToolMessage::Output(( - 0, - format!( - "[error] couldn't create projects path at {}: {e}", - projects_path.display() - ), - ))); - return Ok(()); - } - } - projects_path.push("project.conf"); - let mut client_config_file = File::create(&config_path)?; - client_config_file.write(format!("projects: {}\n", projects_path.display()).as_bytes())?; - client_config_file.write("servers: 127.0.0.1:31337\n".as_bytes())?; - let mut default_project_file = File::create(projects_path)?; - config_path.pop(); - let mut server_path = config_path.clone(); - server_path.push("server"); - create_dir_all(&server_path)?; - server_path.push("server.conf"); - let mut server_config_file = File::create(&server_path)?; - server_config_file.write("address: 127.0.0.1:31337\n".as_bytes())?; - server_config_file.write("running: false\n".as_bytes())?; - default_project_file.write("org_name: default\n".as_bytes())?; - default_project_file.write("name: default\n".as_bytes())?; - default_project_file - .write(format!("notes: {}\n", current_files_path.display()).as_bytes())?; - default_project_file - .write(format!("files: {}\n", current_notes_path.display()).as_bytes())?; - default_project_file.write("stage: current".as_bytes())?; - client_config_file - .write(format!("current_files: {}\n", current_files_path.display()).as_bytes())?; - client_config_file - .write(format!("current_notes: {}\n", current_notes_path.display()).as_bytes())?; - client_config_file - .write(format!("upcoming_files: {}\n", upcoming_files_path.display()).as_bytes())?; - client_config_file - .write(format!("upcoming_notes: {}\n", upcoming_notes_path.display()).as_bytes())?; - client_config_file.write(format!("module_path: {}\n", module_path.display()).as_bytes())?; - client_config_file.write(format!("template_box: {}\n", template_box).as_bytes())?; - client_config_file.write(format!("term_cmd: {}\n", term_cmd).as_bytes())?; - client_config_file.write(format!("tools: {}\n", tools_folder.display()).as_bytes())?; - create_dir_all("./temp")?; - env::set_current_dir("./temp")?; - let git_clone_output = Command::new("git") - .arg("clone") - .arg("https://git.pyro.monster/pyro/tetanus.git") - .output()?; - let git_clone_status = git_clone_output.status.success(); - if git_clone_status { - let mut options = CopyOptions::new(); - options.overwrite = true; - options.copy_inside = true; - copy( - "./tetanus/note_templates", - root_config_folder_path.clone(), - &options, - )?; - copy( - "./tetanus/default-modules", - module_path.join("default"), - &options, - )?; - copy( - "./tetanus/victim_templates", - root_config_folder_path.join("victim"), - &options, - )?; - env::set_current_dir("../")?; - remove_dir_all("./temp")?; - let _ = self.main_tx.send(ToolMessage::Output(( - 0, - "New config structure created!".to_string(), - ))); - } - if let Some(original_value) = self.config.get("alternate_configs") { - let new_value = format!( - "{}|{},{}", - name, - client_conf_path.display().to_string(), - original_value, - ); - self.config - .insert("alternate_configs".to_string(), new_value); - } else { - self.config.insert( - "alternate_configs".to_string(), - format!("{}|{},", name, client_conf_path.display().to_string()), - ); - } - self.prompt.responses.clear(); - self.prompt.prompts.clear(); - self.prompt.action = None; - self.prompt.execute_command.clear(); - Ok(()) - } - - pub fn parse_cs_portscan(&mut self) -> Result<(), Box> { - if let Some(project) = self.projects.get_mut(self.selected_project) { - let walk = WalkDir::new(project.files.clone()); - walk.into_iter().for_each(|res| { - if let Ok(entry) = res { - if entry.file_name().to_string_lossy().contains("services.tsv") { - if let Ok(scan) = read_to_string(entry.path()) { - let mut url_lines = Vec::new(); - let mut web_host_lines = Vec::new(); - let mut rdp_host_lines = Vec::new(); - let mut ftp_host_lines = Vec::new(); - let mut telnet_host_lines = Vec::new(); - let mut winrm_host_lines = Vec::new(); - let mut mysql_host_lines = Vec::new(); - let mut mssql_host_lines = Vec::new(); - let mut snmp_host_lines = Vec::new(); - let mut kerberos_host_lines = Vec::new(); - let mut ldap_host_lines = Vec::new(); - let mut smb_host_lines = Vec::new(); - let mut unknonw_host_lines = Vec::new(); - let mut ssh_host_lines = Vec::new(); - scan.lines().into_iter().for_each(|line| { - if line.len() > 0 { - let data: Vec<&str> = line.split_whitespace().collect(); - match data[1].trim() { - "80" | "443" | "8080" | "8443" | "8000" | "4433" => { - let http_line = - format!("http://{}:{}", data[0], data[1]); - let https_line = - format!("https://{}:{}", data[0], data[1]); - url_lines.push(http_line); - url_lines.push(https_line); - web_host_lines.push(data[0].to_string()); - let mut existing = false; - project - .hosts - .iter_mut() - .filter(|h| h.ip == data[0].to_string()) - .for_each(|h| { - h.add_port(data[1]); - existing = true; - }); - if !existing { - let mut new_host = Host::new(); - new_host.ip = data[0].trim().to_string(); - new_host.add_port(data[1]); - project.add_host(new_host); - } - } - "3389" => { - rdp_host_lines.push(data[0].to_string()); - let mut existing = false; - project - .hosts - .iter_mut() - .filter(|h| h.ip == data[0].to_string()) - .for_each(|h| { - h.add_port(data[1]); - existing = true; - }); - if !existing { - let mut new_host = Host::new(); - new_host.ip = data[0].trim().to_string(); - new_host.add_port(data[1]); - project.add_host(new_host); - } - } - "21" => { - ftp_host_lines.push(data[0].to_string()); - let mut existing = false; - project - .hosts - .iter_mut() - .filter(|h| h.ip == data[0].to_string()) - .for_each(|h| { - h.add_port(data[1]); - existing = true; - }); - if !existing { - let mut new_host = Host::new(); - new_host.ip = data[0].trim().to_string(); - new_host.add_port(data[1]); - project.add_host(new_host); - } - } - "23" => { - telnet_host_lines.push(data[0].to_string()); - let mut existing = false; - project - .hosts - .iter_mut() - .filter(|h| h.ip == data[0].to_string()) - .for_each(|h| { - h.add_port(data[1]); - existing = true; - }); - if !existing { - let mut new_host = Host::new(); - new_host.ip = data[0].trim().to_string(); - new_host.add_port(data[1]); - project.add_host(new_host); - } - } - "22" => { - ssh_host_lines.push(data[0].to_string()); - let mut existing = false; - project - .hosts - .iter_mut() - .filter(|h| h.ip == data[0].to_string()) - .for_each(|h| { - h.add_port(data[1]); - existing = true; - }); - if !existing { - let mut new_host = Host::new(); - new_host.ip = data[0].trim().to_string(); - new_host.add_port(data[1]); - project.add_host(new_host); - } - } - "5985" | "5986" => { - winrm_host_lines.push(data[0].to_string()); - let mut existing = false; - project - .hosts - .iter_mut() - .filter(|h| h.ip == data[0].to_string()) - .for_each(|h| { - h.add_port(data[1]); - existing = true; - }); - if !existing { - let mut new_host = Host::new(); - new_host.ip = data[0].trim().to_string(); - new_host.add_port(data[1]); - project.add_host(new_host); - } - } - "3306" => { - mysql_host_lines.push(data[0].to_string()); - let mut existing = false; - project - .hosts - .iter_mut() - .filter(|h| h.ip == data[0].to_string()) - .for_each(|h| { - h.add_port(data[1]); - existing = true; - }); - if !existing { - let mut new_host = Host::new(); - new_host.ip = data[0].trim().to_string(); - new_host.add_port(data[1]); - project.add_host(new_host); - } - } - "1433" => { - mssql_host_lines.push(data[0].to_string()); - let mut existing = false; - project - .hosts - .iter_mut() - .filter(|h| h.ip == data[0].to_string()) - .for_each(|h| { - h.add_port(data[1]); - existing = true; - }); - if !existing { - let mut new_host = Host::new(); - new_host.ip = data[0].trim().to_string(); - new_host.add_port(data[1]); - project.add_host(new_host); - } - } - "161" => { - snmp_host_lines.push(data[0].to_string()); - let mut existing = false; - project - .hosts - .iter_mut() - .filter(|h| h.ip == data[0].to_string()) - .for_each(|h| { - h.add_port(data[1]); - existing = true; - }); - if !existing { - let mut new_host = Host::new(); - new_host.ip = data[0].trim().to_string(); - new_host.add_port(data[1]); - project.add_host(new_host); - } - } - "88" => { - kerberos_host_lines.push(data[0].to_string()); - let mut existing = false; - project - .hosts - .iter_mut() - .filter(|h| h.ip == data[0].to_string()) - .for_each(|h| { - h.add_port(data[1]); - existing = true; - }); - if !existing { - let mut new_host = Host::new(); - new_host.ip = data[0].trim().to_string(); - new_host.add_port(data[1]); - project.add_host(new_host); - } - } - "389" | "636" => { - ldap_host_lines.push(data[0].to_string()); - let mut existing = false; - project - .hosts - .iter_mut() - .filter(|h| h.ip == data[0].to_string()) - .for_each(|h| { - h.add_port(data[1]); - existing = true; - }); - if !existing { - let mut new_host = Host::new(); - new_host.ip = data[0].trim().to_string(); - new_host.add_port(data[1]); - project.add_host(new_host); - } - } - "445" | "139" => { - smb_host_lines.push(data[0].to_string()); - let mut existing = false; - project - .hosts - .iter_mut() - .filter(|h| h.ip == data[0].to_string()) - .for_each(|h| { - h.add_port(data[1]); - existing = true; - }); - if !existing { - let mut new_host = Host::new(); - new_host.ip = data[0].trim().to_string(); - new_host.add_port(data[1]); - project.add_host(new_host); - } - } - _ => { - unknonw_host_lines.push(data[0].to_string()); - let mut existing = false; - project - .hosts - .iter_mut() - .filter(|h| h.ip == data[0].to_string()) - .for_each(|h| { - h.add_port(data[1]); - existing = true; - }); - if !existing { - let mut new_host = Host::new(); - new_host.ip = data[0].trim().to_string(); - new_host.add_port(data[1]); - project.add_host(new_host); - } - } - } - } - }); - if !url_lines.is_empty() { - let mut file_path = project.files.clone(); - file_path.push("urls.txt"); - if let Ok(mut file) = File::create(file_path) { - let _ = write!(file, "{}", url_lines.join("\n")); - } - } - if !web_host_lines.is_empty() { - let mut file_path = project.files.clone(); - file_path.push("web_hosts.txt"); - if let Ok(mut file) = File::create(file_path) { - let _ = write!(file, "{}", web_host_lines.join("\n")); - } - } - if !rdp_host_lines.is_empty() { - let mut file_path = project.files.clone(); - file_path.push("rdp_hosts.txt"); - if let Ok(mut file) = File::create(file_path) { - let _ = write!(file, "{}", rdp_host_lines.join("\n")); - } - } - if !ftp_host_lines.is_empty() { - let mut file_path = project.files.clone(); - file_path.push("ftp_hosts.txt"); - if let Ok(mut file) = File::create(file_path) { - let _ = write!(file, "{}", ftp_host_lines.join("\n")); - } - } - if !telnet_host_lines.is_empty() { - let mut file_path = project.files.clone(); - file_path.push("telnet_hosts.txt"); - if let Ok(mut file) = File::create(file_path) { - let _ = write!(file, "{}", telnet_host_lines.join("\n")); - } - } - if !winrm_host_lines.is_empty() { - let mut file_path = project.files.clone(); - file_path.push("winrm_hosts.txt"); - if let Ok(mut file) = File::create(file_path) { - let _ = write!(file, "{}", winrm_host_lines.join("\n")); - } - } - if !mysql_host_lines.is_empty() { - let mut file_path = project.files.clone(); - file_path.push("mysql_hosts.txt"); - if let Ok(mut file) = File::create(file_path) { - let _ = write!(file, "{}", mysql_host_lines.join("\n")); - } - } - if !mssql_host_lines.is_empty() { - let mut file_path = project.files.clone(); - file_path.push("mssql_hosts.txt"); - if let Ok(mut file) = File::create(file_path) { - let _ = write!(file, "{}", mssql_host_lines.join("\n")); - } - } - if !snmp_host_lines.is_empty() { - let mut file_path = project.files.clone(); - file_path.push("snmp_hosts.txt"); - if let Ok(mut file) = File::create(file_path) { - let _ = write!(file, "{}", snmp_host_lines.join("\n")); - } - } - if !kerberos_host_lines.is_empty() { - let mut file_path = project.files.clone(); - file_path.push("kerberos_hosts.txt"); - if let Ok(mut file) = File::create(file_path) { - let _ = write!(file, "{}", kerberos_host_lines.join("\n")); - } - } - if !ldap_host_lines.is_empty() { - let mut file_path = project.files.clone(); - file_path.push("ldap_hosts.txt"); - if let Ok(mut file) = File::create(file_path) { - let _ = write!(file, "{}", ldap_host_lines.join("\n")); - } - } - if !smb_host_lines.is_empty() { - let mut file_path = project.files.clone(); - file_path.push("smb_hosts.txt"); - if let Ok(mut file) = File::create(file_path) { - let _ = write!(file, "{}", smb_host_lines.join("\n")); - } - } - if !unknonw_host_lines.is_empty() { - let mut file_path = project.files.clone(); - file_path.push("unknown_hosts.txt"); - if let Ok(mut file) = File::create(file_path) { - let _ = write!(file, "{}", unknonw_host_lines.join("\n")); - } - } - if !ssh_host_lines.is_empty() { - let mut file_path = project.files.clone(); - file_path.push("ssh_hosts.txt"); - if let Ok(mut file) = File::create(file_path) { - let _ = write!(file, "{}", ssh_host_lines.join("\n")); - } - } - } - } - } - }); - project.save_config()?; - } - Ok(()) - } -} - #[derive(Clone)] -struct Prompt { +pub struct Prompt { action: Option, responses: Vec, execute_command: String, diff --git a/src/main.rs b/src/main.rs index 6ef0338..9a8c79b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ use std::{env, path::PathBuf, process::exit}; use sysinfo::*; use tetanus::funcs::*; use tetanus::server::start_server; -use tetanus::{AppState, server}; +use tetanus::{appstate::AppState, server}; mod install;