rusthound_ce/
api.rs

1use std::{collections::HashMap, error::Error};
2
3use indicatif::ProgressBar;
4use ldap3::SearchEntry;
5
6use crate::{
7    args::Options, banner::progress_bar, enums::{get_type, Type, PARSER_MOD_RE1, PARSER_MOD_RE2}, json::{
8        checker::check_all_result,
9    }, 
10    objects::{
11        aiaca::AIACA, certtemplate::CertTemplate, common::parse_unknown, computer::Computer, container::Container, domain::Domain, enterpriseca::EnterpriseCA, fsp::Fsp, gpo::Gpo, group::Group, inssuancepolicie::IssuancePolicie, ntauthstore::NtAuthStore, ou::Ou, rootca::RootCA, trust::Trust, user::User
12    }, 
13    storage::{EntrySource}
14};
15
16#[derive(Default)]
17pub struct ADResults {
18    pub users: Vec<User>,
19    pub groups: Vec<Group>,
20    pub computers: Vec<Computer>,
21    pub ous: Vec<Ou>,
22    pub domains: Vec<Domain>,
23    pub gpos: Vec<Gpo>,
24    pub fsps: Vec<Fsp>,
25    pub containers: Vec<Container>,
26    pub trusts: Vec<Trust>,
27    pub ntauthstores: Vec<NtAuthStore>,
28    pub aiacas: Vec<AIACA>,
29    pub rootcas: Vec<RootCA>,
30    pub enterprisecas: Vec<EnterpriseCA>,
31    pub certtemplates: Vec<CertTemplate>,
32    pub issuancepolicies: Vec<IssuancePolicie>,
33
34    pub mappings: DomainMappings,
35}
36
37#[derive(Default)]
38pub struct DomainMappings {
39    /// DN to SID
40    pub dn_sid: HashMap<String, String>,
41    ///  DN to Type
42    pub sid_type: HashMap<String, String>,
43    /// FQDN to SID
44    pub fqdn_sid: HashMap<String, String>,
45    /// fqdn to an ip address
46    pub fqdn_ip: HashMap<String, String>,
47}
48
49impl ADResults {
50    pub fn new() -> Self {
51        Self::default()
52    }
53}
54
55pub async fn prepare_results_from_source<S: EntrySource>(
56    source: S,
57    options: &Options,
58    total_objects: Option<usize>,
59) -> Result<ADResults, Box<dyn std::error::Error>> {
60    let mut ad_results = parse_result_type_from_source(options, source, total_objects)?;
61
62    // Functions to replace and add missing values
63    check_all_result(
64        options,
65        &mut ad_results.users,
66        &mut ad_results.groups,
67        &mut ad_results.computers,
68        &mut ad_results.ous,
69        &mut ad_results.domains,
70        &mut ad_results.gpos,
71        &mut ad_results.fsps,
72        &mut ad_results.containers,
73        &mut ad_results.trusts,
74        &mut ad_results.ntauthstores,
75        &mut ad_results.aiacas,
76        &mut ad_results.rootcas,
77        &mut ad_results.enterprisecas,
78        &mut ad_results.certtemplates,
79        &mut ad_results.issuancepolicies,
80        &ad_results.mappings.dn_sid,
81        &ad_results.mappings.sid_type,
82        &ad_results.mappings.fqdn_sid,
83        &ad_results.mappings.fqdn_ip,
84    )?;
85
86    Ok(ad_results)
87}
88
89// for `total_objects`, the total number of objects may not be known if the ldap query was never run
90// (e.g run was resumed from cached results)
91pub fn parse_result_type_from_source(
92    common_args: &Options,
93    source: impl EntrySource,
94    total_objects: Option<usize>,
95) -> Result<ADResults, Box<dyn Error>> {
96    let mut results = ADResults::default();
97    // Domain name
98    let domain = &common_args.domain;
99
100    // Needed for progress bar stats
101    let pb = ProgressBar::new(1);
102    let mut count = 0;
103    let total = total_objects;
104    let mut domain_sid: String = "DOMAIN_SID".to_owned();
105
106    log::info!("Starting the LDAP objects parsing...");
107
108    let dn_sid = &mut results.mappings.dn_sid;
109    let sid_type = &mut results.mappings.sid_type;
110    let fqdn_sid = &mut results.mappings.fqdn_sid;
111    let fqdn_ip = &mut results.mappings.fqdn_ip;
112
113    for entry in source.into_entry_iter() {
114        let entry: SearchEntry = entry?.into();
115        // Start parsing with Type matching
116        let atype = get_type(&entry).unwrap_or(Type::Unknown);
117        match atype {
118            Type::User => {
119                let mut user: User = User::new();
120                user.parse(entry, domain, dn_sid, sid_type, &domain_sid)?;
121                results.users.push(user);
122            }
123            Type::Group => {
124                let mut group = Group::new();
125                group.parse(entry, domain, dn_sid, sid_type, &domain_sid)?;
126                results.groups.push(group);
127            }
128            Type::Computer => {
129                let mut computer = Computer::new();
130                computer.parse(
131                    entry,
132                    domain,
133                    dn_sid,
134                    sid_type,
135                    fqdn_sid,
136                    fqdn_ip,
137                    &domain_sid,
138                )?;
139                results.computers.push(computer);
140            }
141            Type::Ou => {
142                let mut ou = Ou::new();
143                ou.parse(entry, domain, dn_sid, sid_type, &domain_sid)?;
144                results.ous.push(ou);
145            }
146            Type::Domain => {
147                let mut domain_object = Domain::new();
148                let domain_sid_from_domain =
149                    domain_object.parse(entry, domain, dn_sid, sid_type)?;
150                domain_sid = domain_sid_from_domain;
151                // Only add domains with valid ObjectIdentifier (excludes DomainDnsZones, ForestDnsZones, etc.)
152                if !domain_object.object_identifier().is_empty() {
153                    results.domains.push(domain_object);
154                }
155            }
156            Type::Gpo => {
157                let mut gpo = Gpo::new();
158                gpo.parse(entry, domain, dn_sid, sid_type, &domain_sid)?;
159                results.gpos.push(gpo);
160            }
161            Type::ForeignSecurityPrincipal => {
162                let mut security_principal = Fsp::new();
163                security_principal.parse(entry, domain, dn_sid, sid_type, &domain_sid)?;
164                results.fsps.push(security_principal);
165            }
166            Type::Container => {
167                if PARSER_MOD_RE1.is_match(&entry.dn.to_uppercase())
168                    || PARSER_MOD_RE2.is_match(&entry.dn.to_uppercase())
169                {
170                    //trace!("Container not to add: {}",&cloneresult.dn.to_uppercase());
171                    continue;
172                }
173
174                //trace!("Container: {}",&entry.dn.to_uppercase());
175                let mut container = Container::new();
176                container.parse(entry, domain, dn_sid, sid_type, &domain_sid)?;
177                results.containers.push(container);
178            }
179            Type::Trust => {
180                let mut trust = Trust::new();
181                trust.parse(entry, domain)?;
182                results.trusts.push(trust);
183            }
184            Type::NtAutStore => {
185                let mut nt_auth_store = NtAuthStore::new();
186                nt_auth_store.parse(entry, domain, dn_sid, sid_type, &domain_sid)?;
187                results.ntauthstores.push(nt_auth_store);
188            }
189            Type::AIACA => {
190                let mut aiaca = AIACA::new();
191                aiaca.parse(entry, domain, dn_sid, sid_type, &domain_sid)?;
192                results.aiacas.push(aiaca);
193            }
194            Type::RootCA => {
195                let mut root_ca = RootCA::new();
196                root_ca.parse(entry, domain, dn_sid, sid_type, &domain_sid)?;
197                results.rootcas.push(root_ca);
198            }
199            Type::EnterpriseCA => {
200                let mut enterprise_ca = EnterpriseCA::new();
201                enterprise_ca.parse(entry, domain, dn_sid, sid_type, &domain_sid)?;
202                results.enterprisecas.push(enterprise_ca);
203            }
204            Type::CertTemplate => {
205                let mut cert_template = CertTemplate::new();
206                cert_template.parse(entry, domain, dn_sid, sid_type, &domain_sid)?;
207                results.certtemplates.push(cert_template);
208            }
209            Type::IssuancePolicie => {
210                let mut issuance_policie = IssuancePolicie::new();
211                issuance_policie.parse(entry, domain, dn_sid, sid_type, &domain_sid)?;
212                results.issuancepolicies.push(issuance_policie);
213            }
214            Type::Unknown => {
215                let _unknown = parse_unknown(entry, domain);
216            }
217        }
218        // Manage progress bar
219        // Pourcentage (%) = 100 x Valeur partielle/Valeur totale
220        if let Some(total) = total {
221            count += 1;
222            let pourcentage = 100 * count / total;
223            progress_bar(
224                pb.to_owned(),
225                "Parsing LDAP objects".to_string(),
226                pourcentage.try_into()?,
227                "%".to_string(),
228            );
229        }
230    }
231
232    pb.finish_and_clear();
233    log::info!("Parsing LDAP objects finished!");
234    Ok(results)
235}
236