ntdsextract2 1.4.29

Display contents of Active Directory database files (ntds.dit)
use libntdsextract2::{ntds::TransitiveClosure, win32_types::Rdn, CDatabase, EntryId, Resolve};

use crate::walk::{
    acl_line::AclLine, attribute_line_set::AttributeLineSet,
    membership_tree_entry::MembershipTreeEntry,
};

pub struct EntryAttributes {
    pub sam_account_name: Option<String>,
    pub object_name: Rdn,
    pub attributes: Vec<AttributeLineSet>,
    pub sacls: Vec<AclLine>,
    pub dacls: Vec<AclLine>,
    pub owner: Option<String>,
    pub group: Option<String>,
    pub member_of: Option<TransitiveClosure<MembershipTreeEntry>>,
    pub members: Option<TransitiveClosure<MembershipTreeEntry>>,
}

impl EntryAttributes {
    pub fn new<'info, 'db>(database: &CDatabase<'info, 'db>, entry_id: EntryId) -> Self {
        let attributes: Vec<_>;
        let mut sacls = Vec::new();
        let mut dacls = Vec::new();
        let mut owner = None;
        let mut group = None;
        let member_of;
        let members;

        if let Ok(Some(entry)) = database.entry(entry_id) {
            let sam_account_name = entry.att_sam_account_name_opt().unwrap();
            let object_name = entry.att_object_name2().expect("missing object name");

            attributes = entry
                .all_attributes()
                .iter()
                .map(|(i, a)| AttributeLineSet::from(database, i, a))
                .collect();

            match entry.security_descriptor(database.sd_table().as_ref().unwrap()) {
                Ok(sd) => {
                    if let Some(sd) = sd.as_ref() {
                        if let Some(user_sid) = sd.as_ref().owner() {
                            match database.resolve(user_sid.sid()) {
                                Some(entry) => {
                                    owner = Some(
                                        entry
                                            .sam_account_name()
                                            .as_ref()
                                            .map(|s| &s[..])
                                            .unwrap_or_else(|| entry.rdn().as_str())
                                            .to_string(),
                                    );
                                }
                                None => {
                                    log::error!("Did not find an entry for SID {}", user_sid.sid());
                                    owner = Some(user_sid.sid().to_string());
                                }
                            }
                        }
                        if let Some(group_sid) = sd.as_ref().group() {
                            match database.resolve(group_sid.sid()) {
                                Some(entry) => {
                                    group = Some(
                                        entry
                                            .sam_account_name()
                                            .as_ref()
                                            .map(|s| &s[..])
                                            .unwrap_or_else(|| entry.rdn().as_str())
                                            .to_string(),
                                    );
                                }
                                None => {
                                    log::error!("Did not find an entry for SID {}", group_sid.sid());
                                    group = Some(group_sid.sid().to_string());
                                }
                            }
                        }
                        if let Some(acl) = sd.as_ref().sacl().as_ref() {
                            sacls = acl
                                .ace_list()
                                .iter()
                                .enumerate()
                                .map(|(index, ace)| AclLine::from(database, index, ace))
                                .collect();
                        }
                        if let Some(acl) = sd.as_ref().dacl().as_ref() {
                            dacls = acl
                                .ace_list()
                                .iter()
                                .enumerate()
                                .map(|(index, ace)| AclLine::from(database, index, ace))
                                .collect();
                        }
                    }
                }
                Err(why) => {
                    log::error!("unable to access security descriptor of {entry_id:?}: {why}");
                }
            }

            member_of = database
                .link_table()
                .transitive_member_of(&entry.ds_record_id().unwrap())
                .filter_map(|record_id| MembershipTreeEntry::try_from(&record_id, database));
            members = database
                .link_table()
                .transitive_members(&entry.ds_record_id().unwrap())
                .filter_map(|record_id| MembershipTreeEntry::try_from(&record_id, database));
            Self {
                sam_account_name,
                object_name,
                attributes,
                sacls,
                dacls,
                owner,
                group,
                member_of,
                members,
            }
        } else {
            panic!("found no database entry for id {entry_id:?}");
        }
    }
}