Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add host nasl built-in functions #1758

Merged
merged 5 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions rust/src/feed/update/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand Down
5 changes: 0 additions & 5 deletions rust/src/nasl/builtin/host/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
## Implements

- get_host_name
- get_host_names

## Missing

- TARGET_IS_IPV6
- add_host_name
- get_host_name
Expand Down
200 changes: 158 additions & 42 deletions rust/src/nasl/builtin/host/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,56 +5,50 @@
#[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::FunctionErrorKind, lookup_keys::TARGET};
use dns_lookup::lookup_addr;
use nasl_function_proc_macro::nasl_function;

use crate::nasl::syntax::NaslValue;
use crate::nasl::utils::{Context, ContextType, Register};

/// 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<String, FunctionErrorKind> {
use std::net::ToSocketAddrs;
use crate::nasl::utils::{error::FunctionErrorKind, hosts::resolve};
use crate::{function_set, nasl::FromNaslValue};

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(),
},
);
use crate::nasl::syntax::NaslValue;
use crate::nasl::utils::{Context, Register};

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),
struct Hostname(String);
impl<'a> FromNaslValue<'a> for Hostname {
fn from_nasl_value(value: &'a NaslValue) -> Result<Self, FunctionErrorKind> {
let str = String::from_nasl_value(value)?;
if str.is_empty() {
Err(FunctionErrorKind::diagnostic_ret_null("Empty hostname."))
} else {
Ok(Self(str))
}
}
}

/// 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<NaslValue, FunctionErrorKind> {
resolve_hostname(register).map(|x| NaslValue::Array(vec![NaslValue::String(x)]))
}

/// 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<NaslValue, FunctionErrorKind> {
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<NaslValue, FunctionErrorKind> {
let hns = context.target_vhosts();
if !hns.is_empty() {
let hns = hns
.into_iter()
.map(|(h, _s)| NaslValue::String(h))
.collect::<Vec<_>>();
return Ok(NaslValue::Array(hns));
};
Ok(NaslValue::Array(vec![NaslValue::String(
context.target().to_string(),
)]))
}

/// Return the target's IP address as IpAddr.
pub fn get_host_ip(context: &Context) -> Result<IpAddr, FunctionErrorKind> {
fn get_host_ip(context: &Context) -> Result<IpAddr, FunctionErrorKind> {
let default_ip = "127.0.0.1";
let r_sock_addr = match context.target() {
x if !x.is_empty() => IpAddr::from_str(x),
Expand All @@ -70,6 +64,66 @@ pub fn get_host_ip(context: &Context) -> Result<IpAddr, FunctionErrorKind> {
}
}

///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<NaslValue, FunctionErrorKind> {
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<NaslValue, FunctionErrorKind> {
let vh = context.target_vhosts();
let v = if !vh.is_empty() {
vh.iter()
.map(|(v, _s)| NaslValue::String(v.to_string()))
.collect::<Vec<_>>()
} 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));
}
jjnicola marked this conversation as resolved.
Show resolved Hide resolved

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,
Expand All @@ -79,14 +133,76 @@ fn nasl_get_host_ip(
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<NaslValue, FunctionErrorKind> {
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<bool, FunctionErrorKind> {
let target = match context.target().is_empty() {
true => {
return Err(FunctionErrorKind::diagnostic_ret_null("Address is NULL!"));
}
false => context.target(),
};
Ok(target.parse::<Ipv6Addr>().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<bool>) -> Result<bool, FunctionErrorKind> {
let h1 = resolve(h1.to_string())?;
let h2 = resolve(h2.to_string())?;

let hostnames1 = h1
.iter()
.filter_map(|x| lookup_addr(x).ok())
.collect::<Vec<_>>();
let hostnames2 = h2
.iter()
.filter_map(|x| lookup_addr(x).ok())
.collect::<Vec<_>>();

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
)
}
7 changes: 5 additions & 2 deletions rust/src/nasl/builtin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ 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
Expand Down Expand Up @@ -137,11 +139,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,
Expand Down
4 changes: 1 addition & 3 deletions rust/src/nasl/builtin/ssh/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading
Loading