Skip to main content

adhammer_core/
object.rs

1//! A generic collected directory object: DN + multi-valued string attributes,
2//! plus the raw binary blobs we need to parse ourselves (objectSid, nTSecurityDescriptor).
3
4use std::collections::HashMap;
5
6/// userAccountControl bit flags (subset we act on).
7pub mod uac {
8    pub const ACCOUNTDISABLE: u32 = 0x0002;
9    pub const HOMEDIR_REQUIRED: u32 = 0x0008;
10    pub const PASSWD_NOTREQD: u32 = 0x0020;
11    pub const NORMAL_ACCOUNT: u32 = 0x0200;
12    pub const DONT_EXPIRE_PASSWORD: u32 = 0x0001_0000;
13    pub const TRUSTED_FOR_DELEGATION: u32 = 0x0008_0000; // unconstrained delegation
14    pub const NOT_DELEGATED: u32 = 0x0010_0000; // "sensitive, cannot be delegated"
15    pub const USE_DES_KEY_ONLY: u32 = 0x0020_0000;
16    pub const DONT_REQ_PREAUTH: u32 = 0x0040_0000; // AS-REP roastable
17    pub const TRUSTED_TO_AUTH_FOR_DELEGATION: u32 = 0x0100_0000; // constrained w/ protocol transition
18    pub const USE_AES: u32 = 0; // placeholder; AES lives in msDS-SupportedEncryptionTypes
19}
20
21#[derive(Clone, Debug, Default)]
22pub struct AdObject {
23    pub dn: String,
24    pub attrs: HashMap<String, Vec<String>>,
25    /// Raw binary attributes (objectSid, nTSecurityDescriptor, objectGUID, ...).
26    pub bin: HashMap<String, Vec<Vec<u8>>>,
27}
28
29impl AdObject {
30    pub fn one(&self, attr: &str) -> Option<&str> {
31        self.attrs.get(attr).and_then(|v| v.first()).map(String::as_str)
32    }
33
34    pub fn all(&self, attr: &str) -> &[String] {
35        self.attrs.get(attr).map(Vec::as_slice).unwrap_or(&[])
36    }
37
38    pub fn int(&self, attr: &str) -> Option<i64> {
39        self.one(attr).and_then(|s| s.parse().ok())
40    }
41
42    pub fn bin1(&self, attr: &str) -> Option<&[u8]> {
43        self.bin.get(attr).and_then(|v| v.first()).map(Vec::as_slice)
44    }
45
46    /// All values of a multi-valued binary attribute (e.g. sIDHistory).
47    pub fn bin_all(&self, attr: &str) -> &[Vec<u8>] {
48        self.bin.get(attr).map(Vec::as_slice).unwrap_or(&[])
49    }
50
51    pub fn uac(&self) -> u32 {
52        self.int("userAccountControl").unwrap_or(0) as u32
53    }
54
55    pub fn has_class(&self, class: &str) -> bool {
56        self.all("objectClass").iter().any(|c| c.eq_ignore_ascii_case(class))
57    }
58
59    /// FILETIME attribute (pwdLastSet, lastLogonTimestamp) as raw 100ns ticks since 1601.
60    pub fn filetime(&self, attr: &str) -> Option<i64> {
61        self.int(attr)
62    }
63}