ntdsextract2 1.4.27

Display contents of Active Directory database files (ntds.dit)
use cursive_table_view::TableViewItem;
use libntdsextract2::CDatabase;
use sddl::{
    ace::header::{Flag, Flags},
    AccessMask, AccessMaskFlag, Ace, Contains,
};
use strum::IntoStaticStr;

use crate::walk::acl_column::AclColumn;

#[derive(Clone, Eq, PartialEq)]
pub(crate) struct AclLine {
    index: usize,
    subject_name: String,
    operation: AclOperation,
    access_mask: AccessMask,
    extended_right: Option<String>,
    flags: Flags,
}

#[derive(Clone, Eq, PartialEq, IntoStaticStr)]
pub enum AclOperation {
    Allowed,
    Denied,
    Audit,
    MandatoryLabel,
    ResourceAttribute,
    ScopedPolicy,
}

impl AclLine {
    pub fn from<'info, 'db>(database: &CDatabase<'info, 'db>, index: usize, ace: &Ace) -> Self {
        let sid = ace.sid().sid();
        let mut subject_name = sid.to_string();
        // try to get the samAccountName
        if let Some(well_known_name) = ace.sid().well_known_name() {
            subject_name = well_known_name.to_string();
        } else if let Some(entry) = database
            .data_table()
            .data_table()
            .metadata()
            .entries_with_rid(*sid.get_rid())
            .next()
        {
            if let Some(sam_account_name) = entry.sam_account_name() {
                subject_name = sam_account_name.clone();
            } else {
                log::error!("{subject_name} has no samAccountName attribute");
            }
        } else {
            //log::error!("unable to find name for {subject_name}");
        }
        let access_mask = *ace.header().mask();
        let operation = match ace {
            Ace::ACCESS_ALLOWED_ACE { .. } => AclOperation::Allowed,
            Ace::ACCESS_ALLOWED_OBJECT_ACE { .. } => AclOperation::Allowed,
            Ace::ACCESS_DENIED_ACE { .. } => AclOperation::Denied,
            Ace::ACCESS_DENIED_OBJECT_ACE { .. } => AclOperation::Denied,
            Ace::ACCESS_ALLOWED_CALLBACK_ACE { .. } => AclOperation::Allowed,
            Ace::ACCESS_DENIED_CALLBACK_ACE { .. } => AclOperation::Denied,
            Ace::ACCESS_ALLOWED_CALLBACK_OBJECT_ACE { .. } => AclOperation::Allowed,
            Ace::ACCESS_DENIED_CALLBACK_OBJECT_ACE { .. } => AclOperation::Denied,
            Ace::SYSTEM_AUDIT_ACE { .. } => AclOperation::Audit,
            Ace::SYSTEM_AUDIT_OBJECT_ACE { .. } => AclOperation::Audit,
            Ace::SYSTEM_AUDIT_CALLBACK_ACE { .. } => AclOperation::Audit,
            Ace::SYSTEM_MANDATORY_LABEL_ACE { .. } => AclOperation::MandatoryLabel,
            Ace::SYSTEM_AUDIT_CALLBACK_OBJECT_ACE { .. } => AclOperation::Audit,
            Ace::SYSTEM_RESOURCE_ATTRIBUTE_ACE { .. } => AclOperation::ResourceAttribute,
            Ace::SYSTEM_SCOPED_POLICY_ID_ACE { .. } => AclOperation::ScopedPolicy,
        };
        let mut extended_right = None;
        if ace.header().mask().contains(AccessMaskFlag::CONTROL_ACCESS) {
            if let Some(guid) = ace.object_type() {
                if let Some(er) = database.data_table().extended_rights().get(guid) {
                    extended_right = Some(er.to_owned());
                } else {
                    log::error!("unable to find extended right with GUID {guid}");
                    extended_right = Some(guid.to_string());
                }
            }
        }

        let flags = *ace.header().ace_flags();

        Self {
            index,
            subject_name,
            operation,
            access_mask,
            extended_right,
            flags,
        }
    }
}

impl TableViewItem<AclColumn> for AclLine {
    fn to_column(&self, column: AclColumn) -> String {
        match column {
            AclColumn::SubjectName => self.subject_name.clone(),
            AclColumn::Operation => {
                let operation: &str = (&self.operation).into();
                operation.to_owned()
            }
            AclColumn::AccessMask => self.access_mask.to_string(),
            AclColumn::ExtendedRight => self
                .extended_right
                .as_ref()
                .map_or("".to_string(), |s| s.to_owned()),
            AclColumn::ObjectInherit => if self.flags.contains(Flag::OBJECT_INHERIT_ACE) {
                ""
            } else {
                ""
            }
            .to_owned(),
            AclColumn::ContainerInherit => if self.flags.contains(Flag::CONTAINER_INHERIT_ACE) {
                ""
            } else {
                ""
            }
            .to_owned(),
            AclColumn::NoPropagate => if self.flags.contains(Flag::NO_PROPAGATE_INHERIT_ACE) {
                ""
            } else {
                ""
            }
            .to_owned(),
            AclColumn::InheritOnly => if self.flags.contains(Flag::INHERIT_ONLY_ACE) {
                ""
            } else {
                ""
            }
            .to_owned(),
            AclColumn::Inherited => if self.flags.contains(Flag::INHERITED_ACE) {
                ""
            } else {
                ""
            }
            .to_owned(),
        }
    }

    fn cmp(&self, other: &Self, _column: AclColumn) -> std::cmp::Ordering
    where
        Self: Sized,
    {
        // ignore any ordering, because the stored order decides
        // about allowed or denied access
        self.index.cmp(&other.index)
    }
}