Skip to content

Commit ab2363c

Browse files
feat: maybe fixing + chore: fmt
1 parent f19b58f commit ab2363c

7 files changed

Lines changed: 161 additions & 148 deletions

File tree

src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
pub mod patterns;
12
pub mod scanner;
23
pub mod service_detection;
3-
pub mod patterns;
4+
pub mod types;
45
pub mod utils;
5-
pub mod types;

src/main.rs

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,28 @@
1+
mod patterns;
12
mod scanner;
23
mod service_detection;
3-
mod utils;
44
mod types;
5-
mod patterns;
5+
mod utils;
66

7-
use std::net::IpAddr;
8-
use std::str::FromStr;
9-
use clap::Parser;
10-
use anyhow::Result;
11-
use std::collections::HashMap;
127
use crate::scanner::Scanner;
138
use crate::types::ScanResult;
149
use crate::utils::get_mac_vendor_for_ip;
10+
use anyhow::Result;
11+
use clap::Parser;
12+
use std::collections::HashMap;
13+
use std::net::IpAddr;
14+
use std::str::FromStr;
15+
16+
/*
17+
* Default concurrency based on typical ulimit -n values.
18+
* macOS: ~256 default, so we use 200 to leave headroom for other FDs.
19+
* Linux: usually 1024+ default, so we can go higher.
20+
*/
21+
#[cfg(target_os = "macos")]
22+
const DEFAULT_CONCURRENCY: usize = 200;
23+
24+
#[cfg(not(target_os = "macos"))]
25+
const DEFAULT_CONCURRENCY: usize = 1000;
1526

