ntdsextract2 1.4.33

Display contents of Active Directory database files (ntds.dit)
use std::{env::temp_dir, fs::{self, File}, io::{BufReader, Read}, os::unix::fs::PermissionsExt, path::PathBuf};

use anyhow::bail;
use clap::Parser;
use getset::Getters;
use sha1::{Sha1, Digest};

use super::Commands;

#[derive(Parser, Getters)]
#[getset(get = "pub")]
#[clap(name="ntdsextract2", author, version, about, long_about = None)]
pub struct Args {
    #[clap(subcommand)]
    pub(crate) command: Commands,

    /// name of the file to analyze
    pub(crate) ntds_file: String,

    #[clap(flatten)]
    pub(crate) verbose: clap_verbosity_flag::Verbosity,

    #[clap(long = "use-cache", default_value_t = false)]
    /// set this flag if you want to use a cache file, to speed up you work
    /// with ntdsextract2
    pub(crate) use_cache: bool,

    /// cache file to use. If you use the cache, but don't specify a filename,
    /// ntdsextract2 will generate a filename and store a cache in /tmp
    ///
    /// Be aware that ntdsextract2 will overwrite this file at any time without
    /// any warning!!!
    #[clap(long = "cache-file")]
    pub(crate) cache_file: Option<String>,
}

impl Args {
    pub fn find_cache_file(&self) -> Result<Option<PathBuf>, anyhow::Error> {
        Ok(if *self.use_cache() {
            if let Some(cache_file) = self.cache_file() {
                let cache_file = PathBuf::from(cache_file).canonicalize()?;

                if !cache_file.exists() {
                    // the cache file does not exist, so we need to create one.
                    // Therefore, the parent directory must be writable
                    if let Some(parent) = cache_file.parent() {
                        let permissions = fs::metadata(parent)?.permissions();
                        if permissions.readonly() {
                            bail!(
                            "directory '{}' must be writable in order to store a cache file there",
                            parent.to_string_lossy()
                        );
                        }

                        let permissions = fs::metadata(&cache_file)?.permissions();
                        if permissions.mode() & 0o177 != 0 {
                            bail!(
                                "cache file '{}' has too lose permissions set",
                                cache_file.to_string_lossy()
                            );
                        }
                        Some(cache_file)
                    } else {
                        bail!("invalid path: '{}'", cache_file.to_string_lossy());
                    }
                } else {
                    if !cache_file.is_file() {
                        bail!(
                            "Path '{}' does not point to a file",
                            cache_file.to_string_lossy()
                        );
                    }
                    Some(cache_file)
                }
            } else {
                // read the first 4k bytes from the database to create a simple fingerprint
                let buffer = {
                    let mut reader = BufReader::new(File::open(self.ntds_file()).unwrap());
                    let mut buffer = [0; 4096];
                    let _ = reader.read(&mut buffer)?;
                    buffer
                };
                let hash = {
                    let mut hasher = Sha1::new();
                    hasher.update(buffer);
                    let result = hasher.finalize();
                    hex::encode(&result[0..8])
                };

                let mut cache_file = temp_dir();
                cache_file.push(format!("ntdsextract2_cache_{}", hash));
                if cache_file.exists() {
                    let permissions = fs::metadata(&cache_file)?.permissions();
                    if permissions.mode() & 0o177 != 0 {
                        bail!(
                            "cache file '{}' has too loose permissions set: {:o}",
                            cache_file.to_string_lossy(),
                            permissions.mode()
                        );
                    }
                }
                Some(cache_file)
            }
        } else {
            None
        })
    }
}