diff --git a/Cargo.toml b/Cargo.toml index 50ed082..5b3902b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,7 +64,7 @@ rlimit = "0.11" # Bluetooth -btleplug = "0.12" +btleplug = { version = "0.12", optional = true } # WPair migrated from ratatui+crossterm TUI to rustyline REPL — deps removed. @@ -163,3 +163,7 @@ strip = true inherits = "release" lto = "thin" # Faster than fat LTO codegen-units = 4 # Parallel codegen + +[features] +default = ["bluetooth"] +bluetooth = ["dep:btleplug"] diff --git a/build.rs b/build.rs index 72e88eb..2b6091e 100644 --- a/build.rs +++ b/build.rs @@ -16,6 +16,9 @@ fn main() { return; } + // Check which features are enabled + let features = Features::detect(); + // Discover categories dynamically from subdirectories of src/modules/ let mut categories: Vec = Vec::new(); let entries = match fs::read_dir(modules_root) { @@ -31,6 +34,11 @@ fn main() { if path.is_dir() { if let Some(name) = path.file_name().and_then(|n| n.to_str()) { if !name.starts_with('.') { + // Skip categories that are entirely disabled + if features.should_skip_category(name) { + println!("cargo:warning=Skipping category '{}' (feature disabled)", name); + continue; + } categories.push(name.to_string()); } } @@ -42,6 +50,9 @@ fn main() { for cat in &categories { println!("cargo:rerun-if-changed=src/modules/{}", cat); } + + // Also rerun if features change + println!("cargo:rerun-if-env-changed=CARGO_FEATURE_BLUETOOTH"); // Compile regexes once, reuse across all categories. let run_re = Regex::new(r"pub\s+async\s+fn\s+run\s*\(\s*[^)]*:\s*&str\s*\)") @@ -60,7 +71,7 @@ fn main() { let out_file = format!("{}_dispatch.rs", dispatch_name(cat)); let display_name = capitalize(cat); - match generate_dispatch(&root, &out_file, &mod_prefix, &display_name, &run_re, &info_re, &check_re) { + match generate_dispatch(&root, &out_file, &mod_prefix, &display_name, &run_re, &info_re, &check_re, &features) { Ok(_module_count) => { registry_entries.push(RegistryEntry { category: cat.clone(), @@ -81,6 +92,40 @@ fn main() { } } +/// Feature detection and module filtering +struct Features { + bluetooth: bool, +} + +impl Features { + fn detect() -> Self { + Self { + bluetooth: env::var("CARGO_FEATURE_BLUETOOTH").is_ok(), + } + } + + /// Check if a category should be entirely skipped + fn should_skip_category(&self, category: &str) -> bool { + match category { + "bluetooth" => !self.bluetooth, + _ => false, + } + } + + /// Check if a module path should be skipped (for feature-gated submodules) + fn should_skip_module(&self, module_path: &str) -> bool { + // If bluetooth feature is disabled, skip any module under bluetooth/ + if !self.bluetooth && module_path.starts_with("bluetooth/") { + return true; + } + + // Add more feature checks here as needed + // Example: if !self.some_feature && module_path.starts_with("some/path/") + + false + } +} + struct RegistryEntry { category: String, dispatch_name: String, @@ -118,6 +163,7 @@ fn generate_dispatch( run_re: &Regex, info_re: &Regex, check_re: &Regex, + features: &Features, ) -> Result> { let out_dir = env::var("OUT_DIR").map_err(|_| "OUT_DIR environment variable not set")?; let dest_path = Path::new(&out_dir).join(out_file); @@ -127,7 +173,7 @@ fn generate_dispatch( return Err(format!("Module directory '{}' does not exist", root).into()); } - let mappings = find_modules(root_path, run_re, info_re, check_re)?; + let mappings = find_modules(root_path, run_re, info_re, check_re, features)?; // Sort for deterministic output let mut sorted_mappings: Vec<_> = mappings.into_iter().collect(); @@ -411,6 +457,7 @@ fn find_modules( run_re: &Regex, info_re: &Regex, check_re: &Regex, + features: &Features, ) -> Result, Box> { let mut mappings = HashSet::new(); @@ -422,6 +469,12 @@ fn find_modules( if let Ok(relative) = path.strip_prefix(root) { let rel_str = relative.with_extension("").to_string_lossy().replace("\\", "/"); + + // Skip modules that are feature-gated out + if features.should_skip_module(&rel_str) { + println!("cargo:warning=Skipping module '{}' (feature disabled)", rel_str); + continue; + } let mut content = String::new(); if File::open(path).and_then(|mut f| f.read_to_string(&mut content)).is_ok() { diff --git a/src/mcp/server.rs b/src/mcp/server.rs index 6b921f5..8e0d3d5 100644 --- a/src/mcp/server.rs +++ b/src/mcp/server.rs @@ -7,23 +7,38 @@ use super::types::{ ServerInfo, ToolsCapability, }; +// You can place this function in src/mcp/server.rs or a shared utility module +fn errno() -> i32 { + unsafe { + #[cfg(any(target_os = "freebsd", target_os = "macos"))] + { *libc::__error() } + + #[cfg(target_os = "linux")] + { *libc::__errno_location() } + + // Add fallbacks for other OSes if needed + #[cfg(not(any(target_os = "freebsd", target_os = "macos", target_os = "linux")))] + { 0 } // Or compile_error! to force checking for a new OS + } +} + fn isolate_protocol_stdout() -> anyhow::Result { use std::os::fd::FromRawFd; unsafe { let saved_fd = libc::dup(1); if saved_fd < 0 { - anyhow::bail!("dup(1) failed: errno {}", *libc::__errno_location()); + anyhow::bail!("dup(1) failed: errno {}", errno()); } let null_path = b"/dev/null\0"; let null_fd = libc::open(null_path.as_ptr() as *const libc::c_char, libc::O_WRONLY); if null_fd < 0 { libc::close(saved_fd); - anyhow::bail!("open(/dev/null) failed: errno {}", *libc::__errno_location()); + anyhow::bail!("open(/dev/null) failed: errno {}", errno()); } if libc::dup2(null_fd, 1) < 0 { libc::close(null_fd); libc::close(saved_fd); - anyhow::bail!("dup2(null, 1) failed: errno {}", *libc::__errno_location()); + anyhow::bail!("dup2(null, 1) failed: errno {}", errno()); } libc::close(null_fd); let std_file = std::fs::File::from_raw_fd(saved_fd); diff --git a/src/modules/exploits/dos/null_syn_exhaustion.rs b/src/modules/exploits/dos/null_syn_exhaustion.rs index b136b81..c1e8dae 100644 --- a/src/modules/exploits/dos/null_syn_exhaustion.rs +++ b/src/modules/exploits/dos/null_syn_exhaustion.rs @@ -318,7 +318,7 @@ fn sendmmsg_batch( msg }).collect(); - let ret = unsafe { libc::sendmmsg(fd, msgs.as_mut_ptr(), count as u32, 0) }; + let ret = unsafe { libc::sendmmsg(fd, msgs.as_mut_ptr(), (count as u32).try_into().unwrap(), 0) }; if ret < 0 { let errno = std::io::Error::last_os_error().raw_os_error().unwrap_or(0); (0, Some(errno)) diff --git a/src/modules/exploits/mod.rs b/src/modules/exploits/mod.rs index 5b46100..88807e6 100644 --- a/src/modules/exploits/mod.rs +++ b/src/modules/exploits/mod.rs @@ -1,4 +1,3 @@ -pub mod bluetooth; pub mod cameras; pub mod cowrie; pub mod crypto; @@ -20,3 +19,6 @@ pub mod vnc; pub mod voip; pub mod webapps; pub mod windows; + +#[cfg(feature = "bluetooth")] +pub mod bluetooth; diff --git a/src/modules/exploits/routers/palo_alto/panos_authbypass_cve_2025_0108.rs b/src/modules/exploits/routers/palo_alto/panos_authbypass_cve_2025_0108.rs index 4c29cfa..4f570ad 100644 --- a/src/modules/exploits/routers/palo_alto/panos_authbypass_cve_2025_0108.rs +++ b/src/modules/exploits/routers/palo_alto/panos_authbypass_cve_2025_0108.rs @@ -33,7 +33,6 @@ use reqwest::Client; use std::{ fs::File, io::{BufRead, BufReader}, - process::Command, time::Duration, }; use url::Url;