1627
#[derive(Parser, Debug)]
1728
#[command(author, version, about, long_about = None)]
@@ -22,7 +33,7 @@ struct Args {
2233
#[arg(short, long, default_value = "1000-2000")]
2334
ports: String,
2435

25-
#[arg(short, long, default_value = "1000")]
36+
#[arg(short, long, default_value_t = DEFAULT_CONCURRENCY)]
2637
concurrency: usize,
2738

2839
#[arg(short, long, default_value = "1000")]
@@ -42,15 +53,17 @@ fn parse_port_range(ports: &str) -> Result<std::ops::RangeInclusive<u16>> {
4253
let end = parts[1].parse::<u16>()?;
4354

4455
if start > end {
45-
return Err(anyhow::anyhow!("Start port must be less than or equal to end port"));
56+
return Err(anyhow::anyhow!(
57+
"Start port must be less than or equal to end port"
58+
));
4659
}
4760

4861
Ok(start..=end)
4962
}
5063

5164
fn format_scan_result(result: &ScanResult) -> String {
5265
let mut output = format!("[+] {}:{} is open", result.ip, result.port);
53-
66+
5467
if let Some(service) = &result.service {
5568
output.push_str(&format!("\n Service: {}", service.name));
5669
if let Some(version) = &service.version {

src/patterns.rs

Lines changed: 101 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
use crate::types::{MacPrefix, NmapMatch, NmapProbe, NmapService, ServicePattern};
2+
use anyhow::Result;
13
use regex::Regex;
4+
use std::collections::HashMap;
25
use std::fs;
36
use std::path::Path;
4-
use anyhow::Result;
5-
use std::collections::HashMap;
6-
use crate::types::{ServicePattern, NmapService, NmapProbe, NmapMatch, MacPrefix};
77

88
lazy_static::lazy_static! {
99
static ref MAC_PREFIXES: HashMap<String, String> = {
@@ -28,15 +28,15 @@ lazy_static::lazy_static! {
2828

2929
static ref SERVICE_PATTERNS: Vec<ServicePattern> = {
3030
let mut patterns = Vec::with_capacity(1000);
31-
31+
3232
patterns.extend(get_ssh_patterns());
3333
patterns.extend(get_http_patterns());
3434
patterns.extend(get_smtp_patterns());
3535
patterns.extend(get_imap_patterns());
3636
patterns.extend(get_ftp_patterns());
3737
patterns.extend(get_mysql_patterns());
3838
patterns.extend(get_redis_patterns());
39-
39+
4040
if let Ok(probes) = load_nmap_probes("src/assets/nmap-service-probes") {
4141
for probe in probes {
4242
for nmap_match in probe.matches {
@@ -77,108 +77,99 @@ pub fn get_services_by_port(port: u16) -> Option<&'static Vec<NmapService>> {
7777
}
7878

7979
pub fn get_ssh_patterns() -> Vec<ServicePattern> {
80-
vec![
81-
ServicePattern {
82-
name: "SSH".to_string(),
83-
regex: Regex::new(r"^SSH-\d\.\d").unwrap(),
84-
probe: "SSH-2.0-OpenSSH_8.2p1\r\n".to_string(),
85-
version_regex: Some(Regex::new(r"SSH-(\d\.\d)").unwrap()),
86-
product_regex: Some(Regex::new(r"OpenSSH_([^\r\n]+)").unwrap()),
87-
os_regex: Some(Regex::new(r"OpenSSH.*?([^\r\n]+)").unwrap()),
88-
extra_info_regex: None,
89-
cpe_regex: None,
90-
},
91-
]
80+
vec![ServicePattern {
81+
name: "SSH".to_string(),
82+
regex: Regex::new(r"^SSH-\d\.\d").unwrap(),
83+
probe: "SSH-2.0-OpenSSH_8.2p1\r\n".to_string(),
84+
version_regex: Some(Regex::new(r"SSH-(\d\.\d)").unwrap()),
85+
product_regex: Some(Regex::new(r"OpenSSH_([^\r\n]+)").unwrap()),
86+
os_regex: Some(Regex::new(r"OpenSSH.*?([^\r\n]+)").unwrap()),
87+
extra_info_regex: None,
88+
cpe_regex: None,
89+
}]
9290
}
9391

9492
pub fn get_http_patterns() -> Vec<ServicePattern> {
95-
vec![
96-
ServicePattern {
97-
name: "HTTP".to_string(),
98-
regex: Regex::new(r"^HTTP/\d\.\d").unwrap(),
99-
probe: "HEAD / HTTP/1.1\r\nHost: localhost\r\n\r\n".to_string(),
100-
version_regex: Some(Regex::new(r"HTTP/(\d\.\d)").unwrap()),
101-
product_regex: Some(Regex::new(r"Server: ([^\r\n]+)").unwrap()),
102-
os_regex: None,
103-
extra_info_regex: None,
104-
cpe_regex: None,
105-
},
106-
]
93+
vec![ServicePattern {
94+
name: "HTTP".to_string(),
95+
regex: Regex::new(r"^HTTP/\d\.\d").unwrap(),
96+
probe: "HEAD / HTTP/1.1\r\nHost: localhost\r\n\r\n".to_string(),
97+
version_regex: Some(Regex::new(r"HTTP/(\d\.\d)").unwrap()),
98+
product_regex: Some(Regex::new(r"Server: ([^\r\n]+)").unwrap()),
99+
os_regex: None,
100+
extra_info_regex: None,
101+
cpe_regex: None,
102+
}]
107103
}
108104

109105
pub fn get_ftp_patterns() -> Vec<ServicePattern> {
110-
vec![
111-
ServicePattern {
112-
name: "FTP".to_string(),
113-
regex: Regex::new(r"^220").unwrap(),
114-
probe: "USER anonymous\r\n".to_string(),
115-
version_regex: Some(Regex::new(r"220 ([^\r\n]+)").unwrap()),
116-
product_regex: None,
117-
os_regex: None,
118-
extra_info_regex: None,
119-
cpe_regex: None,
120-
},
121-
]
106+
vec![ServicePattern {
107+
name: "FTP".to_string(),
108+
regex: Regex::new(r"^220").unwrap(),
109+
probe: "USER anonymous\r\n".to_string(),
110+
version_regex: Some(Regex::new(r"220 ([^\r\n]+)").unwrap()),
111+
product_regex: None,
112+
os_regex: None,
113+
extra_info_regex: None,
114+
cpe_regex: None,
115+
}]
122116
}
123117

124118
pub fn get_mysql_patterns() -> Vec<ServicePattern> {
125-
vec![
126-
ServicePattern {
127-
name: "MySQL".to_string(),
128-
regex: Regex::new(r"^\x00").unwrap(),
129-
probe: "\x4a\x00\x00\x00\x0a\x35\x2e\x35\x2e\x35".to_string(),
130-
version_regex: Some(Regex::new(r"(\d+\.\d+\.\d+)").unwrap()),
131-
product_regex: None,
132-
os_regex: None,
133-
extra_info_regex: None,
134-
cpe_regex: None,
135-
},
136-
]
119+
vec![ServicePattern {
120+
name: "MySQL".to_string(),
121+
regex: Regex::new(r"^\x00").unwrap(),
122+
probe: "\x4a\x00\x00\x00\x0a\x35\x2e\x35\x2e\x35".to_string(),
123+
version_regex: Some(Regex::new(r"(\d+\.\d+\.\d+)").unwrap()),
124+
product_regex: None,
125+
os_regex: None,
126+
extra_info_regex: None,
127+
cpe_regex: None,
128+
}]
137129
}
138130

139131
pub fn get_smtp_patterns() -> Vec<ServicePattern> {
140-
vec![
141-
ServicePattern {
142-
name: "SMTP".to_string(),
143-
regex: Regex::new(r"^220.*ESMTP|^220.*SMTP|^220.*OpenSMTPD|^220.*Postfix|^220.*Sendmail|^220.*Microsoft").unwrap(),
144-
probe: "EHLO localhost\r\n".to_string(),
145-
version_regex: Some(Regex::new(r"220.*?([^\r\n]+)").unwrap()),
146-
product_regex: Some(Regex::new(r"220.*?(OpenSMTPD|Postfix|Sendmail|Microsoft|ESMTP)").unwrap()),
147-
os_regex: None,
148-
extra_info_regex: None,
149-
cpe_regex: None,
150-
},
151-
]
132+
vec![ServicePattern {
133+
name: "SMTP".to_string(),
134+
regex: Regex::new(
135+
r"^220.*ESMTP|^220.*SMTP|^220.*OpenSMTPD|^220.*Postfix|^220.*Sendmail|^220.*Microsoft",
136+
)
137+
.unwrap(),
138+
probe: "EHLO localhost\r\n".to_string(),
139+
version_regex: Some(Regex::new(r"220.*?([^\r\n]+)").unwrap()),
140+
product_regex: Some(
141+
Regex::new(r"220.*?(OpenSMTPD|Postfix|Sendmail|Microsoft|ESMTP)").unwrap(),
142+
),
143+
os_regex: None,
144+
extra_info_regex: None,
145+
cpe_regex: None,
146+
}]
152147
}
153148

154149
pub fn get_imap_patterns() -> Vec<ServicePattern> {
155-
vec![
156-
ServicePattern {
157-
name: "IMAP".to_string(),
158-
regex: Regex::new(r"^\* OK.*IMAP|^\* OK.*Dovecot|^\* OK.*Cyrus").unwrap(),
159-
probe: "A001 CAPABILITY\r\n".to_string(),
160-
version_regex: Some(Regex::new(r"IMAP(\d+[^\s]*)").unwrap()),
161-
product_regex: Some(Regex::new(r"(Dovecot|Cyrus)").unwrap()),
162-
os_regex: None,
163-
extra_info_regex: None,
164-
cpe_regex: None,
165-
},
166-
]
150+
vec![ServicePattern {
151+
name: "IMAP".to_string(),
152+
regex: Regex::new(r"^\* OK.*IMAP|^\* OK.*Dovecot|^\* OK.*Cyrus").unwrap(),
153+
probe: "A001 CAPABILITY\r\n".to_string(),
154+
version_regex: Some(Regex::new(r"IMAP(\d+[^\s]*)").unwrap()),
155+
product_regex: Some(Regex::new(r"(Dovecot|Cyrus)").unwrap()),
156+
os_regex: None,
157+
extra_info_regex: None,
158+
cpe_regex: None,
159+
}]
167160
}
168161

169162
pub fn get_redis_patterns() -> Vec<ServicePattern> {
170-
vec![
171-
ServicePattern {
172-
name: "Redis".to_string(),
173-
regex: Regex::new(r"^\+PONG|^\+OK|^\$-|^\*[0-9]|^:[\d-]+|^ERR|^redis_version").unwrap(),
174-
probe: "PING\r\n".to_string(),
175-
version_regex: Some(Regex::new(r"redis_version:(\d+\.\d+\.\d+)").unwrap()),
176-
product_regex: None,
177-
os_regex: None,
178-
extra_info_regex: None,
179-
cpe_regex: None,
180-
},
181-
]
163+
vec![ServicePattern {
164+
name: "Redis".to_string(),
165+
regex: Regex::new(r"^\+PONG|^\+OK|^\$-|^\*[0-9]|^:[\d-]+|^ERR|^redis_version").unwrap(),
166+
probe: "PING\r\n".to_string(),
167+
version_regex: Some(Regex::new(r"redis_version:(\d+\.\d+\.\d+)").unwrap()),
168+
product_regex: None,
169+
os_regex: None,
170+
extra_info_regex: None,
171+
cpe_regex: None,
172+
}]
182173
}
183174

184175
pub fn load_nmap_services(file_path: &str) -> Result<Vec<NmapService>> {
@@ -198,7 +189,12 @@ pub fn load_nmap_services(file_path: &str) -> Result<Vec<NmapService>> {
198189
let parts: Vec<&str> = line.split_whitespace().collect();
199190
if parts.len() >= 3 {
200191
let name = parts[0].to_string();
201-
let port = parts[1].split('/').next().unwrap_or("0").parse::<u16>().unwrap_or(0);
192+
let port = parts[1]
193+
.split('/')
194+
.next()
195+
.unwrap_or("0")
196+
.parse::<u16>()
197+
.unwrap_or(0);
202198
let protocol = parts[1].split('/').nth(1).unwrap_or("tcp").to_string();
203199
services.push(NmapService {
204200
name,
@@ -248,7 +244,7 @@ pub fn load_nmap_probes(file_path: &str) -> Result<Vec<NmapProbe>> {
248244
if parts.len() >= 3 {
249245
let service = parts[1].to_string();
250246
let pattern = parts[2].trim_matches(|c| c == 'm' || c == '|').to_string();
251-
247+
252248
if pattern.contains("**") || pattern.contains("\\") || pattern.contains("^") {
253249
continue;
254250
}
@@ -286,13 +282,23 @@ pub fn load_nmap_probes(file_path: &str) -> Result<Vec<NmapProbe>> {
286282
}
287283
} else if line.starts_with("totalwaitms ") {
288284
if let Some(probe) = &mut current_probe {
289-
if let Ok(ms) = line.split_whitespace().nth(1).unwrap_or("6000").parse::<u64>() {
285+
if let Ok(ms) = line
286+
.split_whitespace()
287+
.nth(1)
288+
.unwrap_or("6000")
289+
.parse::<u64>()
290+
{
290291
probe.total_wait_ms = ms;
291292
}
292293
}
293294
} else if line.starts_with("tcpwrappedms ") {
294295
if let Some(probe) = &mut current_probe {
295-
if let Ok(ms) = line.split_whitespace().nth(1).unwrap_or("3000").parse::<u64>() {
296+
if let Ok(ms) = line
297+
.split_whitespace()
298+
.nth(1)
299+
.unwrap_or("3000")
300+
.parse::<u64>()
301+
{
296302
probe.tcp_wrapped_ms = ms;
297303
}
298304
}
@@ -325,12 +331,9 @@ pub fn load_mac_prefixes(file_path: &str) -> Result<Vec<MacPrefix>> {
325331
let prefix = parts[0].to_string();
326332
let vendor = parts[1..].join(" ");
327333

328-
prefixes.push(MacPrefix {
329-
prefix,
330-
vendor,
331-
});
334+
prefixes.push(MacPrefix { prefix, vendor });
332335
}
333336
}
334337

335338
Ok(prefixes)
336-
}
339+
}

0 commit comments

Comments
 (0)