diff --git a/snow-scanner/src/event_bus.rs b/snow-scanner/src/event_bus.rs index f90bc1c..a32c6fe 100644 --- a/snow-scanner/src/event_bus.rs +++ b/snow-scanner/src/event_bus.rs @@ -6,7 +6,8 @@ use hickory_resolver::Name; use rocket::futures::channel::mpsc as rocket_mpsc; use rocket::futures::StreamExt; use rocket::tokio; -use snow_scanner_worker::detection::{detect_scanner_from_name, validate_ip}; +use snow_scanner_worker::detection::validate_ip; +use snow_scanner_worker::scanners::Scanners; use crate::Scanner; @@ -57,9 +58,13 @@ impl EventBus { return; } let name = Name::from_str(name.as_str()).unwrap(); - match detect_scanner_from_name(&name) { - Ok(Some(scanner_type)) => { - match Scanner::find_or_new(ip, scanner_type, Some(name), db).await { + let scanner: Result = name.clone().try_into(); + + match scanner { + Ok(scanner_type) => { + match Scanner::find_or_new(ip, scanner_type.to_owned(), Some(name), db) + .await + { Ok(scanner) => { let _ = scanner.save(db).await; } @@ -68,9 +73,6 @@ impl EventBus { } } } - Ok(None) => { - error!("No name detected for: {:?}", name); - } Err(err) => { error!("No name detected error: {:?}", err); diff --git a/snow-scanner/src/main.rs b/snow-scanner/src/main.rs index e17a662..226fdf9 100644 --- a/snow-scanner/src/main.rs +++ b/snow-scanner/src/main.rs @@ -33,11 +33,9 @@ use rocket_ws::WebSocket; use server::Server; use weighted_rs::Weight; -use snow_scanner_worker::detection::{ - detect_scanner, get_dns_client, get_dns_server_config, validate_ip, -}; +use snow_scanner_worker::detection::{get_dns_client, get_dns_server_config, validate_ip}; use snow_scanner_worker::modules::{Network, WorkerMessages}; -use snow_scanner_worker::scanners::IsStatic; +use snow_scanner_worker::scanners::ScannerMethods; use snow_scanner_worker::scanners::Scanners; use snow_scanner_worker::utils::get_dns_rr; @@ -125,14 +123,14 @@ impl FromFormField<'_> for SafeIpAddr { async fn handle_ip( query_address: IpAddr, -) -> Result<(IpAddr, Option, ResolvedResult), ()> { - let ptr_result: Result = std::thread::spawn(move || { +) -> Result<(IpAddr, Option, ResolvedResult), String> { + let ptr_result: Result = std::thread::spawn(move || { let mut rr_dns_servers = get_dns_rr(); let client = get_dns_client(&get_dns_server_config(&rr_dns_servers.next().unwrap())); let ptr_result: ResolvedResult = if let Ok(res) = get_ptr(query_address, client) { res } else { - return Err(()); + return Err("Resolving error".to_string()); }; Ok(ptr_result) }) @@ -140,17 +138,20 @@ async fn handle_ip( .unwrap(); match ptr_result { - Ok(result) => match detect_scanner(&result) { - Ok(Some(scanner_type)) => { - if !validate_ip(query_address) { - error!("Invalid IP address: {query_address}"); - return Err(()); + Ok(result) => { + let scanner: Result = result.query.clone().try_into(); + + match scanner { + Ok(scanner_type) => { + if !validate_ip(query_address) { + error!("Invalid IP address: {query_address}"); + return Err("".to_string()); + } + Ok((query_address, Some(scanner_type), result)) } - Ok((query_address, Some(scanner_type), result)) + Err(err) => Err(err), } - Ok(None) => Ok((query_address, None, result)), - Err(err) => Err(err), - }, + } Err(err) => Err(err), } } @@ -263,44 +264,19 @@ pub struct ReportParams { } fn reply_contents_for_scanner_found(scanner: Scanner) -> HtmlContents { - HtmlContents(match scanner.scanner_name { - Scanners::Binaryedge => match scanner.last_checked_at { - Some(date) => format!( - "Reported a binaryedge ninja! {} known as {} since {date}.", - scanner.ip, - scanner.ip_ptr.unwrap_or("".to_string()) - ), - None => format!( - "Reported a binaryedge ninja! {} known as {}.", - scanner.ip, - scanner.ip_ptr.unwrap_or("".to_string()) - ), - }, - Scanners::Stretchoid => match scanner.last_checked_at { - Some(date) => format!( - "Reported a stretchoid agent! {} known as {} since {date}.", - scanner.ip, - scanner.ip_ptr.unwrap_or("".to_string()) - ), - None => format!( - "Reported a stretchoid agent! {} known as {}.", - scanner.ip, - scanner.ip_ptr.unwrap_or("".to_string()) - ), - }, - Scanners::Shadowserver => match scanner.last_checked_at { - Some(date) => format!( - "Reported a cloudy shadowserver ! {} known as {} since {date}.", - scanner.ip, - scanner.ip_ptr.unwrap_or("".to_string()) - ), - None => format!( - "Reported a cloudy shadowserver ! {} known as {}.", - scanner.ip, - scanner.ip_ptr.unwrap_or("".to_string()) - ), - }, - _ => format!("Not supported"), + HtmlContents(match scanner.last_checked_at { + Some(date) => format!( + "Reported a {}! {} known as {} since {date}.", + scanner.scanner_name.funny_name(), + scanner.ip, + scanner.ip_ptr.unwrap_or("".to_string()) + ), + None => format!( + "Reported a {}! {} known as {}.", + scanner.scanner_name.funny_name(), + scanner.ip, + scanner.ip_ptr.unwrap_or("".to_string()) + ), }) } @@ -397,13 +373,11 @@ async fn handle_list_scanners( let mut path: PathBuf = PathBuf::new(); path.push(static_data_dir); path.push("scanners"); - path.push(match scanner_name { - Scanners::Stretchoid | Scanners::Binaryedge | Scanners::Shadowserver => { - panic!("This should not happen") - } - Scanners::Censys => "censys.txt".to_string(), - Scanners::InternetMeasurement => "internet-measurement.com.txt".to_string(), - }); + path.push( + scanner_name + .static_file_name() + .expect("Static files should have a static file name"), + ); return match NamedFile::open(path).await { Ok(file) => MultiReply::FileContents(file), diff --git a/snow-scanner/src/worker/detection.rs b/snow-scanner/src/worker/detection.rs index 25ff1b7..10f4db5 100644 --- a/snow-scanner/src/worker/detection.rs +++ b/snow-scanner/src/worker/detection.rs @@ -1,12 +1,8 @@ use std::net::IpAddr; -use std::str::FromStr; use std::time::Duration; -use crate::scanners::Scanners; -use dns_ptr_resolver::ResolvedResult; - use hickory_resolver::config::{NameServerConfigGroup, ResolverConfig, ResolverOpts}; -use hickory_resolver::{Name, Resolver}; +use hickory_resolver::Resolver; use crate::ip_addr::is_global_hardcoded; @@ -33,68 +29,3 @@ pub fn validate_ip(ip: IpAddr) -> bool { } return is_global_hardcoded(ip); } - -pub fn detect_scanner(ptr_result: &ResolvedResult) -> Result, ()> { - match &ptr_result.result { - Some(name) => detect_scanner_from_name(&name), - None => Ok(None), - } -} - -pub fn detect_scanner_from_name(name: &Name) -> Result, ()> { - match name { - ref name - if name - .trim_to(2) - .eq_case(&Name::from_str("binaryedge.ninja.").expect("Should parse")) => - { - Ok(Some(Scanners::Binaryedge)) - } - ref name - if name - .trim_to(2) - .eq_case(&Name::from_str("stretchoid.com.").expect("Should parse")) => - { - Ok(Some(Scanners::Stretchoid)) - } - ref name - if name - .trim_to(2) - .eq_case(&Name::from_str("shadowserver.org.").expect("Should parse")) => - { - Ok(Some(Scanners::Shadowserver)) - } - &_ => Ok(None), - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_detect_scanner_from_name() { - let ptr = Name::from_str("scan-47e.shadowserver.org.").unwrap(); - - assert_eq!( - detect_scanner_from_name(&ptr).unwrap(), - Some(Scanners::Shadowserver) - ); - } - - #[test] - fn test_detect_scanner() { - let cname_ptr = Name::from_str("111.0-24.197.62.64.in-addr.arpa.").unwrap(); - let ptr = Name::from_str("scan-47e.shadowserver.org.").unwrap(); - - assert_eq!( - detect_scanner(&ResolvedResult { - query: cname_ptr, - result: Some(ptr), - error: None - }) - .unwrap(), - Some(Scanners::Shadowserver) - ); - } -} diff --git a/snow-scanner/src/worker/scanners.rs b/snow-scanner/src/worker/scanners.rs index 8a56ff0..eb25b0e 100644 --- a/snow-scanner/src/worker/scanners.rs +++ b/snow-scanner/src/worker/scanners.rs @@ -5,10 +5,11 @@ use diesel::mysql::MysqlValue; use diesel::serialize; use diesel::serialize::IsNull; use diesel::sql_types::Text; +use hickory_resolver::Name; use rocket::request::FromParam; +use std::str::FromStr; use serde::{Deserialize, Deserializer}; -use std::fmt; use std::io::Write; #[derive(Debug, Clone, Copy, FromSqlRow, PartialEq)] @@ -20,35 +21,45 @@ pub enum Scanners { InternetMeasurement, } -pub trait IsStatic { +pub trait ScannerMethods { fn is_static(self: &Self) -> bool; + fn static_file_name(self: &Self) -> Option<&str>; + fn funny_name(self: &Self) -> &str; } -impl IsStatic for Scanners { +impl ScannerMethods for Scanners { fn is_static(self: &Self) -> bool { match self { - Scanners::Censys => true, - Scanners::InternetMeasurement => true, + Self::Censys => true, + Self::InternetMeasurement => true, _ => false, } } + + fn static_file_name(self: &Self) -> Option<&str> { + match self { + Self::Censys => Some("censys.txt"), + Self::InternetMeasurement => Some("internet-measurement.com.txt"), + _ => None, + } + } + + fn funny_name(self: &Self) -> &str { + match self { + Self::Stretchoid => "stretchoid agent", + Self::Binaryedge => "binaryedge ninja", + Self::Censys => "Censys node", + Self::InternetMeasurement => "internet measurement probe", + Self::Shadowserver => "cloudy shadowserver", + } + } } impl FromParam<'_> for Scanners { type Error = String; fn from_param(param: &'_ str) -> Result { - match param { - "stretchoid" => Ok(Scanners::Stretchoid), - "binaryedge" => Ok(Scanners::Binaryedge), - "shadowserver" => Ok(Scanners::Shadowserver), - "stretchoid.txt" => Ok(Scanners::Stretchoid), - "binaryedge.txt" => Ok(Scanners::Binaryedge), - "shadowserver.txt" => Ok(Scanners::Shadowserver), - "censys.txt" => Ok(Scanners::Censys), - "internet-measurement.com.txt" => Ok(Scanners::InternetMeasurement), - v => Err(format!("Unknown value: {v}")), - } + param.try_into() } } @@ -59,48 +70,29 @@ impl<'de> Deserialize<'de> for Scanners { { let s = >::deserialize(deserializer)?; let k: &str = s[0].as_str(); - match k { - "stretchoid" => Ok(Scanners::Stretchoid), - "binaryedge" => Ok(Scanners::Binaryedge), - "shadowserver" => Ok(Scanners::Shadowserver), - "stretchoid.txt" => Ok(Scanners::Stretchoid), - "binaryedge.txt" => Ok(Scanners::Binaryedge), - "shadowserver.txt" => Ok(Scanners::Shadowserver), - "censys.txt" => Ok(Scanners::Censys), - "internet-measurement.com.txt" => Ok(Scanners::InternetMeasurement), - v => Err(serde::de::Error::custom(format!( - "Unknown value: {}", - v.to_string() - ))), + match k.try_into() { + Ok(scanners) => Ok(scanners), + Err(v) => Err(serde::de::Error::custom(format!("Unknown value: {}", v))), } } } -impl fmt::Display for Scanners { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - match self { - Self::Stretchoid => "stretchoid", - Self::Binaryedge => "binaryedge", - Self::Censys => "censys", - Self::InternetMeasurement => "internet-measurement.com", - Self::Shadowserver => "shadowserver", - } - ) +impl ToString for Scanners { + fn to_string(&self) -> String { + match self { + Self::Stretchoid => "stretchoid", + Self::Binaryedge => "binaryedge", + Self::Censys => "censys", + Self::InternetMeasurement => "internet-measurement.com", + Self::Shadowserver => "shadowserver", + } + .to_string() } } impl serialize::ToSql for Scanners { fn to_sql(&self, out: &mut serialize::Output) -> serialize::Result { - match *self { - Self::Stretchoid => out.write_all(b"stretchoid")?, - Self::Binaryedge => out.write_all(b"binaryedge")?, - Self::Censys => out.write_all(b"censys")?, - Self::InternetMeasurement => out.write_all(b"internet-measurement.com")?, - Self::Shadowserver => out.write_all(b"shadowserver")?, - }; + out.write_all(self.to_string().as_bytes())?; Ok(IsNull::No) } @@ -118,16 +110,81 @@ impl deserialize::FromSql for Scanners { } } +// Used for FromSql & FromParam & Deserialize impl TryInto for &str { type Error = String; fn try_into(self) -> Result { - match self { + match self.replace(".txt", "").as_str() { "stretchoid" => Ok(Scanners::Stretchoid), "binaryedge" => Ok(Scanners::Binaryedge), "internet-measurement.com" => Ok(Scanners::InternetMeasurement), "shadowserver" => Ok(Scanners::Shadowserver), + "censys" => Ok(Scanners::Censys), value => Err(format!("Invalid value: {value}")), } } } + +// Used by the DNS logic +impl TryInto for Name { + type Error = String; + + fn try_into(self) -> Result { + match self { + ref name + if name + .trim_to(2) + .eq_case(&Name::from_str("binaryedge.ninja.").expect("Should parse")) => + { + Ok(Scanners::Binaryedge) + } + ref name + if name + .trim_to(2) + .eq_case(&Name::from_str("stretchoid.com.").expect("Should parse")) => + { + Ok(Scanners::Stretchoid) + } + ref name + if name + .trim_to(2) + .eq_case(&Name::from_str("shadowserver.org.").expect("Should parse")) => + { + Ok(Scanners::Shadowserver) + } + ref name => Err(format!("Invalid hostname: {name}")), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use std::str::FromStr; + + #[test] + fn test_detect_scanner_from_name() { + let ptr = Name::from_str("scan-47e.shadowserver.org.").unwrap(); + + let res: Result = ptr.try_into(); + + assert_eq!(res.unwrap(), Scanners::Shadowserver); + } + + #[test] + fn test_detect_scanner() { + let cname_ptr = Name::from_str("111.0-24.197.62.64.in-addr.arpa.").unwrap(); + let ptr = Name::from_str("scan-47e.shadowserver.org.").unwrap(); + + assert_eq!( + detect_scanner(&ResolvedResult { + query: cname_ptr, + result: Some(ptr), + error: None + }) + .unwrap(), + Some(Scanners::Shadowserver) + ); + } +} diff --git a/snow-scanner/src/worker/utils.rs b/snow-scanner/src/worker/utils.rs index d049236..9b6815c 100644 --- a/snow-scanner/src/worker/utils.rs +++ b/snow-scanner/src/worker/utils.rs @@ -16,9 +16,9 @@ pub fn get_dns_rr() -> RoundrobinWeight> { IpAddr::from_str("9.9.9.10").unwrap(), IpAddr::from_str("2.56.220.2").unwrap(), // G-Core DNS IpAddr::from_str("95.85.95.85").unwrap(), // G-Core DNS - IpAddr::from_str("193.110.81.0").unwrap(), // dns0.eu AS50902 - IpAddr::from_str("185.253.5.0").unwrap(), // dns0.eu AS50902 - IpAddr::from_str("74.82.42.42").unwrap(), // Hurricane Electric [AS6939] + IpAddr::from_str("193.110.81.0").unwrap(), // dns0.eu AS50902 + IpAddr::from_str("185.253.5.0").unwrap(), // dns0.eu AS50902 + IpAddr::from_str("74.82.42.42").unwrap(), // Hurricane Electric [AS6939] ]; let mut rr: RoundrobinWeight> = RoundrobinWeight::new();