use std::io;
use std::process::{Command, ExitStatus};
pub const DEFAULT_NMAP_ARGS: &[&str] = &["-sV", "-sC"];
pub fn nmap_args(host: &str, ports: &[u16], extra: &[String]) -> Vec<String> {
let mut args: Vec<String> = if extra.is_empty() {
DEFAULT_NMAP_ARGS.iter().map(|s| s.to_string()).collect()
} else {
extra.to_vec()
};
let port_list = ports
.iter()
.map(|p| p.to_string())
.collect::<Vec<_>>()
.join(",");
args.push("-p".to_string());
args.push(port_list);
args.push(host.to_string());
args
}
pub fn split_extra_args(raw: &str) -> Vec<String> {
raw.split_whitespace().map(|s| s.to_string()).collect()
}
pub fn run_nmap(host: &str, ports: &[u16], extra: &[String]) -> io::Result<ExitStatus> {
Command::new("nmap")
.args(nmap_args(host, ports, extra))
.status()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_are_used_when_no_extra_args() {
let args = nmap_args("scanme.nmap.org", &[22, 80], &[]);
assert_eq!(
args,
vec!["-sV", "-sC", "-p", "22,80", "scanme.nmap.org"]
.into_iter()
.map(String::from)
.collect::<Vec<_>>()
);
}
#[test]
fn extra_args_replace_defaults_but_ports_and_host_are_appended() {
let extra = split_extra_args("-A -T4");
let args = nmap_args("10.0.0.1", &[443], &extra);
assert_eq!(
args,
vec!["-A", "-T4", "-p", "443", "10.0.0.1"]
.into_iter()
.map(String::from)
.collect::<Vec<_>>()
);
}
#[test]
fn ports_are_joined_in_order() {
let args = nmap_args("host", &[80, 22, 8080], &[]);
let idx = args.iter().position(|a| a == "-p").unwrap();
assert_eq!(args[idx + 1], "80,22,8080");
}
#[test]
fn split_extra_args_handles_blank_and_whitespace() {
assert!(split_extra_args("").is_empty());
assert!(split_extra_args(" ").is_empty());
assert_eq!(split_extra_args(" -sS -Pn "), vec!["-sS", "-Pn"]);
}
}