Skip to content

Commit 6d9d983

Browse files
committed
Add: Notus related built-in functions
This includes: - security_notus - notus_error - notus - notus_type
1 parent ad26d96 commit 6d9d983

File tree

8 files changed

+274
-10
lines changed

8 files changed

+274
-10
lines changed

doc/manual/nasl/built-in-functions/glue-functions/update_table_driven_lsc_data.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## NAME
44

5+
DEPRECATED
6+
57
**update_table_driven_lsc_data** - Set information, so that openvas can start a table driven lsc
68

79
## SYNOPSIS
@@ -19,10 +21,16 @@ os_release: identifier for the operating system of the target system
1921
After the KB items are set, these information is also transferred to the main process and a notus scan is triggered. The
2022
results of the notus scan are then directly published.
2123

24+
## DEPRECATED
25+
26+
This function is deprecated and **[notus(3)](notus.md)** and **[security_notus(3)](security_notus.md)** should be used instead.
27+
2228
## RETURN VALUE
2329

2430
This function returns nothing.
2531

2632
## SEE ALSO
2733

28-
**[log_message(3)](log_message.md)**
34+
**[log_message(3)](log_message.md)**,
35+
**[notus(3)](notus.md)**,
36+
**[security_notus(3)](security_notus.md)**

rust/src/nasl/builtin/error.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use thiserror::Error;
66

77
use crate::nasl::prelude::*;
88
use crate::nasl::utils::error::FnErrorKind;
9+
use crate::notus::NotusError;
910

