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