Skip to content
Open
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ once_cell = "1.21.4"
parameterized = "2.0.0"
wait-timeout = "0.2"
criterion = { version = "0.8", features = ["html_reports"] }
serde_json = "1.0"

[package.metadata.deb]
depends = "$auto, nmap"
Expand Down
10 changes: 10 additions & 0 deletions examples/default_silent.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// Example that verifies the library is silent by default.
/// Does NOT call enable_output() — all output macros should produce
/// nothing on stdout. Built and spawned by the integration test in
/// tests/stdout_suppression.rs.
fn main() {
// Do NOT call enable_output() — library must be silent by default.
rustscan::warning!("DEFAULT_SILENT_WARNING");
rustscan::detail!("DEFAULT_SILENT_DETAIL");
rustscan::output!("DEFAULT_SILENT_OUTPUT");
}
19 changes: 19 additions & 0 deletions examples/stdout_check.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/// Example that exercises output macros with and without suppress_output().
/// Built and spawned by the integration test in tests/stdout_suppression.rs.
fn main() {
// Enable output first (it is suppressed by default for library consumers).
rustscan::enable_output();

// Before suppression: these markers should appear in stdout.
rustscan::warning!("PRE_SUPPRESS_WARNING");
rustscan::detail!("PRE_SUPPRESS_DETAIL");
rustscan::output!("PRE_SUPPRESS_OUTPUT");

// Suppress all further library output.
rustscan::suppress_output();

// After suppression: these markers must NOT appear in stdout.
rustscan::warning!("POST_SUPPRESS_WARNING");
rustscan::detail!("POST_SUPPRESS_DETAIL");
rustscan::output!("POST_SUPPRESS_OUTPUT");
}
4 changes: 3 additions & 1 deletion src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,9 @@ impl Config {
let config: Config = match toml::from_str(&content) {
Ok(config) => config,
Err(e) => {
println!("Found {e} in configuration file.\nAborting scan.\n");
if !crate::SUPPRESS_STDOUT.load(std::sync::atomic::Ordering::Relaxed) {
println!("Found {e} in configuration file.\nAborting scan.\n");
}
std::process::exit(1);
}
};
Expand Down
78 changes: 78 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,44 @@
//! ```
#![allow(clippy::needless_doctest_main)]

use std::sync::atomic::{AtomicBool, Ordering};

/// When `true`, all stdout output from the library is suppressed.
/// Defaults to `true` so that library consumers get silent output.
/// The CLI entry point (`main.rs`) calls [`enable_output()`] at startup
/// to restore normal terminal printing.
#[doc(hidden)]
pub static SUPPRESS_STDOUT: AtomicBool = AtomicBool::new(true);

/// Suppress all stdout output from the library.
///
/// Output is suppressed by default. Call this only after calling
/// [`enable_output()`] if you need to re-silence output mid-session.
/// Has no effect on `log` crate logging (controlled by `RUST_LOG`).
///
/// ```ignore
/// rustscan::enable_output();
/// // ... print something ...
/// rustscan::suppress_output();
/// // ... output is silent again
/// ```
pub fn suppress_output() {
SUPPRESS_STDOUT.store(true, Ordering::Relaxed);
}

/// Enable stdout output from the library.
///
/// Output is suppressed by default. The CLI calls this at startup;
/// library consumers should call this only if they want terminal output.
///
/// ```ignore
/// rustscan::enable_output();
/// // ... output will now print to stdout
/// ```
pub fn enable_output() {
SUPPRESS_STDOUT.store(false, Ordering::Relaxed);
}

pub mod tui;

pub mod input;
Expand All @@ -56,3 +94,43 @@ pub mod scripts;
pub mod address;

pub mod generated;

#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::Ordering;

/// Sequential test covering all toggle behaviours on the shared
/// `SUPPRESS_STDOUT` static. Individual `#[test]` functions would
/// race under parallel test execution; a single test runs them in
/// the order shown and is safe with any `--test-threads` setting.
///
/// The actual stdout-suppression effect is verified by the
/// integration tests in tests/stdout_suppression.rs, which spawn
/// subprocesses and therefore have no shared state.
#[test]
fn toggle_flag_behavior() {
// Save original value to avoid contaminating parallel unit tests
// that share the process-global AtomicBool.
let original = SUPPRESS_STDOUT.load(Ordering::Relaxed);

// --- suppress_output sets the flag ---
SUPPRESS_STDOUT.store(false, Ordering::Relaxed);
suppress_output();
assert!(SUPPRESS_STDOUT.load(Ordering::Relaxed));

// --- suppress_output is idempotent ---
SUPPRESS_STDOUT.store(false, Ordering::Relaxed);
suppress_output();
suppress_output();
assert!(SUPPRESS_STDOUT.load(Ordering::Relaxed));

// --- enable_output clears the flag ---
SUPPRESS_STDOUT.store(true, Ordering::Relaxed);
enable_output();
assert!(!SUPPRESS_STDOUT.load(Ordering::Relaxed));

// Restore original value so parallel tests see the expected state.
SUPPRESS_STDOUT.store(original, Ordering::Relaxed);
}
}
3 changes: 3 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ extern crate log;
/// Faster Nmap scanning with Rust
/// If you're looking for the actual scanning, check out the module Scanner
fn main() {
// Enable stdout output for CLI usage (suppressed by default for library consumers).
rustscan::enable_output();

#[cfg(not(unix))]
let _ = ansi_term::enable_ansi_support();

Expand Down
6 changes: 4 additions & 2 deletions src/scanner/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,15 +288,17 @@ impl Scanner {
}
}
Err(e) => {
println!("Err E binding sock {e:?}");
if !crate::SUPPRESS_STDOUT.load(std::sync::atomic::Ordering::Relaxed) {
println!("Err E binding sock {e:?}");
}
Err(e)
}
}
}

