public static Dictionary <string, object> ReadDomainProperties(SearchResultEntry entry)
        {
            var props = new Dictionary <string, object> {
                { "description", entry.GetProperty("description") }
            };

            if (!int.TryParse(entry.GetProperty("msds-behavior-version"), out var level))
            {
                level = -1;
            }

            var func = level switch
            {
                0 => "2000 Mixed/Native",
                1 => "2003 Interim",
                2 => "2003",
                3 => "2008",
                4 => "2008 R2",
                5 => "2012",
                6 => "2012 R2",
                7 => "2016",
                _ => "Unknown"
            };

            props.Add("functionallevel", func);

            return(props);
        }
        public static Dictionary <string, object> ReadGPOProperties(SearchResultEntry entry)
        {
            var props = new Dictionary <string, object>
            {
                { "description", entry.GetProperty("description") },
                { "gpcpath", entry.GetProperty("gpcfilesyspath") }
            };

            return(props);
        }
        /// <summary>
        /// Attempts to parse all LDAP attributes outside of the ones already collected and converts them to a human readable format using a best guess
        /// </summary>
        /// <param name="entry"></param>
        private static Dictionary <string, object> ParseAllProperties(SearchResultEntry entry)
        {
            var flag  = IsTextUnicodeFlags.IS_TEXT_UNICODE_STATISTICS;
            var props = new Dictionary <string, object>();

            foreach (var property in entry.Attributes.AttributeNames)
            {
                var propName = property.ToString().ToLower();
                if (ReservedAttributes.Contains(propName))
                {
                    continue;
                }

                var collection = entry.Attributes[propName];
                if (collection.Count == 0)
                {
                    continue;
                }

                if (collection.Count == 1)
                {
                    var testBytes = entry.GetPropertyAsBytes(propName);

                    if (testBytes == null || testBytes.Length == 0 || !IsTextUnicode(testBytes, testBytes.Length, ref flag))
                    {
                        continue;
                    }

                    var testString = entry.GetProperty(propName);

                    if (!string.IsNullOrEmpty(testString))
                    {
                        if (propName == "badpasswordtime")
                        {
                            props.Add(propName, Helpers.ConvertFileTimeToUnixEpoch(testString));
                        }
                        else
                        {
                            props.Add(propName, BestGuessConvert(testString));
                        }
                    }
                }
                else
                {
                    var arrBytes = entry.GetPropertyAsArrayOfBytes(propName);
                    if (arrBytes.Length == 0 || !IsTextUnicode(arrBytes[0], arrBytes[0].Length, ref flag))
                    {
                        continue;
                    }

                    var arr = entry.GetPropertyAsArray(propName);
                    if (arr.Length > 0)
                    {
                        props.Add(propName, arr.Select(BestGuessConvert).ToArray());
                    }
                }
            }

            return(props);
        }
Example #4
0
 /// <summary>
 /// Helper function to print attributes of a SearchResultEntry. Not used currently
 /// </summary>
 /// <param name="searchResultEntry"></param>
 public static void PrintEntry(this SearchResultEntry searchResultEntry)
 {
     foreach (var propertyName in searchResultEntry.Attributes.AttributeNames)
     {
         var property = propertyName.ToString();
         Console.WriteLine(property);
         Console.WriteLine(searchResultEntry.GetProperty(property));
     }
 }
        public static Dictionary <string, object> ReadOUProperties(SearchResultEntry entry)
        {
            var props = new Dictionary <string, object>
            {
                { "description", entry.GetProperty("description") }
            };

            return(props);
        }
Example #6
0
        /// <summary>
        /// Extension method to determine the type of a SearchResultEntry.
        /// Requires objectsid, samaccounttype, objectclass
        /// </summary>
        /// <param name="searchResultEntry"></param>
        /// <returns></returns>
        public static LdapTypeEnum GetLdapType(this SearchResultEntry searchResultEntry)
        {
            var objectId = searchResultEntry.GetObjectIdentifier();

            if (objectId == null)
            {
                return(LdapTypeEnum.Unknown);
            }

            if (searchResultEntry.GetPropertyAsBytes("msds-groupmsamembership") != null)
            {
                return(LdapTypeEnum.User);
            }

            var objectType     = LdapTypeEnum.Unknown;
            var samAccountType = searchResultEntry.GetProperty("samaccounttype");

            //Its not a common principal. Lets use properties to figure out what it actually is
            if (samAccountType != null)
            {
                if (samAccountType == "805306370")
                {
                    return(LdapTypeEnum.Unknown);
                }

                objectType = Helpers.SamAccountTypeToType(samAccountType);
            }
            else
            {
                var objectClasses = searchResultEntry.GetPropertyAsArray("objectClass");
                if (objectClasses == null)
                {
                    objectType = LdapTypeEnum.Unknown;
                }
                else if (objectClasses.Contains("groupPolicyContainer"))
                {
                    objectType = LdapTypeEnum.GPO;
                }
                else if (objectClasses.Contains("organizationalUnit"))
                {
                    objectType = LdapTypeEnum.OU;
                }
                else if (objectClasses.Contains("domain"))
                {
                    objectType = LdapTypeEnum.Domain;
                }
            }

            //Override GMSA object type
            if (searchResultEntry.GetPropertyAsBytes("msds-groupmsamembership") != null)
            {
                objectType = LdapTypeEnum.User;
            }

            return(objectType);
        }
Example #7
0
        /// <summary>
        /// Helper function to print attributes of a SearchResultEntry
        /// </summary>
        /// <param name="searchResultEntry"></param>
        public static void PrintEntry(this SearchResultEntry searchResultEntry)
        {
            var sb = new StringBuilder();

            foreach (var propertyName in searchResultEntry.Attributes.AttributeNames)
            {
                var property = propertyName.ToString();
                sb.Append(property).Append("\t").Append(searchResultEntry.GetProperty(property)).Append("\n");
            }
            Logging.Log(sb.ToString());
        }
        public static Dictionary <string, object> ReadGroupProperties(SearchResultEntry entry)
        {
            var props = new Dictionary <string, object>
            {
                { "description", entry.GetProperty("description") }
            };

            var ac = entry.GetProperty("admincount");

            if (ac != null)
            {
                var a = int.Parse(ac);
                props.Add("admincount", a != 0);
            }
            else
            {
                props.Add("admincount", false);
            }
            return(props);
        }
Example #9
0
        /// <summary>
        /// Extension method to determine the BloodHound type of a SearchResultEntry using LDAP properties
        /// Requires ldap properties objectsid, samaccounttype, objectclass
        /// </summary>
        /// <param name="entry"></param>
        /// <returns></returns>
        public static Label GetLabel(this SearchResultEntry entry)
        {
            //Test if we have the msds-groupmsamembership property first. We want to override this as a user object
            if (entry.GetPropertyAsBytes("msds-groupmsamembership") != null)
            {
                return(Label.User);
            }

            var objectId = entry.GetObjectIdentifier();

            if (objectId.StartsWith("S-1") && WellKnownPrincipal.GetWellKnownPrincipal(objectId, out var commonPrincipal))
            {
                return(commonPrincipal.ObjectType);
            }

            var objectType     = Label.Unknown;
            var samAccountType = entry.GetProperty("samaccounttype");

            //Its not a common principal. Lets use properties to figure out what it actually is
            if (samAccountType != null)
            {
                objectType = Helpers.SamAccountTypeToType(samAccountType);
            }
            else
            {
                var objectClasses = entry.GetPropertyAsArray("objectClass");
                if (objectClasses == null)
                {
                    objectType = Label.Unknown;
                }
                else if (objectClasses.Contains("groupPolicyContainer"))
                {
                    objectType = Label.GPO;
                }
                else if (objectClasses.Contains("organizationalUnit"))
                {
                    objectType = Label.OU;
                }
                else if (objectClasses.Contains("domain"))
                {
                    objectType = Label.Domain;
                }
                else if (objectClasses.Contains("container"))
                {
                    objectType = Label.Container;
                }
            }

            return(objectType);
        }
Example #10
0
        /// <summary>
        ///     Helper function to print attributes of a SearchResultEntry
        /// </summary>
        /// <param name="searchResultEntry"></param>
        public static string PrintEntry(this SearchResultEntry searchResultEntry)
        {
            var sb = new StringBuilder();

            if (searchResultEntry.Attributes.AttributeNames == null)
            {
                return(sb.ToString());
            }
            foreach (var propertyName in searchResultEntry.Attributes.AttributeNames)
            {
                var property = propertyName.ToString();
                sb.Append(property).Append("\t").Append(searchResultEntry.GetProperty(property)).Append("\n");
            }

            return(sb.ToString());
        }
Example #11
0
        /// <summary>
        /// Extension method to determine the type of a SearchResultEntry.
        /// Requires objectsid, samaccounttype, objectclass
        /// </summary>
        /// <param name="searchResultEntry"></param>
        /// <returns></returns>
        public static LdapTypeEnum GetLdapType(this SearchResultEntry searchResultEntry)
        {
            var objectSid = searchResultEntry.GetSid();

            if (CommonPrincipal.GetCommonSid(objectSid, out var commonPrincipal))
            {
                return(commonPrincipal.Type);
            }

            var objectType     = LdapTypeEnum.Unknown;
            var samAccountType = searchResultEntry.GetProperty("samaccounttype");

            //Its not a common principal. Lets use properties to figure out what it actually is
            if (samAccountType != null)
            {
                if (samAccountType == "805306370")
                {
                    return(LdapTypeEnum.Unknown);
                }

                objectType = Helpers.SamAccountTypeToType(samAccountType);
            }
            else
            {
                var objectClasses = searchResultEntry.GetPropertyAsArray("objectClass");
                if (objectClasses == null)
                {
                    objectType = LdapTypeEnum.Unknown;
                }
                else if (objectClasses.Contains("groupPolicyContainer"))
                {
                    objectType = LdapTypeEnum.GPO;
                }
                else if (objectClasses.Contains("organizationalUnit"))
                {
                    objectType = LdapTypeEnum.OU;
                }
                else if (objectClasses.Contains("domain"))
                {
                    objectType = LdapTypeEnum.Domain;
                }
            }
            return(objectType);
        }
Example #12
0
        /// <summary>
        /// Reads the primary group info from a user or computer object and massages it into the proper format.
        /// </summary>
        /// <param name="entry"></param>
        /// <returns></returns>
        public static string GetPrimaryGroupInfo(SearchResultEntry entry)
        {
            var primaryGroupId = entry.GetProperty("primarygroupid");

            if (primaryGroupId == null)
            {
                return(null);
            }

            try
            {
                var domainSid       = new SecurityIdentifier(entry.GetSid()).AccountDomainSid.Value;
                var primaryGroupSid = $"{domainSid}-{primaryGroupId}";
                return(primaryGroupSid);
            }
            catch
            {
                return(null);
            }
        }
Example #13
0
        public static async IAsyncEnumerable <GPLink> ReadContainerGPLinks(SearchResultEntry entry)
        {
            var gpLinkProp = entry.GetProperty("gplink");

            if (gpLinkProp == null)
            {
                yield break;
            }

            foreach (var link in gpLinkProp.Split(']', '[').Where(x => x.StartsWith("LDAP")))
            {
                var s      = link.Split(';');
                var dn     = s[0].Substring(s[0].IndexOf("CN=", StringComparison.OrdinalIgnoreCase));
                var status = s[1];

                // 1 and 3 represent Disabled, Not Enforced and Disabled, Enforced respectively.
                if (status is "3" or "1")
                {
                    continue;
                }

                var enforced = status.Equals("2");

                var res = await LDAPUtils.ResolveDistinguishedName(dn);

                if (res == null)
                {
                    continue;
                }

                yield return(new GPLink
                {
                    GUID = res.ObjectIdentifier,
                    IsEnforced = enforced
                });
            }
        }
Example #14
0
        public static bool ReadBlocksInheritance(SearchResultEntry entry)
        {
            var opts = entry.GetProperty("gpoptions");

            return(opts is "1");
        }
        public static async Task <ComputerProperties> ReadComputerProperties(SearchResultEntry entry)
        {
            var compProps = new ComputerProperties();
            var props     = new Dictionary <string, object>();

            var  uac = entry.GetProperty("useraccountcontrol");
            bool enabled, unconstrained, trustedToAuth;

            if (int.TryParse(uac, out var flag))
            {
                var flags = (UAC.UacFlags)flag;
                enabled       = (flags & UAC.UacFlags.AccountDisable) == 0;
                unconstrained = (flags & UAC.UacFlags.TrustedForDelegation) == UAC.UacFlags.TrustedForDelegation;
                trustedToAuth = (flags & UAC.UacFlags.TrustedToAuthForDelegation) != 0;
            }
            else
            {
                unconstrained = false;
                enabled       = true;
                trustedToAuth = false;
            }

            var domain = Helpers.DistinguishedNameToDomain(entry.DistinguishedName);

            var comps = new List <TypedPrincipal>();

            if (trustedToAuth)
            {
                var delegates = entry.GetPropertyAsArray("msds-allowedToDelegateTo");
                props.Add("allowedtodelegate", delegates);

                foreach (var d in delegates)
                {
                    var hname = d.Contains("/") ? d.Split('/')[1] : d;
                    hname = hname.Split(':')[0];
                    var resolvedHost = await LDAPUtils.ResolveHostToSid(hname, domain);

                    if (resolvedHost.Contains(".") || resolvedHost.Contains("S-1"))
                    {
                        comps.Add(new TypedPrincipal
                        {
                            ObjectIdentifier = resolvedHost,
                            ObjectType       = Label.Computer
                        });
                    }
                }
            }

            compProps.AllowedToDelegate = comps.ToArray();

            var allowedToActPrincipals = new List <TypedPrincipal>();
            var rawAllowedToAct        = entry.GetPropertyAsBytes("msDS-AllowedToActOnBehalfOfOtherIdentity");

            if (rawAllowedToAct != null)
            {
                var sd = new ActiveDirectorySecurity();
                sd.SetSecurityDescriptorBinaryForm(rawAllowedToAct, AccessControlSections.Access);
                foreach (ActiveDirectoryAccessRule rule in sd.GetAccessRules(true, true, typeof(SecurityIdentifier)))
                {
                    var res = LDAPUtils.ResolveIDAndType(rule.IdentityReference.Value, domain);
                    allowedToActPrincipals.Add(res);
                }
            }

            compProps.AllowedToAct = allowedToActPrincipals.ToArray();

            props.Add("enabled", enabled);
            props.Add("unconstraineddelegation", unconstrained);
            props.Add("lastlogon", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty("lastlogon")));
            props.Add("lastlogontimestamp", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty("lastlogontimestamp")));
            props.Add("pwdlastset", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty("pwdlastset")));
            props.Add("serviceprincipalnames", entry.GetPropertyAsArray("serviceprincipalname"));
            var os = entry.GetProperty("operatingsystem");
            var sp = entry.GetProperty("operatingsystemservicepack");

            if (sp != null)
            {
                os = $"{os} {sp}";
            }

            props.Add("operatingsystem", os);
            props.Add("description", entry.GetProperty("description"));

            var sh                   = entry.GetPropertyAsArrayOfBytes("sidhistory");
            var sidHistoryList       = new List <string>();
            var sidHistoryPrincipals = new List <TypedPrincipal>();

            foreach (var sid in sh)
            {
                string sSid;
                try
                {
                    sSid = new SecurityIdentifier(sid, 0).Value;
                }
                catch
                {
                    continue;
                }

                var res = LDAPUtils.ResolveIDAndType(sSid, domain);

                sidHistoryPrincipals.Add(res);
            }

            compProps.SidHistory = sidHistoryPrincipals.ToArray();

            props.Add("sidhistory", sidHistoryList.ToArray());

            compProps.Props = props;

            return(compProps);
        }
        /// <summary>
        /// Converts a SaerchResultEntry into an LdapWrapper
        /// </summary>
        /// <param name="searchResultEntry"></param>
        /// <returns></returns>
        internal static LdapWrapper CreateLdapWrapper(SearchResultEntry searchResultEntry)
        {
            //Look for a null DN first. Not sure why this would happen.
            var distinguishedName = searchResultEntry.DistinguishedName;

            if (distinguishedName == null)
            {
                return(null);
            }

            var accountName    = searchResultEntry.GetProperty("samaccountname");
            var samAccountType = searchResultEntry.GetProperty("samaccounttype");
            var accountDomain  = Helpers.DistinguishedNameToDomain(distinguishedName);
            var objectSid      = searchResultEntry.GetSid();
            var objectId       = searchResultEntry.GetObjectIdentifier();

            //If objectsid/id is null, return
            if (objectSid == null && objectId == null)
            {
                return(null);
            }

            var    objectType = LdapTypeEnum.Unknown;
            string objectIdentifier;

            LdapWrapper wrapper;

            //Lets see if its a "common" principal
            if (objectSid != null && CommonPrincipal.GetCommonSid(objectSid, out var commonPrincipal))
            {
                accountName      = commonPrincipal.Name;
                objectType       = commonPrincipal.Type;
                objectIdentifier = Helpers.ConvertCommonSid(objectSid, accountDomain);
            }
            else
            {
                //Its not a common principal. Lets use properties to figure out what it actually is
                if (samAccountType != null)
                {
                    if (samAccountType == "805306370")
                    {
                        return(null);
                    }

                    objectType = Helpers.SamAccountTypeToType(samAccountType);
                }
                else
                {
                    var objectClasses = searchResultEntry.GetPropertyAsArray("objectClass");
                    if (objectClasses == null)
                    {
                        objectType = LdapTypeEnum.Unknown;
                    }
                    else if (objectClasses.Contains("groupPolicyContainer"))
                    {
                        objectType = LdapTypeEnum.GPO;
                    }
                    else if (objectClasses.Contains("organizationalUnit"))
                    {
                        objectType = LdapTypeEnum.OU;
                    }
                    else if (objectClasses.Contains("domain"))
                    {
                        objectType = LdapTypeEnum.Domain;
                    }
                }

                objectIdentifier = objectId;
            }

            //Override GMSA object type
            if (searchResultEntry.GetPropertyAsBytes("msds-groupmsamembership") != null)
            {
                objectType  = LdapTypeEnum.User;
                accountName = accountName?.TrimEnd('$');
            }

            //Depending on the object type, create the appropriate wrapper object
            switch (objectType)
            {
            case LdapTypeEnum.Computer:
                accountName = accountName?.TrimEnd('$');
                wrapper     = new Computer(searchResultEntry)
                {
                    DisplayName    = $"{accountName}.{accountDomain}".ToUpper(),
                    SamAccountName = accountName
                };

                var hasLaps = searchResultEntry.GetProperty("ms-mcs-admpwdexpirationtime") != null;
                wrapper.Properties.Add("haslaps", hasLaps);
                wrapper.Properties.Add("highvalue", false);
                break;

            case LdapTypeEnum.User:
                wrapper = new User(searchResultEntry)
                {
                    DisplayName = $"{accountName}@{accountDomain}".ToUpper()
                };
                wrapper.Properties.Add("highvalue", false);
                break;

            case LdapTypeEnum.Group:
                wrapper = new Group(searchResultEntry)
                {
                    DisplayName = $"{accountName}@{accountDomain}".ToUpper()
                };

                if (objectIdentifier.EndsWith("-512") || objectIdentifier.EndsWith("-516") || objectIdentifier.EndsWith("-519") ||
                    objectIdentifier.EndsWith("-520") || objectIdentifier.EndsWith("S-1-5-32-544") || objectIdentifier.EndsWith("S-1-5-32-550") ||
                    objectIdentifier.EndsWith("S-1-5-32-549") || objectIdentifier.EndsWith("S-1-5-32-551") || objectIdentifier.EndsWith("S-1-5-32-548"))
                {
                    wrapper.Properties.Add("highvalue", true);
                }
                else
                {
                    wrapper.Properties.Add("highvalue", false);
                }
                break;

            case LdapTypeEnum.GPO:
                accountName = searchResultEntry.GetProperty("displayname");
                wrapper     = new GPO(searchResultEntry)
                {
                    DisplayName = $"{accountName}@{accountDomain}".ToUpper()
                };
                wrapper.Properties.Add("highvalue", false);
                break;

            case LdapTypeEnum.OU:
                accountName = searchResultEntry.GetProperty("name");
                wrapper     = new OU(searchResultEntry)
                {
                    DisplayName = $"{accountName}@{accountDomain}".ToUpper()
                };
                wrapper.Properties.Add("highvalue", false);
                break;

            case LdapTypeEnum.Domain:
                wrapper = new Domain(searchResultEntry)
                {
                    DisplayName = accountDomain.ToUpper()
                };
                wrapper.Properties.Add("highvalue", true);
                break;

            case LdapTypeEnum.Unknown:
                wrapper = null;
                break;

            default:
                throw new ArgumentOutOfRangeException();
            }

            //Null wrappers happen when we cant resolve the object type. Shouldn't ever happen, but just in case, return null here
            if (wrapper == null)
            {
                Console.WriteLine($"Null Wrapper: {distinguishedName}");
                return(null);
            }

            //Set the DN/SID for the wrapper going forward and a couple other properties
            wrapper.DistinguishedName = distinguishedName;
            wrapper.Properties.Add("name", wrapper.DisplayName);
            wrapper.Properties.Add("domain", wrapper.Domain);
            wrapper.Properties.Add("objectid", objectIdentifier.ToUpper());
            wrapper.Properties.Add("distinguishedname", distinguishedName);
            wrapper.ObjectIdentifier = objectIdentifier;

            //Some post processing
            PostProcessWrapper(wrapper);

            //Cache the distinguished name from this object
            Cache.Instance.Add(wrapper.DistinguishedName, new ResolvedPrincipal
            {
                ObjectIdentifier = wrapper.ObjectIdentifier,
                ObjectType       = objectType
            });

            //If the objectidentifier is a SID, cache this mapping too
            if (objectIdentifier.StartsWith("S-1-5"))
            {
                Cache.Instance.Add(wrapper.ObjectIdentifier, objectType);
            }

            //Return our wrapper for the next step in the pipeline
            return(wrapper);
        }
        public ResolvedSearchResult ResolveBloodHoundInfo()
        {
            var res = new ResolvedSearchResult();

            var objectId = GetObjectIdentifier();

            if (objectId == null)
            {
                _log.LogWarning("ObjectIdentifier is null for {DN}", DistinguishedName);
                return(null);
            }

            var uac = _entry.GetProperty(LDAPProperties.UserAccountControl);

            if (int.TryParse(uac, out var flag))
            {
                var flags = (UacFlags)flag;
                if ((flags & UacFlags.ServerTrustAccount) != 0)
                {
                    _log.LogTrace("Marked {SID} as a domain controller", objectId);
                    res.IsDomainController = true;
                    _utils.AddDomainController(objectId);
                }
            }

            res.ObjectId = objectId;
            if (IsDeleted())
            {
                res.Deleted = IsDeleted();
                _log.LogTrace("{SID} is tombstoned, skipping rest of resolution", objectId);
                return(res);
            }

            //Try to resolve the domain
            var    distinguishedName = DistinguishedName;
            string itemDomain;

            if (distinguishedName == null)
            {
                if (objectId.StartsWith("S-1-"))
                {
                    itemDomain = _utils.GetDomainNameFromSid(objectId);
                }
                else
                {
                    _log.LogWarning("Failed to resolve domain for {ObjectID}", objectId);
                    return(null);
                }
            }
            else
            {
                itemDomain = Helpers.DistinguishedNameToDomain(distinguishedName);
            }

            _log.LogTrace("Resolved domain for {SID} to {Domain}", objectId, itemDomain);

            res.Domain = itemDomain;

            if (WellKnownPrincipal.GetWellKnownPrincipal(objectId, out var wkPrincipal))
            {
                res.DomainSid   = _utils.GetSidFromDomainName(itemDomain);
                res.DisplayName = $"{wkPrincipal.ObjectIdentifier}@{itemDomain}";
                res.ObjectType  = wkPrincipal.ObjectType;
                res.ObjectId    = _utils.ConvertWellKnownPrincipal(objectId, itemDomain);

                _log.LogTrace("Resolved {DN} to wkp {ObjectID}", DistinguishedName, res.ObjectId);
                return(res);
            }

            if (objectId.StartsWith("S-1-"))
            {
                try
                {
                    res.DomainSid = new SecurityIdentifier(objectId).AccountDomainSid.Value;
                }
                catch
                {
                    res.DomainSid = _utils.GetSidFromDomainName(itemDomain);
                }
            }
            else
            {
                res.DomainSid = _utils.GetSidFromDomainName(itemDomain);
            }

            var samAccountName = GetProperty(LDAPProperties.SAMAccountName);

            var itemType = GetLabel();

            res.ObjectType = itemType;

            if (IsGMSA() || IsMSA())
            {
                res.ObjectType = Label.User;
                itemType       = Label.User;
            }

            _log.LogTrace("Resolved type for {SID} to {Label}", objectId, itemType);

            switch (itemType)
            {
            case Label.User:
            case Label.Group:
                res.DisplayName = $"{samAccountName}@{itemDomain}";
                break;

            case Label.Computer:
                var shortName = samAccountName?.TrimEnd('$');
                var dns       = GetProperty(LDAPProperties.DNSHostName);
                var cn        = GetProperty(LDAPProperties.CanonicalName);

                if (dns != null)
                {
                    res.DisplayName = dns;
                }
                else if (shortName == null && cn == null)
                {
                    res.DisplayName = $"UNKNOWN.{itemDomain}";
                }
                else if (shortName != null)
                {
                    res.DisplayName = $"{shortName}.{itemDomain}";
                }
                else
                {
                    res.DisplayName = $"{cn}.{itemDomain}";
                }

                break;

            case Label.GPO:
                res.DisplayName = $"{GetProperty(LDAPProperties.DisplayName)}@{itemDomain}";
                break;

            case Label.Domain:
                res.DisplayName = itemDomain;
                break;

            case Label.OU:
            case Label.Container:
                res.DisplayName = $"{GetProperty(LDAPProperties.Name)}@{itemDomain}";
                break;

            case Label.Base:
                res.DisplayName = $"{samAccountName}@{itemDomain}";
                break;

            default:
                throw new ArgumentOutOfRangeException();
            }

            return(res);
        }
        public static async Task <UserProperties> ReadUserProperties(SearchResultEntry entry)
        {
            var userProps = new UserProperties();
            var props     = new Dictionary <string, object>
            {
                { "description", entry.GetProperty("description") }
            };

            var  uac = entry.GetProperty("useraccountcontrol");
            bool enabled, trustedToAuth, sensitive, dontReqPreAuth, passwdNotReq, unconstrained, pwdNeverExpires;

            if (int.TryParse(uac, out var flag))
            {
                var flags = (UAC.UacFlags)flag;
                enabled         = (flags & UAC.UacFlags.AccountDisable) == 0;
                trustedToAuth   = (flags & UAC.UacFlags.TrustedToAuthForDelegation) != 0;
                sensitive       = (flags & UAC.UacFlags.NotDelegated) != 0;
                dontReqPreAuth  = (flags & UAC.UacFlags.DontReqPreauth) != 0;
                passwdNotReq    = (flags & UAC.UacFlags.PasswordNotRequired) != 0;
                unconstrained   = (flags & UAC.UacFlags.TrustedForDelegation) != 0;
                pwdNeverExpires = (flags & UAC.UacFlags.DontExpirePassword) != 0;
            }
            else
            {
                trustedToAuth   = false;
                enabled         = true;
                sensitive       = false;
                dontReqPreAuth  = false;
                passwdNotReq    = false;
                unconstrained   = false;
                pwdNeverExpires = false;
            }

            props.Add("sensitive", sensitive);
            props.Add("dontreqpreauth", dontReqPreAuth);
            props.Add("passwordnotreqd", passwdNotReq);
            props.Add("unconstraineddelegation", unconstrained);
            props.Add("pwdneverexpires", pwdNeverExpires);
            props.Add("enabled", enabled);
            var domain = Helpers.DistinguishedNameToDomain(entry.DistinguishedName);

            var comps = new List <TypedPrincipal>();

            if (trustedToAuth)
            {
                var delegates = entry.GetPropertyAsArray("msds-allowedToDelegateTo");
                props.Add("allowedtodelegate", delegates);

                foreach (var d in delegates)
                {
                    var hname = d.Contains("/") ? d.Split('/')[1] : d;
                    hname = hname.Split(':')[0];
                    var resolvedHost = await LDAPUtils.ResolveHostToSid(hname, domain);

                    if (resolvedHost.Contains(".") || resolvedHost.Contains("S-1"))
                    {
                        comps.Add(new TypedPrincipal
                        {
                            ObjectIdentifier = resolvedHost,
                            ObjectType       = Label.Computer
                        });
                    }
                }
            }

            userProps.AllowedToDelegate = comps.ToArray();

            props.Add("lastlogon", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty("lastlogon")));
            props.Add("lastlogontimestamp", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty("lastlogontimestamp")));
            props.Add("pwdlastset", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty("pwdlastset")));
            var spn = entry.GetPropertyAsArray("serviceprincipalname");

            props.Add("serviceprincipalnames", spn);
            props.Add("hasspn", spn.Length > 0);
            props.Add("displayname", entry.GetProperty("displayname"));
            props.Add("email", entry.GetProperty("mail"));
            props.Add("title", entry.GetProperty("title"));
            props.Add("homedirectory", entry.GetProperty("homedirectory"));
            props.Add("description", entry.GetProperty("description"));
            props.Add("userpassword", entry.GetProperty("userpassword"));

            var ac = entry.GetProperty("admincount");

            if (ac != null)
            {
                var a = int.Parse(ac);
                props.Add("admincount", a != 0);
            }
            else
            {
                props.Add("admincount", false);
            }

            var sh                   = entry.GetPropertyAsArrayOfBytes("sidhistory");
            var sidHistoryList       = new List <string>();
            var sidHistoryPrincipals = new List <TypedPrincipal>();

            foreach (var sid in sh)
            {
                string sSid;
                try
                {
                    sSid = new SecurityIdentifier(sid, 0).Value;
                }
                catch
                {
                    continue;
                }

                var res = LDAPUtils.ResolveIDAndType(sSid, domain);

                sidHistoryPrincipals.Add(res);
            }

            userProps.SidHistory = sidHistoryPrincipals.ToArray();

            props.Add("sidhistory", sidHistoryList.ToArray());

            userProps.Props = props;

            return(userProps);
        }