diff --git a/.github/workflows/push-container.yml b/.github/workflows/push-container.yml index b97bacf92..1680c53c4 100644 --- a/.github/workflows/push-container.yml +++ b/.github/workflows/push-container.yml @@ -141,6 +141,7 @@ jobs: org.opencontainers.image.base.name=greenbone/gvm-libs artifact-name: rs-binaries artifact-path: assets + service: openvas-scanner secrets: COSIGN_KEY_OPENSIGHT: ${{ secrets.cosign_key_opensight }} COSIGN_KEY_PASSWORD_OPENSIGHT: ${{ secrets.cosign_password_opensight }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 5731290e9..c821513dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ message ("-- Configuring the Scanner...") # VERSION: Always include major, minor and patch level. project (openvas - VERSION 23.12.1 + VERSION 23.13.1 LANGUAGES C) if (POLICY CMP0005) diff --git a/charts/openvasd/Chart.yaml b/charts/openvasd/Chart.yaml index 96d29b116..88c1ded4d 100644 --- a/charts/openvasd/Chart.yaml +++ b/charts/openvasd/Chart.yaml @@ -21,4 +21,4 @@ version: 0.1.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "23.12.1" +appVersion: "23.13.1" diff --git a/misc/ipc_openvas_tests.c b/misc/ipc_openvas_tests.c index 56f5e368a..70f257fc5 100644 --- a/misc/ipc_openvas_tests.c +++ b/misc/ipc_openvas_tests.c @@ -23,7 +23,7 @@ Ensure (ipc_openvas, ipc_data_from_json_ua_ok) gchar *ua = "localhost"; // Preapre data to be sent - data_s = g_malloc0 (sizeof (ipc_data_t *)); + data_s = g_malloc0 (sizeof (ipc_data_t)); data_s = ipc_data_type_from_user_agent (ua, strlen (ua)); const char *json = ipc_data_to_json (data_s); @@ -48,7 +48,7 @@ Ensure (ipc_openvas, ipc_data_from_json_hostname_ok) gchar *hns = "TLS certificate"; // Preapre data to be sent - data_s = g_malloc0 (sizeof (ipc_data_t *)); + data_s = g_malloc0 (sizeof (ipc_data_t)); data_s = ipc_data_type_from_hostname (hns, strlen (hns), hn, strlen (hn)); const char *json = ipc_data_to_json (data_s); @@ -70,10 +70,8 @@ Ensure (ipc_openvas, ipc_data_from_json_hostname_ok) Ensure (ipc_openvas, ipc_data_from_json_parse_error) { ipc_data_t *data_r = NULL; - char *json_fake = NULL; - // malformed json string - json_fake = g_strdup ( + char json_fake[1024] = "{\"type\":2,\"user-agent\":\"Mozilla/5.0 [en] (X11, U; Greenbone OS " "22.04.4)\"}{\"type\":2,\"user-agent\":\"Mozilla/5.0 [en] (X11, U; " "Greenbone OS 22.04.4)\"}{\"type\":2,\"user-agent\":\"Mozilla/5.0 [en] " @@ -90,10 +88,10 @@ Ensure (ipc_openvas, ipc_data_from_json_parse_error) "22.04.4)\"}{\"type\":2,\"user-agent\":\"Mozilla/5.0 [en] (X11, U; " "Greenbone OS 22.04.4)\"}{\"type\":2,\"user-agent\":\"Mozilla/5.0 [en] " "(X11, U; Greenbone OS 22.04.4)\"}{\"type\":2,\"user-agent\":\"Mozilla/5.0 " - "[en] (X11, U; Greenbone OS 22.04.4)\"}{\"type\":"); + "[en] (X11, U; Greenbone OS 22.04.4)\"}{\"type\":"; // Read received data - data_r = g_malloc0 (sizeof (ipc_data_t *)); + data_r = g_malloc0 (sizeof (ipc_data_t)); data_r = ipc_data_from_json (json_fake, strlen (json_fake)); assert_that (ipc_get_hostname_from_data (data_r), is_null); assert_that (ipc_get_hostname_source_from_data (data_r), is_null); @@ -103,25 +101,49 @@ Ensure (ipc_openvas, ipc_data_from_json_parse_error) Ensure (ipc_openvas, ipc_data_from_json_parse_many_objects) { ipc_data_t *data_r = NULL; - char *json_fake = NULL; - - // malformed json string - json_fake = - g_strdup ("{\"type\":1,\"source\":\"TLS " - "certificate\",\"hostname\":\"localhost\"}{\"type\":2,\"user-" - "agent\":\"Mozilla/5.0 [en] (X11, U; Greenbone OS " - "22.04.4)\"}"); - - // Read received data - data_r = g_malloc0 (sizeof (ipc_data_t *)); - data_r = ipc_data_from_json (json_fake, strlen (json_fake)); - - assert_that (ipc_get_hostname_from_data (data_r), - is_equal_to_string ("localhost")); - assert_that (ipc_get_hostname_source_from_data (data_r), - is_equal_to_string ("TLS certificate")); - - ipc_data_destroy (&data_r); + int len = 0; + int pos = 0; + + // json string with more than one objects + char json_fake[256] = + "{\"type\":1,\"source\":\"TLS " + "certificate\",\"hostname\":\"localhost\"}{\"type\":2,\"user-agent\":" + "\"Mozilla/5.0 [en] (X11, U; Greenbone OS 22.04.4)\"}"; + + for (int i = 0; json_fake[i] != '\0'; i++) + { + if (json_fake[i] == '}') + { + gchar *message = NULL; + len = i - pos + 1; + + message = g_malloc0 (sizeof (gchar) * (len + 1)); + memcpy (message, &json_fake[pos], len); + printf ("\n\nel mensaje %s\n\n", message); + pos = i + 1; + len = 0; + data_r = g_malloc0 (sizeof (ipc_data_t)); + data_r = ipc_data_from_json (message, strlen (message)); + if (ipc_get_data_type_from_data (data_r) == IPC_DT_HOSTNAME) + { + assert_that (ipc_get_hostname_from_data (data_r), + is_equal_to_string ("localhost")); + assert_that (ipc_get_hostname_source_from_data (data_r), + is_equal_to_string ("TLS certificate")); + + ipc_data_destroy (&data_r); + } + else + { + assert_that ( + ipc_get_user_agent_from_data (data_r), + is_equal_to_string ( + "Mozilla/5.0 [en] (X11, U; Greenbone OS 22.04.4)")); + ipc_data_destroy (&data_r); + } + g_free (message); + } + } assert_that (data_r, is_null); } diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 19d6a9911..2c201289c 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -155,6 +155,45 @@ dependencies = [ "term 1.0.0", ] +[[package]] +name = "asn1-rs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "async-trait" version = "0.1.83" @@ -243,6 +282,16 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bcder" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c627747a6774aab38beb35990d88309481378558875a41da1a4b2e373c906ef0" +dependencies = [ + "bytes", + "smallvec", +] + [[package]] name = "bcrypt-pbkdf" version = "0.10.0" @@ -831,6 +880,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der-parser" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "deranged" version = "0.3.11" @@ -1546,7 +1609,7 @@ dependencies = [ "hyper 1.5.1", "hyper-util", "log", - "rustls 0.23.18", + "rustls 0.23.19", "rustls-native-certs", "rustls-pki-types", "tokio", @@ -1957,9 +2020,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.165" +version = "0.2.166" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb4d3d38eab6c5239a362fa8bae48c03baf980a6e7079f063942d563ef3533e" +checksum = "c2ccc108bbc0b1331bd061864e7cd823c0cab660bbe6970e66e2c0614decde36" [[package]] name = "libgcrypt-sys" @@ -2294,6 +2357,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "oid-registry" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" version = "1.20.2" @@ -2499,6 +2571,16 @@ dependencies = [ "windows-sys 0.36.1", ] +[[package]] +name = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64 0.22.1", + "serde", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -3013,9 +3095,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" dependencies = [ "const-oid", "digest", @@ -3157,6 +3239,15 @@ dependencies = [ "semver", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustix" version = "0.38.41" @@ -3184,9 +3275,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.18" +version = "0.23.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f" +checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" dependencies = [ "aws-lc-rs", "log", @@ -3353,7 +3444,7 @@ dependencies = [ "rsa", "russh", "russh-keys", - "rustls 0.23.18", + "rustls 0.23.19", "rustls-pemfile 1.0.4", "rustls-pemfile 2.2.0", "sequoia-ipc", @@ -3375,6 +3466,8 @@ dependencies = [ "urlencoding", "uuid", "walkdir", + "x509-certificate", + "x509-parser", ] [[package]] @@ -3947,6 +4040,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", + "itoa", "num-conv", "powerfmt", "serde", @@ -4044,7 +4138,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.18", + "rustls 0.23.19", "rustls-pki-types", "tokio", ] @@ -4127,9 +4221,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", @@ -4692,6 +4786,42 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +[[package]] +name = "x509-certificate" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66534846dec7a11d7c50a74b7cdb208b9a581cad890b7866430d438455847c85" +dependencies = [ + "bcder", + "bytes", + "chrono", + "der", + "hex", + "pem", + "ring", + "signature", + "spki", + "thiserror", + "zeroize", +] + +[[package]] +name = "x509-parser" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror", + "time", +] + [[package]] name = "xxhash-rust" version = "0.8.12" @@ -4769,6 +4899,20 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] [[package]] name = "zerovec" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 636b17c7e..c215b4e75 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -60,7 +60,7 @@ rustls-pemfile = "2.1.2" rustls-pemfile-old = { version = "1.0.2", package = "rustls-pemfile" } sequoia-ipc = "0.30.1" sequoia-openpgp = { version = "1.16.1", default-features = false, features = [ - "crypto-openssl", + "crypto-openssl", ] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.96" @@ -78,6 +78,8 @@ tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } urlencoding = "2.1.2" uuid = { version = "1", features = ["v4", "fast-rng", "serde"] } walkdir = "2" +x509-certificate = "0.23.1" +x509-parser = "0.16.0" rayon = { version = "1.8.0", optional = true } pcap = { version = "1.0.0", optional = true } @@ -87,8 +89,8 @@ pnet_macros = { version = "0.33.0", optional = true } pnet_macros_support = { version = "0.33.0", optional = true } libssh-rs = { version = "~0.2", features = [ - "vendored-openssl", - "vendored", + "vendored-openssl", + "vendored", ], optional = true } nasl-function-proc-macro = { path = "crates/nasl-function-proc-macro" } @@ -111,18 +113,18 @@ dep-graph-parallel = ["rayon", "crossbeam-channel"] openvas_serde_support = [] serde_support = [] default = [ - "dep-graph-parallel", - "openvas_serde_support", - "enforce-no-trailing-arguments", - "serde_support", + "dep-graph-parallel", + "openvas_serde_support", + "enforce-no-trailing-arguments", + "serde_support", ] nasl-builtin-raw-ip = [ - "pcap", - "pnet_base", - "pnet", - "pnet_macros", - "pnet_macros_support", + "pcap", + "pnet_base", + "pnet", + "pnet_macros", + "pnet_macros_support", ] nasl-builtin-libssh = ["libssh-rs"] experimental = ["nasl-builtin-raw-ip", "nasl-builtin-libssh", "nasl-c-lib"] diff --git a/rust/src/feed/transpile/mod.rs b/rust/src/feed/transpile/mod.rs index b9e32ede1..8f64fa5cd 100644 --- a/rust/src/feed/transpile/mod.rs +++ b/rust/src/feed/transpile/mod.rs @@ -160,7 +160,7 @@ struct FunctionNameMatcher<'a> { parameter: Option<&'a [FindParameter]>, } -impl<'a> FunctionNameMatcher<'a> { +impl FunctionNameMatcher<'_> { fn is_function(&self, s: &Statement) -> bool { match s.kind() { StatementKind::Exit(..) => self.name.map(|x| x == "exit").unwrap_or(true), @@ -190,7 +190,7 @@ impl<'a> FunctionNameMatcher<'a> { } } -impl<'a> Matcher for FunctionNameMatcher<'a> { +impl Matcher for FunctionNameMatcher<'_> { fn matches(&self, s: &Statement) -> bool { if !self.is_function(s) { return false; @@ -644,9 +644,9 @@ pub struct FeedReplacer<'a> { replace: &'a [ReplaceCommand], } -impl<'a> FeedReplacer<'a> { +impl FeedReplacer<'_> { /// Creates a new FeedReplacer - pub fn new(root: S, replace: &'a [ReplaceCommand]) -> FeedReplacer<'_> + pub fn new(root: S, replace: &[ReplaceCommand]) -> FeedReplacer where S: AsRef, { @@ -670,7 +670,7 @@ impl<'a> FeedReplacer<'a> { } } -impl<'a> Iterator for FeedReplacer<'a> { +impl Iterator for FeedReplacer<'_> { type Item = Result, TranspileError>; fn next(&mut self) -> Option { diff --git a/rust/src/feed/update/mod.rs b/rust/src/feed/update/mod.rs index 39f50246d..8503898da 100644 --- a/rust/src/feed/update/mod.rs +++ b/rust/src/feed/update/mod.rs @@ -15,6 +15,7 @@ use crate::nasl::interpreter::{CodeInterpreter, Interpreter}; use crate::nasl::nasl_std_functions; use crate::nasl::prelude::*; use crate::nasl::syntax::AsBufReader; +use crate::nasl::utils::context::Target; use crate::nasl::ContextType; use crate::storage::{item::NVTField, ContextKey, Dispatcher, NoOpRetriever}; @@ -48,7 +49,7 @@ pub async fn feed_version( let register = Register::default(); let k = ContextKey::default(); let fr = NoOpRetriever::default(); - let target = String::default(); + let target = Target::default(); // TODO add parameter to struct let functions = nasl_std_functions(); let context = Context::new(k, target, dispatcher, &fr, loader, &functions); @@ -147,9 +148,8 @@ where let register = Register::root_initial(&self.initial); let fr = NoOpRetriever::default(); - let target = String::default(); + let target = Target::default(); let functions = nasl_std_functions(); - let context = Context::new( key.clone(), target, diff --git a/rust/src/feed/verify/mod.rs b/rust/src/feed/verify/mod.rs index 05959cd70..2b26c092a 100644 --- a/rust/src/feed/verify/mod.rs +++ b/rust/src/feed/verify/mod.rs @@ -329,7 +329,7 @@ pub struct HashSumFileItem<'a> { reader: &'a FSPluginLoader, } -impl<'a> HashSumFileItem<'a> { +impl HashSumFileItem<'_> { /// Verifies Hashsum pub fn verify(&self) -> Result<(), Error> { let hashsum = self.hasher.hash( diff --git a/rust/src/nasl/builtin/cert/mod.rs b/rust/src/nasl/builtin/cert/mod.rs new file mode 100644 index 000000000..733a220c4 --- /dev/null +++ b/rust/src/nasl/builtin/cert/mod.rs @@ -0,0 +1,446 @@ +// SPDX-FileCopyrightText: 2024 Greenbone AG +// +// SPDX-License-Identifier: GPL-2.0-or-later + +use std::collections::HashMap; +use std::sync::RwLock; + +use nasl_function_proc_macro::nasl_function; +use thiserror::Error; +use x509_certificate::X509Certificate; +use x509_parser::prelude::GeneralName; + +use crate::nasl::prelude::*; + +use super::string::encode_hex; + +#[derive(Debug, Error)] +pub enum CertError { + #[error("Unable to calculate SHA256 fingerprint")] + UnableToCalculateSHA256Fingerprint, + #[error("Unable to calculate SHA1 fingerprint")] + UnableToCalculateSHA1Fingerprint, + #[error("Query parameter 'all' not implemented yet.")] + QueryParamAllNotImplemented, +} + +fn sign_alg_oid_to_name(oid: &str) -> &str { + match oid { + "1.2.840.10040.4.1" => "id-dsa", + "1.2.840.10046.2.1" => "dhpublicnumber", + "2.16.840.1.101.2.1.1.22" => "id-keyExchangeAlgorithm", + "1.2.840.10045.1.1" => "prime-field", + "1.2.840.10045.2.1" => "id-ecPublicKey", + "1.2.840.10045.4.1" => "ecdsa-with-SHA1", + "1.2.840.10045.4.3.1" => "ecdsa-with-SHA224", + "1.2.840.10045.4.3.2" => "ecdsa-with-SHA256", + "1.2.840.10045.4.3.3" => "ecdsa-with-SHA384", + "1.2.840.10045.4.3.4" => "ecdsa-with-SHA512", + "1.3.132.1.12" => "id-ecDH", + "1.2.840.10045.2.13" => "id-ecMQV", + "1.2.840.113549.1.1.10" => "id-RSASSA-PSS", + "1.2.840.113549.1.1.11" => "sha256WithRSAEncryption", + "1.2.840.113549.1.1.12" => "sha384WithRSAEncryption", + "1.2.840.113549.1.1.13" => "sha512WithRSAEncryption", + "1.2.840.113549.1.1.14" => "sha224WithRSAEncryption", + "1.2.840.113549.1.1.8" => "id-mgf1", + "1.2.840.113549.2.2" => "md2", + "1.2.840.113549.2.4" => "md4", + "1.2.840.113549.2.5" => "md5", + "1.2.840.113549.1.1.1" => "rsaEncryption", + "1.2.840.113549.1.1.2" => "md2WithRSAEncryption", + "1.2.840.113549.1.1.3" => "md4WithRSAEncryption", + "1.2.840.113549.1.1.4" => "md5WithRSAEncryption", + "1.2.840.113549.1.1.6" => "rsaOAEPEncryptionSET", + "1.2.840.10045.3.1.1" => "secp192r1", + "1.3.132.0.1" => "sect163k1", + "1.3.132.0.15" => "sect163r2", + "1.3.132.0.33" => "secp224r1", + "1.3.132.0.26" => "sect233k1", + "1.3.132.0.27" => "sect233r1", + "1.2.840.10045.3.1.7" => "secp256r1", + "1.3.132.0.16" => "sect283k1", + "1.3.132.0.17" => "sect283r1", + "1.3.132.0.34" => "secp384r1", + "1.3.132.0.36" => "sect409k1", + "1.3.132.0.37" => "sect409r1", + "1.3.132.0.35" => "sect521r1", + "1.3.132.0.38" => "sect571k1", + "1.3.132.0.39" => "sect571r1", + "2.16.840.1.101.3.4.3.1" => "id-dsa-with-sha224", + "2.16.840.1.101.3.4.3.2" => "id-dsa-with-sha256", + "2.16.840.1.101.3.4.2.1" => "sha256", + "2.16.840.1.101.3.4.2.2" => "sha384", + "2.16.840.1.101.3.4.2.3" => "sha512", + "2.16.840.1.101.3.4.2.4" => "sha224", + _ => "unknown", + } +} + +fn pub_key_alg_oid_to_name(name: &str) -> &str { + match name { + "1.2.840.113549.1.1.1" => "RSA", + "2.5.8.1.1" => "RSA (X.509)", + "1.2.840.113549.1.1.4" => "RSA (MD5)", + "1.2.840.113549.1.1.5" => "RSA (SHA1)", + "1.2.840.10040.4.1" => "DSA", + "1.2.643.2.2.19" => "GOST R 34.10-2001", + "1.2.643.2.2.20" => "GOST R 34.10-94", + "1.2.840.10045.2.1" => "EC", + _ => "unknown", + } +} + +fn subject_oid_to_name(oid: &str) -> &str { + match oid { + "2.5.4.6" => "C", + "2.5.4.8" => "ST", + "2.5.4.7" => "L", + "2.5.4.10" => "O", + "2.5.4.3" => "CN", + "2.5.4.11" => "OU", + "2.5.4.12" => "T", + "2.5.4.42" => "GN", + "2.5.4.43" => "I", + "2.5.4.4" => "SN", + _ => oid, + } +} + +pub enum CertCommands { + Serial, + Issuer, + Subject, + NotBefore, + NotAfter, + All, + Hostnames, + FprSha256, + FprSha1, + Image, + SignatureAlgorithmName, + PublicKeyAlgorithmName, + Modulus, + Exponent, + KeySize, +} + +impl TryFrom<&str> for CertCommands { + type Error = FnError; + + fn try_from(value: &str) -> Result { + match value { + "serial" => Ok(Self::Serial), + "issuer" => Ok(Self::Issuer), + "subject" => Ok(Self::Subject), + "not-before" => Ok(Self::NotBefore), + "not-after" => Ok(Self::NotAfter), + "all" => Ok(Self::All), + "hostnames" => Ok(Self::Hostnames), + "fpr-sha-256" => Ok(Self::FprSha256), + "fpr-sha-1" => Ok(Self::FprSha1), + "image" => Ok(Self::Image), + "algorithm-name" => Ok(Self::SignatureAlgorithmName), + "signature-algorithm-name" => Ok(Self::SignatureAlgorithmName), + "public-key-algorithm-name" => Ok(Self::PublicKeyAlgorithmName), + "modulus" => Ok(Self::Modulus), + "exponent" => Ok(Self::Exponent), + "key-size" => Ok(Self::KeySize), + _ => Err( + ArgumentError::WrongArgument("The given query is not valid.".to_string()).into(), + ), + } + } +} + +/// This structure holds a list of certificates. The entries of the list are +/// Optional to allow for the removal of certificates. The closed list holds +/// the indexes of the removed certificates. +#[derive(Default)] +struct Handles { + certs: HashMap, + next: usize, +} + +#[derive(Default)] +pub struct NaslCerts(RwLock); + +impl NaslCerts { + fn insert(&self, cert: X509Certificate) -> usize { + let mut handle = self.0.write().unwrap(); + let index = handle.next; + handle.certs.insert(index, cert); + handle.next += 1; + handle.next - 1 + } + + /// Create a certificate object. + /// + /// Takes a string/data as unnamed argument and returns an identifier + /// used with the other cert functions. The data is usually the BER + /// encoded certificate but the function will also try a PEM encoding + /// on failure to parse BER encoded one. + /// + /// On success the function returns a cert identifier that can be used + /// for further operations. + #[nasl_function] + fn cert_open(&self, cert: &[u8]) -> Result { + if let Ok(cert) = X509Certificate::from_der(cert) { + return Ok(self.insert(cert)); + } + if let Ok(cert) = X509Certificate::from_pem(cert) { + return Ok(self.insert(cert)); + } + if let Ok(cert) = X509Certificate::from_ber(cert) { + return Ok(self.insert(cert)); + } + + Err(ArgumentError::WrongArgument( + "The given string is not a valid DER, BER or PEM encoded X.509 certificate." + .to_string(), + ) + .into()) + } + + /// Release a certificate object. + /// + /// Takes a cert identifier as returned by cert_open and releases the + /// associated resources. + #[nasl_function] + fn cert_close(&self, cert_handle: usize) { + let mut handle = self.0.write().unwrap(); + handle.certs.remove(&cert_handle); + } + + fn subject(cert: &X509Certificate, idx: usize) -> Option { + // The error originates from the io::Write trait. Internally a Vec is used, which + // implementation of that trait is infallible. Therefore we can unwrap here. + let der = cert.encode_der().unwrap(); + let (_, cert) = x509_parser::parse_x509_certificate(&der).unwrap(); + + if idx == 0 { + Some(cert.subject.to_string()) + } else { + cert.subject_alternative_name() + .ok() + .flatten() + .and_then(|san| san.value.general_names.get(idx - 1)) + .map(|san| Some(san.to_string())) + .unwrap_or(None) + } + } + + fn issuer(cert: &X509Certificate, idx: usize) -> Option { + let subject = cert.issuer_name(); + subject.get(idx).map(|entry| { + entry + .iter() + .filter_map(|val| { + val.value.to_string().ok().map(|value| { + format!("{}={}", subject_oid_to_name(&val.typ.to_string()), value) + }) + }) + .collect::>() + .join(", ") + }) + } + + fn hostnames(cert: &X509Certificate) -> Vec { + let mut ret = vec![]; + if let Some(cn) = cert.subject_common_name() { + ret.push(cn); + } + + let der = cert.encode_der().unwrap(); + let (_, cert) = x509_parser::parse_x509_certificate(&der).unwrap(); + + if let Ok(Some(san)) = cert.subject_alternative_name() { + for name in san.value.general_names.iter() { + if let GeneralName::DNSName(dns) = name { + ret.push(dns.to_string()); + } + } + } + + ret + } + + fn key_size(cert: &X509Certificate) -> Option { + let algorithm = cert.key_algorithm()?; + match algorithm { + x509_certificate::KeyAlgorithm::Rsa => { + if let Ok(data) = cert.rsa_public_key_data() { + return Some(((data.modulus.into_bytes().len() - 1) * 8) as i64); + } + } + _ => { + if let Ok(data) = cert.rsa_public_key_data() { + return Some((data.public_exponent.into_bytes().len() * 8) as i64); + } + } + } + None + } + + /// Query a certificate object. + /// + /// Takes a cert identifier as first unnamed argument and a command + /// string as second argument. That command is used to select specific + /// information from the certificate. For certain commands the named + /// argument @a idx is used as well. Depending on this command the + /// return value may be a number, a string, or an array of strings. + /// Supported commands are: + /// + /// - serial The serial number of the certificate as a hex string. + /// + /// - issuer Returns the issuer. The returned value is a string in + /// rfc-2253 format. + /// + /// - subject Returns the subject. The returned value is a string in + /// rfc-2253 format. To query the subjectAltName the + /// named parameters @a idx with values starting at 1 can + /// be used. In this case the format is either an rfc2253 + /// string as used above, an rfc2822 mailbox name + /// indicated by the first character being a left angle + /// bracket or an S-expression in advanced format for all + /// other types of subjectAltnames which is indicated by + /// an opening parentheses. + /// + /// - not-before The notBefore time as UTC value in ISO time format + /// (e.g. "20120930T143521"). + /// + /// - not-after The notAfter time as UTC value in ISO time format + /// (e.g. "20280929T143520"). + /// + /// - all Return all available information in a human readable + /// format. Not yet implemented. + /// + /// - hostnames Return an array with all hostnames listed in the + /// certificates, i.e. the CN part of the subject and all dns-name + /// type subjectAltNames. + /// + /// - fpr-sha-256 The SHA-256 fingerprint of the certificate. The + /// fingerprint is, as usual, computed over the entire + /// DER encode certificate. + /// + /// - fpr-sha-1 The SHA-1 fingerprint of the certificate. The + /// fingerprint is, as usual, computed over the entire + /// DER encode certificate. + /// + /// - image Return the entire certificate as binary data. + /// + /// - algorithm-name Same as signature-algorithm-name. TODO: Remove it and + /// leave only signature-algorithm-name. + /// + /// - signature-algorithm-name Return the algorithm name used to sign the + /// certificate. Get the OID of the digest + /// algorithm and translated to a name from a + /// list from Wireshark. + /// See epan/dissectors/packet-pkcs1.c + /// + /// - public-key-algorithm-name Return the algorithm name of the public key. + /// + /// - modulus Return the RSA public key's modulus found in the + /// structure of the given cert. + /// + /// - exponent Return the RSA public key's exponent found in + /// the structure of the given cert. + /// + /// - key-size Return the size to hold the parameters size in bits. + /// For RSA the bits returned is the modulus. + /// For DSA the bits returned are of the public exponent. + /// + /// + /// The following arguments are required: + /// - pos(0): Object id of the certificate. + /// + /// - pos(1): A string with the command to select what to return; see above. + /// + /// The following arguments are optional: + /// - idx Used by certain commands to select the n-th value of a set + /// of values. If not given 0 is assumed. + /// + /// A NASL type depending on the used command. + #[nasl_function(named(idx))] + fn cert_query( + &self, + cert_handle: usize, + query: &str, + idx: Option, + ) -> Result { + let idx = idx.unwrap_or(0); + let handle = self.0.read().unwrap(); + + let cert = handle.certs.get(&cert_handle).ok_or_else(|| { + ArgumentError::WrongArgument("The given file descriptor is not valid.".to_string()) + })?; + let result = match CertCommands::try_from(query)? { + CertCommands::Serial => { + let serial = cert.serial_number_asn1().clone().into_bytes(); + NaslValue::String(encode_hex(&serial)) + } + CertCommands::Subject => Self::subject(cert, idx) + .map(NaslValue::String) + .unwrap_or(NaslValue::Null), + CertCommands::Issuer => Self::issuer(cert, idx) + .map(NaslValue::String) + .unwrap_or(NaslValue::Null), + CertCommands::NotBefore => { + let not_before = cert.validity_not_before().format("%Y%m%dT%H%M%S"); + NaslValue::String(not_before.to_string()) + } + CertCommands::NotAfter => { + let not_after = cert.validity_not_after().format("%Y%m%dT%H%M%S"); + NaslValue::String(not_after.to_string()) + } + CertCommands::FprSha256 => cert + .sha256_fingerprint() + .map(|fpr| NaslValue::String(encode_hex(fpr.as_ref()))) + .map_err(|_| CertError::UnableToCalculateSHA256Fingerprint)?, + CertCommands::FprSha1 => cert + .sha1_fingerprint() + .map(|fpr| NaslValue::String(encode_hex(fpr.as_ref()))) + .map_err(|_| CertError::UnableToCalculateSHA1Fingerprint)?, + CertCommands::All => return Err(CertError::QueryParamAllNotImplemented.into()), + CertCommands::Hostnames => NaslValue::Array( + Self::hostnames(cert) + .into_iter() + .map(NaslValue::String) + .collect::>(), + ), + CertCommands::Image => NaslValue::Data(cert.encode_der().unwrap_or_default()), + CertCommands::SignatureAlgorithmName => { + let signature_algorithm_oid = cert.signature_algorithm_oid().to_string(); + let signature_algorithm = sign_alg_oid_to_name(&signature_algorithm_oid); + NaslValue::String(signature_algorithm.to_string()) + } + CertCommands::PublicKeyAlgorithmName => { + let key_algorithm_oid = cert.key_algorithm_oid().to_string(); + let public_key_algorithm = pub_key_alg_oid_to_name(&key_algorithm_oid); + NaslValue::String(public_key_algorithm.to_string()) + } + CertCommands::Modulus => cert + .rsa_public_key_data() + .map(|data| NaslValue::Data(data.modulus.into_bytes().to_vec())) + .unwrap_or(NaslValue::Null), + CertCommands::Exponent => cert + .rsa_public_key_data() + .map(|data| NaslValue::Data(data.public_exponent.into_bytes().to_vec())) + .unwrap_or(NaslValue::Null), + CertCommands::KeySize => Self::key_size(cert) + .map(NaslValue::Number) + .unwrap_or(NaslValue::Null), + }; + Ok(result) + } +} + +function_set! { + NaslCerts, + sync_stateful, + ( + (NaslCerts::cert_open, "cert_open"), + (NaslCerts::cert_close, "cert_close"), + (NaslCerts::cert_query, "cert_query"), + ) +} diff --git a/rust/src/nasl/builtin/cryptographic/bf_cbc.rs b/rust/src/nasl/builtin/cryptographic/bf_cbc.rs index 34103a3ad..1f24a09a7 100644 --- a/rust/src/nasl/builtin/cryptographic/bf_cbc.rs +++ b/rust/src/nasl/builtin/cryptographic/bf_cbc.rs @@ -74,7 +74,6 @@ where /// The return value is an array a with a[0] being the encrypted data and /// a[1] the new initialization vector to use for the next part of the /// data. - fn bf_cbc_encrypt(register: &Register, _: &Context) -> Result { cbc::(register, Crypt::Encrypt) } diff --git a/rust/src/nasl/builtin/error.rs b/rust/src/nasl/builtin/error.rs index 9fd6c52c1..244540ebd 100644 --- a/rust/src/nasl/builtin/error.rs +++ b/rust/src/nasl/builtin/error.rs @@ -3,7 +3,9 @@ use thiserror::Error; use crate::nasl::prelude::*; use crate::nasl::utils::error::FnErrorKind; +use super::cert::CertError; use super::cryptographic::CryptographicError; +use super::host::HostError; use super::http::HttpError; use super::isotime::IsotimeError; use super::knowledge_base::KBError; @@ -30,6 +32,10 @@ pub enum BuiltinError { Isotime(IsotimeError), #[error("{0}")] KB(KBError), + #[error("{0}")] + Host(HostError), + #[error("{0}")] + Cert(CertError), #[cfg(feature = "nasl-builtin-raw-ip")] #[error("{0}")] RawIp(super::raw_ip::RawIpError), @@ -73,6 +79,8 @@ builtin_error_variant!(HttpError, Http); builtin_error_variant!(IsotimeError, Isotime); builtin_error_variant!(RegexError, Regex); builtin_error_variant!(KBError, KB); +builtin_error_variant!(HostError, Host); +builtin_error_variant!(CertError, Cert); #[cfg(feature = "nasl-builtin-raw-ip")] builtin_error_variant!(super::raw_ip::RawIpError, RawIp); diff --git a/rust/src/nasl/builtin/host/README.md b/rust/src/nasl/builtin/host/README.md index 1bb78031a..a28c3c343 100644 --- a/rust/src/nasl/builtin/host/README.md +++ b/rust/src/nasl/builtin/host/README.md @@ -1,10 +1,5 @@ ## Implements -- get_host_name -- get_host_names - -## Missing - - TARGET_IS_IPV6 - add_host_name - get_host_name diff --git a/rust/src/nasl/builtin/host/mod.rs b/rust/src/nasl/builtin/host/mod.rs index 92b4f4559..a5d7fcce0 100644 --- a/rust/src/nasl/builtin/host/mod.rs +++ b/rust/src/nasl/builtin/host/mod.rs @@ -5,52 +5,53 @@ #[cfg(test)] mod tests; -use std::{net::IpAddr, str::FromStr}; +use std::{ + net::{IpAddr, Ipv6Addr}, + str::FromStr, +}; -use crate::function_set; -use crate::nasl::utils::{error::FnError, lookup_keys::TARGET}; +use dns_lookup::lookup_addr; +use thiserror::Error; -use crate::nasl::syntax::NaslValue; -use crate::nasl::utils::{Context, ContextType, Register}; +use crate::nasl::prelude::*; +use crate::nasl::utils::hosts::resolve; -/// Resolves IP address of target to hostname -/// -/// It does lookup TARGET and when not found falls back to 127.0.0.1 to resolve. -/// If the TARGET is not a IP address than we assume that it already is a fqdn or a hostname and will return that instead. -fn resolve_hostname(register: &Register) -> Result { - use std::net::ToSocketAddrs; - - let default_ip = "127.0.0.1"; - // currently we use shadow variables as _FC_ANON_ARGS; the original openvas uses redis for that purpose. - let target = register.named(TARGET).map_or_else( - || default_ip.to_owned(), - |x| match x { - ContextType::Value(NaslValue::String(x)) => x.clone(), - _ => default_ip.to_owned(), - }, - ); - - match target.to_socket_addrs() { - Ok(mut addr) => Ok(addr.next().map_or_else(String::new, |x| x.to_string())), - // assumes that target is already a hostname - Err(_) => Ok(target), - } +#[derive(Debug, Error)] +pub enum HostError { + #[error("Empty hostname.")] + EmptyHostname, + #[error("Empty address.")] + EmptyAddress, + #[error("Target is not a hostname.")] + TargetIsNotAHostname, } -/// NASL function to get all stored vhosts -/// -/// As of now (2023-01-20) there is no vhost handling. -/// Therefore this function does load the registered TARGET and if it is an IP Address resolves it via DNS instead. -fn get_host_names(register: &Register, _: &Context) -> Result { - resolve_hostname(register).map(|x| NaslValue::Array(vec![NaslValue::String(x)])) +struct Hostname(String); +impl<'a> FromNaslValue<'a> for Hostname { + fn from_nasl_value(value: &'a NaslValue) -> Result { + let str = String::from_nasl_value(value)?; + if str.is_empty() { + Err(HostError::EmptyHostname.into()) + } else { + Ok(Self(str)) + } + } } -/// NASL function to get the current hostname -/// -/// As of now (2023-01-20) there is no vhost handling. -/// Therefore this function does load the registered TARGET and if it is an IP Address resolves it via DNS instead. -fn get_host_name(register: &Register, _: &Context) -> Result { - resolve_hostname(register).map(NaslValue::String) +/// Get a list of found hostnames or a IP of the current target in case no hostnames were found yet. +#[nasl_function] +fn get_host_names(context: &Context) -> Result { + let hns = context.target_vhosts(); + if !hns.is_empty() { + let hns = hns + .into_iter() + .map(|(h, _s)| NaslValue::String(h)) + .collect::>(); + return Ok(NaslValue::Array(hns)); + }; + Ok(NaslValue::Array(vec![NaslValue::String( + context.target().to_string(), + )])) } /// Return the target's IP address as IpAddr. @@ -70,20 +71,139 @@ pub fn get_host_ip(context: &Context) -> Result { } } +///Expands the vHosts list with the given hostname. +///The mandatory parameter hostname is of type string. It contains the hostname which should be added to the list of vHosts +///Additionally a source, how the hostname was detected can be added with the named argument source as a string. If it is not given, the value NASL is set as default. +#[nasl_function(named(hostname, source))] +pub fn add_host_name( + context: &Context, + hostname: Hostname, + source: Option<&str>, +) -> Result { + let source = source.filter(|x| !x.is_empty()).unwrap_or("NASL"); + context.add_hostname(hostname.0, source.into()); + Ok(NaslValue::Null) +} + +/// Get the host name of the currently scanned target. If there is no host name available, the IP of the target is returned instead. +pub fn get_host_name(_register: &Register, context: &Context) -> Result { + let vh = context.target_vhosts(); + let v = if !vh.is_empty() { + vh.iter() + .map(|(v, _s)| NaslValue::String(v.to_string())) + .collect::>() + } else { + vec![] + }; + + //TODO: store the current hostname being forked. + //TODO: don't fork if expand_vhost is disabled. + //TODO: don't fork if already in a vhost + if !v.is_empty() { + return Ok(NaslValue::Fork(v)); + } + + let host = match get_host_ip(context) { + Ok(ip) => match lookup_addr(&ip) { + Ok(host) => host, + Err(_) => ip.to_string(), + }, + Err(_) => context.target().to_string(), + }; + Ok(NaslValue::String(host)) +} + +/// This function returns the source of detection of a given hostname. +/// The named parameter hostname is a string containing the hostname. +/// When no hostname is given, the current scanned host is taken. +/// If no virtual hosts are found yet this function always returns IP-address. +#[nasl_function(named(hostname))] +pub fn get_host_name_source(context: &Context, hostname: Hostname) -> String { + let vh = context.target_vhosts(); + if !vh.is_empty() { + if let Some((_, source)) = vh.into_iter().find(|(v, _)| v == &hostname.0) { + return source; + }; + } + context.target().to_string() +} + /// Return the target's IP address or 127.0.0.1 if not set. fn nasl_get_host_ip(_register: &Register, context: &Context) -> Result { let ip = get_host_ip(context)?; Ok(NaslValue::String(ip.to_string())) } +/// Get an IP address corresponding to the host name +#[nasl_function(named(hostname))] +fn resolve_host_name(hostname: Hostname) -> String { + resolve(hostname.0).map_or_else( + |_| "127.0.0.1".to_string(), + |x| x.first().map_or("127.0.0.1".to_string(), |v| v.to_string()), + ) +} + +/// Resolve a hostname to all found addresses and return them in an NaslValue::Array +#[nasl_function(named(hostname))] +fn resolve_hostname_to_multiple_ips(hostname: Hostname) -> Result { + let ips = resolve(hostname.0)? + .into_iter() + .map(|x| NaslValue::String(x.to_string())) + .collect(); + Ok(NaslValue::Array(ips)) +} + +/// Check if the currently scanned target is an IPv6 address. +/// Return TRUE if the current target is an IPv6 address, else FALSE. In case of an error, NULL is returned. +#[nasl_function] +fn target_is_ipv6(context: &Context) -> Result { + let target = match context.target().is_empty() { + true => { + return Err(HostError::EmptyAddress.into()); + } + false => context.target(), + }; + Ok(target.parse::().is_ok()) +} + +/// Compare if two hosts are the same. +/// The first two unnamed arguments are string containing the host to compare +/// If the named argument cmp_hostname is set to TRUE, the given hosts are resolved into their hostnames +#[nasl_function(named(cmp_hostname))] +fn same_host(h1: &str, h2: &str, cmp_hostname: Option) -> Result { + let h1 = resolve(h1.to_string())?; + let h2 = resolve(h2.to_string())?; + + let hostnames1 = h1 + .iter() + .filter_map(|x| lookup_addr(x).ok()) + .collect::>(); + let hostnames2 = h2 + .iter() + .filter_map(|x| lookup_addr(x).ok()) + .collect::>(); + + let any_ip_address_matches = h1.iter().any(|a1| h2.contains(a1)); + let any_hostname_matches = hostnames1.iter().any(|h1| hostnames2.contains(h1)); + let cmp_hostname = cmp_hostname.filter(|x| *x).unwrap_or(false); + + Ok(any_ip_address_matches || (cmp_hostname && any_hostname_matches)) +} + pub struct Host; function_set! { Host, sync_stateless, ( - get_host_name, get_host_names, - (nasl_get_host_ip, "get_host_ip") + (nasl_get_host_ip, "get_host_ip"), + resolve_host_name, + resolve_hostname_to_multiple_ips, + (target_is_ipv6, "TARGET_IS_IPV6"), + same_host, + add_host_name, + get_host_name, + get_host_name_source ) } diff --git a/rust/src/nasl/builtin/mod.rs b/rust/src/nasl/builtin/mod.rs index ae15314eb..2cdc9b05d 100644 --- a/rust/src/nasl/builtin/mod.rs +++ b/rust/src/nasl/builtin/mod.rs @@ -5,6 +5,7 @@ #![doc = include_str!("README.md")] mod array; +mod cert; mod cryptographic; mod description; mod error; @@ -25,11 +26,14 @@ mod string; mod tests; pub use error::BuiltinError; +pub use host::HostError; use crate::nasl::syntax::{Loader, NoOpLoader}; use crate::nasl::utils::{Context, Executor, NaslVarRegister, NaslVarRegisterBuilder, Register}; use crate::storage::{ContextKey, DefaultDispatcher, Storage}; +use super::utils::context::Target; + /// Creates a new Executor and adds all the functions to it. /// /// When you have a function that is considered experimental due to either dependencies on @@ -53,7 +57,8 @@ pub fn nasl_std_functions() -> Executor { .add_set(description::Description) .add_set(isotime::NaslIsotime) .add_set(cryptographic::rc4::CipherHandlers::default()) - .add_set(ssh::Ssh::default()); + .add_set(ssh::Ssh::default()) + .add_set(cert::NaslCerts::default()); #[cfg(feature = "nasl-builtin-raw-ip")] executor.add_set(raw_ip::RawIp); @@ -136,11 +141,12 @@ where /// Creates a new Context with the shared loader, logger and function register pub fn build(&self, key: ContextKey) -> Context { - let target = match &key { + let mut target = Target::default(); + target.set_target(match &key { ContextKey::Scan(_, Some(target)) => target.clone(), ContextKey::Scan(_, None) => String::default(), ContextKey::FileName(target) => target.clone(), - }; + }); Context::new( key, target, diff --git a/rust/src/nasl/builtin/raw_ip/packet_forgery.rs b/rust/src/nasl/builtin/raw_ip/packet_forgery.rs index 2a58b31ec..f1bcf043c 100644 --- a/rust/src/nasl/builtin/raw_ip/packet_forgery.rs +++ b/rust/src/nasl/builtin/raw_ip/packet_forgery.rs @@ -2175,7 +2175,7 @@ fn nasl_send_capture(register: &Register, configs: &Context) -> Result Ok(NaslValue::Null), } diff --git a/rust/src/nasl/builtin/ssh/mod.rs b/rust/src/nasl/builtin/ssh/mod.rs index c3a8f14bc..d5bdfe79c 100644 --- a/rust/src/nasl/builtin/ssh/mod.rs +++ b/rust/src/nasl/builtin/ssh/mod.rs @@ -153,9 +153,7 @@ impl Ssh { let port = port .filter(|_| socket.is_none()) .unwrap_or(DEFAULT_SSH_PORT); - let ip = ctx.target_ip().map_err(|e| { - SshError::from(SshErrorKind::InvalidIpAddr(ctx.target().to_string(), e)) - })?; + let ip = ctx.target_ip(); let timeout = timeout.map(Duration::from_secs); let keytype = keytype .map(|keytype| keytype.0) @@ -252,7 +250,7 @@ impl Ssh { /// If the private key is protected, its passphrase is taken from the /// named argument "passphrase" or, if not given, taken from the KB /// ("Secret/SSH/passphrase"). - + /// /// Note that the named argument "publickey" and the KB item /// ("Secret/SSH/publickey") are ignored - they are not longer required /// because they can be derived from the private key. diff --git a/rust/src/nasl/interpreter/assign.rs b/rust/src/nasl/interpreter/assign.rs index d5e6a7f9c..7719ef313 100644 --- a/rust/src/nasl/interpreter/assign.rs +++ b/rust/src/nasl/interpreter/assign.rs @@ -39,7 +39,7 @@ fn prepare_dict(left: NaslValue) -> HashMap { } } -impl<'a> Interpreter<'a> { +impl Interpreter<'_> { fn save(&mut self, idx: usize, key: &str, value: NaslValue) { self.register_mut() .add_to_index(idx, key, ContextType::Value(value)); diff --git a/rust/src/nasl/interpreter/call.rs b/rust/src/nasl/interpreter/call.rs index fa14acc6e..959c5ed9e 100644 --- a/rust/src/nasl/interpreter/call.rs +++ b/rust/src/nasl/interpreter/call.rs @@ -48,41 +48,52 @@ impl<'a> Interpreter<'a> { ); self.register_mut().create_root_child(named); let result = match self.ctxconfigs.nasl_fn_execute(name, self.register()).await { - Some(r) => { - if let Ok(NaslValue::Fork(mut x)) = r { - Ok(if let Some(r) = x.pop() { - // this is a proposal for the case that the caller is immediately executing - // if not the position needs to be reset - if self.index == 0 { - let position = self.position().current_init_statement(); - for i in x { - tracing::trace!(return_value=?i, return_position=?self.position(), interpreter_position=?position, "creating interpreter instance" ); - self.run_specific.push(RunSpecific { - register: self.register().clone(), - position: position.clone(), - skip_until_return: Some((self.position().clone(), i)), - }); - } + Some(Ok(NaslValue::Fork(x))) if self.index == 0 && !x.is_empty() => { + let mut additional = Vec::with_capacity(x.len() - 1); + let root_pos = self.run_specific[0].position.clone(); + + for (vi, v) in x.iter().enumerate() { + for (rsi, rs) in self.run_specific.iter_mut().enumerate() { + let mut pos = root_pos.clone(); + // needs to be reduced because a previous down statement enhanced the number + pos.reduce_last(); + + if vi == 0 { + rs.skip_until_return.push((pos, v.clone())); } else { - tracing::trace!( - index = self.index, - "we only allow expanding of executions (fork) on root instance" - ); + let position = pos.current_init_statement(); + let mut skip_until_return = rs + .skip_until_return + .iter() + .filter(|(p, _)| p != &pos) + .cloned() + .collect::>(); + skip_until_return.push((pos.clone(), v.clone())); + tracing::trace!(run_specific_index=rsi, value_index=vi, value=?v, ?pos, ?skip_until_return, ?rs.skip_until_return, "new fork"); + + additional.push(RunSpecific { + register: rs.register.clone(), + position: position.clone(), + skip_until_return, + }); } - tracing::trace!(return_value=?r, "returning interpreter instance" ); - r - } else { - NaslValue::Null - }) - } else { - r.map_err(|x| { - InterpretError::new( - InterpretErrorKind::FunctionCallError(FunctionCallError::new(name, x)), - Some(statement.clone()), - ) - }) + } } + self.run_specific.extend(additional); + Ok(x[0].clone()) + } + + Some(Ok(NaslValue::Fork(x))) if self.index == 0 && x.is_empty() => Ok(NaslValue::Null), + + Some(Ok(NaslValue::Fork(_))) => { + unreachable!("NaslValue::Fork must only occur on root instance, all other cases should return a value within run_specific") } + Some(r) => r.map_err(|e| { + InterpretError::new( + InterpretErrorKind::FunctionCallError(FunctionCallError::new(name, e)), + Some(statement.clone()), + ) + }), None => { let found = self .register() @@ -129,4 +140,81 @@ mod tests { t.ok("test(a: 1);", 1); t.ok("test();", 0); } + + #[test] + #[tracing_test::traced_test] + fn multiple_forks() { + let mut t = TestBuilder::default(); + t.run_all( + r#" +set_kb_item(name: "port", value: 1); +set_kb_item(name: "port", value: 2); +set_kb_item(name: "host", value: "a"); +set_kb_item(name: "host", value: "b"); +get_kb_item("port"); +get_kb_item("host"); +"#, + ); + + assert_eq!(t.results().len(), 10); + let results: Vec<_> = t + .results() + .into_iter() + .skip(4) + .filter_map(|x| x.ok()) + .collect(); + + assert_eq!( + results, + vec![ + 1.into(), + 2.into(), + "a".into(), + "a".into(), + "b".into(), + "b".into(), + ] + ); + } + #[test] + #[tracing_test::traced_test] + fn empty_fork() { + let mut t = TestBuilder::default(); + t.run_all( + r#" +get_kb_item("port") + ":" + get_kb_item("host"); +"#, + ); + + let results: Vec<_> = t.results().into_iter().filter_map(|x| x.ok()).collect(); + + assert_eq!(results, vec!["\0:\0".into()]); + } + + #[test] + #[tracing_test::traced_test] + fn multiple_forks_on_one_line() { + let mut t = TestBuilder::default(); + t.run_all( + r#" +set_kb_item(name: "port", value: 1); +set_kb_item(name: "port", value: 2); +set_kb_item(name: "host", value: "a"); +set_kb_item(name: "host", value: "b"); +get_kb_item("port") + ":" + get_kb_item("host"); +"#, + ); + + let results: Vec<_> = t + .results() + .into_iter() + .skip(4) + .filter_map(|x| x.ok()) + .collect(); + + assert_eq!( + results, + vec!["1:a".into(), "2:a".into(), "1:b".into(), "2:b".into(),] + ); + } } diff --git a/rust/src/nasl/interpreter/declare.rs b/rust/src/nasl/interpreter/declare.rs index 531283208..5db59dd9f 100644 --- a/rust/src/nasl/interpreter/declare.rs +++ b/rust/src/nasl/interpreter/declare.rs @@ -18,7 +18,7 @@ pub(crate) trait DeclareFunctionExtension { ) -> InterpretResult; } -impl<'a> DeclareFunctionExtension for Interpreter<'a> { +impl DeclareFunctionExtension for Interpreter<'_> { fn declare_function( &mut self, name: &Token, @@ -46,7 +46,7 @@ pub(crate) trait DeclareVariableExtension { fn declare_variable(&mut self, scope: &Token, stmts: &[Statement]) -> InterpretResult; } -impl<'a> DeclareVariableExtension for Interpreter<'a> { +impl DeclareVariableExtension for Interpreter<'_> { fn declare_variable(&mut self, scope: &Token, stmts: &[Statement]) -> InterpretResult { let mut add = |key: &str| { let value = ContextType::Value(NaslValue::Null); diff --git a/rust/src/nasl/interpreter/interpreter.rs b/rust/src/nasl/interpreter/interpreter.rs index d1309fa90..0700ed688 100644 --- a/rust/src/nasl/interpreter/interpreter.rs +++ b/rust/src/nasl/interpreter/interpreter.rs @@ -22,6 +22,20 @@ pub(crate) struct Position { index: Vec, } +impl std::fmt::Display for Position { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + self.index + .iter() + .map(|x| x.to_string()) + .collect::>() + .join(".") + ) + } +} + impl Position { pub fn new(index: usize) -> Self { Self { index: vec![index] } @@ -31,8 +45,20 @@ impl Position { self.index.push(0); } + pub fn reduce_last(&mut self) { + if let Some(last) = self.index.last_mut() { + if *last > 0 { + *last -= 1; + } + } + } + pub fn down(&mut self) -> Option { - self.index.pop() + let result = self.index.pop(); + if let Some(last) = self.index.last_mut() { + *last += 1; + } + result } pub fn current_init_statement(&self) -> Self { @@ -54,7 +80,7 @@ impl Position { pub(crate) struct RunSpecific { pub(crate) register: Register, pub(crate) position: Position, - pub(crate) skip_until_return: Option<(Position, NaslValue)>, + pub(crate) skip_until_return: Vec<(Position, NaslValue)>, } /// Used to interpret a Statement @@ -75,7 +101,7 @@ impl<'a> Interpreter<'a> { let root_run = RunSpecific { register, position: Position::new(0), - skip_until_return: None, + skip_until_return: Vec::new(), }; Interpreter { run_specific: vec![root_run], @@ -162,9 +188,6 @@ impl<'a> Interpreter<'a> { stmt: &Statement, max_attempts: usize, ) -> InterpretResult { - if let Some(last) = self.position_mut().index.last_mut() { - *last += 1; - } self.index = 0; self.retry_resolve(stmt, max_attempts).await } @@ -200,28 +223,37 @@ impl<'a> Interpreter<'a> { } } - /// Interprets a Statement - pub(crate) async fn resolve(&mut self, statement: &Statement) -> InterpretResult { - self.position_mut().up(); - tracing::trace!(position=?self.position(), statement=statement.to_string(), "executing"); - // On a fork statement run we skip until the root index is reached. Between the root index - // of the return value and the position of the return value the interpretation is - // continued. This is done because the client just executes higher statements. - if let Some((cp, rv)) = &self.skip_until_return() { - tracing::trace!(check_position=?cp); + //// Checks for skip_until_return and returns the value if the current position is in the list + /// if the root index is smaller than the current position it will return None this is done to + /// prevent unnecessary statement execution and has to be seen as guardian functionality. + fn may_return_value(&mut self) -> Option { + for (cp, value) in self.skip_until_return().iter() { if self.position().root_index() < cp.root_index() { - tracing::trace!("skip execution"); - self.position_mut().down(); - return Ok(NaslValue::Null); + return Some(NaslValue::Null); } if cp == self.position() { - tracing::trace!(return=?rv, "skip execution and returning"); - let rv = rv.clone(); - self.set_skip_until_return(None); - self.position_mut().down(); - return Ok(rv); + return Some(value.clone()); } } + None + } + + /// Interprets a Statement + pub(crate) async fn resolve(&mut self, statement: &Statement) -> InterpretResult { + self.position_mut().up(); + let span = tracing::span!(tracing::Level::WARN, "resolve", + statement=statement.to_string(), + index=self.index, + position=%self.position(), + run_specific_len=self.run_specific.len(), + skipped_value_pos=?self.skip_until_return(), ); + let _enter = span.enter(); + if let Some(val) = self.may_return_value() { + tracing::trace!(returns=?val, "skipped" ); + self.position_mut().down(); + return Ok(val); + } + tracing::trace!("executing"); let results = { match statement.kind() { @@ -331,13 +363,8 @@ impl<'a> Interpreter<'a> { rs.register = val; } - pub(crate) fn set_skip_until_return(&mut self, val: Option<(Position, NaslValue)>) { - let rs = &mut self.run_specific[self.index]; - rs.skip_until_return = val; - } - - pub(crate) fn skip_until_return(&self) -> Option<&(Position, NaslValue)> { - self.run_specific[self.index].skip_until_return.as_ref() + pub(crate) fn skip_until_return(&self) -> &[(Position, NaslValue)] { + &self.run_specific[self.index].skip_until_return } async fn resolve_exit(&mut self, statement: &Statement) -> Result { diff --git a/rust/src/nasl/syntax/grouping_extension.rs b/rust/src/nasl/syntax/grouping_extension.rs index 262909d85..6b9f1a05b 100644 --- a/rust/src/nasl/syntax/grouping_extension.rs +++ b/rust/src/nasl/syntax/grouping_extension.rs @@ -20,7 +20,7 @@ pub(crate) trait Grouping { fn parse_grouping(&mut self, token: Token) -> Result<(End, Statement), SyntaxError>; } -impl<'a> Grouping for Lexer<'a> { +impl Grouping for Lexer<'_> { fn parse_paren(&mut self, token: Token) -> Result { let (end, right) = self.statement(0, &|cat| cat == &Category::RightParen)?; diff --git a/rust/src/nasl/syntax/keyword_extension.rs b/rust/src/nasl/syntax/keyword_extension.rs index fbbba41e5..dc77f742c 100644 --- a/rust/src/nasl/syntax/keyword_extension.rs +++ b/rust/src/nasl/syntax/keyword_extension.rs @@ -22,7 +22,7 @@ pub(crate) trait Keywords { ) -> Result<(End, Statement), SyntaxError>; } -impl<'a> Lexer<'a> { +impl Lexer<'_> { fn parse_declaration(&mut self, token: Token) -> Result { let (end, params) = self.parse_comma_group(Category::Semicolon)?; match end { @@ -425,7 +425,7 @@ impl<'a> Lexer<'a> { } } -impl<'a> Keywords for Lexer<'a> { +impl Keywords for Lexer<'_> { fn parse_keyword( &mut self, keyword: IdentifierType, diff --git a/rust/src/nasl/syntax/lexer.rs b/rust/src/nasl/syntax/lexer.rs index 1a8dd6d24..21540f39e 100644 --- a/rust/src/nasl/syntax/lexer.rs +++ b/rust/src/nasl/syntax/lexer.rs @@ -348,7 +348,7 @@ impl<'a> Lexer<'a> { } } -impl<'a> Iterator for Lexer<'a> { +impl Iterator for Lexer<'_> { type Item = Result; fn next(&mut self) -> Option { diff --git a/rust/src/nasl/syntax/prefix_extension.rs b/rust/src/nasl/syntax/prefix_extension.rs index d994be4be..83e9f20c6 100644 --- a/rust/src/nasl/syntax/prefix_extension.rs +++ b/rust/src/nasl/syntax/prefix_extension.rs @@ -34,7 +34,7 @@ fn prefix_binding_power(token: &Token) -> Result { } } -impl<'a> Lexer<'a> { +impl Lexer<'_> { fn parse_variable(&mut self, token: Token) -> Result<(End, Statement), SyntaxError> { if !matches!( token.category(), @@ -110,7 +110,7 @@ impl<'a> Lexer<'a> { } } -impl<'a> Prefix for Lexer<'a> { +impl Prefix for Lexer<'_> { fn prefix_statement( &mut self, token: Token, diff --git a/rust/src/nasl/syntax/token.rs b/rust/src/nasl/syntax/token.rs index e5a546b5d..09c5c3bf5 100644 --- a/rust/src/nasl/syntax/token.rs +++ b/rust/src/nasl/syntax/token.rs @@ -743,7 +743,7 @@ macro_rules! two_symbol_token { }; } -impl<'a> Iterator for Tokenizer<'a> { +impl Iterator for Tokenizer<'_> { type Item = Token; fn next(&mut self) -> Option { diff --git a/rust/src/nasl/utils/context.rs b/rust/src/nasl/utils/context.rs index 2c1457bb4..f7b3391ab 100644 --- a/rust/src/nasl/utils/context.rs +++ b/rust/src/nasl/utils/context.rs @@ -7,10 +7,11 @@ use crate::nasl::syntax::{Loader, NaslValue, Statement}; use crate::storage::{ContextKey, Dispatcher, Retriever}; +use super::hosts::resolve; use super::{executor::Executor, lookup_keys::FC_ANON_ARGS}; /// Contexts are responsible to locate, add and delete everything that is declared within a NASL plugin - +/// /// Represents a Value within the NaslContext #[derive(Clone, Debug, PartialEq, Eq)] pub enum ContextType { @@ -170,7 +171,7 @@ impl Register { } /// Finds a named ContextType - pub fn named<'a>(&'a self, name: &'a str) -> Option<&ContextType> { + pub fn named<'a>(&'a self, name: &'a str) -> Option<&'a ContextType> { self.blocks .last() .and_then(|x| x.named(self, name)) @@ -178,7 +179,7 @@ impl Register { } /// Finds a named ContextType with index - pub fn index_named<'a>(&'a self, name: &'a str) -> Option<(usize, &ContextType)> { + pub fn index_named<'a>(&'a self, name: &'a str) -> Option<(usize, &'a ContextType)> { self.blocks.last().and_then(|x| x.named(self, name)) } @@ -289,7 +290,10 @@ impl Default for Register { } } use std::collections::HashMap; -use std::net::{AddrParseError, IpAddr}; +use std::net::IpAddr; +use std::str::FromStr; +use std::sync::Mutex; + type Named = HashMap; /// NaslContext is a struct to contain variables and if root declared functions @@ -317,7 +321,7 @@ impl NaslContext { &'a self, registrat: &'a Register, name: &'a str, - ) -> Option<(usize, &ContextType)> { + ) -> Option<(usize, &'a ContextType)> { // first check local match self.defined.get(name) { Some(ctx) => Some((self.id, ctx)), @@ -329,6 +333,56 @@ impl NaslContext { } } +#[derive(Debug)] +pub struct Target { + /// The original target. IP or hostname + target: String, + /// The IP address. Always has a valid IP. It defaults to 127.0.0.1 if not possible to resolve target. + ip_addr: IpAddr, + // The shared state is guarded by a mutex. This is a `std::sync::Mutex` and + // not a Tokio mutex. This is because there are no asynchronous operations + // being performed while holding the mutex. Additionally, the critical + // sections are very small. + // + // A Tokio mutex is mostly intended to be used when locks need to be held + // across `.await` yield points. All other cases are **usually** best + // served by a std mutex. If the critical section does not include any + // async operations but is long (CPU intensive or performing blocking + // operations), then the entire operation, including waiting for the mutex, + // is considered a "blocking" operation and `tokio::task::spawn_blocking` + // should be used. + /// vhost list which resolve to the IP address and their sources. + vhosts: Mutex>, +} + +impl Target { + pub fn set_target(&mut self, target: String) -> &Target { + // Target can be an ip address or a hostname + self.target = target; + + // Store the IpAddr if possible, else default to localhost + self.ip_addr = match resolve(self.target.clone()) { + Ok(a) => *a.first().unwrap_or(&IpAddr::from_str("127.0.0.1").unwrap()), + Err(_) => IpAddr::from_str("127.0.0.1").unwrap(), + }; + self + } + + pub fn add_hostname(&self, hostname: String, source: String) -> &Target { + self.vhosts.lock().unwrap().push((hostname, source)); + self + } +} + +impl Default for Target { + fn default() -> Self { + Self { + target: String::new(), + ip_addr: IpAddr::from_str("127.0.0.1").unwrap(), + vhosts: Mutex::new(vec![]), + } + } +} /// Configurations /// /// This struct includes all objects that a nasl function requires. @@ -337,7 +391,7 @@ pub struct Context<'a> { /// key for this context. A file name or a scan id key: ContextKey, /// target to run a scan against - target: String, + target: Target, /// Default Dispatcher dispatcher: &'a dyn Dispatcher, /// Default Retriever @@ -352,7 +406,7 @@ impl<'a> Context<'a> { /// Creates an empty configuration pub fn new( key: ContextKey, - target: String, + target: Target, dispatcher: &'a dyn Dispatcher, retriever: &'a dyn Retriever, loader: &'a dyn Loader, @@ -394,18 +448,27 @@ impl<'a> Context<'a> { &self.key } - /// Get the target host + /// Get the target IP as string pub fn target(&self) -> &str { - &self.target + &self.target.target } - /// Get the target host - pub fn target_ip(&self) -> Result { - match self.target() { - x if !x.is_empty() => x.to_string(), - _ => "127.0.0.1".to_string(), - } - .parse() + /// Get the target host as IpAddr enum member + pub fn target_ip(&self) -> IpAddr { + self.target.ip_addr + } + + /// Get the target VHost list + pub fn target_vhosts(&self) -> Vec<(String, String)> { + self.target.vhosts.lock().unwrap().clone() + } + + pub fn set_target(&mut self, target: String) { + self.target.target = target; + } + + pub fn add_hostname(&self, hostname: String, source: String) { + self.target.add_hostname(hostname, source); } /// Get the storage diff --git a/rust/src/nasl/utils/function/from_nasl_value.rs b/rust/src/nasl/utils/function/from_nasl_value.rs index efb37312b..d8c1cdb9b 100644 --- a/rust/src/nasl/utils/function/from_nasl_value.rs +++ b/rust/src/nasl/utils/function/from_nasl_value.rs @@ -21,7 +21,7 @@ impl<'a> FromNaslValue<'a> for &'a NaslValue { } } -impl<'a> FromNaslValue<'a> for String { +impl FromNaslValue<'_> for String { fn from_nasl_value(value: &NaslValue) -> Result { match value { NaslValue::String(string) => Ok(string.to_string()), diff --git a/rust/src/nasl/utils/hosts.rs b/rust/src/nasl/utils/hosts.rs new file mode 100644 index 000000000..94102dd78 --- /dev/null +++ b/rust/src/nasl/utils/hosts.rs @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2024 Greenbone AG +// +// SPDX-License-Identifier: GPL-2.0-or-later WITH x11vnc-openssl-exception + +use std::net::{IpAddr, ToSocketAddrs}; + +use crate::nasl::builtin::HostError; + +use super::FnError; + +pub fn resolve(mut hostname: String) -> Result, FnError> { + //std::net to_socket_addrs() requires a port. Therefore, using a dummy port + hostname.push_str(":5000"); + + match hostname.to_socket_addrs() { + Ok(addr) => { + let ips = addr.into_iter().map(|x| x.ip()).collect::>(); + Ok(ips) + } + // assumes that target is already a hostname + Err(_) => Err(HostError::TargetIsNotAHostname.into()), + } +} diff --git a/rust/src/nasl/utils/mod.rs b/rust/src/nasl/utils/mod.rs index 77d690815..544cc4e77 100644 --- a/rust/src/nasl/utils/mod.rs +++ b/rust/src/nasl/utils/mod.rs @@ -7,6 +7,7 @@ pub mod context; pub mod error; mod executor; pub mod function; +pub mod hosts; pub mod lookup_keys; use std::collections::HashMap; diff --git a/rust/src/notus/packages/deb.rs b/rust/src/notus/packages/deb.rs index 6044433ce..8318737f7 100644 --- a/rust/src/notus/packages/deb.rs +++ b/rust/src/notus/packages/deb.rs @@ -6,21 +6,23 @@ use super::{Package, PackageVersion}; use lazy_regex::{lazy_regex, Lazy, Regex}; use std::cmp::Ordering; +/// Used for parsing the full name of a deb package static RE: Lazy = lazy_regex!( r"^([a-z0-9](?:[a-z0-9+\-.])*)-(?:(\d*):)?(\d[[:alnum:]+\-.~]*)(?:-([[:alnum:]+\-.~]*))$" ); +/// Used for parsing the full name of a deb package without revision static RE_WO_REVISION: Lazy = lazy_regex!(r"^([a-z0-9](?:[a-z0-9+\-.])*)-(?:(\d*):)?(\d[[:alnum:]+\-.~]*)$"); +/// Used for parsing the full version of a deb package static RE_VERSION: Lazy = lazy_regex!(r"^(?:(\d*):)?(\d[[:alnum:]+\-.~]*)(?:-([[:alnum:]+\-.~]*))$"); +/// Used for parsing the full version of a deb package without revision static RE_VERSION_WO_REVISION: Lazy = lazy_regex!(r"^(?:(\d*):)?(\d[[:alnum:]+\-.~]*)$"); /// Represent a based Redhat package #[derive(Debug, PartialEq, Clone)] pub struct Deb { name: String, - full_name: String, - full_version: String, epoch: u64, upstream_version: PackageVersion, debian_revision: PackageVersion, @@ -32,10 +34,6 @@ impl PartialOrd for Deb { return None; } - if self.full_version == other.full_version { - return Some(Ordering::Equal); - } - if self.epoch != other.epoch { return match self.epoch > other.epoch { true => Some(Ordering::Greater), @@ -103,8 +101,6 @@ impl Package for Deb { Some(Deb { name: name.to_string(), - full_name: full_name.to_string(), - full_version, epoch, upstream_version: PackageVersion(upstream_version.to_string()), debian_revision: PackageVersion(debian_revision.to_string()), @@ -148,8 +144,6 @@ impl Package for Deb { Some(Deb { name: name.to_string(), - full_name: full_name.to_string(), - full_version: full_version.to_string(), epoch, upstream_version: PackageVersion(upstream_version.to_string()), debian_revision: PackageVersion(debian_revision.to_string()), @@ -161,7 +155,17 @@ impl Package for Deb { } fn get_version(&self) -> String { - self.full_version.clone() + let mut ret = "".to_string(); + if self.epoch > 0 { + ret = self.epoch.to_string(); + ret.push(':'); + } + ret.push_str(&self.upstream_version.0); + if !self.debian_revision.0.is_empty() { + ret.push('-'); + ret.push_str(&self.debian_revision.0); + } + ret } } @@ -178,16 +182,12 @@ mod deb_tests { epoch: 1, upstream_version: PackageVersion("1.2.3".to_string()), debian_revision: PackageVersion("4".to_string()), - full_name: "foo-bar-1:1.2.3-4".to_string(), - full_version: "1:1.2.3-4".to_string(), }; let package2 = Deb { name: "foo-bar".to_string(), epoch: 1, upstream_version: PackageVersion("1.2.4".to_string()), debian_revision: PackageVersion("4".to_string()), - full_name: "foo-bar-1:1.2.4-4".to_string(), - full_version: "1:1.2.4-4".to_string(), }; assert!(package2 > package1); @@ -196,8 +196,6 @@ mod deb_tests { epoch: 1, upstream_version: PackageVersion("1.2.3".to_string()), debian_revision: PackageVersion("5".to_string()), - full_name: "foo-bar-1:1.2.3-5".to_string(), - full_version: "1:1.2.3-5".to_string(), }; assert!(package2 > package1); } @@ -209,16 +207,12 @@ mod deb_tests { epoch: 1, upstream_version: PackageVersion("1.2.3".to_string()), debian_revision: PackageVersion("4".to_string()), - full_name: "foo-1:1.2.3-4".to_string(), - full_version: "1:1.2.3-4".to_string(), }; let package2 = Deb { name: "bar".to_string(), epoch: 1, upstream_version: PackageVersion("1.2.3".to_string()), debian_revision: PackageVersion("4".to_string()), - full_name: "bar-1:1.2.3-4".to_string(), - full_version: "1:1.2.3-4".to_string(), }; assert!(package2.partial_cmp(&package1).is_none()); } @@ -230,16 +224,12 @@ mod deb_tests { epoch: 1, upstream_version: PackageVersion("1.2.3".to_string()), debian_revision: PackageVersion("4".to_string()), - full_name: "foo-bar-1:1.2.3-4".to_string(), - full_version: "1:1.2.3-4".to_string(), }; let package2 = Deb { name: "foo-bar".to_string(), epoch: 1, upstream_version: PackageVersion("1.2.4".to_string()), debian_revision: PackageVersion("4".to_string()), - full_name: "foo-bar-1:1.2.4-4".to_string(), - full_version: "1:1.2.4-4".to_string(), }; assert!(package1 < package2); @@ -248,8 +238,6 @@ mod deb_tests { epoch: 1, upstream_version: PackageVersion("1.2.3".to_string()), debian_revision: PackageVersion("5".to_string()), - full_name: "foo-bar-1.2.3-5:1".to_string(), - full_version: "1:1.2.3-5".to_string(), }; assert!(package1 < package2); @@ -258,8 +246,6 @@ mod deb_tests { epoch: 1, upstream_version: PackageVersion("1.2.3~rc".to_string()), debian_revision: PackageVersion("4".to_string()), - full_name: "foo-bar-1:1.2.3~rc-4".to_string(), - full_version: "1:1.2.3~rc-4".to_string(), }; assert!(package2 < package1); } @@ -271,16 +257,12 @@ mod deb_tests { epoch: 1, upstream_version: PackageVersion("1.2.3".to_string()), debian_revision: PackageVersion("4".to_string()), - full_name: "foo-bar-1:1.2.3-4".to_string(), - full_version: "1:1.2.3-4".to_string(), }; let package2 = Deb { name: "foo-bar".to_string(), epoch: 1, upstream_version: PackageVersion("1.2.3".to_string()), debian_revision: PackageVersion("4".to_string()), - full_name: "foo-bar-1:1.2.3-4".to_string(), - full_version: "1:1.2.3-4".to_string(), }; assert!(package1 == package2); } @@ -300,8 +282,7 @@ mod deb_tests { package.debian_revision, PackageVersion("2.20160614".to_string()) ); - assert_eq!(package.full_name, "mesa-libgbm-2:11.2.2-2.20160614"); - assert_eq!(package.full_version, "2:11.2.2-2.20160614"); + assert_eq!(package.get_version(), "2:11.2.2-2.20160614"); let package = Deb::from_full_name("keyutils-1.5.8-3").unwrap(); assert_eq!(package.name, "keyutils"); @@ -311,8 +292,7 @@ mod deb_tests { PackageVersion("1.5.8".to_string()) ); assert_eq!(package.debian_revision, PackageVersion("3".to_string())); - assert_eq!(package.full_name, "keyutils-1.5.8-3"); - assert_eq!(package.full_version, "1.5.8-3"); + assert_eq!(package.get_version(), "1.5.8-3"); let package = Deb::from_full_name("httpd-manual-1:2.4.6-45.0.1.4.h10").unwrap(); assert_eq!(package.name, "httpd-manual"); @@ -325,7 +305,7 @@ mod deb_tests { package.debian_revision, PackageVersion("45.0.1.4.h10".to_string()) ); - assert_eq!(package.full_name, "httpd-manual-1:2.4.6-45.0.1.4.h10"); + assert_eq!(package.get_version(), "1:2.4.6-45.0.1.4.h10"); let package = Deb::from_full_name("libzstd1-1.3.8+dfsg-3+deb10u2").unwrap(); assert_eq!(package.name, "libzstd1"); @@ -338,7 +318,7 @@ mod deb_tests { package.debian_revision, PackageVersion("3+deb10u2".to_string()) ); - assert_eq!(package.full_name, "libzstd1-1.3.8+dfsg-3+deb10u2"); + assert_eq!(package.get_version(), "1.3.8+dfsg-3+deb10u2"); let package = Deb::from_full_name("xserver-xorg-video-intel-2:2.99.917+git20180925-2").unwrap(); @@ -349,10 +329,7 @@ mod deb_tests { PackageVersion("2.99.917+git20180925".to_string()) ); assert_eq!(package.debian_revision, PackageVersion("2".to_string())); - assert_eq!( - package.full_name, - "xserver-xorg-video-intel-2:2.99.917+git20180925-2", - ); + assert_eq!(package.get_version(), "2:2.99.917+git20180925-2"); let package = Deb::from_full_name("ucf-3.0038+nmu1").unwrap(); assert_eq!(package.name, "ucf"); @@ -362,14 +339,14 @@ mod deb_tests { PackageVersion("3.0038+nmu1".to_string()) ); assert_eq!(package.debian_revision, PackageVersion("".to_string())); - assert_eq!(package.full_name, "ucf-3.0038+nmu1"); + assert_eq!(package.get_version(), "3.0038+nmu1"); let package = Deb::from_full_name("apport-symptoms-020").unwrap(); assert_eq!(package.name, "apport-symptoms"); assert_eq!(package.epoch, 0); assert_eq!(package.upstream_version, PackageVersion("020".to_string())); assert_eq!(package.debian_revision, PackageVersion("".to_string())); - assert_eq!(package.full_name, "apport-symptoms-020"); + assert_eq!(package.get_version(), "020"); let package = Deb::from_full_name("mariadb-server-10.6-1:10.6.18+maria~ubu2204").unwrap(); assert_eq!(package.name, "mariadb-server-10.6"); @@ -379,10 +356,7 @@ mod deb_tests { PackageVersion("10.6.18+maria~ubu2204".to_string()) ); assert_eq!(package.debian_revision, PackageVersion("".to_string())); - assert_eq!( - package.full_name, - "mariadb-server-10.6-1:10.6.18+maria~ubu2204" - ); + assert_eq!(package.get_version(), "1:10.6.18+maria~ubu2204"); } #[test] pub fn from_name_and_full_version() { @@ -400,8 +374,7 @@ mod deb_tests { package.debian_revision, PackageVersion("2.20160614".to_string()) ); - assert_eq!(package.full_name, "mesa-libgbm-2:11.2.2-2.20160614"); - assert_eq!(package.full_version, "2:11.2.2-2.20160614"); + assert_eq!(package.get_version(), "2:11.2.2-2.20160614"); let package = Deb::from_name_and_full_version("mesa-libgbm", "2:11.2.2").unwrap(); assert_eq!(package.name, "mesa-libgbm"); @@ -411,8 +384,7 @@ mod deb_tests { PackageVersion("11.2.2".to_string()) ); assert_eq!(package.debian_revision, PackageVersion("".to_string())); - assert_eq!(package.full_name, "mesa-libgbm-2:11.2.2"); - assert_eq!(package.full_version, "2:11.2.2"); + assert_eq!(package.get_version(), "2:11.2.2"); let package = Deb::from_name_and_full_version("mesa-libgbm", "11.2.2").unwrap(); assert_eq!(package.name, "mesa-libgbm"); @@ -422,8 +394,7 @@ mod deb_tests { PackageVersion("11.2.2".to_string()) ); assert_eq!(package.debian_revision, PackageVersion("".to_string())); - assert_eq!(package.full_name, "mesa-libgbm-11.2.2"); - assert_eq!(package.full_version, "11.2.2"); + assert_eq!(package.get_version(), "11.2.2"); let package = Deb::from_name_and_full_version("mesa-libgbm", "11.2.2-2.20160614").unwrap(); assert_eq!(package.name, "mesa-libgbm"); @@ -436,7 +407,6 @@ mod deb_tests { package.debian_revision, PackageVersion("2.20160614".to_string()) ); - assert_eq!(package.full_name, "mesa-libgbm-11.2.2-2.20160614"); - assert_eq!(package.full_version, "11.2.2-2.20160614"); + assert_eq!(package.get_version(), "11.2.2-2.20160614"); } } diff --git a/rust/src/notus/packages/ebuild.rs b/rust/src/notus/packages/ebuild.rs index b59f9653d..0944f058c 100644 --- a/rust/src/notus/packages/ebuild.rs +++ b/rust/src/notus/packages/ebuild.rs @@ -10,8 +10,7 @@ use super::{Package, PackageVersion}; #[derive(Debug, PartialEq, Clone)] pub struct EBuild { name: String, - full_name: String, - full_version: PackageVersion, + version: PackageVersion, } impl PartialOrd for EBuild { @@ -20,11 +19,7 @@ impl PartialOrd for EBuild { return None; } - if self.full_version == other.full_version { - return Some(Ordering::Equal); - } - - self.full_version.partial_cmp(&other.full_version) + self.version.partial_cmp(&other.version) } } @@ -54,8 +49,7 @@ impl Package for EBuild { Some(EBuild { name, - full_name: full_name.to_string(), - full_version: PackageVersion(full_version), + version: PackageVersion(full_version), }) } @@ -65,14 +59,10 @@ impl Package for EBuild { } let name = name.trim(); let full_version = full_version.trim(); - let mut full_name = name.to_owned(); - full_name.push('-'); - full_name.push_str(full_version); Some(EBuild { name: name.to_string(), - full_name, - full_version: PackageVersion(full_version.to_string()), + version: PackageVersion(full_version.to_string()), }) } @@ -81,7 +71,7 @@ impl Package for EBuild { } fn get_version(&self) -> String { - self.full_version.0.clone() + self.version.0.clone() } } diff --git a/rust/src/notus/packages/rpm.rs b/rust/src/notus/packages/rpm.rs index 5ea6e0d5d..1f3311cf2 100644 --- a/rust/src/notus/packages/rpm.rs +++ b/rust/src/notus/packages/rpm.rs @@ -6,15 +6,15 @@ use super::{Package, PackageVersion}; use lazy_regex::{lazy_regex, Lazy, Regex}; use std::cmp::Ordering; +/// Used for parsing the full name of a package static RE: Lazy = lazy_regex!(r"^(.*)-(?:(\d+):)?([^-]+)-([^-]+)\.([^-]+)$"); +/// Used for parsing the full version of a package static RE_VERSION: Lazy = lazy_regex!(r"^(?:(\d+):)?([^-]+)-([^-]+)\.([^-]+)$"); /// Represent a based Redhat package #[derive(Debug, PartialEq, Clone)] pub struct Rpm { name: String, - full_name: String, - full_version: String, epoch: u64, version: PackageVersion, release: PackageVersion, @@ -23,6 +23,15 @@ pub struct Rpm { static EXCEPTIONS: [&str; 2] = ["_fips", ".ksplice"]; +fn find_any_exception(name: &str) -> String { + for exception in EXCEPTIONS.iter() { + if name.contains(exception) { + return exception.to_string(); + } + } + "".to_string() +} + impl PartialOrd for Rpm { fn partial_cmp(&self, other: &Self) -> Option { if self.name != other.name { @@ -31,16 +40,8 @@ impl PartialOrd for Rpm { if self.arch != other.arch { return None; } - for e in EXCEPTIONS { - let a = self.full_version.find(e); - let b = other.full_version.find(e); - if a.is_some() != b.is_some() { - return None; - } - } - - if self.full_version == other.full_version { - return Some(Ordering::Equal); + if find_any_exception(&self.release.0) != find_any_exception(&other.release.0) { + return None; } if self.epoch != other.epoch { @@ -68,7 +69,7 @@ impl Package for Rpm { let full_name = full_name.trim(); // Get all fields - let (name, epochstr, version, release, arch) = match RE.captures(full_name) { + let (name, epoch_str, version, release, arch) = match RE.captures(full_name) { None => { return None; } @@ -81,9 +82,9 @@ impl Package for Rpm { ), }; // parse epoch to u64. If should never fail. Therefore I let it panic - let epoch = epochstr.parse::().unwrap(); + let epoch = epoch_str.parse::().unwrap(); - let mut full_version = epochstr.to_owned(); + let mut full_version = epoch_str.to_owned(); full_version.push(':'); full_version.push_str(version); full_version.push('-'); @@ -93,8 +94,6 @@ impl Package for Rpm { Some(Rpm { name: name.to_string(), - full_name: full_name.to_string(), - full_version, epoch, version: PackageVersion(version.to_string()), release: PackageVersion(release.to_string()), @@ -111,7 +110,7 @@ impl Package for Rpm { let full_version = full_version.trim(); // Get all fields - let (epochstr, version, release, arch) = match RE_VERSION.captures(full_version) { + let (epoch_str, version, release, arch) = match RE_VERSION.captures(full_version) { None => { return None; } @@ -124,7 +123,7 @@ impl Package for Rpm { }; // parse epoch to u64. If should never fail. Therefore I let it panic - let epoch = epochstr.parse::().unwrap(); + let epoch = epoch_str.parse::().unwrap(); let mut full_name = name.to_owned(); full_name.push('-'); @@ -132,8 +131,6 @@ impl Package for Rpm { Some(Rpm { name: name.to_string(), - full_name: full_name.to_string(), - full_version: full_version.to_string(), epoch, version: PackageVersion(version.to_string()), release: PackageVersion(release.to_string()), @@ -146,7 +143,17 @@ impl Package for Rpm { } fn get_version(&self) -> String { - self.full_version.clone() + let mut ret = "".to_string(); + if self.epoch > 0 { + ret.push_str(&self.epoch.to_string()); + ret.push(':'); + } + ret.push_str(&self.version.0); + ret.push('-'); + ret.push_str(&self.release.0); + ret.push('.'); + ret.push_str(&self.arch); + ret } } @@ -176,70 +183,70 @@ mod rpm_tests { assert_eq!(package.name, "mesa-libgbm"); assert_eq!(package.version, PackageVersion("11.2.2".to_string())); assert_eq!(package.release, PackageVersion("2.20160614".to_string())); - assert_eq!(package.full_name, "mesa-libgbm-11.2.2-2.20160614.x86_64"); + assert_eq!(package.get_version(), "11.2.2-2.20160614.x86_64"); let package = Rpm::from_full_name("keyutils-1.5.8-3.x86_64").unwrap(); assert_eq!(package.arch, "x86_64"); assert_eq!(package.name, "keyutils"); assert_eq!(package.version, PackageVersion("1.5.8".to_string())); assert_eq!(package.release, PackageVersion("3".to_string())); - assert_eq!(package.full_name, "keyutils-1.5.8-3.x86_64"); + assert_eq!(package.get_version(), "1.5.8-3.x86_64"); let package = Rpm::from_full_name("httpd-manual-2.4.6-45.0.1.4.h10.noarch").unwrap(); assert_eq!(package.arch, "noarch"); assert_eq!(package.name, "httpd-manual"); assert_eq!(package.version, PackageVersion("2.4.6".to_string())); assert_eq!(package.release, PackageVersion("45.0.1.4.h10".to_string())); - assert_eq!(package.full_name, "httpd-manual-2.4.6-45.0.1.4.h10.noarch"); + assert_eq!(package.get_version(), "2.4.6-45.0.1.4.h10.noarch"); let package = Rpm::from_full_name("cups-libs-1.6.3-26.h1.x86_64").unwrap(); assert_eq!(package.arch, "x86_64"); assert_eq!(package.name, "cups-libs"); assert_eq!(package.version, PackageVersion("1.6.3".to_string())); assert_eq!(package.release, PackageVersion("26.h1".to_string())); - assert_eq!(package.full_name, "cups-libs-1.6.3-26.h1.x86_64"); + assert_eq!(package.get_version(), "1.6.3-26.h1.x86_64"); let package = Rpm::from_full_name("GConf2-3.2.6-8.x86_64").unwrap(); assert_eq!(package.arch, "x86_64"); assert_eq!(package.name, "GConf2"); assert_eq!(package.version, PackageVersion("3.2.6".to_string())); assert_eq!(package.release, PackageVersion("8".to_string())); - assert_eq!(package.full_name, "GConf2-3.2.6-8.x86_64"); + assert_eq!(package.get_version(), "3.2.6-8.x86_64"); let package = Rpm::from_full_name("libtool-ltdl-2.4.2-21.x86_64").unwrap(); assert_eq!(package.arch, "x86_64"); assert_eq!(package.name, "libtool-ltdl"); assert_eq!(package.version, PackageVersion("2.4.2".to_string())); assert_eq!(package.release, PackageVersion("21".to_string())); - assert_eq!(package.full_name, "libtool-ltdl-2.4.2-21.x86_64"); + assert_eq!(package.get_version(), "2.4.2-21.x86_64"); let package = Rpm::from_full_name("microcode_ctl-2.1-22.6.h2.x86_64").unwrap(); assert_eq!(package.arch, "x86_64"); assert_eq!(package.name, "microcode_ctl"); assert_eq!(package.version, PackageVersion("2.1".to_string())); assert_eq!(package.release, PackageVersion("22.6.h2".to_string())); - assert_eq!(package.full_name, "microcode_ctl-2.1-22.6.h2.x86_64"); + assert_eq!(package.get_version(), "2.1-22.6.h2.x86_64"); let package = Rpm::from_full_name("postgresql-libs-9.2.23-3.x86_64").unwrap(); assert_eq!(package.arch, "x86_64"); assert_eq!(package.name, "postgresql-libs"); assert_eq!(package.version, PackageVersion("9.2.23".to_string())); assert_eq!(package.release, PackageVersion("3".to_string())); - assert_eq!(package.full_name, "postgresql-libs-9.2.23-3.x86_64"); + assert_eq!(package.get_version(), "9.2.23-3.x86_64"); let package = Rpm::from_full_name("NetworkManager-1.8.0-9.h2.x86_64").unwrap(); assert_eq!(package.arch, "x86_64"); assert_eq!(package.name, "NetworkManager"); assert_eq!(package.version, PackageVersion("1.8.0".to_string())); assert_eq!(package.release, PackageVersion("9.h2".to_string())); - assert_eq!(package.full_name, "NetworkManager-1.8.0-9.h2.x86_64"); + assert_eq!(package.get_version(), "1.8.0-9.h2.x86_64"); let package = Rpm::from_full_name("perl-Pod-Escapes-1.04-285.h2.noarch").unwrap(); assert_eq!(package.arch, "noarch"); assert_eq!(package.name, "perl-Pod-Escapes"); assert_eq!(package.version, PackageVersion("1.04".to_string())); assert_eq!(package.release, PackageVersion("285.h2".to_string())); - assert_eq!(package.full_name, "perl-Pod-Escapes-1.04-285.h2.noarch"); + assert_eq!(package.get_version(), "1.04-285.h2.noarch"); let package = Rpm::from_full_name(" libtool-ltdl-2.4.2-21.x86_64\r\n").unwrap(); assert_eq!(package.arch, "x86_64"); @@ -248,11 +255,7 @@ mod rpm_tests { Rpm::from_full_name("docker-engine-1:18.09.0-200.h62.33.19.eulerosv2r10.x86_64") .unwrap(); assert_eq!( - package.full_name, - "docker-engine-1:18.09.0-200.h62.33.19.eulerosv2r10.x86_64", - ); - assert_eq!( - package.full_version, + package.get_version(), "1:18.09.0-200.h62.33.19.eulerosv2r10.x86_64" ); assert_eq!(package.epoch, 1); @@ -262,6 +265,14 @@ mod rpm_tests { PackageVersion("200.h62.33.19.eulerosv2r10".to_string()) ); assert_eq!(package.arch, "x86_64"); + + let package = Rpm::from_full_name("libaspell15-0.60.6.1-18.3.1.x86_64").unwrap(); + assert_eq!(package.epoch, 0); + assert_eq!(package.arch, "x86_64"); + assert_eq!(package.name, "libaspell15"); + assert_eq!(package.version, PackageVersion("0.60.6.1".to_string())); + assert_eq!(package.release, PackageVersion("18.3.1".to_string())); + assert_eq!(package.get_version(), "0.60.6.1-18.3.1.x86_64"); } #[test] @@ -294,8 +305,6 @@ mod rpm_tests { version: PackageVersion("1.2.3".to_string()), release: PackageVersion("4".to_string()), arch: "x86_64".to_string(), - full_name: "foo-bar-1.2.3-4.x86_64".to_string(), - full_version: "1.2.3-4.x86_64".to_string(), }; let package2 = Rpm { name: "foo-bar".to_string(), @@ -303,8 +312,6 @@ mod rpm_tests { version: PackageVersion("1.2.4".to_string()), release: PackageVersion("4".to_string()), arch: "x86_64".to_string(), - full_name: "foo-bar-1.2.4-4.x86_64".to_string(), - full_version: "1.2.4-4.x86_64".to_string(), }; assert!(package2 > package1); @@ -314,8 +321,6 @@ mod rpm_tests { version: PackageVersion("1.2.3".to_string()), release: PackageVersion("5".to_string()), arch: "x86_64".to_string(), - full_name: "foo-bar-1.2.3-5.x86_64".to_string(), - full_version: "1.2.3-5.x86_64".to_string(), }; assert!(package2 > package1); } @@ -328,8 +333,6 @@ mod rpm_tests { version: PackageVersion("1.2.3".to_string()), release: PackageVersion("4".to_string()), arch: "x86_64".to_string(), - full_name: "foo-bar-1.2.3-4.x86_64".to_string(), - full_version: "1.2.3-4.x86_64".to_string(), }; let package2 = Rpm { name: "foo-bar".to_string(), @@ -337,8 +340,6 @@ mod rpm_tests { version: PackageVersion("1.2.3".to_string()), release: PackageVersion("4".to_string()), arch: "aarch64".to_string(), - full_name: "foo-bar-1.2.3-4.aarch64".to_string(), - full_version: "1.2.3-4.aarch64".to_string(), }; let package3 = Rpm { name: "foo-bar".to_string(), @@ -346,8 +347,6 @@ mod rpm_tests { version: PackageVersion("1.2.4".to_string()), release: PackageVersion("4".to_string()), arch: "aarch64".to_string(), - full_name: "foo-bar-1.2.4-4.aarch64".to_string(), - full_version: "1.2.4-4.aarch64".to_string(), }; let package4 = Rpm { name: "foo-bar".to_string(), @@ -355,8 +354,6 @@ mod rpm_tests { version: PackageVersion("1.2.3".to_string()), release: PackageVersion("5".to_string()), arch: "aarch64".to_string(), - full_name: "foo-bar-1.2.3-5.aarch64".to_string(), - full_version: "1.2.3-5.aarch64".to_string(), }; //Not comparable, because different archs. Compare returns None. assert!(package2.partial_cmp(&package1).is_none()); @@ -375,8 +372,6 @@ mod rpm_tests { version: PackageVersion("1.2.3".to_string()), release: PackageVersion("4".to_string()), arch: "x86_64".to_string(), - full_name: "foo-bar-1.2.3-4.x86_64".to_string(), - full_version: "1.2.3-4.x86_64".to_string(), }; let package2 = Rpm { name: "foo-bar".to_string(), @@ -384,8 +379,6 @@ mod rpm_tests { version: PackageVersion("1.2.3".to_string()), release: PackageVersion("4".to_string()), arch: "x86_64".to_string(), - full_name: "foo-bar-1.2.3-4.x86_64".to_string(), - full_version: "1:1.2.3-4.x86_64".to_string(), }; assert!(package2 > package1); } @@ -398,8 +391,6 @@ mod rpm_tests { version: PackageVersion("1.2.3".to_string()), release: PackageVersion("4".to_string()), arch: "x86_64".to_string(), - full_name: "foo-1.2.3-4.x86_64".to_string(), - full_version: "1.2.3-4.x86_64".to_string(), }; let package2 = Rpm { name: "bar".to_string(), @@ -407,8 +398,6 @@ mod rpm_tests { version: PackageVersion("1.2.3".to_string()), release: PackageVersion("4".to_string()), arch: "x86_64".to_string(), - full_name: "bar-1.2.3-4.x86_64".to_string(), - full_version: "1.2.3-4.x86_64".to_string(), }; assert!(package2.partial_cmp(&package1).is_none()); assert!(package1.partial_cmp(&package2).is_none()); @@ -422,8 +411,6 @@ mod rpm_tests { version: PackageVersion("1.2.3".to_string()), release: PackageVersion("4".to_string()), arch: "x86_64".to_string(), - full_name: "foo-bar-1.2.3-4.x86_64".to_string(), - full_version: "1.2.3-4.x86_64".to_string(), }; let package2 = Rpm { name: "foo-bar".to_string(), @@ -431,8 +418,6 @@ mod rpm_tests { version: PackageVersion("1.2.4".to_string()), release: PackageVersion("4".to_string()), arch: "x86_64".to_string(), - full_name: "foo-bar-1.2.4-4.x86_64".to_string(), - full_version: "1.2.4-4.x86_64".to_string(), }; assert!(package1 < package2); @@ -442,8 +427,6 @@ mod rpm_tests { version: PackageVersion("1.2.3".to_string()), release: PackageVersion("5".to_string()), arch: "x86_64".to_string(), - full_name: "foo-bar-1.2.3-5.x86_64".to_string(), - full_version: "1.2.3-5.x86_64".to_string(), }; assert!(package1 < package2); @@ -453,8 +436,6 @@ mod rpm_tests { version: PackageVersion("9.0.2092".to_string()), release: PackageVersion("8.oe2403".to_string()), arch: "x86_64".to_string(), - full_name: "vim-minimal-9.0.2092-8.oe2403.x86_64".to_string(), - full_version: "9.0.2092-8.oe2403.x86_64".to_string(), }; let package2 = Rpm { @@ -463,8 +444,6 @@ mod rpm_tests { version: PackageVersion("4294967296.0.2092".to_string()), release: PackageVersion("8.oe2403".to_string()), arch: "x86_64".to_string(), - full_name: "vim-minimal-4294967296.0.2092-8.oe2403.x86_64".to_string(), - full_version: "4294967296.0.2092-8.oe2403.x86_64".to_string(), }; assert!(package1 < package2); @@ -474,9 +453,6 @@ mod rpm_tests { version: PackageVersion("429496729542949672954294967295.0.2092".to_string()), release: PackageVersion("8.oe2403".to_string()), arch: "x86_64".to_string(), - full_name: "vim-minimal-429496729542949672954294967295.0.2092-8.oe2403.x86_64" - .to_string(), - full_version: "429496729542949672954294967295.0.2092-8.oe2403.x86_64".to_string(), }; assert!(package1 < package2); } @@ -489,8 +465,6 @@ mod rpm_tests { version: PackageVersion("18.09.0.200".to_string()), release: PackageVersion("20.h47.28.15.eulerosv2r10".to_string()), arch: "x86_64".to_string(), - full_name: "docker-engine-18.09.0.200-200.h47.28.15.eulerosv2r10.x86_64".to_string(), - full_version: "18.09.0.200-200.h47.28.15.eulerosv2r10.x86_64".to_string(), }; let package2 = Rpm { name: "docker-engine".to_string(), @@ -498,8 +472,6 @@ mod rpm_tests { version: PackageVersion("18.09.0".to_string()), release: PackageVersion("20.h62.33.19.eulerosv2r10".to_string()), arch: "x86_64".to_string(), - full_name: "docker-engine-1:18.09.0-200.h62.33.19.eulerosv2r10.x86_64".to_string(), - full_version: "1:18.09.0-200.h62.33.19.eulerosv2r10.x86_64".to_string(), }; assert!(package2 > package1) @@ -515,6 +487,6 @@ mod rpm_tests { assert_eq!(package.name, "cups-libs"); assert_eq!(package.version, PackageVersion("1.6.3".to_string())); assert_eq!(package.release, PackageVersion("26.h1".to_string())); - assert_eq!(package.full_name, "cups-libs-1.6.3-26.h1.x86_64"); + assert_eq!(package.get_version(), "1.6.3-26.h1.x86_64"); } } diff --git a/rust/src/notus/packages/slack.rs b/rust/src/notus/packages/slack.rs index 64d417325..30016c443 100644 --- a/rust/src/notus/packages/slack.rs +++ b/rust/src/notus/packages/slack.rs @@ -13,8 +13,6 @@ static RE_VERSION: Lazy = lazy_regex!(r"(..*)-(..*)-(\d)(?:_slack(..*))?" #[derive(Debug, PartialEq, Clone)] pub struct Slack { name: String, - full_name: String, - full_version: String, build: PackageVersion, target: PackageVersion, version: PackageVersion, @@ -31,10 +29,6 @@ impl PartialOrd for Slack { return None; } - if self.full_version == other.full_version { - return Some(Ordering::Equal); - } - if let Some(comp) = self.version.partial_cmp(&other.version) { if comp.is_ne() { return Some(comp); @@ -85,8 +79,6 @@ impl Package for Slack { Some(Slack { name: name.to_string(), - full_name: full_name.to_string(), - full_version, target: PackageVersion(target.to_string()), build: PackageVersion(build.to_string()), version: PackageVersion(version.to_string()), @@ -121,8 +113,6 @@ impl Package for Slack { Some(Slack { name: name.to_string(), - full_name, - full_version: full_version.to_string(), target: PackageVersion(target.to_string()), build: PackageVersion(build.to_string()), version: PackageVersion(version.to_string()), @@ -135,7 +125,12 @@ impl Package for Slack { } fn get_version(&self) -> String { - self.full_version.clone() + let mut ret = format!("{}-{}-{}", self.version.0, self.arch, self.build.0); + if !self.target.0.is_empty() { + ret.push_str("_slack"); + ret.push_str(&self.target.0); + } + ret } } @@ -153,8 +148,6 @@ mod slack_tests { build: PackageVersion("4".to_string()), arch: "x86_64".to_string(), target: PackageVersion("15.0".to_string()), - full_name: "foo-bar-1.2.3-x86_64-4_slack15.0".to_string(), - full_version: "1.2.3-x86_64-4_slack15.0".to_string(), }; let package2 = Slack { name: "foo-bar".to_string(), @@ -162,8 +155,6 @@ mod slack_tests { build: PackageVersion("4".to_string()), arch: "x86_64".to_string(), target: PackageVersion("15.0".to_string()), - full_name: "foo-bar-1.2.4-x86_64-4_slack15.0".to_string(), - full_version: "1.2.4-x86_64-4_slack15.0".to_string(), }; assert!(package2 > package1); @@ -173,8 +164,6 @@ mod slack_tests { build: PackageVersion("5".to_string()), arch: "x86_64".to_string(), target: PackageVersion("15.0".to_string()), - full_name: "foo-bar-1.2.3-x86_64-5_slack15.0".to_string(), - full_version: "1.2.3-x86_64-5_slack15.0".to_string(), }; assert!(package2 > package1); @@ -184,8 +173,6 @@ mod slack_tests { build: PackageVersion("4".to_string()), arch: "x86_64".to_string(), target: PackageVersion("15.1".to_string()), - full_name: "foo-bar-1.2.3-x86_64-4_slack15.1".to_string(), - full_version: "1.2.3-x86_64-4_slack15.1".to_string(), }; assert!(package2 > package1); } @@ -198,8 +185,6 @@ mod slack_tests { build: PackageVersion("4".to_string()), arch: "x86_64".to_string(), target: PackageVersion("15.0".to_string()), - full_name: "foo-bar-1.2.3-x86_64-4_slack15.0".to_string(), - full_version: "1.2.3-x86_64-4_slack15.0".to_string(), }; let package2 = Slack { name: "foo-bar".to_string(), @@ -207,8 +192,6 @@ mod slack_tests { build: PackageVersion("4".to_string()), arch: "aarch64".to_string(), target: PackageVersion("15.0".to_string()), - full_name: "foo-bar-1.2.3-aarch64-4_slack15.0".to_string(), - full_version: "1.2.3-aarch64-4_slack15.0".to_string(), }; let package3 = Slack { name: "foo-bar".to_string(), @@ -216,8 +199,6 @@ mod slack_tests { build: PackageVersion("4".to_string()), arch: "aarch64".to_string(), target: PackageVersion("15.0".to_string()), - full_name: "foo-bar-1.2.4-aarch64-4_slack15.0".to_string(), - full_version: "1.2.4-aarch64-4_slack15.0".to_string(), }; let package4 = Slack { name: "foo-bar".to_string(), @@ -225,8 +206,6 @@ mod slack_tests { build: PackageVersion("5".to_string()), arch: "aarch64".to_string(), target: PackageVersion("15.0".to_string()), - full_name: "foo-bar-1.2.3-aarch64-5_slack15.0".to_string(), - full_version: "1.2.3-aarch64-5_slack15.0".to_string(), }; let package5 = Slack { name: "foo-bar".to_string(), @@ -234,8 +213,6 @@ mod slack_tests { build: PackageVersion("4".to_string()), arch: "aarch64".to_string(), target: PackageVersion("15.1".to_string()), - full_name: "foo-bar-1.2.3-aarch64-4_slack15.1".to_string(), - full_version: "1.2.3-aarch64-4_slack15.1".to_string(), }; assert!(package2.partial_cmp(&package1).is_none()); @@ -256,8 +233,6 @@ mod slack_tests { build: PackageVersion("4".to_string()), arch: "x86_64".to_string(), target: PackageVersion("15.0".to_string()), - full_name: "foo-1.2.3-x86_64-4_slack15.0".to_string(), - full_version: "1.2.3-x86_64-4_slack15.0".to_string(), }; let package2 = Slack { name: "bar".to_string(), @@ -265,8 +240,6 @@ mod slack_tests { build: PackageVersion("4".to_string()), arch: "x86_64".to_string(), target: PackageVersion("15.0".to_string()), - full_name: "bar-1.2.3-x86_64-4_slack15.0".to_string(), - full_version: "1.2.3-x86_64-4_slack15.0".to_string(), }; assert!(package2.partial_cmp(&package1).is_none()); @@ -281,8 +254,6 @@ mod slack_tests { build: PackageVersion("4".to_string()), arch: "x86_64".to_string(), target: PackageVersion("15.0".to_string()), - full_name: "foo-bar-1.2.3-x86_64-4_slack15.0".to_string(), - full_version: "1.2.3-x86_64-4_slack15.0".to_string(), }; let package2 = Slack { name: "foo-bar".to_string(), @@ -290,8 +261,6 @@ mod slack_tests { build: PackageVersion("4".to_string()), arch: "x86_64".to_string(), target: PackageVersion("15.0".to_string()), - full_name: "foo-bar-1.2.4-x86_64-4_slack15.0".to_string(), - full_version: "1.2.4-x86_64-4_slack15.0".to_string(), }; assert!(package1 < package2); @@ -301,8 +270,6 @@ mod slack_tests { build: PackageVersion("5".to_string()), arch: "x86_64".to_string(), target: PackageVersion("15.0".to_string()), - full_name: "foo-bar-1.2.3-x86_64-5_slack15.0".to_string(), - full_version: "1.2.3-x86_64-5_slack15.0".to_string(), }; assert!(package1 < package2); @@ -312,8 +279,6 @@ mod slack_tests { build: PackageVersion("4".to_string()), arch: "x86_64".to_string(), target: PackageVersion("15.1".to_string()), - full_name: "foo-bar-1.2.3-x86_64-4_slack15.1".to_string(), - full_version: "1.2.3-x86_64-4_slack15.1".to_string(), }; assert!(package1 < package2); } @@ -326,8 +291,6 @@ mod slack_tests { build: PackageVersion("4".to_string()), arch: "x86_64".to_string(), target: PackageVersion("15.0".to_string()), - full_name: "foo-bar-1.2.3-x86_64-4_slack15.0".to_string(), - full_version: "1.2.3-x86_64-4_slack15.0".to_string(), }; let package2 = Slack { name: "foo-bar".to_string(), @@ -335,8 +298,6 @@ mod slack_tests { build: PackageVersion("4".to_string()), arch: "x86_64".to_string(), target: PackageVersion("15.0".to_string()), - full_name: "foo-bar-1.2.3-x86_64-4_slack15.0".to_string(), - full_version: "1.2.3-x86_64-4_slack15.0".to_string(), }; assert!(package1 == package2); } @@ -354,8 +315,7 @@ mod slack_tests { assert_eq!(package.version, PackageVersion("1.3.4".to_string())); assert_eq!(package.build, PackageVersion("1".to_string())); assert_eq!(package.target, PackageVersion("15.0".to_string())); - assert_eq!(package.full_version, "1.3.4-x86_64-1_slack15.0"); - assert_eq!(package.full_name, "flac-1.3.4-x86_64-1_slack15.0"); + assert_eq!(package.get_version(), "1.3.4-x86_64-1_slack15.0"); let package = Slack::from_full_name("kernel-source-5.15.27-noarch-1").unwrap(); assert_eq!(package.arch, "noarch"); @@ -363,8 +323,7 @@ mod slack_tests { assert_eq!(package.version, PackageVersion("5.15.27".to_string())); assert_eq!(package.build, PackageVersion("1".to_string())); assert_eq!(package.target, PackageVersion("".to_string())); - assert_eq!(package.full_version, "5.15.27-noarch-1"); - assert_eq!(package.full_name, "kernel-source-5.15.27-noarch-1"); + assert_eq!(package.get_version(), "5.15.27-noarch-1"); let package = Slack::from_full_name("libjpeg-v8a-x86_64-2").unwrap(); assert_eq!(package.arch, "x86_64"); @@ -372,8 +331,7 @@ mod slack_tests { assert_eq!(package.version, PackageVersion("v8a".to_string())); assert_eq!(package.build, PackageVersion("2".to_string())); assert_eq!(package.target, PackageVersion("".to_string())); - assert_eq!(package.full_version, "v8a-x86_64-2"); - assert_eq!(package.full_name, "libjpeg-v8a-x86_64-2"); + assert_eq!(package.get_version(), "v8a-x86_64-2"); let package = Slack::from_full_name(" libjpeg-v8a-x86_64-2\r\n").unwrap(); assert_eq!(package.arch, "x86_64"); @@ -381,8 +339,7 @@ mod slack_tests { assert_eq!(package.version, PackageVersion("v8a".to_string())); assert_eq!(package.build, PackageVersion("2".to_string())); assert_eq!(package.target, PackageVersion("".to_string())); - assert_eq!(package.full_version, "v8a-x86_64-2"); - assert_eq!(package.full_name, "libjpeg-v8a-x86_64-2"); + assert_eq!(package.get_version(), "v8a-x86_64-2"); let package = Slack::from_full_name("libjpeg-v8a-x86_64"); assert!(package.is_none()); @@ -402,8 +359,7 @@ mod slack_tests { assert_eq!(package.version, PackageVersion("1.3.4".to_string())); assert_eq!(package.build, PackageVersion("1".to_string())); assert_eq!(package.target, PackageVersion("15.0".to_string())); - assert_eq!(package.full_version, "1.3.4-x86_64-1_slack15.0"); - assert_eq!(package.full_name, "flac-1.3.4-x86_64-1_slack15.0"); + assert_eq!(package.get_version(), "1.3.4-x86_64-1_slack15.0"); let package = Slack::from_name_and_full_version("flac", "1.3.4-x86_64"); assert!(package.is_none()); diff --git a/rust/src/openvas/cmd.rs b/rust/src/openvas/cmd.rs index c947b0a94..1329c6531 100644 --- a/rust/src/openvas/cmd.rs +++ b/rust/src/openvas/cmd.rs @@ -7,9 +7,10 @@ use std::{ io::Result, process::{Child, Command}, }; + /// This module provides functions to call the openvas executable for different /// purposes, e.g. start or stopping a scan. - +/// /// Check if it is possible to start openvas. pub fn check() -> bool { Command::new("openvas").spawn().is_ok() diff --git a/rust/src/osp/commands.rs b/rust/src/osp/commands.rs index 086f2f40d..6191381ad 100644 --- a/rust/src/osp/commands.rs +++ b/rust/src/osp/commands.rs @@ -27,7 +27,7 @@ pub enum ScanCommand<'a> { type Result = std::result::Result; type Writer = quick_xml::Writer>>; -impl<'a> ScanCommand<'a> { +impl ScanCommand<'_> { fn as_byte_response( scan_id: &str, element_name: &str, diff --git a/rust/src/osp/response.rs b/rust/src/osp/response.rs index bebe31137..c879fb6d6 100644 --- a/rust/src/osp/response.rs +++ b/rust/src/osp/response.rs @@ -81,7 +81,7 @@ impl<'de> Deserialize<'de> for StringF32 { D: serde::Deserializer<'de>, { struct MyVisitor; - impl<'de> Visitor<'de> for MyVisitor { + impl Visitor<'_> for MyVisitor { type Value = StringF32; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("string") @@ -107,7 +107,7 @@ impl<'de> Deserialize<'de> for StringU64 { D: serde::Deserializer<'de>, { struct MyVisitor; - impl<'de> Visitor<'de> for MyVisitor { + impl Visitor<'_> for MyVisitor { type Value = StringU64; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { diff --git a/rust/src/scanner/scan_runner.rs b/rust/src/scanner/scan_runner.rs index f9120a1c0..221b93ced 100644 --- a/rust/src/scanner/scan_runner.rs +++ b/rust/src/scanner/scan_runner.rs @@ -118,6 +118,7 @@ pub(super) mod tests { use crate::models::Target; use crate::models::VT; use crate::nasl::syntax::NaslValue; + use crate::nasl::utils::context::Target as ContextTarget; use crate::nasl::utils::Context; use crate::nasl::utils::Executor; use crate::nasl::utils::Register; @@ -320,7 +321,7 @@ exit({rc}); let storage = DefaultDispatcher::new(); let register = Register::root_initial(&initial); - let target = String::default(); + let target = ContextTarget::default(); let functions = nasl_std_functions(); let loader = |_: &str| code.to_string(); let key = ContextKey::FileName(id.to_string()); diff --git a/rust/src/scanner/vt_runner.rs b/rust/src/scanner/vt_runner.rs index a7bc69292..0ed9b997c 100644 --- a/rust/src/scanner/vt_runner.rs +++ b/rust/src/scanner/vt_runner.rs @@ -1,5 +1,6 @@ use crate::models::{Host, Parameter, Protocol, ScanId}; use crate::nasl::syntax::{Loader, NaslValue}; +use crate::nasl::utils::context::Target; use crate::nasl::utils::{Executor, Register}; use crate::scheduling::Stage; use crate::storage::item::Nvt; @@ -193,10 +194,12 @@ impl<'a, Stack: ScannerStack> VTRunner<'a, Stack> { if let Err(e) = self.check_keys(self.vt) { return e; } + let mut target = Target::default(); + target.set_target(self.target.clone()); let context = Context::new( self.generate_key(), - self.target.clone(), + target, self.storage.as_dispatcher(), self.storage.as_retriever(), self.loader, diff --git a/rust/src/scannerctl/interpret/mod.rs b/rust/src/scannerctl/interpret/mod.rs index 5be30be59..fcff5c871 100644 --- a/rust/src/scannerctl/interpret/mod.rs +++ b/rust/src/scannerctl/interpret/mod.rs @@ -125,17 +125,17 @@ where } async fn run(&self, script: &str) -> Result<(), CliErrorKind> { - let context = self.context_builder.build(ContextKey::Scan( - self.scan_id.clone(), - Some(self.target.clone()), - )); + let target = match self.target.is_empty() { + true => None, + false => Some(self.target.clone()), + }; + let context = self + .context_builder + .build(ContextKey::Scan(self.scan_id.clone(), target)); let register = RegisterBuilder::build(); let code = self.load(script)?; - let results: Vec<_> = CodeInterpreter::new(&code, register, &context) - .stream() - .collect() - .await; - for result in results { + let mut results = CodeInterpreter::new(&code, register, &context).stream(); + while let Some(result) = results.next().await { let r = match result { Ok(x) => x, Err(e) => { diff --git a/rust/src/scheduling/mod.rs b/rust/src/scheduling/mod.rs index 5b08d3a2e..10973c1f9 100644 --- a/rust/src/scheduling/mod.rs +++ b/rust/src/scheduling/mod.rs @@ -147,7 +147,7 @@ pub type ConcurrentVTResult = Result; /// data. /// See: issues/63063 impl Trait in type aliases is unstable /// type ExecutionPlanerResult = Result, VTError>; - +/// /// Is used by a ExecutionPlaner to order VTs in a specific manner and be returned. /// /// It is meant to be used as an Iterator by the caller of ExecutionPlaner while the diff --git a/rust/src/storage/redis/connector.rs b/rust/src/storage/redis/connector.rs index 9e73ca368..2398236ff 100644 --- a/rust/src/storage/redis/connector.rs +++ b/rust/src/storage/redis/connector.rs @@ -278,7 +278,7 @@ pub trait RedisAddAdvisory: RedisWrapper { /// Add an NVT in the redis cache. /// /// The NVT metadata is stored in two different keys: - + /// /// - 'nvt:': stores the general metadata ordered following the KbNvtPos indexes /// - 'oid::prefs': stores the plugins preferences, including the script_timeout /// (which is especial and uses preferences id 0) @@ -479,11 +479,11 @@ pub trait RedisAddNvt: RedisWrapper { // The string ", " is not accepted as reference value, since it will misunderstood // as ref separator. - return ( + ( cves.iter().as_ref().join(", "), bids.iter().as_ref().join(", "), xrefs.iter().as_ref().join(", "), - ); + ) } /// Transforms prefs to string representation {id}:{name}:{id}:{default} so that it can be stored into redis @@ -509,7 +509,7 @@ pub trait RedisAddNvt: RedisWrapper { /// Add an NVT in the redis cache. /// /// The NVT metadata is stored in two different keys: - + /// /// - 'nvt:': stores the general metadata ordered following the KbNvtPos indexes /// - 'oid::prefs': stores the plugins preferences, including the script_timeout /// (which is especial and uses preferences id 0) diff --git a/rust/typos.toml b/rust/typos.toml index 25e73c048..411162901 100644 --- a/rust/typos.toml +++ b/rust/typos.toml @@ -5,6 +5,12 @@ des_ede_cbc_encrypt = "des_ede_cbc_encrypt" [default.extend-words] hd = "hd" guid = "guid" +GOST = "GOST" +fpr = "fpr" [files] -extend-exclude = ["data/osp/response_*.xml", "crates/smoketest/configs/client_sample.cert", "*.notus"] +extend-exclude = [ + "data/osp/response_*.xml", + "crates/smoketest/configs/client_sample.cert", + "*.notus", +]