rusthound_ce/json/checker/
common.rs

1use std::collections::HashMap;
2use std::error::Error;
3
4use regex::Regex;
5use crate::enums::ldaptype::*;
6use crate::objects::common::Link;
7use crate::objects::{
8    user::User,
9    computer::Computer,
10    group::Group,
11    ou::Ou,
12    domain::Domain,
13    trust::Trust,
14    common::{Member, GPOChange, LdapObject}
15};
16//use log::{info,debug,trace};
17use crate::ldap::prepare_ldap_dc;
18use crate::utils::format::domain_to_dc;
19use crate::enums::regex::{COMMON_RE1,DOMAIN_SID_RE1};
20use indicatif::ProgressBar;
21use log::{error,trace};
22
23/// Function to add default groups
24/// <https://github.com/fox-it/BloodHound.py/blob/645082e3462c93f31b571db945cde1fd7b837fb9/bloodhound/enumeration/memberships.py#L411>
25pub fn add_default_groups(
26    vec_groups: &mut Vec<Group>,
27    vec_computers: &[Computer],
28    domain: String
29) -> Result<(), Box<dyn Error>> {
30    let mut member_sid = "".to_owned();
31    let mut domain_sid = "".to_owned();
32
33    let mut template_member = Member::new();
34    *template_member.object_type_mut() = "Computer".to_string();
35
36    // ENTERPRISE DOMAIN CONTROLLERS
37    let mut edc_group = Group::new();
38    let mut sid = domain.to_uppercase();
39    sid.push_str("-S-1-5-9");
40
41    let mut name = "ENTERPRISE DOMAIN CONTROLLERS@".to_owned();
42    name.push_str(&domain.to_uppercase());
43
44    let mut vec_members: Vec<Member> = Vec::new();
45    for computer in vec_computers {
46        if computer.properties().get_is_dc().to_owned()
47        {
48            // *template_member.object_identifier_mut() = computer.object_identifier().to_string();
49            // vec_members.push(template_member.to_owned());
50            // let re = Regex::new(r"^S-[0-9]{1}-[0-9]{1}-[0-9]{1,}-[0-9]{1,}-[0-9]{1,}-[0-9]{1,}")?;
51            // let mut sids: Vec<String> = Vec::new();
52            // for sid in re.captures_iter(&computer.object_identifier().to_string())
53            // {
54            //     sids.push(sid[0].to_owned().to_string());
55            // }
56            // domain_sid = sids[0].to_string();
57            *template_member.object_identifier_mut() = computer.object_identifier().clone();
58            vec_members.push(template_member.clone());
59            if let Some(capture) = COMMON_RE1.captures(computer.object_identifier()) {
60
61                member_sid = capture.get(0).map(|m| m.as_str().to_string()).unwrap_or_default();
62
63                if let Some(capture) = DOMAIN_SID_RE1.captures(computer.object_identifier()) {
64                    domain_sid = capture.get(0).unwrap().as_str().to_string();
65                }
66
67            }
68        }
69    }
70
71    *edc_group.object_identifier_mut() = sid;
72    *edc_group.properties_mut().name_mut() = name;
73    *edc_group.members_mut() = vec_members;
74    *edc_group.properties_mut().domain_mut() = domain.to_owned();
75    *edc_group.properties_mut().domainsid_mut() = domain_sid.to_owned();
76    vec_groups.push(edc_group);
77
78    // ACCOUNT OPERATORS
79    let mut account_operators_group = Group::new();
80    sid = domain.to_owned().to_uppercase();
81    sid.push_str("-S-1-5-32-548");
82    let mut name = "ACCOUNT OPERATORS@".to_owned();
83    name.push_str(&domain.to_uppercase());
84    
85    *account_operators_group.object_identifier_mut() = sid;
86    *account_operators_group.properties_mut().name_mut() = name;
87    *account_operators_group.properties_mut().highvalue_mut() = true;
88    *account_operators_group.properties_mut().domain_mut() = domain.to_owned();
89    *account_operators_group.properties_mut().domainsid_mut() = domain_sid.to_owned();
90    vec_groups.push(account_operators_group);
91
92    // WINDOWS AUTHORIZATION ACCESS GROUP
93    let mut waag_group = Group::new();
94    sid = domain.to_uppercase();
95    sid.push_str("-S-1-5-32-560");
96    let mut name = "WINDOWS AUTHORIZATION ACCESS GROUP@".to_owned();
97    name.push_str(&domain.to_uppercase());
98    *waag_group.object_identifier_mut() = sid;
99    *waag_group.properties_mut().name_mut() = name;
100    *waag_group.properties_mut().domainsid_mut() = domain_sid.to_owned();
101    vec_groups.push(waag_group);
102
103    // EVERYONE
104    let mut everyone_group = Group::new();
105    sid = domain.to_uppercase();
106    sid.push_str("-S-1-1-0");
107    let mut name = "EVERYONE@".to_owned();
108    name.push_str(&domain.to_uppercase());
109
110    let mut vec_everyone_members: Vec<Member> = Vec::new();
111    let mut member_id = member_sid.to_owned();
112    member_id.push_str("-515");
113    *template_member.object_identifier_mut() = member_id.to_owned();
114    *template_member.object_type_mut() = "Group".to_string();
115    vec_everyone_members.push(template_member.to_owned());
116
117    member_id = member_sid.to_owned();
118    member_id.push_str("-513");
119    *template_member.object_identifier_mut() = member_id.to_owned();
120    *template_member.object_type_mut() = "Group".to_string();
121    vec_everyone_members.push(template_member.to_owned());
122
123    *everyone_group.object_identifier_mut() = sid;
124    *everyone_group.properties_mut().name_mut() = name;
125    *everyone_group.properties_mut().domain_mut() = domain.to_owned();
126    *everyone_group.properties_mut().domainsid_mut() = domain_sid.to_owned();
127    *everyone_group.members_mut() = vec_everyone_members;
128    vec_groups.push(everyone_group);
129
130    // AUTHENTICATED USERS
131    let mut auth_users_group = Group::new();
132    sid = domain.to_uppercase();
133    sid.push_str("-S-1-5-11");
134    let mut name = "AUTHENTICATED USERS@".to_owned();
135    name.push_str(&domain.to_uppercase());
136
137    let mut vec_auth_users_members: Vec<Member> = Vec::new();
138    member_id = member_sid.to_owned();
139    member_id.push_str("-515");
140    *template_member.object_identifier_mut() = member_id.to_owned();
141    *template_member.object_type_mut() = "Group".to_string();
142    vec_auth_users_members.push(template_member.to_owned());
143
144    member_id = member_sid.to_owned();
145    member_id.push_str("-513");
146    *template_member.object_identifier_mut() = member_id.to_owned();
147    *template_member.object_type_mut() = "Group".to_string();
148    vec_auth_users_members.push(template_member.to_owned());
149
150    *auth_users_group.object_identifier_mut() = sid;
151    *auth_users_group.properties_mut().name_mut() = name;
152    *auth_users_group.properties_mut().domain_mut() = domain.to_owned();
153    *auth_users_group.members_mut() = vec_auth_users_members;
154    *auth_users_group.properties_mut().domainsid_mut() = domain_sid.to_owned();
155    vec_groups.push(auth_users_group);
156
157    // ADMINISTRATORS
158    let mut administrators_group = Group::new();
159    sid = domain.to_uppercase();
160    sid.push_str("-S-1-5-32-544");
161    let mut name = "ADMINISTRATORS@".to_owned();
162    name.push_str(&domain.to_uppercase());
163
164    *administrators_group.object_identifier_mut() = sid;
165    *administrators_group.properties_mut().name_mut() = name;
166    *administrators_group.properties_mut().highvalue_mut() = true;
167    *administrators_group.properties_mut().domain_mut() = domain.to_owned();
168    *administrators_group.properties_mut().domainsid_mut() = domain_sid.to_owned();
169    vec_groups.push(administrators_group);
170
171    // PRE-WINDOWS 2000 COMPATIBLE ACCESS
172    let mut pw2000ca_group = Group::new();
173    sid = domain.to_uppercase();
174    sid.push_str("-S-1-5-32-554");
175    let mut name = "PRE-WINDOWS 2000 COMPATIBLE ACCESS@".to_owned();
176    name.push_str(&domain.to_uppercase());
177            
178    *pw2000ca_group.object_identifier_mut() = sid;
179    *pw2000ca_group.properties_mut().name_mut() = name;
180    *pw2000ca_group.properties_mut().domain_mut() = domain.to_owned();
181    *pw2000ca_group.properties_mut().domainsid_mut() = domain_sid.to_owned();
182    vec_groups.push(pw2000ca_group);    
183
184    // INTERACTIVE
185    let mut interactive_group = Group::new();
186    sid = domain.to_uppercase();
187    sid.push_str("-S-1-5-4");
188    let mut name = "INTERACTIVE@".to_owned();
189    name.push_str(&domain.to_uppercase());
190
191    *interactive_group.object_identifier_mut() = sid;
192    *interactive_group.properties_mut().name_mut() = name;
193    *interactive_group.properties_mut().domain_mut() = domain.to_owned();
194    *interactive_group.properties_mut().domainsid_mut() = domain_sid.to_owned();
195    vec_groups.push(interactive_group);
196
197    // PRINT OPERATORS
198    let mut print_operators_group = Group::new();
199    sid = domain.to_uppercase();
200    sid.push_str("-S-1-5-32-550");
201    let mut name = "PRINT OPERATORS@".to_owned();
202    name.push_str(&domain.to_uppercase());
203            
204    *print_operators_group.object_identifier_mut() = sid;
205    *print_operators_group.properties_mut().name_mut() = name;
206    *print_operators_group.properties_mut().highvalue_mut() = true;
207    *print_operators_group.properties_mut().domain_mut() = domain.to_owned();
208    *print_operators_group.properties_mut().domainsid_mut() = domain_sid.to_owned();
209    vec_groups.push(print_operators_group); 
210
211    // TERMINAL SERVER LICENSE SERVERS
212    let mut tsls_group = Group::new();
213    sid = domain.to_uppercase();
214    sid.push_str("-S-1-5-32-561");
215    let mut name = "TERMINAL SERVER LICENSE SERVERS@".to_owned();
216    name.push_str(&domain.to_uppercase());
217            
218    *tsls_group.object_identifier_mut() = sid;
219    *tsls_group.properties_mut().name_mut() = name;
220    *tsls_group.properties_mut().domain_mut() = domain.to_owned();
221    *tsls_group.properties_mut().domainsid_mut() = domain_sid.to_owned();
222    vec_groups.push(tsls_group); 
223
224    // INCOMING FOREST TRUST BUILDERS
225    let mut iftb_group = Group::new();
226    sid = domain.to_uppercase();
227    sid.push_str("-S-1-5-32-557");
228    let mut name = "INCOMING FOREST TRUST BUILDERS@".to_owned();
229    name.push_str(&domain.to_uppercase());
230            
231    *iftb_group.object_identifier_mut() = sid;
232    *iftb_group.properties_mut().name_mut() = name;
233    *iftb_group.properties_mut().domain_mut() = domain.to_owned();
234    *iftb_group.properties_mut().domainsid_mut() = domain_sid.to_owned();
235    vec_groups.push(iftb_group); 
236 
237    // THIS ORGANIZATION 
238    let mut this_organization_group = Group::new();
239    sid = domain.to_uppercase();
240    sid.push_str("-S-1-5-15");
241    let mut name = "THIS ORGANIZATION@".to_owned();
242    name.push_str(&domain.to_uppercase());
243            
244    *this_organization_group.object_identifier_mut() = sid;
245    *this_organization_group.properties_mut().name_mut() = name;
246    *this_organization_group.properties_mut().domain_mut() = domain.to_owned();
247    *this_organization_group.properties_mut().domainsid_mut() = domain_sid.to_owned();
248    vec_groups.push(this_organization_group);
249    Ok(())
250}
251
252/// Function to add default user
253/// <https://github.com/fox-it/BloodHound.py/blob/645082e3462c93f31b571db945cde1fd7b837fb9/bloodhound/enumeration/memberships.py#L411>
254pub fn add_default_users(
255    vec_users: &mut Vec<User>,
256    domain: String
257) -> Result<(), Box<dyn Error>> {
258    // NT AUTHORITY
259    let mut ntauthority_user = User::new();
260    let mut sid = domain.to_uppercase();
261    sid.push_str("-S-1-5-20");
262    let mut name = "NT AUTHORITY@".to_owned();
263    name.push_str(&domain.to_uppercase());
264    *ntauthority_user.properties_mut().name_mut() = name;
265    *ntauthority_user.object_identifier_mut() = sid;
266
267    if let Some(first_user) = vec_users.get(0) {
268        *ntauthority_user.properties_mut().domainsid_mut() = first_user.properties().domainsid().to_string();
269    } else {
270        error!("vec_users is empty, skipping domain SID assignment");
271    }
272
273    vec_users.push(ntauthority_user);
274    Ok(())
275}
276
277/// This function is to push user SID in ChildObjects v2
278pub fn add_childobjects_members<T: LdapObject>(
279    vec_replaced: &mut [T],
280    dn_sid: &HashMap<String, String>,
281    sid_type: &HashMap<String, String>,
282) -> Result<(), Box<dyn Error>> {
283    // Needed for progress bar stats
284    let total = vec_replaced.len();
285    let pb = ProgressBar::new(total as u64);
286
287    // Iterate over the objects
288    for (count, object) in vec_replaced.iter_mut().enumerate() {
289        // Update progress bar periodically
290        if count % (total / 100).max(1) == 0 {
291            pb.set_position(count as u64);
292        }
293
294        // Get the SID, DN, and name of the current object
295        let sid = object.get_object_identifier().to_uppercase();
296        let dn = dn_sid
297            .iter()
298            .find(|(_, v)| **v == sid)
299            .map(|(k, _)| k)
300            .unwrap_or(&sid);
301        let name = get_name_from_full_distinguishedname(dn);
302        let _otype = sid_type.get(&sid).unwrap();
303
304        // Filter direct members from dn_sid
305        let direct_members: Vec<Member> = dn_sid
306            .iter()
307            .filter_map(|(dn_object, value_sid)| {
308                let dn_object_upper = dn_object.to_uppercase();
309
310                // Check if dn_object is related to the current object's DN
311                if dn_object_upper.contains(dn)
312                    && &dn_object_upper != dn
313                    && dn_object_upper.split(',')
314                        .nth(1)
315                        .and_then(|s| s.split('=').nth(1))
316                        == Some(&name)
317                {
318                    let mut member = Member::new();
319                    *member.object_identifier_mut() = value_sid.clone();
320                    *member.object_type_mut() = sid_type.get(value_sid).unwrap_or(&value_sid).to_string();
321                    if !member.object_identifier().is_empty() {
322                        return Some(member);
323                    }
324                }
325                None
326            })
327            .collect();
328
329        // Set direct members for the object
330        object.set_child_objects(direct_members);
331    }
332
333    pb.finish_and_clear();
334    Ok(())
335}
336
337/// This function is to push user SID in ChildObjects for Ou v2
338pub fn add_childobjects_members_for_ou(
339    vec_replaced: &mut [Ou],
340    dn_sid: &HashMap<String, String>,
341    sid_type: &HashMap<String, String>,
342) -> Result<(), Box<dyn Error>> {
343    // Progress bar setup
344    let total = vec_replaced.len();
345    let pb = ProgressBar::new(total as u64);
346
347    // Cache common values to avoid repeated allocations
348    let null = "NULL".to_string();
349
350    for (count, object) in vec_replaced.iter_mut().enumerate() {
351        // Update progress bar periodically
352        if count % (total / 100).max(1) == 0 {
353            pb.set_position(count as u64);
354        }
355
356        let mut direct_members = Vec::new();
357        let mut affected_computers = Vec::new();
358
359        // Fetch properties of the current object
360        let dn = object.properties().distinguishedname();
361        let mut name = object.properties().name().to_owned();
362        let sid = dn_sid.get(dn).unwrap_or(&null);
363        let otype = sid_type.get(sid).unwrap_or(&null);
364
365        // Adjust the name if not a domain
366        if otype != "Domain" {
367            if let Some(first_part) = name.split('@').next() {
368                name = first_part.to_string();
369            }
370        }
371
372        // Process all dn_sid entries
373        for (dn_object, value_sid) in dn_sid {
374            let dn_object_upper = dn_object.to_uppercase();
375
376            // Parse the "first" component of the DN
377            let first = dn_object_upper
378                .split(',')
379                .nth(1)
380                .and_then(|part| part.split('=').nth(1))
381                .unwrap_or("");
382
383            if otype != "Domain" {
384                // For non-domain objects
385                if dn_object_upper.contains(dn) && &dn_object_upper != dn && first == name {
386                    let mut member = Member::new();
387                    *member.object_identifier_mut() = value_sid.clone();
388                    let object_type = sid_type.get(value_sid).unwrap_or(&null).to_string();
389                    *member.object_type_mut() = object_type.clone();
390
391                    direct_members.push(member.clone());
392
393                    // Add computers to affected_computers if applicable
394                    if object_type == "Computer" {
395                        affected_computers.push(member);
396                    }
397                }
398            } else {
399                // For domain objects
400                if let Some(cn) = name.split('.').next() {
401                    if first.contains(cn) {
402                        let mut member = Member::new();
403                        *member.object_identifier_mut() = value_sid.clone();
404                        *member.object_type_mut() = sid_type.get(value_sid).unwrap_or(&null).to_string();
405                        direct_members.push(member);
406                    }
407                }
408            }
409        }
410
411        // Set child objects and GPO changes for OUs
412        *object.child_objects_mut() = direct_members;
413        if otype == "OU" {
414            let mut gpo_changes = GPOChange::new();
415            *gpo_changes.affected_computers_mut() = affected_computers;
416            *object.gpo_changes_mut() = gpo_changes;
417        }
418    }
419
420    pb.finish_and_clear();
421    Ok(())
422}
423
424/// This function checks GUID for all Gplinks and replaces them with the correct GUIDs
425pub fn replace_guid_gplink<T: LdapObject>(
426    vec_replaced: &mut [T],
427    dn_sid: &HashMap<String, String>,
428) -> Result<(), Box<dyn Error>> {
429    // Progress bar setup
430    let total = vec_replaced.len();
431    let pb = ProgressBar::new(total as u64);
432
433    // Iterate over the objects
434    for (count, object) in vec_replaced.iter_mut().enumerate() {
435        // Update progress bar periodically
436        if count % (total / 100).max(1) == 0 {
437            pb.set_position(count as u64);
438        }
439
440        // Process links if they exist
441        if !object.get_links().is_empty() {
442            // Replace GUIDs in links
443            let updated_links: Vec<Link> = object
444                .get_links()
445                .iter()
446                .map(|link| {
447                    let mut new_link = link.clone(); // Clone the Link to create a new instance
448                    if let Some(new_guid) = dn_sid
449                        .iter()
450                        .find(|(key, _)| key.contains(link.guid()))
451                        .map(|(_, guid)| guid.to_owned())
452                    {
453                        *new_link.guid_mut() = new_guid;
454                    }
455                    new_link
456                })
457                .collect();
458
459            // Update the object's links
460            object.set_links(updated_links);
461        }
462    }
463
464    pb.finish_and_clear();
465    Ok(())
466}
467
468/// This function pushes computer SIDs into the domain's GPO changes v2
469pub fn add_affected_computers(
470    vec_domains: &mut [Domain],
471    sid_type: &HashMap<String, String>,
472) -> Result<(), Box<dyn Error>> {
473    // Filter only "Computer" SIDs and map them to Member objects
474    let vec_affected_computers: Vec<Member> = sid_type
475        .iter()
476        .filter(|&(_, obj_type)| obj_type == "Computer")
477        .map(|(sid, _)| {
478            let mut member = Member::new();
479            *member.object_type_mut() = "Computer".to_string();
480            *member.object_identifier_mut() = sid.clone();
481            member
482        })
483        .collect();
484
485    // Update the GPO changes of the first domain
486    if let Some(domain) = vec_domains.get_mut(0) {
487        let mut gpo_changes = GPOChange::new();
488        *gpo_changes.affected_computers_mut() = vec_affected_computers;
489        *domain.gpo_changes_mut() = gpo_changes;
490    }
491    Ok(())
492}
493
494/// This function pushes computer SIDs into GPO changes for each OU
495pub fn add_affected_computers_for_ou(
496    vec_ous: &mut [Ou],
497    dn_sid: &HashMap<String, String>,
498    sid_type: &HashMap<String, String>,
499) -> Result<(), Box<dyn Error>> {
500    // Filter all computers DN:SID in advance
501    let dn_sid_filtered: Vec<(&String, &String)> = dn_sid
502        .iter()
503        .filter(|(_, sid)| sid_type.get(*sid).map(|t| t == "Computer").unwrap_or(false))
504        .collect();
505
506    // Map each OU's identifier to its DN
507    let ou_dn_map: HashMap<String, String> = vec_ous
508        .iter()
509        .filter_map(|ou| {
510            dn_sid
511                .iter()
512                .find_map(|(dn, sid)| {
513                    if *sid == *ou.get_object_identifier() {
514                        Some((ou.get_object_identifier().to_owned(), dn.clone()))
515                    } else {
516                        None
517                    }
518                })
519        })
520        .collect();
521
522    // For each OU, add affected computers
523    for ou in vec_ous.iter_mut() {
524        if let Some(ou_dn) = ou_dn_map.get(ou.get_object_identifier()) {
525            let vec_affected_computers: Vec<Member> = dn_sid_filtered
526                .iter()
527                .filter_map(|(dn, sid)| {
528                    if get_contained_by_name_from_distinguishedname(
529                        &get_cn_object_name_from_full_distinguishedname(dn),
530                        dn,
531                    ) == *ou_dn
532                    {
533                        let mut member = Member::new();
534                        *member.object_identifier_mut() = sid.to_string();
535                        *member.object_type_mut() = "Computer".to_string();
536                        Some(member)
537                    } else {
538                        None
539                    }
540                })
541                .collect();
542
543            // Update GPO changes for the OU
544            let mut gpo_changes = GPOChange::new();
545            *gpo_changes.affected_computers_mut() = vec_affected_computers;
546            *ou.gpo_changes_mut() = gpo_changes;
547        }
548    }
549    Ok(())
550}
551
552/// This function replaces FQDN by SID in users' SPNTargets or computers' AllowedToDelegate
553pub fn replace_fqdn_by_sid<T: LdapObject>(
554    object_type: Type,
555    vec_src: &mut [T],
556    fqdn_sid: &HashMap<String, String>,
557) -> Result<(), Box<dyn Error>> {
558    // Progress bar setup
559    let total = vec_src.len();
560    let pb = ProgressBar::new(total as u64);
561
562    // Process based on the object type
563    match object_type {
564        Type::User => {
565            for (count, obj) in vec_src.iter_mut().enumerate() {
566                // Update progress bar
567                if count % (total / 100).max(1) == 0 {
568                    pb.set_position(count as u64);
569                }
570
571                // Process SPNTargets
572                for target in obj.get_spntargets_mut().iter_mut() {
573                    let sid = fqdn_sid
574                        .get(target.computer_sid())
575                        .unwrap_or_else(|| target.computer_sid());
576                    *target.computer_sid_mut() = sid.to_string();
577                }
578
579                // Process AllowedToDelegate
580                for target in obj.get_allowed_to_delegate_mut().iter_mut() {
581                    let sid = fqdn_sid
582                        .get(target.object_identifier())
583                        .unwrap_or_else(|| target.object_identifier());
584                    *target.object_identifier_mut() = sid.to_string();
585                }
586            }
587        }
588        Type::Computer => {
589            for (count, obj) in vec_src.iter_mut().enumerate() {
590                // Update progress bar
591                if count % (total / 100).max(1) == 0 {
592                    pb.set_position(count as u64);
593                }
594
595                // Process AllowedToDelegate
596                for delegate in obj.get_allowed_to_delegate_mut().iter_mut() {
597                    let sid = fqdn_sid
598                        .get(delegate.object_identifier())
599                        .unwrap_or_else(|| delegate.object_identifier());
600                    *delegate.object_identifier_mut() = sid.to_string();
601                }
602            }
603        }
604        _ => {}
605    }
606
607    pb.finish_and_clear();
608    Ok(())
609}
610
611/// This function checks and replaces object names by SIDs in group members v2
612pub fn replace_sid_members(
613    vec_groups: &mut [Group],
614    dn_sid: &HashMap<String, String>,
615    sid_type: &HashMap<String, String>,
616    vec_trusts: &[Trust],
617) -> Result<(), Box<dyn Error>> {
618    let total = vec_groups.len();
619    let pb = ProgressBar::new(total as u64);
620
621    let default_type = "Group".to_string();
622
623    for (count, group) in vec_groups.iter_mut().enumerate() {
624        if count % (total / 100).max(1) == 0 {
625            pb.set_position(count as u64);
626        }
627
628        for member in group.members_mut() {
629            let member_dn = member.object_identifier();
630
631            // 1) Skip clearly invalid entries
632            if member_dn.trim().is_empty() || member_dn.eq_ignore_ascii_case("SID") {
633                error!("Skipping empty/invalid member DN in group");
634                continue;
635            }
636
637            // 2) Try to resolve via dn_sid
638            if let Some(sid) = dn_sid.get(member_dn) {
639                if !sid.is_empty() && sid != "NULL" {
640                    let type_object =
641                        sid_type.get(sid).cloned().unwrap_or_else(|| default_type.clone());
642                    *member.object_identifier_mut() = sid.clone();
643                    *member.object_type_mut() = type_object;
644                    continue;
645                }
646            }
647
648            // 3) Fallback: try trusts
649            let generated_sid = sid_maker_from_another_domain(vec_trusts, member_dn)?;
650            if !generated_sid.is_empty() {
651                *member.object_identifier_mut() = generated_sid;
652                *member.object_type_mut() = default_type.clone();
653            } else {
654                error!("Could not resolve SID for member DN: {}", member_dn);
655            }
656        }
657    }
658
659    pb.finish_and_clear();
660    Ok(())
661}
662
663/// Make the SID from domain present in trust v2
664fn sid_maker_from_another_domain(
665    vec_trusts: &[Trust],
666    object_identifier: &String,
667) -> Result<String, Box<dyn Error>> {
668    // Create the regex for SID matching
669    let sid_regex = Regex::new(r"S-[0-9]+-[0-9]+-[0-9]+(?:-[0-9]+)+")?;
670
671    // Check if the object_identifier matches any trusted domain
672    for trust in vec_trusts {
673        let ldap_dc = prepare_ldap_dc(trust.target_domain_name());
674        if object_identifier.contains(&ldap_dc[0]) {
675            let id = get_id_from_objectidentifier(object_identifier)?;
676            return Ok(format!("{}{}", trust.target_domain_name(), id))
677        }
678    }
679
680    // Check if object_identifier contains an SID
681    if object_identifier.contains("CN=S-") {
682        if let Some(capture) = sid_regex.captures(object_identifier).and_then(|cap| cap.get(0)) {
683            return Ok(capture.as_str().to_owned())
684        }
685    }
686
687    // Default case: return the object_identifier as-is
688    Ok(object_identifier.to_string())
689}
690
691// Get id from objectidentifier for all common group (Administrators ...) v2
692// https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/understand-security-identifiers
693fn get_id_from_objectidentifier(
694    object_identifier: &str
695) -> Result<String, Box<dyn Error>> {
696
697    // Static mapping of group names to RIDs
698    const NAME_TO_RID: [(&str, &str); 16] = [
699        ("DOMAIN ADMINS", "-512"),
700        ("ADMINISTRATEURS DU DOMAINE", "-512"),
701        ("DOMAIN USERS", "-513"),
702        ("UTILISATEURS DU DOMAINE", "-513"),
703        ("DOMAIN GUESTS", "-514"),
704        ("INVITES DE DOMAINE", "-514"),
705        ("DOMAIN COMPUTERS", "-515"),
706        ("ORDINATEURS DE DOMAINE", "-515"),
707        ("DOMAIN CONTROLLERS", "-516"),
708        ("CONTRÔLEURS DE DOMAINE", "-516"),
709        ("CERT PUBLISHERS", "-517"),
710        ("EDITEURS DE CERTIFICATS", "-517"),
711        ("SCHEMA ADMINS", "-518"),
712        ("ADMINISTRATEURS DU SCHEMA", "-518"),
713        ("ENTERPRISE ADMINS", "-519"),
714        ("ADMINISTRATEURS DE L'ENTREPRISE", "-519"),
715    ];
716
717    // Iterate over the static array to find a match
718    for (name, rid) in NAME_TO_RID.iter() {
719        if object_identifier.contains(name) {
720            return Ok(rid.to_string())
721        }
722    }
723
724    // Default case if no match is found
725    Ok("NULL_ID1".to_string())
726}
727
728/// This function push trust domain values in domain
729pub fn add_trustdomain(
730    vec_domains: &mut Vec<Domain>,
731    vec_trusts: &mut [Trust]
732) -> Result<(), Box<dyn Error>> {
733    if !&vec_trusts[0].target_domain_sid().to_string().contains("SID") {
734        let mut trusts: Vec<Trust> = Vec::new();
735        for trust in vec_trusts {
736            trusts.push(trust.to_owned());
737            let mut new_domain = Domain::new();
738            *new_domain.object_identifier_mut() = trust.target_domain_sid().to_string();
739            *new_domain.properties_mut().name_mut() = trust.target_domain_name().to_string();
740            *new_domain.properties_mut().domain_mut() = trust.target_domain_name().to_string();
741            *new_domain.properties_mut().distinguishedname_mut() = domain_to_dc(trust.target_domain_name());
742            *new_domain.properties_mut().highvalue_mut() = true;
743            vec_domains.push(new_domain);
744        }
745        *vec_domains[0].trusts_mut() = trusts.to_owned();
746    }
747    Ok(())
748}
749
750/// This function checks PrincipalSID for all ACEs and adds the PrincipalType ("Group", "User", "Computer") v2
751pub fn add_type_for_ace<T: LdapObject>(
752    object: &mut [T],
753    sid_type: &HashMap<String, String>,
754) -> Result<(), Box<dyn Error>> {
755    // Progress bar setup
756    let total = object.len();
757    let pb = ProgressBar::new(total as u64);
758
759    // Default type for unmatched SIDs
760    let default_type = "Group".to_string();
761
762    // Iterate over each object
763    for (count, obj) in object.iter_mut().enumerate() {
764        // Update progress bar
765        if count % (total / 100).max(1) == 0 {
766            pb.set_position(count as u64);
767        }
768
769        // Get mutable reference to ACEs
770        for ace in obj.get_aces_mut() {
771            // Fetch the type from sid_type or use the default
772            let type_object = sid_type
773                .get(ace.principal_sid())
774                .unwrap_or(&default_type)
775                .clone();
776
777            // Update the principal type
778            *ace.principal_type_mut() = type_object;
779        }
780    }
781
782    pb.finish_and_clear();
783    Ok(())
784}
785
786/// This function checks PrincipalSID for all AllowedToAct objects and adds the PrincipalType ("Group", "User", "Computer") v2
787pub fn add_type_for_allowtedtoact(
788    computer: &mut [Computer],
789    sid_type: &HashMap<String, String>,
790) -> Result<(), Box<dyn Error>> {
791    // Progress bar setup
792    let total = computer.len();
793    let pb = ProgressBar::new(total as u64);
794
795    // Default type for unmatched SIDs
796    let default_type = "Computer".to_string();
797
798    // Iterate over all computers
799    for (count, comp) in computer.iter_mut().enumerate() {
800        // Update progress bar periodically
801        if count % (total / 100).max(1) == 0 {
802            pb.set_position(count as u64);
803        }
804
805        // Process all AllowedToAct objects
806        for allowed in comp.allowed_to_act_mut() {
807            let type_object = sid_type
808                .get(allowed.object_identifier())
809                .unwrap_or(&default_type)
810                .clone();
811
812            *allowed.object_type_mut() = type_object;
813        }
814    }
815
816    pb.finish_and_clear();
817    Ok(())
818}
819
820/// This function pushes user SID into ChildObjects for Ou v2
821pub fn add_contained_by_for<T: LdapObject>(
822    vec_replaced: &mut [T],
823    dn_sid: &HashMap<String, String>, 
824    sid_type: &HashMap<String, String>,
825) -> Result<(), Box<dyn Error>> {
826    // Progress bar setup
827    let total = vec_replaced.len();
828    let pb = ProgressBar::new(total as u64);
829
830    // Default type for unmatched SIDs
831    let default_type = "Group".to_string();
832
833    for (count, object) in vec_replaced.iter_mut().enumerate() {
834        // Update progress bar periodically
835        if count % (total / 100).max(1) == 0 {
836            pb.set_position(count as u64);
837        }
838
839        // Fetch SID and DN for the current object
840        let sid = object.get_object_identifier();
841        let dn = dn_sid.iter().find_map(|(key, value)| if value == sid { Some(key) } else { None });
842
843        if let Some(dn) = dn {
844            let otype = sid_type.get(sid).unwrap_or(&default_type);
845
846            if otype != "Domain" {
847                // Extract CN name and contained-by name
848                let cn_name = get_cn_object_name_from_full_distinguishedname(dn);
849                let contained_by_name = get_contained_by_name_from_distinguishedname(&cn_name, dn);
850
851                // Check if the contained-by name exists in dn_sid
852                if let Some(sid_contained_by) = dn_sid.get(&contained_by_name) {
853                    let type_contained_by = sid_type.get(sid_contained_by).unwrap_or(&default_type);
854
855                    // Create and set the contained_by Member
856                    let mut contained_by = Member::new();
857                    *contained_by.object_identifier_mut() = sid_contained_by.to_string();
858                    *contained_by.object_type_mut() = type_contained_by.to_string();
859                    object.set_contained_by(Some(contained_by));
860                }
861            }
862        }
863    }
864
865    pb.finish_and_clear();
866    Ok(())
867}
868
869/// Function to get name from DN
870pub fn get_name_from_full_distinguishedname(dn_object: &str) -> String {
871    // Example:
872    // dn_object = CN=G0H4N,CN=USERS,DC=ESSOS,DC=LOCAL
873    trace!("get_name_from_full_distinguishedname() {:?}",&dn_object);
874    let split1 = dn_object.split(",");
875    let vec1 = split1.collect::<Vec<&str>>();
876    let split2 = vec1[0].split("=");
877    let vec2 = split2.collect::<Vec<&str>>();
878    let name = vec2[1].to_owned();
879    // name = G0H4N
880    name
881}
882
883/// Function to get CN=name from DN
884fn get_cn_object_name_from_full_distinguishedname(dn_object: &String) -> String {
885    // Example:
886    // dn_object = CN=G0H4N,CN=USERS,DC=ESSOS,DC=LOCAL
887    let name = dn_object.to_owned();
888    let split = name.split(",");
889    let vec = split.collect::<Vec<&str>>();
890    let name = vec[0].to_owned();
891    // name = CN=G0H4N
892    name
893}
894
895/// Function to get first degree contained by name from DN
896fn get_contained_by_name_from_distinguishedname(cn_name: &str, dn_object: &str) -> String {
897    // Example:
898    // dn_object = CN=G0H4N,CN=USERS,DC=ESSOS,DC=LOCAL
899    let name = format!("{},",cn_name);
900    let split = dn_object.split(&name);
901    let vec = split.collect::<Vec<&str>>();
902    let dn_contained_by = vec[1].to_owned();
903    // dn_contained_by = CN=USERS,DC=ESSOS,DC=LOCAL
904    dn_contained_by
905}
906
907//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
908
909#[cfg(test)]
910mod tests {
911    
912    use crate::json::checker::common::{
913        get_name_from_full_distinguishedname,
914        get_cn_object_name_from_full_distinguishedname,
915        get_contained_by_name_from_distinguishedname
916    };
917    
918    #[test]
919    #[rustfmt::skip]
920    pub fn test_get_name_from_full_distinguishedname() {
921        // Example:
922        // dn_object = CN=G0H4N,CN=USERS,DC=ESSOS,DC=LOCAL
923        let dn_object = "CN=G0H4N,CN=USERS,DC=ESSOS,DC=LOCAL".to_string();
924        let cn_name =  get_name_from_full_distinguishedname(&dn_object);
925        println!("dn_object: {:?}",dn_object);
926        println!("cn_name: {:?}",cn_name);
927        assert_eq!(cn_name, "G0H4N".to_string());
928    }
929
930    #[test]
931    #[rustfmt::skip]
932    pub fn test_get_cn_object_name_from_full_distinguishedname() {
933        // Example:
934        // dn_object = CN=G0H4N,CN=USERS,DC=ESSOS,DC=LOCAL
935        let dn_object = "CN=G0H4N,CN=USERS,DC=ESSOS,DC=LOCAL".to_string();
936        let cn_name =  get_cn_object_name_from_full_distinguishedname(&dn_object);
937        println!("dn_object: {:?}",dn_object);
938        println!("cn_name: {:?}",cn_name);
939        assert_eq!(cn_name, "CN=G0H4N".to_string());
940    }
941    
942    #[test]
943    #[rustfmt::skip]
944    pub fn test_get_contained_by_name_from_name() {
945        // Example:
946        // dn_object = CN=G0H4N,CN=USERS,DC=ESSOS,DC=LOCAL
947        let dn_object = "CN=G0H4N,CN=USERS,DC=ESSOS,DC=LOCAL".to_string();
948        let cn_name = "CN=G0H4N".to_string();
949        let contained_by_dn =  get_contained_by_name_from_distinguishedname(&cn_name, &dn_object);
950        println!("dn_object: {:?}",dn_object);
951        println!("contained_by_dn: {:?}",contained_by_dn);
952        assert_eq!(contained_by_dn, "CN=USERS,DC=ESSOS,DC=LOCAL".to_string());
953    }
954}