1use serde_json::value::Value;
2use serde::{Deserialize, Serialize};
3use ldap3::SearchEntry;
4use log::{debug, error, trace};
5use std::collections::HashMap;
6use std::error::Error;
7use std::collections::HashSet;
8use x509_parser::prelude::*;
9
10use crate::enums::decode_guid_le;
11use crate::enums::regex::{OBJECT_SID_RE1, SID_PART1_RE1};
12use crate::objects::common::{LdapObject, AceTemplate, SPNTarget, Link, Member};
13use crate::utils::date::{convert_timestamp, string_to_epoch};
14use crate::utils::crypto::convert_encryption_types;
15use crate::enums::acl::{
16 parse_embedded_security_descriptor, parse_gmsa, parse_ntsecuritydescriptor,
17};
18use crate::enums::secdesc::LdapSid;
19use crate::enums::sid::sid_maker;
20use crate::enums::spntasks::check_spn;
21use crate::enums::uacflags::get_flag;
22
23#[derive(Debug, Clone, Deserialize, Serialize, Default)]
25pub struct User {
26 #[serde(rename ="ObjectIdentifier")]
27 object_identifier: String,
28 #[serde(rename ="IsDeleted")]
29 is_deleted: bool,
30 #[serde(rename ="IsACLProtected")]
31 is_acl_protected: bool,
32 #[serde(rename ="Properties")]
33 properties: UserProperties,
34 #[serde(rename ="PrimaryGroupSID")]
35 primary_group_sid: String,
36 #[serde(rename ="SPNTargets")]
37 spn_targets: Vec<SPNTarget>,
38 #[serde(rename ="UnconstrainedDelegation")]
39 unconstrained_delegation: bool,
40 #[serde(rename ="DomainSID")]
41 domain_sid: String,
42 #[serde(rename ="Aces")]
43 aces: Vec<AceTemplate>,
44 #[serde(rename ="AllowedToDelegate")]
45 allowed_to_delegate: Vec<Member>,
46 #[serde(rename ="HasSIDHistory")]
47 has_sid_history: Vec<Member>,
48 #[serde(rename ="ContainedBy")]
49 contained_by: Option<Member>,
50}
51
52impl User {
53 pub fn new() -> Self {
55 Self { ..Default::default()}
56 }
57
58 pub fn properties(&self) -> &UserProperties {
60 &self.properties
61 }
62 pub fn aces(&self) -> &Vec<AceTemplate> {
63 &self.aces
64 }
65 pub fn object_identifier(&self) -> &String {
66 &self.object_identifier
67 }
68
69 pub fn properties_mut(&mut self) -> &mut UserProperties {
71 &mut self.properties
72 }
73 pub fn aces_mut(&mut self) -> &mut Vec<AceTemplate> {
74 &mut self.aces
75 }
76 pub fn object_identifier_mut(&mut self) -> &mut String {
77 &mut self.object_identifier
78 }
79 pub fn has_sid_history_mut(&mut self) -> &mut Vec<Member> {
80 &mut self.has_sid_history
81 }
82
83 pub fn parse(
86 &mut self,
87 result: SearchEntry,
88 domain: &str,
89 dn_sid: &mut HashMap<String, String>,
90 sid_type: &mut HashMap<String, String>,
91 domain_sid: &str,
92 schema_guid_map: &HashMap<String, String>,
93 ) -> Result<(), Box<dyn Error>> {
94 let result_dn: String = result.dn.to_uppercase();
95 let result_attrs: HashMap<String, Vec<String>> = result.attrs;
96 let result_bin: HashMap<String, Vec<Vec<u8>>> = result.bin_attrs;
97
98 debug!("Parse user: {result_dn}");
100
101 for (key, value) in &result_attrs {
103 trace!(" {key:?}:{value:?}");
104 }
105 for (key, value) in &result_bin {
107 trace!(" {key:?}:{value:?}");
108 }
109
110 self.properties.domain = domain.to_uppercase();
112 self.properties.distinguishedname = result_dn;
113 self.properties.enabled = true;
114 self.domain_sid = domain_sid.to_string();
115
116 let mut group_id: String ="".to_owned();
118 for (key, value) in &result_attrs {
119 match key.as_str() {
120 "sAMAccountName" => {
121 let name = &value[0];
122 let email = format!("{}@{}",name.to_owned(),domain);
123 self.properties.name = email.to_uppercase();
124 self.properties.samaccountname = name.to_string();
125 }
126 "description" => {
127 self.properties.description = Some(value[0].to_owned());
128 }
129 "mail" => {
130 self.properties.email = value[0].to_owned();
131 }
132 "title" => {
133 self.properties.title = value[0].to_owned();
134 }
135 "userPassword" => {
136 self.properties.userpassword = value[0].to_owned();
137 }
138 "unixUserPassword" => {
139 self.properties.unixpassword = value[0].to_owned();
140 }
141 "unicodePwd" => {
142 self.properties.unicodepassword = value[0].to_owned();
143 }
144 "msSFU30Password" => {
145 }
147 "displayName" => {
148 self.properties.displayname = value[0].to_owned();
149 }
150 "adminCount" => {
151 let admincount = value[0].parse::<i32>().unwrap_or(0) != 0;
152 self.properties.admincount = admincount;
153 self.properties.adminsdholderprotected = admincount;
154 }
155 "homeDirectory" => {
156 self.properties.homedirectory = value[0].to_owned();
157 }
158 "scriptPath" => {
159 self.properties.logonscript = value[0].to_owned();
160 }
161 "profilePath" | "profilepath" => {
162 if let Some(profile_path) = value.first() {
163 self.properties.profilepath = profile_path.to_owned();
164 }
165 }
166 "userAccountControl" => {
167 let uac = &value[0].parse::<u32>().unwrap_or(0);
168 self.properties.useraccountcontrol = *uac;
169 let uac_flags = get_flag(*uac);
170 for flag in uac_flags {
172 if flag.contains("AccountDisable") {
173 self.properties.enabled = false;
174 };
175 if flag.contains("PasswordNotRequired") {
177 self.properties.passwordnotreqd = true;
178 };
179 if flag.contains("DontExpirePassword") {
180 self.properties.pwdneverexpires = true;
181 };
182 if flag.contains("DontReqPreauth") {
183 self.properties.dontreqpreauth = true;
184 };
185 if flag.contains("TrustedForDelegation") {
187 self.properties.unconstraineddelegation = true;
188 self.unconstrained_delegation = true;
189 };
190 if flag.contains("NotDelegated") {
191 self.properties.sensitive = true;
192 };
193 if flag.contains("TrustedToAuthForDelegation") {
195 self.properties.trustedtoauth = true;
196 };
197 if flag.contains("SmartcardRequired") {
198 self.properties.smartcardrequired = true;
199 };
200 if flag.contains("UseDesKeyOnly") {
201 self.properties.usedeskeyonly = true;
202 };
203 if flag.contains("EncryptedTextPwdAllowed") {
204 self.properties.encryptedtextpwdallowed = true;
205 };
206 if flag.contains("Script") {
207 self.properties.logonscriptenabled = true;
208 };
209 }
210 }
211 "msDS-User-Account-Control-Computed" => {
212 const UF_LOCKOUT: u32 = 0x0000_0010;
216 const UF_PASSWORD_EXPIRED: u32 = 0x0080_0000;
217 let computed = value[0].parse::<u32>().unwrap_or(0);
218 self.properties.lockedout = computed & UF_LOCKOUT != 0;
219 self.properties.passwordexpired = computed & UF_PASSWORD_EXPIRED != 0;
220 }
221 "msDS-AllowedToDelegateTo" => {
222 let mut vec_members2: Vec<Member> = Vec::new();
223 let mut seen = HashSet::<String>::new();
224
225 for spn_raw in value {
226 let spn = spn_raw.trim().replace('\\', "/");
228 let host_part = spn
230 .split_once('/') .map(|(_, rest)| rest)
232 .unwrap_or(spn.as_str());
233
234 let host = host_part.split(':').next().unwrap_or(host_part);
236
237 let fqdn_upper = host.trim().to_ascii_uppercase();
239 if fqdn_upper.is_empty() {
240 error!("Skipping empty host in SPN: {:?}", spn_raw);
241 continue;
242 }
243
244 if seen.insert(fqdn_upper.clone()) {
246 let mut m = Member::new();
247 *m.object_identifier_mut() = fqdn_upper; *m.object_type_mut() = "Computer".to_string();
249 vec_members2.push(m);
250 }
251 }
252
253 self.allowed_to_delegate = vec_members2;
254 }
255 "lastLogon" => {
256 let lastlogon = &value[0].parse::<i64>().unwrap_or(0);
257 if lastlogon.is_positive() {
258 let epoch = convert_timestamp(*lastlogon);
259 self.properties.lastlogon = epoch;
260 }
261 }
262 "lastLogonTimestamp" => {
263 let lastlogontimestamp = &value[0].parse::<i64>().unwrap_or(0);
264 if lastlogontimestamp.is_positive() {
265 let epoch = convert_timestamp(*lastlogontimestamp);
266 self.properties.lastlogontimestamp = epoch;
267 }
268 }
269 "pwdLastSet" => {
270 let pwdlastset = &value[0].parse::<i64>().unwrap_or(0);
271 if pwdlastset.is_positive() {
272 let epoch = convert_timestamp(*pwdlastset);
273 self.properties.pwdlastset = epoch;
274 }
275 }
276 "whenCreated" => {
277 let epoch = string_to_epoch(&value[0])?;
278 if epoch.is_positive() {
279 self.properties.whencreated = epoch;
280 }
281 }
282 "servicePrincipalName" => {
283 let mut targets: Vec<SPNTarget> = Vec::new();
285 let mut result: Vec<String> = Vec::new();
286 let mut added: bool = false;
287 for v in value {
288 result.push(v.to_owned());
289 let _target = match check_spn(v).to_owned() {
291 Some(_target) => {
292 if !added {
293 targets.push(_target.to_owned());
294 added = true;
295 }
296 },
297 None => {}
298 };
299 }
300 self.properties.serviceprincipalnames = result;
301 self.properties.hasspn = true;
302 self.spn_targets = targets;
303 }
304 "primaryGroupID" => {
305 group_id = value[0].to_owned();
306 }
307 "isDeleted" => {
308 self.is_deleted = true;
309 }
310 "msDS-SupportedEncryptionTypes" => {
311 self.properties.supportedencryptiontypes = convert_encryption_types(value[0].parse::<i32>().unwrap_or(0));
312 }
313 _ => {}
314 }
315 }
316
317 let mut sid: String = "".to_owned();
319 for (key, value) in &result_bin {
320 match key.as_str() {
321 "objectGUID" => {
322 let guid = decode_guid_le(&value[0]);
324 self.properties.objectguid = guid;
325 }
326 "objectSid" => {
327 sid = sid_maker(LdapSid::parse(&value[0]).unwrap().1, domain);
328 self.object_identifier = sid.to_owned();
329
330 for domain_sid in OBJECT_SID_RE1.captures_iter(&sid) {
331 self.properties.domainsid = domain_sid[0].to_owned().to_string();
332 }
333 }
334 "nTSecurityDescriptor" => {
335 let relations_ace = parse_ntsecuritydescriptor(
337 self,
338 &value[0],
339 "User",
340 &result_attrs,
341 &result_bin,
342 domain,
343 schema_guid_map,
344 );
345 self.aces_mut().extend(relations_ace);
346 }
347 "sIDHistory" => {
348 let mut list_sid_history: Vec<String> = Vec::new();
349 let mut has_sid_history: Vec<Member> = Vec::new();
350 for bsid in value {
351 debug!("sIDHistory: {:?}", &bsid);
352 let sid = sid_maker(LdapSid::parse(bsid).unwrap().1, domain);
353 let mut member = Member::new();
354 *member.object_identifier_mut() = sid.clone();
355 has_sid_history.push(member);
356 list_sid_history.push(sid);
357 }
358 self.properties.sidhistory = list_sid_history;
359 self.has_sid_history = has_sid_history;
360 }
361 "msDS-GroupMSAMembership" => {
362 let mut relations_ace = parse_embedded_security_descriptor(
364 self,
365 &value[0],
366 "User",
367 &result_attrs,
368 &result_bin,
369 domain,
370 schema_guid_map,
371 );
372 parse_gmsa(&mut relations_ace, self);
375 }
377 "userCertificate" => {
378 let res = X509Certificate::from_der(&value[0]);
381 match res {
382 Ok((_rem, _cert)) => {},
383 _ => error!("CA x509 certificate parsing failed: {:?}", res),
384 }
385 }
386 _ => {}
387 }
388 }
389
390 #[allow(irrefutable_let_patterns)]
392 if let id = group_id {
393 if let Some(part1) = SID_PART1_RE1.find(&sid) {
394 self.primary_group_sid = format!("{}{}", part1.as_str(), id);
395 } else {
396 eprintln!("[!] Regex did not match any part of the SID");
397 }
398 }
399
400 dn_sid.insert(
402 self.properties.distinguishedname.to_owned(),
403 self.object_identifier.to_owned(),
404 );
405 sid_type.insert(
407 self.object_identifier.to_owned(),
408 "User".to_string(),
409 );
410
411 Ok(())
414 }
415}
416
417impl LdapObject for User {
419 fn to_json(&self) -> Value {
421 serde_json::to_value(self).unwrap()
422 }
423
424 fn get_object_identifier(&self) -> &String {
426 &self.object_identifier
427 }
428 fn get_is_acl_protected(&self) -> &bool {
429 &self.is_acl_protected
430 }
431 fn get_aces(&self) -> &Vec<AceTemplate> {
432 &self.aces
433 }
434 fn get_spntargets(&self) -> &Vec<SPNTarget> {
435 &self.spn_targets
436 }
437 fn get_allowed_to_delegate(&self) -> &Vec<Member> {
438 &self.allowed_to_delegate
439 }
440 fn get_links(&self) -> &Vec<Link> {
441 panic!("Not used by current object.");
442 }
443 fn get_contained_by(&self) -> &Option<Member> {
444 &self.contained_by
445 }
446 fn get_child_objects(&self) -> &Vec<Member> {
447 panic!("Not used by current object.");
448 }
449 fn get_haslaps(&self) -> &bool {
450 &false
451 }
452
453 fn get_aces_mut(&mut self) -> &mut Vec<AceTemplate> {
455 &mut self.aces
456 }
457 fn get_spntargets_mut(&mut self) -> &mut Vec<SPNTarget> {
458 &mut self.spn_targets
459 }
460 fn get_allowed_to_delegate_mut(&mut self) -> &mut Vec<Member> {
461 &mut self.allowed_to_delegate
462 }
463
464 fn set_is_acl_protected(&mut self, is_acl_protected: bool) {
466 self.is_acl_protected = is_acl_protected;
467 self.properties.isaclprotected = is_acl_protected;
468 }
469 fn set_aces(&mut self, aces: Vec<AceTemplate>) {
470 self.aces = aces;
471 }
472 fn set_spntargets(&mut self, spn_targets: Vec<SPNTarget>) {
473 self.spn_targets = spn_targets;
474 }
475 fn set_allowed_to_delegate(&mut self, allowed_to_delegate: Vec<Member>) {
476 self.allowed_to_delegate = allowed_to_delegate;
477 }
478 fn set_links(&mut self, _links: Vec<Link>) {
479 }
481 fn set_contained_by(&mut self, contained_by: Option<Member>) {
482 self.contained_by = contained_by;
483 }
484 fn set_child_objects(&mut self, _child_objects: Vec<Member>) {
485 }
487 fn set_owner_rights_flags(&mut self, any: bool, any_inherited: bool) {
488 self.properties.doesanyacegrantownerrights = any;
489 self.properties.doesanyinheritedacegrantownerrights = any_inherited;
490 }
491}
492
493#[derive(Debug, Clone, Deserialize, Serialize, Default)]
495pub struct UserProperties {
496 domain: String,
497 name: String,
498 domainsid: String,
499 objectguid: String,
500 doesanyacegrantownerrights: bool,
501 doesanyinheritedacegrantownerrights: bool,
502 isaclprotected: bool,
503 distinguishedname: String,
504 highvalue: bool,
505 description: Option<String>,
506 whencreated: i64,
507 sensitive: bool,
508 dontreqpreauth: bool,
509 passwordnotreqd: bool,
510 unconstraineddelegation: bool,
511 pwdneverexpires: bool,
512 enabled: bool,
513 trustedtoauth: bool,
514 lastlogon: i64,
515 lastlogontimestamp: i64,
516 pwdlastset: i64,
517 serviceprincipalnames: Vec<String>,
518 hasspn: bool,
519 displayname: String,
520 email: String,
521 title: String,
522 homedirectory: String,
523 logonscript: String,
524 useraccountcontrol: u32,
525 samaccountname: String,
526 userpassword: String,
527 unixpassword: String,
528 unicodepassword: String,
529 sfupassword: String,
530 profilepath: String,
531 admincount: bool,
532 adminsdholderprotected: bool,
533 smartcardrequired: bool,
534 usedeskeyonly: bool,
535 encryptedtextpwdallowed: bool,
536 logonscriptenabled: bool,
537 lockedout: bool,
538 passwordexpired: bool,
539 supportedencryptiontypes: Vec<String>,
540 sidhistory: Vec<String>,
541 allowedtodelegate: Vec<String>,
542}
543
544impl UserProperties {
545 pub fn name(&self) -> &String {
547 &self.name
548 }
549 pub fn domainsid(&self) -> &String {
550 &self.domainsid
551 }
552 pub fn isaclprotected(&self) -> &bool {
553 &self.isaclprotected
554 }
555
556 pub fn name_mut(&mut self) -> &mut String {
558 &mut self.name
559 }
560 pub fn domainsid_mut(&mut self) -> &mut String {
561 &mut self.domainsid
562 }
563 pub fn isaclprotected_mut(&mut self) -> &mut bool {
564 &mut self.isaclprotected
565 }
566}
567
568#[cfg(test)]
569mod tests {
570 use super::*;
571
572 fn parse_user_with_attrs(attrs: HashMap<String, Vec<String>>) -> User {
573 let mut user = User::new();
574 let result = SearchEntry {
575 dn: "CN=Test User,OU=Users,DC=example,DC=local".to_string(),
576 attrs,
577 bin_attrs: HashMap::new(),
578 };
579 let mut dn_sid = HashMap::new();
580 let mut sid_type = HashMap::new();
581 let schema_guid_map = HashMap::new();
582
583 user.parse(
584 result,
585 "example.local",
586 &mut dn_sid,
587 &mut sid_type,
588 "S-1-5-21-1-2-3",
589 &schema_guid_map,
590 )
591 .unwrap();
592
593 user
594 }
595
596 #[test]
597 fn parse_sets_profilepath_from_ldap_profile_path() {
598 let mut attrs = HashMap::new();
599 attrs.insert(
600 "sAMAccountName".to_string(),
601 vec!["rh.profilepath".to_string()],
602 );
603 attrs.insert(
604 "profilePath".to_string(),
605 vec![r"\\FILE01\Profiles\rh.profilepath".to_string()],
606 );
607
608 let user = parse_user_with_attrs(attrs);
609
610 assert_eq!(
611 user.properties.profilepath,
612 r"\\FILE01\Profiles\rh.profilepath"
613 );
614 assert_eq!(
615 user.to_json()["Properties"]["profilepath"],
616 r"\\FILE01\Profiles\rh.profilepath"
617 );
618 }
619
620 #[test]
621 fn parse_defaults_profilepath_to_empty_string_when_absent() {
622 let mut attrs = HashMap::new();
623 attrs.insert(
624 "sAMAccountName".to_string(),
625 vec!["rh.profilepath.control".to_string()],
626 );
627
628 let user = parse_user_with_attrs(attrs);
629
630 assert_eq!(user.properties.profilepath, "");
631 assert_eq!(user.to_json()["Properties"]["profilepath"], "");
632 }
633
634 #[test]
635 fn parse_populates_has_sid_history() {
636 let mut user = User::new();
637 let result = SearchEntry {
638 dn: "CN=Test User,OU=Users,DC=example,DC=local".to_string(),
639 attrs: HashMap::new(),
640 bin_attrs: HashMap::from([(
641 "sIDHistory".to_string(),
642 vec![vec![1, 2, 0, 0, 0, 0, 0, 5, 21, 0, 0, 0, 0x15, 0xCD, 0x5B, 0x07]],
643 )]),
644 };
645 let mut dn_sid = HashMap::new();
646 let mut sid_type = HashMap::new();
647 let schema_guid_map = HashMap::new();
648
649 user.parse(
650 result,
651 "example.local",
652 &mut dn_sid,
653 &mut sid_type,
654 "S-1-5-21-1-2-3",
655 &schema_guid_map,
656 )
657 .unwrap();
658 }
659}