e_libscanner/service/
detector.rs

1use native_tls::TlsConnector;
2use rayon::prelude::*;
3use std::collections::HashMap;
4use std::fmt;
5use std::io::{prelude::*, BufReader, BufWriter};
6use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream};
7use std::sync::{Arc, Mutex};
8use std::time::Duration;
9
10/// Struct for service detection
11/// # Example
12/// ```
13/// fn main() -> Result<(), String> {
14/// #[cfg(feature = "service")]
15/// {
16///     use e_libscanner::service::{self, PortDatabase, ServiceDetector};
17///     use e_libscanner::Opts;
18///     use std::thread;
19///     // more command information use: -h
20///     let mut scanner = Opts::new(Some(&[
21///         "e-libscanner",
22///         "--ips",
23///         "192.168.80.10",
24///         "--ports",
25///         "8000",
26///         "8080",
27///         "20-100",
28///         "--rate",
29///         "1",
30///         "--model",
31///         "service",
32///         "--scan",
33///         "tcpsyn",
34///         "--no-gui",
35///     ]))?
36///     .init()?
37///     .downcast::<service::Scanner>()
38///     .unwrap();
39///     let rx = scanner.get_progress_receiver();
40///     let time = std::time::Instant::now();
41///     // Run scan
42///     let handle = thread::spawn(move || scanner.scan(None));
43///     // Print progress
44///     while let Ok(socket_addr) = rx.lock().unwrap().recv() {
45///         println!("Check: {}", socket_addr);
46///     }
47///     let result = handle.join().unwrap();
48///     for (ip, _ports) in result.ip_with_port.clone() {
49///         let mut service_detector = ServiceDetector::new();
50///         service_detector.set_dst_ip(ip);
51///         service_detector.set_open_ports(result.get_open_ports(ip));
52///         println!("{}", service_detector.scan(Some(PortDatabase::default())));
53///     }
54///     println!("time -> {}/s", time.elapsed().as_secs_f64());
55/// }
56/// Ok(())
57/// }
58/// ```
59#[derive(Clone, Debug)]
60pub struct ServiceDetector {
61    /// Destination IP address
62    pub dst_ip: IpAddr,
63    /// Destination Host Name
64    pub dst_name: String,
65    /// Target ports for service detection
66    pub open_ports: Vec<u16>,
67    /// TCP connect (open) timeout
68    pub connect_timeout: Duration,
69    /// TCP read timeout
70    pub read_timeout: Duration,
71    /// SSL/TLS certificate validation when detecting HTTPS services.  
72    ///
73    /// Default value is false, which means validation is enabled.
74    pub accept_invalid_certs: bool,
75}
76
77impl ServiceDetector {
78    /// Create new ServiceDetector
79    pub fn new() -> ServiceDetector {
80        ServiceDetector {
81            dst_ip: IpAddr::V4(Ipv4Addr::LOCALHOST),
82            dst_name: String::new(),
83            open_ports: vec![],
84            connect_timeout: Duration::from_millis(200),
85            read_timeout: Duration::from_secs(5),
86            accept_invalid_certs: false,
87        }
88    }
89    /// Set Destination IP address
90    pub fn set_dst_ip(&mut self, dst_ip: IpAddr) {
91        self.dst_ip = dst_ip;
92    }
93    /// Set Destination Host Name
94    pub fn set_dst_name(&mut self, host_name: IpAddr) {
95        self.dst_ip = host_name;
96    }
97    /// Set target ports
98    pub fn set_open_ports(&mut self, open_ports: Vec<u16>) {
99        self.open_ports = open_ports;
100    }
101    /// Add target port
102    pub fn add_open_port(&mut self, open_port: u16) {
103        self.open_ports.push(open_port);
104    }
105    /// Set connect (open) timeout
106    pub fn set_connect_timeout(&mut self, connect_timeout: Duration) {
107        self.connect_timeout = connect_timeout;
108    }
109    /// Set TCP read timeout
110    pub fn set_read_timeout(&mut self, read_timeout: Duration) {
111        self.read_timeout = read_timeout;
112    }
113    /// Set SSL/TLS certificate validation enable/disable.
114    pub fn set_accept_invalid_certs(&mut self, accept_invalid_certs: bool) {
115        self.accept_invalid_certs = accept_invalid_certs;
116    }
117    /// Run service detection and return result
118    ///
119    /// PortDatabase can be omitted with None (use default list)
120    pub fn detect(&self, port_db: Option<PortDatabase>) -> HashMap<u16, String> {
121        detect_service(self, port_db.unwrap_or(PortDatabase::default()))
122    }
123    /// scan services
124    pub fn scan(&self, port_db: Option<PortDatabase>) -> ScanServiceResult {
125        let ports = self.detect(port_db).drain().collect::<Vec<(u16, String)>>();
126        ScanServiceResult {
127            dst_ip: self.dst_ip,
128            dst_name: self.dst_name.clone(),
129            ports,
130        }
131    }
132}
133
134fn detect_service(setting: &ServiceDetector, port_db: PortDatabase) -> HashMap<u16, String> {
135    let service_map: Arc<Mutex<HashMap<u16, String>>> = Arc::new(Mutex::new(HashMap::new()));
136    setting.clone().open_ports.into_par_iter().for_each(|port| {
137        let sock_addr: SocketAddr = SocketAddr::new(setting.dst_ip, port);
138        match TcpStream::connect_timeout(&sock_addr, setting.connect_timeout) {
139            Ok(stream) => {
140                stream
141                    .set_read_timeout(Some(setting.read_timeout))
142                    .expect("Failed to set read timeout.");
143                let mut reader = BufReader::new(&stream);
144                let mut writer = BufWriter::new(&stream);
145                let msg: String = if port_db.http_ports.contains(&port) {
146                    write_head_request(&mut writer, setting.dst_ip.to_string());
147                    let header = read_response(&mut reader);
148                    parse_header(header)
149                } else if port_db.https_ports.contains(&port) {
150                    let header = head_request_secure(
151                        setting.dst_name.clone(),
152                        port,
153                        setting.accept_invalid_certs,
154                    );
155                    parse_header(header)
156                } else {
157                    read_response(&mut reader).replace("\r\n", "")
158                };
159                service_map.lock().unwrap().insert(port, msg);
160            }
161            Err(e) => {
162                service_map.lock().unwrap().insert(port, e.to_string());
163            }
164        }
165    });
166    let result_map: HashMap<u16, String> = service_map.lock().unwrap().clone();
167    result_map
168}
169
170fn read_response(reader: &mut BufReader<&TcpStream>) -> String {
171    let mut msg = String::new();
172    match reader.read_to_string(&mut msg) {
173        Ok(_) => {}
174        Err(_) => {}
175    }
176    msg
177}
178
179fn parse_header(response_header: String) -> String {
180    let header_fields: Vec<&str> = response_header.split("\r\n").collect();
181    if header_fields.len() == 1 {
182        return response_header;
183    }
184    for field in header_fields {
185        if field.contains("Server:") {
186            return field.trim().to_string();
187        }
188    }
189    String::new()
190}
191
192fn write_head_request(writer: &mut BufWriter<&TcpStream>, _ip_addr: String) {
193    let msg = format!("HEAD / HTTP/1.0\r\n\r\n");
194    match writer.write(msg.as_bytes()) {
195        Ok(_) => {}
196        Err(_) => {}
197    }
198    writer.flush().unwrap();
199}
200
201fn head_request_secure(host_name: String, port: u16, accept_invalid_certs: bool) -> String {
202    if host_name.is_empty() {
203        return String::from("Error: Invalid host name");
204    }
205    let sock_addr: String = format!("{}:{}", host_name, port);
206    let connector = if accept_invalid_certs {
207        match TlsConnector::builder()
208            .danger_accept_invalid_certs(true)
209            .build()
210        {
211            Ok(c) => c,
212            Err(e) => return format!("Error: {}", e.to_string()),
213        }
214    } else {
215        match TlsConnector::new() {
216            Ok(c) => c,
217            Err(e) => return format!("Error: {}", e.to_string()),
218        }
219    };
220    let stream = match TcpStream::connect(sock_addr.clone()) {
221        Ok(s) => s,
222        Err(e) => return format!("Error: {}", e.to_string()),
223    };
224    match stream.set_read_timeout(Some(Duration::from_secs(10))) {
225        Ok(_) => {}
226        Err(e) => return format!("Error: {}", e.to_string()),
227    }
228    let mut stream = match connector.connect(host_name.as_str(), stream) {
229        Ok(s) => s,
230        Err(e) => return format!("Error: {}", e.to_string()),
231    };
232    let msg = format!("HEAD / HTTP/1.0\r\n\r\n");
233    match stream.write(msg.as_bytes()) {
234        Ok(_) => {}
235        Err(e) => return format!("Error: {}", e.to_string()),
236    }
237    let mut res = vec![];
238    match stream.read_to_end(&mut res) {
239        Ok(_) => {
240            let result = String::from_utf8_lossy(&res);
241            return result.to_string();
242        }
243        Err(e) => return format!("Error: {}", e.to_string()),
244    };
245}
246/// List of ports for which more detailed information can be obtained, by service.
247///
248/// HTTP/HTTPS, etc.
249#[doc(hidden)]
250#[derive(Clone, Debug)]
251pub struct PortDatabase {
252    pub http_ports: Vec<u16>,
253    pub https_ports: Vec<u16>,
254}
255
256impl PortDatabase {
257    pub fn new() -> PortDatabase {
258        PortDatabase {
259            http_ports: vec![],
260            https_ports: vec![],
261        }
262    }
263    pub fn default() -> PortDatabase {
264        PortDatabase {
265            http_ports: vec![80, 8080],
266            https_ports: vec![443, 8443],
267        }
268    }
269}
270
271/// Service Result
272#[doc(hidden)]
273#[derive(Debug, Clone)]
274pub struct ScanServiceResult {
275    pub dst_ip: IpAddr,
276    pub dst_name: String,
277    pub ports: Vec<(u16, String)>,
278}
279impl fmt::Display for ScanServiceResult {
280    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281        // The `f` value implements the `Write` trait, which is what the
282        // write! macro is expecting. Note that this formatting ignores the
283        // various flags provided to format strings.
284        write!(
285            f,
286            "[{} {} [ {:?} ]]",
287            self.dst_ip, self.dst_name, self.ports
288        )
289    }
290}