diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 6d596400b..2ba732341 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -2465,12 +2465,6 @@ dependencies = [ "universal-hash", ] -[[package]] -name = "portable-atomic" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" - [[package]] name = "powerfmt" version = "0.2.0" diff --git a/rust/src/openvas/result_collector.rs b/rust/src/openvas/result_collector.rs index 2dd8d22c1..d1daf04a8 100644 --- a/rust/src/openvas/result_collector.rs +++ b/rust/src/openvas/result_collector.rs @@ -11,7 +11,7 @@ use std::{ }; use crate::openvas::openvas_redis::{KbAccess, VtHelper}; -use crate::osp::{OspResultType, OspScanResult, StringF32}; +use crate::osp::{OspResultType, OspScanResult}; use crate::storage::redis::RedisStorageResult; /// Structure to hold the results retrieve from redis main kb @@ -111,62 +111,29 @@ where } }; } - - if error_msg { + let mut push_result = |x| { scan_results.push(OspScanResult { - result_type: OspResultType::Error, - host: current_host, - hostname: host_name, - port, - test_id: roid.to_string(), - description: value, - severity: StringF32::from(0.0), - name: rname, + result_type: x, + host: Some(current_host.clone()), + hostname: Some(host_name.clone()), + port: Some(port.clone()), + test_id: Some(roid.to_string()), + description: value.clone(), + severity: None, + name: rname.clone(), }); + }; + + if error_msg { + push_result(OspResultType::Error); } else if result_type == "LOG" { - scan_results.push(OspScanResult { - result_type: OspResultType::Log, - host: current_host, - hostname: host_name, - port, - test_id: roid.to_string(), - description: value, - severity: StringF32::from(0.0), - name: rname, - }); + push_result(OspResultType::Log); } else if result_type == "HOST_START" { - scan_results.push(OspScanResult { - result_type: OspResultType::HostStart, - host: current_host, - hostname: host_name, - port, - test_id: roid.to_string(), - description: value, - severity: StringF32::from(0.0), - name: rname, - }); + push_result(OspResultType::HostStart); } else if result_type == "HOST_END" { - scan_results.push(OspScanResult { - result_type: OspResultType::HostEnd, - host: current_host, - hostname: host_name, - port, - test_id: roid.to_string(), - description: value, - severity: StringF32::from(0.0), - name: rname, - }); + push_result(OspResultType::HostEnd); } else if result_type == "ALARM" { - scan_results.push(OspScanResult { - result_type: OspResultType::Alarm, - host: current_host, - hostname: host_name, - port, - test_id: roid.to_string(), - description: value, - severity: StringF32::from(0.0), - name: rname, - }); + push_result(OspResultType::Alarm); } else if result_type == "DEADHOST" { new_dead += i64::from_str(&value).expect("Valid amount of dead hosts"); } else if host_count { diff --git a/rust/src/osp/response.rs b/rust/src/osp/response.rs index 8fb1a7c3f..ec18d2cae 100644 --- a/rust/src/osp/response.rs +++ b/rust/src/osp/response.rs @@ -13,6 +13,12 @@ use super::commands::Error; #[derive(Clone, Copy, Debug, PartialEq)] pub struct StringU64(u64); +impl Default for StringU64 { + fn default() -> Self { + StringU64(0) + } +} + impl From for StringU64 { fn from(value: i64) -> Self { StringU64(value as u64) @@ -41,6 +47,12 @@ impl From for i32 { #[derive(Clone, Debug, PartialEq)] pub struct StringF32(f32); +impl Default for StringF32 { + fn default() -> Self { + StringF32(0.0) + } +} + impl From for StringF32 { fn from(value: f32) -> Self { StringF32(value) @@ -59,6 +71,16 @@ impl From for f64 { } } +fn invalid_number_msg(value: &str) -> Result +where + V: Default, +{ + if !value.trim().is_empty() { + tracing::warn!(value, "invalid number, returning default"); + } + Ok(V::default()) +} + impl<'de> Deserialize<'de> for StringF32 { fn deserialize(deserializer: D) -> Result where @@ -77,7 +99,7 @@ impl<'de> Deserialize<'de> for StringF32 { { match value.parse::() { Ok(value) => Ok(StringF32(value)), - Err(_) => Err(E::custom("invalid number")), + Err(_) => invalid_number_msg::(value), } } } @@ -104,7 +126,7 @@ impl<'de> Deserialize<'de> for StringU64 { { match value.parse::() { Ok(value) => Ok(StringU64(value)), - Err(_) => Err(E::custom("invalid number")), + Err(_) => invalid_number_msg::(value), } } } @@ -326,19 +348,19 @@ pub enum ResultType { pub struct ScanResult { #[serde(rename = "@host")] /// Host - pub host: String, + pub host: Option, #[serde(rename = "@hostname")] /// Hostname - pub hostname: String, + pub hostname: Option, #[serde(rename = "@severity")] /// Severity - pub severity: StringF32, + pub severity: Option, #[serde(rename = "@port")] /// Port - pub port: String, + pub port: Option, #[serde(rename = "@test_id")] /// Test ID - pub test_id: String, + pub test_id: Option, #[serde(rename = "@name")] /// Name pub name: String, @@ -383,14 +405,13 @@ impl From<&ScanResult> for crate::models::Result { fn from(result: &ScanResult) -> Self { // name == script_name can be found via oid and is ignored here let (port, protocol) = { - let (m_port, m_protocol) = result - .port - .split_once('/') - .unwrap_or((result.port.as_str(), "")); - ( - m_port.parse().ok(), - crate::models::Protocol::try_from(m_protocol).ok(), - ) + result.clone().port.map_or((None, None), |port| { + let (m_port, m_protocol) = port.split_once('/').unwrap_or((port.as_str(), "")); + ( + m_port.parse().ok(), + crate::models::Protocol::try_from(m_protocol).ok(), + ) + }) }; let r_type = result.into(); let message = match result.description.as_str() { @@ -408,17 +429,11 @@ impl From<&ScanResult> for crate::models::Result { crate::models::Result { id: 0, - hostname: match result.hostname.as_str() { - "" => None, - _ => Some(result.hostname.clone()), - }, - ip_address: match result.host.as_str() { - "" => None, - _ => Some(result.host.clone()), - }, + hostname: result.hostname.clone(), + ip_address: result.host.clone(), port, protocol, - oid: Some(result.test_id.clone()), + oid: result.test_id.clone(), r_type, message, detail: detail.extract(), @@ -673,6 +688,59 @@ mod tests { ); } + #[test] + fn empty_optional_fields() { + // types Alarm, Log Message, nn + // TODO write tests for Log Message, Error Message, Alarm + let xml = r#" + + + + + bla + + + bla + + + + + "#; + let response: Response = from_str(xml).unwrap(); + match response { + Response::GetScans { status, scan } => { + assert_eq!(status.text, "OK"); + assert_eq!(status.code, 200.into()); + if let Some(scan) = scan { + assert_eq!(scan.results.result[0].severity, None); + assert_eq!(scan.results.result[1].severity, None); + assert_eq!(scan.results.result[1].hostname, None); + assert_eq!(scan.results.result[1].host, None); + assert_eq!(scan.results.result[1].test_id, None); + } else { + panic!("no scan"); + } + } + _ => panic!("wrong type: {:?}", response), + } + } + + #[test] fn init_response() { let xml = r#" @@ -753,11 +821,11 @@ mod tests { assert_eq!(scan.progress, 78.into()); assert_eq!(scan.status, "finished".into()); assert_eq!(scan.start_time, Some(1432824206.into())); - assert_eq!(scan.results.result[0].host, "192.168.1.252"); - assert_eq!(scan.results.result[0].hostname, ""); - assert_eq!(scan.results.result[0].severity, 2.5.into()); - assert_eq!(scan.results.result[0].port, "443/tcp"); - assert_eq!(scan.results.result[0].test_id, ""); + assert_eq!(scan.results.result[0].host, Some("192.168.1.252".into())); + assert_eq!(scan.results.result[0].hostname, None); + assert_eq!(scan.results.result[0].severity, Some(2.5.into())); + assert_eq!(scan.results.result[0].port, Some("443/tcp".into())); + assert_eq!(scan.results.result[0].test_id, None); assert_eq!(scan.results.result[0].name, "Path disclosure vulnerability"); assert_eq!(scan.results.result[0].result_type, ResultType::Log); assert_eq!(scan.results.result[0].description, "bla");