Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
653 changes: 368 additions & 285 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@ version = "0.3.1"
authors = ["SensorFleet R&D <rd.contact@sensorfleet.com>"]
homepage = "https://github.com/sensorfleet/pscan"
repository = "https://github.com/sensorfleet/pscan"
edition = "2021"
edition = "2024"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
clap={version = "4.4"}
log="0.4"
env_logger="0.11"
cidr= {version = "0.3", features=["std"]}
tokio={version="1", features=["full"]}
serde_json = {version = "1.0"}
Expand All @@ -21,4 +19,6 @@ signal-hook = {version="0.3"}
futures = {version="0.3"}
base64 = "0.22"
rand = "0.9"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

26 changes: 8 additions & 18 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -636,8 +636,7 @@ impl Config {
if let Some(c) = self.concurrent_scans {
if c == 0 {
return Err(Error::Message(format!(
"invalid value for {}: Value needs to be non-zero",
ARG_CONCURRENT_SCANS
"invalid value for {ARG_CONCURRENT_SCANS}: Value needs to be non-zero"
)));
}
} else {
Expand All @@ -646,16 +645,14 @@ impl Config {
if let Some(h) = self.concurrent_hosts {
if h == 0 {
return Err(Error::Message(format!(
"invalid value for {}: Value needs to be non-zero",
ARG_CONCURRENT_HOSTS
"invalid value for {ARG_CONCURRENT_HOSTS}: Value needs to be non-zero"
)));
}
}
if let Some(c) = self.try_count {
if c == 0 {
return Err(Error::Message(format!(
"invalid value for {}: value needs to be non-zero",
ARG_TRY_COUNT
"invalid value for {ARG_TRY_COUNT}: value needs to be non-zero"
)));
}
} else {
Expand All @@ -678,8 +675,7 @@ impl Config {
let fields = missing_fields.join(", ");

return Err(Error::Message(format!(
"missing configuration values for: {}",
fields
"missing configuration values for: {fields}"
)));
}
Ok(())
Expand All @@ -690,12 +686,11 @@ impl Config {
let p = Path::new(file_name);
if !p.exists() {
return Err(Error::Message(format!(
"configuration file {} not found",
file_name
"configuration file {file_name} not found"
)));
}
let data = fs::read_to_string(p)
.map_err(|e| Error::Message(format!("unable to read configuration file: {}", e)))?;
.map_err(|e| Error::Message(format!("unable to read configuration file: {e}")))?;
serde_json::from_str(&data).map_err(|e| Error::Message(e.to_string()))
}
}
Expand Down Expand Up @@ -813,10 +808,7 @@ mod tests {
name: "ports",
arg: &["--ports", "22"],
check: Box::new(|c| {
let p = (0..c.ports.as_ref().unwrap().port_count() as usize)
.map(|i| c.ports.as_ref().unwrap().get(i));
assert_eq!(p.len(), 1);
let ports = p.collect::<Vec<u16>>();
let ports = c.ports.unwrap().into_iter().collect::<Vec<u16>>();
assert_eq!(ports.len(), 1);
ports[0] == 22
}),
Expand Down Expand Up @@ -931,9 +923,7 @@ mod tests {
assert_eq!(addrs.len(), 1);
assert_eq!(addrs[0], IpCidr::from_str("192.168.1.0/24").unwrap());

let pi = (0..cfg.ports.as_ref().unwrap().port_count() as usize)
.map(|i| cfg.ports.as_ref().unwrap().get(i));
let ports = pi.collect::<Vec<u16>>();
let ports = cfg.ports.unwrap().into_iter().collect::<Vec<u16>>();
assert_eq!(ports.len(), 2);
assert!(ports[0] == 1 && ports[1] == 2);

Expand Down
56 changes: 31 additions & 25 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#[macro_use]
extern crate log;

use signal_hook::consts::{SIGINT, SIGTERM};
use signal_hook_tokio::Signals;
use std::net::IpAddr;
use std::sync::{atomic::AtomicBool, Arc};
use std::sync::{Arc, atomic::AtomicBool};
use tokio::sync::mpsc::UnboundedReceiver;

use futures::StreamExt;
Expand Down Expand Up @@ -43,9 +40,9 @@ async fn collect_results(
info.add_closed_port(status.port);
info.add_delay(d);
}
scanner::PortState::Timeout(_) => info.add_filtered_port(status.port),
scanner::PortState::HostDown() => info.mark_down(),
scanner::PortState::NetError() => info.mark_down(),
scanner::PortState::Timeout => info.add_filtered_port(status.port),
scanner::PortState::HostDown => info.mark_down(),
scanner::PortState::NetError => info.mark_down(),
}
}
scanner::ScanInfo::HostScanned(addr) => {
Expand All @@ -57,8 +54,8 @@ async fn collect_results(
}
}
}
trace!("Collector stopping");
return host_infos.drain().map(|(_, v)| v).collect();
tracing::trace!("Collector stopping");
host_infos.into_values().collect()
}

/// Print the results, if `output_file` is `None`, then information is printed
Expand All @@ -85,7 +82,7 @@ async fn output_results(
fn exit_error(message: Option<String>) -> ! {
let mut code = 0;
if let Some(msg) = message {
error!("{}", msg);
tracing::error!("{msg}");
code = 127;
}

Expand All @@ -99,25 +96,34 @@ async fn sighandler(signals: Signals, flag: Arc<AtomicBool>) {
while let Some(sig) = s.next().await {
match sig {
SIGINT | SIGTERM => {
debug!("Received termination signal, setting flag");
tracing::debug!("Received termination signal, setting flag");
flag.store(true, std::sync::atomic::Ordering::SeqCst);
}
_ => warn!("Received unexpected signal"),
_ => tracing::warn!("Received unexpected signal"),
}
}
}

#[tokio::main]
async fn main() {
env_logger::init();
#[cfg(debug_assertions)]
tracing_subscriber::fmt()
.pretty()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.init();
#[cfg(not(debug_assertions))]
tracing_subscriber::fmt()
.with_ansi(false)
.with_writer(std::io::stderr)
.init();

let app = config::build_commandline_args();

let matches = match app.try_get_matches() {
Ok(m) => m,
Err(e) => match e.kind() {
clap::error::ErrorKind::DisplayHelp | clap::error::ErrorKind::DisplayVersion => {
println!("{}", e);
println!("{e}");
exit_error(None);
}
_ => exit_error(Some(e.to_string())),
Expand All @@ -130,41 +136,41 @@ async fn main() {
matches.get_one::<String>(config::ARG_CONFIG_FILE).unwrap(),
) {
Ok(c) => Some(c),
Err(e) => exit_error(Some(format!("Configuration error: {}", e))),
Err(e) => exit_error(Some(format!("Configuration error: {e}"))),
}
} else {
None
};

let cfg = if let Some(cf) = cfg_from_file {
match cf.override_with(&matches) {
Err(e) => exit_error(Some(format!("Configuration error: {}", e))),
Err(e) => exit_error(Some(format!("Configuration error: {e}"))),
Ok(c) => c,
}
} else {
match config::Config::try_from(matches) {
Err(e) => exit_error(Some(format!("Configuration error: {}", e))),
Err(e) => exit_error(Some(format!("Configuration error: {e}"))),
Ok(c) => c,
}
};

// make sure configuration is good
if let Err(e) = cfg.verify() {
exit_error(Some(format!("Configuration error: {}", e)))
exit_error(Some(format!("Configuration error: {e}")))
}

let verbose = cfg.verbose();
let params: scanner::ScanParameters = cfg.as_params();

if params.retry_on_error {
info!("Retry on error set")
tracing::info!("Retry on error set")
}

let (tx, rx) = tokio::sync::mpsc::unbounded_channel();

let signals = match Signals::new([SIGINT, SIGTERM]) {
Ok(h) => h,
Err(e) => exit_error(Some(format!("Unable to register signal handler: {}", e))),
Err(e) => exit_error(Some(format!("Unable to register signal handler: {e}"))),
};
let stop = Arc::new(AtomicBool::new(false));
let handle = signals.handle();
Expand All @@ -182,27 +188,27 @@ async fn main() {
// fatal error, results can not be trusted.
exit_error(Some(e.to_string()));
} else {
error!("Error while scanning: {}", e);
tracing::error!("Error while scanning: {e}");
}
}
match col_result {
Ok(infos) => {
// print results now that scan is complete
if let Err(er) = output_results(&infos, number_of_ports, cfg.json()).await {
error!("Unable to output results: {}", er);
tracing::error!("Unable to output results: {er}");
}
}
Err(error) => {
exit_error(Some(error.to_string()));
}
};
handle.close();
debug!(
tracing::debug!(
"Waiting for sighandler task, stop is {}",
stop.load(std::sync::atomic::Ordering::SeqCst)
);
if let Err(e) = sig_h.await {
warn!("signal handler error: {}", e);
if let Err(err) = sig_h.await {
tracing::warn!(?err, "error in signal handler ");
}
if stop.load(std::sync::atomic::Ordering::SeqCst) {
std::process::exit(2);
Expand Down
38 changes: 20 additions & 18 deletions src/output.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use serde::ser::SerializeMap;
use serde::Serialize;
use serde::ser::SerializeMap;
use std::collections::HashMap;
use std::fmt::Write as _;
use std::fmt::{self, Display};
Expand Down Expand Up @@ -42,14 +42,23 @@ struct Banners {
impl Display for Banners {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut builder = String::new();
builder.push_str("\n\t Banners received from open ports:\n");
builder.push_str("\n\tBanners received from open ports:\n");
for (port, b) in &self.values {
let _ = match std::str::from_utf8(b) {
Ok(s) => write!(builder, "\t\tPort: {} \"{}\"", port, s),
Err(_e) => write!(builder, "\t\tPort: {}: {} bytes of data", port, b.len()),
Ok(s) => {
let pretty: String = s
.chars()
.map(|c| match c.is_control() {
true => '\u{00b7}',
false => c,
})
.collect();
writeln!(builder, "\t\tPort: {port} \"{pretty}\"")
}
Err(_e) => writeln!(builder, "\t\tPort: {}: {} bytes of data", port, b.len()),
};
}
write!(f, "{}", builder)
write!(f, "{builder}")
}
}

Expand Down Expand Up @@ -155,13 +164,7 @@ impl HostInfo {
impl fmt::Display for HostInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut pstr = String::new();
let status = {
if self.down {
"Down"
} else {
"Up"
}
};
let status = { if self.down { "Down" } else { "Up" } };
let _ = write!(
pstr,
"{} is {} \n\t{} Open Ports:",
Expand All @@ -170,7 +173,7 @@ impl fmt::Display for HostInfo {
self.open_ports.len()
);
for port in &self.open_ports {
let _ = write!(pstr, " {}", port);
let _ = write!(pstr, " {port}");
}
let _ = write!(
pstr,
Expand All @@ -188,7 +191,7 @@ impl fmt::Display for HostInfo {
if !self.banners.is_empty() {
pstr.push_str(&self.banners.to_string());
}
write!(f, "{}", pstr)
write!(f, "{pstr}")
}
}
/// `ScanComplete` information to print as JSON. Contains the results of
Expand Down Expand Up @@ -249,11 +252,10 @@ pub fn write_results_to_stdout(
number_of_silent_hosts += 1;
continue;
}
println!("{}\n", info);
println!("{info}\n");
}
println!(
"{} ports on {} hosts scanned, {} hosts did not have open ports, {} hosts reported down by OS",
number_of_ports, number_of_hosts, number_of_silent_hosts, number_of_down_hosts
"{number_of_ports} ports on {number_of_hosts} hosts scanned, {number_of_silent_hosts} hosts did not have open ports, {number_of_down_hosts} hosts reported down by OS"
);
Ok(())
}
Expand All @@ -266,6 +268,6 @@ pub fn write_single_host_info(info: &HostInfo) {
} else if info.open_port_count() == 0 {
println!("{}: no open ports", info.address)
} else {
println!("{}", info)
println!("{info}")
}
}
Loading