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