mirror of
https://github.com/edu4rdshl/unimap.git
synced 2026-07-17 23:24:49 +00:00
(feat): update from old clap definition
This commit is contained in:
parent
f5049e1074
commit
cba63c2a04
7 changed files with 173 additions and 243 deletions
206
src/args.rs
206
src/args.rs
|
|
@ -1,73 +1,167 @@
|
|||
use {
|
||||
crate::{
|
||||
defaults,
|
||||
logic::validate_target,
|
||||
misc::{return_matches_vec, sanitize_target_string},
|
||||
structs::Args,
|
||||
},
|
||||
crate::{defaults, logic::validate_target, misc::sanitize_target_string},
|
||||
chrono::Utc,
|
||||
clap::{load_yaml, value_t, App},
|
||||
clap::Parser,
|
||||
std::{collections::HashSet, time::Instant},
|
||||
};
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
#[must_use]
|
||||
pub fn get_args() -> Args {
|
||||
let yaml = load_yaml!("cli.yml");
|
||||
let matches = App::from_yaml(yaml)
|
||||
.version(clap::crate_version!())
|
||||
.get_matches();
|
||||
/// Scan only once by IP address and reduce scan times with Nmap for large amounts of data.
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
#[command(author = "Eduard Tolosa <edu4rdshl@protonmail.com>", version, about, long_about = None)]
|
||||
#[command(arg_required_else_help = true)]
|
||||
pub struct Args {
|
||||
/// Target host
|
||||
#[arg(short, long, conflicts_with_all = ["files", "stdin"])]
|
||||
pub target: Option<String>,
|
||||
|
||||
Args {
|
||||
target: {
|
||||
let target = sanitize_target_string(
|
||||
value_t!(matches, "target", String).unwrap_or_else(|_| String::new()),
|
||||
);
|
||||
if validate_target(&target) {
|
||||
target
|
||||
/// Use a list of targets written in a file as input
|
||||
#[arg(short, long, conflicts_with_all = ["target", "stdin"])]
|
||||
pub files: Vec<String>,
|
||||
|
||||
/// Write to an output file. The name of the output file will be unimap-log-date
|
||||
#[arg(short, long, conflicts_with = "unique_output")]
|
||||
pub output: bool,
|
||||
|
||||
/// Write the output in CSV format to the specified filename
|
||||
#[arg(short, long, conflicts_with = "output")]
|
||||
pub unique_output: Option<String>,
|
||||
|
||||
/// Remove informative messages
|
||||
#[arg(short, long)]
|
||||
pub quiet: bool,
|
||||
|
||||
/// Number of threads to use to perform the resolution
|
||||
#[arg(long)]
|
||||
pub threads: Option<usize>,
|
||||
|
||||
/// Path to a file (or files) containing a list of DNS IP address. If no specified then a list of built-in DNS servers is used
|
||||
#[arg(long = "resolvers")]
|
||||
pub custom_resolvers: Vec<String>,
|
||||
|
||||
/// Ports to scan. You can specify a range of ports, a list, or both. Put them inside double quotes, for example: "22, 80, 443, 1000-5000"
|
||||
#[arg(long)]
|
||||
pub ports: Option<String>,
|
||||
|
||||
/// Nmap --min-rate value for ports scan
|
||||
#[arg(long)]
|
||||
pub min_rate: Option<String>,
|
||||
|
||||
/// Use fast scanning for ports (no version detection)
|
||||
#[arg(long)]
|
||||
pub fast_scan: bool,
|
||||
|
||||
/// Path to save the CSV data of the process and/or Nmap XML files. Default to logs/
|
||||
#[arg(long, default_value = "unimap_logs")]
|
||||
pub logs_dir: String,
|
||||
|
||||
/// Keep Nmap XML files created in the logs/ folder for every scanned IP. This data will be useful for other tasks
|
||||
#[arg(short = 'k', long)]
|
||||
pub no_keep_nmap_logs: bool,
|
||||
|
||||
/// Use raw output instead of a table
|
||||
#[arg(short, long, conflicts_with = "url_output")]
|
||||
pub raw_output: bool,
|
||||
|
||||
/// Use HOST:IP output format
|
||||
#[arg(long, conflicts_with = "raw_output")]
|
||||
pub url_output: bool,
|
||||
|
||||
/// Read from stdin instead of files or arguments
|
||||
#[arg(long, conflicts_with_all = ["files", "target"])]
|
||||
pub stdin: bool,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
/// Create the processed args with computed fields
|
||||
#[must_use]
|
||||
pub fn into_processed_args(self) -> ProcessedArgs {
|
||||
let target = self.target.map_or_else(String::new, |target| {
|
||||
let sanitized = sanitize_target_string(target);
|
||||
if validate_target(&sanitized) {
|
||||
sanitized
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
},
|
||||
file_name: if matches.is_present("output") {
|
||||
value_t!(matches, "logs-dir", String).unwrap_or_else(|_| "unimap_logs".to_string())
|
||||
+ "/"
|
||||
+ "unimap"
|
||||
+ &Utc::now().format("-log-%Y-%m-%d_%H-%M-%S").to_string()
|
||||
+ ".csv"
|
||||
} else if matches.is_present("unique-output") {
|
||||
matches.value_of("unique-output").unwrap().to_string()
|
||||
});
|
||||
|
||||
let file_name = if self.output {
|
||||
format!(
|
||||
"{}/unimap{}",
|
||||
self.logs_dir,
|
||||
Utc::now().format("-log-%Y-%m-%d_%H-%M-%S")
|
||||
) + ".csv"
|
||||
} else if let Some(unique_output) = &self.unique_output {
|
||||
unique_output.clone()
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
logs_dir: value_t!(matches, "logs-dir", String)
|
||||
.unwrap_or_else(|_| "unimap_logs".to_string()),
|
||||
threads: if matches.is_present("ports") && !matches.is_present("threads") {
|
||||
};
|
||||
|
||||
let threads = if self.ports.is_some() && self.threads.is_none() {
|
||||
30
|
||||
} else {
|
||||
value_t!(matches, "threads", usize).unwrap_or_else(|_| 50)
|
||||
},
|
||||
version: clap::crate_version!().to_string(),
|
||||
ports: value_t!(matches, "ports", String).unwrap_or_else(|_| String::new()),
|
||||
with_output: matches.is_present("output") || matches.is_present("unique-output"),
|
||||
unique_output_flag: matches.is_present("unique-output"),
|
||||
from_file_flag: matches.is_present("files"),
|
||||
quiet_flag: matches.is_present("quiet"),
|
||||
custom_resolvers: matches.is_present("custom-resolvers"),
|
||||
custom_ports_range: matches.is_present("ports"),
|
||||
fast_scan: matches.is_present("fast-scan"),
|
||||
no_keep_nmap_logs: matches.is_present("no-keep-nmap-logs"),
|
||||
raw_output: matches.is_present("raw-output"),
|
||||
url_output: matches.is_present("url-output"),
|
||||
from_stdin: matches.is_present("stdin"),
|
||||
files: return_matches_vec(&matches, "files"),
|
||||
min_rate: value_t!(matches, "min-rate", String).unwrap_or_else(|_| String::new()),
|
||||
resolvers: if matches.is_present("custom-resolvers") {
|
||||
return_matches_vec(&matches, "custom-resolvers")
|
||||
self.threads.unwrap_or(50)
|
||||
};
|
||||
|
||||
let custom_resolvers_flag = !self.custom_resolvers.is_empty();
|
||||
let custom_ports_range = self.ports.is_some();
|
||||
let ports_value = self.ports.unwrap_or_default();
|
||||
|
||||
let resolvers = if custom_resolvers_flag {
|
||||
self.custom_resolvers
|
||||
} else {
|
||||
defaults::ipv4_resolvers()
|
||||
},
|
||||
targets: HashSet::new(),
|
||||
time_wasted: Instant::now(),
|
||||
};
|
||||
|
||||
ProcessedArgs {
|
||||
target,
|
||||
file_name,
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
logs_dir: self.logs_dir,
|
||||
threads,
|
||||
ports: ports_value,
|
||||
with_output: self.output || self.unique_output.is_some(),
|
||||
unique_output_flag: self.unique_output.is_some(),
|
||||
min_rate: self.min_rate.unwrap_or_default(),
|
||||
from_file_flag: !self.files.is_empty(),
|
||||
quiet_flag: self.quiet,
|
||||
custom_resolvers: custom_resolvers_flag,
|
||||
custom_ports_range,
|
||||
no_keep_nmap_logs: self.no_keep_nmap_logs,
|
||||
raw_output: self.raw_output,
|
||||
fast_scan: self.fast_scan,
|
||||
url_output: self.url_output,
|
||||
from_stdin: self.stdin,
|
||||
files: self.files,
|
||||
resolvers,
|
||||
targets: HashSet::new(),
|
||||
time_wasted: Instant::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Processed args with computed fields and flags
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ProcessedArgs {
|
||||
pub target: String,
|
||||
pub file_name: String,
|
||||
pub version: String,
|
||||
pub logs_dir: String,
|
||||
pub threads: usize,
|
||||
pub ports: String,
|
||||
pub with_output: bool,
|
||||
pub unique_output_flag: bool,
|
||||
pub min_rate: String,
|
||||
pub from_file_flag: bool,
|
||||
pub quiet_flag: bool,
|
||||
pub custom_resolvers: bool,
|
||||
pub custom_ports_range: bool,
|
||||
pub no_keep_nmap_logs: bool,
|
||||
pub raw_output: bool,
|
||||
pub fast_scan: bool,
|
||||
pub url_output: bool,
|
||||
pub from_stdin: bool,
|
||||
pub files: Vec<String>,
|
||||
pub resolvers: Vec<String>,
|
||||
pub targets: HashSet<String>,
|
||||
pub time_wasted: Instant,
|
||||
}
|
||||
|
|
|
|||
113
src/cli.yml
113
src/cli.yml
|
|
@ -1,113 +0,0 @@
|
|||
name: Unimap
|
||||
author: Eduard Tolosa <edu4rdshl@protonmail.com>
|
||||
settings:
|
||||
- ArgRequiredElseHelp
|
||||
- StrictUtf8
|
||||
about: Scan only once by IP address and reduce scan times with Nmap for large amounts of data.
|
||||
args:
|
||||
- target:
|
||||
short: t
|
||||
long: target
|
||||
help: Target host.
|
||||
takes_value: true
|
||||
multiple: false
|
||||
conflicts_with:
|
||||
- files
|
||||
- stdin
|
||||
|
||||
- files:
|
||||
short: f
|
||||
long: file
|
||||
help: Use a list of targets writen in a file as input.
|
||||
takes_value: true
|
||||
multiple: true
|
||||
conflicts_with:
|
||||
- target
|
||||
- stdin
|
||||
|
||||
- output:
|
||||
short: o
|
||||
long: output
|
||||
help: Write to an output file. The name of the output file will be unimap-log-date.
|
||||
takes_value: false
|
||||
|
||||
- unique-output:
|
||||
short: u
|
||||
long: unique-output
|
||||
help: Write the output in CSV format to the specified filename.
|
||||
takes_value: true
|
||||
multiple: false
|
||||
conflicts_with:
|
||||
- output
|
||||
|
||||
- quiet:
|
||||
short: q
|
||||
long: quiet
|
||||
help: Remove informative messages.
|
||||
takes_value: false
|
||||
|
||||
- threads:
|
||||
help: Number of threads to use to perform the resolution.
|
||||
long: threads
|
||||
takes_value: true
|
||||
|
||||
- custom-resolvers:
|
||||
help: Path to a file (or files) containing a list of DNS IP address. If no specified then 1.6k of built-in valid DNS servers from public-dns.info are used.
|
||||
long: resolvers
|
||||
takes_value: true
|
||||
multiple: true
|
||||
|
||||
- ports:
|
||||
help: 'Ports to scan. You can specify a range of ports, a list, or both. Put them inside double quotes, for example: "22, 80, 443, 1000-5000"'
|
||||
long: ports
|
||||
takes_value: true
|
||||
multiple: false
|
||||
|
||||
- min-rate:
|
||||
help: Nmap --min-rate value for ports scan.
|
||||
long: min-rate
|
||||
takes_value: true
|
||||
|
||||
- fast-scan:
|
||||
help: Use fast scanning for ports (no version detection).
|
||||
long: fast-scan
|
||||
takes_value: false
|
||||
|
||||
- logs-dir:
|
||||
help: Path to save the CSV data of the process and/or Nmap XML files. Default to logs/.
|
||||
long: logs-dir
|
||||
takes_value: true
|
||||
multiple: false
|
||||
|
||||
- no-keep-nmap-logs:
|
||||
help: Keep Nmap XML files created in the logs/ folder for every scanned IP. This data will be useful for other tasks.
|
||||
short: k
|
||||
long: no-keep-nmap-logs
|
||||
takes_value: false
|
||||
multiple: false
|
||||
|
||||
- raw-output:
|
||||
help: Use raw output instead of a table.
|
||||
short: r
|
||||
long: raw-output
|
||||
takes_value: false
|
||||
multiple: false
|
||||
conflicts_with:
|
||||
- url-output
|
||||
|
||||
- url-output:
|
||||
help: Use HOST:IP output format.
|
||||
long: url-output
|
||||
takes_value: false
|
||||
multiple: false
|
||||
conflicts_with:
|
||||
- raw-output
|
||||
|
||||
- stdin:
|
||||
help: Read from stdin instead of files or aguments.
|
||||
long: stdin
|
||||
takes_value: false
|
||||
multiple: false
|
||||
conflicts_with:
|
||||
- files
|
||||
- target
|
||||
15
src/files.rs
15
src/files.rs
|
|
@ -1,7 +1,7 @@
|
|||
use {
|
||||
crate::{
|
||||
args::ProcessedArgs,
|
||||
errors::{Result, ResultExt},
|
||||
structs::Args,
|
||||
},
|
||||
log::error,
|
||||
prettytable::Table,
|
||||
|
|
@ -14,14 +14,17 @@ use {
|
|||
};
|
||||
|
||||
#[must_use]
|
||||
pub fn return_file_targets(args: &Args, mut files: Vec<String>) -> Vec<String> {
|
||||
pub fn return_file_targets(args: &ProcessedArgs, mut files: Vec<String>) -> Vec<String> {
|
||||
let mut targets: Vec<String> = Vec::new();
|
||||
files.sort();
|
||||
files.dedup();
|
||||
for f in files {
|
||||
match File::open(&f) {
|
||||
Ok(file) => {
|
||||
for target in BufReader::new(file).lines().flatten() {
|
||||
for target in BufReader::new(file)
|
||||
.lines()
|
||||
.map_while(std::result::Result::ok)
|
||||
{
|
||||
targets.push(target);
|
||||
}
|
||||
}
|
||||
|
|
@ -46,7 +49,7 @@ pub fn table_to_file(table: &Table, file_name: Option<std::fs::File>) -> Result<
|
|||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn return_output_file(args: &Args) -> Option<File> {
|
||||
pub fn return_output_file(args: &ProcessedArgs) -> Option<File> {
|
||||
if args.file_name.is_empty() || !args.with_output {
|
||||
None
|
||||
} else {
|
||||
|
|
@ -67,7 +70,7 @@ pub fn check_full_path(full_path: &str) -> bool {
|
|||
|| fs::create_dir_all(full_path).is_ok()
|
||||
}
|
||||
|
||||
pub fn delete_files(paths: &HashSet<String>) {
|
||||
pub fn delete_files<S: ::std::hash::BuildHasher>(paths: &HashSet<String, S>) {
|
||||
for file in paths {
|
||||
if Path::new(&file).exists() {
|
||||
match std::fs::remove_file(file) {
|
||||
|
|
@ -78,7 +81,7 @@ pub fn delete_files(paths: &HashSet<String>) {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn string_to_file(data: String, mut file: File) -> Result<()> {
|
||||
pub fn string_to_file(data: &str, mut file: File) -> Result<()> {
|
||||
file.write_all(data.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use {
|
||||
clap::Parser,
|
||||
log::{error, Level},
|
||||
std::{collections::HashSet, iter::FromIterator},
|
||||
unimap::{args, errors::Result, files::return_file_targets, logger, misc, resolver_engine},
|
||||
|
|
@ -10,7 +11,9 @@ fn run() -> Result<()> {
|
|||
} else {
|
||||
logger::init_with_level(Level::Info).unwrap();
|
||||
}
|
||||
let mut arguments = args::get_args();
|
||||
|
||||
let mut arguments = args::Args::parse().into_processed_args();
|
||||
|
||||
if !arguments.files.is_empty() {
|
||||
arguments.targets =
|
||||
HashSet::from_iter(return_file_targets(&arguments, arguments.files.clone()));
|
||||
|
|
|
|||
25
src/misc.rs
25
src/misc.rs
|
|
@ -12,31 +12,6 @@ pub fn sanitize_target_string(target: String) -> String {
|
|||
.replace('/', "")
|
||||
}
|
||||
|
||||
pub fn return_matches_vec(matches: &clap::ArgMatches, value: &str) -> Vec<String> {
|
||||
if matches.is_present(value) {
|
||||
matches
|
||||
.values_of(value)
|
||||
.unwrap()
|
||||
.map(str::to_owned)
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn return_matches_hashset(matches: &clap::ArgMatches, value: &str) -> HashSet<String> {
|
||||
if matches.is_present(value) {
|
||||
matches
|
||||
.values_of(value)
|
||||
.unwrap()
|
||||
.map(str::to_owned)
|
||||
.collect()
|
||||
} else {
|
||||
HashSet::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_stdin() -> HashSet<String> {
|
||||
let mut buffer = String::new();
|
||||
let mut stdin = io::stdin();
|
||||
|
|
|
|||
21
src/nmap.rs
21
src/nmap.rs
|
|
@ -1,18 +1,8 @@
|
|||
use {
|
||||
crate::resolver_engine,
|
||||
log::error,
|
||||
std::{path::Path, process::Command},
|
||||
std::{net::Ipv4Addr, path::Path, process::Command},
|
||||
};
|
||||
|
||||
lazy_static! {
|
||||
static ref NMAP_DNS_RESOLVERS: String = resolver_engine::RESOLVERS
|
||||
.clone()
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<String>>()
|
||||
.join(",");
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Root {
|
||||
|
|
@ -114,12 +104,19 @@ pub fn get_nmap_data(
|
|||
min_rate: &str,
|
||||
ports: &str,
|
||||
fast_scan: bool,
|
||||
resolvers: &[Ipv4Addr],
|
||||
) -> Result<Nmaprun, serde_xml_rs::Error> {
|
||||
let min_rate = min_rate.to_string();
|
||||
let nmap_dns_resolvers = resolvers
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<String>>()
|
||||
.join(",");
|
||||
|
||||
let mut nmap_args = vec![
|
||||
"nmap",
|
||||
"--dns-servers",
|
||||
&NMAP_DNS_RESOLVERS,
|
||||
&nmap_dns_resolvers,
|
||||
"-Pn",
|
||||
"-sS",
|
||||
"--open",
|
||||
|
|
|
|||
|
|
@ -1,33 +1,4 @@
|
|||
use {
|
||||
crate::nmap::Port,
|
||||
std::{collections::HashSet, time::Instant},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Args {
|
||||
pub target: String,
|
||||
pub file_name: String,
|
||||
pub version: String,
|
||||
pub logs_dir: String,
|
||||
pub threads: usize,
|
||||
pub ports: String,
|
||||
pub with_output: bool,
|
||||
pub unique_output_flag: bool,
|
||||
pub min_rate: String,
|
||||
pub from_file_flag: bool,
|
||||
pub quiet_flag: bool,
|
||||
pub custom_resolvers: bool,
|
||||
pub custom_ports_range: bool,
|
||||
pub no_keep_nmap_logs: bool,
|
||||
pub raw_output: bool,
|
||||
pub fast_scan: bool,
|
||||
pub url_output: bool,
|
||||
pub from_stdin: bool,
|
||||
pub files: Vec<String>,
|
||||
pub resolvers: Vec<String>,
|
||||
pub targets: HashSet<String>,
|
||||
pub time_wasted: Instant,
|
||||
}
|
||||
use crate::nmap::Port;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct ResolvData {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue