< Summary

Class:SharpHoundCommonLib.LdapUtils
Assembly:SharpHoundCommonLib
File(s):D:\a\SharpHoundCommon\SharpHoundCommon\src\CommonLib\LdapUtils.cs
Covered lines:249
Uncovered lines:653
Coverable lines:902
Total lines:1304
Line coverage:27.6% (249 of 902)
Covered branches:160
Total branches:608
Branch coverage:26.3% (160 of 608)

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.cctor()100%10100%
.ctor()100%10100%
.ctor(...)0%600%
RangedRetrieval(...)100%100%
Query(...)100%10100%
PagedQuery(...)100%100%
ResolveIDAndType()0%200%
ResolveIDAndType()25%20031.57%
LookupSidType()0%2000%
LookupGuidType()0%1400%
GetWellKnownPrincipal()75%40100%
GetWellKnownPrincipalObjectIdentifier()57.14%14060%
GetForest()0%800%
GetForestFromLdap()0%600%
GetDomainNameFromSid()0%2000%
ConvertDomainSidToDomainNameFromLdap()0%2400%
GetDomainSidFromDomainName()0%1600%
GetDomain(...)33.33%12061.9%
GetDomain(...)30%10061.9%
GetDomain(...)0%400%
ResolveAccountName()0%1400%
ResolveHostToSid()1.72%5807.81%
GetWorkstationInfo()0%600%
GetGlobalCatalogMatches()27.27%22054.54%
ResolveCertTemplateByProperty()0%600%
RequestNETBIOSNameFromComputer(...)0%800%
MakeSecurityDescriptor()100%100%
ConvertLocalWellKnownPrincipal()0%1400%
IsDomainController()0%1000%
ResolveDistinguishedName()0%2800%
GetDSHueristics()0%600%
AddDomainController(...)100%10100%
GetWellKnownPrincipalOutput()41.3%46023.07%
GetEnterpriseDCGroups()78.26%46096.15%
SetLdapConfig(...)100%100%
TestLdapConnection(...)100%100%
GetNamingContextPath()0%2400%
ResetUtils()0%200%
CreateDirectoryEntry(...)0%200%
Dispose()0%200%
ResolveLabel(...)89.13%46096.22%
ResolveSearchResult()38.63%88050.44%

File(s)

D:\a\SharpHoundCommon\SharpHoundCommon\src\CommonLib\LdapUtils.cs

#LineLine coverage
 1using System;
 2using System.Collections.Concurrent;
 3using System.Collections.Generic;
 4using System.DirectoryServices;
 5using System.DirectoryServices.AccountManagement;
 6using System.DirectoryServices.ActiveDirectory;
 7using System.Linq;
 8using System.Net;
 9using System.Net.Sockets;
 10using System.Security.Principal;
 11using System.Text;
 12using System.Text.RegularExpressions;
 13using System.Threading;
 14using System.Threading.Tasks;
 15using Microsoft.Extensions.Logging;
 16using SharpHoundCommonLib.DirectoryObjects;
 17using SharpHoundCommonLib.Enums;
 18using SharpHoundCommonLib.LDAPQueries;
 19using SharpHoundCommonLib.OutputTypes;
 20using SharpHoundCommonLib.Processors;
 21using SharpHoundRPC.NetAPINative;
 22using Domain = System.DirectoryServices.ActiveDirectory.Domain;
 23using Group = SharpHoundCommonLib.OutputTypes.Group;
 24using SearchScope = System.DirectoryServices.Protocols.SearchScope;
 25
 26namespace SharpHoundCommonLib {
 27    public class LdapUtils : ILdapUtils {
 28        //This cache is indexed by domain sid
 129        private static ConcurrentDictionary<string, Domain> _domainCache = new();
 130        private static ConcurrentHashSet _domainControllers = new(StringComparer.OrdinalIgnoreCase);
 131        private static ConcurrentHashSet _unresolvablePrincipals = new(StringComparer.OrdinalIgnoreCase);
 32
 133        private static readonly ConcurrentDictionary<string, string> DomainToForestCache =
 134            new(StringComparer.OrdinalIgnoreCase);
 35
 136        private static readonly ConcurrentDictionary<string, ResolvedWellKnownPrincipal>
 137            SeenWellKnownPrincipals = new();
 38
 6239        private readonly ConcurrentDictionary<string, string>
 6240            _hostResolutionMap = new(StringComparer.OrdinalIgnoreCase);
 41
 6242        private readonly ConcurrentDictionary<string, TypedPrincipal> _distinguishedNameCache =
 6243            new(StringComparer.OrdinalIgnoreCase);
 44
 45        private readonly ILogger _log;
 46        private readonly PortScanner _portScanner;
 47        private readonly NativeMethods _nativeMethods;
 6248        private readonly string _nullCacheKey = Guid.NewGuid().ToString();
 149        private static readonly Regex SIDRegex = new(@"^(S-\d+-\d+-\d+-\d+-\d+-\d+)(-\d+)?$");
 50
 6251        private readonly string[] _translateNames = { "Administrator", "admin" };
 6252        private LdapConfig _ldapConfig = new();
 53
 54        private ConnectionPoolManager _connectionPool;
 55
 156        private static readonly byte[] NameRequest = {
 157            0x80, 0x94, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
 158            0x00, 0x00, 0x00, 0x00, 0x20, 0x43, 0x4b, 0x41,
 159            0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
 160            0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
 161            0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
 162            0x41, 0x41, 0x41, 0x41, 0x41, 0x00, 0x00, 0x21,
 163            0x00, 0x01
 164        };
 65
 66        private class ResolvedWellKnownPrincipal {
 367            public string DomainName { get; set; }
 368            public string WkpId { get; set; }
 69        }
 70
 12471        public LdapUtils() {
 6272            _nativeMethods = new NativeMethods();
 6273            _portScanner = new PortScanner();
 6274            _log = Logging.LogProvider.CreateLogger("LDAPUtils");
 6275            _connectionPool = new ConnectionPoolManager(_ldapConfig, _log);
 6276        }
 77
 078        public LdapUtils(NativeMethods nativeMethods = null, PortScanner scanner = null, ILogger log = null) {
 079            _nativeMethods = nativeMethods ?? new NativeMethods();
 080            _portScanner = scanner ?? new PortScanner();
 081            _log = log ?? Logging.LogProvider.CreateLogger("LDAPUtils");
 082            _connectionPool = new ConnectionPoolManager(_ldapConfig, scanner: _portScanner);
 083        }
 84
 85        public IAsyncEnumerable<Result<string>> RangedRetrieval(string distinguishedName,
 086            string attributeName, CancellationToken cancellationToken = new()) {
 087            return _connectionPool.RangedRetrieval(distinguishedName, attributeName, cancellationToken);
 088        }
 89
 90        public IAsyncEnumerable<LdapResult<IDirectoryObject>> Query(LdapQueryParameters queryParameters,
 191            CancellationToken cancellationToken = new()) {
 192            return _connectionPool.Query(queryParameters, cancellationToken);
 193        }
 94
 95        public IAsyncEnumerable<LdapResult<IDirectoryObject>> PagedQuery(LdapQueryParameters queryParameters,
 096            CancellationToken cancellationToken = new()) {
 097            return _connectionPool.PagedQuery(queryParameters, cancellationToken);
 098        }
 99
 100        public async Task<(bool Success, TypedPrincipal Principal)> ResolveIDAndType(
 101            SecurityIdentifier securityIdentifier,
 0102            string objectDomain) {
 0103            return await ResolveIDAndType(securityIdentifier.Value, objectDomain);
 0104        }
 105
 106        public async Task<(bool Success, TypedPrincipal Principal)>
 2107            ResolveIDAndType(string identifier, string objectDomain) {
 3108            if (identifier.IndexOf("0ACNF", StringComparison.OrdinalIgnoreCase) >= 0) {
 1109                return (false, new TypedPrincipal(identifier, Label.Base));
 110            }
 111
 2112            if (await GetWellKnownPrincipal(identifier, objectDomain) is (true, var principal)) {
 1113                return (true, principal);
 114            }
 115
 0116            if (_unresolvablePrincipals.Contains(identifier)) {
 0117                return (false, new TypedPrincipal(identifier, Label.Base));
 118            }
 119
 0120            if (identifier.StartsWith("S-")) {
 0121                var result = await LookupSidType(identifier, objectDomain);
 0122                if (!result.Success) {
 0123                    _unresolvablePrincipals.Add(identifier);
 0124                }
 125
 0126                return (result.Success, new TypedPrincipal(identifier, result.Type));
 127            }
 128
 0129            var (success, type) = await LookupGuidType(identifier, objectDomain);
 0130            if (!success) {
 0131                _unresolvablePrincipals.Add(identifier);
 0132            }
 133
 0134            return (success, new TypedPrincipal(identifier, type));
 2135        }
 136
 0137        private async Task<(bool Success, Label Type)> LookupSidType(string sid, string domain) {
 0138            if (Cache.GetIDType(sid, out var type)) {
 0139                return (true, type);
 140            }
 141
 0142            var tempDomain = domain;
 143
 0144            if (await GetDomainNameFromSid(sid) is (true, var domainName)) {
 0145                tempDomain = domainName;
 0146            }
 147
 0148            var result = await Query(new LdapQueryParameters() {
 0149                DomainName = tempDomain,
 0150                LDAPFilter = CommonFilters.SpecificSID(sid),
 0151                Attributes = CommonProperties.TypeResolutionProps
 0152            }).DefaultIfEmpty(LdapResult<IDirectoryObject>.Fail()).FirstOrDefaultAsync();
 153
 0154            if (result.IsSuccess) {
 0155                if (result.Value.GetLabel(out type)) {
 0156                    Cache.AddType(sid, type);
 0157                    return (true, type);
 158                }
 0159            }
 160
 0161            try {
 0162                var entry = CreateDirectoryEntry($"LDAP://<SID={sid}>");
 0163                if (entry.GetLabel(out type)) {
 0164                    Cache.AddType(sid, type);
 0165                    return (true, type);
 166                }
 0167            } catch {
 168                //pass
 0169            }
 170
 0171            try {
 0172                using (var ctx = new PrincipalContext(ContextType.Domain)) {
 0173                    var principal = Principal.FindByIdentity(ctx, IdentityType.Sid, sid);
 0174                    if (principal != null) {
 0175                        var entry = ((DirectoryEntry)principal.GetUnderlyingObject()).ToDirectoryObject();
 0176                        if (entry.GetLabel(out type)) {
 0177                            Cache.AddType(sid, type);
 0178                            return (true, type);
 179                        }
 0180                    }
 0181                }
 0182            } catch {
 183                //pass
 0184            }
 185
 186
 0187            return (false, Label.Base);
 0188        }
 189
 0190        private async Task<(bool Success, Label type)> LookupGuidType(string guid, string domain) {
 0191            if (Cache.GetIDType(guid, out var type)) {
 0192                return (true, type);
 193            }
 194
 0195            var result = await Query(new LdapQueryParameters() {
 0196                DomainName = domain,
 0197                LDAPFilter = CommonFilters.SpecificGUID(guid),
 0198                Attributes = CommonProperties.TypeResolutionProps
 0199            }).DefaultIfEmpty(LdapResult<IDirectoryObject>.Fail()).FirstOrDefaultAsync();
 200
 0201            if (result.IsSuccess && result.Value.GetLabel(out type)) {
 0202                Cache.AddType(guid, type);
 0203                return (true, type);
 204            }
 205
 0206            try {
 0207                var entry = CreateDirectoryEntry($"LDAP://<GUID={guid}>");
 0208                if (entry.GetLabel(out type)) {
 0209                    Cache.AddType(guid, type);
 0210                    return (true, type);
 211                }
 0212            } catch {
 213                //pass
 0214            }
 215
 0216            try {
 0217                using (var ctx = new PrincipalContext(ContextType.Domain)) {
 0218                    var principal = Principal.FindByIdentity(ctx, IdentityType.Guid, guid);
 0219                    if (principal != null) {
 0220                        var entry = ((DirectoryEntry)principal.GetUnderlyingObject()).ToDirectoryObject();
 0221                        if (entry.GetLabel(out type)) {
 0222                            Cache.AddType(guid, type);
 0223                            return (true, type);
 224                        }
 0225                    }
 0226                }
 0227            } catch {
 228                //pass
 0229            }
 230
 231
 0232            return (false, Label.Base);
 0233        }
 234
 235        public async Task<(bool Success, TypedPrincipal WellKnownPrincipal)> GetWellKnownPrincipal(
 4236            string securityIdentifier, string objectDomain) {
 5237            if (!WellKnownPrincipal.GetWellKnownPrincipal(securityIdentifier, out var wellKnownPrincipal)) {
 1238                return (false, null);
 239            }
 240
 3241            var (newIdentifier, newDomain) =
 3242                await GetWellKnownPrincipalObjectIdentifier(securityIdentifier, objectDomain);
 243
 3244            wellKnownPrincipal.ObjectIdentifier = newIdentifier;
 3245            SeenWellKnownPrincipals.TryAdd(wellKnownPrincipal.ObjectIdentifier, new ResolvedWellKnownPrincipal {
 3246                DomainName = newDomain,
 3247                WkpId = securityIdentifier
 3248            });
 249
 3250            return (true, wellKnownPrincipal);
 4251        }
 252
 253        private async Task<(string ObjectID, string Domain)> GetWellKnownPrincipalObjectIdentifier(
 3254            string securityIdentifier, string domain) {
 3255            if (!WellKnownPrincipal.GetWellKnownPrincipal(securityIdentifier, out _))
 0256                return (securityIdentifier, string.Empty);
 257
 5258            if (!securityIdentifier.Equals("S-1-5-9", StringComparison.OrdinalIgnoreCase)) {
 2259                var tempDomain = domain;
 2260                if (GetDomain(tempDomain, out var domainObject) && domainObject.Name != null) {
 0261                    tempDomain = domainObject.Name;
 0262                }
 263
 2264                return ($"{tempDomain}-{securityIdentifier}".ToUpper(), tempDomain);
 265            }
 266
 2267            if (await GetForest(domain) is (true, var forest)) {
 1268                return ($"{forest}-{securityIdentifier}".ToUpper(), forest);
 269            }
 270
 0271            _log.LogWarning("Failed to get a forest name for domain {Domain}, unable to resolve enterprise DC sid",
 0272                domain);
 0273            return ($"UNKNOWN-{securityIdentifier}", "UNKNOWN");
 3274        }
 275
 0276        public virtual async Task<(bool Success, string ForestName)> GetForest(string domain) {
 0277            if (DomainToForestCache.TryGetValue(domain, out var cachedForest)) {
 0278                return (true, cachedForest);
 279            }
 280
 0281            if (GetDomain(domain, out var domainObject)) {
 0282                try {
 0283                    var forestName = domainObject.Forest.Name.ToUpper();
 0284                    DomainToForestCache.TryAdd(domain, forestName);
 0285                    return (true, forestName);
 0286                } catch {
 287                    //pass
 0288                }
 0289            }
 290
 0291            var (success, forest) = await GetForestFromLdap(domain);
 0292            if (success) {
 0293                DomainToForestCache.TryAdd(domain, forest);
 0294                return (true, forest);
 295            }
 296
 0297            return (false, null);
 0298        }
 299
 0300        private async Task<(bool Success, string ForestName)> GetForestFromLdap(string domain) {
 0301            var queryParameters = new LdapQueryParameters {
 0302                Attributes = new[] { LDAPProperties.RootDomainNamingContext },
 0303                SearchScope = SearchScope.Base,
 0304                DomainName = domain,
 0305                LDAPFilter = new LdapFilter().AddAllObjects().GetFilter(),
 0306            };
 307
 0308            var result = await Query(queryParameters).DefaultIfEmpty(LdapResult<IDirectoryObject>.Fail())
 0309                .FirstOrDefaultAsync();
 0310            if (result.IsSuccess &&
 0311                result.Value.TryGetProperty(LDAPProperties.RootDomainNamingContext, out var rootNamingContext)) {
 0312                return (true, Helpers.DistinguishedNameToDomain(rootNamingContext).ToUpper());
 313            }
 314
 0315            return (false, null);
 0316        }
 317
 0318        public virtual async Task<(bool Success, string DomainName)> GetDomainNameFromSid(string sid) {
 319            string domainSid;
 0320            try {
 0321                domainSid = new SecurityIdentifier(sid).AccountDomainSid?.Value.ToUpper();
 0322            } catch {
 0323                var match = SIDRegex.Match(sid);
 0324                domainSid = match.Success ? match.Groups[1].Value : null;
 0325            }
 326
 0327            if (domainSid == null) {
 0328                return (false, "");
 329            }
 330
 0331            if (Cache.GetDomainSidMapping(domainSid, out var domain)) {
 0332                return (true, domain);
 333            }
 334
 0335            try {
 0336                var entry = CreateDirectoryEntry($"LDAP://<SID={domainSid}>");
 0337                if (entry.TryGetDistinguishedName(out var dn)) {
 0338                    Cache.AddDomainSidMapping(domainSid, Helpers.DistinguishedNameToDomain(dn));
 0339                    return (true, Helpers.DistinguishedNameToDomain(dn));
 340                }
 0341            } catch {
 342                //pass
 0343            }
 344
 0345            if (await ConvertDomainSidToDomainNameFromLdap(sid) is (true, var domainName)) {
 0346                Cache.AddDomainSidMapping(domainSid, domainName);
 0347                return (true, domainName);
 348            }
 349
 0350            try {
 0351                using (var ctx = new PrincipalContext(ContextType.Domain)) {
 0352                    var principal = Principal.FindByIdentity(ctx, IdentityType.Sid, sid);
 0353                    if (principal != null) {
 0354                        var dn = principal.DistinguishedName;
 0355                        if (!string.IsNullOrWhiteSpace(dn)) {
 0356                            Cache.AddDomainSidMapping(domainSid, Helpers.DistinguishedNameToDomain(dn));
 0357                            return (true, Helpers.DistinguishedNameToDomain(dn));
 358                        }
 0359                    }
 0360                }
 0361            } catch {
 362                //pass
 0363            }
 364
 365
 0366            return (false, string.Empty);
 0367        }
 368
 0369        private async Task<(bool Success, string DomainName)> ConvertDomainSidToDomainNameFromLdap(string domainSid) {
 0370            if (!GetDomain(out var domain) || domain?.Name == null) {
 0371                return (false, string.Empty);
 372            }
 373
 0374            var result = await Query(new LdapQueryParameters {
 0375                DomainName = domain.Name,
 0376                Attributes = new[] { LDAPProperties.DistinguishedName },
 0377                GlobalCatalog = true,
 0378                LDAPFilter = new LdapFilter().AddDomains(CommonFilters.SpecificSID(domainSid)).GetFilter()
 0379            }).DefaultIfEmpty(LdapResult<IDirectoryObject>.Fail()).FirstOrDefaultAsync();
 380
 0381            if (result.IsSuccess && result.Value.TryGetDistinguishedName(out var distinguishedName)) {
 0382                return (true, Helpers.DistinguishedNameToDomain(distinguishedName));
 383            }
 384
 0385            result = await Query(new LdapQueryParameters {
 0386                DomainName = domain.Name,
 0387                Attributes = new[] { LDAPProperties.DistinguishedName },
 0388                GlobalCatalog = true,
 0389                LDAPFilter = new LdapFilter().AddFilter("(objectclass=trusteddomain)", true)
 0390                    .AddFilter($"(securityidentifier={Helpers.ConvertSidToHexSid(domainSid)})", true).GetFilter()
 0391            }).DefaultIfEmpty(LdapResult<IDirectoryObject>.Fail()).FirstOrDefaultAsync();
 392
 0393            if (result.IsSuccess && result.Value.TryGetDistinguishedName(out distinguishedName)) {
 0394                return (true, Helpers.DistinguishedNameToDomain(distinguishedName));
 395            }
 396
 0397            result = await Query(new LdapQueryParameters {
 0398                DomainName = domain.Name,
 0399                Attributes = new[] { LDAPProperties.DistinguishedName },
 0400                LDAPFilter = new LdapFilter().AddFilter("(objectclass=domaindns)", true)
 0401                    .AddFilter(CommonFilters.SpecificSID(domainSid), true).GetFilter()
 0402            }).DefaultIfEmpty(LdapResult<IDirectoryObject>.Fail()).FirstOrDefaultAsync();
 403
 0404            if (result.IsSuccess && result.Value.TryGetDistinguishedName(out distinguishedName)) {
 0405                return (true, Helpers.DistinguishedNameToDomain(distinguishedName));
 406            }
 407
 0408            return (false, string.Empty);
 0409        }
 410
 0411        public virtual async Task<(bool Success, string DomainSid)> GetDomainSidFromDomainName(string domainName) {
 0412            if (Cache.GetDomainSidMapping(domainName, out var domainSid)) return (true, domainSid);
 413
 0414            try {
 0415                var entry = CreateDirectoryEntry($"LDAP://{domainName}");
 416                //Force load objectsid into the object cache
 0417                if (entry.TryGetSecurityIdentifier(out var sid)) {
 0418                    Cache.AddDomainSidMapping(domainName, sid);
 0419                    domainSid = sid;
 0420                    return (true, domainSid);
 421                }
 0422            } catch {
 423                //we expect this to fail sometimes
 0424            }
 425
 0426            if (GetDomain(domainName, out var domainObject))
 0427                try {
 0428                    var entry = domainObject.GetDirectoryEntry().ToDirectoryObject();
 0429                    if (entry.TryGetSecurityIdentifier(out domainSid)) {
 0430                        Cache.AddDomainSidMapping(domainName, domainSid);
 0431                        return (true, domainSid);
 432                    }
 0433                } catch {
 434                    //we expect this to fail sometimes (not sure why, but better safe than sorry)
 0435                }
 436
 0437            foreach (var name in _translateNames)
 0438                try {
 0439                    var account = new NTAccount(domainName, name);
 0440                    var sid = (SecurityIdentifier)account.Translate(typeof(SecurityIdentifier));
 0441                    domainSid = sid.AccountDomainSid.ToString().ToUpper();
 0442                    Cache.AddDomainSidMapping(domainName, domainSid);
 0443                    return (true, domainSid);
 0444                } catch {
 445                    //We expect this to fail if the username doesn't exist in the domain
 0446                }
 447
 0448            var result = await Query(new LdapQueryParameters() {
 0449                DomainName = domainName,
 0450                Attributes = new[] { LDAPProperties.ObjectSID },
 0451                LDAPFilter = new LdapFilter().AddFilter(CommonFilters.DomainControllers, true).GetFilter()
 0452            }).DefaultIfEmpty(LdapResult<IDirectoryObject>.Fail()).FirstOrDefaultAsync();
 453
 0454            if (result.IsSuccess && result.Value.TryGetSecurityIdentifier(out var securityIdentifier)) {
 0455                domainSid = new SecurityIdentifier(securityIdentifier).AccountDomainSid.Value.ToUpper();
 0456                Cache.AddDomainSidMapping(domainName, domainSid);
 0457                return (true, domainSid);
 458            }
 459
 0460            return (false, string.Empty);
 0461        }
 462
 463        /// <summary>
 464        ///     Attempts to get the Domain object representing the target domain. If null is specified for the domain na
 465        ///     the user's current domain
 466        /// </summary>
 467        /// <param name="domain"></param>
 468        /// <param name="domainName"></param>
 469        /// <returns></returns>
 2470        public bool GetDomain(string domainName, out Domain domain) {
 2471            var cacheKey = domainName ?? _nullCacheKey;
 2472            if (_domainCache.TryGetValue(cacheKey, out domain)) return true;
 473
 2474            try {
 475                DirectoryContext context;
 2476                if (_ldapConfig.Username != null)
 0477                    context = domainName != null
 0478                        ? new DirectoryContext(DirectoryContextType.Domain, domainName, _ldapConfig.Username,
 0479                            _ldapConfig.Password)
 0480                        : new DirectoryContext(DirectoryContextType.Domain, _ldapConfig.Username,
 0481                            _ldapConfig.Password);
 482                else
 2483                    context = domainName != null
 2484                        ? new DirectoryContext(DirectoryContextType.Domain, domainName)
 2485                        : new DirectoryContext(DirectoryContextType.Domain);
 486
 2487                domain = Domain.GetDomain(context);
 0488                if (domain == null) return false;
 0489                _domainCache.TryAdd(cacheKey, domain);
 0490                return true;
 4491            } catch (Exception e) {
 2492                _log.LogDebug(e, "GetDomain call failed for domain name {Name}", domainName);
 2493                return false;
 494            }
 2495        }
 496
 2497        public static bool GetDomain(string domainName, LdapConfig ldapConfig, out Domain domain) {
 2498            if (_domainCache.TryGetValue(domainName, out domain)) return true;
 499
 2500            try {
 501                DirectoryContext context;
 2502                if (ldapConfig.Username != null)
 0503                    context = domainName != null
 0504                        ? new DirectoryContext(DirectoryContextType.Domain, domainName, ldapConfig.Username,
 0505                            ldapConfig.Password)
 0506                        : new DirectoryContext(DirectoryContextType.Domain, ldapConfig.Username,
 0507                            ldapConfig.Password);
 508                else
 2509                    context = domainName != null
 2510                        ? new DirectoryContext(DirectoryContextType.Domain, domainName)
 2511                        : new DirectoryContext(DirectoryContextType.Domain);
 512
 2513                domain = Domain.GetDomain(context);
 0514                if (domain == null) return false;
 0515                _domainCache.TryAdd(domainName, domain);
 0516                return true;
 4517            } catch (Exception e) {
 2518                Logging.Logger.LogDebug("Static GetDomain call failed for domain {DomainName}: {Error}", domainName,
 2519                    e.Message);
 2520                return false;
 521            }
 2522        }
 523
 524        /// <summary>
 525        ///     Attempts to get the Domain object representing the target domain. If null is specified for the domain na
 526        ///     the user's current domain
 527        /// </summary>
 528        /// <param name="domain"></param>
 529        /// <param name="domainName"></param>
 530        /// <returns></returns>
 0531        public bool GetDomain(out Domain domain) {
 0532            if (_domainCache.TryGetValue(_nullCacheKey, out domain)) return true;
 533
 0534            try {
 0535                var context = _ldapConfig.Username != null
 0536                    ? new DirectoryContext(DirectoryContextType.Domain, _ldapConfig.Username,
 0537                        _ldapConfig.Password)
 0538                    : new DirectoryContext(DirectoryContextType.Domain);
 539
 0540                domain = Domain.GetDomain(context);
 0541                _domainCache.TryAdd(_nullCacheKey, domain);
 0542                return true;
 0543            } catch (Exception e) {
 0544                _log.LogDebug(e, "GetDomain call failed for blank domain");
 0545                return false;
 546            }
 0547        }
 548
 0549        public async Task<(bool Success, TypedPrincipal Principal)> ResolveAccountName(string name, string domain) {
 0550            if (string.IsNullOrWhiteSpace(name)) {
 0551                return (false, null);
 552            }
 553
 0554            if (Cache.GetPrefixedValue(name, domain, out var id) && Cache.GetIDType(id, out var type))
 0555                return (true, new TypedPrincipal {
 0556                    ObjectIdentifier = id,
 0557                    ObjectType = type
 0558                });
 559
 0560            var result = await Query(new LdapQueryParameters() {
 0561                DomainName = domain,
 0562                Attributes = CommonProperties.TypeResolutionProps,
 0563                LDAPFilter = $"(samaccountname={name})"
 0564            }).DefaultIfEmpty(LdapResult<IDirectoryObject>.Fail()).FirstOrDefaultAsync();
 565
 0566            if (result.IsSuccess && result.Value.GetObjectIdentifier(out id)) {
 0567                result.Value.GetLabel(out type);
 0568                Cache.AddPrefixedValue(name, domain, id);
 0569                Cache.AddType(id, type);
 570
 0571                var (tempID, _) = await GetWellKnownPrincipalObjectIdentifier(id, domain);
 0572                return (true, new TypedPrincipal(tempID, type));
 573            }
 574
 0575            return (false, null);
 0576        }
 577
 1578        public async Task<(bool Success, string SecurityIdentifier)> ResolveHostToSid(string host, string domain) {
 579            //Remove SPN prefixes from the host name so we're working with a clean name
 1580            var strippedHost = Helpers.StripServicePrincipalName(host).ToUpper().TrimEnd('$');
 2581            if (string.IsNullOrEmpty(strippedHost)) {
 1582                return (false, string.Empty);
 583            }
 584
 0585            if (_hostResolutionMap.TryGetValue(strippedHost, out var sid)) return (sid != null, sid);
 586
 587            //Immediately start with NetWkstaGetInfo as it's our most reliable indicator if successful
 0588            if (await GetWorkstationInfo(strippedHost) is (true, var workstationInfo)) {
 0589                var tempName = workstationInfo.ComputerName;
 0590                var tempDomain = workstationInfo.LanGroup;
 591
 0592                if (string.IsNullOrWhiteSpace(tempDomain)) {
 0593                    tempDomain = domain;
 0594                }
 595
 0596                if (!string.IsNullOrWhiteSpace(tempName)) {
 0597                    tempName = $"{tempName}$".ToUpper();
 0598                    if (await ResolveAccountName(tempName, tempDomain) is (true, var principal)) {
 0599                        _hostResolutionMap.TryAdd(strippedHost, principal.ObjectIdentifier);
 0600                        return (true, principal.ObjectIdentifier);
 601                    }
 0602                }
 0603            }
 604
 605            //Try some socket magic to get the NETBIOS name
 0606            if (RequestNETBIOSNameFromComputer(strippedHost, domain, out var netBiosName)) {
 0607                if (!string.IsNullOrWhiteSpace(netBiosName)) {
 0608                    var result = await ResolveAccountName($"{netBiosName}$", domain);
 0609                    if (result.Success) {
 0610                        _hostResolutionMap.TryAdd(strippedHost, result.Principal.ObjectIdentifier);
 0611                        return (true, result.Principal.ObjectIdentifier);
 612                    }
 0613                }
 0614            }
 615
 616            //Start by handling non-IP address names
 0617            if (!IPAddress.TryParse(strippedHost, out _)) {
 618                //PRIMARY.TESTLAB.LOCAL
 0619                if (strippedHost.Contains(".")) {
 0620                    var split = strippedHost.Split('.');
 0621                    var name = split[0];
 0622                    var result = await ResolveAccountName($"{name}$", domain);
 0623                    if (result.Success) {
 0624                        _hostResolutionMap.TryAdd(strippedHost, result.Principal.ObjectIdentifier);
 0625                        return (true, result.Principal.ObjectIdentifier);
 626                    }
 627
 0628                    var tempDomain = string.Join(".", split.Skip(1).ToArray());
 0629                    result = await ResolveAccountName($"{name}$", tempDomain);
 0630                    if (result.Success) {
 0631                        _hostResolutionMap.TryAdd(strippedHost, result.Principal.ObjectIdentifier);
 0632                        return (true, result.Principal.ObjectIdentifier);
 633                    }
 0634                } else {
 635                    //Format: WIN10 (probably a netbios name)
 0636                    var result = await ResolveAccountName($"{strippedHost}$", domain);
 0637                    if (result.Success) {
 0638                        _hostResolutionMap.TryAdd(strippedHost, result.Principal.ObjectIdentifier);
 0639                        return (true, result.Principal.ObjectIdentifier);
 640                    }
 0641                }
 0642            }
 643
 0644            try {
 0645                var resolvedHostname = (await Dns.GetHostEntryAsync(strippedHost)).HostName;
 0646                var split = resolvedHostname.Split('.');
 0647                var name = split[0];
 0648                var result = await ResolveAccountName($"{name}$", domain);
 0649                if (result.Success) {
 0650                    _hostResolutionMap.TryAdd(strippedHost, result.Principal.ObjectIdentifier);
 0651                    return (true, result.Principal.ObjectIdentifier);
 652                }
 653
 0654                var tempDomain = string.Join(".", split.Skip(1).ToArray());
 0655                result = await ResolveAccountName($"{name}$", tempDomain);
 0656                if (result.Success) {
 0657                    _hostResolutionMap.TryAdd(strippedHost, result.Principal.ObjectIdentifier);
 0658                    return (true, result.Principal.ObjectIdentifier);
 659                }
 0660            } catch {
 661                //pass
 0662            }
 663
 0664            _hostResolutionMap.TryAdd(strippedHost, null);
 0665            return (false, "");
 1666        }
 667
 668        /// <summary>
 669        ///     Calls the NetWkstaGetInfo API on a hostname
 670        /// </summary>
 671        /// <param name="hostname"></param>
 672        /// <returns></returns>
 0673        private async Task<(bool Success, NetAPIStructs.WorkstationInfo100 Info)> GetWorkstationInfo(string hostname) {
 0674            if (!await _portScanner.CheckPort(hostname))
 0675                return (false, default);
 676
 0677            var result = _nativeMethods.CallNetWkstaGetInfo(hostname);
 0678            if (result.IsSuccess) return (true, result.Value);
 679
 0680            return (false, default);
 0681        }
 682
 1683        public async Task<(bool Success, string[] Sids)> GetGlobalCatalogMatches(string name, string domain) {
 1684            if (Cache.GetGCCache(name, out var matches)) {
 0685                return (true, matches);
 686            }
 687
 1688            var sids = new List<string>();
 689
 3690            await foreach (var result in Query(new LdapQueryParameters {
 1691                               DomainName = domain,
 1692                               Attributes = new[] { LDAPProperties.ObjectSID },
 1693                               GlobalCatalog = true,
 1694                               LDAPFilter = new LdapFilter().AddUsers($"(samaccountname={name})").GetFilter()
 1695                           })) {
 0696                if (result.IsSuccess && result.Value.TryGetSecurityIdentifier(out var sid)) {
 0697                    if (await GetWellKnownPrincipal(sid, domain) is (true, var principal)) {
 0698                        sids.Add(principal.ObjectIdentifier);
 0699                    } else {
 0700                        sids.Add(sid);
 0701                    }
 0702                } else {
 0703                    return (false, Array.Empty<string>());
 704                }
 0705            }
 706
 1707            Cache.AddGCCache(name, sids.ToArray());
 1708            return (true, sids.ToArray());
 1709        }
 710
 711        public async Task<(bool Success, TypedPrincipal Principal)> ResolveCertTemplateByProperty(string propertyValue,
 0712            string propertyName, string domainName) {
 0713            var filter = new LdapFilter().AddCertificateTemplates()
 0714                .AddFilter($"({propertyName}={propertyValue})", true);
 0715            var result = await Query(new LdapQueryParameters {
 0716                DomainName = domainName,
 0717                Attributes = CommonProperties.TypeResolutionProps,
 0718                SearchScope = SearchScope.OneLevel,
 0719                NamingContext = NamingContext.Configuration,
 0720                RelativeSearchBase = DirectoryPaths.CertTemplateLocation,
 0721                LDAPFilter = filter.GetFilter(),
 0722            }).DefaultIfEmpty(LdapResult<IDirectoryObject>.Fail()).FirstOrDefaultAsync();
 723
 0724            if (!result.IsSuccess) {
 0725                _log.LogWarning(
 0726                    "Could not find certificate template with {PropertyName}:{PropertyValue}: {Error}",
 0727                    propertyName, propertyValue, result.Error);
 0728                return (false, null);
 729            }
 730
 0731            if (result.Value.TryGetGuid(out var guid)) {
 0732                return (true, new TypedPrincipal(guid, Label.CertTemplate));
 733            }
 734
 0735            return (false, default);
 0736        }
 737
 738        /// <summary>
 739        ///     Uses a socket and a set of bytes to request the NETBIOS name from a remote computer
 740        /// </summary>
 741        /// <param name="server"></param>
 742        /// <param name="domain"></param>
 743        /// <param name="netbios"></param>
 744        /// <returns></returns>
 0745        private static bool RequestNETBIOSNameFromComputer(string server, string domain, out string netbios) {
 0746            var receiveBuffer = new byte[1024];
 0747            var requestSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
 0748            try {
 749                //Set receive timeout to 1 second
 0750                requestSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 1000);
 751                EndPoint remoteEndpoint;
 752
 753                //We need to create an endpoint to bind too. If its an IP, just use that.
 0754                if (IPAddress.TryParse(server, out var parsedAddress))
 0755                    remoteEndpoint = new IPEndPoint(parsedAddress, 137);
 756                else
 757                    //If its not an IP, we're going to try and resolve it from DNS
 0758                    try {
 759                        IPAddress address;
 0760                        if (server.Contains("."))
 0761                            address = Dns
 0762                                .GetHostAddresses(server).First(x => x.AddressFamily == AddressFamily.InterNetwork);
 763                        else
 0764                            address = Dns.GetHostAddresses($"{server}.{domain}")[0];
 765
 0766                        if (address == null) {
 0767                            netbios = null;
 0768                            return false;
 769                        }
 770
 0771                        remoteEndpoint = new IPEndPoint(address, 137);
 0772                    } catch {
 773                        //Failed to resolve an IP, so return null
 0774                        netbios = null;
 0775                        return false;
 776                    }
 777
 0778                var originEndpoint = new IPEndPoint(IPAddress.Any, 0);
 0779                requestSocket.Bind(originEndpoint);
 780
 0781                try {
 0782                    requestSocket.SendTo(NameRequest, remoteEndpoint);
 0783                    var receivedByteCount = requestSocket.ReceiveFrom(receiveBuffer, ref remoteEndpoint);
 0784                    if (receivedByteCount >= 90) {
 0785                        netbios = new ASCIIEncoding().GetString(receiveBuffer, 57, 16).Trim('\0', ' ');
 0786                        return true;
 787                    }
 788
 0789                    netbios = null;
 0790                    return false;
 0791                } catch (SocketException) {
 0792                    netbios = null;
 0793                    return false;
 794                }
 0795            } finally {
 796                //Make sure we close the socket if its open
 0797                requestSocket.Close();
 0798            }
 0799        }
 800
 801        /// <summary>
 802        /// Created for testing purposes
 803        /// </summary>
 804        /// <returns></returns>
 0805        public ActiveDirectorySecurityDescriptor MakeSecurityDescriptor() {
 0806            return new ActiveDirectorySecurityDescriptor(new ActiveDirectorySecurity());
 0807        }
 808
 809        public async Task<(bool Success, TypedPrincipal Principal)> ConvertLocalWellKnownPrincipal(
 810            SecurityIdentifier sid,
 0811            string computerDomainSid, string computerDomain) {
 0812            if (!WellKnownPrincipal.GetWellKnownPrincipal(sid.Value, out var common)) return (false, null);
 813            //The "Everyone" and "Authenticated Users" principals are special and will be converted to the domain equiva
 0814            if (sid.Value is "S-1-1-0" or "S-1-5-11") {
 0815                return await GetWellKnownPrincipal(sid.Value, computerDomain);
 816            }
 817
 818            //Use the computer object id + the RID of the sid we looked up to create our new principal
 0819            var principal = new TypedPrincipal {
 0820                ObjectIdentifier = $"{computerDomainSid}-{sid.Rid()}",
 0821                ObjectType = common.ObjectType switch {
 0822                    Label.User => Label.LocalUser,
 0823                    Label.Group => Label.LocalGroup,
 0824                    _ => common.ObjectType
 0825                }
 0826            };
 827
 0828            return (true, principal);
 0829        }
 830
 0831        public async Task<bool> IsDomainController(string computerObjectId, string domainName) {
 0832            if (_domainControllers.Contains(computerObjectId)) {
 0833                return true;
 834            }
 835
 0836            var resDomain = await GetDomainNameFromSid(domainName) is (false, var tempDomain) ? tempDomain : domainName;
 0837            var filter = new LdapFilter().AddFilter(CommonFilters.SpecificSID(computerObjectId), true)
 0838                .AddFilter(CommonFilters.DomainControllers, true);
 0839            var result = await Query(new LdapQueryParameters() {
 0840                DomainName = resDomain,
 0841                Attributes = CommonProperties.ObjectID,
 0842                LDAPFilter = filter.GetFilter(),
 0843            }).DefaultIfEmpty(LdapResult<IDirectoryObject>.Fail()).FirstOrDefaultAsync();
 0844            if (result.IsSuccess) {
 0845                _domainControllers.Add(computerObjectId);
 0846            }
 847
 0848            return result.IsSuccess;
 0849        }
 850
 0851        public async Task<(bool Success, TypedPrincipal Principal)> ResolveDistinguishedName(string distinguishedName) {
 0852            if (_distinguishedNameCache.TryGetValue(distinguishedName, out var principal)) {
 0853                return (true, principal);
 854            }
 855
 0856            if (_unresolvablePrincipals.Contains(distinguishedName)) {
 0857                return (false, default);
 858            }
 859
 0860            var domain = Helpers.DistinguishedNameToDomain(distinguishedName);
 0861            var result = await Query(new LdapQueryParameters {
 0862                DomainName = domain,
 0863                Attributes = CommonProperties.TypeResolutionProps,
 0864                SearchBase = distinguishedName,
 0865                SearchScope = SearchScope.Base,
 0866                LDAPFilter = new LdapFilter().AddAllObjects().GetFilter()
 0867            }).DefaultIfEmpty(LdapResult<IDirectoryObject>.Fail()).FirstOrDefaultAsync();
 868
 0869            if (result.IsSuccess && result.Value.GetObjectIdentifier(out var id)) {
 0870                var entry = result.Value;
 871
 0872                if (await GetWellKnownPrincipal(id, domain) is (true, var wellKnownPrincipal)) {
 0873                    _distinguishedNameCache.TryAdd(distinguishedName, wellKnownPrincipal);
 0874                    return (true, wellKnownPrincipal);
 875                }
 876
 0877                entry.GetLabel(out var type);
 0878                principal = new TypedPrincipal(id, type);
 0879                _distinguishedNameCache.TryAdd(distinguishedName, principal);
 0880                return (true, principal);
 881            }
 882
 0883            try {
 0884                using (var ctx = new PrincipalContext(ContextType.Domain)) {
 0885                    var lookupPrincipal =
 0886                        Principal.FindByIdentity(ctx, IdentityType.DistinguishedName, distinguishedName);
 0887                    if (lookupPrincipal != null) {
 0888                        var entry = ((DirectoryEntry)lookupPrincipal.GetUnderlyingObject()).ToDirectoryObject();
 0889                        if (entry.GetObjectIdentifier(out var identifier) && entry.GetLabel(out var label)) {
 0890                            if (await GetWellKnownPrincipal(identifier, domain) is (true, var wellKnownPrincipal)) {
 0891                                _distinguishedNameCache.TryAdd(distinguishedName, wellKnownPrincipal);
 0892                                return (true, wellKnownPrincipal);
 893                            }
 894
 0895                            principal = new TypedPrincipal(identifier, label);
 0896                            _distinguishedNameCache.TryAdd(distinguishedName, principal);
 0897                            return (true, new TypedPrincipal(identifier, label));
 898                        }
 0899                    }
 900
 0901                    return (false, default);
 902                }
 0903            } catch {
 0904                _unresolvablePrincipals.Add(distinguishedName);
 0905                return (false, default);
 906            }
 907
 0908        }
 909
 0910        public async Task<(bool Success, string DSHeuristics)> GetDSHueristics(string domain, string dn) {
 0911            var configPath = CommonPaths.CreateDNPath(CommonPaths.DirectoryServicePath, dn);
 0912            var queryParameters = new LdapQueryParameters {
 0913                Attributes = new[] { LDAPProperties.DSHeuristics },
 0914                SearchScope = SearchScope.Base,
 0915                DomainName = domain,
 0916                LDAPFilter = new LdapFilter().AddAllObjects().GetFilter(),
 0917                NamingContext = NamingContext.Configuration,
 0918                SearchBase = configPath
 0919            };
 920
 0921            var result = await Query(queryParameters).DefaultIfEmpty(LdapResult<IDirectoryObject>.Fail())
 0922                .FirstOrDefaultAsync();
 0923            if (result.IsSuccess &&
 0924                result.Value.TryGetProperty(LDAPProperties.DSHeuristics, out var dsh)) {
 0925                return (true, dsh);
 926            }
 927
 0928            return (false, null);
 0929        }
 930
 3931        public void AddDomainController(string domainControllerSID) {
 3932            _domainControllers.Add(domainControllerSID);
 3933        }
 934
 1935        public async IAsyncEnumerable<OutputBase> GetWellKnownPrincipalOutput() {
 3936            foreach (var wkp in SeenWellKnownPrincipals) {
 0937                WellKnownPrincipal.GetWellKnownPrincipal(wkp.Value.WkpId, out var principal);
 0938                OutputBase output = principal.ObjectType switch {
 0939                    Label.User => new User(),
 0940                    Label.Computer => new Computer(),
 0941                    Label.Group => new Group(),
 0942                    Label.GPO => new GPO(),
 0943                    Label.Domain => new OutputTypes.Domain(),
 0944                    Label.OU => new OU(),
 0945                    Label.Container => new Container(),
 0946                    Label.Configuration => new Container(),
 0947                    _ => throw new ArgumentOutOfRangeException()
 0948                };
 949
 0950                output.Properties.Add("name", $"{principal.ObjectIdentifier}@{wkp.Value.DomainName}".ToUpper());
 0951                if (await GetDomainSidFromDomainName(wkp.Value.DomainName) is (true, var sid)) {
 0952                    output.Properties.Add("domainsid", sid);
 0953                }
 954
 0955                output.Properties.Add("domain", wkp.Value.DomainName.ToUpper());
 0956                output.ObjectIdentifier = wkp.Key;
 0957                yield return output;
 0958            }
 959
 6960            await foreach (var entdc in GetEnterpriseDCGroups()) {
 1961                yield return entdc;
 1962            }
 1963        }
 964
 1965        private async IAsyncEnumerable<Group> GetEnterpriseDCGroups() {
 1966            var grouped = new ConcurrentDictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
 1967            var forestSidToName = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
 7968            foreach (var domainSid in _domainControllers.Values().GroupBy(x =>
 6969                         new SecurityIdentifier(x).AccountDomainSid.Value)) {
 2970                if (await GetDomainNameFromSid(domainSid.Key) is (true, var domainName) &&
 2971                    await GetForest(domainName) is (true, var forestName) &&
 4972                    await GetDomainSidFromDomainName(forestName) is (true, var forestDomainSid)) {
 2973                    forestSidToName.TryAdd(forestDomainSid, forestName);
 3974                    if (!grouped.ContainsKey(forestDomainSid)) {
 1975                        grouped[forestDomainSid] = [];
 1976                    }
 977
 15978                    foreach (var k in domainSid) {
 3979                        grouped[forestDomainSid].Add(k);
 3980                    }
 2981                }
 2982            }
 983
 6984            foreach (var f in grouped) {
 1985                if (!forestSidToName.TryGetValue(f.Key, out var forestName)) {
 0986                    continue;
 987                }
 1988                var group = new Group { ObjectIdentifier = $"{forestName}-S-1-5-9" };
 1989                group.Properties.Add("name", $"ENTERPRISE DOMAIN CONTROLLERS@{forestName}".ToUpper());
 1990                group.Properties.Add("domainsid", f.Key);
 1991                group.Properties.Add("domain", forestName);
 4992                group.Members = f.Value.Select(x => new TypedPrincipal(x, Label.Computer)).ToArray();
 1993                yield return group;
 1994            }
 1995        }
 996
 0997        public void SetLdapConfig(LdapConfig config) {
 0998            _ldapConfig = config;
 0999            _log.LogInformation("New LDAP Config Set:\n {ConfigString}", config.ToString());
 01000            _connectionPool.Dispose();
 01001            _connectionPool = new ConnectionPoolManager(_ldapConfig, scanner: _portScanner);
 01002        }
 1003
 01004        public Task<(bool Success, string Message)> TestLdapConnection(string domain) {
 01005            return _connectionPool.TestDomainConnection(domain, false);
 01006        }
 1007
 01008        public async Task<(bool Success, string Path)> GetNamingContextPath(string domain, NamingContext context) {
 01009            if (await _connectionPool.GetLdapConnection(domain, false) is (true, var wrapper, _)) {
 01010                _connectionPool.ReleaseConnection(wrapper);
 01011                if (wrapper.GetSearchBase(context, out var searchBase)) {
 01012                    return (true, searchBase);
 1013                }
 01014            }
 1015
 01016            var property = context switch {
 01017                NamingContext.Default => LDAPProperties.DefaultNamingContext,
 01018                NamingContext.Configuration => LDAPProperties.ConfigurationNamingContext,
 01019                NamingContext.Schema => LDAPProperties.SchemaNamingContext,
 01020                _ => throw new ArgumentOutOfRangeException(nameof(context), context, null)
 01021            };
 1022
 01023            try {
 01024                var entry = CreateDirectoryEntry($"LDAP://{domain}/RootDSE");
 01025                if (entry.TryGetProperty(property, out var searchBase)) {
 01026                    return (true, searchBase);
 1027                }
 01028            } catch {
 1029                //pass
 01030            }
 1031
 01032            if (GetDomain(domain, out var domainObj)) {
 01033                try {
 01034                    var entry = domainObj.GetDirectoryEntry().ToDirectoryObject();
 01035                    if (entry.TryGetProperty(property, out var searchBase)) {
 01036                        return (true, searchBase);
 1037                    }
 01038                } catch {
 1039                    //pass
 01040                }
 1041
 01042                var name = domainObj.Name;
 01043                if (!string.IsNullOrWhiteSpace(name)) {
 01044                    var tempPath = Helpers.DomainNameToDistinguishedName(name);
 1045
 01046                    var searchBase = context switch {
 01047                        NamingContext.Configuration => $"CN=Configuration,{tempPath}",
 01048                        NamingContext.Schema => $"CN=Schema,CN=Configuration,{tempPath}",
 01049                        NamingContext.Default => tempPath,
 01050                        _ => throw new ArgumentOutOfRangeException()
 01051                    };
 1052
 01053                    return (true, searchBase);
 1054                }
 01055            }
 1056
 01057            return (false, default);
 01058        }
 1059
 01060        public void ResetUtils() {
 01061            _unresolvablePrincipals = new ConcurrentHashSet(StringComparer.OrdinalIgnoreCase);
 01062            _domainCache = new ConcurrentDictionary<string, Domain>();
 01063            _domainControllers = new ConcurrentHashSet(StringComparer.OrdinalIgnoreCase);
 01064            _connectionPool?.Dispose();
 01065            _connectionPool = new ConnectionPoolManager(_ldapConfig, scanner: _portScanner);
 01066        }
 1067
 01068        private IDirectoryObject CreateDirectoryEntry(string path) {
 01069            if (_ldapConfig.Username != null) {
 01070                return new DirectoryEntry(path, _ldapConfig.Username, _ldapConfig.Password).ToDirectoryObject();
 1071            }
 1072
 01073            return new DirectoryEntry(path).ToDirectoryObject();
 01074        }
 1075
 01076        public void Dispose() {
 01077            _connectionPool?.Dispose();
 01078        }
 1079
 1080        internal static bool ResolveLabel(string objectIdentifier, string distinguishedName, string samAccountType,
 271081            string[] objectClasses, int flags, out Label type) {
 271082            type = Label.Base;
 271083            if (objectIdentifier != null &&
 281084                WellKnownPrincipal.GetWellKnownPrincipal(objectIdentifier, out var principal)) {
 11085                type = principal.ObjectType;
 11086                return true;
 1087            }
 1088
 1089            //Override GMSA/MSA account to treat them as users for the graph
 261090            if (objectClasses != null &&
 261091                (objectClasses.Contains(ObjectClass.MSAClass, StringComparer.OrdinalIgnoreCase) ||
 291092                 objectClasses.Contains(ObjectClass.GMSAClass, StringComparer.OrdinalIgnoreCase))) {
 31093                type = Label.User;
 31094                return true;
 1095            }
 1096
 321097            if (samAccountType != null) {
 91098                var objectType = Helpers.SamAccountTypeToType(samAccountType);
 181099                if (objectType != Label.Base) {
 91100                    type = objectType;
 91101                    return true;
 1102                }
 01103            }
 1104
 151105            if (objectClasses == null || objectClasses.Length == 0) {
 11106                type = Label.Base;
 11107                return false;
 1108            }
 1109
 131110            if (objectClasses.Contains(ObjectClass.GroupPolicyContainerClass, StringComparer.OrdinalIgnoreCase))
 11111                type = Label.GPO;
 121112            else if (objectClasses.Contains(ObjectClass.OrganizationalUnitClass, StringComparer.OrdinalIgnoreCase))
 01113                type = Label.OU;
 121114            else if (objectClasses.Contains(ObjectClass.DomainClass, StringComparer.OrdinalIgnoreCase))
 11115                type = Label.Domain;
 111116            else if (objectClasses.Contains(ObjectClass.ContainerClass, StringComparer.OrdinalIgnoreCase))
 11117                type = Label.Container;
 101118            else if (objectClasses.Contains(ObjectClass.ConfigurationClass, StringComparer.OrdinalIgnoreCase))
 11119                type = Label.Configuration;
 91120            else if (objectClasses.Contains(ObjectClass.PKICertificateTemplateClass, StringComparer.OrdinalIgnoreCase))
 11121                type = Label.CertTemplate;
 81122            else if (objectClasses.Contains(ObjectClass.PKIEnrollmentServiceClass, StringComparer.OrdinalIgnoreCase))
 11123                type = Label.EnterpriseCA;
 71124            else if (objectClasses.Contains(ObjectClass.CertificationAuthorityClass,
 111125                         StringComparer.OrdinalIgnoreCase)) {
 41126                if (distinguishedName.IndexOf(DirectoryPaths.RootCALocation, StringComparison.OrdinalIgnoreCase) >= 0)
 11127                    type = Label.RootCA;
 41128                if (distinguishedName.IndexOf(DirectoryPaths.AIACALocation, StringComparison.OrdinalIgnoreCase) >= 0)
 11129                    type = Label.AIACA;
 41130                if (distinguishedName.IndexOf(DirectoryPaths.NTAuthStoreLocation, StringComparison.OrdinalIgnoreCase) >=
 41131                    0)
 21132                    type = Label.NTAuthStore;
 91133            } else if (objectClasses.Contains(ObjectClass.OIDContainerClass, StringComparer.OrdinalIgnoreCase)) {
 21134                if (distinguishedName.StartsWith(DirectoryPaths.OIDContainerLocation,
 21135                        StringComparison.OrdinalIgnoreCase))
 11136                    type = Label.Container;
 21137                else if (flags == 2) {
 11138                    type = Label.IssuancePolicy;
 11139                }
 21140            }
 1141
 131142            return type != Label.Base;
 271143        }
 1144
 1145        public static async Task<(bool Success, ResolvedSearchResult ResolvedResult)> ResolveSearchResult(
 81146            IDirectoryObject directoryObject, ILdapUtils utils) {
 91147            if (!directoryObject.GetObjectIdentifier(out var objectIdentifier)) {
 11148                return (false, default);
 1149            }
 1150
 71151            var res = new ResolvedSearchResult {
 71152                ObjectId = objectIdentifier
 71153            };
 1154
 1155            //If the object is deleted, we can short circuit the rest of this logic as we don't really care about anythi
 81156            if (directoryObject.IsDeleted()) {
 11157                res.Deleted = true;
 11158                return (true, res);
 1159            }
 1160
 111161            if (directoryObject.TryGetLongProperty(LDAPProperties.UserAccountControl, out var rawUac)) {
 51162                var flags = (UacFlags)rawUac;
 101163                if (flags.HasFlag(UacFlags.ServerTrustAccount)) {
 51164                    res.IsDomainController = true;
 51165                    utils.AddDomainController(objectIdentifier);
 51166                }
 51167            }
 1168
 1169            string domain;
 1170
 81171            if (directoryObject.TryGetDistinguishedName(out var distinguishedName)) {
 21172                domain = Helpers.DistinguishedNameToDomain(distinguishedName);
 61173            } else {
 41174                if (objectIdentifier.StartsWith("S-1-5") &&
 81175                    await utils.GetDomainNameFromSid(objectIdentifier) is (true, var domainName)) {
 41176                    domain = domainName;
 41177                } else {
 01178                    return (false, default);
 1179                }
 41180            }
 1181
 1182            string domainSid;
 61183            var match = SIDRegex.Match(objectIdentifier);
 121184            if (match.Success) {
 61185                domainSid = match.Groups[1].Value;
 61186            } else if (await utils.GetDomainSidFromDomainName(domain) is (true, var sid)) {
 01187                domainSid = sid;
 01188            } else {
 01189                Logging.Logger.LogWarning("Failed to resolve domain sid for object {Identifier}", objectIdentifier);
 01190                domainSid = null;
 01191            }
 1192
 61193            res.Domain = domain;
 61194            res.DomainSid = domainSid;
 1195
 61196            if (WellKnownPrincipal.GetWellKnownPrincipal(objectIdentifier, out var wellKnownPrincipal)) {
 01197                res.DisplayName = $"{wellKnownPrincipal.ObjectIdentifier}@{domain}";
 01198                res.ObjectType = wellKnownPrincipal.ObjectType;
 01199                if (await utils.GetWellKnownPrincipal(objectIdentifier, domain) is (true, var convertedPrincipal)) {
 01200                    res.ObjectId = convertedPrincipal.ObjectIdentifier;
 01201                }
 1202
 01203                return (true, res);
 1204            }
 1205
 61206            if (!directoryObject.GetLabel(out var label)) {
 01207                if (await utils.ResolveIDAndType(objectIdentifier, domain) is (true, var typedPrincipal)) {
 01208                    label = typedPrincipal.ObjectType;
 01209                }
 01210            }
 1211
 71212            if (directoryObject.IsMSA() || directoryObject.IsGMSA()) {
 11213                label = Label.User;
 11214            }
 1215
 61216            res.ObjectType = label;
 1217
 61218            directoryObject.TryGetProperty(LDAPProperties.SAMAccountName, out var samAccountName);
 1219
 61220            switch (label) {
 1221                case Label.User:
 1222                case Label.Group:
 1223                case Label.Base:
 11224                    res.DisplayName = $"{samAccountName}@{domain}";
 11225                    break;
 51226                case Label.Computer: {
 51227                    var shortName = samAccountName?.TrimEnd('$');
 71228                    if (directoryObject.TryGetProperty(LDAPProperties.DNSHostName, out var dns)) {
 21229                        res.DisplayName = dns;
 51230                    } else if (!string.IsNullOrWhiteSpace(shortName)) {
 01231                        res.DisplayName = $"{shortName}.{domain}";
 31232                    } else if (directoryObject.TryGetProperty(LDAPProperties.CanonicalName,
 41233                                   out var canonicalName)) {
 11234                        res.DisplayName = $"{canonicalName}.{domain}";
 41235                    } else if (directoryObject.TryGetProperty(LDAPProperties.Name, out var name)) {
 11236                        res.DisplayName = $"{name}.{domain}";
 21237                    } else {
 11238                        res.DisplayName = $"UNKNOWN.{domain}";
 11239                    }
 1240
 51241                    break;
 1242                }
 1243                case Label.GPO:
 01244                case Label.IssuancePolicy: {
 01245                    if (directoryObject.TryGetProperty(LDAPProperties.DisplayName, out var displayName)) {
 01246                        res.DisplayName = $"{displayName}@{domain}";
 01247                    } else if (directoryObject.TryGetProperty(LDAPProperties.CanonicalName,
 01248                                   out var canonicalName)) {
 01249                        res.DisplayName = $"{canonicalName}@{domain}";
 01250                    } else {
 01251                        res.DisplayName = $"UNKNOWN@{domain}";
 01252                    }
 1253
 01254                    break;
 1255                }
 1256                case Label.Domain:
 01257                    res.DisplayName = domain;
 01258                    break;
 01259                case Label.OU: {
 01260                    if (directoryObject.TryGetProperty(LDAPProperties.Name, out var name)) {
 01261                        res.DisplayName = $"{name}@{domain}";
 01262                    } else if (directoryObject.TryGetProperty(LDAPProperties.OU, out var ou)) {
 01263                        res.DisplayName = $"{ou}@{domain}";
 01264                    } else {
 01265                        res.DisplayName = $"UNKNOWN@{domain}";
 01266                    }
 1267
 01268                    break;
 1269                }
 01270                case Label.Container: {
 01271                    if (directoryObject.TryGetProperty(LDAPProperties.Name, out var name)) {
 01272                        res.DisplayName = $"{name}@{domain}";
 01273                    } else if (directoryObject.TryGetProperty(LDAPProperties.CanonicalName,
 01274                                   out var canonicalName)) {
 01275                        res.DisplayName = $"{canonicalName}@{domain}";
 01276                    } else {
 01277                        res.DisplayName = $"UNKNOWN@{domain}";
 01278                    }
 1279
 01280                    break;
 1281                }
 1282                case Label.Configuration:
 1283                case Label.RootCA:
 1284                case Label.AIACA:
 1285                case Label.NTAuthStore:
 1286                case Label.EnterpriseCA:
 01287                case Label.CertTemplate: {
 01288                    if (directoryObject.TryGetProperty(LDAPProperties.Name, out var name)) {
 01289                        res.DisplayName = $"{name}@{domain}";
 01290                    } else {
 01291                        res.DisplayName = $"UNKNOWN@{domain}";
 01292                    }
 1293
 01294                    break;
 1295                }
 1296                default:
 01297                    throw new ArgumentOutOfRangeException();
 1298            }
 1299
 61300            res.DisplayName = res.DisplayName.ToUpper();
 61301            return (true, res);
 81302        }
 1303    }
 1304}

Methods/Properties

.cctor()
.ctor()
DomainName()
WkpId()
.ctor(SharpHoundCommonLib.NativeMethods,SharpHoundCommonLib.Processors.PortScanner,Microsoft.Extensions.Logging.ILogger)
RangedRetrieval(System.String,System.String,System.Threading.CancellationToken)
Query(SharpHoundCommonLib.LdapQueryParameters,System.Threading.CancellationToken)
PagedQuery(SharpHoundCommonLib.LdapQueryParameters,System.Threading.CancellationToken)
ResolveIDAndType()
ResolveIDAndType()
LookupSidType()
LookupGuidType()
GetWellKnownPrincipal()
GetWellKnownPrincipalObjectIdentifier()
GetForest()
GetForestFromLdap()
GetDomainNameFromSid()
ConvertDomainSidToDomainNameFromLdap()
GetDomainSidFromDomainName()
GetDomain(System.String,System.DirectoryServices.ActiveDirectory.Domain&)
GetDomain(System.String,SharpHoundCommonLib.LdapConfig,System.DirectoryServices.ActiveDirectory.Domain&)
GetDomain(System.DirectoryServices.ActiveDirectory.Domain&)
ResolveAccountName()
ResolveHostToSid()
GetWorkstationInfo()
GetGlobalCatalogMatches()
ResolveCertTemplateByProperty()
RequestNETBIOSNameFromComputer(System.String,System.String,System.String&)
MakeSecurityDescriptor()
ConvertLocalWellKnownPrincipal()
IsDomainController()
ResolveDistinguishedName()
GetDSHueristics()
AddDomainController(System.String)
GetWellKnownPrincipalOutput()
GetEnterpriseDCGroups()
SetLdapConfig(SharpHoundCommonLib.LdapConfig)
TestLdapConnection(System.String)
GetNamingContextPath()
ResetUtils()
CreateDirectoryEntry(System.String)
Dispose()
ResolveLabel(System.String,System.String,System.String,System.String[],System.Int32,SharpHoundCommonLib.Enums.Label&)
ResolveSearchResult()