From 7a8ca4eff2547c5badf6b9a2ccc7d540e4455598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Nicola?= Date: Wed, 4 Sep 2024 11:16:25 +0200 Subject: [PATCH 1/7] Add: nasl built regex library, regex nasl functions and tests. (#1704) * Add: nasl built regex library, regex nasl functions and tests. * Fix: nasl_syntax token for NaslValue::Data Do not replace raw char new line (0x0A) for String type, but for Data type. This is how C implementation behaves. * Fix: string functions and char escape * Fix: more string functions and tests --- rust/Cargo.lock | 18 +- rust/Cargo.toml | 1 + rust/nasl-builtin-regex/Cargo.toml | 17 ++ rust/nasl-builtin-regex/README.md | 6 + rust/nasl-builtin-regex/src/lib.rs | 189 ++++++++++++++++ rust/nasl-builtin-regex/tests/regex.rs | 277 +++++++++++++++++++++++ rust/nasl-builtin-std/Cargo.toml | 1 + rust/nasl-builtin-std/src/lib.rs | 1 + rust/nasl-builtin-string/src/lib.rs | 117 ++++++++-- rust/nasl-builtin-string/tests/string.rs | 140 ++++++++++-- rust/nasl-syntax/src/token.rs | 18 +- 11 files changed, 737 insertions(+), 48 deletions(-) create mode 100644 rust/nasl-builtin-regex/Cargo.toml create mode 100644 rust/nasl-builtin-regex/README.md create mode 100644 rust/nasl-builtin-regex/src/lib.rs create mode 100644 rust/nasl-builtin-regex/tests/regex.rs diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 5a61f6fdd..ab1a9eba8 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -2063,6 +2063,19 @@ dependencies = [ "tracing", ] +[[package]] +name = "nasl-builtin-regex" +version = "0.1.0" +dependencies = [ + "nasl-builtin-std", + "nasl-builtin-utils", + "nasl-function-proc-macro", + "nasl-interpreter", + "nasl-syntax", + "regex", + "storage", +] + [[package]] name = "nasl-builtin-ssh" version = "0.1.0" @@ -2088,6 +2101,7 @@ dependencies = [ "nasl-builtin-misc", "nasl-builtin-network", "nasl-builtin-raw-ip", + "nasl-builtin-regex", "nasl-builtin-ssh", "nasl-builtin-string", "nasl-builtin-utils", @@ -2865,9 +2879,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.5" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 7a49dceed..501c68267 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -12,6 +12,7 @@ members = [ "nasl-builtin-network", "nasl-builtin-description", "nasl-builtin-utils", + "nasl-builtin-regex", "nasl-builtin-std", "nasl-syntax", "nasl-interpreter", diff --git a/rust/nasl-builtin-regex/Cargo.toml b/rust/nasl-builtin-regex/Cargo.toml new file mode 100644 index 000000000..f197d2b51 --- /dev/null +++ b/rust/nasl-builtin-regex/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "nasl-builtin-regex" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nasl-builtin-utils = {path = "../nasl-builtin-utils"} +nasl-syntax = { path = "../nasl-syntax" } +nasl-function-proc-macro = {path = "../nasl-function-proc-macro"} +storage = {path = "../storage"} +regex = "1.10.6" + +[dev-dependencies] +nasl-interpreter = { path = "../nasl-interpreter" } +nasl-builtin-std = {path = "../nasl-builtin-std"} diff --git a/rust/nasl-builtin-regex/README.md b/rust/nasl-builtin-regex/README.md new file mode 100644 index 000000000..6e41f3b1f --- /dev/null +++ b/rust/nasl-builtin-regex/README.md @@ -0,0 +1,6 @@ +## Implements + - ereg + - ereg_replace + - egrep + - eregmatch + diff --git a/rust/nasl-builtin-regex/src/lib.rs b/rust/nasl-builtin-regex/src/lib.rs new file mode 100644 index 000000000..e977335b2 --- /dev/null +++ b/rust/nasl-builtin-regex/src/lib.rs @@ -0,0 +1,189 @@ +// SPDX-FileCopyrightText: 2024 Greenbone AG +// +// SPDX-License-Identifier: GPL-2.0-or-later + +use nasl_builtin_utils::{error::FunctionErrorKind, NaslFunction}; +use nasl_builtin_utils::{Context, Register}; +use nasl_function_proc_macro::nasl_function; +use nasl_syntax::NaslValue; +use regex::{Regex, RegexBuilder}; + +fn parse_search_string(mut s: &str, rnul: bool, multiline: bool) -> &str { + if !rnul { + s = s.split('\0').next().unwrap(); + } + if !multiline { + s = s.split('\n').next().unwrap(); + } + + s +} + +fn make_regex(pattern: &str, icase: bool, multiline: bool) -> Result { + match RegexBuilder::new(pattern.to_string().as_str()) + .case_insensitive(icase) + .multi_line(multiline) + .build() + { + Ok(re) => Ok(re), + Err(e) => Err(FunctionErrorKind::Dirty(format!( + " Error building regular expression pattern: {}", + e + ))), + } +} + +/// Matches a string against a regular expression. +/// - string String to search the pattern in +/// - pattern the pattern that should be matched +/// - icase case insensitive flag +/// - rnul replace the null char in the string. Default TRUE. +/// - multiline Is FALSE by default (string is truncated at the first +/// “end of line”), and can be set to TRUE for multiline search. +/// Return true if matches, false otherwise +#[nasl_function(named(string, pattern, icase, rnul, multiline))] +fn ereg( + string: NaslValue, + pattern: NaslValue, + icase: Option, + rnul: Option, + multiline: Option, +) -> Result { + let icase = icase.unwrap_or(false); + let rnul = rnul.unwrap_or(true); + let multiline = multiline.unwrap_or(false); + + let string = string.to_string(); + let string = parse_search_string(&string, rnul, multiline); + + let re = make_regex(&pattern.to_string(), icase, multiline)?; + Ok(re.is_match(string)) +} + +/// Search for a pattern in a string and replace it. +/// - string String to search the pattern in +/// - pattern pattern to search in the string for +/// - replace string to replace the pattern with +/// - icase case insensitive flag +/// - rnul replace the null char in the string. Default TRUE. +/// +/// Return the new string with the pattern replaced with replace. +#[nasl_function(named(string, pattern, replace, icase, rnul))] +fn ereg_replace( + string: NaslValue, + pattern: NaslValue, + replace: NaslValue, + icase: Option, + rnul: Option, +) -> Result { + let icase = icase.unwrap_or(false); + let rnul = rnul.unwrap_or(true); + + let string = string.to_string(); + let string = parse_search_string(&string, rnul, true); + let re = make_regex(&pattern.to_string(), icase, false)?; + + let out = re + .replace_all(string, replace.to_string().as_str()) + .to_string(); + Ok(out) +} + +/// Looks for a pattern in a string, line by line. +/// +/// - string String to search the pattern in +/// - pattern the pattern that should be matched +/// - icase case insensitive flag +/// - rnul replace the null char in the string. Default TRUE. +/// +/// Returns the concatenation of all lines that match. Null otherwise. +#[nasl_function(named(string, pattern, replace, icase, rnul))] +fn egrep( + string: NaslValue, + pattern: NaslValue, + icase: Option, + rnul: Option, +) -> Result { + let icase = icase.unwrap_or(false); + let rnul = rnul.unwrap_or(true); + + let string = string.to_string(); + let string = parse_search_string(&string, rnul, true); + let re = make_regex(&pattern.to_string(), icase, true)?; + + let lines: Vec<&str> = string + .split_inclusive('\n') + .filter(|l| re.is_match(l)) + .collect(); + + Ok(lines.concat()) +} + +/// Does extended regular expression pattern matching. +/// +/// - pattern An regex pattern +/// - string A string +/// - icase Boolean, for case sensitive +/// - find_all Boolean, to find all matches +/// - rnul replace the null char in the string. Default TRUE. +/// +/// Return an array with the first match (find_all: False) +/// or an array with all matches (find_all: TRUE). +/// NULL or empty if no match was found. +#[nasl_function(named(string, pattern, find_all, icase, rnul))] +fn eregmatch( + string: NaslValue, + pattern: NaslValue, + find_all: Option, + icase: Option, + rnul: Option, +) -> Result { + let icase = icase.unwrap_or(false); + let rnul = rnul.unwrap_or(true); + let find_all = find_all.unwrap_or(false); + + let string = string.to_string(); + let string = parse_search_string(&string, rnul, true); + let re = make_regex(&pattern.to_string(), icase, true)?; + + let matches = match find_all { + true => re + .find_iter(string) + .map(|m| NaslValue::String(m.as_str().to_string())) + .collect(), + false => match re.find(string) { + Some(s) => vec![NaslValue::String(s.as_str().to_string())], + None => vec![], + }, + }; + + Ok(NaslValue::Array(matches)) +} + +/// Returns found function for key or None when not found +pub fn lookup(key: &str) -> Option { + match key { + "ereg" => Some(ereg), + "egrep" => Some(egrep), + "ereg_replace" => Some(ereg_replace), + "eregmatch" => Some(eregmatch), + _ => None, + } +} + +pub struct RegularExpressions; + +impl nasl_builtin_utils::NaslFunctionExecuter for RegularExpressions { + fn nasl_fn_execute( + &self, + name: &str, + register: &Register, + context: &Context, + ) -> Option { + lookup(name).map(|x| x(register, context)) + } + + fn nasl_fn_defined(&self, name: &str) -> bool { + lookup(name).is_some() + } +} diff --git a/rust/nasl-builtin-regex/tests/regex.rs b/rust/nasl-builtin-regex/tests/regex.rs new file mode 100644 index 000000000..b125dc1eb --- /dev/null +++ b/rust/nasl-builtin-regex/tests/regex.rs @@ -0,0 +1,277 @@ +// SPDX-FileCopyrightText: 2024 Greenbone AG +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#[cfg(test)] +mod tests { + use nasl_builtin_std::ContextFactory; + use nasl_builtin_utils::Register; + use nasl_interpreter::CodeInterpreter; + use nasl_syntax::NaslValue; + + #[test] + fn ereg_rnul_true_success() { + let code = r#" + string = 'NASL' + raw_string(0x00) + 'Test'; + ereg(string:string, pattern:"NASL.+Test", icase:FALSE, rnul:TRUE); + "#; + let register = Register::default(); + let mut binding = ContextFactory::default(); + binding + .functions + .push_executer(nasl_builtin_regex::RegularExpressions); + + let context = binding.build(Default::default(), Default::default()); + let mut parser = CodeInterpreter::new(code, register, &context); + parser.next(); + assert_eq!(parser.next(), Some(Ok(NaslValue::Boolean(true)))); + } + + /// In this test, the string is truncated at the first '\0', therefore there is no match + #[test] + fn ereg_rnul_false_failed() { + let code = r#" + string = 'NASL' + raw_string(0x00) + 'Test'; + ereg(string:string, pattern:"NASL.+Test", icase:FALSE, rnul:FALSE); + "#; + let register = Register::default(); + let mut binding = ContextFactory::default(); + binding + .functions + .push_executer(nasl_builtin_regex::RegularExpressions); + + let context = binding.build(Default::default(), Default::default()); + let mut parser = CodeInterpreter::new(code, register, &context); + parser.next(); + assert_eq!(parser.next(), Some(Ok(NaslValue::Boolean(false)))); + } + + #[test] + fn ereg_icase_true_success() { + let code = r#" + string = 'NASL' + raw_string(0x00) + 'Test'; + ereg(string:string, pattern:"nasl.+test", icase:TRUE); + "#; + let register = Register::default(); + let mut binding = ContextFactory::default(); + binding + .functions + .push_executer(nasl_builtin_regex::RegularExpressions); + + let context = binding.build(Default::default(), Default::default()); + let mut parser = CodeInterpreter::new(code, register, &context); + parser.next(); + assert_eq!(parser.next(), Some(Ok(NaslValue::Boolean(true)))); + } + + #[test] + fn ereg_icase_false_failed() { + let code = r#" + string = 'NASL' + raw_string(0x00) + 'Test'; + ereg(string:string, pattern:"nasl.+test"); + "#; + let register = Register::default(); + let mut binding = ContextFactory::default(); + binding + .functions + .push_executer(nasl_builtin_regex::RegularExpressions); + + let context = binding.build(Default::default(), Default::default()); + let mut parser = CodeInterpreter::new(code, register, &context); + parser.next(); + assert_eq!(parser.next(), Some(Ok(NaslValue::Boolean(false)))); + } + + // The following test for multiline are done to behave exactly as C implementation. + #[test] + fn ereg_multiline_true_success() { + let code = r#" + string = 'AAAAAAAA\n NASLTest'; + ereg(string:string, pattern:"NASLTest", multiline: TRUE); + "#; + let register = Register::default(); + let mut binding = ContextFactory::default(); + binding + .functions + .push_executer(nasl_builtin_regex::RegularExpressions); + + let context = binding.build(Default::default(), Default::default()); + let mut parser = CodeInterpreter::new(code, register, &context); + parser.next(); + assert_eq!(parser.next(), Some(Ok(NaslValue::Boolean(true)))); + } + + #[test] + fn ereg_multiline_false_failed() { + let code = r#" + string = 'AAAAAAAA\n NASLTest'; + ereg(string:string, pattern:"NASLTest", multiline: FALSE); + "#; + let register = Register::default(); + let mut binding = ContextFactory::default(); + binding + .functions + .push_executer(nasl_builtin_regex::RegularExpressions); + + let context = binding.build(Default::default(), Default::default()); + let mut parser = CodeInterpreter::new(code, register, &context); + parser.next(); + assert_eq!(parser.next(), Some(Ok(NaslValue::Boolean(false)))); + } + + #[test] + fn ereg_multiline_string_true_success() { + let code = r#" + string = "AAAAAAAA\n NASLTest"; + ereg(string:string, pattern:"NASLTest", multiline: TRUE); + "#; + let register = Register::default(); + let mut binding = ContextFactory::default(); + binding + .functions + .push_executer(nasl_builtin_regex::RegularExpressions); + + let context = binding.build(Default::default(), Default::default()); + let mut parser = CodeInterpreter::new(code, register, &context); + parser.next(); + assert_eq!(parser.next(), Some(Ok(NaslValue::Boolean(true)))); + } + + #[test] + fn ereg_multiline_string_false_success() { + let code = r#" + string = "AAAAAAAA\n NASLTest"; + ereg(string:string, pattern:"NASLTest", multiline: FALSE); + "#; + let register = Register::default(); + let mut binding = ContextFactory::default(); + binding + .functions + .push_executer(nasl_builtin_regex::RegularExpressions); + + let context = binding.build(Default::default(), Default::default()); + let mut parser = CodeInterpreter::new(code, register, &context); + parser.next(); + assert_eq!(parser.next(), Some(Ok(NaslValue::Boolean(true)))); + } + + #[test] + fn ereg_replace() { + let code = r#" + string = "Greenbone Network Gmbh"; + ereg_replace(string:string, pattern:"Network Gmbh", replace: "AG"); + "#; + let register = Register::default(); + let mut binding = ContextFactory::default(); + binding + .functions + .push_executer(nasl_builtin_regex::RegularExpressions); + + let context = binding.build(Default::default(), Default::default()); + let mut parser = CodeInterpreter::new(code, register, &context); + parser.next(); + assert_eq!( + parser.next(), + Some(Ok(NaslValue::String("Greenbone AG".to_string()))) + ); + } + + #[test] + fn egrep() { + let code = r#" + string = "Pair 0 + Odd 1 + Pair 2 + Odd 3"; + egrep(string:string, pattern:"Pair"); + "#; + let register = Register::default(); + let mut binding = ContextFactory::default(); + binding + .functions + .push_executer(nasl_builtin_regex::RegularExpressions); + + let context = binding.build(Default::default(), Default::default()); + let mut parser = CodeInterpreter::new(code, register, &context); + parser.next(); + assert_eq!( + parser.next(), + Some(Ok(NaslValue::String( + "Pair 0\n Pair 2\n".to_string() + ))) + ); + } + + #[test] + fn egrep_data() { + let code = r#" + string = 'Pair 0 + Odd 1 + Pair 2 + Odd 3'; + egrep(string:string, pattern:"Pair"); + "#; + let register = Register::default(); + let mut binding = ContextFactory::default(); + binding + .functions + .push_executer(nasl_builtin_regex::RegularExpressions); + + let context = binding.build(Default::default(), Default::default()); + let mut parser = CodeInterpreter::new(code, register, &context); + parser.next(); + assert_eq!( + parser.next(), + Some(Ok(NaslValue::String( + "Pair 0\n Pair 2\n".to_string() + ))) + ); + } + + #[test] + fn eregmatch_all() { + let code = r#" + string = "Foo Bar Bee 123 true false"; + eregmatch(string: string, pattern: "Bar|true", find_all: TRUE); + "#; + let register = Register::default(); + let mut binding = ContextFactory::default(); + binding + .functions + .push_executer(nasl_builtin_regex::RegularExpressions); + + let context = binding.build(Default::default(), Default::default()); + let mut parser = CodeInterpreter::new(code, register, &context); + parser.next(); + assert_eq!( + parser.next(), + Some(Ok(NaslValue::Array(vec![ + NaslValue::String("Bar".to_string()), + NaslValue::String("true".to_string()) + ]))) + ); + } + + #[test] + fn eregmatch_first() { + let code = r#" + string = "Foo Bar Bee 123 true false"; + eregmatch(string: string, pattern: "Bar|true", find_all: FALSE); + "#; + let register = Register::default(); + let mut binding = ContextFactory::default(); + binding + .functions + .push_executer(nasl_builtin_regex::RegularExpressions); + + let context = binding.build(Default::default(), Default::default()); + let mut parser = CodeInterpreter::new(code, register, &context); + parser.next(); + assert_eq!( + parser.next(), + Some(Ok(NaslValue::Array(vec![NaslValue::String( + "Bar".to_string() + )]))) + ); + } +} diff --git a/rust/nasl-builtin-std/Cargo.toml b/rust/nasl-builtin-std/Cargo.toml index 2c2b0e5fd..79fd7ade0 100644 --- a/rust/nasl-builtin-std/Cargo.toml +++ b/rust/nasl-builtin-std/Cargo.toml @@ -15,6 +15,7 @@ nasl-builtin-http = { version = "0.1.0", path = "../nasl-builtin-http" } nasl-builtin-description = { path = "../nasl-builtin-description" } nasl-builtin-network = { path = "../nasl-builtin-network" } nasl-builtin-misc = { path = "../nasl-builtin-misc" } +nasl-builtin-regex = { path = "../nasl-builtin-regex" } nasl-function-proc-macro = { path = "../nasl-function-proc-macro" } storage = { path = "../storage" } models = { path = "../models" } diff --git a/rust/nasl-builtin-std/src/lib.rs b/rust/nasl-builtin-std/src/lib.rs index 1d6e318ab..fb577188e 100644 --- a/rust/nasl-builtin-std/src/lib.rs +++ b/rust/nasl-builtin-std/src/lib.rs @@ -79,6 +79,7 @@ pub fn nasl_std_functions() -> nasl_builtin_utils::NaslFunctionRegister { .push_register(nasl_builtin_http::NaslHttp::default()) .push_register(nasl_builtin_network::socket::NaslSockets::default()) .push_register(nasl_builtin_network::network::Network) + .push_register(nasl_builtin_regex::RegularExpressions) .push_register(nasl_builtin_cryptographic::Cryptographic) .push_register(nasl_builtin_description::Description); diff --git a/rust/nasl-builtin-string/src/lib.rs b/rust/nasl-builtin-string/src/lib.rs index 95b500d71..71f3aab5a 100644 --- a/rust/nasl-builtin-string/src/lib.rs +++ b/rust/nasl-builtin-string/src/lib.rs @@ -235,7 +235,9 @@ fn hex(s: i64) -> String { /// /// The first positional argument must be a string, all other arguments are ignored. If either the no argument was given or the first positional is not a string, a error is returned. #[nasl_function] -fn hexstr_to_data(s: &str) -> Result, FunctionErrorKind> { +fn hexstr_to_data(s: NaslValue) -> Result, FunctionErrorKind> { + let s = s.to_string(); + let s = s.as_str(); decode_hex(s).map_err(|_| { FunctionErrorKind::WrongArgument(format!( "Expected an even-length string containing only 0-9a-fA-F, found '{}'", @@ -257,9 +259,13 @@ fn data_to_hexstr(bytes: Maybe<&[u8]>) -> Option { /// Length argument is required and can be a named argument or a positional argument. /// Data argument is an optional named argument and is taken to be "X" if not provided. #[nasl_function(maybe_named(length), named(data))] -fn crap(length: usize, data: Option<&str>) -> String { - let data = data.unwrap_or("X"); - data.repeat(length) +fn crap(length: usize, data: Option) -> String { + let data = match data { + Some(x) => x.to_string(), + None => "X".to_string(), + }; + + data.as_str().repeat(length) } /// NASL function to remove trailing whitespaces from a string @@ -276,9 +282,14 @@ fn chomp(s: StringOrData) -> String { /// The second positional argument is the *string* to search for. /// The optional third positional argument is an *int* containing an offset from where to start the search. #[nasl_function] -fn stridx(haystack: String, needle: String, offset: Option) -> i64 { +fn stridx(haystack: NaslValue, needle: NaslValue, offset: Option) -> i64 { + let h = haystack.to_string(); + let haystack = h.as_str(); + let n = needle.to_string(); + let needle = n.as_str(); + let offset = offset.unwrap_or(0); - match &haystack[offset..].find(&needle) { + match &haystack[offset..].find(needle) { Some(index) => *index as i64, None => -1, } @@ -296,7 +307,9 @@ fn display(register: &Register, configs: &Context) -> Result Option { +fn ord(s: NaslValue) -> Option { + let s = s.to_string(); + let s = s.as_str(); s.chars().next().map(|c| c as u8) } @@ -330,11 +343,16 @@ fn str_to_int(s: &str) -> i64 { /// 4rd positional argument (optional): end index in the original string at which to perform the replacement. #[nasl_function] fn insstr( - mut s: String, - to_insert: &str, + mut s: NaslValue, + to_insert: NaslValue, start: usize, end: Option, ) -> Result { + let mut s = s.to_string(); + + let insb = to_insert.to_string(); + let ins = insb.as_str(); + let end = end.unwrap_or(s.len()).min(s.len()); if start > end { return Err(FunctionErrorKind::WrongArgument(format!( @@ -342,7 +360,13 @@ fn insstr( start, end ))); } - s.replace_range(start..end, to_insert); + + if s.len() >= (end + 1) { + s.replace_range(start..(end + 1), ins); + } else { + s.replace_range(start..(end), ins); + } + Ok(s) } @@ -352,12 +376,21 @@ fn insstr( /// `pattern` contains the pattern to search for. /// The optional argument `icase` toggles case sensitivity. Default: false (case sensitive). If true, search is case insensitive. #[nasl_function(named(string, pattern, icase))] -fn match_(string: &str, pattern: &str, icase: Option) -> Result { +fn match_( + string: NaslValue, + pattern: NaslValue, + icase: Option, +) -> Result { let options = MatchOptions { case_sensitive: !icase.unwrap_or(false), require_literal_separator: false, require_literal_leading_dot: false, }; + let strb = string.to_string(); + let string = strb.as_str(); + let pattb = pattern.to_string(); + let pattern = pattb.as_str(); + Ok(Pattern::new(pattern) .map_err(|err| { FunctionErrorKind::WrongArgument(format!( @@ -368,11 +401,11 @@ fn match_(string: &str, pattern: &str, icase: Option) -> Result) -> Result, keep: Option) -> Vec { - let sep = sep.unwrap_or("\n"); +fn split(string: NaslValue, sep: Option, keep: Option) -> Vec { + let strb = string.to_string(); + let str = strb.as_str(); + + let separator: String; + if let Some(s) = sep { + separator = s.to_string(); + } else { + separator = "\n".to_string(); + } + + let sep_aux = separator.as_str(); + if keep.unwrap_or(true) { - string.split_inclusive(sep).map(String::from).collect() + str.split_inclusive(sep_aux).map(String::from).collect() } else { - string.split(sep).map(String::from).collect() + str.split(sep_aux).map(String::from).collect() } } @@ -399,10 +443,28 @@ fn split(string: &str, sep: Option<&str>, keep: Option) -> Vec { /// limits the number of replacements made to count. If left out /// or set to 0, there is no limit on the number of replacements. #[nasl_function(named(string, find, replace, count))] -fn replace(string: &str, find: &str, replace: Option<&str>, count: Option) -> String { +fn str_replace( + string: NaslValue, + find: NaslValue, + replace: Option, + count: Option, +) -> String { + let strb = string.to_string(); + let string = strb.as_str(); + + let findb = find.to_string(); + let find = findb.as_str(); + + let rep: String; + if let Some(r) = replace { + rep = r.to_string(); + } else { + rep = "".to_string(); + } + match count { - Some(count) if count > 0 => string.replacen(find, replace.unwrap_or(""), count), - _ => string.replace(find, replace.unwrap_or("")), + Some(count) if count > 0 => string.replacen(find, rep.as_str(), count), + _ => string.replace(find, rep.as_str()), } } @@ -413,8 +475,17 @@ fn replace(string: &str, find: &str, replace: Option<&str>, count: Option /// /// 1st positional argument: string to search in. /// 2nd positional argument: substring to search for. -fn strstr(string: &str, find: &str) -> Option<&str> { - string.find(find).map(|index| &string[index..]) +fn strstr(string: NaslValue, find: NaslValue) -> NaslValue { + let strb = string.to_string(); + let string = strb.as_str(); + + let findb = find.to_string(); + let find = findb.as_str(); + + if let Some(i) = string.find(find) { + return NaslValue::String(string[i..].to_string()); + } + NaslValue::Null } /// Returns found function for key or None when not found @@ -440,7 +511,7 @@ fn lookup(key: &str) -> Option { "insstr" => Some(insstr), "int" => Some(int), "split" => Some(split), - "replace" => Some(replace), + "str_replace" => Some(str_replace), "strstr" => Some(strstr), _ => None, } diff --git a/rust/nasl-builtin-string/tests/string.rs b/rust/nasl-builtin-string/tests/string.rs index 02d844fcc..dc9478a0a 100644 --- a/rust/nasl-builtin-string/tests/string.rs +++ b/rust/nasl-builtin-string/tests/string.rs @@ -49,6 +49,8 @@ mod tests { fn strlen() { check_ok("strlen(0x7B);", 0i64); check_ok("strlen('hallo');", 5i64); + check_ok("strlen('hallo\n');", 6i64); + check_ok(r#"strlen("hallo\n");"#, 7i64); } #[test] @@ -68,9 +70,13 @@ mod tests { #[test] fn crap() { + check_ok("crap(5);", "XXXXX"); check_ok("crap(5);", "XXXXX"); check_ok("crap(length: 5);", "XXXXX"); check_ok(r#"crap(data: "ab", length: 5);"#, "ababababab"); + check_ok(r#"crap(data: 'ab', length: 5);"#, "ababababab"); + check_ok(r#"crap(data: 'a\n', length: 2);"#, "a\na\n"); + check_ok(r#"crap(data: "a\n", length: 2);"#, "a\\na\\n"); } #[test] @@ -120,8 +126,11 @@ mod tests { check_ok(r#"ord("a");"#, 97); check_ok(r#"ord("b");"#, 98); check_ok(r#"ord("c");"#, 99); + check_ok(r#"ord("\n");"#, 92); + check_ok(r#"ord('\n');"#, 10); + check_ok(r#"ord("c");"#, 99); check_ok(r#"ord("");"#, Null); - check_err_matches!("ord(1);", WrongArgument { .. }); + check_ok("ord(1);", 49); check_err_matches!("ord();", MissingPositionalArguments { .. }); } @@ -159,9 +168,48 @@ mod tests { fn insstr() { check_ok(r#"insstr("foo bar", "rab", 4);"#, "foo rab"); check_ok(r#"insstr("foo bar", "rab", 4, 100);"#, "foo rab"); + check_ok(r#"insstr("foo bar", "rab", 4, 5);"#, "foo rabr"); check_err_matches!(r#"insstr("foo bar", "rab", 4, 0);"#, WrongArgument { .. }); } + #[test] + fn insstr_data_new_line() { + let code = r#" + insstr('foo\nbar', "123456", 4 ,5); + "#; + let register = Register::default(); + let mut binding = ContextFactory::default(); + binding + .functions + .push_executer(nasl_builtin_string::NaslString); + + let context = binding.build(Default::default(), Default::default()); + let mut parser = CodeInterpreter::new(code, register, &context); + assert_eq!( + parser.next(), + Some(Ok(NaslValue::String("foo\n123456r".to_string()))) + ); + } + + #[test] + fn insstr_string_new_line() { + let code = r#" + insstr("foo\nbar", "123456", 4 ,5); + "#; + let register = Register::default(); + let mut binding = ContextFactory::default(); + binding + .functions + .push_executer(nasl_builtin_string::NaslString); + + let context = binding.build(Default::default(), Default::default()); + let mut parser = CodeInterpreter::new(code, register, &context); + assert_eq!( + parser.next(), + Some(Ok(NaslValue::String("foo\\123456ar".to_string()))) + ); + } + #[test] fn int() { check_ok(r#"int("123");"#, 123); @@ -172,15 +220,74 @@ mod tests { } #[test] - fn split() { - check_ok( - r#"split("a\nb\nc");"#, - vec!["a\n".to_string(), "b\n".to_string(), "c".to_string()], + fn split_string_default_new_line() { + let code = r#" + split("a\nb\nc"); + "#; + let register = Register::default(); + let mut binding = ContextFactory::default(); + binding + .functions + .push_executer(nasl_builtin_string::NaslString); + + let context = binding.build(Default::default(), Default::default()); + let mut parser = CodeInterpreter::new(code, register, &context); + assert_eq!( + parser.next(), + Some(Ok(NaslValue::Array(vec![NaslValue::String( + "a\\nb\\nc".to_string() + )]))) ); - check_ok( - r#"split("a\nb\nc", keep: FALSE);"#, - vec!["a".to_string(), "b".to_string(), "c".to_string()], + } + + #[test] + fn split_data_default_new_line() { + let code = r#" + split('a\nb\nc'); + "#; + let register = Register::default(); + let mut binding = ContextFactory::default(); + binding + .functions + .push_executer(nasl_builtin_string::NaslString); + + let context = binding.build(Default::default(), Default::default()); + let mut parser = CodeInterpreter::new(code, register, &context); + assert_eq!( + parser.next(), + Some(Ok(NaslValue::Array(vec![ + NaslValue::String("a\n".to_string()), + NaslValue::String("b\n".to_string()), + NaslValue::String("c".to_string()), + ]))) + ); + } + + #[test] + fn split_data_default_new_line_no_keep() { + let code = r#" + split('a\nb\nc', keep: FALSE); + "#; + let register = Register::default(); + let mut binding = ContextFactory::default(); + binding + .functions + .push_executer(nasl_builtin_string::NaslString); + + let context = binding.build(Default::default(), Default::default()); + let mut parser = CodeInterpreter::new(code, register, &context); + assert_eq!( + parser.next(), + Some(Ok(NaslValue::Array(vec![ + NaslValue::String("a".to_string()), + NaslValue::String("b".to_string()), + NaslValue::String("c".to_string()), + ]))) ); + } + + #[test] + fn split() { check_ok( r#"split("a;b;c", sep: ";");"#, vec!["a;".to_string(), "b;".to_string(), "c".to_string()], @@ -191,19 +298,26 @@ mod tests { #[test] fn replace() { check_ok( - r#"replace(string: "abc", find: "b", replace: "foo");"#, + r#"str_replace(string: "abc", find: "b", replace: "foo");"#, "afooc", ); - check_err_matches!(r#"replace();"#, MissingArguments { .. }); - check_err_matches!(r#"replace(string: "abc");"#, MissingArguments { .. }); - check_ok(r#"replace(string: "abc", find: "b");"#, "ac"); - check_ok(r#"replace(string: "abcbd", find: "b", count: 1);"#, "acbd"); + check_err_matches!(r#"str_replace();"#, MissingArguments { .. }); + check_err_matches!(r#"str_replace(string: "abc");"#, MissingArguments { .. }); + check_ok(r#"str_replace(string: "abc", find: "b");"#, "ac"); + check_ok( + r#"str_replace(string: "abcbd", find: "b", count: 1);"#, + "acbd", + ); + check_ok(r#"str_replace(string: "ab\nc", find: "\n");"#, "abc"); + check_ok(r#"str_replace(string: 'ab\nc', find: '\n');"#, "abc"); + check_ok(r#"str_replace(string: 'ab\nc', find: "\n");"#, "ab\nc"); } #[test] fn strstr() { check_ok(r#"strstr("abc", "b");"#, "bc"); check_ok(r#"strstr("abcbd", "b");"#, "bcbd"); + check_ok(r#"strstr('a\rbcbd', '\rb');"#, "\rbcbd"); check_err_matches!(r#"strstr();"#, MissingPositionalArguments { .. }); check_err_matches!(r#"strstr("a");"#, MissingPositionalArguments { .. }); } diff --git a/rust/nasl-syntax/src/token.rs b/rust/nasl-syntax/src/token.rs index 36c6d6b96..ee6e0c753 100644 --- a/rust/nasl-syntax/src/token.rs +++ b/rust/nasl-syntax/src/token.rs @@ -566,18 +566,11 @@ impl<'a> Tokenizer<'a> { if self.cursor.is_eof() { Category::Unclosed(UnclosedCategory::String) } else { - let mut result = self.code[Range { + let result = self.code[Range { start, end: self.cursor.len_consumed(), }] .to_owned(); - result = result.replace(r"\n", "\n"); - result = result.replace(r"\\", "\\"); - result = result.replace(r#"\""#, "\""); - result = result.replace(r"\'", "'"); - result = result.replace(r"\r", "\r"); - result = result.replace(r"\t", "\t"); - // skip "" self.cursor.advance(); Category::String(result) } @@ -605,6 +598,11 @@ impl<'a> Tokenizer<'a> { }] .to_owned(); raw_str = raw_str.replace(r#"\""#, "\""); + raw_str = raw_str.replace(r#"\n"#, "\n"); + raw_str = raw_str.replace(r"\\", "\\"); + raw_str = raw_str.replace(r"\'", "'"); + raw_str = raw_str.replace(r"\r", "\r"); + raw_str = raw_str.replace(r"\t", "\t"); self.cursor.advance(); Category::Data(raw_str.as_bytes().to_vec()) } @@ -897,7 +895,7 @@ mod tests { #[test] fn quotable_string() { verify_tokens!( - "'Hello \\'you\\'!'", + r#"'Hello \\\'you\\\'!'"#, ["[72, 101, 108, 108, 111, 32, 92, 39, 121, 111, 117, 92, 39, 33]"] ); verify_tokens!("'Hello \\'you\\'!\\'", ["UnclosedData"]); @@ -951,7 +949,7 @@ mod tests { #[test] fn string_quoting() { verify_tokens!( - r"'webapps\\appliance\\'", + r"'webapps\\\\appliance\\\\'", [ r"[119, 101, 98, 97, 112, 112, 115, 92, 92, 97, 112, 112, 108, 105, 97, 110, 99, 101, 92, 92]", ] From 95fd30e713a35c2fee946229328ead022c4a77d4 Mon Sep 17 00:00:00 2001 From: Greenbone Bot Date: Wed, 4 Sep 2024 09:55:34 +0000 Subject: [PATCH 2/7] Automated commit: change version from 23.8.5 -> 23.9.0 --- CMakeLists.txt | 2 +- charts/openvasd/Chart.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c9df5485b..8006bf640 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.8.5 + VERSION 23.9.0 LANGUAGES C) if (POLICY CMP0005) diff --git a/charts/openvasd/Chart.yaml b/charts/openvasd/Chart.yaml index 6a77a05d4..b98048fcf 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.8.5" +appVersion: "23.9.0" From e6757506401d3d52d2181d585c33794c4b2230b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Nicola?= Date: Thu, 5 Sep 2024 09:05:32 +0200 Subject: [PATCH 3/7] Fix: change HEAD endpoint (#1695) - change `HEAD /scans` to return 204 when a client certificate is known and 401 when not other unknown paths for HEAD return 200 OK with an empty body as before. Jira SC-1125 --- rust/doc/openapi.yml | 37 +++++++++++++++++++++++++++ rust/openvasd/src/controller/entry.rs | 11 +++++--- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/rust/doc/openapi.yml b/rust/doc/openapi.yml index 6f435b283..5cc2cc825 100644 --- a/rust/doc/openapi.yml +++ b/rust/doc/openapi.yml @@ -70,6 +70,43 @@ paths: schema: type: "string" description: "Header" + /scans: + head: + description: "Get the response header. It contains the API version, feed version and available authentication methods." + operationId: "get_info" + tags: + - "general" + responses: + "204": + headers: + api-version: + description: "Comma separated list of available API versions" + schema: + type: "string" + feed-version: + description: "The version of the VT feed" + schema: + type: "string" + authentication: + description: "Supported authentication methods" + schema: + type: "string" + description: "Authenticated and authorized" + "401": + headers: + api-version: + description: "Comma separated list of available API versions" + schema: + type: "string" + feed-version: + description: "The version of the VT feed" + schema: + type: "string" + authentication: + description: "Supported authentication methods" + schema: + type: "string" + description: "Unauthorized. Required or invalid client certificates" /health/alive: get: diff --git a/rust/openvasd/src/controller/entry.rs b/rust/openvasd/src/controller/entry.rs index c93f111b1..9f2dce13e 100644 --- a/rust/openvasd/src/controller/entry.rs +++ b/rust/openvasd/src/controller/entry.rs @@ -22,6 +22,7 @@ use crate::{ }; use models::scanner::*; +#[derive(PartialEq, Eq)] enum HealthOpts { /// Ready Ready, @@ -31,6 +32,7 @@ enum HealthOpts { Alive, } /// The supported paths of openvasd +#[derive(PartialEq, Eq)] enum KnownPaths { /// /scans/{id} Scans(Option), @@ -185,11 +187,11 @@ where let cid = self.cid.clone(); Box::pin(async move { use KnownPaths::*; - // on head requests we just return an empty response without checking the api key - if req.method() == Method::HEAD { + let kp = KnownPaths::from_path(req.uri().path(), &ctx.mode); + // on head requests we just return an empty response, except for /scans + if req.method() == Method::HEAD && kp != KnownPaths::Scans(None) { return Ok(ctx.response.empty(hyper::StatusCode::OK)); } - let kp = KnownPaths::from_path(req.uri().path(), &ctx.mode); let cid: Option = { match &*cid { ClientIdentifier::Disabled => { @@ -255,6 +257,9 @@ where "process call", ); match (req.method(), kp) { + (&Method::HEAD, Scans(None)) => { + Ok(ctx.response.empty(hyper::StatusCode::NO_CONTENT)) + } (&Method::GET, Health(HealthOpts::Alive)) | (&Method::GET, Health(HealthOpts::Started)) => { Ok(ctx.response.empty(hyper::StatusCode::OK)) From b78ec229d9447235535948e635293acf9945784f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20Kr=C3=A4mer?= Date: Thu, 5 Sep 2024 11:33:15 +0200 Subject: [PATCH 4/7] Use nasl function macro for builtin socket functions (#1697) * Use proc macro nasl_function for sockets * Fix bind local udp socket depending on target IP version * Fix lookup kdc hostname * Ensure and document unsafe code block for send * Some minor improvements --- rust/Cargo.lock | 13 ++ rust/nasl-builtin-network/Cargo.toml | 1 + rust/nasl-builtin-network/src/lib.rs | 75 +------- rust/nasl-builtin-network/src/network.rs | 5 +- rust/nasl-builtin-network/src/socket.rs | 214 +++++++++++++---------- 5 files changed, 140 insertions(+), 168 deletions(-) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index ab1a9eba8..e1da64045 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -817,6 +817,18 @@ dependencies = [ "syn 2.0.72", ] +[[package]] +name = "dns-lookup" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5766087c2235fec47fafa4cfecc81e494ee679d0fd4a59887ea0919bfb0e4fc" +dependencies = [ + "cfg-if", + "libc", + "socket2 0.5.7", + "windows-sys 0.48.0", +] + [[package]] name = "dunce" version = "1.0.4" @@ -2032,6 +2044,7 @@ dependencies = [ name = "nasl-builtin-network" version = "0.1.0" dependencies = [ + "dns-lookup", "libc", "nasl-builtin-utils", "nasl-function-proc-macro", diff --git a/rust/nasl-builtin-network/Cargo.toml b/rust/nasl-builtin-network/Cargo.toml index de4ec1baa..7902aae3b 100644 --- a/rust/nasl-builtin-network/Cargo.toml +++ b/rust/nasl-builtin-network/Cargo.toml @@ -11,6 +11,7 @@ nasl-function-proc-macro = { path = "../nasl-function-proc-macro" } nasl-syntax = { path = "../nasl-syntax" } storage = { path = "../storage" } +dns-lookup = "2.0" libc = "0.2" rustls = "0.23.5" rustls-pemfile = "2.1" diff --git a/rust/nasl-builtin-network/src/lib.rs b/rust/nasl-builtin-network/src/lib.rs index 035b61753..30f25fbcf 100644 --- a/rust/nasl-builtin-network/src/lib.rs +++ b/rust/nasl-builtin-network/src/lib.rs @@ -2,9 +2,9 @@ // // SPDX-License-Identifier: GPL-2.0-or-later -use std::fmt::Display; +use std::{fmt::Display, net::IpAddr}; -use nasl_builtin_utils::{Context, FunctionErrorKind, Register}; +use nasl_builtin_utils::{Context, FunctionErrorKind}; use nasl_syntax::NaslValue; use storage::Field; @@ -14,14 +14,14 @@ pub mod socket; // 512 Bytes are typically supported by network devices. The ip header maximum size is 60 and a UDP // header contains 8 bytes, which must be subtracted from the max size for UDP packages. -// TODO: Calculate the MTU dynamically const MTU: usize = 512 - 60 - 8; /// Standard port for networking functions -/// @return none const DEFAULT_PORT: u16 = 33435; -pub fn mtu() -> usize { +// Get the max MTU possible for network communication +// TODO: Calculate the MTU dynamically +pub fn mtu(_: IpAddr) -> usize { MTU } @@ -74,47 +74,6 @@ impl Display for OpenvasEncaps { } } -fn get_named_value(r: &Register, name: &str) -> Result { - match r.named(name) { - Some(x) => match x { - nasl_builtin_utils::ContextType::Function(_, _) => Err( - FunctionErrorKind::WrongArgument(format!("{name} is a function")), - ), - nasl_builtin_utils::ContextType::Value(val) => Ok(val.to_owned()), - }, - None => Err(FunctionErrorKind::MissingArguments(vec![name.to_string()])), - } -} - -fn get_usize(r: &Register, name: &str) -> Result { - match get_named_value(r, name)? { - NaslValue::Number(num) => { - if num < 0 { - return Err(FunctionErrorKind::WrongArgument(format!( - "Argument {name} must be >= 0" - ))); - } - Ok(num as usize) - } - _ => Err(FunctionErrorKind::WrongArgument( - "Wrong type for argument, expected a number".to_string(), - )), - } -} - -fn get_data(r: &Register) -> Result, FunctionErrorKind> { - Ok((get_named_value(r, "data")?).into()) -} - -fn get_opt_int(r: &Register, name: &str) -> Option { - get_named_value(r, name) - .map(|val| match val { - NaslValue::Number(len) => Some(len), - _ => None, - }) - .unwrap_or_default() -} - pub fn get_kb_item(context: &Context, name: &str) -> Result, FunctionErrorKind> { context .retriever() @@ -129,30 +88,6 @@ pub fn get_kb_item(context: &Context, name: &str) -> Result, F .map_err(|e| e.into()) } -pub fn get_pos_port(r: &Register) -> Result { - match r - .positional() - .first() - .ok_or(FunctionErrorKind::MissingPositionalArguments { - expected: 1, - got: 0, - })? { - NaslValue::Number(port) => { - if *port < 0 || *port > 65535 { - return Err(FunctionErrorKind::WrongArgument(format!( - "{} is not a valid port number", - *port - ))); - } - Ok(*port as u16) - } - x => Err(FunctionErrorKind::WrongArgument(format!( - "{} is not a valid port number", - x - ))), - } -} - pub fn verify_port(port: i64) -> Result { if !(0..=65535).contains(&port) { return Err(FunctionErrorKind::WrongArgument(format!( diff --git a/rust/nasl-builtin-network/src/network.rs b/rust/nasl-builtin-network/src/network.rs index ab7025ed0..bae241d44 100644 --- a/rust/nasl-builtin-network/src/network.rs +++ b/rust/nasl-builtin-network/src/network.rs @@ -42,8 +42,9 @@ fn this_host_name() -> String { /// get the maximum transition unit for the scanned host #[nasl_function] -fn get_mtu() -> i64 { - mtu() as i64 +fn get_mtu(context: &Context) -> Result { + let target = ipstr2ipaddr(context.target())?; + Ok(mtu(target) as i64) } /// check if the currently scanned host is the localhost diff --git a/rust/nasl-builtin-network/src/socket.rs b/rust/nasl-builtin-network/src/socket.rs index d303222ab..dfcbcd389 100644 --- a/rust/nasl-builtin-network/src/socket.rs +++ b/rust/nasl-builtin-network/src/socket.rs @@ -5,14 +5,16 @@ use std::{ fs, io::{self, BufReader, Read, Write}, - net::{TcpStream, ToSocketAddrs, UdpSocket}, + net::{IpAddr, SocketAddr, TcpStream, ToSocketAddrs, UdpSocket}, os::fd::AsRawFd, sync::{Arc, RwLock}, thread::sleep, time::{Duration, SystemTime}, }; +use dns_lookup::lookup_host; use nasl_builtin_utils::{error::FunctionErrorKind, Context, Register}; +use nasl_function_proc_macro::nasl_function; use nasl_syntax::NaslValue; use pkcs8::der::Decode; use rustls::{ @@ -20,7 +22,11 @@ use rustls::{ ClientConfig, ClientConnection, RootCertStore, Stream, }; -use crate::{get_kb_item, get_pos_port, mtu, OpenvasEncaps}; +use crate::{ + get_kb_item, mtu, + network_utils::{bind_local_socket, ipstr2ipaddr}, + verify_port, OpenvasEncaps, +}; // Number of times to resend a UDP packet, when no response is received const NUM_TIMES_TO_RESEND: usize = 5; @@ -58,9 +64,9 @@ struct TCPConnection { } impl TCPConnection { - /// Send data on a TCP connection using the libc send function. This function is unsafe, because - /// the provided length can be larger than the actual data length, which can lead to a - /// segmentation fault. + /// Send data on a TCP connection using the libc send function. + /// To ensure safety of the function, the caller must ensure, that the given length does not + /// exceed the length of the given data data. unsafe fn send( &self, mut data: &[u8], @@ -70,11 +76,12 @@ impl TCPConnection { let fd = self.socket.as_raw_fd(); let mut ret = 0; while !data.is_empty() { - let n = unsafe { libc::send(fd, data.as_ptr() as *const libc::c_void, len, flags) }; + let n = + unsafe { libc::send(fd, data.as_ptr() as *const libc::c_void, len - ret, flags) }; if n < 0 { return Err(io::Error::last_os_error().into()); } - ret += n; + ret += n as usize; data = &data[n as usize..]; } Ok(NaslValue::Number(ret as i64)) @@ -87,9 +94,9 @@ struct UDPConnection { } impl UDPConnection { - /// Send data on a UDP connection using the libc send function. This function is unsafe, because - /// the provided length can be larger than the actual data length, which can lead to a - /// segmentation fault. + /// Send data on a UDP connection using the libc send function. + /// To ensure safety of the function, the caller must ensure, that the given length does not + /// exceed the length of the given data data. unsafe fn send( &mut self, data: &[u8], @@ -98,11 +105,18 @@ impl UDPConnection { ) -> Result { let fd = self.socket.as_raw_fd(); - if len > mtu() { - return Err(FunctionErrorKind::Dirty(format!( - "udp data exceeds the maximum length of {}", - mtu() - ))); + let ip = self.socket.peer_addr()?.ip(); + + let mtu = mtu(ip); + + if len > mtu { + return Err(FunctionErrorKind::Diagnostic( + format!( + "udp data of size {} exceeds the maximum length of {}", + len, mtu + ), + None, + )); } let n = libc::send(fd, data.as_ptr() as *const libc::c_void, len, flags); @@ -133,9 +147,22 @@ pub struct NaslSockets { } impl NaslSockets { - fn open_udp(addr: &str, port: u16) -> Result { - let socket = UdpSocket::bind("0.0.0.0:0")?; - socket.connect(format!("{addr}:{port}"))?; + fn resolve_socket_addr(addr: IpAddr, port: u16) -> Result { + (addr, port) + .to_socket_addrs()? + .next() + .ok_or(FunctionErrorKind::Diagnostic( + format!( + "the given address and port do not correspond to a valid address: {addr}:{port}" + ), + None, + )) + } + + fn open_udp(addr: IpAddr, port: u16) -> Result { + let sock_addr = Self::resolve_socket_addr(addr, port)?; + let socket = bind_local_socket(&sock_addr)?; + socket.connect(sock_addr)?; socket.set_read_timeout(Some(Duration::from_secs(1)))?; Ok(NaslSocket::Udp(UDPConnection { socket, @@ -144,18 +171,14 @@ impl NaslSockets { } fn open_tcp( - ip: &str, + addr: IpAddr, port: u16, bufsz: Option, timeout: Duration, tls_config: Option<&TLSConfig>, ) -> Result { // Resolve Address and Port to SocketAddr - let sock = format!("{ip}:{port}").to_socket_addrs()?.next().ok_or( - FunctionErrorKind::WrongArgument(format!( - "the given address and port do not correspond to a valid address: {ip}:{port}" - )), - )?; + let sock_addr = Self::resolve_socket_addr(addr, port)?; // Create Vec depending of buffer size let buffer = if let Some(bufsz) = bufsz { if bufsz > 0 { @@ -167,7 +190,7 @@ impl NaslSockets { None }; - let socket = TcpStream::connect_timeout(&sock, timeout)?; + let socket = TcpStream::connect_timeout(&sock_addr, timeout)?; // Unwrap, because it cannot fail socket @@ -213,34 +236,27 @@ impl NaslSockets { } /// Close a given file descriptor taken as an unnamed argument. - fn close(&self, r: &Register, _: &Context) -> Result { - let args = r.positional(); - let socket = match args.first() { - Some(x) => match x { - NaslValue::Number(x) => { - if *x < 0 { - return Err(FunctionErrorKind::WrongArgument( - "Socket FD is smaller than 0".to_string(), - )); - } - *x as usize - } - _ => { - return Err(FunctionErrorKind::WrongArgument( - "Argument has wrong type, expected a Number".to_string(), - )) - } - }, + #[nasl_function] + fn close(&self, socket_fd: usize) -> Result { + let mut handles = self.handles.write().unwrap(); + match handles.handles.get_mut(socket_fd) { + Some(NaslSocket::Close) => { + return Err(FunctionErrorKind::Diagnostic( + "the given socket FD is already closed".to_string(), + None, + )) + } + Some(socket) => { + *socket = NaslSocket::Close; + handles.closed_fd.push(socket_fd); + } None => { - return Err(FunctionErrorKind::MissingPositionalArguments { - expected: 1, - got: args.len(), - }) + return Err(FunctionErrorKind::Diagnostic( + "the given socket FD does not exist".to_string(), + None, + )) } - }; - let mut handles = self.handles.write().unwrap(); - handles.handles[socket] = NaslSocket::Close; - handles.closed_fd.push(socket); + } Ok(NaslValue::Null) } @@ -259,15 +275,19 @@ impl NaslSockets { /// - option: is the flags for the send() system call. You should not use a raw numeric value here. /// /// On success the number of sent bytes is returned. - fn send(&self, r: &Register, _: &Context) -> Result { - let socket = super::get_usize(r, "socket")?; - let data = super::get_data(r)?; - let flags = super::get_opt_int(r, "option"); - let len = if let Some(len) = super::get_opt_int(r, "length") { - if len < 1 { + #[nasl_function(named(socket, data, flags, len))] + fn send( + &self, + socket: usize, + data: &[u8], + flags: Option, + len: Option, + ) -> Result { + let len = if let Some(len) = len { + if len < 1 || len > data.len() { data.len() } else { - len as usize + len } } else { data.len() @@ -336,14 +356,17 @@ impl NaslSockets { /// - length the number of bytes that you want to read at most. recv may return before length bytes have been read: as soon as at least one byte has been received, the timeout is lowered to 1 second. If no data is received during that time, the function returns the already read data; otherwise, if the full initial timeout has not been reached, a 1 second timeout is re-armed and the script tries to receive more data from the socket. This special feature was implemented to get a good compromise between reliability and speed when openvas-scanner talks to unknown or complex protocols. Two other optional named integer arguments can twist this behavior: /// - min is the minimum number of data that must be read in case the “magic read function” is activated and the timeout is lowered. By default this is 0. It works together with length. More info https://lists.archive.carbon60.com/nessus/devel/13796 /// - timeout can be changed from the default. - fn recv(&self, r: &Register, _: &Context) -> Result { - let socket = super::get_usize(r, "socket")?; - let len = super::get_usize(r, "length")?; - // TODO: process min for magic read function - let min = super::get_opt_int(r, "min") - .map(|x| if x <= 0 { len } else { x as usize }) + #[nasl_function(named(socket, len, min, timeout))] + fn recv( + &self, + socket: usize, + len: usize, + min: Option, + timeout: Option, + ) -> Result { + let min = min + .map(|min| if min < 0 { len } else { min as usize }) .unwrap_or(len); - let timeout = super::get_opt_int(r, "timeout"); let mut data = vec![0; len]; let mut ret = Ok(NaslValue::Null); @@ -424,11 +447,8 @@ impl NaslSockets { /// - Secret/kdc_hostname /// - Secret/kdc_port /// - Secret/kdc_use_tcp - fn open_sock_kdc( - &self, - _: &Register, - context: &Context, - ) -> Result { + #[nasl_function] + fn open_sock_kdc(&self, context: &Context) -> Result { let hostname = match get_kb_item(context, "Secret/kdc_hostname")? { Some(x) => Ok(x.to_string()), None => Err(FunctionErrorKind::Diagnostic( @@ -437,6 +457,17 @@ impl NaslSockets { )), }?; + let ip = lookup_host(&hostname) + .map_err(|_| { + FunctionErrorKind::Diagnostic(format!("unable to lookup hostname {hostname}"), None) + })? + .into_iter() + .next() + .ok_or(FunctionErrorKind::Diagnostic( + format!("No IP found for hostname {hostname}"), + None, + ))?; + let port = get_kb_item(context, "Secret/kdc_port")?; let port = match port { @@ -465,8 +496,8 @@ impl NaslSockets { .unwrap_or(false); let socket = match use_tcp { - true => Self::open_tcp(&hostname, port, None, Duration::from_secs(30), None), - false => Self::open_udp(&hostname, port), + true => Self::open_tcp(ip, port, None, Duration::from_secs(30), None), + false => Self::open_udp(ip, port), }?; let ret = self.add(socket); @@ -490,21 +521,20 @@ impl NaslSockets { /// - priority A string value with priorities for an TLS encapsulation. For the syntax of the /// priority string see the GNUTLS manual. This argument is only used in ENCAPS_TLScustom /// encapsulation. + #[nasl_function(named(timeout, transport, bufsz))] fn open_sock_tcp( &self, - register: &Register, context: &Context, + port: i64, + timeout: Option, + transport: Option, + bufsz: Option, + // TODO: Extract information from custom priority string + // priority: Option<&str>, ) -> Result { // Get port - let port = get_pos_port(register)?; - let timeout = super::get_opt_int(register, "timeout"); - let transport = super::get_opt_int(register, "transport").unwrap_or(-1); - // TODO: Extract information from custom priority string - // let _priority = super::get_named_value(register, "priority") - // .ok() - // .map(|val| val.to_string()); - let bufsz = - super::get_opt_int(register, "bufsz").and_then(|x| if x < 0 { None } else { Some(x) }); + let port = verify_port(port)?; + let transport = transport.unwrap_or(-1); let addr = context.target(); if addr.is_empty() { @@ -599,7 +629,8 @@ impl NaslSockets { timeout: Duration, tls_config: Option, ) -> Result { - let mut retry = get_kb_item(context, "timeout_retry")? + let addr = ipstr2ipaddr(addr)?; + let mut retry = super::get_kb_item(context, "timeout_retry")? .map(|val| match val { NaslValue::String(val) => val.parse::().unwrap_or_default(), NaslValue::Number(val) => val, @@ -735,19 +766,10 @@ impl NaslSockets { } /// Open a UDP socket to the target host - fn open_sock_udp( - &self, - register: &Register, - context: &Context, - ) -> Result { - let port = get_pos_port(register)?; - let addr = context.target(); - - if addr.is_empty() { - return Err(FunctionErrorKind::Dirty( - "A target must be specified to open a socket".to_string(), - )); - } + #[nasl_function] + fn open_sock_udp(&self, context: &Context, port: i64) -> Result { + let port = verify_port(port)?; + let addr = ipstr2ipaddr(context.target())?; let socket = Self::open_udp(addr, port)?; let fd = self.add(socket); From 48ea362ec5289d1065fec7e49d45a3ebfec8ffac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Nicola?= Date: Thu, 5 Sep 2024 11:40:56 +0200 Subject: [PATCH 5/7] Add: nasl builtin functions for kb manipulation (#1703) * use nasl function builtin macro for kb nasl functions * Add: nasl builtin functions for kb manipulation - replace_kb_item() - get_kb_list() --- rust/Cargo.lock | 1 + rust/nasl-builtin-knowledge-base/Cargo.toml | 1 + rust/nasl-builtin-knowledge-base/README.md | 7 +- rust/nasl-builtin-knowledge-base/src/lib.rs | 99 ++++++++++++++------ rust/nasl-builtin-knowledge-base/tests/kb.rs | 37 ++++++++ rust/openvasd/src/feed.rs | 8 ++ rust/storage/src/item.rs | 9 ++ rust/storage/src/lib.rs | 42 ++++++++- 8 files changed, 169 insertions(+), 35 deletions(-) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index e1da64045..e658aa3de 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -2022,6 +2022,7 @@ name = "nasl-builtin-knowledge-base" version = "0.1.0" dependencies = [ "nasl-builtin-utils", + "nasl-function-proc-macro", "nasl-interpreter", "nasl-syntax", "storage", diff --git a/rust/nasl-builtin-knowledge-base/Cargo.toml b/rust/nasl-builtin-knowledge-base/Cargo.toml index 5b9302369..78ae0b166 100644 --- a/rust/nasl-builtin-knowledge-base/Cargo.toml +++ b/rust/nasl-builtin-knowledge-base/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] nasl-builtin-utils = {path = "../nasl-builtin-utils"} nasl-syntax = {path = "../nasl-syntax"} +nasl-function-proc-macro = {path = "../nasl-function-proc-macro"} storage = {path = "../storage"} [dev-dependencies] diff --git a/rust/nasl-builtin-knowledge-base/README.md b/rust/nasl-builtin-knowledge-base/README.md index 8cd180bdf..bf6d2caf4 100644 --- a/rust/nasl-builtin-knowledge-base/README.md +++ b/rust/nasl-builtin-knowledge-base/README.md @@ -2,9 +2,8 @@ - set_kb_item - get_kp_item - -## Missing -- get_host_kb_index - get_kb_list -- index - replace_kb_item + +## Missing +- get_host_kb_index: Do not apply. Redis specific and currently not used in any script diff --git a/rust/nasl-builtin-knowledge-base/src/lib.rs b/rust/nasl-builtin-knowledge-base/src/lib.rs index d8bea2f7c..1f0f9d86f 100644 --- a/rust/nasl-builtin-knowledge-base/src/lib.rs +++ b/rust/nasl-builtin-knowledge-base/src/lib.rs @@ -4,26 +4,32 @@ use std::time::{SystemTime, UNIX_EPOCH}; -use nasl_builtin_utils::{error::FunctionErrorKind, get_named_parameter, NaslFunction}; +use nasl_builtin_utils::{error::FunctionErrorKind, NaslFunction}; use storage::{Field, Kb, Retrieve}; use nasl_builtin_utils::{Context, Register}; +use nasl_function_proc_macro::nasl_function; use nasl_syntax::NaslValue; -/// NASL function to set a knowledge base -fn set_kb_item(register: &Register, c: &Context) -> Result { - let name = get_named_parameter(register, "name", true)?; - let value = get_named_parameter(register, "value", true)?; - let expires = match get_named_parameter(register, "expires", false) { - Ok(NaslValue::Number(x)) => Some(*x), - Ok(NaslValue::Exit(0)) => None, - Ok(x) => { +/// NASL function to set a value under name in a knowledge base +/// Only pushes unique values for the given name. +#[nasl_function(named(name, value, expires))] +fn set_kb_item( + name: NaslValue, + value: NaslValue, + expires: Option, + c: &Context, +) -> Result { + let expires = match expires { + Some(NaslValue::Number(x)) => Some(x), + Some(NaslValue::Exit(0)) => None, + None => None, + Some(x) => { return Err(FunctionErrorKind::Diagnostic( format!("expected expires to be a number but is {x}."), None, )) } - Err(e) => return Err(e), } .map(|seconds| { let start = SystemTime::now(); @@ -46,26 +52,57 @@ fn set_kb_item(register: &Register, c: &Context) -> Result Result { - match register.positional() { - [x] => c - .retriever() - .retrieve(c.key(), Retrieve::KB(x.to_string())) - .map(|r| { - r.into_iter() - .filter_map(|x| match x { - Field::NVT(_) | Field::NotusAdvisory(_) | Field::Result(_) => None, - Field::KB(kb) => Some(kb.value.into()), - }) - .collect::>() - }) - .map(NaslValue::Fork) - .map_err(|e| e.into()), - x => Err(FunctionErrorKind::Diagnostic( - format!("expected one positional argument but got: {}", x.len()), - None, - )), - } +#[nasl_function] +fn get_kb_item(key: &str, c: &Context) -> Result { + c.retriever() + .retrieve(c.key(), Retrieve::KB(key.to_string())) + .map(|r| { + r.into_iter() + .filter_map(|x| match x { + Field::NVT(_) | Field::NotusAdvisory(_) | Field::Result(_) => None, + Field::KB(kb) => Some(kb.value.into()), + }) + .collect::>() + }) + .map(NaslValue::Fork) + .map_err(|e| e.into()) +} + +/// NASL function to replace a kb list +#[nasl_function(named(name, value, expires))] +fn replace_kb_item( + name: NaslValue, + value: NaslValue, + c: &Context, +) -> Result { + c.dispatcher() + .dispatch_replace( + c.key(), + Field::KB(Kb { + key: name.to_string(), + value: value.clone().as_primitive(), + expire: None, + }), + ) + .map(|_| NaslValue::Null) + .map_err(|e| e.into()) +} + +/// NASL function to retrieve an item in a KB. +#[nasl_function(named(name, value, expires))] +fn get_kb_list(key: NaslValue, c: &Context) -> Result { + c.retriever() + .retrieve(c.key(), Retrieve::KB(key.to_string())) + .map(|r| { + r.into_iter() + .filter_map(|x| match x { + Field::NVT(_) | Field::NotusAdvisory(_) | Field::Result(_) => None, + Field::KB(kb) => Some(kb.value.into()), + }) + .collect::>() + }) + .map(NaslValue::Array) + .map_err(|e| e.into()) } /// Returns found function for key or None when not found @@ -73,6 +110,8 @@ pub fn lookup(key: &str) -> Option { match key { "set_kb_item" => Some(set_kb_item), "get_kb_item" => Some(get_kb_item), + "get_kb_list" => Some(get_kb_list), + "replace_kb_item" => Some(replace_kb_item), _ => None, } } diff --git a/rust/nasl-builtin-knowledge-base/tests/kb.rs b/rust/nasl-builtin-knowledge-base/tests/kb.rs index 207bb5a9b..d39f635b8 100644 --- a/rust/nasl-builtin-knowledge-base/tests/kb.rs +++ b/rust/nasl-builtin-knowledge-base/tests/kb.rs @@ -36,4 +36,41 @@ mod tests { assert_eq!(parser.next(), Some(Ok(NaslValue::Number(1)))); assert!(matches!(parser.next(), Some(Err(_)))); } + #[test] + fn get_kb_list() { + let code = r#" + set_kb_item(name: "test", value: 1); + set_kb_item(name: "test", value: 2); + get_kb_list("test"); + + "#; + let register = Register::default(); + let binding = ContextFactory::default(); + let context = binding.build(Default::default(), Default::default()); + let mut parser = CodeInterpreter::new(code, register, &context); + assert_eq!(parser.next(), Some(Ok(NaslValue::Null))); + assert_eq!(parser.next(), Some(Ok(NaslValue::Null))); + assert_eq!( + parser.next(), + Some(Ok(NaslValue::Array(vec![ + NaslValue::Number(1), + NaslValue::Number(2) + ]))) + ); + } + #[test] + fn replace_kb_item() { + let code = r#" + set_kb_item(name: "test", value: 1); + replace_kb_item(name: "test", value: 2); + get_kb_item("test"); + "#; + let register = Register::default(); + let binding = ContextFactory::default(); + let context = binding.build(Default::default(), Default::default()); + let mut parser = CodeInterpreter::new(code, register, &context); + assert_eq!(parser.next(), Some(Ok(NaslValue::Null))); + assert_eq!(parser.next(), Some(Ok(NaslValue::Null))); + assert_eq!(parser.next(), Some(Ok(NaslValue::Number(2)))); + } } diff --git a/rust/openvasd/src/feed.rs b/rust/openvasd/src/feed.rs index 02f2256cc..3e1e0fa52 100644 --- a/rust/openvasd/src/feed.rs +++ b/rust/openvasd/src/feed.rs @@ -95,6 +95,14 @@ impl storage::Dispatcher for FeedIdentifier { Ok(()) } + fn dispatch_replace( + &self, + _: &ContextKey, + _scope: storage::Field, + ) -> Result<(), storage::StorageError> { + Ok(()) + } + fn on_exit(&self) -> Result<(), storage::StorageError> { Ok(()) } diff --git a/rust/storage/src/item.rs b/rust/storage/src/item.rs index 797701a54..b16eb50d6 100644 --- a/rust/storage/src/item.rs +++ b/rust/storage/src/item.rs @@ -922,6 +922,15 @@ where } } + fn dispatch_replace(&self, key: &ContextKey, scope: Field) -> Result<(), StorageError> { + match scope { + Field::NVT(nvt) => self.store_nvt_field(nvt), + Field::KB(kb) => self.dispatcher.dispatch_kb(key, kb), + Field::NotusAdvisory(adv) => self.dispatcher.dispatch_advisory(key.as_ref(), *adv), + Field::Result(result) => self.dispatch(key, Field::Result(result)), + } + } + fn on_exit(&self) -> Result<(), StorageError> { let mut data = Arc::as_ref(&self.nvt) .lock() diff --git a/rust/storage/src/lib.rs b/rust/storage/src/lib.rs index ee3164b8f..b1ae18457 100644 --- a/rust/storage/src/lib.rs +++ b/rust/storage/src/lib.rs @@ -223,6 +223,10 @@ pub trait Dispatcher: Sync + Send { /// A key is usually a OID that was given when starting a script but in description run it is the filename. fn dispatch(&self, key: &ContextKey, scope: Field) -> Result<(), StorageError>; + /// Replace all fields under a key with the new field + /// + fn dispatch_replace(&self, key: &ContextKey, scope: Field) -> Result<(), StorageError>; + /// On exit is called when a script exit /// /// Some database require a cleanup therefore this method is called when a script finishes. @@ -256,6 +260,10 @@ where self.as_ref().dispatch(key, scope) } + fn dispatch_replace(&self, key: &ContextKey, scope: Field) -> Result<(), StorageError> { + self.as_ref().dispatch_replace(key, scope) + } + fn on_exit(&self) -> Result<(), StorageError> { self.as_ref().on_exit() } @@ -354,7 +362,25 @@ impl DefaultDispatcher { let mut data = self.kbs.as_ref().write()?; if let Some(scan_entry) = data.get_mut(scan_id) { if let Some(kb_entry) = scan_entry.get_mut(&kb.key) { - kb_entry.push(kb); + if kb_entry.iter().position(|x| x.value == kb.value).is_none() { + kb_entry.push(kb); + }; + } else { + scan_entry.insert(kb.key.clone(), vec![kb]); + } + } else { + let mut scan_entry = HashMap::new(); + scan_entry.insert(kb.key.clone(), vec![kb]); + data.insert(scan_id.to_string(), scan_entry); + } + Ok(()) + } + + fn replace_kb(&self, scan_id: &str, kb: Kb) -> Result<(), StorageError> { + let mut data = self.kbs.as_ref().write()?; + if let Some(scan_entry) = data.get_mut(scan_id) { + if let Some(kb_entry) = scan_entry.get_mut(&kb.key) { + *kb_entry = vec![kb]; } else { scan_entry.insert(kb.key.clone(), vec![kb]); } @@ -409,6 +435,20 @@ impl Dispatcher for DefaultDispatcher { Ok(()) } + fn dispatch_replace(&self, key: &ContextKey, scope: Field) -> Result<(), StorageError> { + match scope { + Field::NVT(x) => self.cache_nvt_field(key.as_ref(), x)?, + Field::KB(x) => self.replace_kb(key.as_ref(), x)?, + Field::NotusAdvisory(x) => { + if let Some(x) = *x { + self.cache_notus_advisory(x)? + } + } + Field::Result(x) => self.cache_result(key.as_ref(), *x)?, + } + Ok(()) + } + fn on_exit(&self) -> Result<(), StorageError> { if !self.dirty { self.cleanse()?; From a18383e76a700ff2d611f6e7c3532427b07f8c81 Mon Sep 17 00:00:00 2001 From: Kraemii Date: Thu, 5 Sep 2024 11:53:10 +0200 Subject: [PATCH 6/7] Fix clippy warning --- rust/storage/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/storage/src/lib.rs b/rust/storage/src/lib.rs index b1ae18457..73c00c99c 100644 --- a/rust/storage/src/lib.rs +++ b/rust/storage/src/lib.rs @@ -362,7 +362,7 @@ impl DefaultDispatcher { let mut data = self.kbs.as_ref().write()?; if let Some(scan_entry) = data.get_mut(scan_id) { if let Some(kb_entry) = scan_entry.get_mut(&kb.key) { - if kb_entry.iter().position(|x| x.value == kb.value).is_none() { + if !kb_entry.iter().any(|x| x.value == kb.value) { kb_entry.push(kb); }; } else { From cfc22f80e3df18fc798573c23f5c7578456fe231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Nicola?= Date: Thu, 5 Sep 2024 13:19:50 +0200 Subject: [PATCH 7/7] Fix: load the feed metadata from an existing json file (#1708) * Fix: load the feed metadata from an existing json file * Fix: bug that the FSPluginLoader is incorrectly set is fixed We just use the feed.json when it is available and if not we execute like previously. Patch provided by @nichtsfrei --- rust/scannerctl/src/error.rs | 14 ++++++++- rust/scannerctl/src/interpret/mod.rs | 44 ++++++++++++++++++++++------ rust/storage/src/lib.rs | 8 ++++- 3 files changed, 55 insertions(+), 11 deletions(-) diff --git a/rust/scannerctl/src/error.rs b/rust/scannerctl/src/error.rs index 24d059298..ffba6862b 100644 --- a/rust/scannerctl/src/error.rs +++ b/rust/scannerctl/src/error.rs @@ -2,7 +2,10 @@ // // SPDX-License-Identifier: GPL-2.0-or-later WITH x11vnc-openssl-exception -use std::{fmt::Display, path::PathBuf}; +use std::{ + fmt::Display, + path::{Path, PathBuf}, +}; use feed::VerifyError; use nasl_interpreter::{InterpretError, LoadError}; @@ -51,6 +54,15 @@ pub struct CliError { pub kind: CliErrorKind, } +impl CliError { + pub fn load_error(err: std::io::Error, path: &Path) -> Self { + Self { + filename: path.to_owned().to_string_lossy().to_string(), + kind: CliErrorKind::LoadError(LoadError::Dirty(err.to_string())), + } + } +} + impl Display for CliErrorKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { diff --git a/rust/scannerctl/src/interpret/mod.rs b/rust/scannerctl/src/interpret/mod.rs index bfc1937e1..7842f5101 100644 --- a/rust/scannerctl/src/interpret/mod.rs +++ b/rust/scannerctl/src/interpret/mod.rs @@ -2,7 +2,10 @@ // // SPDX-License-Identifier: GPL-2.0-or-later WITH x11vnc-openssl-exception -use std::path::PathBuf; +use std::{ + fs::{self}, + path::PathBuf, +}; use nasl_interpreter::{ load_non_utf8_path, CodeInterpreter, FSPluginLoader, LoadError, NaslValue, NoOpLoader, @@ -153,22 +156,35 @@ fn create_redis_storage( redis_storage::CacheDispatcher::as_dispatcher(url, FEEDUPDATE_SELECTOR).unwrap() } -fn create_fp_loader(storage: &S, path: PathBuf) -> Result, CliError> +fn load_feed_by_exec(storage: &S, pl: &FSPluginLoader) -> Result<(), CliError> where S: storage::Dispatcher, { // update feed with storage tracing::info!("loading feed. This may take a while."); - let result = FSPluginLoader::new(path); - let verifier = feed::HashSumNameLoader::sha256(&result)?; - let updater = feed::Update::init("scannerctl", 5, &result, storage, verifier); + let verifier = feed::HashSumNameLoader::sha256(pl)?; + let updater = feed::Update::init("scannerctl", 5, pl, storage, verifier); for u in updater { - tracing::warn!(updated=?u); + tracing::trace!(updated=?u); u?; } tracing::info!("loaded feed."); - Ok(result) + Ok(()) +} + +fn load_feed_by_json(store: &DefaultDispatcher, path: &PathBuf) -> Result<(), CliError> { + tracing::info!(path=?path, "loading feed via json. This may take a while."); + let buf = fs::read_to_string(path).map_err(|e| CliError::load_error(e, path))?; + let vts: Vec = serde_json::from_str(&buf)?; + let all_vts = vts.into_iter().map(|v| (v.filename.clone(), v)).collect(); + + store.set_vts(all_vts).map_err(|e| CliError { + filename: path.to_owned().to_string_lossy().to_string(), + kind: e.into(), + })?; + tracing::info!("loaded feed."); + Ok(()) } pub fn run( @@ -188,12 +204,22 @@ pub fn run( (Db::InMemory, None) => builder.build().run(script), (Db::Redis(url), Some(path)) => { let storage = create_redis_storage(url); - let builder = RunBuilder::default().loader(create_fp_loader(&storage, path)?); + let loader = FSPluginLoader::new(path); + load_feed_by_exec(&storage, &loader)?; + let builder = RunBuilder::default().loader(loader); builder.storage(storage).build().run(script) } (Db::InMemory, Some(path)) => { let storage = DefaultDispatcher::new(true); - let builder = RunBuilder::default().loader(create_fp_loader(&storage, path)?); + let guessed_feed_json = path.join("feed.json"); + let loader = FSPluginLoader::new(path.clone()); + if guessed_feed_json.exists() { + load_feed_by_json(&storage, &guessed_feed_json)? + } else { + load_feed_by_exec(&storage, &loader)? + } + + let builder = RunBuilder::default().loader(loader); builder.storage(storage).build().run(script) } }; diff --git a/rust/storage/src/lib.rs b/rust/storage/src/lib.rs index 73c00c99c..99630b02c 100644 --- a/rust/storage/src/lib.rs +++ b/rust/storage/src/lib.rs @@ -298,7 +298,7 @@ type Kbs = HashMap>>; /// Vts are using a relative file path as a key. This should make includes, script_dependency /// lookups relative simple. -type Vts = HashMap; +pub type Vts = HashMap; /// The results generated by log_, security_, error_message. type Results = HashMap>; @@ -324,6 +324,12 @@ impl DefaultDispatcher { } } + /// Stores an already existing Vts structure. + pub fn set_vts(&self, vts: Vts) -> Result<(), StorageError> { + let mut data = self.vts.as_ref().write()?; + *data = vts; + Ok(()) + } /// Cleanses stored data. pub fn cleanse(&self) -> Result<(), StorageError> { // TODO cleanse at least kbs, may rest?