malwaredb 0.3.3

Service for storing malicious, benign, or unknown files and related metadata and relationships.
// SPDX-License-Identifier: Apache-2.0

//! How to generate shell completions.
//! Instructions based on [Clap](https://github.com/clap-rs/clap/blob/master/clap_complete/examples/completion-derive.rs).
//!
//! Usage with zsh:
//! ```console
//! $ cargo run --features=admin --bin mdb_server -- generate=zsh > /usr/local/share/zsh/site-functions/_completion_derive
//! $ compinit
//! $ ./target/debug/mdb_server --<TAB>
//! ```
//! fish:
//! ```console
//! $ cargo run --features=admin --bin mdb_server -- generate=fish > completion_derive.fish
//! $ . ./completion_derive.fish
//! $ ./target/debug/mdb_server --<TAB>
//! ```
//! bash:
//! ```console
//! $ cargo run --features=admin --bin mdb_server -- generate=bash > completion_derive.bash
//! $ source completion_derive.bash
//! $ ./target/debug/mdb_server --<TAB>
//! ```

#[cfg(feature = "admin")]
mod admin;
mod cart_io;
pub(crate) mod config;
mod run;
mod service;
#[cfg(feature = "vt")]
mod vt;

#[cfg(feature = "admin-gui")]
use crate::gui;
use service::{Install, Installer};

use std::process::ExitCode;
use std::str::FromStr;

use anyhow::{bail, Result};
use clap::{Command, CommandFactory, Parser, Subcommand};
use clap_complete::{generate, Shell};

pub const VERSION: &str = concat!(env!("MDB_VERSION"), " ", env!("MDB_BUILD_DATE"));

/// Malware Database
///
/// Malware Database maintains the bookkeeping for unknown, malicious, and benign binaries
/// using a database, and optionally storing the files in a given location for later retrieval.
#[derive(Parser, Debug)]
#[command(author, about, version = VERSION)]
pub struct Options {
    /// Subcommands (with their own options)
    #[clap(subcommand)]
    cmd: Option<Subcommands>,

    /// Logging options
    #[clap(flatten)]
    pub logger: LogOptions,
}

/// Common logging / output options
#[derive(Parser, Clone, Debug, PartialEq)]
pub struct LogOptions {
    /// Set fancier logging filters.
    ///
    /// This is equivalent to the `RUST_LOG` environment variable.
    #[clap(long = "log-filter", env = "MDB_LOG")]
    pub log_filter: Option<String>,

    /// Set log output target ("stderr", "stdout")
    #[clap(long, default_value = "stderr")]
    pub log_target: LogTarget,
}

/// Represents logging target.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LogTarget {
    Stdout,
    Stderr,
}

/// Convert a str to a Log Target. This is how Clap parses CLI args.
impl FromStr for LogTarget {
    type Err = anyhow::Error;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_ascii_lowercase().as_str() {
            "stdout" => Ok(Self::Stdout),
            "stderr" => Ok(Self::Stderr),
            _ => bail!("unknown log target {s:?}"),
        }
    }
}

impl Options {
    pub async fn execute(self) -> Result<ExitCode> {
        match self.cmd {
            Some(Subcommands::Run(cmd)) => cmd.execute().await,
            #[cfg(feature = "admin")]
            Some(Subcommands::Admin(cmd)) => cmd.execute().await,
            #[cfg(feature = "admin-gui")]
            Some(Subcommands::AdminGui(cmd)) => cmd.execute().await,
            Some(Subcommands::InstallService(install)) => install.do_install(),
            #[cfg(feature = "vt")]
            Some(Subcommands::Vt(cmd)) => cmd.execute().await,
            Some(Subcommands::Cart(cmd)) => cmd.execute(),
            Some(Subcommands::Generate(cmd)) => Ok(cmd.execute()),
            None => {
                eprintln!("Please run with `--help` for options.");
                Ok(ExitCode::FAILURE)
            }
        }
    }
}

#[derive(Subcommand, Debug)]
enum Subcommands {
    #[command(visible_alias = "hint")]
    Run(run::Run),
    #[cfg(feature = "admin")]
    Admin(admin::Admin),
    #[cfg(feature = "admin-gui")]
    AdminGui(gui::AdminGui),
    /// Register Malware DB as a system service
    InstallService(Install),
    /// Virus Total options
    #[cfg(feature = "vt")]
    Vt(vt::VtOption),
    /// Encode or decode a `CaRT` file
    Cart(cart_io::CartIO),
    /// Shell autocomplete generation
    Generate(Generator),
}

#[derive(Parser, Debug, Clone, PartialEq)]
pub struct Generator {
    #[arg(value_enum)]
    pub(crate) generator: Shell,
}

impl Generator {
    pub fn execute(&self) -> ExitCode {
        let mut cmd = Options::command();
        eprintln!("Generating completion file for {:?}...", self.generator);
        print_completions(self.generator, &mut cmd);
        ExitCode::SUCCESS
    }
}

pub fn print_completions<G: clap_complete::Generator>(gen: G, cmd: &mut Command) {
    generate(gen, cmd, cmd.get_name().to_string(), &mut std::io::stdout());
}

#[cfg(test)]
mod tests {
    use super::Options;

    use clap::CommandFactory;

    #[test]
    fn verify_cli() {
        Options::command().debug_assert();
    }
}