From d9a9f537de03f7e2af34a297c9f01abf67c85e87 Mon Sep 17 00:00:00 2001 From: Philipp Eder Date: Thu, 21 Dec 2023 15:25:58 +0100 Subject: [PATCH] Change: refactor nasl-cli Removed structg based approach in favor of module based. Each subcommand has an own module with a - exetend_args - run method. To add a new subcommand, create a module with those two methods. Then add the extend_args method into the main.rs::main and the run method inside main.rs::run. --- rust/nasl-cli/src/execute/mod.rs | 40 ++ rust/nasl-cli/src/feed/mod.rs | 153 +++++ .../src/{feed_update.rs => feed/update.rs} | 0 rust/nasl-cli/src/main.rs | 536 +++--------------- rust/nasl-cli/src/scanconfig.rs | 38 +- .../src/{syntax_check.rs => syntax/check.rs} | 0 rust/nasl-cli/src/syntax/mod.rs | 37 ++ 7 files changed, 331 insertions(+), 473 deletions(-) create mode 100644 rust/nasl-cli/src/execute/mod.rs create mode 100644 rust/nasl-cli/src/feed/mod.rs rename rust/nasl-cli/src/{feed_update.rs => feed/update.rs} (100%) rename rust/nasl-cli/src/{syntax_check.rs => syntax/check.rs} (100%) create mode 100644 rust/nasl-cli/src/syntax/mod.rs diff --git a/rust/nasl-cli/src/execute/mod.rs b/rust/nasl-cli/src/execute/mod.rs new file mode 100644 index 000000000..1e37b037e --- /dev/null +++ b/rust/nasl-cli/src/execute/mod.rs @@ -0,0 +1,40 @@ +use std::path::PathBuf; + +use clap::{arg, value_parser, Arg, Command}; + +use crate::{interpret, CliError, Db}; + +pub fn run(root: &clap::ArgMatches) -> Option> { + let (args, _) = crate::get_args_set_logging(root, "execute")?; + let feed = args.get_one::("path").cloned(); + let script = match args.get_one::("script").cloned() { + Some(path) => path, + _ => unreachable!("path is set to required"), + }; + let target = args.get_one::("target").cloned(); + Some(interpret::run( + &Db::InMemory, + feed.clone(), + script.to_string(), + target.clone(), + )) +} +pub fn extend_args(cmd: Command) -> Command { + crate::add_verbose( + cmd.subcommand( + Command::new("execute") + .about( + "Executes a nasl-script. +A script can either be a file to be executed or an ID. +When ID is used than a valid feed path must be given within the path parameter.", + ) + .arg( + arg!(-p --path "Path to the feed.") + .required(false) + .value_parser(value_parser!(PathBuf)), + ) + .arg(Arg::new("script").required(true)) + .arg(arg!(-t --target "Target to scan").required(false)), + ), + ) +} diff --git a/rust/nasl-cli/src/feed/mod.rs b/rust/nasl-cli/src/feed/mod.rs new file mode 100644 index 000000000..a25895966 --- /dev/null +++ b/rust/nasl-cli/src/feed/mod.rs @@ -0,0 +1,153 @@ +pub mod update; +use std::{io, path::PathBuf}; + +use clap::{arg, value_parser, ArgAction, Command}; +// re-export to work around name conflict +pub use feed::transpile; +use storage::StorageError; + +use crate::{get_path_from_openvas, read_openvas_config, CliError, CliErrorKind}; + +pub fn extend_args(cmd: Command) -> Command { + crate::add_verbose( + cmd.subcommand( + Command::new("feed") + .about("Handles feed related tasks") + .subcommand_required(true) + .subcommand(Command::new("update") + .about("Runs nasl scripts in description mode and updates data into redis") + .arg(arg!(-p --path "Path to the feed.") .required(false) + .value_parser(value_parser!(PathBuf))) + .arg(arg!(-x --"signature-check" "Enable NASL signature check.") .required(false).action(ArgAction::SetTrue)) + .arg(arg!(-r --redis "Redis url. Must either start `unix://` or `redis://`.").required(false)) + ) + .subcommand(Command::new("transform") + .about("Runs nasl scripts in description mode and returns it as a json array into stdout") + .arg(arg!(-p --path "Path to the feed.") .required(false) + .value_parser(value_parser!(PathBuf))) + ) + .subcommand(Command::new("transpile") + .about("Transforms each nasl script and inc file based on the given rules.") + .arg(arg!(-p --path "Path to the feed.") .required(false) + .value_parser(value_parser!(PathBuf))) + .arg(arg!(-r --rules "Path to transpiler rules.").required(true) + .value_parser(value_parser!(PathBuf))) + ) + )) +} + +pub fn run(root: &clap::ArgMatches) -> Option> { + fn get_path(args: &clap::ArgMatches) -> PathBuf { + args.get_one::("path").cloned().unwrap_or_else(|| { + let config = + read_openvas_config().expect("openvas -s must be executable when path is not set"); + get_path_from_openvas(config) + }) + } + + let (args, verbose) = crate::get_args_set_logging(root, "feed")?; + match args.subcommand() { + Some(("update", args)) => { + let path = get_path(args); + let redis = match args.get_one::("redis").cloned() { + Some(x) => x, + None => { + let config = read_openvas_config() + .expect("openvas -s must be executable when path is not set"); + let dba = config + .get("default", "db_address") + .expect("openvas -s must contain db_address"); + + if dba.starts_with("redis://") || dba.starts_with("unix://") { + dba + } else if dba.starts_with("tcp://") { + dba.replace("tcp://", "redis://") + } else { + format!("unix://{dba}") + } + } + }; + let signature_check = args + .get_one::("signature-check") + .cloned() + .unwrap_or(false); + + let dispatcher = redis_storage::NvtDispatcher::as_dispatcher(&redis) + .map_err(StorageError::from) + .map_err(|e| CliError { + kind: e.into(), + filename: format!("{path:?}"), + }); + Some(dispatcher.and_then(|dispatcher| update::run(dispatcher, path, signature_check))) + } + Some(("transform", args)) => { + let path = get_path(args); + + let mut o = json_storage::ArrayWrapper::new(io::stdout()); + let dispatcher = json_storage::NvtDispatcher::as_dispatcher(&mut o); + Some(match update::run(dispatcher, path, false) { + Ok(_) => o.end().map_err(StorageError::from).map_err(|se| CliError { + filename: "".to_string(), + kind: se.into(), + }), + Err(e) => Err(e), + }) + } + + Some(("transpile", args)) => { + let path = get_path(args); + let rules = match args.get_one::("rules").cloned() { + Some(x) => x, + None => unreachable!("rules is set to required"), + }; + + #[derive(serde::Deserialize, serde::Serialize)] + struct Wrapper { + cmds: Vec, + } + + let rules = std::fs::read_to_string(rules).unwrap(); + let rules: Wrapper = toml::from_str(&rules).unwrap(); + let rules = rules.cmds; + let base = path.to_str().unwrap_or_default(); + for r in feed::transpile::FeedReplacer::new(base, &rules) { + let name = r.unwrap(); + if let Some((name, content)) = name { + use std::io::Write; + let f = std::fs::OpenOptions::new() + .write(true) + .truncate(true) + .open(&name) + .map_err(|e| { + let kind = CliErrorKind::Corrupt(format!("unable to open {name}: {e}")); + CliError { + filename: name.clone(), + kind, + } + }); + match f.and_then(|mut f| { + f.write_all(content.as_bytes()).map_err(|e| { + let kind = + CliErrorKind::Corrupt(format!("unable to write {name}: {e}")); + CliError { + filename: name.clone(), + kind, + } + }) + }) { + Ok(_) => {} + Err(e) => { + return Some(Err(e)); + } + } + + if verbose > 0 { + eprintln!("changed {name}"); + } + } + } + Some(Ok(())) + } + _ => unreachable!("subcommand_required prevents None"), + } +} diff --git a/rust/nasl-cli/src/feed_update.rs b/rust/nasl-cli/src/feed/update.rs similarity index 100% rename from rust/nasl-cli/src/feed_update.rs rename to rust/nasl-cli/src/feed/update.rs diff --git a/rust/nasl-cli/src/main.rs b/rust/nasl-cli/src/main.rs index 0ebc3628e..78ca2e458 100644 --- a/rust/nasl-cli/src/main.rs +++ b/rust/nasl-cli/src/main.rs @@ -4,54 +4,22 @@ #![doc = include_str!("../README.md")] mod error; -mod feed_update; +mod execute; +mod feed; mod interpret; mod scanconfig; -mod syntax_check; +mod syntax; use configparser::ini::Ini; pub use error::*; use std::{ - io::{self}, path::PathBuf, process, }; use storage::StorageError; -use clap::{arg, value_parser, Arg, ArgAction, Command}; - -#[derive(Debug)] -enum Commands { - /// Checks nasl-file or a directory of for syntax errors. - Syntax { - /// The path for the file or dir to parse - path: PathBuf, - /// Disables printing of progress - no_progress: bool, - /// Verbose output - verbose: bool, - }, - /// Controls the feed - Feed { - /// The action to perform on a feed - action: FeedAction, - }, - /// Executes a script - Execute { - db: Db, - feed: Option, - script: String, - target: Option, - }, - /// Transforms a scan config to scan json for openvasd - ScanConfig { - feed: Option, - config: Vec, - port_list: Option, - stdin: bool, - }, -} +use clap::{arg, ArgAction, ArgMatches, Command}; #[derive(Debug, Clone)] pub enum Db { @@ -59,198 +27,6 @@ pub enum Db { InMemory, } -#[derive(Debug, Clone)] -enum FeedAction { - /// Updates feed data into redis. - Update { - /// Redis address inform of tcp (redis://) or unix socket (unix://). - /// - /// It must be the complete redis address in either the form of a unix socket or tcp. - /// For tcp provide the address in the form of: `redis://host:port`. - /// For unix socket provide the path to the socket in the form of: `unix://path/to/redis.sock`. - /// When it is skipped it will be obtained via `openvas -s` - redis: Option, - /// The path to the NASL plugins. - /// - /// When it is skipped it will be obtained via `openvas -s` - path: Option, - /// If the signature check of the sha256sums file must be performed. - signature_check: Option, - }, - /// Transforms the feed into stdout - /// - Transform { - /// The path to the NASL plugins. - /// - /// When it is skipped it will be obtained via `openvas -s` - path: Option, - }, - /// Transpiles the feed based on a given ruleset. - /// - Transpile { - /// The path to the NASL plugins. - /// - path: PathBuf, - /// Describes the rules for changing the rules. - /// - /// The rules describe how to find a certain element and how to replace it. - /// Currently only toml in the following format is supported: - /// ```toml - /// [[cmds]] - /// - /// [cmds.find] - /// FunctionByName = "register_host_detail" - /// - /// [cmds.with] - /// Name = "add_host_detail" - /// ``` - rules: PathBuf, - /// Prints the changed file names - verbose: bool, - }, -} - -trait RunAction { - type Error; - fn run(&self) -> Result; -} - -impl RunAction<()> for FeedAction { - type Error = CliError; - fn run(&self) -> Result<(), Self::Error> { - match self { - FeedAction::Update { - redis: _, - path, - signature_check: _, - } => { - let update_config: FeedUpdateConfiguration = - self.as_config().map_err(|kind| CliError { - filename: String::new(), - kind, - })?; - let dispatcher = - redis_storage::NvtDispatcher::as_dispatcher(&update_config.redis_url) - .map_err(StorageError::from) - .map_err(|e| CliError { - kind: e.into(), - filename: format!("{path:?}"), - })?; - feed_update::run( - dispatcher, - update_config.plugin_path, - update_config.check_enabled, - ) - } - FeedAction::Transform { path } => { - let transform_config: TransformConfiguration = - self.as_config().map_err(|kind| CliError { - filename: format!("{path:?}"), - kind, - })?; - - let mut o = json_storage::ArrayWrapper::new(io::stdout()); - let dispatcher = json_storage::NvtDispatcher::as_dispatcher(&mut o); - match feed_update::run(dispatcher, transform_config.plugin_path, false) { - Ok(_) => o.end().map_err(StorageError::from).map_err(|se| CliError { - filename: "".to_string(), - kind: se.into(), - }), - Err(e) => Err(e), - } - } - FeedAction::Transpile { - path, - rules, - verbose, - } => { - #[derive(serde::Deserialize, serde::Serialize)] - struct Wrapper { - cmds: Vec, - } - - let rules = std::fs::read_to_string(rules).unwrap(); - let rules: Wrapper = toml::from_str(&rules).unwrap(); - let rules = rules.cmds; - let base = path.to_str().unwrap_or_default(); - for r in feed::transpile::FeedReplacer::new(base, &rules) { - let name = r.unwrap(); - if let Some((name, content)) = name { - use std::io::Write; - let mut f = std::fs::OpenOptions::new() - .write(true) - .truncate(true) - .open(&name) - .map_err(|e| { - let kind = - CliErrorKind::Corrupt(format!("unable to open {name}: {e}")); - CliError { - filename: name.clone(), - kind, - } - })?; - f.write_all(content.as_bytes()).map_err(|e| { - let kind = - CliErrorKind::Corrupt(format!("unable to write {name}: {e}")); - CliError { - filename: name.clone(), - kind, - } - })?; - - if *verbose { - eprintln!("changed {name}"); - } - } - } - Ok(()) - } - } - } -} - -impl RunAction<()> for Commands { - type Error = CliError; - fn run(&self) -> Result<(), Self::Error> { - match self { - Commands::Syntax { - path, - no_progress, - verbose, - } => syntax_check::run(path, *verbose, *no_progress), - Commands::Feed { action } => action.run(), - Commands::Execute { - db, - feed, - script, - target, - } => interpret::run(db, feed.clone(), script.to_string(), target.clone()), - Commands::ScanConfig { - feed, - config, - port_list, - stdin, - } => scanconfig::run(feed.as_ref(), config, port_list.as_ref(), *stdin), - } - } -} - -trait AsConfig { - fn as_config(&self) -> Result; -} - -/// Is the configuration required to run feed related operations. -struct FeedUpdateConfiguration { - /// Plugin path is required for either - plugin_path: PathBuf, - redis_url: String, - check_enabled: bool, -} - -struct TransformConfiguration { - plugin_path: PathBuf, -} - fn read_openvas_config() -> Result { let oconfig = process::Command::new("openvas") .arg("-s") @@ -269,87 +45,6 @@ fn read_openvas_config() -> Result { Ok(config) } -impl AsConfig for FeedAction { - fn as_config(&self) -> Result { - match self { - FeedAction::Transform { path } => { - if let Some(path) = path { - Ok(TransformConfiguration { - plugin_path: path.clone(), - }) - } else { - let plugin_path = get_path_from_openvas(read_openvas_config()?); - Ok(TransformConfiguration { plugin_path }) - } - } - _ => unreachable!("only transform can be TransformConfiguration"), - } - } -} - -impl AsConfig for FeedAction { - fn as_config(&self) -> Result { - match self.clone() { - FeedAction::Update { - redis: Some(redis_url), - path: Some(plugin_path), - signature_check: Some(check_enabled), - } => Ok(FeedUpdateConfiguration { - redis_url, - plugin_path, - check_enabled, - }), - FeedAction::Update { - redis, - path, - signature_check, - } => { - // This is only valid as long as we don't have a proper configuration file and rely on openvas. - let config = read_openvas_config()?; - let redis_url = { - if let Some(rp) = redis { - rp - } else { - let dba = config - .get("default", "db_address") - .expect("openvas -s must contain db_address"); - if dba.starts_with("redis://") || dba.starts_with("unix://") { - dba - } else if dba.starts_with("tcp://") { - dba.replace("tcp://", "redis://") - } else { - format!("unix://{dba}") - } - } - }; - - let plugin_path = { - if let Some(p) = path { - p - } else { - get_path_from_openvas(config) - } - }; - - let check_enabled = { - if let Some(c) = signature_check { - c - } else { - false - } - }; - - Ok(FeedUpdateConfiguration { - plugin_path, - redis_url, - check_enabled, - }) - } - _ => unreachable!("only update can be converted to FeedUpdateConfiguration"), - } - } -} - fn get_path_from_openvas(config: Ini) -> PathBuf { PathBuf::from( config @@ -359,167 +54,19 @@ fn get_path_from_openvas(config: Ini) -> PathBuf { } fn main() { - let matches = Command::new("nasl-cli") - .version("1.0") - .about("Is a CLI tool around NASL.") - .arg(arg!(-v --verbose ... "Prints more details while running").required(false).action(ArgAction::Count)) - .arg(arg!(-vv --very-verbose ... "Prints even more details while running").required(false).action(ArgAction::SetTrue)) - .subcommand_required(true) - .subcommand( - Command::new("feed") - .about("Handles feed related tasks") - .subcommand_required(true) - .subcommand(Command::new("update") - .about("Runs nasl scripts in description mode and updates data into redis") - .arg(arg!(-p --path "Path to the feed.") .required(false) - .value_parser(value_parser!(PathBuf))) - .arg(arg!(-x --"signature-check" "Enable NASL signature check.") .required(false).action(ArgAction::SetTrue)) - .arg(arg!(-r --redis "Redis url. Must either start `unix://` or `redis://`.").required(false)) - ) - .subcommand(Command::new("transform") - .about("Runs nasl scripts in description mode and returns it as a json array into stdout") - .arg(arg!(-p --path "Path to the feed.") .required(false) - .value_parser(value_parser!(PathBuf))) - ) - .subcommand(Command::new("transpile") - .about("Transforms each nasl script and inc file based on the given rules.") - .arg(arg!(-p --path "Path to the feed.") .required(false) - .value_parser(value_parser!(PathBuf))) - .arg(arg!(-r --rules "Path to transpiler rules.").required(true) - .value_parser(value_parser!(PathBuf))) - ) - ) - .subcommand( - Command::new("syntax") - .about("Verifies syntax of NASL files in given dir or file.") - .arg(Arg::new("path").required(true) - .value_parser(value_parser!(PathBuf))) - .arg(arg!(-q --quiet "Prints only error output and no progress.").required(false).action(ArgAction::SetTrue)) - ) - .subcommand( - Command::new("execute") - .about("Executes a nasl-script. -A script can either be a file to be executed or an ID. -When ID is used than a valid feed path must be given within the path parameter.") - .arg(arg!(-p --path "Path to the feed.") .required(false) - .value_parser(value_parser!(PathBuf))) - .arg(Arg::new("script").required(true)) - .arg(arg!(-t --target "Target to scan") .required(false)) - ) - .subcommand( - Command::new("scan-config") - .about("Transforms a scan-config xml to a scan json for openvasd. -When piping a scan json it is enriched with the scan-config xml and may the portlist otherwise it will print a scan json without target or credentials.") - .arg(arg!(-p --path "Path to the feed.") .required(false) - .value_parser(value_parser!(PathBuf))) - .arg(Arg::new("scan-config").required(true).action(ArgAction::Append)) - .arg(arg!(-i --input "Parses scan json from stdin.").required(false).action(ArgAction::SetTrue)) - .arg(arg!(-l --portlist "Path to the port list xml") .required(false)) - ) -.get_matches(); - let verbose = matches - .get_one::("verbose") - .cloned() - .unwrap_or_default(); - let lv = if verbose > 1 { - tracing::Level::TRACE - } else if verbose > 0 { - tracing::Level::DEBUG - } else { - tracing::Level::INFO - }; - tracing_subscriber::fmt() - .with_writer(std::io::stderr) - .with_max_level(lv) - .init(); - let command = match matches.subcommand() { - Some(("feed", args)) => match args.subcommand() { - Some(("update", args)) => { - let path = args.get_one::("path").cloned(); - let redis = args.get_one::("redis").cloned(); - let signature_check = args.get_one::("signature-check").cloned(); - Commands::Feed { - action: FeedAction::Update { - redis, - path, - signature_check, - }, - } - } - Some(("transform", args)) => { - let path = args.get_one::("path").cloned(); - Commands::Feed { - action: FeedAction::Transform { path }, - } - } - - Some(("transpile", args)) => { - let path = match args.get_one("path").cloned() { - Some(x) => x, - None => unreachable!("path is set to required"), - }; - let rules = match args.get_one("rules").cloned() { - Some(x) => x, - None => unreachable!("rules is set to required"), - }; - Commands::Feed { - action: FeedAction::Transpile { - path, - rules, - verbose: verbose > 0, - }, - } - } - _ => unreachable!("subcommand_required prevents None"), - }, - Some(("syntax", args)) => { - let path = match args.get_one::("path").cloned() { - Some(path) => path, - _ => unreachable!("path is set to required"), - }; - let quiet = args.get_one::("quiet").cloned().unwrap_or_default(); - Commands::Syntax { - path, - verbose: verbose > 0, - no_progress: quiet, - } - } - Some(("execute", args)) => { - let feed = args.get_one::("path").cloned(); - let script = match args.get_one::("script").cloned() { - Some(path) => path, - _ => unreachable!("path is set to required"), - }; - let target = args.get_one::("target").cloned(); - - Commands::Execute { - db: Db::InMemory, - feed, - script, - target, - } - } - Some(("scan-config", args)) => { - let feed = args.get_one::("path").cloned(); - let config = args - .get_many::("scan-config") - .expect("scan-config is required") - .cloned() - .collect(); - let port_list = args.get_one::("portlist").cloned(); - tracing::debug!("port_list: {port_list:?}"); - let stdin = args.get_one::("input").cloned().unwrap_or_default(); - Commands::ScanConfig { - feed, - config, - port_list, - stdin, - } - } - _ => unreachable!("subcommand_required prevents None"), - }; - - match command.run() { + let matches = add_verbose( + Command::new("nasl-cli") + .version("1.0") + .about("Is a CLI tool around NASL.") + .subcommand_required(true), + ); + let matches = syntax::extend_args(matches); + let matches = scanconfig::extend_args(matches); + let matches = execute::extend_args(matches); + let matches = feed::extend_args(matches).get_matches(); + let result = run(&matches); + + match result { Ok(_) => {} Err(e) => match e.kind { CliErrorKind::StorageError(StorageError::UnexpectedData(x)) => match &x as &str { @@ -534,3 +81,52 @@ When piping a scan json it is enriched with the scan-config xml and may the port }, } } + +fn run(matches: &ArgMatches) -> Result<(), CliError> { + let functions = [feed::run, syntax::run, execute::run, scanconfig::run]; + for f in functions.iter() { + if let Some(result) = f(matches) { + return result; + } + } + Err(CliError { + filename: "".to_string(), + kind: CliErrorKind::Corrupt(format!( + "No valid subcommand found: {:?}", + matches.subcommand() + )), + }) +} + +pub fn set_logging(level: u8) { + let lv = if level > 1 { + tracing::Level::TRACE + } else if level > 0 { + tracing::Level::DEBUG + } else { + tracing::Level::INFO + }; + tracing_subscriber::fmt() + .with_writer(std::io::stderr) + .with_max_level(lv) + .init(); +} + +pub fn add_verbose(cmd: Command) -> Command { + cmd.arg( + arg!(-v --verbose ... "Prints more details while running") + .required(false) + .action(ArgAction::Count), + ) +} + +pub fn get_args_set_logging<'a>( + root: &'a ArgMatches, + name: &'a str, +) -> Option<(&'a ArgMatches, u8)> { + let verbose = root.get_one::("verbose").cloned().unwrap_or_default(); + let args = root.subcommand_matches(name)?; + let verbose = args.get_one::("verbose").cloned().unwrap_or(verbose); + set_logging(verbose); + Some((args, verbose)) +} diff --git a/rust/nasl-cli/src/scanconfig.rs b/rust/nasl-cli/src/scanconfig.rs index a1d917ea1..2e3db6c19 100644 --- a/rust/nasl-cli/src/scanconfig.rs +++ b/rust/nasl-cli/src/scanconfig.rs @@ -1,8 +1,40 @@ use std::{io::BufReader, path::PathBuf, sync::Arc}; -use crate::{feed_update, get_path_from_openvas, read_openvas_config, CliError, CliErrorKind}; +use clap::{arg, value_parser, Arg, ArgAction, Command}; -pub(crate) fn run( +use crate::{get_path_from_openvas, read_openvas_config, CliError, CliErrorKind}; + +pub fn extend_args(cmd: Command) -> Command { + crate::add_verbose( + cmd.subcommand( + Command::new("scan-config") + .about("Transforms a scan-config xml to a scan json for openvasd. +When piping a scan json it is enriched with the scan-config xml and may the portlist otherwise it will print a scan json without target or credentials.") + .arg(arg!(-p --path "Path to the feed.") .required(false) + .value_parser(value_parser!(PathBuf))) + .arg(Arg::new("scan-config").required(true).action(ArgAction::Append)) + .arg(arg!(-i --input "Parses scan json from stdin.").required(false).action(ArgAction::SetTrue)) + .arg(arg!(-l --portlist "Path to the port list xml") .required(false)) + ) + ) +} + +pub fn run(root: &clap::ArgMatches) -> Option> { + let (args, _) = crate::get_args_set_logging(root, "scan-config")?; + + let feed = args.get_one::("path").cloned(); + let config: Vec = args + .get_many::("scan-config") + .expect("scan-config is required") + .cloned() + .collect(); + let port_list = args.get_one::("portlist").cloned(); + tracing::debug!("port_list: {port_list:?}"); + let stdin = args.get_one::("input").cloned().unwrap_or_default(); + Some(execute(feed.as_ref(), &config, port_list.as_ref(), stdin)) +} + +fn execute( feed: Option<&PathBuf>, config: &[String], port_list: Option<&String>, @@ -43,7 +75,7 @@ pub(crate) fn run( }; tracing::info!("loading feed. This may take a while."); - feed_update::run(Arc::clone(&storage), feed.to_owned(), false)?; + crate::feed::update::run(Arc::clone(&storage), feed.to_owned(), false)?; tracing::info!("feed loaded."); let ports = match port_list { Some(ports) => { diff --git a/rust/nasl-cli/src/syntax_check.rs b/rust/nasl-cli/src/syntax/check.rs similarity index 100% rename from rust/nasl-cli/src/syntax_check.rs rename to rust/nasl-cli/src/syntax/check.rs diff --git a/rust/nasl-cli/src/syntax/mod.rs b/rust/nasl-cli/src/syntax/mod.rs new file mode 100644 index 000000000..100eaaf63 --- /dev/null +++ b/rust/nasl-cli/src/syntax/mod.rs @@ -0,0 +1,37 @@ +use std::path::PathBuf; + +use clap::{arg, value_parser, Arg, ArgAction, Command}; + +use crate::{add_verbose, CliError}; + +pub mod check; + +pub fn run(root: &clap::ArgMatches) -> Option> { + let (args, verbose) = crate::get_args_set_logging(root, "syntax")?; + let path = match args.get_one::("path").cloned() { + Some(path) => path, + _ => unreachable!("path is set to required"), + }; + let quiet = args.get_one::("quiet").cloned().unwrap_or_default(); + + Some(check::run(&path, verbose > 0, quiet)) +} + +pub fn extend_args(cmd: Command) -> Command { + add_verbose( + cmd.subcommand( + Command::new("syntax") + .about("Verifies syntax of NASL files in given dir or file.") + .arg( + Arg::new("path") + .required(true) + .value_parser(value_parser!(PathBuf)), + ) + .arg( + arg!(-q --quiet "Prints only error output and no progress.") + .required(false) + .action(ArgAction::SetTrue), + ), + ), + ) +}