mirror of
https://github.com/edu4rdshl/unimap.git
synced 2026-07-17 23:24:49 +00:00
Compare commits
11 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20b6822834 | ||
| c6a06db04b | |||
| a6c166e470 | |||
| cba63c2a04 | |||
| f5049e1074 | |||
| bc08bab228 | |||
| f56b71a008 | |||
| 127d1f0a9a | |||
| d72e115859 | |||
| 482b6f6e21 | |||
| 8b0c7fcb27 |
18 changed files with 2027 additions and 1191 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -2,3 +2,4 @@
|
|||
/logs
|
||||
*.txt
|
||||
/unimap_logs
|
||||
/ghbinaries
|
||||
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
|
|
@ -9,7 +9,7 @@
|
|||
"request": "launch",
|
||||
"name": "Debug",
|
||||
"program": "${workspaceRoot}/target/debug/${workspaceRootFolderName}",
|
||||
"args": "-f targets.txt -u log.csv --resolvers resolvers.txt --iport 1 --lport 10000 --min-rate 10",
|
||||
"args": "-t useast1.sugarcrm.com --fast-scan",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"sourceLanguages": [
|
||||
"rust"
|
||||
|
|
|
|||
2235
Cargo.lock
generated
2235
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
43
Cargo.toml
43
Cargo.toml
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "unimap"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
authors = ["Eduard Tolosa <edu4rdshl@protonmail.com>"]
|
||||
edition = "2018"
|
||||
description = "Scan only once by IP address and reduce scan times with Nmap for large amounts of data."
|
||||
|
|
@ -12,33 +12,31 @@ readme = "README.md"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
trust-dns-resolver = "0.20.4"
|
||||
rayon = "1.7.0"
|
||||
log = { version = "0.4.20", features = ["std"] }
|
||||
colored = { version = "2.0.4", optional = true }
|
||||
rand = "0.8.5"
|
||||
lazy_static = "1.4.0"
|
||||
serde = { version = "1.0.186", features = ["derive"] }
|
||||
serde_derive = "1.0.186"
|
||||
prettytable-rs = "0.8.0"
|
||||
serde-xml-rs = "0.5.1"
|
||||
chrono = "0.4.26"
|
||||
clap = { version = "2.34.0", features = ["yaml"] }
|
||||
config = { version = "0.11.0", features = ["yaml", "json", "toml", "hjson", "ini"] }
|
||||
hickory-resolver = "0.25.2"
|
||||
rayon = "1.10.0"
|
||||
log = { version = "0.4.27", features = ["std"] }
|
||||
colored = { version = "3.0.0", optional = true }
|
||||
rand = "0.9.2"
|
||||
lazy_static = "1.5.0"
|
||||
tokio = { version = "1.47.0", features = ["rt"] }
|
||||
futures = "0.3"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_derive = "1.0.219"
|
||||
prettytable-rs = "0.10.0"
|
||||
serde-xml-rs = "0.8.1"
|
||||
chrono = "0.4.41"
|
||||
clap = { version = "4.5.42", features = ["derive"] }
|
||||
config = { version = "0.15.13", features = ["yaml", "json", "toml", "ini"] }
|
||||
failure = "0.1.8"
|
||||
|
||||
# https://github.com/bluejekyll/trust-dns/pull/1632
|
||||
[patch.crates-io]
|
||||
trust-dns-resolver = { git = "https://github.com/Findomain/trust-dns", package = "trust-dns-resolver", branch = "custombranch" }
|
||||
|
||||
[target.arm-unknown-linux-gnueabihf.dependencies]
|
||||
openssl = { version = "0.10.56", features = ["vendored"] }
|
||||
openssl = { version = "0.10.73", features = ["vendored"] }
|
||||
|
||||
[target.aarch64-unknown-linux-gnu.dependencies]
|
||||
openssl = { version = "0.10.56", features = ["vendored"] }
|
||||
openssl = { version = "0.10.73", features = ["vendored"] }
|
||||
|
||||
[target.armv7-unknown-linux-gnueabihf.dependencies]
|
||||
openssl = { version = "0.10.56", features = ["vendored"] }
|
||||
openssl = { version = "0.10.73", features = ["vendored"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
atty = "0.2.14"
|
||||
|
|
@ -49,5 +47,4 @@ winapi = { version = "0.3.9", features = ["handleapi", "winbase"] }
|
|||
lto = true
|
||||
codegen-units = 1
|
||||
panic = 'abort'
|
||||
incremental = false
|
||||
opt-level = "s"
|
||||
strip = true
|
||||
|
|
|
|||
65
builder.sh
65
builder.sh
|
|
@ -1,5 +1,4 @@
|
|||
#!/usr/bin/env bash
|
||||
# Rusolver releaser
|
||||
NAME="unimap"
|
||||
|
||||
LINUX_TARGET="x86_64-unknown-linux-musl"
|
||||
|
|
@ -7,24 +6,21 @@ LINUX_X86_TARGET="i686-unknown-linux-musl"
|
|||
WIN_TARGET="x86_64-pc-windows-gnu"
|
||||
ARMV7_TARGET="armv7-unknown-linux-gnueabihf"
|
||||
AARCH_TARGET="aarch64-unknown-linux-gnu"
|
||||
OSX_TARGET="x86_64-apple-darwin"
|
||||
# OSX_TARGET="x86_64-apple-darwin"
|
||||
MANPAGE_DIR="./$NAME.1"
|
||||
BIN_OUTPUT_DIR="./ghbinaries"
|
||||
|
||||
if ! systemctl is-active docker >/dev/null 2>&1; then
|
||||
echo "Docker is not running. Starting docker."
|
||||
if ! sudo systemctl start docker; then
|
||||
echo "Failed to start docker."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
rm -rf "$BIN_OUTPUT_DIR"
|
||||
mkdir -p "$BIN_OUTPUT_DIR"
|
||||
|
||||
# Linux build
|
||||
echo "Building Linux artifact."
|
||||
if cargo build -q --release --target="$LINUX_TARGET"; then
|
||||
if cross build -q --release --target="$LINUX_TARGET"; then
|
||||
echo "Linux artifact build: SUCCESS"
|
||||
cp "target/$LINUX_TARGET/release/$NAME" "target/$LINUX_TARGET/release/$NAME-linux"
|
||||
strip "target/$LINUX_TARGET/release/$NAME-linux"
|
||||
sha512sum "target/$LINUX_TARGET/release/$NAME-linux" >"target/$LINUX_TARGET/release/$NAME-linux.sha512"
|
||||
sha512sum "target/$LINUX_TARGET/release/$NAME-linux" >"$BIN_OUTPUT_DIR/$NAME-linux.sha512"
|
||||
zip -q -j "$BIN_OUTPUT_DIR/$NAME-linux-x64.zip" "target/$LINUX_TARGET/release/$NAME-linux"
|
||||
else
|
||||
echo "Linux artifact build: FAILED"
|
||||
fi
|
||||
|
|
@ -35,7 +31,8 @@ if cross build -q --release --target="$LINUX_X86_TARGET"; then
|
|||
echo "Linux x86 artifact build: SUCCESS"
|
||||
cp "target/$LINUX_X86_TARGET/release/$NAME" "target/$LINUX_X86_TARGET/release/$NAME-linux-i386"
|
||||
strip "target/$LINUX_X86_TARGET/release/$NAME-linux-i386"
|
||||
sha512sum "target/$LINUX_X86_TARGET/release/$NAME-linux-i386" >"target/$LINUX_X86_TARGET/release/$NAME-linux-i386.sha512"
|
||||
sha512sum "target/$LINUX_X86_TARGET/release/$NAME-linux-i386" >"$BIN_OUTPUT_DIR/$NAME-linux-i386.sha512"
|
||||
zip -q -j "$BIN_OUTPUT_DIR/$NAME-linux-i386.zip" "target/$LINUX_X86_TARGET/release/$NAME-linux-i386"
|
||||
else
|
||||
echo "Linux x86 artifact build: FAILED"
|
||||
fi
|
||||
|
|
@ -46,7 +43,8 @@ if cross build -q --release --target="$WIN_TARGET"; then
|
|||
echo "Windows artifact build: SUCCESS"
|
||||
cp "target/$WIN_TARGET/release/$NAME.exe" "target/$WIN_TARGET/release/$NAME-windows.exe"
|
||||
strip "target/$WIN_TARGET/release/$NAME-windows.exe"
|
||||
sha512sum "target/$WIN_TARGET/release/$NAME-windows.exe" >"target/$WIN_TARGET/release/$NAME-windows.exe.sha512"
|
||||
sha512sum "target/$WIN_TARGET/release/$NAME-windows.exe" >"$BIN_OUTPUT_DIR/$NAME-windows.exe.sha512"
|
||||
zip -q -j "$BIN_OUTPUT_DIR/$NAME-windows.zip" "target/$WIN_TARGET/release/$NAME-windows.exe"
|
||||
else
|
||||
echo "Windows artifact build: FAILED"
|
||||
fi
|
||||
|
|
@ -57,7 +55,8 @@ if cross build -q --release --target="$ARMV7_TARGET"; then
|
|||
echo "ARMv7 artifact build: SUCCESS"
|
||||
cp "target/$ARMV7_TARGET/release/$NAME" "target/$ARMV7_TARGET/release/$NAME-armv7"
|
||||
strip "target/$ARMV7_TARGET/release/$NAME-armv7"
|
||||
sha512sum "target/$ARMV7_TARGET/release/$NAME-armv7" >"target/$ARMV7_TARGET/release/$NAME-armv7.sha512"
|
||||
sha512sum "target/$ARMV7_TARGET/release/$NAME-armv7" >"$BIN_OUTPUT_DIR/$NAME-armv7.sha512"
|
||||
zip -q -j "$BIN_OUTPUT_DIR/$NAME-armv7.zip" "target/$ARMV7_TARGET/release/$NAME-armv7"
|
||||
else
|
||||
echo "ARMv7 artifact build: FAILED"
|
||||
fi
|
||||
|
|
@ -68,41 +67,31 @@ if cross build -q --release --target="$AARCH_TARGET"; then
|
|||
echo "Aarch64 artifact build: SUCCESS"
|
||||
cp "target/$AARCH_TARGET/release/$NAME" "target/$AARCH_TARGET/release/$NAME-aarch64"
|
||||
strip "target/$AARCH_TARGET/release/$NAME-aarch64"
|
||||
sha512sum "target/$AARCH_TARGET/release/$NAME-aarch64" >"$BIN_OUTPUT_DIR/$NAME-aarch64.sha512"
|
||||
zip -q -j "$BIN_OUTPUT_DIR/$NAME-aarch64.zip" "target/$AARCH_TARGET/release/$NAME-aarch64"
|
||||
else
|
||||
echo "Aarch64 artifact build: FAILED"
|
||||
fi
|
||||
|
||||
# Mac OS build
|
||||
echo "Building OSX artifact."
|
||||
if CC=o64-clang CXX=o64-clang++ LIBZ_SYS_STATIC=1 cargo build -q --release --target="$OSX_TARGET"; then
|
||||
echo "OSX artifact build: SUCCESS"
|
||||
cp "target/$OSX_TARGET/release/$NAME" "target/$OSX_TARGET/release/$NAME-osx"
|
||||
strip "target/$OSX_TARGET/release/$NAME-osx"
|
||||
sha512sum "target/$OSX_TARGET/release/$NAME-osx" >"target/$OSX_TARGET/release/$NAME-osx.sha512"
|
||||
else
|
||||
echo "OSX artifact build: FAILED"
|
||||
fi
|
||||
# # Mac OS build
|
||||
# echo "Building OSX artifact."
|
||||
# if CC=o64-clang CXX=o64-clang++ LIBZ_SYS_STATIC=1 cargo build -q --release --target="$OSX_TARGET"; then
|
||||
# echo "OSX artifact build: SUCCESS"
|
||||
# cp "target/$OSX_TARGET/release/$NAME" "target/$OSX_TARGET/release/$NAME-osx"
|
||||
# strip "target/$OSX_TARGET/release/$NAME-osx"
|
||||
# sha512sum "target/$OSX_TARGET/release/$NAME-osx" >"target/$OSX_TARGET/release/$NAME-osx.sha512"
|
||||
# zip -q "$BIN_OUTPUT_DIR/$NAME-osx.zip" "target/$OSX_TARGET/release/$NAME-osx"
|
||||
# else
|
||||
# echo "OSX artifact build: FAILED"
|
||||
# fi
|
||||
|
||||
echo "Creating manpage..."
|
||||
if command -v help2man >/dev/null; then
|
||||
if help2man -o "$MANPAGE_DIR" "target/$LINUX_TARGET/release/$NAME"; then
|
||||
echo "Manpage created sucessfully and saved in $MANPAGE_DIR"
|
||||
echo "Manpage created successfully and saved in $MANPAGE_DIR"
|
||||
else
|
||||
echo "Error creating manpage."
|
||||
fi
|
||||
else
|
||||
echo "Please install the help2man package."
|
||||
fi
|
||||
|
||||
if command -v git >/dev/null; then
|
||||
git add .
|
||||
git commit -m "Bump version."
|
||||
git push
|
||||
fi
|
||||
|
||||
# Stop docker
|
||||
echo "Stopping docker."
|
||||
if ! sudo systemctl stop docker; then
|
||||
echo "Failed to stop docker."
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
|||
201
src/args.rs
201
src/args.rs
|
|
@ -1,72 +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)]
|
||||
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()
|
||||
},
|
||||
};
|
||||
|
||||
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
|
||||
|
|
@ -16,12 +16,6 @@ pub fn ipv4_resolvers() -> Vec<String> {
|
|||
// Verisign
|
||||
"64.6.64.6",
|
||||
"64.6.65.6",
|
||||
// UncensoredDNS
|
||||
"91.239.100.100",
|
||||
"89.233.43.71",
|
||||
// dns.watch
|
||||
"84.200.69.80",
|
||||
"84.200.70.40",
|
||||
]
|
||||
.into_iter()
|
||||
.map(str::to_owned)
|
||||
|
|
|
|||
30
src/files.rs
30
src/files.rs
|
|
@ -1,5 +1,8 @@
|
|||
use {
|
||||
crate::{errors::*, structs::Args},
|
||||
crate::{
|
||||
args::ProcessedArgs,
|
||||
errors::{Result, ResultExt},
|
||||
},
|
||||
log::error,
|
||||
prettytable::Table,
|
||||
std::{
|
||||
|
|
@ -10,26 +13,27 @@ use {
|
|||
},
|
||||
};
|
||||
|
||||
pub fn return_file_targets(args: &Args, mut files: Vec<String>) -> Vec<String> {
|
||||
#[must_use]
|
||||
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);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
if args.files.len() == 1 {
|
||||
error!("Can not open file {}. Error: {}\n", f, e);
|
||||
error!("Can not open file {f}. Error: {e}\n");
|
||||
std::process::exit(1)
|
||||
} else if !args.quiet_flag {
|
||||
error!(
|
||||
"Can not open file {}, working with next file. Error: {}\n",
|
||||
f, e
|
||||
);
|
||||
error!("Can not open file {f}, working with next file. Error: {e}\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -44,7 +48,8 @@ pub fn table_to_file(table: &Table, file_name: Option<std::fs::File>) -> Result<
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn return_output_file(args: &Args) -> Option<File> {
|
||||
#[must_use]
|
||||
pub fn return_output_file(args: &ProcessedArgs) -> Option<File> {
|
||||
if args.file_name.is_empty() || !args.with_output {
|
||||
None
|
||||
} else {
|
||||
|
|
@ -59,23 +64,24 @@ pub fn return_output_file(args: &Args) -> Option<File> {
|
|||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn check_full_path(full_path: &str) -> bool {
|
||||
(Path::new(full_path).exists() && Path::new(full_path).is_dir())
|
||||
|| 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) {
|
||||
Ok(_) => (),
|
||||
Ok(()) => (),
|
||||
Err(e) => error!("Error deleting the file {}. Description: {}", &file, e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,6 +1,5 @@
|
|||
#[cfg(windows)]
|
||||
extern crate atty;
|
||||
#[cfg(feature = "chrono")]
|
||||
extern crate chrono;
|
||||
#[cfg(feature = "colored")]
|
||||
extern crate colored;
|
||||
|
|
@ -8,7 +7,6 @@ extern crate log;
|
|||
#[cfg(windows)]
|
||||
extern crate winapi;
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
use chrono::Local;
|
||||
#[cfg(feature = "colored")]
|
||||
use colored::*;
|
||||
|
|
@ -23,10 +21,10 @@ impl Log for SimpleLogger {
|
|||
}
|
||||
|
||||
fn log(&self, record: &Record) {
|
||||
let target = if !record.target().is_empty() {
|
||||
record.target()
|
||||
} else {
|
||||
let target = if record.target().is_empty() {
|
||||
record.module_path().unwrap_or_default()
|
||||
} else {
|
||||
record.target()
|
||||
};
|
||||
if target.contains("unimap") && self.enabled(record.metadata()) {
|
||||
let level_string = {
|
||||
|
|
@ -45,7 +43,6 @@ impl Log for SimpleLogger {
|
|||
record.level().to_string()
|
||||
}
|
||||
};
|
||||
#[cfg(feature = "chrono")]
|
||||
{
|
||||
print!(
|
||||
"\n{} [{}] {}",
|
||||
|
|
@ -54,10 +51,6 @@ impl Log for SimpleLogger {
|
|||
record.args()
|
||||
);
|
||||
}
|
||||
#[cfg(not(feature = "chrono"))]
|
||||
{
|
||||
print!("\n[{}] {}", level_string, record.args());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ pub fn validate_target(target: &str) -> bool {
|
|||
!target.starts_with('.')
|
||||
&& target.contains('.')
|
||||
&& !target.contains(&SPECIAL_CHARS[..])
|
||||
&& target.chars().all(|c| c.is_ascii())
|
||||
&& target.is_ascii()
|
||||
}
|
||||
|
||||
pub fn null_ip_checker(ip: &str) -> String {
|
||||
|
|
|
|||
27
src/main.rs
27
src/main.rs
|
|
@ -1,27 +1,30 @@
|
|||
use {
|
||||
clap::Parser,
|
||||
log::{error, Level},
|
||||
std::{collections::HashSet, iter::FromIterator},
|
||||
unimap::{args, errors::*, files::return_file_targets, logger, misc, resolver_engine},
|
||||
unimap::{args, errors::Result, files::return_file_targets, logger, misc, resolver_engine},
|
||||
};
|
||||
|
||||
fn run() -> Result<()> {
|
||||
if std::env::var("UNIMAP_LOG_LEVEL").is_ok() {
|
||||
logger::init_by_env()
|
||||
logger::init_by_env();
|
||||
} else {
|
||||
logger::init_with_level(Level::Info).unwrap()
|
||||
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()))
|
||||
HashSet::from_iter(return_file_targets(&arguments, arguments.files.clone()));
|
||||
} else if !arguments.target.is_empty() {
|
||||
arguments.targets.insert(arguments.target.clone());
|
||||
} else {
|
||||
arguments.targets = misc::read_stdin()
|
||||
arguments.targets = misc::read_stdin();
|
||||
}
|
||||
|
||||
if arguments.targets.len() < 50 {
|
||||
arguments.threads = arguments.targets.len()
|
||||
arguments.threads = arguments.targets.len();
|
||||
}
|
||||
|
||||
rayon::ThreadPoolBuilder::new()
|
||||
|
|
@ -29,19 +32,19 @@ fn run() -> Result<()> {
|
|||
.build_global()
|
||||
.unwrap();
|
||||
|
||||
if !arguments.targets.is_empty() {
|
||||
resolver_engine::parallel_resolver_all(&mut arguments)
|
||||
} else {
|
||||
if arguments.targets.is_empty() {
|
||||
error!("Error: Target is empty or invalid!\n");
|
||||
std::process::exit(1)
|
||||
} else {
|
||||
resolver_engine::parallel_resolver_all(&mut arguments)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if let Err(err) = run() {
|
||||
error!("Error: {}", err);
|
||||
error!("Error: {err}");
|
||||
for cause in err.iter_chain().skip(1) {
|
||||
error!("Error description: {}\n", cause);
|
||||
error!("Error description: {cause}\n");
|
||||
}
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
|
|
|||
26
src/misc.rs
26
src/misc.rs
|
|
@ -3,6 +3,7 @@ use std::{
|
|||
io::{self, Read},
|
||||
};
|
||||
|
||||
#[must_use]
|
||||
pub fn sanitize_target_string(target: String) -> String {
|
||||
target
|
||||
.replace("www.", "")
|
||||
|
|
@ -11,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();
|
||||
|
|
|
|||
|
|
@ -1,43 +1,43 @@
|
|||
use {
|
||||
crate::{files, structs::Args},
|
||||
std::{collections::HashSet, net::SocketAddr},
|
||||
trust_dns_resolver::{
|
||||
config::{NameServerConfig, NameServerConfigGroup, Protocol, ResolverConfig, ResolverOpts},
|
||||
Resolver,
|
||||
crate::{args::ProcessedArgs, files},
|
||||
hickory_resolver::{
|
||||
config::{NameServerConfig, NameServerConfigGroup, ResolverConfig, ResolverOpts},
|
||||
name_server::TokioConnectionProvider,
|
||||
proto::xfer::Protocol,
|
||||
TokioResolver,
|
||||
},
|
||||
std::{collections::HashSet, net::SocketAddr},
|
||||
};
|
||||
|
||||
pub fn get_records(resolver: &Resolver, domain: &str) -> String {
|
||||
if let Ok(ips) = resolver.ipv4_lookup(domain) {
|
||||
pub fn get_records(resolver: &TokioResolver, domain: &str) -> String {
|
||||
futures::executor::block_on(resolver.ipv4_lookup(domain)).map_or_else(
|
||||
|_| String::new(),
|
||||
|ips| {
|
||||
ips.iter()
|
||||
.map(|x| x.to_string())
|
||||
.map(std::string::ToString::to_string)
|
||||
.next()
|
||||
.expect("Failed to get IPV4.")
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_resolver(nameserver_ips: HashSet<SocketAddr>, opts: ResolverOpts) -> Resolver {
|
||||
let mut name_servers = NameServerConfigGroup::with_capacity(nameserver_ips.len() * 2);
|
||||
name_servers.extend(nameserver_ips.into_iter().flat_map(|socket_addr| {
|
||||
std::iter::once(NameServerConfig {
|
||||
socket_addr,
|
||||
protocol: Protocol::Udp,
|
||||
tls_dns_name: None,
|
||||
trust_nx_responses: false,
|
||||
})
|
||||
.chain(std::iter::once(NameServerConfig {
|
||||
socket_addr,
|
||||
protocol: Protocol::Tcp,
|
||||
tls_dns_name: None,
|
||||
trust_nx_responses: false,
|
||||
}))
|
||||
}));
|
||||
Resolver::new(ResolverConfig::from_parts(None, vec![], name_servers), opts).unwrap()
|
||||
pub fn get_resolver(nameserver_ips: HashSet<SocketAddr>, opts: ResolverOpts) -> TokioResolver {
|
||||
let mut name_servers = NameServerConfigGroup::with_capacity(nameserver_ips.len());
|
||||
name_servers.extend(
|
||||
nameserver_ips
|
||||
.into_iter()
|
||||
.map(|socket_addr| NameServerConfig::new(socket_addr, Protocol::Udp)),
|
||||
);
|
||||
|
||||
TokioResolver::builder_with_config(
|
||||
ResolverConfig::from_parts(None, vec![], name_servers),
|
||||
TokioConnectionProvider::default(),
|
||||
)
|
||||
.with_options(opts)
|
||||
.build()
|
||||
}
|
||||
|
||||
pub fn return_socket_address(args: &Args) -> HashSet<SocketAddr> {
|
||||
pub fn return_socket_address(args: &ProcessedArgs) -> HashSet<SocketAddr> {
|
||||
let mut resolver_ips = HashSet::new();
|
||||
if args.custom_resolvers {
|
||||
for r in &files::return_file_targets(args, args.resolvers.clone()) {
|
||||
|
|
|
|||
32
src/nmap.rs
32
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",
|
||||
|
|
@ -132,17 +129,17 @@ pub fn get_nmap_data(
|
|||
];
|
||||
|
||||
if !min_rate.is_empty() {
|
||||
nmap_args.append(&mut vec!["--min-rate", &min_rate])
|
||||
nmap_args.append(&mut vec!["--min-rate", &min_rate]);
|
||||
}
|
||||
|
||||
if fast_scan {
|
||||
nmap_args.append(&mut vec!["--host-timeout", "20m"])
|
||||
nmap_args.append(&mut vec!["--host-timeout", "20m"]);
|
||||
} else {
|
||||
nmap_args.append(&mut vec!["-sV"])
|
||||
nmap_args.append(&mut vec!["-sV"]);
|
||||
}
|
||||
|
||||
if !ports.is_empty() {
|
||||
nmap_args.append(&mut vec!["-p", ports])
|
||||
nmap_args.append(&mut vec!["-p", ports]);
|
||||
}
|
||||
|
||||
nmap_args.push(host);
|
||||
|
|
@ -159,8 +156,7 @@ pub fn get_nmap_data(
|
|||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Error waiting command to finish for {}, continuing with remaining hosts. Description: {}",
|
||||
host, e
|
||||
"Error waiting command to finish for {host}, continuing with remaining hosts. Description: {e}"
|
||||
);
|
||||
Ok(Nmaprun::default())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
use {
|
||||
crate::{
|
||||
args,
|
||||
errors::*,
|
||||
args::ProcessedArgs,
|
||||
errors::Result,
|
||||
files, logic, networking,
|
||||
nmap::{self, Nmaprun},
|
||||
structs::{Args, ResolvData},
|
||||
structs::ResolvData,
|
||||
},
|
||||
hickory_resolver::config::{LookupIpStrategy, ResolverOpts},
|
||||
log::{error, info},
|
||||
prettytable,
|
||||
prettytable::Table,
|
||||
|
|
@ -15,61 +16,55 @@ use {
|
|||
net::Ipv4Addr,
|
||||
time::Duration,
|
||||
},
|
||||
trust_dns_resolver::{
|
||||
config::{LookupIpStrategy, ResolverOpts},
|
||||
Resolver,
|
||||
},
|
||||
};
|
||||
|
||||
lazy_static! {
|
||||
pub static ref RESOLVERS: Vec<Ipv4Addr> = {
|
||||
let args = args::get_args();
|
||||
fn create_resolvers(args: &ProcessedArgs) -> Vec<Ipv4Addr> {
|
||||
let mut resolver_ips = Vec::new();
|
||||
if args.custom_resolvers {
|
||||
for r in &files::return_file_targets(&args, args.resolvers.clone()) {
|
||||
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);
|
||||
error!("Error parsing the {r} IP from resolvers file to IP address. Please check and try again. Error: {e}\n");
|
||||
std::process::exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for r in args.resolvers {
|
||||
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);
|
||||
error!("Error parsing the {r} IP from resolvers file to IP address. Please check and try again. Error: {e}\n");
|
||||
std::process::exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
resolver_ips
|
||||
};
|
||||
}
|
||||
|
||||
pub fn parallel_resolver_all(args: &mut Args) -> Result<()> {
|
||||
files::check_full_path(&args.logs_dir);
|
||||
pub fn parallel_resolver_all(args: &mut ProcessedArgs) -> Result<()> {
|
||||
if !files::check_full_path(&args.logs_dir) {
|
||||
error!("The logs directory {} does not exist.\n", args.logs_dir);
|
||||
std::process::exit(1)
|
||||
}
|
||||
|
||||
if !args.quiet_flag {
|
||||
info!(
|
||||
"Performing parallel resolution for {} targets with {} threads, it will take a while...\n",
|
||||
args.targets.len(), args.threads
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
let opts = ResolverOpts {
|
||||
timeout: Duration::from_secs(1),
|
||||
ip_strategy: LookupIpStrategy::Ipv4Only,
|
||||
num_concurrent_reqs: 1,
|
||||
..Default::default()
|
||||
};
|
||||
let mut opts = ResolverOpts::default();
|
||||
opts.timeout = Duration::from_secs(1);
|
||||
opts.ip_strategy = LookupIpStrategy::Ipv4Only;
|
||||
opts.num_concurrent_reqs = 1;
|
||||
|
||||
let resolver = networking::get_resolver(networking::return_socket_address(args), opts);
|
||||
|
||||
let data = parallel_resolver_engine(args, args.targets.clone(), resolver);
|
||||
let data = parallel_resolver_engine(args, &args.targets, &resolver);
|
||||
|
||||
let mut table = Table::new();
|
||||
table.set_titles(row![
|
||||
|
|
@ -79,9 +74,9 @@ pub fn parallel_resolver_all(args: &mut Args) -> Result<()> {
|
|||
"SERVICES"
|
||||
]);
|
||||
if args.raw_output && !args.quiet_flag {
|
||||
println!("HOST,IP,PORT,SERVICE,VERSION,PRODUCT,OS,EXTRAINFO")
|
||||
println!("HOST,IP,PORT,SERVICE,VERSION,PRODUCT,OS,EXTRAINFO");
|
||||
} else if args.url_output && !args.quiet_flag {
|
||||
println!("HOST:IP")
|
||||
println!("HOST:IP");
|
||||
}
|
||||
for (target, resolv_data) in &data {
|
||||
if !resolv_data.ip.is_empty() {
|
||||
|
|
@ -120,11 +115,11 @@ pub fn parallel_resolver_all(args: &mut Args) -> Result<()> {
|
|||
.extrainfo
|
||||
.clone()
|
||||
.unwrap_or_else(|| "NULL".to_string())
|
||||
)
|
||||
);
|
||||
}
|
||||
} else if args.url_output {
|
||||
for port_data in &resolv_data.ports_data {
|
||||
println!("{}:{}", target, port_data.portid)
|
||||
println!("{}:{}", target, port_data.portid);
|
||||
}
|
||||
} else {
|
||||
let mut services_table = Table::new();
|
||||
|
|
@ -179,7 +174,7 @@ pub fn parallel_resolver_all(args: &mut Args) -> Result<()> {
|
|||
error!(
|
||||
"An error occurred while writing the output file {}.\n",
|
||||
args.file_name
|
||||
)
|
||||
);
|
||||
}
|
||||
if !args.quiet_flag && !args.raw_output && !args.url_output {
|
||||
table.printstd();
|
||||
|
|
@ -199,36 +194,51 @@ pub fn parallel_resolver_all(args: &mut Args) -> Result<()> {
|
|||
}
|
||||
|
||||
fn parallel_resolver_engine(
|
||||
args: &Args,
|
||||
targets: HashSet<String>,
|
||||
resolver: Resolver,
|
||||
args: &ProcessedArgs,
|
||||
targets: &HashSet<String>,
|
||||
resolver: &hickory_resolver::TokioResolver,
|
||||
) -> HashMap<String, ResolvData> {
|
||||
let resolv_data: HashMap<String, ResolvData> = targets
|
||||
.par_iter()
|
||||
.map(|target| {
|
||||
let fqdn_target = format!("{}.", target);
|
||||
let fqdn_target = format!("{target}.");
|
||||
let mut resolv_data = ResolvData::default();
|
||||
resolv_data.ip = networking::get_records(&resolver, &fqdn_target);
|
||||
resolv_data.ip = networking::get_records(resolver, &fqdn_target);
|
||||
(target.to_owned(), resolv_data)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let resolvers = create_resolvers(args);
|
||||
|
||||
let mut nmap_ips: HashSet<String> = resolv_data
|
||||
.values()
|
||||
.map(|resolv_data| resolv_data.ip.clone())
|
||||
.collect();
|
||||
|
||||
let nmap_ips_orig = nmap_ips.clone();
|
||||
|
||||
nmap_ips.retain(|ip| {
|
||||
!ip.is_empty()
|
||||
&& ip.parse::<Ipv4Addr>().is_ok()
|
||||
&& !ip.parse::<Ipv4Addr>().unwrap().is_private()
|
||||
&& ip.parse::<Ipv4Addr>().is_ok()
|
||||
});
|
||||
|
||||
if nmap_ips.is_empty() {
|
||||
error!("No valid IPs found for scanning. IPs found: {nmap_ips_orig:?}\n");
|
||||
std::process::exit(1)
|
||||
} else {
|
||||
let nmap_data: HashMap<String, Nmaprun> = nmap_ips
|
||||
.par_iter()
|
||||
.map(|ip| {
|
||||
let filename = format!("{}/{}.xml", &args.logs_dir, &ip);
|
||||
match nmap::get_nmap_data(&filename, ip, &args.min_rate, &args.ports, args.fast_scan) {
|
||||
match nmap::get_nmap_data(
|
||||
&filename,
|
||||
ip,
|
||||
&args.min_rate,
|
||||
&args.ports,
|
||||
args.fast_scan,
|
||||
&resolvers,
|
||||
) {
|
||||
Ok(nmap_data) => {
|
||||
nmap_data
|
||||
.host
|
||||
|
|
@ -239,7 +249,7 @@ fn parallel_resolver_engine(
|
|||
.port
|
||||
.retain(|f| f.state.state == "open");
|
||||
if args.no_keep_nmap_logs && std::fs::remove_file(&filename).is_err() {
|
||||
error!("Error removing filename {}.", &filename)
|
||||
error!("Error removing filename {}.", &filename);
|
||||
}
|
||||
(ip.clone(), nmap_data)
|
||||
}
|
||||
|
|
@ -253,7 +263,7 @@ fn parallel_resolver_engine(
|
|||
|
||||
// Delete the args.logs_dir directory if it's empty
|
||||
if args.no_keep_nmap_logs && std::fs::remove_dir(&args.logs_dir).is_err() {
|
||||
error!("Error removing directory {}.", &args.logs_dir)
|
||||
error!("Error removing directory {}.", &args.logs_dir);
|
||||
}
|
||||
|
||||
resolv_data
|
||||
|
|
@ -281,4 +291,5 @@ fn parallel_resolver_engine(
|
|||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
@ -35,8 +6,8 @@ pub struct ResolvData {
|
|||
pub ports_data: Vec<Port>,
|
||||
}
|
||||
impl ResolvData {
|
||||
pub fn default() -> ResolvData {
|
||||
ResolvData {
|
||||
pub const fn default() -> Self {
|
||||
Self {
|
||||
ip: String::new(),
|
||||
ports_data: Vec::new(),
|
||||
}
|
||||
|
|
|
|||
106
unimap.1
106
unimap.1
|
|
@ -1,81 +1,73 @@
|
|||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.48.3.
|
||||
.TH UNIMAP "1" "May 2021" "Unimap 0.5.1" "User Commands"
|
||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
|
||||
.TH UNIMAP "1" "August 2025" "unimap 0.7.0" "User Commands"
|
||||
.SH NAME
|
||||
Unimap \- manual page for Unimap 0.5.1
|
||||
unimap \- manual page for unimap 0.7.0
|
||||
.SH SYNOPSIS
|
||||
.B unimap
|
||||
[\fI\,OPTIONS\/\fR]
|
||||
.SH DESCRIPTION
|
||||
Unimap 0.5.1
|
||||
Eduard Tolosa <edu4rdshl@protonmail.com>
|
||||
Scan only once by IP address and reduce scan times with Nmap for large amounts of data.
|
||||
.SS "USAGE:"
|
||||
.IP
|
||||
unimap [FLAGS] [OPTIONS]
|
||||
.SS "FLAGS:"
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
\fB\-\-fast\-scan\fR
|
||||
Use fast scanning for ports (no version detection).
|
||||
\fB\-t\fR, \fB\-\-target\fR <TARGET>
|
||||
Target host
|
||||
.TP
|
||||
\fB\-h\fR, \fB\-\-help\fR
|
||||
Prints help information
|
||||
.TP
|
||||
\fB\-k\fR, \fB\-\-no\-keep\-nmap\-logs\fR
|
||||
Keep Nmap XML files created in the logs/ folder for every scanned IP. This data will be
|
||||
useful for other tasks.
|
||||
\fB\-f\fR, \fB\-\-files\fR <FILES>
|
||||
Use a list of targets written in a file as input
|
||||
.TP
|
||||
\fB\-o\fR, \fB\-\-output\fR
|
||||
Write to an output file. The name of the output file will be unimap\-log\-date.
|
||||
Write to an output file. The name of the output file will be unimap\-log\-date
|
||||
.TP
|
||||
\fB\-u\fR, \fB\-\-unique\-output\fR <UNIQUE_OUTPUT>
|
||||
Write the output in CSV format to the specified filename
|
||||
.TP
|
||||
\fB\-q\fR, \fB\-\-quiet\fR
|
||||
Remove informative messages.
|
||||
Remove informative messages
|
||||
.TP
|
||||
\fB\-\-threads\fR <THREADS>
|
||||
Number of threads to use to perform the resolution
|
||||
.TP
|
||||
\fB\-\-resolvers\fR <CUSTOM_RESOLVERS>
|
||||
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
|
||||
.TP
|
||||
\fB\-\-ports\fR <PORTS>
|
||||
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"
|
||||
.TP
|
||||
\fB\-\-min\-rate\fR <MIN_RATE>
|
||||
Nmap \fB\-\-min\-rate\fR value for ports scan
|
||||
.TP
|
||||
\fB\-\-fast\-scan\fR
|
||||
Use fast scanning for ports (no version detection)
|
||||
.TP
|
||||
\fB\-\-logs\-dir\fR <LOGS_DIR>
|
||||
Path to save the CSV data of the process and/or Nmap XML files. Default to logs/ [default: unimap_logs]
|
||||
.TP
|
||||
\fB\-k\fR, \fB\-\-no\-keep\-nmap\-logs\fR
|
||||
Keep Nmap XML files created in the logs/ folder for every scanned IP. This data will be useful for other tasks
|
||||
.TP
|
||||
\fB\-r\fR, \fB\-\-raw\-output\fR
|
||||
Use raw output instead of a table.
|
||||
.TP
|
||||
\fB\-\-stdin\fR
|
||||
Read from stdin instead of files or aguments.
|
||||
Use raw output instead of a table
|
||||
.TP
|
||||
\fB\-\-url\-output\fR
|
||||
Use HOST:IP output format.
|
||||
Use HOST:IP output format
|
||||
.TP
|
||||
\fB\-\-stdin\fR
|
||||
Read from stdin instead of files or arguments
|
||||
.TP
|
||||
\fB\-h\fR, \fB\-\-help\fR
|
||||
Print help
|
||||
.TP
|
||||
\fB\-V\fR, \fB\-\-version\fR
|
||||
Prints version information
|
||||
.SS "OPTIONS:"
|
||||
.TP
|
||||
\fB\-\-resolvers\fR <custom\-resolvers>...
|
||||
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.
|
||||
.TP
|
||||
\fB\-f\fR, \fB\-\-file\fR <files>...
|
||||
Use a list of targets writen in a file as input.
|
||||
.TP
|
||||
\fB\-\-logs\-dir\fR <logs\-dir>
|
||||
Path to save the CSV data of the process and/or Nmap XML files. Default to
|
||||
logs/.
|
||||
.TP
|
||||
\fB\-\-min\-rate\fR <min\-rate>
|
||||
Nmap \fB\-\-min\-rate\fR value for ports scan.
|
||||
.TP
|
||||
\fB\-\-ports\fR <ports>
|
||||
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"
|
||||
.TP
|
||||
\fB\-t\fR, \fB\-\-target\fR <target>
|
||||
Target host.
|
||||
.TP
|
||||
\fB\-\-threads\fR <threads>
|
||||
Number of threads to use to perform the resolution.
|
||||
.TP
|
||||
\fB\-u\fR, \fB\-\-unique\-output\fR <unique\-output>
|
||||
Write the output in CSV format to the specified filename.
|
||||
Print version
|
||||
.SH "SEE ALSO"
|
||||
The full documentation for
|
||||
.B Unimap
|
||||
.B unimap
|
||||
is maintained as a Texinfo manual. If the
|
||||
.B info
|
||||
and
|
||||
.B Unimap
|
||||
.B unimap
|
||||
programs are properly installed at your site, the command
|
||||
.IP
|
||||
.B info Unimap
|
||||
.B info unimap
|
||||
.PP
|
||||
should give you access to the complete manual.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue