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
65 pub fn properties_mut(&mut self) -> &mut UserProperties {
67 &mut self.properties
68 }
69 pub fn aces_mut(&mut self) -> &mut Vec<AceTemplate> {
70 &mut self.aces
71 }
72 pub fn object_identifier_mut(&mut self) -> &mut String {
73 &mut self.object_identifier
74 }
75
76 pub fn parse(
79 &mut self,
80 result: SearchEntry,
81 domain: &str,
82 dn_sid: &mut HashMap<String, String>,
83 sid_type: &mut HashMap<String, String>,
84 domain_sid: &str,
85 schema_guid_map: &HashMap<String, String>,
86 ) -> Result<(), Box<dyn Error>> {
87 let result_dn: String = result.dn.to_uppercase();
88 let result_attrs: HashMap<String, Vec<String>> = result.attrs;
89 let result_bin: HashMap<String, Vec<Vec<u8>>> = result.bin_attrs;
90
91 debug!("Parse user: {result_dn}");
93
94 for (key, value) in &result_attrs {
96 trace!(" {key:?}:{value:?}");
97 }
98 for (key, value) in &result_bin {
100 trace!(" {key:?}:{value:?}");
101 }
102
103 self.properties.domain = domain.to_uppercase();
105 self.properties.distinguishedname = result_dn;
106 self.properties.enabled = true;
107 self.domain_sid = domain_sid.to_string();
108
109 let mut group_id: String ="".to_owned();
111 for (key, value) in &result_attrs {
112 match key.as_str() {
113 "sAMAccountName" => {
114 let name = &value[0];
115 let email = format!("{}@{}",name.to_owned(),domain);
116 self.properties.name = email.to_uppercase();
117 self.properties.samaccountname = name.to_string();
118 }
119 "description" => {
120 self.properties.description = Some(value[0].to_owned());
121 }
122 "mail" => {
123 self.properties.email = value[0].to_owned();
124 }
125 "title" => {
126 self.properties.title = value[0].to_owned();
127 }
128 "userPassword" => {
129 self.properties.userpassword = value[0].to_owned();
130 }
131 "unixUserPassword" => {
132 self.properties.unixpassword = value[0].to_owned();
133 }
134 "unicodepwd" => {
135 self.properties.unicodepassword = value[0].to_owned();
136 }
137 "sfupassword" => {
138 }
140 "displayName" => {
141 self.properties.displayname = value[0].to_owned();
142 }
143 "adminCount" => {
144 let isadmin = &value[0];
145 let mut admincount = false;
146 if isadmin =="1" {
147 admincount = true;
148 }
149 self.properties.admincount = admincount;
150 }
151 "homeDirectory" => {
152 self.properties.homedirectory = value[0].to_owned();
153 }
154 "scriptpath" => {
155 self.properties.logonscript = value[0].to_owned();
156 }
157 "profilePath" | "profilepath" => {
158 if let Some(profile_path) = value.first() {
159 self.properties.profilepath = profile_path.to_owned();
160 }
161 }
162 "userAccountControl" => {
163 let uac = &value[0].parse::<u32>().unwrap_or(0);
164 self.properties.useraccountcontrol = *uac;
165 let uac_flags = get_flag(*uac);
166 for flag in uac_flags {
168 if flag.contains("AccountDisable") {
169 self.properties.enabled = false;
170 };
171 if flag.contains("PasswordNotRequired") {
173 self.properties.passwordnotreqd = true;
174 };
175 if flag.contains("DontExpirePassword") {
176 self.properties.pwdneverexpires = true;
177 };
178 if flag.contains("DontReqPreauth") {
179 self.properties.dontreqpreauth = true;
180 };
181 if flag.contains("TrustedForDelegation") {
183 self.properties.unconstraineddelegation = true;
184 self.unconstrained_delegation = true;
185 };
186 if flag.contains("NotDelegated") {
187 self.properties.sensitive = true;
188 };
189 if flag.contains("TrustedToAuthForDelegation") {
191 self.properties.trustedtoauth = true;
192 };
193 }
194 }
195 "msDS-AllowedToDelegateTo" => {
196 let mut vec_members2: Vec<Member> = Vec::new();
197 let mut seen = HashSet::<String>::new();
198
199 for spn_raw in value {
200 let spn = spn_raw.trim().replace('\\', "/");
202 let host_part = spn
204 .split_once('/') .map(|(_, rest)| rest)
206 .unwrap_or(spn.as_str());
207
208 let host = host_part.split(':').next().unwrap_or(host_part);
210
211 let fqdn_upper = host.trim().to_ascii_uppercase();
213 if fqdn_upper.is_empty() {
214 error!("Skipping empty host in SPN: {:?}", spn_raw);
215 continue;
216 }
217
218 if seen.insert(fqdn_upper.clone()) {
220 let mut m = Member::new();
221 *m.object_identifier_mut() = fqdn_upper; *m.object_type_mut() = "Computer".to_string();
223 vec_members2.push(m);
224 }
225 }
226
227 self.allowed_to_delegate = vec_members2;
228 }
229 "lastLogon" => {
230 let lastlogon = &value[0].parse::<i64>().unwrap_or(0);
231 if lastlogon.is_positive() {
232 let epoch = convert_timestamp(*lastlogon);
233 self.properties.lastlogon = epoch;
234 }
235 }
236 "lastLogonTimestamp" => {
237 let lastlogontimestamp = &value[0].parse::<i64>().unwrap_or(0);
238 if lastlogontimestamp.is_positive() {
239 let epoch = convert_timestamp(*lastlogontimestamp);
240 self.properties.lastlogontimestamp = epoch;
241 }
242 }
243 "pwdLastSet" => {
244 let pwdlastset = &value[0].parse::<i64>().unwrap_or(0);
245 if pwdlastset.is_positive() {
246 let epoch = convert_timestamp(*pwdlastset);
247 self.properties.pwdlastset = epoch;
248 }
249 }
250 "whenCreated" => {
251 let epoch = string_to_epoch(&value[0])?;
252 if epoch.is_positive() {
253 self.properties.whencreated = epoch;
254 }
255 }
256 "servicePrincipalName" => {
257 let mut targets: Vec<SPNTarget> = Vec::new();
259 let mut result: Vec<String> = Vec::new();
260 let mut added: bool = false;
261 for v in value {
262 result.push(v.to_owned());
263 let _target = match check_spn(v).to_owned() {
265 Some(_target) => {
266 if !added {
267 targets.push(_target.to_owned());
268 added = true;
269 }
270 },
271 None => {}
272 };
273 }
274 self.properties.serviceprincipalnames = result;
275 self.properties.hasspn = true;
276 self.spn_targets = targets;
277 }
278 "primaryGroupID" => {
279 group_id = value[0].to_owned();
280 }
281 "isDeleted" => {
282 self.is_deleted = true;
283 }
284 "msDS-SupportedEncryptionTypes" => {
285 self.properties.supportedencryptiontypes = convert_encryption_types(value[0].parse::<i32>().unwrap_or(0));
286 }
287 _ => {}
288 }
289 }
290
291 let mut sid: String = "".to_owned();
293 for (key, value) in &result_bin {
294 match key.as_str() {
295 "objectSid" => {
296 sid = sid_maker(LdapSid::parse(&value[0]).unwrap().1, domain);
297 self.object_identifier = sid.to_owned();
298
299 for domain_sid in OBJECT_SID_RE1.captures_iter(&sid) {
300 self.properties.domainsid = domain_sid[0].to_owned().to_string();
301 }
302 }
303 "nTSecurityDescriptor" => {
304 let relations_ace = parse_ntsecuritydescriptor(
306 self,
307 &value[0],
308 "User",
309 &result_attrs,
310 &result_bin,
311 domain,
312 schema_guid_map,
313 );
314 self.aces_mut().extend(relations_ace);
315 }
316 "sIDHistory" => {
317 let mut list_sid_history: Vec<String> = Vec::new();
320 for bsid in value {
321 debug!("sIDHistory: {:?}", &bsid);
322 list_sid_history.push(sid_maker(LdapSid::parse(bsid).unwrap().1, domain));
323 }
325 self.properties.sidhistory = list_sid_history;
326 }
327 "msDS-GroupMSAMembership" => {
328 let mut relations_ace = parse_embedded_security_descriptor(
330 self,
331 &value[0],
332 "User",
333 &result_attrs,
334 &result_bin,
335 domain,
336 schema_guid_map,
337 );
338 parse_gmsa(&mut relations_ace, self);
341 }
343 "userCertificate" => {
344 let res = X509Certificate::from_der(&value[0]);
347 match res {
348 Ok((_rem, _cert)) => {},
349 _ => error!("CA x509 certificate parsing failed: {:?}", res),
350 }
351 }
352 _ => {}
353 }
354 }
355
356 #[allow(irrefutable_let_patterns)]
358 if let id = group_id {
359 if let Some(part1) = SID_PART1_RE1.find(&sid) {
360 self.primary_group_sid = format!("{}{}", part1.as_str(), id);
361 } else {
362 eprintln!("[!] Regex did not match any part of the SID");
363 }
364 }
365
366 dn_sid.insert(
368 self.properties.distinguishedname.to_owned(),
369 self.object_identifier.to_owned(),
370 );
371 sid_type.insert(
373 self.object_identifier.to_owned(),
374 "User".to_string(),
375 );
376
377 Ok(())
380 }
381}
382
383impl LdapObject for User {
385 fn to_json(&self) -> Value {
387 serde_json::to_value(self).unwrap()
388 }
389
390 fn get_object_identifier(&self) -> &String {
392 &self.object_identifier
393 }
394 fn get_is_acl_protected(&self) -> &bool {
395 &self.is_acl_protected
396 }
397 fn get_aces(&self) -> &Vec<AceTemplate> {
398 &self.aces
399 }
400 fn get_spntargets(&self) -> &Vec<SPNTarget> {
401 &self.spn_targets
402 }
403 fn get_allowed_to_delegate(&self) -> &Vec<Member> {
404 &self.allowed_to_delegate
405 }
406 fn get_links(&self) -> &Vec<Link> {
407 panic!("Not used by current object.");
408 }
409 fn get_contained_by(&self) -> &Option<Member> {
410 &self.contained_by
411 }
412 fn get_child_objects(&self) -> &Vec<Member> {
413 panic!("Not used by current object.");
414 }
415 fn get_haslaps(&self) -> &bool {
416 &false
417 }
418
419 fn get_aces_mut(&mut self) -> &mut Vec<AceTemplate> {
421 &mut self.aces
422 }
423 fn get_spntargets_mut(&mut self) -> &mut Vec<SPNTarget> {
424 &mut self.spn_targets
425 }
426 fn get_allowed_to_delegate_mut(&mut self) -> &mut Vec<Member> {
427 &mut self.allowed_to_delegate
428 }
429
430 fn set_is_acl_protected(&mut self, is_acl_protected: bool) {
432 self.is_acl_protected = is_acl_protected;
433 self.properties.isaclprotected = is_acl_protected;
434 }
435 fn set_aces(&mut self, aces: Vec<AceTemplate>) {
436 self.aces = aces;
437 }
438 fn set_spntargets(&mut self, spn_targets: Vec<SPNTarget>) {
439 self.spn_targets = spn_targets;
440 }
441 fn set_allowed_to_delegate(&mut self, allowed_to_delegate: Vec<Member>) {
442 self.allowed_to_delegate = allowed_to_delegate;
443 }
444 fn set_links(&mut self, _links: Vec<Link>) {
445 }
447 fn set_contained_by(&mut self, contained_by: Option<Member>) {
448 self.contained_by = contained_by;
449 }
450 fn set_child_objects(&mut self, _child_objects: Vec<Member>) {
451 }
453}
454
455#[derive(Debug, Clone, Deserialize, Serialize, Default)]
457pub struct UserProperties {
458 domain: String,
459 name: String,
460 domainsid: String,
461 isaclprotected: bool,
462 distinguishedname: String,
463 highvalue: bool,
464 description: Option<String>,
465 whencreated: i64,
466 sensitive: bool,
467 dontreqpreauth: bool,
468 passwordnotreqd: bool,
469 unconstraineddelegation: bool,
470 pwdneverexpires: bool,
471 enabled: bool,
472 trustedtoauth: bool,
473 lastlogon: i64,
474 lastlogontimestamp: i64,
475 pwdlastset: i64,
476 serviceprincipalnames: Vec<String>,
477 hasspn: bool,
478 displayname: String,
479 email: String,
480 title: String,
481 homedirectory: String,
482 logonscript: String,
483 useraccountcontrol: u32,
484 samaccountname: String,
485 userpassword: String,
486 unixpassword: String,
487 unicodepassword: String,
488 sfupassword: String,
489 profilepath: String,
490 admincount: bool,
491 supportedencryptiontypes: Vec<String>,
492 sidhistory: Vec<String>,
493 allowedtodelegate: Vec<String>
494}
495
496impl UserProperties {
497 pub fn name(&self) -> &String {
499 &self.name
500 }
501 pub fn domainsid(&self) -> &String {
502 &self.domainsid
503 }
504 pub fn isaclprotected(&self) -> &bool {
505 &self.isaclprotected
506 }
507
508 pub fn name_mut(&mut self) -> &mut String {
510 &mut self.name
511 }
512 pub fn domainsid_mut(&mut self) -> &mut String {
513 &mut self.domainsid
514 }
515 pub fn isaclprotected_mut(&mut self) -> &mut bool {
516 &mut self.isaclprotected
517 }
518}
519
520#[cfg(test)]
521mod tests {
522 use super::*;
523
524 fn parse_user_with_attrs(attrs: HashMap<String, Vec<String>>) -> User {
525 let mut user = User::new();
526 let result = SearchEntry {
527 dn: "CN=Test User,OU=Users,DC=example,DC=local".to_string(),
528 attrs,
529 bin_attrs: HashMap::new(),
530 };
531 let mut dn_sid = HashMap::new();
532 let mut sid_type = HashMap::new();
533 let schema_guid_map = HashMap::new();
534
535 user.parse(
536 result,
537 "example.local",
538 &mut dn_sid,
539 &mut sid_type,
540 "S-1-5-21-1-2-3",
541 &schema_guid_map,
542 )
543 .unwrap();
544
545 user
546 }
547
548 #[test]
549 fn parse_sets_profilepath_from_ldap_profile_path() {
550 let mut attrs = HashMap::new();
551 attrs.insert(
552 "sAMAccountName".to_string(),
553 vec!["rh.profilepath".to_string()],
554 );
555 attrs.insert(
556 "profilePath".to_string(),
557 vec![r"\\FILE01\Profiles\rh.profilepath".to_string()],
558 );
559
560 let user = parse_user_with_attrs(attrs);
561
562 assert_eq!(
563 user.properties.profilepath,
564 r"\\FILE01\Profiles\rh.profilepath"
565 );
566 assert_eq!(
567 user.to_json()["Properties"]["profilepath"],
568 r"\\FILE01\Profiles\rh.profilepath"
569 );
570 }
571
572 #[test]
573 fn parse_defaults_profilepath_to_empty_string_when_absent() {
574 let mut attrs = HashMap::new();
575 attrs.insert(
576 "sAMAccountName".to_string(),
577 vec!["rh.profilepath.control".to_string()],
578 );
579
580 let user = parse_user_with_attrs(attrs);
581
582 assert_eq!(user.properties.profilepath, "");
583 assert_eq!(user.to_json()["Properties"]["profilepath"], "");
584 }
585}