135 lines
3.7 KiB
Rust
135 lines
3.7 KiB
Rust
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<Arc<AtomicBool>> = std::sync::OnceLock::new();
|
|
|
|
fn get_nmap_cancel() -> Arc<AtomicBool> {
|
|
NMAP_CANCEL
|
|
.get_or_init(|| Arc::new(AtomicBool::new(false)))
|
|
.clone()
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct NmapConfig {
|
|
pub target: String,
|
|
pub args: Vec<String>,
|
|
}
|
|
|
|
#[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(())
|
|
}
|