use crate::payload::{PayloadInfo, PayloadType};
use crate::result::{ServiceProbeError, ServiceProbeResult};
use crate::setting::{NoCertificateVerification, ProbeSetting};
use futures::stream::{self, StreamExt};
use rayon::prelude::*;
use std::collections::HashMap;
use std::io::prelude::*;
use std::io::{BufReader, BufWriter};
use std::net::{SocketAddr, TcpStream};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use crate::tcp_service::PORT_SERVICE_MAP;
#[derive(Clone, Debug)]
pub struct ServiceDetector {
pub setting: ProbeSetting,
result: HashMap<u16, ServiceProbeResult>,
}
impl ServiceDetector {
pub fn new(setting: ProbeSetting) -> ServiceDetector {
ServiceDetector {
setting,
result: HashMap::new(),
}
}
pub fn detect(&self) -> HashMap<u16, ServiceProbeResult> {
self.detect_mt()
}
pub async fn async_detect(&self) -> HashMap<u16, ServiceProbeResult> {
self.detect_async().await
}
pub fn start_detection(&mut self) {
self.result = self.detect_mt()
}
pub async fn start_async_detection(&mut self) {
self.result = self.detect_async().await
}
pub fn get_result(&self) -> &HashMap<u16, ServiceProbeResult> {
&self.result
}
pub fn get_result_for_port(&self, port: u16) -> Option<&ServiceProbeResult> {
self.result.get(&port)
}
fn probe_port(&self, port: u16, payload_info: Option<PayloadInfo>) -> ServiceProbeResult {
let service_name: String = match PORT_SERVICE_MAP.get(&port) {
Some(name) => name.to_string(),
None => String::new(),
};
let mut probe_result: ServiceProbeResult =
ServiceProbeResult::new(port, service_name, Vec::new());
let socket_addr: SocketAddr = SocketAddr::new(self.setting.ip_addr, port);
match TcpStream::connect_timeout(&socket_addr, self.setting.connect_timeout) {
Ok(stream) => {
stream
.set_read_timeout(Some(self.setting.read_timeout))
.expect("Failed to set read_timeout.");
let mut reader = BufReader::new(&stream);
let mut writer = BufWriter::new(&stream);
if let Some(payload) = payload_info {
match payload.payload_type {
PayloadType::Http => match writer.write_all(&payload.payload) {
Ok(_) => match writer.flush() {
Ok(_) => match read_response(&mut reader) {
Ok(bytes) => {
probe_result.service_detail = parse_http_header(&bytes);
probe_result.response = bytes;
}
Err(e) => {
probe_result.error =
Some(ServiceProbeError::ReadError(e.to_string()));
}
},
Err(e) => {
probe_result.error =
Some(ServiceProbeError::WriteError(e.to_string()));
}
},
Err(e) => {
probe_result.error =
Some(ServiceProbeError::WriteError(e.to_string()));
}
},
PayloadType::Https => {
let hostname: String = if self.setting.hostname.is_empty() {
self.setting.ip_addr.to_string()
} else {
self.setting.hostname.clone()
};
match send_payload_tls(
hostname,
port,
payload.payload,
self.setting.accept_invalid_certs,
) {
Ok(res) => {
probe_result.response = res.clone();
probe_result.service_detail = parse_http_header(&res);
}
Err(e) => {
probe_result.error =
Some(ServiceProbeError::TlsError(e.to_string()));
}
}
}
PayloadType::CommonTls => {
let hostname: String = if self.setting.hostname.is_empty() {
self.setting.ip_addr.to_string()
} else {
self.setting.hostname.clone()
};
match send_payload_tls(
hostname,
port,
payload.payload,
self.setting.accept_invalid_certs,
) {
Ok(res) => {
probe_result.response = res.clone();
probe_result.service_detail =
Some(String::from_utf8(res).unwrap());
}
Err(e) => {
probe_result.error =
Some(ServiceProbeError::TlsError(e.to_string()));
}
}
}
_ => match writer.write_all(&payload.payload) {
Ok(_) => match writer.flush() {
Ok(_) => match read_response(&mut reader) {
Ok(bytes) => {
match String::from_utf8(bytes.clone()) {
Ok(res) => {
probe_result.service_detail =
Some(res.replace("\r\n", ""));
}
Err(_) => {
probe_result.service_detail = Some(
String::from_utf8_lossy(&bytes).to_string(),
);
}
}
probe_result.response = bytes;
}
Err(e) => {
probe_result.error =
Some(ServiceProbeError::ReadError(e.to_string()));
}
},
Err(e) => {
probe_result.error =
Some(ServiceProbeError::WriteError(e.to_string()));
}
},
Err(e) => {
probe_result.error =
Some(ServiceProbeError::WriteError(e.to_string()));
}
},
}
} else {
match read_response(&mut reader) {
Ok(bytes) => {
match String::from_utf8(bytes.clone()) {
Ok(res) => {
probe_result.service_detail = Some(res.replace("\r\n", ""));
}
Err(_) => {
probe_result.service_detail =
Some(String::from_utf8_lossy(&bytes).to_string());
}
}
probe_result.response = bytes;
}
Err(e) => {
probe_result.error = Some(ServiceProbeError::ReadError(e.to_string()));
}
}
}
}
Err(e) => {
probe_result.error = Some(ServiceProbeError::ConnectionError(e.to_string()));
}
}
probe_result
}
fn detect_mt(&self) -> HashMap<u16, ServiceProbeResult> {
let service_map: Arc<Mutex<HashMap<u16, ServiceProbeResult>>> =
Arc::new(Mutex::new(HashMap::new()));
self.setting.clone().ports.into_par_iter().for_each(|port| {
let probe_result: ServiceProbeResult =
self.probe_port(port, self.setting.payload_map.get(&port).cloned());
service_map.lock().unwrap().insert(port, probe_result);
});
let result_map: HashMap<u16, ServiceProbeResult> = service_map.lock().unwrap().clone();
result_map
}
async fn detect_async(&self) -> HashMap<u16, ServiceProbeResult> {
let service_map: Arc<Mutex<HashMap<u16, ServiceProbeResult>>> =
Arc::new(Mutex::new(HashMap::new()));
let fut_port = stream::iter(self.setting.clone().ports).for_each_concurrent(
self.setting.concurrent_limit,
|port| {
let c_service_map: Arc<Mutex<HashMap<u16, ServiceProbeResult>>> =
Arc::clone(&service_map);
async move {
let probe_result: ServiceProbeResult =
self.probe_port(port, self.setting.payload_map.get(&port).cloned());
c_service_map.lock().unwrap().insert(port, probe_result);
}
},
);
fut_port.await;
let result_map: HashMap<u16, ServiceProbeResult> = service_map.lock().unwrap().clone();
result_map
}
}
fn read_response(reader: &mut BufReader<&TcpStream>) -> std::io::Result<Vec<u8>> {
let mut io_error: std::io::Error =
std::io::Error::new(std::io::ErrorKind::Other, "No response");
let mut response: Vec<u8> = Vec::new();
match reader.read_to_end(&mut response) {
Ok(_) => {}
Err(e) => {
io_error = e;
}
}
if response.len() == 0 {
return Err(io_error);
} else {
Ok(response)
}
}
fn send_payload_tls(
hostname: String,
port: u16,
payload: Vec<u8>,
accept_invalid_certs: bool,
) -> std::io::Result<Vec<u8>> {
let sock_addr: String = format!("{}:{}", hostname, port);
let mut root_store = rustls::RootCertStore::empty();
match rustls_native_certs::load_native_certs() {
Ok(certs) => {
for cert in certs {
root_store.add(&rustls::Certificate(cert.0)).unwrap();
}
}
Err(e) => return Err(e),
}
let mut config = rustls::ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(root_store)
.with_no_client_auth();
if accept_invalid_certs {
let mut dangerous_config: rustls::client::DangerousClientConfig =
rustls::ClientConfig::dangerous(&mut config);
dangerous_config.set_certificate_verifier(Arc::new(NoCertificateVerification {}));
}
let mut tls_connection: rustls::ClientConnection =
rustls::ClientConnection::new(Arc::new(config), hostname.as_str().try_into().unwrap())
.unwrap();
let mut stream: TcpStream = match TcpStream::connect(sock_addr.clone()) {
Ok(s) => s,
Err(e) => return Err(e),
};
match stream.set_read_timeout(Some(Duration::from_secs(10))) {
Ok(_) => {}
Err(e) => return Err(e),
}
let mut tls_stream: rustls::Stream<rustls::ClientConnection, TcpStream> =
rustls::Stream::new(&mut tls_connection, &mut stream);
match tls_stream.write_all(&payload) {
Ok(_) => {}
Err(e) => return Err(e),
}
let mut io_error: std::io::Error =
std::io::Error::new(std::io::ErrorKind::Other, "No response");
let mut res = Vec::new();
match tls_stream.read_to_end(&mut res) {
Ok(_) => {}
Err(e) => {
io_error = e;
}
}
if res.len() == 0 {
return Err(io_error);
} else {
Ok(res)
}
}
fn parse_http_header(res_bytes: &Vec<u8>) -> Option<String> {
let res_string: String = res_bytes.iter().map(|&c| c as char).collect();
let header_fields: Vec<&str> = res_string.split("\r\n").collect();
if header_fields.len() == 1 {
if res_string.contains("Server:") {
return Some(res_string);
} else {
return None;
}
}
for field in header_fields {
if field.contains("Server:") {
let server_info: String = field.trim().to_string();
return Some(server_info);
}
}
None
}