/// Formats and prints the port status
fn fmt_ports(&self, socket: SocketAddr) {
if !self.greppable {
if !crate::SUPPRESS_STDOUT.load(std::sync::atomic::Ordering::Relaxed) && !self.greppable {
if self.accessible {
println!("Open {socket}");
} else {
Expand Down
94 changes: 51 additions & 43 deletions src/tui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
#[macro_export]
macro_rules! warning {
($name:expr) => {
println!("{} {}", ansi_term::Colour::Red.bold().paint("[!]"), $name);
if !$crate::SUPPRESS_STDOUT.load(std::sync::atomic::Ordering::Relaxed) {
println!("{} {}", ansi_term::Colour::Red.bold().paint("[!]"), $name);
}
};
($name:expr, $greppable:expr, $accessible:expr) => {
// if not greppable then print, otherwise no else statement so do not print.
if !$greppable {
if !$crate::SUPPRESS_STDOUT.load(std::sync::atomic::Ordering::Relaxed) && !$greppable {
if $accessible {
// Don't print the ascii art
println!("{}", $name);
Expand All @@ -23,11 +25,13 @@ macro_rules! warning {
#[macro_export]
macro_rules! detail {
($name:expr) => {
println!("{} {}", ansi_term::Colour::Blue.bold().paint("[~]"), $name);
if !$crate::SUPPRESS_STDOUT.load(std::sync::atomic::Ordering::Relaxed) {
println!("{} {}", ansi_term::Colour::Blue.bold().paint("[~]"), $name);
}
};
($name:expr, $greppable:expr, $accessible:expr) => {
// if not greppable then print, otherwise no else statement so do not print.
if !$greppable {
if !$crate::SUPPRESS_STDOUT.load(std::sync::atomic::Ordering::Relaxed) && !$greppable {
if $accessible {
// Don't print the ascii art
println!("{}", $name);
Expand All @@ -41,15 +45,17 @@ macro_rules! detail {
#[macro_export]
macro_rules! output {
($name:expr) => {
println!(
"{} {}",
ansi_term::Colour::RGB(0, 255, 9).bold().paint("[>]"),
$name
);
if !$crate::SUPPRESS_STDOUT.load(std::sync::atomic::Ordering::Relaxed) {
println!(
"{} {}",
ansi_term::Colour::RGB(0, 255, 9).bold().paint("[>]"),
$name
);
}
};
($name:expr, $greppable:expr, $accessible:expr) => {
// if not greppable then print, otherwise no else statement so do not print.
if !$greppable {
if !$crate::SUPPRESS_STDOUT.load(std::sync::atomic::Ordering::Relaxed) && !$greppable {
if $accessible {
// Don't print the ascii art
println!("{}", $name);
Expand All @@ -68,39 +74,41 @@ macro_rules! output {
macro_rules! funny_opening {
// prints a funny quote / opening
() => {
use rand::seq::IndexedRandom;
let quotes = vec![
"Nmap? More like slowmap.🐢",
"🌍HACK THE PLANET🌍",
"Real hackers hack time ⌛",
"Please contribute more quotes to our GitHub https://github.com/rustscan/rustscan",
"😵 https://admin.tryhackme.com",
"0day was here ♥",
"I don't always scan ports, but when I do, I prefer RustScan.",
"RustScan: Where scanning meets swagging. 😎",
"To scan or not to scan? That is the question.",
"RustScan: Because guessing isn't hacking.",
"Scanning ports like it's my full-time job. Wait, it is.",
"Open ports, closed hearts.",
"I scanned my computer so many times, it thinks we're dating.",
"Port scanning: Making networking exciting since... whenever.",
"You miss 100% of the ports you don't scan. - RustScan",
"Breaking and entering... into the world of open ports.",
"TCP handshake? More like a friendly high-five!",
"Scanning ports: The virtual equivalent of knocking on doors.",
"RustScan: Making sure 'closed' isn't just a state of mind.",
"RustScan: allowing you to send UDP packets into the void 1200x faster than NMAP",
"Port scanning: Because every port has a story to tell.",
"I scanned ports so fast, even my computer was surprised.",
"Scanning ports faster than you can say 'SYN ACK'",
"RustScan: Where '404 Not Found' meets '200 OK'.",
"RustScan: Exploring the digital landscape, one IP at a time.",
"TreadStone was here 🚀",
"With RustScan, I scan ports so fast, even my firewall gets whiplash 💨",
"Scanning ports so fast, even the internet got a speeding ticket!",
];
let random_quote = quotes.choose(&mut rand::rng()).unwrap();
if !$crate::SUPPRESS_STDOUT.load(std::sync::atomic::Ordering::Relaxed) {
use rand::seq::IndexedRandom;
let quotes = vec![
"Nmap? More like slowmap.🐢",
"🌍HACK THE PLANET🌍",
"Real hackers hack time ⌛",
"Please contribute more quotes to our GitHub https://github.com/rustscan/rustscan",
"😵 https://admin.tryhackme.com",
"0day was here ♥",
"I don't always scan ports, but when I do, I prefer RustScan.",
"RustScan: Where scanning meets swagging. 😎",
"To scan or not to scan? That is the question.",
"RustScan: Because guessing isn't hacking.",
"Scanning ports like it's my full-time job. Wait, it is.",
"Open ports, closed hearts.",
"I scanned my computer so many times, it thinks we're dating.",
"Port scanning: Making networking exciting since... whenever.",
"You miss 100% of the ports you don't scan. - RustScan",
"Breaking and entering... into the world of open ports.",
"TCP handshake? More like a friendly high-five!",
"Scanning ports: The virtual equivalent of knocking on doors.",
"RustScan: Making sure 'closed' isn't just a state of mind.",
"RustScan: allowing you to send UDP packets into the void 1200x faster than NMAP",
"Port scanning: Because every port has a story to tell.",
"I scanned ports so fast, even my computer was surprised.",
"Scanning ports faster than you can say 'SYN ACK'",
"RustScan: Where '404 Not Found' meets '200 OK'.",
"RustScan: Exploring the digital landscape, one IP at a time.",
"TreadStone was here 🚀",
"With RustScan, I scan ports so fast, even my firewall gets whiplash 💨",
"Scanning ports so fast, even the internet got a speeding ticket!",
];
let random_quote = quotes.choose(&mut rand::rng()).unwrap();

println!("{}\n", random_quote);
println!("{}\n", random_quote);
}
};
}
Loading