Skip to content

Commit 0f8400a

Browse files
committed
iniitial work for also showing closed ports
1 parent e1689e5 commit 0f8400a

File tree

3 files changed

+117
-18
lines changed

3 files changed

+117
-18
lines changed

src/input.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ pub struct Opts {
162162
/// UDP scanning mode, finds UDP ports that send back responses
163163
#[arg(long)]
164164
pub udp: bool,
165+
166+
/// Also show closed Ports
167+
#[arg(long)]
168+
pub closed: bool,
165169
}
166170

167171
#[cfg(not(tarpaulin_include))]
@@ -201,7 +205,7 @@ impl Opts {
201205

202206
merge_required!(
203207
addresses, greppable, accessible, batch_size, timeout, tries, scan_order, scripts,
204-
command, udp
208+
command, udp, closed
205209
);
206210
}
207211

@@ -252,6 +256,7 @@ impl Default for Opts {
252256
exclude_ports: None,
253257
exclude_addresses: None,
254258
udp: false,
259+
closed: false,
255260
}
256261
}
257262
}
@@ -278,6 +283,7 @@ pub struct Config {
278283
exclude_ports: Option<Vec<u16>>,
279284
exclude_addresses: Option<Vec<String>>,
280285
udp: Option<bool>,
286+
closed: Option<bool>,
281287
}
282288

283289
#[cfg(not(tarpaulin_include))]
@@ -295,6 +301,7 @@ impl Config {
295301
/// scan_order = "Serial"
296302
/// exclude_ports = [8080, 9090, 80]
297303
/// udp = false
304+
/// closed = false
298305
///
299306
pub fn read(custom_config_path: Option<PathBuf>) -> Self {
300307
let mut content = String::new();
@@ -353,6 +360,7 @@ mod tests {
353360
exclude_ports: None,
354361
exclude_addresses: None,
355362
udp: Some(false),
363+
closed: Some(false),
356364
}
357365
}
358366
}

