use anyhow::anyhow;
use hashbrown::{HashMap, HashSet};
use std::{
fmt::Display,
hash::Hash,
sync::{Arc, Mutex, Weak},
};
use getset::Getters;
use lazy_static::lazy_static;
use crate::{
cache::{MetaDataCache, RecordPointer, SpecialRecords},
ntds::SdTable,
win32_types::{Rdn, SecurityDescriptor},
};
lazy_static! {
static ref DOMAINROOT_CHILDREN: HashSet<String> = HashSet::from_iter(vec![
"Deleted Objects".to_string(),
"Configuration".to_string(),
"Builtin".to_string(),
"NTDS Quotas".to_string()
].into_iter());
}
#[derive(Getters, Debug)]
#[getset(get = "pub")]
pub struct ObjectTreeEntry {
name: Rdn,
relative_distinguished_name: String,
distinguished_name: String,
record_ptr: RecordPointer,
_sddl: Option<Result<SecurityDescriptor, crate::ntds::Error>>,
children: Mutex<hashbrown::HashSet<Arc<ObjectTreeEntry>>>,
parent: Option<Weak<Self>>,
}
impl Eq for ObjectTreeEntry {}
impl PartialEq for ObjectTreeEntry {
fn eq(&self, other: &Self) -> bool {
self.name == other.name && self.record_ptr == other.record_ptr
}
}
impl Hash for ObjectTreeEntry {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.name.hash(state);
self.record_ptr.hash(state);
}
}
impl Ord for ObjectTreeEntry {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.distinguished_name.cmp(&other.distinguished_name)
}
}
impl PartialOrd for ObjectTreeEntry {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Display for ObjectTreeEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let is_deleted = self.name().deleted_from_container().is_some();
let display_name = self.relative_distinguished_name();
let sddl = "";
let flags = if is_deleted { "DELETED; " } else { "" };
write!(f, "{display_name} ({flags}{}{sddl})", self.record_ptr)
}
}
impl ObjectTreeEntry {
pub fn get(&self, rdn: &str) -> Option<Arc<Self>> {
log::debug!("searching for {rdn}");
if let Ok(children) = self.children.lock() {
for child in children.iter() {
log::debug!(" candidate is {}", child.name);
if child.name.name() == rdn {
return Some(Arc::clone(child));
}
}
}
None
}
pub(crate) fn populate_object_tree(
metadata: &MetaDataCache,
sd_table: Option<Arc<SdTable>>,
record_index: &mut HashMap<RecordPointer, Weak<Self>>,
) -> Arc<ObjectTreeEntry> {
log::info!("populating the object tree");
Self::create_tree_node(
metadata.root(),
metadata,
sd_table.as_deref(),
None,
record_index,
)
}
fn create_tree_node(
record_ptr: &RecordPointer,
metadata: &MetaDataCache,
sd_table: Option<&SdTable>,
parent: Option<Weak<Self>>,
record_index: &mut HashMap<RecordPointer, Weak<Self>>,
) -> Arc<ObjectTreeEntry> {
let entry = &metadata[record_ptr];
let name = entry.rdn().to_owned();
let relative_distinguished_name = metadata.rdn(entry);
let distinguished_name = match &parent {
Some(parent) => match parent.upgrade() {
Some(parent) => {
if parent.parent.is_none() {
log::debug!("hiding the $ROOT_OBJECT$ item");
relative_distinguished_name.clone()
} else {
format!(
"{relative_distinguished_name},{}",
parent.distinguished_name()
)
}
}
None => {
panic!(
"unable to upgrade weak link to parent object; there \
seems to be an inconsistency in the object tree"
);
}
},
None => {
log::debug!("found the object tree root");
relative_distinguished_name.clone()
}
};
let _sddl = sd_table.and_then(|sd_table| {
entry
.sd_id()
.as_ref()
.and_then(|sd_id| sd_table.descriptor(sd_id))
});
let me = Arc::new(ObjectTreeEntry {
name,
relative_distinguished_name,
distinguished_name,
record_ptr: *record_ptr,
children: Mutex::new(hashbrown::HashSet::new()),
parent,
_sddl,
});
record_index.insert(*record_ptr, Arc::downgrade(&me));
#[allow(clippy::mutable_key_type)]
let children: hashbrown::HashSet<_> = metadata
.children_of(record_ptr)
.map(|c| {
Self::create_tree_node(
c.record_ptr(),
metadata,
sd_table,
Some(Arc::downgrade(&me)),
record_index,
)
})
.collect();
*me.children.lock().unwrap() = children;
me
}
pub fn get_special_records(root: Arc<ObjectTreeEntry>) -> anyhow::Result<SpecialRecords> {
log::info!("obtaining special record ids");
let domain_root =
ObjectTreeEntry::find_domain_root(&root).ok_or(anyhow!("db has no domain root"))?;
log::info!("found domain root '{}'", domain_root[0].name());
let configuration = domain_root[0]
.find_child_by_name("Configuration")
.ok_or(anyhow!("db has no `Configuration` entry"))?;
let schema_subpath = configuration
.find_child_by_name("Schema")
.ok_or(anyhow!("db has no `Schema` entry"))?;
let deleted_objects = domain_root[0]
.find_child_by_name("Deleted Objects")
.ok_or(anyhow!("db has no `Deleted Objects` entry"))?;
let extended_rights_container = configuration
.find_child_by_name("Extended Rights")
.ok_or(anyhow!("db has no `Extended Rights` entry"))?;
Ok(SpecialRecords::new(
schema_subpath,
deleted_objects,
extended_rights_container,
))
}
pub fn find_domain_root(root: &Arc<ObjectTreeEntry>) -> Option<Vec<Arc<ObjectTreeEntry>>> {
let my_children: HashSet<_> = root
.children()
.lock()
.unwrap()
.iter()
.map(|o| o.name().to_string())
.collect();
if my_children.is_superset(&DOMAINROOT_CHILDREN) {
return Some(vec![Arc::clone(root)]);
} else {
for child in root.children().lock().unwrap().iter() {
if let Some(mut path) = Self::find_domain_root(child) {
path.push(Arc::clone(root));
return Some(path);
}
}
}
None
}
pub fn find_child_by_name(&self, name: &str) -> Option<Arc<ObjectTreeEntry>> {
self.children()
.lock()
.unwrap()
.iter()
.find(|e| e.name().name() == name)
.cloned()
}
}