rusthound_ce/objects/
domain.rs

1use serde_json::value::Value;
2use serde::{Deserialize, Serialize};
3
4use crate::objects::common::{
5    LdapObject,
6    GPOChange,
7    Link,
8    AceTemplate,
9    SPNTarget,
10    Member
11};
12use crate::objects::trust::Trust;
13
14use colored::Colorize;
15use ldap3::SearchEntry;
16use log::{info, debug, trace};
17use regex::Regex;
18use std::collections::HashMap;
19use std::error::Error;
20
21use crate::utils::date::{span_to_string, string_to_epoch};
22use crate::enums::acl::parse_ntsecuritydescriptor;
23use crate::enums::forestlevel::get_forest_level;
24use crate::enums::gplink::parse_gplink;
25use crate::enums::secdesc::LdapSid;
26use crate::enums::sid::sid_maker;
27
28/// Domain structure
29#[derive(Debug, Clone, Deserialize, Serialize, Default)]
30pub struct Domain {
31    #[serde(rename = "Properties")]
32    properties: DomainProperties,
33    #[serde(rename = "GPOChanges")]
34    gpo_changes: GPOChange,
35    #[serde(rename = "ChildObjects")]
36    child_objects: Vec<Member>,
37    #[serde(rename = "Trusts")]
38    trusts: Vec<Trust>,
39    #[serde(rename = "Links")]
40    links: Vec<Link>,
41    #[serde(rename = "Aces")]
42    aces: Vec<AceTemplate>,
43    #[serde(rename = "ObjectIdentifier")]
44    object_identifier: String,
45    #[serde(rename = "IsDeleted")]
46    is_deleted: bool,
47    #[serde(rename = "IsACLProtected")]
48    is_acl_protected: bool,
49    #[serde(rename = "ContainedBy")]
50    contained_by: Option<Member>,
51}
52
53impl Domain {
54    // New domain.
55    pub fn new() -> Self { 
56        Self { ..Default::default() } 
57    }
58
59    // Mutable access.
60    pub fn properties_mut(&mut self) -> &mut DomainProperties {
61        &mut self.properties
62    }
63    pub fn object_identifier_mut(&mut self) -> &mut String {
64        &mut self.object_identifier
65    }
66    pub fn gpo_changes_mut(&mut self) -> &mut GPOChange {
67        &mut self.gpo_changes
68    }
69    pub fn trusts_mut(&mut self) -> &mut Vec<Trust> {
70        &mut self.trusts
71    }
72
73    /// Function to parse and replace value for domain object.
74    /// <https://bloodhound.readthedocs.io/en/latest/further-reading/json.html#domains>
75    pub fn parse(
76        &mut self,
77        result: SearchEntry,
78        domain_name: &String,
79        dn_sid: &mut HashMap<String, String>,
80        sid_type: &mut HashMap<String, String>,
81    ) -> Result<String, Box<dyn Error>> {
82        let result_dn: String = result.dn.to_uppercase();
83        let result_attrs: HashMap<String, Vec<String>> = result.attrs;
84        let result_bin: HashMap<String, Vec<Vec<u8>>> = result.bin_attrs;
85
86        // Debug for current object
87        debug!("Parse domain: {}", result_dn);
88        // Trace all result attributes
89        for (key, value) in &result_attrs {
90            trace!("  {:?}:{:?}", key, value);
91        }
92        // Trace all bin result attributes
93        for (key, value) in &result_bin {
94            trace!("  {:?}:{:?}", key, value);
95        }
96
97        // Change all values...
98        self.properties.domain = domain_name.to_uppercase();
99        self.properties.distinguishedname = result_dn;
100
101        // Change all values...
102        #[allow(unused_assignments)]
103        let mut sid: String = "".to_owned();
104        let mut global_domain_sid: String = "DOMAIN_SID".to_owned();
105        // With a check
106        for (key, value) in &result_attrs {
107            match key.as_str() {
108                "distinguishedName" => {
109                    // name & domain & distinguishedname
110                    self.properties.distinguishedname = value[0].to_owned().to_uppercase();
111                    let name = value[0]
112                        .split(",")
113                        .filter(|x| x.starts_with("DC="))
114                        .map(|x| x.strip_prefix("DC=").unwrap_or(""))
115                        .collect::<Vec<&str>>()
116                        .join(".");
117                    self.properties.name = name.to_uppercase();
118                    self.properties.domain = name.to_uppercase();
119                }
120                "msDS-Behavior-Version" => {
121                    let level = get_forest_level(value[0].to_string());
122                    self.properties.functionallevel  = level;
123                }
124                "whenCreated" => {
125                    let epoch = string_to_epoch(&value[0])?;
126                    if epoch.is_positive() {
127                        self.properties.whencreated = epoch;
128                    }
129                }
130                "gPLink" => {
131                    self.links = parse_gplink(value[0].to_string())?;
132                }
133                "isCriticalSystemObject" => {
134                    self.properties.highvalue = value[0].contains("TRUE");
135                }
136                // The number of computer accounts that a user is allowed to create in a domain.
137                "ms-DS-MachineAccountQuota" => {
138                    let machine_account_quota = value[0].parse::<i32>().unwrap_or(0);
139                    self.properties.machineaccountquota = machine_account_quota;
140                    if machine_account_quota > 0 {
141                        info!("MachineAccountQuota: {}", machine_account_quota.to_string().yellow().bold());
142                    }
143                }
144                "IsDeleted" => {
145                    self.is_deleted = true;
146                }
147                "msDS-ExpirePasswordsOnSmartCardOnlyAccounts" => {
148                    self.properties.expirepasswordsonsmartcardonlyaccounts = true;
149                }
150                "minPwdLength" => {
151                    self.properties.minpwdlength = value[0].parse::<i32>().unwrap_or(0);
152                }
153                "pwdProperties" => {
154                    self.properties.pwdproperties = value[0].parse::<i32>().unwrap_or(0);
155                }
156                "pwdHistoryLength" => {
157                    self.properties.pwdhistorylength = value[0].parse::<i32>().unwrap_or(0);
158                }
159                "lockoutThreshold" => {
160                    self.properties.lockoutthreshold = value[0].parse::<i32>().unwrap_or(0);
161                }
162                "minPwdAge" => {
163                    self.properties.minpwdage = span_to_string(value[0].parse::<i64>().unwrap_or(0));
164                }
165                "maxPwdAge" => {
166                    self.properties.maxpwdage = span_to_string(value[0].parse::<i64>().unwrap_or(0));
167                }
168                "lockoutDuration" => {
169                    self.properties.lockoutduration = span_to_string(value[0].parse::<i64>().unwrap_or(0));
170                }
171                "lockOutObservationWindow" => {
172                    self.properties.lockoutobservationwindow = value[0].parse::<i64>().unwrap_or(0);
173                }
174                _ => {}
175            }
176        }
177        // For all, bins attributes
178        for (key, value) in &result_bin {
179            match key.as_str() {
180                "objectSid" => {
181                    // objectSid raw to string
182                    sid = sid_maker(LdapSid::parse(&value[0]).unwrap().1, domain_name);
183                    self.object_identifier = sid.to_owned();
184
185                    let re = Regex::new(r"^S-[0-9]{1}-[0-9]{1}-[0-9]{1,}-[0-9]{1,}-[0-9]{1,}-[0-9]{1,}")?;
186                    for domain_sid in re.captures_iter(&sid) 
187                    {
188                        self.properties.domainsid = domain_sid[0].to_owned().to_string();
189                        global_domain_sid = domain_sid[0].to_owned().to_string();
190                    }
191
192                    // Data Quality flag
193                    self.properties.collected = true;
194                }
195                "nTSecurityDescriptor" => {
196                    // Needed with acl
197                    let entry_type = "Domain".to_string();
198                    // nTSecurityDescriptor raw to string
199                    let relations_ace = parse_ntsecuritydescriptor(
200                        self,
201                        &value[0],
202                        entry_type,
203                        &result_attrs,
204                        &result_bin,
205                        &domain_name,
206                    );
207                    self.aces = relations_ace.into();
208                }
209                _ => {}
210            }
211        }
212
213        // Push DN and SID in HashMap
214        dn_sid.insert(
215        self.properties.distinguishedname.to_string(),
216        self.object_identifier.to_string()
217        );
218        // Push DN and Type
219        sid_type.insert(
220            self.object_identifier.to_string(),
221            "Domain".to_string(),
222        );
223
224        // Trace and return Domain struct
225        // trace!("JSON OUTPUT: {:?}",serde_json::to_string(&self).unwrap());
226        Ok(global_domain_sid)
227    }
228}
229
230impl LdapObject for Domain {
231    // To JSON
232    fn to_json(&self) -> Value {
233        serde_json::to_value(&self).unwrap()
234    }
235
236    // Get values
237    fn get_object_identifier(&self) -> &String {
238        &self.object_identifier
239    }
240    fn get_is_acl_protected(&self) -> &bool {
241        &self.is_acl_protected
242    }
243    fn get_aces(&self) -> &Vec<AceTemplate> {
244        &self.aces
245    }
246    fn get_spntargets(&self) -> &Vec<SPNTarget> {
247        panic!("Not used by current object.");
248    }
249    fn get_allowed_to_delegate(&self) -> &Vec<Member> {
250        panic!("Not used by current object.");
251    }
252    fn get_links(&self) -> &Vec<Link> {
253        &self.links
254    }
255    fn get_contained_by(&self) -> &Option<Member> {
256        &self.contained_by
257    }
258    fn get_child_objects(&self) -> &Vec<Member> {
259        &self.child_objects
260    }
261    fn get_haslaps(&self) -> &bool {
262        &false
263    }
264    
265    // Get mutable values
266    fn get_aces_mut(&mut self) -> &mut Vec<AceTemplate> {
267        &mut self.aces
268    }
269    fn get_spntargets_mut(&mut self) -> &mut Vec<SPNTarget> {
270        panic!("Not used by current object.");
271    }
272    fn get_allowed_to_delegate_mut(&mut self) -> &mut Vec<Member> {
273        panic!("Not used by current object.");
274    }
275    
276    // Edit values
277    fn set_is_acl_protected(&mut self, is_acl_protected: bool) {
278        self.is_acl_protected = is_acl_protected;
279        self.properties.isaclprotected = is_acl_protected;
280    }
281    fn set_aces(&mut self, aces: Vec<AceTemplate>) {
282        self.aces = aces;
283    }
284    fn set_spntargets(&mut self, _spn_targets: Vec<SPNTarget>) {
285        // Not used by current object.
286    }
287    fn set_allowed_to_delegate(&mut self, _allowed_to_delegate: Vec<Member>) {
288        // Not used by current object.
289    }
290    fn set_links(&mut self, links: Vec<Link>) {
291        self.links = links;
292    }
293    fn set_contained_by(&mut self, contained_by: Option<Member>) {
294        self.contained_by = contained_by;
295    }
296    fn set_child_objects(&mut self, child_objects: Vec<Member>) {
297        self.child_objects = child_objects
298    }
299}
300
301// Domain properties structure
302#[derive(Debug, Clone, Deserialize, Serialize, Default)]
303pub struct DomainProperties {
304    domain: String,
305    name: String,
306    distinguishedname: String,
307    domainsid: String,
308    isaclprotected: bool,
309    highvalue: bool,
310    description: Option<String>,
311    whencreated: i64,
312    machineaccountquota: i32,
313    expirepasswordsonsmartcardonlyaccounts: bool,
314    minpwdlength: i32,
315    pwdproperties: i32,
316    pwdhistorylength: i32,
317    lockoutthreshold: i32,
318    minpwdage: String,
319    maxpwdage: String,
320    lockoutduration: String,
321    lockoutobservationwindow: i64,
322    functionallevel: String,
323    collected: bool
324}
325
326impl DomainProperties {
327    // Mutable access.
328    pub fn domain_mut(&mut self) -> &mut String {
329       &mut self.domain
330    }
331    pub fn name_mut(&mut self) -> &mut String {
332       &mut self.name
333    }
334    pub fn highvalue_mut(&mut self) -> &mut bool {
335        &mut self.highvalue
336     }
337    pub fn distinguishedname_mut(&mut self) -> &mut String {
338        &mut self.distinguishedname
339     }
340}