use std::process::Stdio; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use serde::Deserialize; use tauri::{AppHandle, Emitter}; use tokio::io::{AsyncBufReadExt, BufReader}; use tokio::process::Command; static NMAP_CANCEL: std::sync::OnceLock> = std::sync::OnceLock::new(); fn get_nmap_cancel() -> Arc { NMAP_CANCEL .get_or_init(|| Arc::new(AtomicBool::new(false))) .clone() } #[derive(Debug, Deserialize)] pub struct NmapConfig { pub target: String, pub args: Vec, } #[tauri::command] pub async fn run_nmap(app: AppHandle, config: NmapConfig) -> Result<(), String> { // Check nmap exists let check = Command::new("nmap") .arg("--version") .output() .await .map_err(|_| "nmap not found. Please install nmap.".to_string())?; if !check.status.success() { return Err("nmap not found. Please install nmap.".to_string()); } let cancel = get_nmap_cancel(); cancel.store(false, Ordering::Relaxed); let _ = app.emit("nmap:start", &config.target); let mut cmd = Command::new("nmap"); for arg in &config.args { cmd.arg(arg); } for target in config.target.split_whitespace() { cmd.arg(target); } cmd.stdout(Stdio::piped()); cmd.stderr(Stdio::piped()); #[cfg(target_os = "windows")] { use std::os::windows::process::CommandExt; const CREATE_NO_WINDOW: u32 = 0x08000000; cmd.creation_flags(CREATE_NO_WINDOW); } let mut child = cmd .spawn() .map_err(|e| format!("Failed to start nmap: {}", e))?; let stdout = child .stdout .take() .ok_or_else(|| "Failed to capture nmap stdout".to_string())?; let stderr = child .stderr .take() .ok_or_else(|| "Failed to capture nmap stderr".to_string())?; // Stream stdout let app2 = app.clone(); let cancel2 = cancel.clone(); let stdout_handle = tokio::spawn(async move { let mut reader = BufReader::new(stdout).lines(); while let Ok(Some(line)) = reader.next_line().await { if cancel2.load(Ordering::Relaxed) { break; } let _ = app2.emit("nmap:line", &line); } }); // Stream stderr let app3 = app.clone(); let stderr_handle = tokio::spawn(async move { let mut reader = BufReader::new(stderr).lines(); while let Ok(Some(line)) = reader.next_line().await { let _ = app3.emit("nmap:line", &line); } }); // Wait for completion or cancellation tokio::select! { status = child.wait() => { match status { Ok(s) if s.success() => { let _ = app.emit("nmap:complete", "Scan complete"); } Ok(s) => { let _ = app.emit("nmap:complete", format!("nmap exited with code {}", s.code().unwrap_or(-1))); } Err(e) => { let _ = app.emit("nmap:error", format!("nmap error: {}", e)); } } } _ = async { loop { tokio::time::sleep(std::time::Duration::from_millis(100)).await; if cancel.load(Ordering::Relaxed) { break; } } } => { let _ = child.kill().await; let _ = app.emit("nmap:complete", "Scan cancelled"); } } let _ = stdout_handle.await; let _ = stderr_handle.await; Ok(()) } #[tauri::command] pub async fn stop_nmap() -> Result<(), String> { let cancel = get_nmap_cancel(); cancel.store(true, Ordering::Relaxed); tracing::info!("Nmap scan cancelled"); Ok(()) }