use std::{
fs::{self, File},
io::{BufReader, BufWriter},
os::unix::fs::PermissionsExt,
path::PathBuf, sync::Arc,
};
use getset::Getters;
use serde::Serialize;
use crate::{
cache::{self, MetaDataCache},
cli::{EntryFormat, OutputOptions, TimelineFormat},
ntds::{self, Computer, DataTable, DataTableRecord, Group, LinkTable, ObjectType, Person, Schema, SdTable},
object_tree::ObjectTree,
EntryId, EsedbInfo, SerializationType,
};
#[derive(Getters)]
#[getset(get="pub")]
pub struct CDatabase<'info, 'db> {
esedbinfo: &'info EsedbInfo<'db>,
data_table: DataTable<'info, 'db>,
link_table: Arc<LinkTable>,
sd_table: Option<Arc<SdTable>>,
}
impl<'info, 'db> CDatabase<'info, 'db> {
pub fn new(
esedbinfo: &'info EsedbInfo<'db>,
load_sd_table: bool,
cache_file: Option<PathBuf>,
) -> anyhow::Result<Self> {
let cached_sd_table = cache::SdTable::try_from("sd_table", esedbinfo)?;
let sd_table = if load_sd_table {
match SdTable::new(&cached_sd_table) {
Ok(sd_table) => Some(Arc::new(sd_table)),
Err(why) => {
log::warn!("Error while reading table 'sd_table': {why}. Security descriptors will not be available");
None
}
}
} else {
None
};
let metadata_cache = if let Some(cache_file) = cache_file {
if cache_file.exists() {
log::info!("using cache from {}", cache_file.to_string_lossy());
rmp_serde::from_read(BufReader::new(File::open(&cache_file)?))?
} else {
log::warn!("cache file does not exist, creating it");
let metadata_cache = MetaDataCache::try_from(esedbinfo)?;
let file = BufWriter::new(File::create(&cache_file)?);
let mut serializer = rmp_serde::encode::Serializer::new(file);
metadata_cache.serialize(&mut serializer)?;
let mut permissions = fs::metadata(&cache_file)?.permissions();
permissions.set_mode(0o600);
fs::set_permissions(&cache_file, permissions)?;
metadata_cache
}
} else {
MetaDataCache::try_from(esedbinfo)?
};
let object_tree = Arc::new(ObjectTree::new(&metadata_cache, sd_table.clone()));
let special_records = object_tree.get_special_records()?;
let schema_record_id = special_records.schema().record_ptr();
log::debug!("found the schema record id is '{}'", schema_record_id);
let schema = Schema::new(&metadata_cache, &special_records);
let cached_data_table = cache::DataTable::new(
esedbinfo.data_table(),
"datatable",
esedbinfo,
metadata_cache,
)?;
let cached_link_table =
cache::LinkTable::try_from(esedbinfo.link_table(), "link_table", esedbinfo)?;
let link_table = Arc::new(LinkTable::new(
cached_link_table,
&cached_data_table,
*schema_record_id,
)?);
let data_table = DataTable::new(
cached_data_table,
object_tree,
*schema_record_id,
Arc::clone(&link_table),
sd_table.clone(),
schema,
special_records,
)?;
Ok(Self {
esedbinfo,
link_table,
data_table,
sd_table,
})
}
pub fn show_users<T: SerializationType>(&self, options: &OutputOptions) -> anyhow::Result<()> {
self.show_typed_objects::<Person<T>>(options, ObjectType::Person)
}
pub fn show_groups<T: SerializationType>(&self, options: &OutputOptions) -> anyhow::Result<()> {
self.show_typed_objects::<Group<T>>(options, ObjectType::Group)
}
pub fn show_computers<T: SerializationType>(
&self,
options: &OutputOptions,
) -> anyhow::Result<()> {
self.show_typed_objects::<Computer<T>>(options, ObjectType::Computer)
}
pub fn show_typed_objects<O: ntds::FromDataTable + ntds::IsMemberOf>(
&self,
options: &OutputOptions,
object_type: ObjectType,
) -> anyhow::Result<()> {
self.data_table
.show_typed_objects::<O>(options, object_type)
}
pub fn show_type_names<T>(&self, options: &OutputOptions) -> anyhow::Result<()>
where
T: SerializationType,
{
self.data_table.show_type_names::<T>(options)
}
pub fn show_timeline(
&self,
options: &OutputOptions,
include_deleted: bool,
format: &TimelineFormat,
) -> anyhow::Result<()> {
self.data_table
.show_timeline(options, &self.link_table, include_deleted, format)
}
pub fn entry(&self,
entry_id: EntryId,) -> crate::ntds::Result<Option<DataTableRecord<'info, 'db>>> {
self.data_table.entry(entry_id)
}
pub fn show_entry(
&self,
entry_id: EntryId,
entry_format: EntryFormat,
) -> crate::ntds::Result<()> {
self.data_table.show_entry(entry_id, entry_format)
}
pub fn show_tree(&self, max_depth: u8) -> crate::ntds::Result<()> {
self.data_table.show_tree(max_depth)
}
pub fn object_tree(&self) -> &ObjectTree {
self.data_table.object_tree()
}
pub fn search_entries(&self, regex: &str) -> anyhow::Result<()> {
self.data_table.search_entries(regex)
}
pub fn show_objects_by_permission(
&self,
include_dn: bool,
format: &crate::cli::OutputFormat,
ace_filter: fn(&sddl::Ace) -> Option<&sddl::Sid>,
) -> Result<(), anyhow::Error> {
self.data_table
.show_objects_by_permission(include_dn, format, ace_filter)
}
pub fn show_hidden_objects(
&self,
include_dn: bool,
format: &crate::cli::OutputFormat,
) -> Result<(), anyhow::Error> {
self.data_table.show_hidden_objects(include_dn, format)
}
}