ntdsextract2 1.4.29

Display contents of Active Directory database files (ntds.dit)
use getset::Getters;
use hashbrown::{HashMap, HashSet};

use crate::cache::RecordPointer;
use crate::cache::{self, RecordId};
use crate::ntds::link_table_builder::LinkTableBuilder;
use crate::win32_types::Rdn;
use crate::{Membership, MembershipSet, SerializationType};

use super::DataTable;

/// wraps a ESEDB Table.
/// This class assumes the a NTDS link_table is being wrapped
pub struct LinkTable {
    pub(crate) forward_map: HashMap<RecordId, HashSet<RecordPointer>>,
    pub(crate) backward_map: HashMap<RecordId, HashSet<RecordPointer>>,
}

impl LinkTable {
    /// create a new datatable wrapper
    pub fn new<'info, 'db>(
        link_table: cache::LinkTable<'info, 'db>,
        data_table: &cache::DataTable<'info, 'db>,
        schema_record_id: RecordPointer,
    ) -> crate::ntds::Result<Self> {
        log::info!("reading link information and creating link_table cache");

        let builder = LinkTableBuilder::from(link_table, data_table, schema_record_id)?;
        builder.build(data_table.metadata())
    }

    pub fn member_of(&self, dnt: &RecordId) -> Option<&HashSet<RecordPointer>> {
        self.backward_map.get(dnt)
    }

    pub fn members(&self, dnt: &RecordId) -> Option<&HashSet<RecordPointer>> {
        self.forward_map.get(dnt)
    }

    pub fn member_names_of(&self, object_id: RecordId, data_table: &DataTable<'_, '_>) -> Vec<Rdn> {
        let member_of = if let Some(children) = self.member_of(&object_id) {
            children
                .iter()
                .map(|child_id| &data_table.data_table().metadata()[child_id])
                .map(|record| record.rdn().clone())
                .collect()
        } else {
            vec![]
        };
        member_of
    }

    pub fn member_refs_of<T: SerializationType>(
        &self,
        object_id: RecordId,
        data_table: &DataTable<'_, '_>,
    ) -> MembershipSet<T> {
        let member_of = if let Some(children) = self.member_of(&object_id) {
            children
                .iter()
                .map(|child_id| &data_table.data_table().metadata()[child_id])
                .map(|record| {
                    (
                        *record.record_ptr(),
                        record.rdn().clone(),
                        record.sid().clone(),
                        record.sam_account_name().clone(),
                    )
                })
                .collect()
        } else {
            vec![]
        };
        MembershipSet::<T>::from(member_of.into_iter().map(Membership::from))
    }

    pub fn transitive_member_of(&self, dnt: &RecordId) -> TransitiveClosure<RecordId> {
        Self::transitive_closure(dnt, &self.backward_map)
    }

    pub fn transitive_members(&self, dnt: &RecordId) -> TransitiveClosure<RecordId> {
        Self::transitive_closure(dnt, &self.forward_map)
    }

    fn transitive_closure(
        record_id: &RecordId,
        mapping_table: &HashMap<RecordId, HashSet<RecordPointer>>,
    ) -> TransitiveClosure<RecordId> {
        TransitiveClosure::<RecordId> {
            value: *record_id,
            children: if let Some(children) = mapping_table.get(record_id) {
                children
                    .iter()
                    .map(|ch| *(ch.ds_record_id()))
                    .map(|child_record_id| {
                        Self::transitive_closure(&child_record_id, mapping_table)
                    })
                    .collect()
            } else {
                Vec::new()
            },
        }
    }
}

#[derive(Getters)]
#[getset(get = "pub")]
pub struct TransitiveClosure<T> {
    value: T,
    children: Vec<Self>,
}

impl<S> TransitiveClosure<S> {
    pub fn filter_map<D, F>(self, mapping: F) -> Option<TransitiveClosure<D>>
    where
        F: Fn(S) -> Option<D> + Clone,
    {
        match mapping(self.value) {
            None => None,
            Some(value) => Some(TransitiveClosure::<D> {
                value,
                children: self
                    .children
                    .into_iter()
                    .filter_map(|ch| ch.filter_map(mapping.clone()))
                    .collect(),
            }),
        }
    }

    pub fn explode(self) -> (S, Vec<Self>) {
        (self.value, self.children)
    }
}