1use std::{collections::HashMap, error::Error};
2
3use indicatif::ProgressBar;
4use ldap3::SearchEntry;
5use rayon::prelude::*;
6
7use crate::{
8 args::Options, banner::progress_bar, enums::{PARSER_MOD_RE1, PARSER_MOD_RE2, Type, get_type}, json::checker::check_all_result,
9 objects::{
10 aiaca::AIACA,
11 certtemplate::CertTemplate,
12 common::parse_unknown,
13 computer::Computer,
14 container::Container,
15 domain::Domain,
16 enterpriseca::EnterpriseCA,
17 fsp::Fsp,
18 gpo::Gpo,
19 group::Group,
20 inssuancepolicie::IssuancePolicie,
21 ntauthstore::NtAuthStore,
22 ou::Ou,
23 rootca::RootCA,
24 trust::Trust,
25 user::User,
26 schema::Schema,
27 },
28 transport::ldap::LdapSearchEntry,
29 storage::{DiskStorageReader, EntrySource},
30};
31
32#[derive(Default)]
33pub struct ADResults {
34 pub users: Vec<User>,
35 pub groups: Vec<Group>,
36 pub computers: Vec<Computer>,
37 pub ous: Vec<Ou>,
38 pub domains: Vec<Domain>,
39 pub gpos: Vec<Gpo>,
40 pub fsps: Vec<Fsp>,
41 pub containers: Vec<Container>,
42 pub trusts: Vec<Trust>,
43 pub ntauthstores: Vec<NtAuthStore>,
44 pub aiacas: Vec<AIACA>,
45 pub rootcas: Vec<RootCA>,
46 pub enterprisecas: Vec<EnterpriseCA>,
47 pub certtemplates: Vec<CertTemplate>,
48 pub issuancepolicies: Vec<IssuancePolicie>,
49 pub mappings: DomainMappings,
50}
51
52#[derive(Default)]
53pub struct DomainMappings {
54 pub dn_sid: HashMap<String, String>,
56 pub sid_type: HashMap<String, String>,
58 pub fqdn_sid: HashMap<String, String>,
60 pub fqdn_ip: HashMap<String, String>,
62 pub schema_guid_map: HashMap<String, String>,
64}
65
66impl ADResults {
67 pub fn new() -> Self {
68 Self::default()
69 }
70}
71
72pub async fn prepare_results_from_source<S: EntrySource>(
73 source: S,
74 options: &Options,
75 total_objects: Option<usize>,
76) -> Result<ADResults, Box<dyn std::error::Error>> {
77 let mut ad_results = parse_result_type_from_source(options, source, total_objects)?;
78 run_checker(options, &mut ad_results)?;
79 Ok(ad_results)
80}
81
82pub async fn prepare_results_from_disk(
87 reader: DiskStorageReader<LdapSearchEntry>,
88 options: &Options,
89 total_objects: Option<usize>,
90) -> Result<ADResults, Box<dyn std::error::Error>> {
91 let mut ad_results = parse_result_type_from_disk(options, reader, total_objects)?;
92 run_checker(options, &mut ad_results)?;
93 Ok(ad_results)
94}
95
96fn run_checker(options: &Options, ad_results: &mut ADResults) -> Result<(), Box<dyn std::error::Error>> {
98 check_all_result(
99 options,
100 &mut ad_results.users,
101 &mut ad_results.groups,
102 &mut ad_results.computers,
103 &mut ad_results.ous,
104 &mut ad_results.domains,
105 &mut ad_results.gpos,
106 &mut ad_results.fsps,
107 &mut ad_results.containers,
108 &mut ad_results.trusts,
109 &mut ad_results.ntauthstores,
110 &mut ad_results.aiacas,
111 &mut ad_results.rootcas,
112 &mut ad_results.enterprisecas,
113 &mut ad_results.certtemplates,
114 &mut ad_results.issuancepolicies,
115 &ad_results.mappings.dn_sid,
116 &ad_results.mappings.sid_type,
117 &ad_results.mappings.fqdn_sid,
118 &ad_results.mappings.fqdn_ip,
119 )
120}
121
122const PARSE_BATCH: usize = 16_384;
128
129#[derive(Default)]
136struct LocalMaps {
137 dn_sid: HashMap<String, String>,
138 sid_type: HashMap<String, String>,
139 fqdn_sid: HashMap<String, String>,
140 fqdn_ip: HashMap<String, String>,
141}
142
143enum Parsed {
147 User(Box<User>),
148 Group(Box<Group>),
149 Computer(Box<Computer>),
150 Ou(Box<Ou>),
151 Gpo(Box<Gpo>),
152 Fsp(Box<Fsp>),
153 Container(Box<Container>),
154 Trust(Box<Trust>),
155 NtAuthStore(Box<NtAuthStore>),
156 AIACA(Box<AIACA>),
157 RootCA(Box<RootCA>),
158 EnterpriseCA(Box<EnterpriseCA>),
159 CertTemplate(Box<CertTemplate>),
160 IssuancePolicie(Box<IssuancePolicie>),
161 Skip,
163}
164
165fn parse_one(
169 entry: SearchEntry,
170 domain: &str,
171 domain_sid: &str,
172 schema_guid_map: &HashMap<String, String>,
173) -> Result<(Parsed, LocalMaps), Box<dyn Error>> {
174 let mut m = LocalMaps::default();
175 let atype = get_type(&entry).unwrap_or(Type::Unknown);
176
177 let parsed = match atype {
178 Type::User => {
179 let mut user = User::new();
180 user.parse(entry, domain, &mut m.dn_sid, &mut m.sid_type, domain_sid, schema_guid_map)?;
181 Parsed::User(Box::new(user))
182 }
183 Type::Group => {
184 let mut group = Group::new();
185 group.parse(entry, domain, &mut m.dn_sid, &mut m.sid_type, domain_sid, schema_guid_map)?;
186 Parsed::Group(Box::new(group))
187 }
188 Type::Computer => {
189 let mut computer = Computer::new();
190 computer.parse(
191 entry, domain,
192 &mut m.dn_sid, &mut m.sid_type, &mut m.fqdn_sid, &mut m.fqdn_ip,
193 domain_sid, schema_guid_map,
194 )?;
195 Parsed::Computer(Box::new(computer))
196 }
197 Type::Ou => {
198 let mut ou = Ou::new();
199 ou.parse(entry, domain, &mut m.dn_sid, &mut m.sid_type, domain_sid, schema_guid_map)?;
200 Parsed::Ou(Box::new(ou))
201 }
202 Type::Gpo => {
203 let mut gpo = Gpo::new();
204 gpo.parse(entry, domain, &mut m.dn_sid, &mut m.sid_type, domain_sid, schema_guid_map)?;
205 Parsed::Gpo(Box::new(gpo))
206 }
207 Type::ForeignSecurityPrincipal => {
208 let mut security_principal = Fsp::new();
209 security_principal.parse(entry, domain, &mut m.dn_sid, &mut m.sid_type, domain_sid)?;
210 Parsed::Fsp(Box::new(security_principal))
211 }
212 Type::Container => {
213 if PARSER_MOD_RE1.is_match(&entry.dn.to_uppercase())
214 || PARSER_MOD_RE2.is_match(&entry.dn.to_uppercase())
215 {
216 Parsed::Skip
217 } else {
218 let mut container = Container::new();
219 container.parse(entry, domain, &mut m.dn_sid, &mut m.sid_type, domain_sid, schema_guid_map)?;
220 Parsed::Container(Box::new(container))
221 }
222 }
223 Type::Trust => {
224 let mut trust = Trust::new();
225 trust.parse(entry, domain)?;
226 Parsed::Trust(Box::new(trust))
227 }
228 Type::NtAutStore => {
229 let mut nt_auth_store = NtAuthStore::new();
230 nt_auth_store.parse(entry, domain, &mut m.dn_sid, &mut m.sid_type, domain_sid, schema_guid_map)?;
231 Parsed::NtAuthStore(Box::new(nt_auth_store))
232 }
233 Type::AIACA => {
234 let mut aiaca = AIACA::new();
235 aiaca.parse(entry, domain, &mut m.dn_sid, &mut m.sid_type, domain_sid, schema_guid_map)?;
236 Parsed::AIACA(Box::new(aiaca))
237 }
238 Type::RootCA => {
239 let mut root_ca = RootCA::new();
240 root_ca.parse(entry, domain, &mut m.dn_sid, &mut m.sid_type, domain_sid, schema_guid_map)?;
241 Parsed::RootCA(Box::new(root_ca))
242 }
243 Type::EnterpriseCA => {
244 let mut enterprise_ca = EnterpriseCA::new();
245 enterprise_ca.parse(entry, domain, &mut m.dn_sid, &mut m.sid_type, domain_sid, schema_guid_map)?;
246 Parsed::EnterpriseCA(Box::new(enterprise_ca))
247 }
248 Type::CertTemplate => {
249 let mut cert_template = CertTemplate::new();
250 cert_template.parse(entry, domain, &mut m.dn_sid, &mut m.sid_type, domain_sid, schema_guid_map)?;
251 Parsed::CertTemplate(Box::new(cert_template))
252 }
253 Type::IssuancePolicie => {
254 let mut issuance_policie = IssuancePolicie::new();
255 issuance_policie.parse(entry, domain, &mut m.dn_sid, &mut m.sid_type, domain_sid, schema_guid_map)?;
256 Parsed::IssuancePolicie(Box::new(issuance_policie))
257 }
258 Type::Domain | Type::Schema => Parsed::Skip,
260 Type::Unknown => {
261 let _unknown = parse_unknown(entry, domain);
262 Parsed::Skip
263 }
264 };
265
266 Ok((parsed, m))
267}
268
269#[allow(clippy::too_many_arguments)]
272fn parse_batch(
273 buffer: &mut Vec<SearchEntry>,
274 results: &mut ADResults,
275 domain: &str,
276 domain_sid: &str,
277 count: &mut usize,
278 total: Option<usize>,
279 pb: &ProgressBar,
280) -> Result<(), Box<dyn Error>> {
281 if buffer.is_empty() {
282 return Ok(());
283 }
284
285 let entries = std::mem::take(buffer);
286
287 let parsed: Vec<Result<(Parsed, LocalMaps), String>> = {
290 let schema_guid_map = &results.mappings.schema_guid_map;
291 entries
292 .into_par_iter()
293 .map(|entry| parse_one(entry, domain, domain_sid, schema_guid_map).map_err(|e| e.to_string()))
294 .collect()
295 };
296
297 for item in parsed {
300 let (obj, maps) = item.map_err(|e| -> Box<dyn Error> { e.into() })?;
301
302 results.mappings.dn_sid.extend(maps.dn_sid);
303 results.mappings.sid_type.extend(maps.sid_type);
304 results.mappings.fqdn_sid.extend(maps.fqdn_sid);
305 results.mappings.fqdn_ip.extend(maps.fqdn_ip);
306
307 match obj {
308 Parsed::User(o) => results.users.push(*o),
309 Parsed::Group(o) => results.groups.push(*o),
310 Parsed::Computer(o) => results.computers.push(*o),
311 Parsed::Ou(o) => results.ous.push(*o),
312 Parsed::Gpo(o) => results.gpos.push(*o),
313 Parsed::Fsp(o) => results.fsps.push(*o),
314 Parsed::Container(o) => results.containers.push(*o),
315 Parsed::Trust(o) => results.trusts.push(*o),
316 Parsed::NtAuthStore(o) => results.ntauthstores.push(*o),
317 Parsed::AIACA(o) => results.aiacas.push(*o),
318 Parsed::RootCA(o) => results.rootcas.push(*o),
319 Parsed::EnterpriseCA(o) => results.enterprisecas.push(*o),
320 Parsed::CertTemplate(o) => results.certtemplates.push(*o),
321 Parsed::IssuancePolicie(o) => results.issuancepolicies.push(*o),
322 Parsed::Skip => {}
323 }
324
325 update_progress(count, total, pb)?;
326 }
327
328 Ok(())
329}
330
331fn update_progress(count: &mut usize, total: Option<usize>, pb: &ProgressBar) -> Result<(), Box<dyn Error>> {
333 if let Some(total) = total {
334 *count += 1;
335 let percentage = 100 * *count / total;
337 progress_bar(
338 pb.to_owned(),
339 "Parsing LDAP objects".to_string(),
340 percentage.try_into()?,
341 "%".to_string(),
342 );
343 }
344 Ok(())
345}
346
347pub fn parse_result_type_from_source(
358 common_args: &Options,
359 source: impl EntrySource,
360 total_objects: Option<usize>,
361) -> Result<ADResults, Box<dyn Error>> {
362 let mut results = ADResults::default();
363 let domain = &common_args.domain;
365
366 let pb = ProgressBar::new(1);
368 let mut count = 0usize;
369 let total = total_objects;
370 let mut domain_sid: String = "DOMAIN_SID".to_owned();
371
372 log::info!("Starting the LDAP objects parsing...");
373
374 let mut buffer: Vec<SearchEntry> = Vec::with_capacity(PARSE_BATCH);
375
376 for entry in source.into_entry_iter() {
377 let entry: SearchEntry = entry?.into();
378 route_entry(entry, &mut results, &mut buffer, domain, &mut domain_sid, &mut count, total, &pb)?;
379 }
380
381 parse_batch(&mut buffer, &mut results, domain, &domain_sid, &mut count, total, &pb)?;
383
384 pb.finish_and_clear();
385 log::info!("Parsing LDAP objects finished!");
386 Ok(results)
387}
388
389#[allow(clippy::too_many_arguments)]
393fn route_entry(
394 entry: SearchEntry,
395 results: &mut ADResults,
396 buffer: &mut Vec<SearchEntry>,
397 domain: &str,
398 domain_sid: &mut String,
399 count: &mut usize,
400 total: Option<usize>,
401 pb: &ProgressBar,
402) -> Result<(), Box<dyn Error>> {
403 let atype = get_type(&entry).unwrap_or(Type::Unknown);
404 match atype {
405 Type::Schema => {
406 let schema = Schema::new();
408 schema.parse(entry, &mut results.mappings.schema_guid_map)?;
409 update_progress(count, total, pb)?;
410 }
411 Type::Domain => {
412 parse_batch(buffer, results, domain, domain_sid.as_str(), count, total, pb)?;
417
418 let mut domain_object = Domain::new();
419 let domain_sid_from_domain = domain_object.parse(
420 entry,
421 domain,
422 &mut results.mappings.dn_sid,
423 &mut results.mappings.sid_type,
424 &results.mappings.schema_guid_map,
425 )?;
426 if domain_sid_from_domain != "DOMAIN_SID" && !domain_sid_from_domain.is_empty() {
428 *domain_sid = domain_sid_from_domain;
429 }
430 if !domain_object.object_identifier().is_empty() {
431 results.domains.push(domain_object);
432 }
433 update_progress(count, total, pb)?;
434 }
435 _ => {
436 buffer.push(entry);
437 if buffer.len() >= PARSE_BATCH {
438 parse_batch(buffer, results, domain, domain_sid.as_str(), count, total, pb)?;
439 }
440 }
441 }
442 Ok(())
443}
444
445pub fn parse_result_type_from_disk(
452 common_args: &Options,
453 mut reader: DiskStorageReader<LdapSearchEntry>,
454 total_objects: Option<usize>,
455) -> Result<ADResults, Box<dyn Error>> {
456 let mut results = ADResults::default();
457 let domain = &common_args.domain;
458
459 let pb = ProgressBar::new(1);
460 let mut count = 0usize;
461 let total = total_objects;
462 let mut domain_sid: String = "DOMAIN_SID".to_owned();
463
464 log::info!("Starting the LDAP objects parsing...");
465
466 let mut raw_buf: Vec<Vec<u8>> = Vec::with_capacity(PARSE_BATCH);
467 let mut buffer: Vec<SearchEntry> = Vec::with_capacity(PARSE_BATCH);
468
469 loop {
470 match reader.next_raw() {
471 Some(Ok(blob)) => {
472 raw_buf.push(blob);
473 if raw_buf.len() >= PARSE_BATCH {
474 decode_and_route(&mut raw_buf, &mut results, &mut buffer, domain, &mut domain_sid, &mut count, total, &pb)?;
475 }
476 }
477 Some(Err(e)) => return Err(e.into()),
478 None => break,
479 }
480 }
481
482 decode_and_route(&mut raw_buf, &mut results, &mut buffer, domain, &mut domain_sid, &mut count, total, &pb)?;
484 parse_batch(&mut buffer, &mut results, domain, &domain_sid, &mut count, total, &pb)?;
485
486 pb.finish_and_clear();
487 log::info!("Parsing LDAP objects finished!");
488 Ok(results)
489}
490
491#[allow(clippy::too_many_arguments)]
494fn decode_and_route(
495 raw_buf: &mut Vec<Vec<u8>>,
496 results: &mut ADResults,
497 buffer: &mut Vec<SearchEntry>,
498 domain: &str,
499 domain_sid: &mut String,
500 count: &mut usize,
501 total: Option<usize>,
502 pb: &ProgressBar,
503) -> Result<(), Box<dyn Error>> {
504 if raw_buf.is_empty() {
505 return Ok(());
506 }
507
508 let blobs = std::mem::take(raw_buf);
509
510 let decoded: Vec<Result<SearchEntry, String>> = blobs
512 .into_par_iter()
513 .map(|data| {
514 bincode::decode_from_slice::<LdapSearchEntry, _>(&data, bincode::config::standard())
515 .map(|(entry, _)| SearchEntry::from(entry))
516 .map_err(|e| format!("Failed to decode item: {e:?}"))
517 })
518 .collect();
519
520 for decoded_entry in decoded {
521 let entry = decoded_entry.map_err(|e| -> Box<dyn Error> { e.into() })?;
522 route_entry(entry, results, buffer, domain, domain_sid, count, total, pb)?;
523 }
524
525 Ok(())
526}
527