diff --git a/rust/Cargo.lock b/rust/Cargo.lock index f450bf57a..a8416c65c 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1760,8 +1760,8 @@ dependencies = [ "nasl-interpreter", "nasl-syntax", "notus", + "quick-xml", "redis-storage", - "scanconfig", "serde", "serde_json", "storage", @@ -2677,19 +2677,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scanconfig" -version = "0.1.0" -dependencies = [ - "models", - "quick-xml", - "serde", - "serde_json", - "storage", - "tracing", - "urlencoding", -] - [[package]] name = "schannel" version = "0.1.23" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 5ebca7547..cb7420d0c 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -26,7 +26,6 @@ members = [ "osp", "openvasctl", "openvasd", - "scanconfig", "infisto", "smoketest", "notus", diff --git a/rust/nasl-cli/Cargo.toml b/rust/nasl-cli/Cargo.toml index 4eb8d8048..8191810f8 100644 --- a/rust/nasl-cli/Cargo.toml +++ b/rust/nasl-cli/Cargo.toml @@ -16,7 +16,6 @@ walkdir = "2" feed = {path = "../feed"} nasl-syntax = { path = "../nasl-syntax" } -scanconfig = { path = "../scanconfig" } models = { path = "../models" } nasl-interpreter = { path = "../nasl-interpreter", default-features = false } storage = { path = "../storage" } @@ -28,6 +27,7 @@ serde_json = "1.0.96" toml = "0.8.6" serde = "1.0.190" notus = { version = "0.1.0", path = "../notus" } +quick-xml = { version = "0.28.1", features = ["serialize"] } [features] diff --git a/rust/nasl-cli/src/scanconfig.rs b/rust/nasl-cli/src/scanconfig.rs index 2e3db6c19..a320b8ddd 100644 --- a/rust/nasl-cli/src/scanconfig.rs +++ b/rust/nasl-cli/src/scanconfig.rs @@ -1,8 +1,12 @@ +use std::fmt::{Display, Formatter}; use std::{io::BufReader, path::PathBuf, sync::Arc}; use clap::{arg, value_parser, Arg, ArgAction, Command}; +use serde::Deserialize; use crate::{get_path_from_openvas, read_openvas_config, CliError, CliErrorKind}; +use std::collections::HashMap; +use std::io::BufRead; pub fn extend_args(cmd: Command) -> Command { crate::add_verbose( @@ -40,7 +44,7 @@ fn execute( port_list: Option<&String>, stdin: bool, ) -> Result<(), CliError> { - let map_error = |f: &str, e: scanconfig::Error| CliError { + let map_error = |f: &str, e: Error| CliError { filename: f.to_string(), kind: CliErrorKind::Corrupt(format!("{e:?}")), }; @@ -81,15 +85,15 @@ fn execute( Some(ports) => { tracing::debug!("reading port list from {ports}"); let reader = as_bufreader(ports)?; - scanconfig::parse_portlist(reader).map_err(|e| map_error(ports, e))? + parse_portlist(reader).map_err(|e| map_error(ports, e))? } None => vec![], }; let mut vts = vec![]; for a in config.iter().map(|f| { - as_bufreader(f).map_err(CliError::from).and_then(|r| { - scanconfig::parse_vts(r, storage.as_ref(), &scan.vts).map_err(|e| map_error(f, e)) - }) + as_bufreader(f) + .map_err(CliError::from) + .and_then(|r| parse_vts(r, storage.as_ref(), &scan.vts).map_err(|e| map_error(f, e))) }) { vts.extend(a?); } @@ -102,3 +106,383 @@ fn execute( println!("{}", out); Ok(()) } + +#[derive(Clone, Debug, Deserialize, PartialEq)] +struct PortRange { + #[serde(rename = "@id")] + id: String, + start: usize, + end: usize, + #[serde(rename(deserialize = "type"))] + port_type: String, + comment: Option, +} + +impl PortRange { + fn as_port_range(&self) -> models::PortRange { + let end = match self.end { + 0 => None, + _ => Some(self.end), + }; + models::PortRange { + start: self.start, + end, + } + } +} + +#[derive(Clone, Debug, Deserialize, PartialEq)] +struct PortRangeList { + port_range: Vec, +} + +#[derive(Clone, Debug, Deserialize, PartialEq)] +struct PortList { + #[serde(rename = "@id")] + id: String, + name: Option, + comment: Option, + port_ranges: PortRangeList, +} + +impl PortList { + fn as_port_list(&self) -> Vec { + let mut tcp = vec![]; + let mut udp = vec![]; + let mut none = vec![]; + for p in self.port_ranges.port_range.iter() { + match p.port_type.as_str() { + "tcp" => tcp.push(p.as_port_range()), + "udp" => udp.push(p.as_port_range()), + _ => none.push(p.as_port_range()), + } + } + vec![ + models::Port { + protocol: Some(models::Protocol::TCP), + range: tcp, + }, + models::Port { + protocol: Some(models::Protocol::UDP), + range: udp, + }, + models::Port { + protocol: None, + range: none, + }, + ] + } +} + +/// Error types +#[derive(Debug, Clone)] +pub enum Error { + /// XML parse error + ParseError(String), + /// Storage error + StorageError(storage::StorageError), +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Error::ParseError(s) => write!(f, "Parse error: {}", s), + Error::StorageError(s) => write!(f, "Storage error: {}", s), + } + } +} + +impl From for Error { + fn from(e: storage::StorageError) -> Self { + Error::StorageError(e) + } +} + +impl std::error::Error for Error {} + +/// Parse a port list from a string. +pub fn parse_portlist(pl: R) -> Result, Error> +where + R: BufRead, +{ + let result = quick_xml::de::from_reader::(pl) + .map_err(|e| Error::ParseError(format!("Error parsing port list: {}", e)))?; + tracing::trace!( + "transforming portlist {} {} ({}) with {} entries.", + &result.id, + result.name.as_deref().unwrap_or(""), + result.comment.as_deref().unwrap_or(""), + &result.port_ranges.port_range.len() + ); + Ok(result.as_port_list()) +} + +#[derive(Clone, Debug, Deserialize, PartialEq)] +struct ScanConfig { + #[serde(rename = "@id")] + id: String, + name: Option, + comment: Option, + #[serde(rename(deserialize = "type"))] + scan_type: String, + #[serde(rename(deserialize = "usage_type"))] + usage_type: String, + preferences: ScanConfigPreferences, + nvt_selectors: ScanConfigNvtSelectors, +} + +#[derive(Clone, Debug, Deserialize, PartialEq)] +struct ScanConfigNvtSelectors { + nvt_selector: Vec, +} + +#[derive(Clone, Debug, Deserialize, PartialEq)] +struct ScanConfigNvtSelector { + include: usize, + #[serde(rename(deserialize = "type"))] + nvt_type: usize, + family_or_nvt: String, +} + +#[derive(Clone, Debug, Deserialize, PartialEq)] +struct ScanConfigPreferences { + preference: Vec, +} + +#[derive(Clone, Debug, Deserialize, PartialEq)] +struct ScanConfigPreference { + id: u16, + name: String, + value: String, + #[serde(rename(deserialize = "type"))] + preference_type: String, + nvt: ScanConfigPreferenceNvt, +} + +#[derive(Clone, Debug, Deserialize, PartialEq)] +struct ScanConfigPreferenceNvt { + #[serde(rename = "@oid")] + oid: String, + name: String, +} + +pub fn parse_vts( + sc: R, + retriever: &dyn storage::Retriever, + vts: &[models::VT], +) -> Result, Error> +where + R: BufRead, +{ + let result = quick_xml::de::from_reader::(sc) + .map_err(|e| Error::ParseError(format!("Error parsing vts: {}", e)))?; + tracing::debug!( + "transforming vts {} {} ({}) with {} entries.", + &result.id, + result.name.as_deref().unwrap_or(""), + result.comment.as_deref().unwrap_or(""), + &result.preferences.preference.len() + ); + let preference_lookup: HashMap> = result + .preferences + .preference + .iter() + .map(|p| { + ( + p.nvt.oid.clone(), + vec![models::Parameter { + id: p.id, + value: p.value.clone(), + }], + ) + }) + .collect(); + let oid_to_vt = |oid: &String| -> Result { + let parameters = preference_lookup.get(oid).unwrap_or(&vec![]).clone(); + Ok(models::VT { + oid: oid.clone(), + parameters, + }) + }; + let is_not_already_present = |oid: &String| -> bool { !vts.iter().any(|vt| vt.oid == *oid) }; + result + .nvt_selectors + .nvt_selector + .iter() + .flat_map(|s| { + if s.nvt_type == 2 { + if is_not_already_present(&s.family_or_nvt) { + vec![oid_to_vt(&s.family_or_nvt)] + } else { + vec![] + } + } else { + // lookup oids via family + use storage::item::NVTField; + use storage::item::NVTKey; + use storage::Field; + use storage::Retrieve; + match retriever.retrieve_by_field( + Field::NVT(NVTField::Family(s.family_or_nvt.clone())), + Retrieve::NVT(Some(NVTKey::Oid)), + ) { + Ok(nvt) => { + let result: Vec<_> = nvt + .flat_map(|(_, f)| match &f { + Field::NVT(NVTField::Oid(oid)) if is_not_already_present(oid) => { + Some(oid_to_vt(oid)) + } + _ => None, + }) + .collect(); + + tracing::debug!( + "found {} nvt entries for family {}", + result.len(), + s.family_or_nvt + ); + result + } + Err(e) => vec![Err(e.into())], + } + } + }) + .collect() +} + +#[cfg(test)] +mod tests { + use storage::Storage; + + use super::*; + + #[test] + fn parse_portlist() { + let pl = r#" + + OpenVAS Default + Version 20200827. + + + 1 + 5 + tcp + + + + 7 + 7 + tcp + + + + 7 + 7 + udp + + + + 7 + 7 + + + + +"#; + let presult = quick_xml::de::from_str::(pl).unwrap(); + assert_eq!(presult.port_ranges.port_range.len(), 4); + + let result = super::parse_portlist(pl.as_bytes()).unwrap(); + assert_eq!(result.len(), 3); + assert_eq!(result[0].protocol, Some(models::Protocol::TCP)); + assert_eq!(result[0].range.len(), 2); + assert_eq!(result[1].protocol, Some(models::Protocol::UDP)); + assert_eq!(result[1].range.len(), 1); + assert_eq!(result[2].protocol, None); + assert_eq!(result[2].range.len(), 1); + } + + #[test] + fn parse_scanconfig() { + let sc = r#" + + Discovery + Network Discovery scan configuration. Version 20201215. + 0 + scan + + + + Ping Host + + Report about unreachable Hosts + checkbox + no + 6 + + + + Services + + Test SSL based services + radio + All;Known SSL ports;None + All;None + 1 + + + + Ping Host + + Mark unreachable Hosts as dead (not scanning) + checkbox + yes + 5 + + + + + 1 + 2 + 1.3.6.1.4.1.25623.1.0.803575 + + + 1 + 1 + Product detection + + + "#; + let result = quick_xml::de::from_str::(sc).unwrap(); + assert_eq!(result.nvt_selectors.nvt_selector.len(), 2); + assert_eq!(result.preferences.preference.len(), 3); + let shop: storage::DefaultDispatcher = storage::DefaultDispatcher::default(); + let add_product_detection = |oid: &str| { + shop.as_dispatcher() + .dispatch( + &oid.to_string(), + storage::Field::NVT(storage::item::NVTField::Oid(oid.to_owned().to_string())), + ) + .unwrap(); + shop.as_dispatcher() + .dispatch( + &oid.to_string(), + storage::Field::NVT(storage::item::NVTField::Family( + "Product detection".to_string(), + )), + ) + .unwrap(); + }; + add_product_detection("1"); + add_product_detection("2"); + add_product_detection("4"); + add_product_detection("5"); + let exists = vec![models::VT { + oid: "1".to_string(), + parameters: vec![], + }]; + + let result = super::parse_vts(sc.as_bytes(), &shop, &exists).unwrap(); + assert_eq!(result.len(), 4); + } +} diff --git a/rust/scanconfig/Cargo.toml b/rust/scanconfig/Cargo.toml deleted file mode 100644 index b4101a34a..000000000 --- a/rust/scanconfig/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "scanconfig" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -storage = { path = "../storage" } -models = { path = "../models" } -serde = { version = "1.0", features = ["derive"]} -quick-xml = { version = "0.28.1", features = ["serialize"] } -serde_json = "1.0" -urlencoding = "2.1.2" -tracing = "0.1.37" diff --git a/rust/scanconfig/src/lib.rs b/rust/scanconfig/src/lib.rs deleted file mode 100644 index 4885a6812..000000000 --- a/rust/scanconfig/src/lib.rs +++ /dev/null @@ -1,387 +0,0 @@ -use std::{ - collections::HashMap, - fmt::{Display, Formatter}, - io::BufRead, -}; - -use serde::Deserialize; - -#[derive(Clone, Debug, Deserialize, PartialEq)] -struct PortRange { - #[serde(rename = "@id")] - id: String, - start: usize, - end: usize, - #[serde(rename(deserialize = "type"))] - port_type: String, - comment: Option, -} - -impl PortRange { - fn as_port_range(&self) -> models::PortRange { - let end = match self.end { - 0 => None, - _ => Some(self.end), - }; - models::PortRange { - start: self.start, - end, - } - } -} - -#[derive(Clone, Debug, Deserialize, PartialEq)] -struct PortRangeList { - port_range: Vec, -} - -#[derive(Clone, Debug, Deserialize, PartialEq)] -struct PortList { - #[serde(rename = "@id")] - id: String, - name: Option, - comment: Option, - port_ranges: PortRangeList, -} - -impl PortList { - fn as_port_list(&self) -> Vec { - let mut tcp = vec![]; - let mut udp = vec![]; - let mut none = vec![]; - for p in self.port_ranges.port_range.iter() { - match p.port_type.as_str() { - "tcp" => tcp.push(p.as_port_range()), - "udp" => udp.push(p.as_port_range()), - _ => none.push(p.as_port_range()), - } - } - vec![ - models::Port { - protocol: Some(models::Protocol::TCP), - range: tcp, - }, - models::Port { - protocol: Some(models::Protocol::UDP), - range: udp, - }, - models::Port { - protocol: None, - range: none, - }, - ] - } -} - -/// Error types -#[derive(Debug, Clone)] -pub enum Error { - /// XML parse error - ParseError(String), - /// Storage error - StorageError(storage::StorageError), -} - -impl Display for Error { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Error::ParseError(s) => write!(f, "Parse error: {}", s), - Error::StorageError(s) => write!(f, "Storage error: {}", s), - } - } -} - -impl From for Error { - fn from(e: storage::StorageError) -> Self { - Error::StorageError(e) - } -} - -impl std::error::Error for Error {} - -/// Parse a port list from a string. -pub fn parse_portlist(pl: R) -> Result, Error> -where - R: BufRead, -{ - let result = quick_xml::de::from_reader::(pl) - .map_err(|e| Error::ParseError(format!("Error parsing port list: {}", e)))?; - tracing::trace!( - "transforming portlist {} {} ({}) with {} entries.", - &result.id, - result.name.as_deref().unwrap_or(""), - result.comment.as_deref().unwrap_or(""), - &result.port_ranges.port_range.len() - ); - Ok(result.as_port_list()) -} - -#[derive(Clone, Debug, Deserialize, PartialEq)] -struct ScanConfig { - #[serde(rename = "@id")] - id: String, - name: Option, - comment: Option, - #[serde(rename(deserialize = "type"))] - scan_type: String, - #[serde(rename(deserialize = "usage_type"))] - usage_type: String, - preferences: ScanConfigPreferences, - nvt_selectors: ScanConfigNvtSelectors, -} - -#[derive(Clone, Debug, Deserialize, PartialEq)] -struct ScanConfigNvtSelectors { - nvt_selector: Vec, -} - -#[derive(Clone, Debug, Deserialize, PartialEq)] -struct ScanConfigNvtSelector { - include: usize, - #[serde(rename(deserialize = "type"))] - nvt_type: usize, - family_or_nvt: String, -} - -#[derive(Clone, Debug, Deserialize, PartialEq)] -struct ScanConfigPreferences { - preference: Vec, -} - -#[derive(Clone, Debug, Deserialize, PartialEq)] -struct ScanConfigPreference { - id: u16, - name: String, - value: String, - #[serde(rename(deserialize = "type"))] - preference_type: String, - nvt: ScanConfigPreferenceNvt, -} - -#[derive(Clone, Debug, Deserialize, PartialEq)] -struct ScanConfigPreferenceNvt { - #[serde(rename = "@oid")] - oid: String, - name: String, -} - -pub fn parse_vts( - sc: R, - retriever: &dyn storage::Retriever, - vts: &[models::VT], -) -> Result, Error> -where - R: BufRead, -{ - let result = quick_xml::de::from_reader::(sc) - .map_err(|e| Error::ParseError(format!("Error parsing vts: {}", e)))?; - tracing::debug!( - "transforming vts {} {} ({}) with {} entries.", - &result.id, - result.name.as_deref().unwrap_or(""), - result.comment.as_deref().unwrap_or(""), - &result.preferences.preference.len() - ); - let preference_lookup: HashMap> = result - .preferences - .preference - .iter() - .map(|p| { - ( - p.nvt.oid.clone(), - vec![models::Parameter { - id: p.id, - value: p.value.clone(), - }], - ) - }) - .collect(); - let oid_to_vt = |oid: &String| -> Result { - let parameters = preference_lookup.get(oid).unwrap_or(&vec![]).clone(); - Ok(models::VT { - oid: oid.clone(), - parameters, - }) - }; - let is_not_already_present = |oid: &String| -> bool { !vts.iter().any(|vt| vt.oid == *oid) }; - result - .nvt_selectors - .nvt_selector - .iter() - .flat_map(|s| { - if s.nvt_type == 2 { - if is_not_already_present(&s.family_or_nvt) { - vec![oid_to_vt(&s.family_or_nvt)] - } else { - vec![] - } - } else { - // lookup oids via family - use storage::item::NVTField; - use storage::item::NVTKey; - use storage::Field; - use storage::Retrieve; - match retriever.retrieve_by_field( - Field::NVT(NVTField::Family(s.family_or_nvt.clone())), - Retrieve::NVT(Some(NVTKey::Oid)), - ) { - Ok(nvt) => { - let result: Vec<_> = nvt - .flat_map(|(_, f)| match &f { - Field::NVT(NVTField::Oid(oid)) if is_not_already_present(oid) => { - Some(oid_to_vt(oid)) - } - _ => None, - }) - .collect(); - - tracing::debug!( - "found {} nvt entries for family {}", - result.len(), - s.family_or_nvt - ); - result - } - Err(e) => vec![Err(e.into())], - } - } - }) - .collect() -} - -#[cfg(test)] -mod tests { - use storage::Storage; - - use super::*; - - #[test] - fn parse_portlist() { - let pl = r#" - - OpenVAS Default - Version 20200827. - - - 1 - 5 - tcp - - - - 7 - 7 - tcp - - - - 7 - 7 - udp - - - - 7 - 7 - - - - -"#; - let presult = quick_xml::de::from_str::(pl).unwrap(); - assert_eq!(presult.port_ranges.port_range.len(), 4); - - let result = super::parse_portlist(pl.as_bytes()).unwrap(); - assert_eq!(result.len(), 3); - assert_eq!(result[0].protocol, Some(models::Protocol::TCP)); - assert_eq!(result[0].range.len(), 2); - assert_eq!(result[1].protocol, Some(models::Protocol::UDP)); - assert_eq!(result[1].range.len(), 1); - assert_eq!(result[2].protocol, None); - assert_eq!(result[2].range.len(), 1); - } - - #[test] - fn parse_scanconfig() { - let sc = r#" - - Discovery - Network Discovery scan configuration. Version 20201215. - 0 - scan - - - - Ping Host - - Report about unreachable Hosts - checkbox - no - 6 - - - - Services - - Test SSL based services - radio - All;Known SSL ports;None - All;None - 1 - - - - Ping Host - - Mark unreachable Hosts as dead (not scanning) - checkbox - yes - 5 - - - - - 1 - 2 - 1.3.6.1.4.1.25623.1.0.803575 - - - 1 - 1 - Product detection - - - "#; - let result = quick_xml::de::from_str::(sc).unwrap(); - assert_eq!(result.nvt_selectors.nvt_selector.len(), 2); - assert_eq!(result.preferences.preference.len(), 3); - let shop: storage::DefaultDispatcher = storage::DefaultDispatcher::default(); - let add_product_detection = |oid: &str| { - shop.as_dispatcher() - .dispatch( - &oid.to_string(), - storage::Field::NVT(storage::item::NVTField::Oid(oid.to_owned().to_string())), - ) - .unwrap(); - shop.as_dispatcher() - .dispatch( - &oid.to_string(), - storage::Field::NVT(storage::item::NVTField::Family( - "Product detection".to_string(), - )), - ) - .unwrap(); - }; - add_product_detection("1"); - add_product_detection("2"); - add_product_detection("4"); - add_product_detection("5"); - let exists = vec![models::VT { - oid: "1".to_string(), - parameters: vec![], - }]; - - let result = super::parse_vts(sc.as_bytes(), &shop, &exists).unwrap(); - assert_eq!(result.len(), 4); - } -}