Compare commits

...

11 commits

Author SHA1 Message Date
RoomWithOutRoof
20b6822834
fix: correct sucessfully to successfully typo in echo message (#14) 2026-06-27 16:12:46 -05:00
c6a06db04b (chore): bump dependencies and adapt code to new ones 2025-08-02 15:11:48 -05:00
a6c166e470 (chore): remove invalid DNS 2025-08-02 15:11:15 -05:00
cba63c2a04 (feat): update from old clap definition 2025-08-02 15:10:52 -05:00
f5049e1074 Update manpage and exclude binaries folder 2025-08-02 14:05:49 -05:00
bc08bab228 Update builder.sh 2025-08-02 13:55:44 -05:00
f56b71a008 (chore): update builder.sh
- Disable docker checks, we now use podman
- Use cross for all builds, otherwise the build ends up in glibc versions mismatch
2025-08-02 13:00:43 -05:00
127d1f0a9a Update dependencies. 2025-04-20 14:47:28 -05:00
d72e115859 Clippy fixes. 2025-04-20 14:47:21 -05:00
482b6f6e21 (chore): show invalid IP address found. 2025-04-07 16:13:13 +00:00
8b0c7fcb27 (fix): do not launch nmap scans if not valid IPs are found.
Fixes #12
2025-04-07 16:10:42 +00:00
18 changed files with 2027 additions and 1191 deletions

1
.gitignore vendored
View file

@ -2,3 +2,4 @@
/logs /logs
*.txt *.txt
/unimap_logs /unimap_logs
/ghbinaries

2
.vscode/launch.json vendored
View file

@ -9,7 +9,7 @@
"request": "launch", "request": "launch",
"name": "Debug", "name": "Debug",
"program": "${workspaceRoot}/target/debug/${workspaceRootFolderName}", "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}", "cwd": "${workspaceRoot}",
"sourceLanguages": [ "sourceLanguages": [
"rust" "rust"

2235
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[package] [package]
name = "unimap" name = "unimap"
version = "0.6.0" version = "0.7.0"
authors = ["Eduard Tolosa <edu4rdshl@protonmail.com>"] authors = ["Eduard Tolosa <edu4rdshl@protonmail.com>"]
edition = "2018" edition = "2018"
description = "Scan only once by IP address and reduce scan times with Nmap for large amounts of data." 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 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
trust-dns-resolver = "0.20.4" hickory-resolver = "0.25.2"
rayon = "1.7.0" rayon = "1.10.0"
log = { version = "0.4.20", features = ["std"] } log = { version = "0.4.27", features = ["std"] }
colored = { version = "2.0.4", optional = true } colored = { version = "3.0.0", optional = true }
rand = "0.8.5" rand = "0.9.2"
lazy_static = "1.4.0" lazy_static = "1.5.0"
serde = { version = "1.0.186", features = ["derive"] } tokio = { version = "1.47.0", features = ["rt"] }
serde_derive = "1.0.186" futures = "0.3"
prettytable-rs = "0.8.0" serde = { version = "1.0.219", features = ["derive"] }
serde-xml-rs = "0.5.1" serde_derive = "1.0.219"
chrono = "0.4.26" prettytable-rs = "0.10.0"
clap = { version = "2.34.0", features = ["yaml"] } serde-xml-rs = "0.8.1"
config = { version = "0.11.0", features = ["yaml", "json", "toml", "hjson", "ini"] } 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" 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] [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] [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] [target.armv7-unknown-linux-gnueabihf.dependencies]
openssl = { version = "0.10.56", features = ["vendored"] } openssl = { version = "0.10.73", features = ["vendored"] }
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
atty = "0.2.14" atty = "0.2.14"
@ -49,5 +47,4 @@ winapi = { version = "0.3.9", features = ["handleapi", "winbase"] }
lto = true lto = true
codegen-units = 1 codegen-units = 1
panic = 'abort' panic = 'abort'
incremental = false strip = true
opt-level = "s"

View file

@ -1,5 +1,4 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Rusolver releaser
NAME="unimap" NAME="unimap"
LINUX_TARGET="x86_64-unknown-linux-musl" 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" WIN_TARGET="x86_64-pc-windows-gnu"
ARMV7_TARGET="armv7-unknown-linux-gnueabihf" ARMV7_TARGET="armv7-unknown-linux-gnueabihf"
AARCH_TARGET="aarch64-unknown-linux-gnu" AARCH_TARGET="aarch64-unknown-linux-gnu"
OSX_TARGET="x86_64-apple-darwin" # OSX_TARGET="x86_64-apple-darwin"
MANPAGE_DIR="./$NAME.1" MANPAGE_DIR="./$NAME.1"
BIN_OUTPUT_DIR="./ghbinaries"
if ! systemctl is-active docker >/dev/null 2>&1; then rm -rf "$BIN_OUTPUT_DIR"
echo "Docker is not running. Starting docker." mkdir -p "$BIN_OUTPUT_DIR"
if ! sudo systemctl start docker; then
echo "Failed to start docker."
exit 1
fi
fi
# Linux build # Linux build
echo "Building Linux artifact." 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" echo "Linux artifact build: SUCCESS"
cp "target/$LINUX_TARGET/release/$NAME" "target/$LINUX_TARGET/release/$NAME-linux" cp "target/$LINUX_TARGET/release/$NAME" "target/$LINUX_TARGET/release/$NAME-linux"
strip "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 else
echo "Linux artifact build: FAILED" echo "Linux artifact build: FAILED"
fi fi
@ -35,7 +31,8 @@ if cross build -q --release --target="$LINUX_X86_TARGET"; then
echo "Linux x86 artifact build: SUCCESS" echo "Linux x86 artifact build: SUCCESS"
cp "target/$LINUX_X86_TARGET/release/$NAME" "target/$LINUX_X86_TARGET/release/$NAME-linux-i386" cp "target/$LINUX_X86_TARGET/release/$NAME" "target/$LINUX_X86_TARGET/release/$NAME-linux-i386"
strip "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 else
echo "Linux x86 artifact build: FAILED" echo "Linux x86 artifact build: FAILED"
fi fi
@ -46,7 +43,8 @@ if cross build -q --release --target="$WIN_TARGET"; then
echo "Windows artifact build: SUCCESS" echo "Windows artifact build: SUCCESS"
cp "target/$WIN_TARGET/release/$NAME.exe" "target/$WIN_TARGET/release/$NAME-windows.exe" cp "target/$WIN_TARGET/release/$NAME.exe" "target/$WIN_TARGET/release/$NAME-windows.exe"
strip "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 else
echo "Windows artifact build: FAILED" echo "Windows artifact build: FAILED"
fi fi
@ -57,7 +55,8 @@ if cross build -q --release --target="$ARMV7_TARGET"; then
echo "ARMv7 artifact build: SUCCESS" echo "ARMv7 artifact build: SUCCESS"
cp "target/$ARMV7_TARGET/release/$NAME" "target/$ARMV7_TARGET/release/$NAME-armv7" cp "target/$ARMV7_TARGET/release/$NAME" "target/$ARMV7_TARGET/release/$NAME-armv7"
strip "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 else
echo "ARMv7 artifact build: FAILED" echo "ARMv7 artifact build: FAILED"
fi fi
@ -68,41 +67,31 @@ if cross build -q --release --target="$AARCH_TARGET"; then
echo "Aarch64 artifact build: SUCCESS" echo "Aarch64 artifact build: SUCCESS"
cp "target/$AARCH_TARGET/release/$NAME" "target/$AARCH_TARGET/release/$NAME-aarch64" cp "target/$AARCH_TARGET/release/$NAME" "target/$AARCH_TARGET/release/$NAME-aarch64"
strip "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 else
echo "Aarch64 artifact build: FAILED" echo "Aarch64 artifact build: FAILED"
fi fi
# Mac OS build # # Mac OS build
echo "Building OSX artifact." # echo "Building OSX artifact."
if CC=o64-clang CXX=o64-clang++ LIBZ_SYS_STATIC=1 cargo build -q --release --target="$OSX_TARGET"; then # if CC=o64-clang CXX=o64-clang++ LIBZ_SYS_STATIC=1 cargo build -q --release --target="$OSX_TARGET"; then
echo "OSX artifact build: SUCCESS" # echo "OSX artifact build: SUCCESS"
cp "target/$OSX_TARGET/release/$NAME" "target/$OSX_TARGET/release/$NAME-osx" # cp "target/$OSX_TARGET/release/$NAME" "target/$OSX_TARGET/release/$NAME-osx"
strip "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" # sha512sum "target/$OSX_TARGET/release/$NAME-osx" >"target/$OSX_TARGET/release/$NAME-osx.sha512"
else # zip -q "$BIN_OUTPUT_DIR/$NAME-osx.zip" "target/$OSX_TARGET/release/$NAME-osx"
echo "OSX artifact build: FAILED" # else
fi # echo "OSX artifact build: FAILED"
# fi
echo "Creating manpage..." echo "Creating manpage..."
if command -v help2man >/dev/null; then if command -v help2man >/dev/null; then
if help2man -o "$MANPAGE_DIR" "target/$LINUX_TARGET/release/$NAME"; 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 else
echo "Error creating manpage." echo "Error creating manpage."
fi fi
else else
echo "Please install the help2man package." echo "Please install the help2man package."
fi 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

View file

@ -1,72 +1,167 @@
use { use {
crate::{ crate::{defaults, logic::validate_target, misc::sanitize_target_string},
defaults,
logic::validate_target,
misc::{return_matches_vec, sanitize_target_string},
structs::Args,
},
chrono::Utc, chrono::Utc,
clap::{load_yaml, value_t, App}, clap::Parser,
std::{collections::HashSet, time::Instant}, std::{collections::HashSet, time::Instant},
}; };
#[allow(clippy::cognitive_complexity)] /// Scan only once by IP address and reduce scan times with Nmap for large amounts of data.
pub fn get_args() -> Args { #[derive(Parser, Debug, Clone)]
let yaml = load_yaml!("cli.yml"); #[command(author = "Eduard Tolosa <edu4rdshl@protonmail.com>", version, about, long_about = None)]
let matches = App::from_yaml(yaml) #[command(arg_required_else_help = true)]
.version(clap::crate_version!()) pub struct Args {
.get_matches(); /// Target host
#[arg(short, long, conflicts_with_all = ["files", "stdin"])]
pub target: Option<String>,
Args { /// Use a list of targets written in a file as input
target: { #[arg(short, long, conflicts_with_all = ["target", "stdin"])]
let target = sanitize_target_string( pub files: Vec<String>,
value_t!(matches, "target", String).unwrap_or_else(|_| String::new()),
); /// Write to an output file. The name of the output file will be unimap-log-date
if validate_target(&target) { #[arg(short, long, conflicts_with = "unique_output")]
target 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 { } else {
String::new() String::new()
} }
}, });
file_name: if matches.is_present("output") {
value_t!(matches, "logs-dir", String).unwrap_or_else(|_| "unimap_logs".to_string()) let file_name = if self.output {
+ "/" format!(
+ "unimap" "{}/unimap{}",
+ &Utc::now().format("-log-%Y-%m-%d_%H-%M-%S").to_string() self.logs_dir,
+ ".csv" Utc::now().format("-log-%Y-%m-%d_%H-%M-%S")
} else if matches.is_present("unique-output") { ) + ".csv"
matches.value_of("unique-output").unwrap().to_string() } else if let Some(unique_output) = &self.unique_output {
unique_output.clone()
} else { } else {
String::new() String::new()
}, };
logs_dir: value_t!(matches, "logs-dir", String)
.unwrap_or_else(|_| "unimap_logs".to_string()), let threads = if self.ports.is_some() && self.threads.is_none() {
threads: if matches.is_present("ports") && !matches.is_present("threads") {
30 30
} else { } else {
value_t!(matches, "threads", usize).unwrap_or_else(|_| 50) self.threads.unwrap_or(50)
}, };
version: clap::crate_version!().to_string(),
ports: value_t!(matches, "ports", String).unwrap_or_else(|_| String::new()), let custom_resolvers_flag = !self.custom_resolvers.is_empty();
with_output: matches.is_present("output") || matches.is_present("unique-output"), let custom_ports_range = self.ports.is_some();
unique_output_flag: matches.is_present("unique-output"), let ports_value = self.ports.unwrap_or_default();
from_file_flag: matches.is_present("files"),
quiet_flag: matches.is_present("quiet"), let resolvers = if custom_resolvers_flag {
custom_resolvers: matches.is_present("custom-resolvers"), self.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")
} else { } else {
defaults::ipv4_resolvers() 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(), targets: HashSet::new(),
time_wasted: Instant::now(), 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,
}

View file

@ -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

View file

@ -16,12 +16,6 @@ pub fn ipv4_resolvers() -> Vec<String> {
// Verisign // Verisign
"64.6.64.6", "64.6.64.6",
"64.6.65.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() .into_iter()
.map(str::to_owned) .map(str::to_owned)

View file

@ -1,5 +1,8 @@
use { use {
crate::{errors::*, structs::Args}, crate::{
args::ProcessedArgs,
errors::{Result, ResultExt},
},
log::error, log::error,
prettytable::Table, prettytable::Table,
std::{ 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(); let mut targets: Vec<String> = Vec::new();
files.sort(); files.sort();
files.dedup(); files.dedup();
for f in files { for f in files {
match File::open(&f) { match File::open(&f) {
Ok(file) => { 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); targets.push(target);
} }
} }
Err(e) => { Err(e) => {
if args.files.len() == 1 { 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) std::process::exit(1)
} else if !args.quiet_flag { } else if !args.quiet_flag {
error!( error!("Can not open file {f}, working with next file. Error: {e}\n");
"Can not open file {}, working with next file. Error: {}\n",
f, e
);
} }
} }
} }
@ -44,7 +48,8 @@ pub fn table_to_file(table: &Table, file_name: Option<std::fs::File>) -> Result<
Ok(()) 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 { if args.file_name.is_empty() || !args.with_output {
None None
} else { } 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 { pub fn check_full_path(full_path: &str) -> bool {
(Path::new(full_path).exists() && Path::new(full_path).is_dir()) (Path::new(full_path).exists() && Path::new(full_path).is_dir())
|| fs::create_dir_all(full_path).is_ok() || 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 { for file in paths {
if Path::new(&file).exists() { if Path::new(&file).exists() {
match std::fs::remove_file(file) { match std::fs::remove_file(file) {
Ok(_) => (), Ok(()) => (),
Err(e) => error!("Error deleting the file {}. Description: {}", &file, e), 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())?; file.write_all(data.as_bytes())?;
Ok(()) Ok(())
} }

View file

@ -1,6 +1,5 @@
#[cfg(windows)] #[cfg(windows)]
extern crate atty; extern crate atty;
#[cfg(feature = "chrono")]
extern crate chrono; extern crate chrono;
#[cfg(feature = "colored")] #[cfg(feature = "colored")]
extern crate colored; extern crate colored;
@ -8,7 +7,6 @@ extern crate log;
#[cfg(windows)] #[cfg(windows)]
extern crate winapi; extern crate winapi;
#[cfg(feature = "chrono")]
use chrono::Local; use chrono::Local;
#[cfg(feature = "colored")] #[cfg(feature = "colored")]
use colored::*; use colored::*;
@ -23,10 +21,10 @@ impl Log for SimpleLogger {
} }
fn log(&self, record: &Record) { fn log(&self, record: &Record) {
let target = if !record.target().is_empty() { let target = if record.target().is_empty() {
record.target()
} else {
record.module_path().unwrap_or_default() record.module_path().unwrap_or_default()
} else {
record.target()
}; };
if target.contains("unimap") && self.enabled(record.metadata()) { if target.contains("unimap") && self.enabled(record.metadata()) {
let level_string = { let level_string = {
@ -45,7 +43,6 @@ impl Log for SimpleLogger {
record.level().to_string() record.level().to_string()
} }
}; };
#[cfg(feature = "chrono")]
{ {
print!( print!(
"\n{} [{}] {}", "\n{} [{}] {}",
@ -54,10 +51,6 @@ impl Log for SimpleLogger {
record.args() record.args()
); );
} }
#[cfg(not(feature = "chrono"))]
{
print!("\n[{}] {}", level_string, record.args());
}
} }
} }

View file

@ -9,7 +9,7 @@ pub fn validate_target(target: &str) -> bool {
!target.starts_with('.') !target.starts_with('.')
&& target.contains('.') && target.contains('.')
&& !target.contains(&SPECIAL_CHARS[..]) && !target.contains(&SPECIAL_CHARS[..])
&& target.chars().all(|c| c.is_ascii()) && target.is_ascii()
} }
pub fn null_ip_checker(ip: &str) -> String { pub fn null_ip_checker(ip: &str) -> String {

View file

@ -1,27 +1,30 @@
use { use {
clap::Parser,
log::{error, Level}, log::{error, Level},
std::{collections::HashSet, iter::FromIterator}, 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<()> { fn run() -> Result<()> {
if std::env::var("UNIMAP_LOG_LEVEL").is_ok() { if std::env::var("UNIMAP_LOG_LEVEL").is_ok() {
logger::init_by_env() logger::init_by_env();
} else { } 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() { if !arguments.files.is_empty() {
arguments.targets = 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() { } else if !arguments.target.is_empty() {
arguments.targets.insert(arguments.target.clone()); arguments.targets.insert(arguments.target.clone());
} else { } else {
arguments.targets = misc::read_stdin() arguments.targets = misc::read_stdin();
} }
if arguments.targets.len() < 50 { if arguments.targets.len() < 50 {
arguments.threads = arguments.targets.len() arguments.threads = arguments.targets.len();
} }
rayon::ThreadPoolBuilder::new() rayon::ThreadPoolBuilder::new()
@ -29,19 +32,19 @@ fn run() -> Result<()> {
.build_global() .build_global()
.unwrap(); .unwrap();
if !arguments.targets.is_empty() { if arguments.targets.is_empty() {
resolver_engine::parallel_resolver_all(&mut arguments)
} else {
error!("Error: Target is empty or invalid!\n"); error!("Error: Target is empty or invalid!\n");
std::process::exit(1) std::process::exit(1)
} else {
resolver_engine::parallel_resolver_all(&mut arguments)
} }
} }
fn main() { fn main() {
if let Err(err) = run() { if let Err(err) = run() {
error!("Error: {}", err); error!("Error: {err}");
for cause in err.iter_chain().skip(1) { for cause in err.iter_chain().skip(1) {
error!("Error description: {}\n", cause); error!("Error description: {cause}\n");
} }
std::process::exit(1); std::process::exit(1);
} }

View file

@ -3,6 +3,7 @@ use std::{
io::{self, Read}, io::{self, Read},
}; };
#[must_use]
pub fn sanitize_target_string(target: String) -> String { pub fn sanitize_target_string(target: String) -> String {
target target
.replace("www.", "") .replace("www.", "")
@ -11,31 +12,6 @@ pub fn sanitize_target_string(target: String) -> String {
.replace('/', "") .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> { pub fn read_stdin() -> HashSet<String> {
let mut buffer = String::new(); let mut buffer = String::new();
let mut stdin = io::stdin(); let mut stdin = io::stdin();

View file

@ -1,43 +1,43 @@
use { use {
crate::{files, structs::Args}, crate::{args::ProcessedArgs, files},
std::{collections::HashSet, net::SocketAddr}, hickory_resolver::{
trust_dns_resolver::{ config::{NameServerConfig, NameServerConfigGroup, ResolverConfig, ResolverOpts},
config::{NameServerConfig, NameServerConfigGroup, Protocol, ResolverConfig, ResolverOpts}, name_server::TokioConnectionProvider,
Resolver, proto::xfer::Protocol,
TokioResolver,
}, },
std::{collections::HashSet, net::SocketAddr},
}; };
pub fn get_records(resolver: &Resolver, domain: &str) -> String { pub fn get_records(resolver: &TokioResolver, domain: &str) -> String {
if let Ok(ips) = resolver.ipv4_lookup(domain) { futures::executor::block_on(resolver.ipv4_lookup(domain)).map_or_else(
|_| String::new(),
|ips| {
ips.iter() ips.iter()
.map(|x| x.to_string()) .map(std::string::ToString::to_string)
.next() .next()
.expect("Failed to get IPV4.") .expect("Failed to get IPV4.")
} else { },
String::new() )
}
} }
pub fn get_resolver(nameserver_ips: HashSet<SocketAddr>, opts: ResolverOpts) -> Resolver { pub fn get_resolver(nameserver_ips: HashSet<SocketAddr>, opts: ResolverOpts) -> TokioResolver {
let mut name_servers = NameServerConfigGroup::with_capacity(nameserver_ips.len() * 2); let mut name_servers = NameServerConfigGroup::with_capacity(nameserver_ips.len());
name_servers.extend(nameserver_ips.into_iter().flat_map(|socket_addr| { name_servers.extend(
std::iter::once(NameServerConfig { nameserver_ips
socket_addr, .into_iter()
protocol: Protocol::Udp, .map(|socket_addr| NameServerConfig::new(socket_addr, Protocol::Udp)),
tls_dns_name: None, );
trust_nx_responses: false,
}) TokioResolver::builder_with_config(
.chain(std::iter::once(NameServerConfig { ResolverConfig::from_parts(None, vec![], name_servers),
socket_addr, TokioConnectionProvider::default(),
protocol: Protocol::Tcp, )
tls_dns_name: None, .with_options(opts)
trust_nx_responses: false, .build()
}))
}));
Resolver::new(ResolverConfig::from_parts(None, vec![], name_servers), opts).unwrap()
} }
pub fn return_socket_address(args: &Args) -> HashSet<SocketAddr> { pub fn return_socket_address(args: &ProcessedArgs) -> HashSet<SocketAddr> {
let mut resolver_ips = HashSet::new(); let mut resolver_ips = HashSet::new();
if args.custom_resolvers { 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()) {

View file

@ -1,18 +1,8 @@
use { use {
crate::resolver_engine,
log::error, 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)] #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Root { pub struct Root {
@ -114,12 +104,19 @@ pub fn get_nmap_data(
min_rate: &str, min_rate: &str,
ports: &str, ports: &str,
fast_scan: bool, fast_scan: bool,
resolvers: &[Ipv4Addr],
) -> Result<Nmaprun, serde_xml_rs::Error> { ) -> Result<Nmaprun, serde_xml_rs::Error> {
let min_rate = min_rate.to_string(); 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![ let mut nmap_args = vec![
"nmap", "nmap",
"--dns-servers", "--dns-servers",
&NMAP_DNS_RESOLVERS, &nmap_dns_resolvers,
"-Pn", "-Pn",
"-sS", "-sS",
"--open", "--open",
@ -132,17 +129,17 @@ pub fn get_nmap_data(
]; ];
if !min_rate.is_empty() { 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 { if fast_scan {
nmap_args.append(&mut vec!["--host-timeout", "20m"]) nmap_args.append(&mut vec!["--host-timeout", "20m"]);
} else { } else {
nmap_args.append(&mut vec!["-sV"]) nmap_args.append(&mut vec!["-sV"]);
} }
if !ports.is_empty() { if !ports.is_empty() {
nmap_args.append(&mut vec!["-p", ports]) nmap_args.append(&mut vec!["-p", ports]);
} }
nmap_args.push(host); nmap_args.push(host);
@ -159,8 +156,7 @@ pub fn get_nmap_data(
} }
Err(e) => { Err(e) => {
error!( error!(
"Error waiting command to finish for {}, continuing with remaining hosts. Description: {}", "Error waiting command to finish for {host}, continuing with remaining hosts. Description: {e}"
host, e
); );
Ok(Nmaprun::default()) Ok(Nmaprun::default())
} }

View file

@ -1,11 +1,12 @@
use { use {
crate::{ crate::{
args, args::ProcessedArgs,
errors::*, errors::Result,
files, logic, networking, files, logic, networking,
nmap::{self, Nmaprun}, nmap::{self, Nmaprun},
structs::{Args, ResolvData}, structs::ResolvData,
}, },
hickory_resolver::config::{LookupIpStrategy, ResolverOpts},
log::{error, info}, log::{error, info},
prettytable, prettytable,
prettytable::Table, prettytable::Table,
@ -15,61 +16,55 @@ use {
net::Ipv4Addr, net::Ipv4Addr,
time::Duration, time::Duration,
}, },
trust_dns_resolver::{
config::{LookupIpStrategy, ResolverOpts},
Resolver,
},
}; };
lazy_static! { fn create_resolvers(args: &ProcessedArgs) -> Vec<Ipv4Addr> {
pub static ref RESOLVERS: Vec<Ipv4Addr> = {
let args = args::get_args();
let mut resolver_ips = Vec::new(); let mut resolver_ips = Vec::new();
if args.custom_resolvers { 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>() { match r.parse::<Ipv4Addr>() {
Ok(ip) => resolver_ips.push(ip), Ok(ip) => resolver_ips.push(ip),
Err(e) => { 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) std::process::exit(1)
} }
} }
} }
} else { } else {
for r in args.resolvers { for r in &args.resolvers {
match r.parse::<Ipv4Addr>() { match r.parse::<Ipv4Addr>() {
Ok(ip) => resolver_ips.push(ip), Ok(ip) => resolver_ips.push(ip),
Err(e) => { 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) std::process::exit(1)
} }
} }
} }
} }
resolver_ips resolver_ips
};
} }
pub fn parallel_resolver_all(args: &mut Args) -> Result<()> { pub fn parallel_resolver_all(args: &mut ProcessedArgs) -> Result<()> {
files::check_full_path(&args.logs_dir); 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 { if !args.quiet_flag {
info!( info!(
"Performing parallel resolution for {} targets with {} threads, it will take a while...\n", "Performing parallel resolution for {} targets with {} threads, it will take a while...\n",
args.targets.len(), args.threads args.targets.len(), args.threads
) );
} }
let opts = ResolverOpts { let mut opts = ResolverOpts::default();
timeout: Duration::from_secs(1), opts.timeout = Duration::from_secs(1);
ip_strategy: LookupIpStrategy::Ipv4Only, opts.ip_strategy = LookupIpStrategy::Ipv4Only;
num_concurrent_reqs: 1, opts.num_concurrent_reqs = 1;
..Default::default()
};
let resolver = networking::get_resolver(networking::return_socket_address(args), opts); 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(); let mut table = Table::new();
table.set_titles(row![ table.set_titles(row![
@ -79,9 +74,9 @@ pub fn parallel_resolver_all(args: &mut Args) -> Result<()> {
"SERVICES" "SERVICES"
]); ]);
if args.raw_output && !args.quiet_flag { 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 { } else if args.url_output && !args.quiet_flag {
println!("HOST:IP") println!("HOST:IP");
} }
for (target, resolv_data) in &data { for (target, resolv_data) in &data {
if !resolv_data.ip.is_empty() { if !resolv_data.ip.is_empty() {
@ -120,11 +115,11 @@ pub fn parallel_resolver_all(args: &mut Args) -> Result<()> {
.extrainfo .extrainfo
.clone() .clone()
.unwrap_or_else(|| "NULL".to_string()) .unwrap_or_else(|| "NULL".to_string())
) );
} }
} else if args.url_output { } else if args.url_output {
for port_data in &resolv_data.ports_data { for port_data in &resolv_data.ports_data {
println!("{}:{}", target, port_data.portid) println!("{}:{}", target, port_data.portid);
} }
} else { } else {
let mut services_table = Table::new(); let mut services_table = Table::new();
@ -179,7 +174,7 @@ pub fn parallel_resolver_all(args: &mut Args) -> Result<()> {
error!( error!(
"An error occurred while writing the output file {}.\n", "An error occurred while writing the output file {}.\n",
args.file_name args.file_name
) );
} }
if !args.quiet_flag && !args.raw_output && !args.url_output { if !args.quiet_flag && !args.raw_output && !args.url_output {
table.printstd(); table.printstd();
@ -199,36 +194,51 @@ pub fn parallel_resolver_all(args: &mut Args) -> Result<()> {
} }
fn parallel_resolver_engine( fn parallel_resolver_engine(
args: &Args, args: &ProcessedArgs,
targets: HashSet<String>, targets: &HashSet<String>,
resolver: Resolver, resolver: &hickory_resolver::TokioResolver,
) -> HashMap<String, ResolvData> { ) -> HashMap<String, ResolvData> {
let resolv_data: HashMap<String, ResolvData> = targets let resolv_data: HashMap<String, ResolvData> = targets
.par_iter() .par_iter()
.map(|target| { .map(|target| {
let fqdn_target = format!("{}.", target); let fqdn_target = format!("{target}.");
let mut resolv_data = ResolvData::default(); 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) (target.to_owned(), resolv_data)
}) })
.collect(); .collect();
let resolvers = create_resolvers(args);
let mut nmap_ips: HashSet<String> = resolv_data let mut nmap_ips: HashSet<String> = resolv_data
.values() .values()
.map(|resolv_data| resolv_data.ip.clone()) .map(|resolv_data| resolv_data.ip.clone())
.collect(); .collect();
let nmap_ips_orig = nmap_ips.clone();
nmap_ips.retain(|ip| { nmap_ips.retain(|ip| {
!ip.is_empty() !ip.is_empty()
&& ip.parse::<Ipv4Addr>().is_ok()
&& !ip.parse::<Ipv4Addr>().unwrap().is_private() && !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 let nmap_data: HashMap<String, Nmaprun> = nmap_ips
.par_iter() .par_iter()
.map(|ip| { .map(|ip| {
let filename = format!("{}/{}.xml", &args.logs_dir, &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) => { Ok(nmap_data) => {
nmap_data nmap_data
.host .host
@ -239,7 +249,7 @@ fn parallel_resolver_engine(
.port .port
.retain(|f| f.state.state == "open"); .retain(|f| f.state.state == "open");
if args.no_keep_nmap_logs && std::fs::remove_file(&filename).is_err() { 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) (ip.clone(), nmap_data)
} }
@ -253,7 +263,7 @@ fn parallel_resolver_engine(
// Delete the args.logs_dir directory if it's empty // 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() { 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 resolv_data
@ -282,3 +292,4 @@ fn parallel_resolver_engine(
}) })
.collect() .collect()
} }
}

View file

@ -1,33 +1,4 @@
use { use crate::nmap::Port;
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,
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)] #[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct ResolvData { pub struct ResolvData {
@ -35,8 +6,8 @@ pub struct ResolvData {
pub ports_data: Vec<Port>, pub ports_data: Vec<Port>,
} }
impl ResolvData { impl ResolvData {
pub fn default() -> ResolvData { pub const fn default() -> Self {
ResolvData { Self {
ip: String::new(), ip: String::new(),
ports_data: Vec::new(), ports_data: Vec::new(),
} }

106
unimap.1
View file

@ -1,81 +1,73 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.48.3. .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH UNIMAP "1" "May 2021" "Unimap 0.5.1" "User Commands" .TH UNIMAP "1" "August 2025" "unimap 0.7.0" "User Commands"
.SH NAME .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 .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. Scan only once by IP address and reduce scan times with Nmap for large amounts of data.
.SS "USAGE:" .SH OPTIONS
.IP
unimap [FLAGS] [OPTIONS]
.SS "FLAGS:"
.TP .TP
\fB\-\-fast\-scan\fR \fB\-t\fR, \fB\-\-target\fR <TARGET>
Use fast scanning for ports (no version detection). Target host
.TP .TP
\fB\-h\fR, \fB\-\-help\fR \fB\-f\fR, \fB\-\-files\fR <FILES>
Prints help information Use a list of targets written in a file as input
.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 .TP
\fB\-o\fR, \fB\-\-output\fR \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 .TP
\fB\-q\fR, \fB\-\-quiet\fR \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 .TP
\fB\-r\fR, \fB\-\-raw\-output\fR \fB\-r\fR, \fB\-\-raw\-output\fR
Use raw output instead of a table. Use raw output instead of a table
.TP
\fB\-\-stdin\fR
Read from stdin instead of files or aguments.
.TP .TP
\fB\-\-url\-output\fR \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 .TP
\fB\-V\fR, \fB\-\-version\fR \fB\-V\fR, \fB\-\-version\fR
Prints version information Print version
.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.
.SH "SEE ALSO" .SH "SEE ALSO"
The full documentation for The full documentation for
.B Unimap .B unimap
is maintained as a Texinfo manual. If the is maintained as a Texinfo manual. If the
.B info .B info
and and
.B Unimap .B unimap
programs are properly installed at your site, the command programs are properly installed at your site, the command
.IP .IP
.B info Unimap .B info unimap
.PP .PP
should give you access to the complete manual. should give you access to the complete manual.