use crate::cache::RecordPointer;
use crate::cli::OutputOptions;
use crate::win32_types::{Rdn, SecurityDescriptor, TimelineEntry, TruncatedWindowsFileTime, WindowsFileTime};
use crate::win32_types::{SamAccountType, Sid, UserAccountControl};
use crate::{FormattedValue, Membership, MembershipSet, SerializationType};
use bodyfile::Bodyfile3Line;
use dfir_windows_types::Guid;
use getset::Getters;
use serde::ser::SerializeStruct;
use serde::{Deserialize, Serialize};
use crate::ntds::{
DataTable, DataTableRecord, FromDataTable, HasObjectType, IsMemberOf, LinkTable,
};
use std::marker::PhantomData;
use super::{HasSerializableFields, SpecificObjectAttributes};
#[derive(Getters, Deserialize)]
#[getset(get = "pub")]
#[serde(bound = "T: SerializationType")]
pub struct Object<T, O, A>
where
O: HasObjectType,
T: SerializationType,
A: SpecificObjectAttributes,
{
distinguished_name: FormattedValue<String>,
sid: Option<Sid>,
user_principal_name: Option<String>,
service_principal_name: Option<String>,
rdn: Option<Rdn>,
sam_account_name: Option<String>,
sam_account_type: Option<SamAccountType>,
user_account_control: Option<UserAccountControl>,
logon_count: Option<i32>,
bad_pwd_count: Option<i32>,
admin_count: Option<i32>,
is_deleted: bool,
#[allow(dead_code)]
primary_group_id: Option<i32>,
primary_group: Option<Membership<T>>,
member_of: MembershipSet<T>,
comment: Option<String>,
record_time: Option<TruncatedWindowsFileTime>,
when_created: Option<TruncatedWindowsFileTime>,
when_changed: Option<TruncatedWindowsFileTime>,
last_logon: Option<WindowsFileTime>,
last_logon_time_stamp: Option<WindowsFileTime>,
account_expires: Option<WindowsFileTime>,
password_last_set: Option<WindowsFileTime>,
bad_pwd_time: Option<WindowsFileTime>,
object_guid: Option<Guid>,
sddl: Option<String>,
specific_attributes: A,
#[serde(skip)]
_marker: PhantomData<O>,
#[serde(skip)]
ptr: RecordPointer,
}
impl<T, O, A> HasSerializableFields for Object<T, O, A>
where
O: HasObjectType,
T: SerializationType,
A: SpecificObjectAttributes,
{
fn fields() -> &'static Vec<&'static str> {
lazy_static::lazy_static! {
static ref FIELDS: Vec<&'static str> = vec![
"sid",
"user_principal_name",
"service_principal_name",
"rdn",
"sam_account_name",
"sam_account_type",
"user_account_control",
"logon_count",
"bad_pwd_count",
"admin_count",
"is_deleted",
"primary_group_id",
"primary_group",
"member_of",
"comment",
"record_time",
"when_created",
"when_changed",
"last_logon",
"last_logon_time_stamp",
"account_expires",
"password_last_set",
"bad_pwd_time",
"object_guid",
"sddl"
];
}
&FIELDS
}
}
impl<T, O, A> Serialize for Object<T, O, A>
where
O: HasObjectType,
T: SerializationType,
A: SpecificObjectAttributes,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut s =
serializer.serialize_struct("Object", Self::field_count() + A::field_count())?;
s.serialize_field("sid", self.sid())?;
match &self.distinguished_name {
FormattedValue::NoValue => s.serialize_field("distinguished_name", &None::<String>)?,
FormattedValue::Hide => (),
FormattedValue::Value(dn) => s.serialize_field("distinguished_name", dn)?,
}
s.serialize_field("user_principal_name", self.user_principal_name())?;
s.serialize_field("service_principal_name", self.service_principal_name())?;
s.serialize_field("rdn", self.rdn())?;
s.serialize_field("sam_account_name", self.sam_account_name())?;
s.serialize_field("sam_account_type", self.sam_account_type())?;
s.serialize_field("user_account_control", self.user_account_control())?;
s.serialize_field("logon_count", self.logon_count())?;
s.serialize_field("bad_pwd_count", self.bad_pwd_count())?;
s.serialize_field("admin_count", self.admin_count())?;
s.serialize_field("is_deleted", self.is_deleted())?;
s.serialize_field("primary_group_id", self.primary_group_id())?;
s.serialize_field("primary_group", self.primary_group())?;
s.serialize_field("member_of", self.member_of())?;
s.serialize_field("comment", self.comment())?;
s.serialize_field("record_time", self.record_time())?;
s.serialize_field("when_created", self.when_created())?;
s.serialize_field("when_changed", self.when_changed())?;
s.serialize_field("last_logon", self.last_logon())?;
s.serialize_field("last_logon_time_stamp", self.last_logon_time_stamp())?;
s.serialize_field("account_expires", self.account_expires())?;
s.serialize_field("password_last_set", self.password_last_set())?;
s.serialize_field("bad_pwd_time", self.bad_pwd_time())?;
s.serialize_field("object_guid", self.object_guid())?;
s.serialize_field("sddl", self.sddl())?;
self.specific_attributes().serialize_to::<S>(&mut s)?;
s.end()
}
}
impl<T, O, A> FromDataTable for Object<T, O, A>
where
O: HasObjectType,
T: SerializationType,
A: SpecificObjectAttributes,
{
fn new(
dbrecord: DataTableRecord,
options: &OutputOptions,
data_table: &DataTable,
link_table: &LinkTable,
distinguished_name: FormattedValue<String>,
sd: Option<&SecurityDescriptor>
) -> Result<Self, anyhow::Error> {
let object_id = dbrecord.ds_record_id()?;
let primary_group_id = dbrecord.att_primary_group_id().ok();
let primary_group = primary_group_id.and_then(|group_id| {
match data_table
.data_table()
.metadata()
.entries_with_rid(group_id.try_into().unwrap())
.next() .map(|e| {
data_table
.data_table()
.data_table_record_from(*e.record_ptr())
.unwrap()
}) {
Some(group) => {
let rdn = group.att_object_name2().unwrap();
let sid = group.att_object_sid_opt().unwrap();
let dn = data_table.object_tree().dn_of(group.ptr());
let sam_account_name = group.att_sam_account_name_opt().unwrap();
if let Some(dn) = dn {
Some(Membership::<T>::from((dn, rdn, sid, sam_account_name)))
} else {
Some(Membership::<T>::from((
*group.ptr(),
rdn,
sid,
sam_account_name,
)))
}
}
None => None,
}
});
let member_refs = link_table.member_refs_of::<T>(object_id, data_table);
let specific_attributes = A::from(&dbrecord)?;
let sddl = match options.show_security_descriptor() {
crate::cli::SdDisplayOption::Hide => None,
crate::cli::SdDisplayOption::Sddl => sd.map(|sd| format!("{sd}")),
crate::cli::SdDisplayOption::SddlNumeric => sd.map(|sd| format!("{sd:#}")),
};
Ok(Self {
distinguished_name,
record_time: dbrecord.ds_record_time().ok(),
when_created: dbrecord.att_when_created().ok(),
when_changed: dbrecord.att_when_changed().ok(),
sid: dbrecord.att_object_sid().ok(),
sam_account_name: dbrecord.att_sam_account_name().ok(),
rdn: dbrecord.att_object_name2().ok(),
user_principal_name: dbrecord.att_user_principal_name().ok(),
service_principal_name: dbrecord.att_service_principal_name().ok(),
sam_account_type: dbrecord.att_sam_account_type().ok(),
user_account_control: dbrecord.att_user_account_control().ok(),
last_logon: dbrecord.att_last_logon().ok(),
last_logon_time_stamp: dbrecord.att_last_logon_time_stamp().ok(),
account_expires: dbrecord.att_account_expires().ok(),
password_last_set: dbrecord.att_password_last_set().ok(),
bad_pwd_time: dbrecord.att_bad_pwd_time().ok(),
logon_count: dbrecord.att_logon_count().ok(),
bad_pwd_count: dbrecord.att_bad_pwd_count().ok(),
admin_count: dbrecord.att_admin_count().ok(),
is_deleted: dbrecord.att_is_deleted().unwrap_or(false),
primary_group_id,
primary_group,
comment: dbrecord.att_comment().ok(),
member_of: member_refs,
specific_attributes,
object_guid: dbrecord.att_object_guid().ok(),
sddl,
_marker: PhantomData,
ptr: *dbrecord.ptr(),
})
}
}
impl<T, O, A> From<Object<T, O, A>> for Vec<Bodyfile3Line>
where
O: HasObjectType,
T: SerializationType,
A: SpecificObjectAttributes,
{
fn from(obj: Object<T, O, A>) -> Self {
let object_type = O::object_type();
let upn = match obj.sam_account_name() {
Some(n) => Some(n.to_string()),
None => obj
.rdn()
.as_ref()
.map(|n| n.name().to_string())
.or(Some("UNNAMED_OBJECT".to_string())),
};
let inode = obj.ptr().ds_record_id().to_string();
if let Some(upn) = upn {
vec![
obj.record_time().as_ref().map(|ts| {
ts.cr_entry(&upn, "record creation time", object_type)
.with_inode(&inode)
}),
obj.when_created().as_ref().map(|ts| {
ts.cr_entry(&upn, "object created", object_type)
.with_inode(&inode)
}),
obj.when_changed().as_ref().map(|ts| {
ts.c_entry(&upn, "object changed", object_type)
.with_inode(&inode)
}),
obj.last_logon().as_ref().map(|ts| {
ts.a_entry(&upn, "last logon on this DC", object_type)
.with_inode(&inode)
}),
obj.last_logon_time_stamp().as_ref().map(|ts| {
ts.c_entry(&upn, "last logon on any DC", object_type)
.with_inode(&inode)
}),
obj.bad_pwd_time().as_ref().map(|ts| {
ts.c_entry(&upn, "bad pwd time", object_type)
.with_inode(&inode)
}),
obj.password_last_set().as_ref().map(|ts| {
ts.m_entry(&upn, "password last set", object_type)
.with_inode(&inode)
}),
]
.into_iter()
.flatten()
.collect()
} else {
Vec::new()
}
}
}
impl<T, O, A> IsMemberOf for Object<T, O, A>
where
O: HasObjectType,
T: SerializationType,
A: SpecificObjectAttributes,
{
fn update_membership_dn(&mut self, tree: &crate::object_tree::ObjectTree) {
self.member_of.update_dn(tree)
}
}