unimap 0.1.0

Scan only once by IP address and reduce scan times with Nmap for large amounts of data.
Documentation
use {
    crate::{
        args,
        errors::*,
        files, logic, networking,
        nmap::{self, Nmaprun},
        structs::{Args, ResolvData},
    },
    log::{error, info},
    prettytable,
    prettytable::Table,
    rayon::prelude::*,
    std::{
        collections::{HashMap, HashSet},
        net::Ipv4Addr,
        time::Duration,
    },
    trust_dns_resolver::{config::ResolverOpts, proto::rr::RecordType},
};

lazy_static! {
    pub static ref RESOLVERS: Vec<Ipv4Addr> = {
        let args = args::get_args();
        let mut resolver_ips = Vec::new();
        if args.custom_resolvers {
            for r in &files::return_file_targets(&args, args.resolvers.clone()) {
                match r.parse::<Ipv4Addr>() {
                    Ok(ip) => resolver_ips.push(ip),
                    Err(e) => {
                        error!("Error parsing the {} IP from resolvers file to IP address. Please check and try again. Error: {}\n", r, e);
                        std::process::exit(1)
                    }
                }
            }
        } else {
            for r in args.resolvers {
                match r.parse::<Ipv4Addr>() {
                    Ok(ip) => resolver_ips.push(ip),
                    Err(e) => {
                        error!("Error parsing the {} IP from resolvers file to IP address. Please check and try again. Error: {}\n", r, e);
                        std::process::exit(1)
                    }
                }
            }
        }
        resolver_ips
    };
}

pub fn async_resolver_all(args: &mut Args) -> Result<()> {
    if args.with_output && !args.unique_output_flag && !files::check_full_path(&args.logs_dir) {
        error!(
            "Error creating the logs folder in the path {}. Leaving...",
            &args.logs_dir
        );
        std::process::exit(1)
    }

    if !args.quiet_flag {
        info!(
            "Performing asynchronous resolution for {} targets with {} threads, it will take a while...\n",
            args.targets.len(), args.threads
        )
    }

    let data = async_resolver_engine(&args, args.targets.clone());

    let mut table = Table::new();
    table.set_titles(row![
        bcFg => "HOST",
       "IP",
       "OPEN PORTS",
       "SERVICES"
    ]);
    for (target, resolv_data) in &data {
        if !resolv_data.ip.is_empty() {
            let mut services_table = Table::new();
            for port_data in &resolv_data.ports_data {
                services_table
                    .add_row(row![bc => &format!("PORT => {}", port_data.portid.clone())]);
                services_table.add_row(
                    row![c => &format!("SERVICE: {}", port_data.service.clone().unwrap_or_default().name)],
                );
                services_table.add_row(row![c => &format!("VERSION: {}" ,port_data
                .service.clone().unwrap_or_default()
                .version
                .clone()
                .unwrap_or_else(|| "NULL".to_string()))]);
                services_table.add_row(row![c => &format!("PRODUCT: {}", port_data
                    .service.clone().unwrap_or_default()
                    .product
                    .clone()
                    .unwrap_or_else(|| "NULL".to_string()))]);
                services_table.add_row(row![c => &format!("OS TYPE: {}", port_data
                    .service.clone().unwrap_or_default()
                    .ostype
                    .clone()
                    .unwrap_or_else(|| "NULL".to_string()))]);
                services_table.add_row(row![c => &format!("EXTRA INFO: {}", port_data
                    .service.clone().unwrap_or_default()
                    .extrainfo
                    .clone()
                    .unwrap_or_else(|| "NULL".to_string()))]);
            }
            table.add_row(row![ d =>
                target,
                logic::null_ip_checker(&resolv_data.ip),
                logic::return_ports_string(
                    &resolv_data
                        .ports_data
                        .iter()
                        .map(|f| f.portid.clone())
                        .collect(),
                ),
                services_table,
            ]);
        }
    }

    if args.with_output && !args.targets.is_empty() {
        if files::table_to_file(&table, files::return_output_file(&args)).is_err()
            && !args.quiet_flag
        {
            error!(
                "An error occurred while writing the output file {}.\n",
                args.file_name
            )
        }
    }
    if !args.quiet_flag {
        table.printstd();
    }

    if ((args.with_output && !args.unique_output_flag) || args.unique_output_flag)
        && !args.quiet_flag
    {
        info!(
            "Job finished in {} seconds.\n",
            args.time_wasted.elapsed().as_secs()
        );
        info!("Logfile saved in {}\n\n", args.file_name);
    }

    Ok(())
}

fn async_resolver_engine(args: &Args, targets: HashSet<String>) -> HashMap<String, ResolvData> {
    let mut opts = ResolverOpts::default();
    opts.timeout = Duration::from_secs(2);

    let resolv_data: HashMap<String, ResolvData> = targets
        .par_iter()
        .map(|target| {
            let fqdn_target = format!("{}.", target);
            let mut resolv_data = ResolvData::default();
            resolv_data.ip = networking::get_records(
                &networking::get_resolver(&RESOLVERS, &opts),
                &fqdn_target,
                RecordType::A,
            );
            (target.to_owned(), resolv_data)
        })
        .collect();

    let mut nmap_ips: HashSet<String> = resolv_data
        .iter()
        .map(|(_, resolv_data)| resolv_data.ip.clone())
        .collect();

    nmap_ips.retain(|ip| !ip.is_empty());

    let nmap_data: HashMap<String, Nmaprun> = nmap_ips
        .par_iter()
        .map(|ip| {
            let filename = format!("{}.xml", &ip);
            match nmap::get_nmap_data(
                &filename,
                &ip,
                args.min_rate,
                args.initial_port,
                args.last_port,
                args.fast_scan,
            ) {
                Ok(nmap_data) => {
                    nmap_data
                        .host
                        .clone()
                        .unwrap_or_default()
                        .ports
                        .unwrap_or_default()
                        .port
                        .retain(|f| f.state.state == "open");
                    match std::fs::remove_file(&filename) {
                        Ok(_) => (),
                        Err(e) => {
                            error!("Error removing filename {}. Description: {}", &filename, e)
                        }
                    };
                    (ip.clone(), nmap_data)
                }
                Err(e) => {
                    error!("Error scanning the ip {}. Description: {}", &ip, e);
                    (String::new(), Nmaprun::default())
                }
            }
        })
        .collect();

    resolv_data
        .iter()
        .map(|(target, resolv_data)| {
            (
                target.clone(),
                ResolvData {
                    ip: resolv_data.ip.clone(),
                    ports_data: if resolv_data.ip.is_empty() {
                        resolv_data.ports_data.clone()
                    } else {
                        nmap_data
                            .get_key_value(&resolv_data.ip)
                            .unwrap()
                            .1
                            .host
                            .clone()
                            .unwrap_or_default()
                            .ports
                            .unwrap_or_default()
                            .port
                    },
                },
            )
        })
        .collect()
}