malwaredb 0.3.2

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

use super::config::Config;
use malwaredb_server::State;

use std::process::ExitCode;

use clap::{Parser, Subcommand};

/// Malware Database Server
///
/// `load` will parse a provided config file for the needed parameters
///
/// `config` allows config parameters to be provided on the command line
///
/// Neither, and Malware DB will attempt to find a config file. See [`Config`] for details.
#[derive(Parser, Debug)]
pub struct Run {
    #[clap(subcommand)]
    pub cmd: Option<Subcommands>,
}

impl Run {
    pub async fn into_state(self) -> anyhow::Result<State> {
        match self.cmd {
            Some(Subcommands::Load(loader)) => loader.config()?,
            Some(Subcommands::Config(config)) => config,
            None => Config::from_found_files()?,
        }
        .into_state()
        .await
    }

    pub async fn execute(self) -> anyhow::Result<ExitCode> {
        let state = self.into_state().await?;

        #[cfg(feature = "admin")]
        {
            let groups = state.db_type.list_groups().await?;
            if groups.len() < 2 {
                println!("First time? Create a group and source to get started loading samples into MalwareDB.");
                if let Ok(current_exe) = std::env::current_exe() {
                    let current_exe = current_exe.to_str().unwrap();
                    println!("Run `{current_exe} admin --help` for more information.");
                }
            }
        }

        state
            .serve(
                #[cfg(target_family = "windows")]
                None,
            )
            .await?;
        Ok(ExitCode::SUCCESS)
    }
}

/// Provide a path to a configuration file
#[derive(Clone, Parser, Debug, PartialEq)]
pub struct Load {
    #[arg(value_name = "FILE", value_hint = clap::ValueHint::FilePath)]
    file: std::path::PathBuf,
}

impl Load {
    pub fn config(&self) -> anyhow::Result<Config> {
        Config::from_file(&self.file)
    }
}

#[derive(Subcommand, Debug)]
pub enum Subcommands {
    Load(Load),
    Config(Config),
}

#[cfg(test)]
mod tests {
    use malwaredb_server::StateBuilder;

    use anyhow::Context;

    const DB_FILE: &str = "testing_sqlite.db";

    #[tokio::test]
    async fn state_info() {
        let builder = StateBuilder::new(&format!("file:{DB_FILE}"), None)
            .await
            .unwrap();
        let state = builder.into_state().await.unwrap();

        let info = state.get_info().await.unwrap();
        eprintln!("State info(): {info:?}");

        std::fs::remove_file(DB_FILE)
            .context(format!("failed to delete SQLite file {DB_FILE}"))
            .unwrap();
    }
}