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\nexport nessus csv report, only checking the host, protocol, and port boxes.\n".to_string()); self.help.push( "Shortcuts:\nCTRL+P = new_project\nCTRL+s = save\nCTRL_+h = select_host\n".to_string(), ); self.help.push("exit\nquit the tool\n".to_string()); self.initialize_modules(); return Ok(()); } 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(cmd) = self.config.get("term_cmd") { if let Some(db) = project.db { db.launch_terminal(cmd.to_string()); } else { if let Some((prog, _)) = cmd.split_once(" ") { let mut launch_cmd = Command::new(prog); match launch_cmd.spawn() { Ok(_) => {} Err(e) => { let _ = self.main_tx.send(ToolMessage::Output(( rid, format!("[error] launching terminal: {e}"), ))); } } } } } } "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() { if let Some((setting, data)) = args.split_once(" ") { key = setting.to_string(); value = data.to_string(); if self.config.contains_key(&key) { ready = true; } else { 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 p.contains("-") { let (first, last) = p.split_once("-").unwrap(); if let Ok(start) = first.parse::() { if let Ok(end) = last.parse::() { ports.extend(start..=end); } } } else if let Ok(port) = p.trim().parse::() { ports.push(port); } }); } 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 progress_bar = Arc::new(RwLock::new(ToolProgress { name: format!("portscan {} - {}", project.org_name, project.name), total: hosts.len() * ports.len(), complete: 0, finished: false, })); self.progress_bars.push(progress_bar.clone()); let project_clone = Arc::new(Mutex::new(project.clone())); let id = self.selected_project.clone(); let tx_clone = self.main_tx.clone(); let pb_clone = progress_bar.clone(); self.workers.spawn(move || { hosts.par_iter_mut().for_each(|h| { let rt = tokio::runtime::Runtime::new().unwrap(); let host = rt.block_on(h.portscan( ports.clone(), tx_clone.clone(), pb_clone.clone(), )); if let Ok(mut lock) = project_clone.lock() { if !lock.hosts.iter().any(|ph| ph.ip == host.ip) { lock.hosts.push(host); } else { lock.hosts .iter_mut() .filter(|ph| ph.ip == host.ip) .for_each(|ph| { host.open_ports.iter().for_each(|p| { if !ph.open_ports.contains(p) { ph.open_ports.push(p.clone()); } }); }); } } }); if let Ok(mut lock) = pb_clone.write() { lock.finished = true; } if let Ok(lock) = project_clone.lock() { let _ = tx_clone.send(ToolMessage::UpdateProject(id, lock.clone())); 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 mut db = None; if template_box != &"NONE".to_string() { db = Some(DistroBox { name: format!("{}-{}-{}", template_box, org, name), volumes: vec![ project_files.display().to_string(), project_notes.display().to_string(), tools.to_string(), ], created: false, template: template_box.to_string(), }); }; let mut new_project = Project { org_name: org, name: name, notes: project_notes, files: project_files, hosts: Vec::new(), config_folder: project_config_file, current: false, db: 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(()) } pub fn run_cmd(&mut self, cmd: String) { if let Some(db) = self.projects[self.selected_project].db.clone() { db.run_command(&cmd); } else { if let Some(term_cmd) = self.config.get("term_cmd") { if let Some((prog, args)) = term_cmd.split_once(" ") { let mut launch_cmd = Command::new(prog); args.split(" ").into_iter().for_each(|arg| { launch_cmd.arg(arg); }); launch_cmd.arg(&cmd); match launch_cmd.spawn() { Ok(_) => { let _ = self.main_tx.send(ToolMessage::Output(( 0, format!("[success] running {}", cmd), ))); } Err(e) => { let _ = self.main_tx.send(ToolMessage::Output(( 0, format!("[error] running {}, {e}", cmd), ))); } } } } } } }