ntdsextract2 0.9.2

Display contents of Active Directory database files (ntds.dit)
use anyhow::{anyhow, bail, Result};

use chrono::{DateTime, Duration, NaiveDate, Utc};
use libesedb::Value;

pub(crate) trait FromValue
where
    Self: Sized,
{
    fn from_value(value: &Value, attribute_name: &str) -> Result<Self> {
        if matches!(value, Value::Null(())) {
            bail!("missing value in '{attribute_name}'");
        } else {
            match Self::from_value_opt(value, attribute_name)? {
                Some(x) => Ok(x),
                None => bail!("value of '{attribute_name}' has no content"),
            }
        }
    }
    fn from_value_opt(value: &Value, attribute_name: &str) -> Result<Option<Self>>;
}

impl FromValue for i32 {
    fn from_value_opt(value: &Value, attribute_name: &str) -> Result<Option<Self>> {
        match value {
            Value::I32(val) => Ok(Some(*val)),
            Value::Null(()) => Ok(None),
            _ => bail!("invalid value detected: {value:?} in attribute '{attribute_name}'"),
        }
    }
}

impl FromValue for u32 {
    fn from_value_opt(value: &Value, attribute_name: &str) -> Result<Option<Self>> {
        match value {
            Value::U8(val) => Ok(Some((*val).into())),
            Value::U16(val) => Ok(Some((*val).into())),
            Value::U32(val) => Ok(Some(*val)),
            Value::I16(val) => Ok(Some((*val).try_into()?)),
            Value::I32(val) => Ok(Some((*val).try_into()?)),
            Value::Null(()) => Ok(None),
            _ => bail!("invalid value detected: {value:?} in attribute '{attribute_name}'"),
        }
    }
}

impl FromValue for String {
    fn from_value_opt(value: &Value, attribute_name: &str) -> Result<Option<Self>> {
        match value {
            Value::Text(val) => Ok(Some(val.to_owned())),
            Value::LargeText(val) => Ok(Some(val.to_owned())),
            Value::Binary(val) | Value::LargeBinary(val) => Ok(Some(hex::encode(val))),
            Value::Null(()) => Ok(None),
            _ => bail!("invalid value detected: {value:?} in field {attribute_name}"),
        }
    }
}

pub(crate) type UtcDatetime = DateTime<Utc>;
impl FromValue for UtcDatetime {
    fn from_value_opt(value: &Value, attrib_name: &str) -> Result<Option<Self>> {
        match value {
            Value::Currency(val) => Ok(Some(currency_to_datetime(*val))),
            Value::Null(()) => Ok(None),
            _ => Err(anyhow!(
                "invalid value detected: {:?} in field {}",
                value,
                attrib_name
            )),
        }
    }
}

fn currency_to_datetime(val: i64) -> DateTime<Utc> {
    let dt_base = DateTime::<Utc>::from_utc(NaiveDate::from_ymd(1601, 1, 1).and_hms(0, 0, 0), Utc);
    let duration = Duration::microseconds(val / 10);
    dt_base + duration
}