1011
use super::KBError;
1112
use super::cert::CertError;
@@ -28,6 +29,8 @@ pub enum BuiltinError {
2829
#[error("{0}")]
2930
Http(HttpError),
3031
#[error("{0}")]
32+
Notus(NotusError),
33+
#[error("{0}")]
3134
String(StringError),
3235
#[error("{0}")]
3336
Misc(MiscError),
@@ -101,6 +104,6 @@ builtin_error_variant!(CertError, Cert);
101104
builtin_error_variant!(SysError, Sys);
102105
builtin_error_variant!(FindServiceError, FindService);
103106
builtin_error_variant!(SnmpError, Snmp);
104-
107+
builtin_error_variant!(NotusError, Notus);
105108
#[cfg(feature = "nasl-builtin-raw-ip")]
106109
builtin_error_variant!(RawIpError, RawIp);

rust/src/nasl/builtin/mod.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ mod isotime;
1616
mod knowledge_base;
1717
pub mod misc;
1818
pub mod network;
19-
mod snmp;
20-
19+
mod notus;
2120
mod preferences;
2221
#[cfg(feature = "nasl-builtin-raw-ip")]
2322
pub mod raw_ip;
2423
mod regex;
2524
mod report_functions;
25+
mod snmp;
2626
mod ssh;
2727
mod string;
2828
mod sys;
@@ -64,7 +64,8 @@ pub fn nasl_std_functions() -> Executor {
6464
.add_set(find_service::FindService)
6565
.add_set(wmi::Wmi)
6666
.add_set(snmp::Snmp)
67-
.add_set(cert::NaslCerts::default());
67+
.add_set(cert::NaslCerts::default())
68+
.add_set(notus::NaslNotus::default());
6869

6970
#[cfg(feature = "nasl-builtin-raw-ip")]
7071
executor.add_set(raw_ip::RawIp);

rust/src/nasl/builtin/notus/mod.rs

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// SPDX-FileCopyrightText: 2026 Greenbone AG
2+
//
3+
// SPDX-License-Identifier: GPL-2.0-or-later WITH x11vnc-openssl-exception
4+
5+
use std::{
6+
collections::HashMap,
7+
io::{Read, Write},
8+
net::{SocketAddr, TcpStream},
9+
};
10+
11+
use greenbone_scanner_framework::models::FixedVersion;
12+
use nasl_function_proc_macro::nasl_function;
13+
use serde::{Deserialize, Serialize};
14+
use serde_json;
15+
16+
use crate::{
17+
function_set,
18+
nasl::{
19+
ArgumentError, FnError, NaslValue, ScanCtx, builtin::http::HttpError,
20+
utils::scan_ctx::NotusCtx,
21+
},
22+
notus::{HashsumProductLoader, Notus},
23+
};
24+
25+
#[nasl_function]
26+
fn notus_type() -> i64 {
27+
1
28+
}
29+
30+
#[derive(Serialize, Deserialize, Debug)]
31+
struct NotusResult {
32+
oid: String,
33+
message: String,
34+
}
35+
36+
impl NaslNotus {
37+
fn notus_self(
38+
&self,
39+
notus: &mut Notus<HashsumProductLoader>,
40+
pkg_list: &[String],
41+
product: &str,
42+
) -> Result<NaslValue, FnError> {
43+
let res = notus.scan(product, pkg_list)?;
44+
45+
let mut ret = HashMap::new();
46+
for (oid, vuls) in res {
47+
let message = vuls.into_iter().map(|vul| match vul.fixed_version {
48+
FixedVersion::Single { version, specifier } => format!("Vulnerable package: {}\nInstalled version: {}\nFixed version: {:2}{}", vul.name, vul.installed_version, specifier.to_string(), version),
49+
FixedVersion::Range { start, end } => format!("Vulnerable package: {}\nInstalled version: {}\nFixed version: < {}\nFixed version: >={}", vul.name, vul.installed_version, start, end),
50+
}).collect::<Vec<String>>().join("\n\n");
51+
ret.insert(oid, NaslValue::String(message));
52+
}
53+
54+
Ok(NaslValue::Dict(ret))
55+
}
56+
57+
fn notus_extern(
58+
&self,
59+
addr: &SocketAddr,
60+
pkg_list: &[String],
61+
product: &str,
62+
) -> Result<NaslValue, FnError> {
63+
let mut sock = TcpStream::connect(addr).map_err(|e| HttpError::IO(e.kind()))?;
64+
let pkg_json = serde_json::to_string(pkg_list).unwrap();
65+
66+
let request = format!(
67+
"POST /notus/{} HTTP/1.1\r\nContent-Length: {}\r\n\r\n{}",
68+
product,
69+
pkg_json.len(),
70+
pkg_json
71+
);
72+
sock.write_all(request.as_bytes())
73+
.map_err(|e| HttpError::IO(e.kind()))?;
74+
let mut response = Vec::new();
75+
sock.read_to_end(&mut response)
76+
.map_err(|e| HttpError::IO(e.kind()))?;
77+
let response_str = String::from_utf8(response).unwrap();
78+
79+
// Split headers and body
80+
let parts: Vec<&str> = response_str.split("\r\n\r\n").collect();
81+
let body = if parts.len() > 1 {
82+
parts[1]
83+
} else {
84+
&response_str
85+
};
86+
87+
// Parse JSON array of results
88+
let results: Vec<NotusResult> = serde_json::from_str(body).unwrap();
89+
90+
// Convert to NaslValue (Dict mapping oid -> message)
91+
let mut ret = HashMap::new();
92+
for result in results {
93+
ret.insert(result.oid, NaslValue::String(result.message));
94+
}
95+
96+
Ok(NaslValue::Dict(ret))
97+
}
98+
99+
#[nasl_function]
100+
fn notus_error(&self) -> Option<String> {
101+
self.last_error.clone()
102+
}
103+
104+
#[nasl_function(named(pkg_list, product))]
105+
fn notus(
106+
&mut self,
107+
context: &ScanCtx,
108+
pkg_list: NaslValue,
109+
product: &str,
110+
) -> Result<NaslValue, FnError> {
111+
let notus = if let Some(notus) = &context.notus {
112+
notus
113+
} else {
114+
self.last_error = Some("Configuration Error: Notus context not found".to_string());
115+
return Ok(NaslValue::Null);
116+
};
117+
let pkg_list: Vec<String> = match pkg_list {
118+
NaslValue::String(s) => s.split(',').map(|s| s.trim().to_string()).collect(),
119+
NaslValue::Array(arr) => arr.iter().map(|v| v.to_string()).collect(),
120+
x => {
121+
return Err(ArgumentError::wrong_argument(
122+
"pkg_list",
123+
"String as Comma Separated List or Array of Strings",
124+
&format!("{:?}", x),
125+
)
126+
.into());
127+
}
128+
};
129+
let ret = match notus {
130+
NotusCtx::Direct(notus) => {
131+
self.notus_self(&mut notus.lock().unwrap(), &pkg_list, product)
132+
}
133+
NotusCtx::Address(addr) => self.notus_extern(addr, &pkg_list, product),
134+
};
135+
match ret {
136+
Err(e) => {
137+
self.last_error = Some(e.to_string());
138+
Ok(NaslValue::Null)
139+
}
140+
Ok(ret) => {
141+
self.last_error = None;
142+
Ok(ret)
143+
}
144+
}
145+
}
146+
}
147+
148+
#[derive(Default)]
149+
pub struct NaslNotus {
150+
last_error: Option<String>,
151+
}
152+
153+
function_set! {
154+
NaslNotus,
155+
(
156+
NaslNotus::notus_error,
157+
NaslNotus::notus,
158+
)
159+
}

rust/src/nasl/builtin/report_functions/mod.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,48 @@ impl Reporting {
111111
fn error_message(&self, register: &Register, context: &ScanCtx) -> Result<NaslValue, FnError> {
112112
self.store_result(ResultType::Error, register, context)
113113
}
114+
115+
#[nasl_function(named(result))]
116+
fn security_notus(&self, context: &ScanCtx, result: NaslValue) -> Result<(), FnError> {
117+
match result {
118+
NaslValue::Dict(dict) => {
119+
if let (Some(NaslValue::String(oid)), Some(NaslValue::String(message))) =
120+
(dict.get("oid"), dict.get("message"))
121+
{
122+
let result = models::Result {
123+
id: self.id(),
124+
r_type: ResultType::Alarm,
125+
ip_address: Some(context.target().ip_addr().to_string()),
126+
hostname: None,
127+
oid: Some(oid.to_owned()),
128+
port: None,
129+
protocol: None, // TODO: This field is set to "package" in the c scanner result
130+
message: Some(message.to_owned()),
131+
detail: None,
132+
};
133+
context
134+
.storage()
135+
.retry_dispatch(context.scan().clone(), result, 5)?;
136+
} else {
137+
return Err(ArgumentError::wrong_argument(
138+
"result",
139+
"Dict with 'oid' and 'message' as String values",
140+
&format!("{:?}", dict),
141+
)
142+
.into());
143+
}
144+
Ok(())
145+
}
146+
x => {
147+
return Err(ArgumentError::wrong_argument(
148+
"result",
149+
"Dict with 'oid' and 'message' as String values",
150+
&format!("{:?}", x),
151+
)
152+
.into());
153+
}
154+
}
155+
}
114156
}
115157

116158
function_set! {
@@ -119,5 +161,6 @@ function_set! {
119161
(Reporting::log_message, "log_message"),
120162
(Reporting::security_message, "security_message"),
121163
(Reporting::error_message, "error_message"),
164+
(Reporting::security_notus, "security_notus"),
122165
)
123166
}

rust/src/nasl/builtin/report_functions/tests.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,35 @@ fn security_message() {
7171
fn error_message() {
7272
verify("error_message", ResultType::Error)
7373
}
74+
75+
#[test]
76+
fn security_notus() {
77+
let mut t = TestBuilder::default();
78+
t.run_all(
79+
r###"
80+
result["oid"] = "1.2.3.4.5";
81+
result["message"] = "test message";
82+
security_notus(result: result);
83+
"###,
84+
);
85+
t.check_no_errors();
86+
let (results, context) = t.results_and_context();
87+
assert_eq!(results.len(), 3);
88+
let result = context
89+
.storage()
90+
.retrieve(&(context.scan().clone(), 0))
91+
.unwrap()
92+
.unwrap();
93+
assert_eq!(result.id, 0);
94+
assert_eq!(result.r_type, ResultType::Alarm);
95+
assert_eq!(
96+
result.ip_address,
97+
Some(context.target().ip_addr().to_string())
98+
);
99+
assert_eq!(result.hostname, None);
100+
assert_eq!(result.oid, Some("1.2.3.4.5".to_string()));
101+
assert_eq!(result.port, None);
102+
assert_eq!(result.protocol, None);
103+
assert_eq!(result.message, Some("test message".into()));
104+
assert_eq!(result.detail, None);
105+
}

rust/src/nasl/test_utils.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,10 @@ use std::{
88
fmt::{self, Display, Formatter},
99
panic::Location,
1010
path::PathBuf,
11-
sync::Mutex,
1211
};
1312

13+
use crate::storage::{ScanID, inmemory::InMemoryStorage};
1414
use crate::{nasl::prelude::*, scanner::preferences::preference::ScanPrefs};
15-
use crate::{
16-
notus::{HashsumProductLoader, Notus},
17-
storage::{ScanID, inmemory::InMemoryStorage},
18-
};
1915
use futures::{Stream, StreamExt};
2016

2117
use super::{

rust/src/storage/items/kb.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ pub enum KbKey {
1717
/// Contains SSL/TLS Kb keys
1818
Ssl(Ssl),
1919

20+
Ssh(Ssh),
21+
2022
/// Contains Port related Kb keys
2123
Port(Port),
2224

@@ -73,6 +75,19 @@ pub enum Ssl {
7375
Ca,
7476
}
7577

78+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
79+
#[serde(rename_all = "snake_case")]
80+
pub enum Ssh {
81+
Login(Login),
82+
}
83+
84+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
85+
#[serde(rename_all = "snake_case")]
86+
pub enum Login {
87+
PackageListNotus,
88+
ReleaseNotus,
89+
}
90+
7691
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
7792
#[serde(rename_all = "snake_case")]
7893
pub enum Port {
@@ -175,6 +190,13 @@ impl Display for KbKey {
175190
KbKey::Ssl(Ssl::Password) => write!(f, "SSL/password"),
176191
KbKey::Ssl(Ssl::Ca) => write!(f, "SSL/ca"),
177192

193+
KbKey::Ssh(Ssh::Login(Login::PackageListNotus)) => {
194+
write!(f, "ssh/login/package_list_notus")
195+
}
196+
KbKey::Ssh(Ssh::Login(Login::ReleaseNotus)) => {
197+
write!(f, "ssh/login/release_notus")
198+
}
199+
178200
KbKey::Port(Port::Tcp(port)) => write!(f, "Ports/tcp/{port}"),
179201
KbKey::Port(Port::Udp(port)) => write!(f, "Ports/udp/{port}"),
180202

0 commit comments

Comments
 (0)