src/main.rs

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ extern crate log;
3535
/// Faster Nmap scanning with Rust
3636
/// If you're looking for the actual scanning, check out the module Scanner
3737
fn main() {
38+
use rustscan::scanner::PortStatus;
39+
3840
#[cfg(not(unix))]
3941
let _ = ansi_term::enable_ansi_support();
4042

@@ -93,6 +95,7 @@ fn main() {
9395
opts.accessible,
9496
opts.exclude_ports.unwrap_or_default(),
9597
opts.udp,
98+
opts.closed,
9699
);
97100
debug!("Scanner finished building: {:?}", scanner);
98101

@@ -101,17 +104,28 @@ fn main() {
101104
portscan_bench.end();
102105
benchmarks.push(portscan_bench);
103106

104-
let mut ports_per_ip = HashMap::new();
107+
let mut open_ports_per_ip = HashMap::new();
108+
let mut closed_ports_per_ip = HashMap::new();
105109

106110
for socket in scan_result {
107-
ports_per_ip
108-
.entry(socket.ip())
109-
.or_insert_with(Vec::new)
110-
.push(socket.port());
111+
match socket {
112+
PortStatus::Open(socket) => {
113+
open_ports_per_ip
114+
.entry(socket.ip())
115+
.or_insert_with(Vec::new)
116+
.push(socket.port());
117+
}
118+
PortStatus::Closed(socket) => {
119+
closed_ports_per_ip
120+
.entry(socket.ip())
121+
.or_insert_with(Vec::new)
122+
.push(socket.port());
123+
}
124+
}
111125
}
112126

113127
for ip in ips {
114-
if ports_per_ip.contains_key(&ip) {
128+
if open_ports_per_ip.contains_key(&ip) || closed_ports_per_ip.contains_key(&ip) {
115129
continue;
116130
}
117131

@@ -128,7 +142,63 @@ fn main() {
128142
}
129143

130144
let mut script_bench = NamedTimer::start("Scripts");
131-
for (ip, ports) in &ports_per_ip {
145+
for (ip, ports) in &open_ports_per_ip {
146+
let vec_str_ports: Vec<String> = ports.iter().map(ToString::to_string).collect();
147+
148+
// nmap port style is 80,443. Comma separated with no spaces.
149+
let ports_str = vec_str_ports.join(",");
150+
151+
// if option scripts is none, no script will be spawned
152+
if opts.greppable || opts.scripts == ScriptsRequired::None {
153+
println!("{} -> [{}]", &ip, ports_str);
154+
continue;
155+
}
156+
detail!("Starting Script(s)", opts.greppable, opts.accessible);
157+
158+
// Run all the scripts we found and parsed based on the script config file tags field.
159+
for mut script_f in scripts_to_run.clone() {
160+
// This part allows us to add commandline arguments to the Script call_format, appending them to the end of the command.
161+
if !opts.command.is_empty() {
162+
let user_extra_args = &opts.command.join(" ");
163+
debug!("Extra args vec {:?}", user_extra_args);
164+
if script_f.call_format.is_some() {
165+
let mut call_f = script_f.call_format.unwrap();
166+
call_f.push(' ');
167+
call_f.push_str(user_extra_args);
168+
output!(
169+
format!("Running script {:?} on ip {}\nDepending on the complexity of the script, results may take some time to appear.", call_f, &ip),
170+
opts.greppable,
171+
opts.accessible
172+
);
173+
debug!("Call format {}", call_f);
174+
script_f.call_format = Some(call_f);
175+
}
176+
}
177+
178+
// Building the script with the arguments from the ScriptFile, and ip-ports.
179+
let script = Script::build(
180+
script_f.path,
181+
*ip,
182+
ports.clone(),
183+
script_f.port,
184+
script_f.ports_separator,
185+
script_f.tags,
186+
script_f.call_format,
187+
);
188+
match script.run() {
189+
Ok(script_result) => {
190+
detail!(script_result.to_string(), opts.greppable, opts.accessible);
191+
}
192+
Err(e) => {
193+
warning!(&format!("Error {e}"), opts.greppable, opts.accessible);
194+
}
195+
}
196+
}
197+
}
198+
199+
// TODO:
200+
println!("closed ports:");
201+
for (ip, ports) in &closed_ports_per_ip {
132202
let vec_str_ports: Vec<String> = ports.iter().map(ToString::to_string).collect();
133203

134204
// nmap port style is 80,443. Comma separated with no spaces.

src/scanner/mod.rs

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ pub struct Scanner {
3737
accessible: bool,
3838
exclude_ports: Vec<u16>,
3939
udp: bool,
40+
closed: bool,
41+
}
42+
43+
#[derive(Debug)]
44+
pub enum PortStatus {
45+
Open(SocketAddr),
46+
Closed(SocketAddr),
4047
}
4148

4249
// Allowing too many arguments for clippy.
@@ -52,6 +59,7 @@ impl Scanner {
5259
accessible: bool,
5360
exclude_ports: Vec<u16>,
5461
udp: bool,
62+
closed: bool,
5563
) -> Self {
5664
Self {
5765
batch_size,
@@ -63,13 +71,14 @@ impl Scanner {
6371
accessible,
6472
exclude_ports,
6573
udp,
74+
closed,
6675
}
6776
}
6877

6978
/// Runs scan_range with chunk sizes
7079
/// If you want to run RustScan normally, this is the entry point used
7180
/// Returns all open ports as `Vec<u16>`
72-
pub async fn run(&self) -> Vec<SocketAddr> {
81+
pub async fn run(&self) -> Vec<PortStatus> {
7382
let ports: Vec<u16> = self
7483
.port_strategy
7584
.order()
@@ -78,7 +87,7 @@ impl Scanner {
7887
.copied()
7988
.collect();
8089
let mut socket_iterator: SocketIterator = SocketIterator::new(&self.ips, &ports);
81-
let mut open_sockets: Vec<SocketAddr> = Vec::new();
90+
let mut found_sockets: Vec<PortStatus> = Vec::new();
8291
let mut ftrs = FuturesUnordered::new();
8392
let mut errors: HashSet<String> = HashSet::new();
8493
let udp_map = get_parsed_data();
@@ -103,7 +112,7 @@ impl Scanner {
103112
}
104113

105114
match result {
106-
Ok(socket) => open_sockets.push(socket),
115+
Ok(socket) => found_sockets.push(socket),
107116
Err(e) => {
108117
let error_string = e.to_string();
109118
if errors.len() < self.ips.len() * 1000 {
@@ -113,8 +122,8 @@ impl Scanner {
113122
}
114123
}
115124
debug!("Typical socket connection errors {:?}", errors);
116-
debug!("Open Sockets found: {:?}", &open_sockets);
117-
open_sockets
125+
debug!("Open Sockets found: {:?}", &found_sockets);
126+
found_sockets
118127
}
119128

120129
/// Given a socket, scan it self.tries times.
@@ -135,7 +144,7 @@ impl Scanner {
135144
&self,
136145
socket: SocketAddr,
137146
udp_map: BTreeMap<Vec<u16>, Vec<u8>>,
138-
) -> io::Result<SocketAddr> {
147+
) -> io::Result<PortStatus> {
139148
if self.udp {
140149
return self.scan_udp_socket(socket, udp_map).await;
141150
}
@@ -154,11 +163,23 @@ impl Scanner {
154163
self.fmt_ports(socket);
155164

156165
debug!("Return Ok after {} tries", nr_try);
157-
return Ok(socket);
166+
return Ok(PortStatus::Open(socket));
158167
}
159168
Err(e) => {
160169
let mut error_string = e.to_string();
161170

171+
if e.kind() == io::ErrorKind::ConnectionRefused {
172+
if !self.greppable {
173+
if self.accessible {
174+
println!("Closed {socket}");
175+
} else {
176+
println!("Closed {}", socket.to_string().red());
177+
}
178+
}
179+
180+
return Ok(PortStatus::Closed(socket));
181+
}
182+
162183
assert!(!error_string.to_lowercase().contains("too many open files"), "Too many open files. Please reduce batch size. The default is 5000. Try -b 2500.");
163184

164185
if nr_try == tries {
@@ -176,7 +197,7 @@ impl Scanner {
176197
&self,
177198
socket: SocketAddr,
178199
udp_map: BTreeMap<Vec<u16>, Vec<u8>>,
179-
) -> io::Result<SocketAddr> {
200+
) -> io::Result<PortStatus> {
180201
let mut payload: Vec<u8> = Vec::new();
181202
for (key, value) in udp_map {
182203
if key.contains(&socket.port()) {
@@ -187,13 +208,13 @@ impl Scanner {
187208
let tries = self.tries.get();
188209
for _ in 1..=tries {
189210
match self.udp_scan(socket, &payload, self.timeout).await {
190-
Ok(true) => return Ok(socket),
211+
Ok(true) => return Ok(PortStatus::Open(socket)),
191212
Ok(false) => continue,
192213
Err(e) => return Err(e),
193214
}
194215
}
195216

196-
Ok(socket)
217+
Ok(PortStatus::Open(socket))
197218
}
198219

199220
/// Performs the connection to the socket with timeout

0 commit comments

Comments
 (0)