unimap 0.1.0

Scan only once by IP address and reduce scan times with Nmap for large amounts of data.
Documentation
use {
    crate::resolver_engine,
    log::error,
    std::{path::Path, process::Command},
};

lazy_static! {
    static ref NMAP_DNS_RESOLVERS: String = resolver_engine::RESOLVERS
        .clone()
        .iter()
        .map(|f| f.to_string())
        .collect::<Vec<String>>()
        .join(",");
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Root {
    pub nmaprun: Nmaprun,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Nmaprun {
    pub scanner: String,
    pub args: String,
    pub start: String,
    pub startstr: String,
    pub version: String,
    pub xmloutputversion: String,
    pub host: Option<Host>,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Host {
    pub starttime: String,
    pub endtime: String,
    pub status: Status,
    pub address: Address,
    pub hostnames: Hostnames,
    pub ports: Option<Ports>,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Status {
    pub state: String,
    pub reason: String,
    #[serde(rename = "reason_ttl")]
    pub reason_ttl: String,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Address {
    pub addr: Option<String>,
    pub addrtype: Option<String>,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Hostnames {
    pub hostname: Option<Hostname>,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Hostname {
    pub name: String,
    #[serde(rename = "type")]
    pub type_field: String,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Ports {
    pub port: Vec<Port>,
}

#[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Hash)]
#[serde(rename_all = "camelCase")]
pub struct Port {
    pub protocol: String,
    pub portid: String,
    pub state: State,
    pub service: Option<Service>,
}

#[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Hash)]
#[serde(rename_all = "camelCase")]
pub struct State {
    pub state: String,
    pub reason: String,
    #[serde(rename = "reason_ttl")]
    pub reason_ttl: String,
}

#[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Hash)]
#[serde(rename_all = "camelCase")]
pub struct Service {
    pub name: String,
    pub method: String,
    pub conf: String,
    pub product: Option<String>,
    pub ostype: Option<String>,
    pub version: Option<String>,
    pub extrainfo: Option<String>,
}

pub fn get_nmap_data(
    filename: &str,
    host: &str,
    min_rate: usize,
    initial_port: usize,
    last_port: usize,
    fast_scan: bool,
) -> Result<Nmaprun, serde_xml_rs::Error> {
    let _ports_range = format!("{}-{}", initial_port, last_port);
    let min_rate = min_rate.to_string();
    let nmap_args = if fast_scan {
        vec![
            "nmap",
            "-n",
            "--dns-servers",
            &NMAP_DNS_RESOLVERS,
            "-Pn",
            "--host-timeout",
            "5m",
            "--min-rate",
            &min_rate,
            "-sS",
            "--open",
            "-dd",
            "-T4",
            "--max-retries",
            "2",
            "-oX",
            filename,
            host,
        ]
    } else {
        vec![
            "nmap",
            "-n",
            "--dns-servers",
            &NMAP_DNS_RESOLVERS,
            "-Pn",
            "--host-timeout",
            "10m",
            "-sV",
            "--min-rate",
            &min_rate,
            "-sS",
            "-p-",
            "--open",
            "-dd",
            "-T4",
            "--max-retries",
            "2",
            "-oX",
            filename,
            host,
        ]
    };
    match Command::new("sudo").args(&nmap_args).output() {
        Ok(_) => {
            if Path::new(&filename).exists() && Path::new(&filename).is_file() {
                serde_xml_rs::from_str(&std::fs::read_to_string(filename).unwrap_or_default())
            } else {
                error!("Error executing nmap, you need root permissions. Leaving.\n");
                std::process::exit(1)
            }
        }
        Err(e) => {
            error!(
                    "Error waiting command to finish for {}, continuing with remaining hosts. Description: {}",
                    host, e
                );
            Ok(Nmaprun::default())
        }
    }
}