diff --git a/snow-scanner/src/event_bus.rs b/snow-scanner/src/event_bus.rs index a32c6fe..874cd4a 100644 --- a/snow-scanner/src/event_bus.rs +++ b/snow-scanner/src/event_bus.rs @@ -7,7 +7,7 @@ use rocket::futures::channel::mpsc as rocket_mpsc; use rocket::futures::StreamExt; use rocket::tokio; use snow_scanner_worker::detection::validate_ip; -use snow_scanner_worker::scanners::Scanners; +use snow_scanner_worker::scanners::ScannerNode; use crate::Scanner; @@ -58,11 +58,11 @@ impl EventBus { return; } let name = Name::from_str(name.as_str()).unwrap(); - let scanner: Result = name.clone().try_into(); + 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) + match Scanner::find_or_new(ip, scanner_type.info.to_owned(), Some(name), db) .await { Ok(scanner) => { diff --git a/snow-scanner/src/main.rs b/snow-scanner/src/main.rs index d6650ae..610e8ca 100644 --- a/snow-scanner/src/main.rs +++ b/snow-scanner/src/main.rs @@ -33,11 +33,16 @@ use rocket_ws::WebSocket; use server::Server; use weighted_rs::Weight; -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::ScannerMethods; -use snow_scanner_worker::scanners::Scanners; use snow_scanner_worker::utils::get_dns_rr; +use snow_scanner_worker::{ + detection::{get_dns_client, get_dns_server_config, validate_ip}, + scanners::STRETCHOID, +}; +use snow_scanner_worker::{ + modules::{Network, WorkerMessages}, + scanners::{ScannerData, ScannerNode}, +}; use std::net::SocketAddr; use std::{ @@ -123,7 +128,7 @@ impl FromFormField<'_> for SafeIpAddr { async fn handle_ip( query_address: IpAddr, -) -> Result<(IpAddr, Option, ResolvedResult), String> { +) -> 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())); @@ -139,7 +144,7 @@ async fn handle_ip( match ptr_result { Ok(result) => { - let scanner: Result = result.query.clone().try_into(); + let scanner: Result = result.query.clone().try_into(); match scanner { Ok(scanner_type) => { @@ -263,17 +268,17 @@ pub struct ReportParams { ip: SafeIpAddr, } -fn reply_contents_for_scanner_found(scanner: Scanner) -> HtmlContents { +fn reply_contents_for_scanner_found(scanner: Scanner, scanner_type: ScannerData) -> HtmlContents { HtmlContents(match scanner.last_checked_at { Some(date) => format!( "Reported a {}! {} known as {} since {date}.", - scanner.scanner_name.funny_name(), + scanner_type.funny_name, scanner.ip, scanner.ip_ptr.unwrap_or("".to_string()) ), None => format!( "Reported a {}! {} known as {}.", - scanner.scanner_name.funny_name(), + scanner_type.funny_name, scanner.ip, scanner.ip_ptr.unwrap_or("".to_string()) ), @@ -286,13 +291,16 @@ async fn handle_report(mut db: DbConn, form: Form) -> MultiReply { Ok((query_address, scanner_type, result)) => match scanner_type { Some(scanner_type) => match Scanner::find_or_new( query_address, - scanner_type, + scanner_type.info, result.result.clone(), &mut db, ) .await { - Ok(scanner) => MultiReply::Content(reply_contents_for_scanner_found(scanner)), + Ok(scanner) => MultiReply::Content(reply_contents_for_scanner_found( + scanner, + scanner_type.info, + )), Err(err) => MultiReply::Error(ServerError(format!( "The IP {} resolved as {} could not be saved, server error: {err}.", form.ip.addr, @@ -365,7 +373,7 @@ async fn handle_get_collection( #[get("/scanners/")] async fn handle_list_scanners( mut db: DbConn, - scanner_name: Scanners, + scanner_name: ScannerNode, app_configs: &State, ) -> MultiReply { let static_data_dir: String = app_configs.static_data_dir.clone(); @@ -385,7 +393,7 @@ async fn handle_list_scanners( }; } - let scanners_list = match Scanner::list_names(scanner_name, &mut db).await { + let scanners_list = match Scanner::list_names(scanner_name.info, &mut db).await { Ok(data) => Ok(data), Err(err) => Err(err), }; @@ -511,7 +519,7 @@ async fn report_counts<'a>(rocket: Rocket) -> Rocket error!("{e}")); panic!("aborting launch"); }); - match Scanner::list_names(Scanners::Stretchoid, &mut DbConnection(conn)).await { + match Scanner::list_names(STRETCHOID, &mut DbConnection(conn)).await { Ok(d) => info!("Found {} Stretchoid scanners", d.len()), Err(err) => error!("Unable to fetch Stretchoid scanners: {err}"), } diff --git a/snow-scanner/src/models.rs b/snow-scanner/src/models.rs index ee1024a..546a61c 100644 --- a/snow-scanner/src/models.rs +++ b/snow-scanner/src/models.rs @@ -1,9 +1,10 @@ use std::net::IpAddr; -use crate::{DbConn, Scanners}; +use crate::DbConn; use chrono::{NaiveDateTime, Utc}; use hickory_resolver::Name; use rocket_db_pools::diesel::{dsl::insert_into, prelude::*, result::Error as DieselError}; +use snow_scanner_worker::scanners::ScannerData; use crate::schema::scan_tasks::dsl::scan_tasks; use crate::schema::scanners::dsl::scanners; @@ -14,7 +15,7 @@ use crate::schema::scanners::dsl::scanners; pub struct Scanner { pub ip: String, pub ip_type: u8, - pub scanner_name: Scanners, + pub scanner_name: String, pub ip_ptr: Option, pub created_at: NaiveDateTime, pub updated_at: Option, @@ -25,7 +26,7 @@ pub struct Scanner { impl Scanner { pub async fn find_or_new( query_address: IpAddr, - scanner_name: Scanners, + scanner_data: ScannerData<'static>, ptr: Option, conn: &mut DbConn, ) -> Result { @@ -45,7 +46,7 @@ impl Scanner { Scanner { ip: query_address.to_string(), ip_type: ip_type, - scanner_name: scanner_name.clone(), + scanner_name: scanner_data.value.to_string(), ip_ptr: match ptr { Some(ptr) => Some(ptr.to_string()), None => None, @@ -79,15 +80,16 @@ impl Scanner { } pub async fn list_names( - scanner_name: Scanners, + scanner_data: ScannerData<'static>, conn: &mut DbConn, ) -> Result, DieselError> { use crate::schema::scanners; use crate::schema::scanners::ip; + use crate::schema::scanners::scanner_name; scanners .select(ip) - .filter(scanners::scanner_name.eq(scanner_name.to_string())) + .filter(scanner_name.eq(scanner_data.value)) .order((scanners::ip_type.desc(), scanners::created_at.desc())) .load::(conn) .await diff --git a/snow-scanner/src/worker/scanners.rs b/snow-scanner/src/worker/scanners.rs index b487401..f3d418a 100644 --- a/snow-scanner/src/worker/scanners.rs +++ b/snow-scanner/src/worker/scanners.rs @@ -12,14 +12,72 @@ use std::str::FromStr; use serde::{Deserialize, Deserializer}; use std::io::Write; +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct ScannerData<'a> { + pub static_file_name: Option<&'a str>, + pub funny_name: &'a str, + pub display_name: &'a str, + pub value: &'a str, + pub dns_prefix: Option<&'a str>, +} + +pub const STRETCHOID: ScannerData = ScannerData { + static_file_name: None, + funny_name: "stretchoid agent", + display_name: "stretchoid", + value: "stretchoid", + dns_prefix: Some("stretchoid.com."), +}; +pub const BINARYEDGE: ScannerData = ScannerData { + static_file_name: None, + funny_name: "binaryedge ninja", + display_name: "binaryedge", + value: "binaryedge", + dns_prefix: Some("binaryedge.ninja."), +}; + +pub const SHADOWSERVER: ScannerData = ScannerData { + static_file_name: None, + funny_name: "cloudy shadowserver", + display_name: "shadowserver", + value: "shadowserver", + dns_prefix: Some("shadowserver.org."), +}; + +pub fn get_scanners() -> Vec> { + vec![ + STRETCHOID, + BINARYEDGE, + SHADOWSERVER, + ScannerData { + static_file_name: Some("censys.txt"), + funny_name: "Censys node", + display_name: "censys", + value: "censys", + dns_prefix: None, + }, + ScannerData { + static_file_name: Some("internet-measurement.com.txt"), + funny_name: "internet measurement probe", + display_name: "internet-measurement.com", + value: "internet-measurement.com", + dns_prefix: None, + }, + ScannerData { + static_file_name: Some("anssi.txt"), + funny_name: "French ANSSI probe", + display_name: "anssi", + value: "anssi", + dns_prefix: None, + }, + ] +} + +pub type ScannerNode = ScannersWrapper>; + #[derive(Debug, Clone, Copy, FromSqlRow, PartialEq)] -pub enum Scanners { - Stretchoid, - Binaryedge, - Shadowserver, - Censys, - InternetMeasurement, - Anssi, +pub struct ScannersWrapper { + pub info: ScannerData, } pub trait ScannerMethods { @@ -28,33 +86,21 @@ pub trait ScannerMethods { fn funny_name(self: &Self) -> &str; } -impl ScannerMethods for Scanners { +impl ScannerMethods for ScannerNode { fn is_static(self: &Self) -> bool { self.static_file_name().is_some() } fn static_file_name(self: &Self) -> Option<&str> { - match self { - Self::Censys => Some("censys.txt"), - Self::InternetMeasurement => Some("internet-measurement.com.txt"), - Self::Anssi => Some("anssi.txt"), - _ => None, - } + self.info.static_file_name } 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", - _ => (*self).into(), - } + self.info.funny_name } } -impl FromParam<'_> for Scanners { +impl FromParam<'_> for ScannerNode { type Error = String; fn from_param(param: &'_ str) -> Result { @@ -62,7 +108,7 @@ impl FromParam<'_> for Scanners { } } -impl<'de> Deserialize<'de> for Scanners { +impl<'de> Deserialize<'de> for ScannerNode { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, @@ -76,27 +122,20 @@ impl<'de> Deserialize<'de> for Scanners { } } -impl ToString for Scanners { +impl ToString for ScannerNode { fn to_string(&self) -> String { let res: &str = (*self).into(); res.to_string() } } -impl Into<&str> for Scanners { +impl Into<&str> for ScannerNode { fn into(self) -> &'static str { - match self { - Self::Stretchoid => "stretchoid", - Self::Binaryedge => "binaryedge", - Self::Censys => "censys", - Self::InternetMeasurement => "internet-measurement.com", - Self::Shadowserver => "shadowserver", - Self::Anssi => "anssi", - } + self.info.display_name } } -impl serialize::ToSql for Scanners { +impl serialize::ToSql for ScannerNode { fn to_sql(&self, out: &mut serialize::Output) -> serialize::Result { let res: &str = (*self).into(); out.write_all(res.as_bytes())?; @@ -105,11 +144,11 @@ impl serialize::ToSql for Scanners { } } -impl deserialize::FromSql for Scanners { +impl deserialize::FromSql for ScannerNode { fn from_sql(bytes: MysqlValue) -> deserialize::Result { let value = >::from_sql(bytes)?; let value = &value as &str; - let value: Result = value.try_into(); + let value: Result = value.try_into(); match value { Ok(d) => Ok(d), Err(err) => Err(err.into()), @@ -118,50 +157,38 @@ impl deserialize::FromSql for Scanners { } // Used for FromSql & FromParam & Deserialize -impl TryInto for &str { +impl TryInto for &str { type Error = String; - fn try_into(self) -> Result { - 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), - "anssi" => Ok(Scanners::Anssi), - value => Err(format!("Invalid value: {value}")), + fn try_into(self) -> Result { + let value: String = self.replace(".txt", "").as_str().to_string(); + match get_scanners() + .iter() + .find(|scanner| scanner.value.eq(&value)) + { + Some(scanner) => Ok(ScannersWrapper { info: *scanner }), + None => Err(format!("Invalid value: {value}")), } } } // Used by the DNS logic -impl TryInto for Name { +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}")), + fn try_into(self) -> Result { + let short_name = self.trim_to(2); + match get_scanners() + .iter() + .filter(|scanner| scanner.dns_prefix.is_some()) + .find(|scanner| { + short_name.eq_case( + &Name::from_str(scanner.dns_prefix.expect("Should have a DNS prefix")) + .expect("Should parse"), + ) + }) { + Some(scanner) => Ok(ScannersWrapper { info: *scanner }), + None => Err(format!("Invalid hostname: {self}")), } } } @@ -175,8 +202,8 @@ mod test { fn test_detect_scanner_from_name() { let ptr = Name::from_str("scan-47e.shadowserver.org.").unwrap(); - let res: Result = ptr.try_into(); + let res: Result = ptr.try_into(); - assert_eq!(res.unwrap(), Scanners::Shadowserver); + assert_eq!(res.unwrap().info, SHADOWSERVER); } } diff --git a/snow-scanner/src/worker/worker.rs b/snow-scanner/src/worker/worker.rs index 29c0346..82fc75d 100644 --- a/snow-scanner/src/worker/worker.rs +++ b/snow-scanner/src/worker/worker.rs @@ -4,7 +4,7 @@ use chrono::{Duration, NaiveDateTime, Utc}; use cidr::IpCidr; use dns_ptr_resolver::{get_ptr, ResolvedResult}; use log2::*; -use scanners::Scanners; +use scanners::ScannerNode; use tungstenite::stream::MaybeTlsStream; use tungstenite::{connect, Error, Message, WebSocket}; use weighted_rs::Weight; @@ -160,7 +160,7 @@ impl Worker { let client = get_dns_client(&get_dns_server_config(&rr_dns_servers.next().unwrap())); match get_ptr(addr, client) { Ok(result) => { - let scanner: Result = result.query.clone().try_into(); + let scanner: Result = result.query.clone().try_into(); match scanner { Ok(scanner_name) => { @@ -181,7 +181,12 @@ impl Worker { } } - fn report_detection(&mut self, scanner_name: Scanners, addr: IpAddr, result: ResolvedResult) { + fn report_detection( + &mut self, + scanner_name: ScannerNode, + addr: IpAddr, + result: ResolvedResult, + ) { info!("Detected {:?} for {addr}", scanner_name); let request = WorkerMessages::ScannerFoundResponse { name: result.result.unwrap().to_string(),