wifiscan 0.4.0

Wireless network scanner TUI with monitor mode, handshake capture, deauth, and evil twin
Documentation
use std::time::Duration;

pub fn is_monitor_mode(iface: &str) -> bool {
    let output = std::process::Command::new("iw")
        .args(["dev", iface, "info"])
        .output();
    match output {
        Ok(o) => {
            let s = String::from_utf8_lossy(&o.stdout);
            s.contains("type monitor")
        }
        Err(_) => false,
    }
}

pub fn get_iface_type(iface: &str) -> String {
    let output = std::process::Command::new("iw")
        .args(["dev", iface, "info"])
        .output();
    match output {
        Ok(o) => {
            let s = String::from_utf8_lossy(&o.stdout);
            for line in s.lines() {
                let trimmed = line.trim();
                if trimmed.starts_with("type ") {
                    return trimmed.strip_prefix("type ").unwrap_or("unknown").to_string();
                }
            }
            "unknown".to_string()
        }
        Err(_) => "error".to_string(),
    }
}

pub fn is_root() -> bool {
    unsafe { libc::geteuid() == 0 }
}

fn kill_interfering_processes() {
    let _ = std::process::Command::new("airmon-ng")
        .args(["check", "kill"])
        .output();
    for proc in &["wpa_supplicant", "NetworkManager", "dhclient"] {
        let _ = std::process::Command::new("pkill")
            .args(["-f", proc])
            .output();
    }
    std::thread::sleep(Duration::from_millis(500));
}

pub fn enable_monitor_mode(iface: &str) -> Result<String, String> {
    if is_monitor_mode(iface) {
        return Ok(iface.to_string());
    }

    eprintln!("[*] Killing interfering processes...");
    kill_interfering_processes();

    // Method 1: iw (modern, preferred)
    eprintln!("[*] Setting {} down...", iface);
    let down = std::process::Command::new("ip")
        .args(["link", "set", iface, "down"])
        .output()
        .map_err(|e| format!("Failed to run ip: {}", e))?;
    if !down.status.success() {
        return Err(format!(
            "Failed to bring {} down: {}",
            iface,
            String::from_utf8_lossy(&down.stderr)
        ));
    }

    eprintln!("[*] Setting monitor mode via iw...");
    let monitor = std::process::Command::new("iw")
        .args(["dev", iface, "set", "type", "monitor"])
        .output()
        .map_err(|e| format!("Failed to run iw: {}", e))?;

    if monitor.status.success() {
        eprintln!("[*] Bringing {} up...", iface);
        let up = std::process::Command::new("ip")
            .args(["link", "set", iface, "up"])
            .output()
            .map_err(|e| format!("Failed to run ip: {}", e))?;
        if !up.status.success() {
            return Err(format!(
                "Failed to bring {} up: {}",
                iface,
                String::from_utf8_lossy(&up.stderr)
            ));
        }

        std::thread::sleep(Duration::from_millis(500));

        if is_monitor_mode(iface) {
            return Ok(iface.to_string());
        }
    }

    // Method 2: airmon-ng
    eprintln!("[*] iw method failed, trying airmon-ng...");
    let _ = std::process::Command::new("ip")
        .args(["link", "set", iface, "up"])
        .output();

    let airmon = std::process::Command::new("airmon-ng")
        .args(["start", iface])
        .output();

    match airmon {
        Ok(o) if o.status.success() => {
            let s = String::from_utf8_lossy(&o.stdout);
            let mon_name = format!("{}mon", iface);
            if is_monitor_mode(&mon_name) {
                return Ok(mon_name);
            }
            if is_monitor_mode(iface) {
                return Ok(iface.to_string());
            }
            for line in s.lines() {
                if line.contains("monitor mode") || line.contains("enabled") {
                    if let Some(start) = line.find('(') {
                        if let Some(end) = line.find(')') {
                            let candidate = &line[start + 1..end];
                            if candidate.contains("mon") && is_monitor_mode(candidate) {
                                return Ok(candidate.to_string());
                            }
                        }
                    }
                }
            }
            Err("airmon-ng ran but monitor mode not confirmed".to_string())
        }
        _ => Err(format!(
            "Could not enable monitor mode on {}. Check that your adapter supports monitor mode.",
            iface
        )),
    }
}

pub fn disable_monitor_mode(iface: &str) {
    let _ = std::process::Command::new("ip")
        .args(["link", "set", iface, "down"])
        .output();
    let _ = std::process::Command::new("iw")
        .args(["dev", iface, "set", "type", "managed"])
        .output();
    let _ = std::process::Command::new("ip")
        .args(["link", "set", iface, "up"])
        .output();
    let _ = std::process::Command::new("systemctl")
        .args(["start", "NetworkManager"])
        .output();
}