< Summary

Class:SharpHoundCommonLib.LDAPUtils
Assembly:SharpHoundCommonLib
File(s):D:\a\SharpHoundCommon\SharpHoundCommon\src\CommonLib\LDAPUtils.cs
Covered lines:189
Uncovered lines:752
Coverable lines:941
Total lines:1729
Line coverage:20% (189 of 941)
Covered branches:58
Total branches:385
Branch coverage:15% (58 of 385)

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.cctor()100%10100%
.ctor()100%10100%
.ctor(...)0%600%
SetLDAPConfig(...)0%600%
GetWellKnownPrincipal(...)87.5%80100%
AddDomainController(...)100%100%
GetWellKnownPrincipalOutput()0%1300%
ConvertWellKnownPrincipal(...)60%100100%
GetUserGlobalCatalogMatches(...)50%2090%
ResolveIDAndType(...)50%6075%
ResolveCertTemplateByProperty(...)0%600%
LookupSidType(...)0%600%
LookupGuidType(...)0%600%
GetDomainNameFromSid(...)0%800%
GetSidFromDomainName(...)0%800%
DoRangedRetrieval()0%1600%
ResolveHostToSid()0%4200%
ResolveAccountName(...)0%1000%
ResolveDistinguishedName(...)0%1000%
QueryLDAP(...)100%10100%
QueryLDAP()8.82%3409.57%
CreateNewConnection(...)0%200%
QueryLDAP(...)100%10100%
GetNextBackoff(...)100%100%
GetForest(...)33.33%6080%
MakeSecurityDescriptor()100%100%
BuildLdapPath(...)100%40100%
TestLDAPConfig(...)0%800%
GetDomain(...)60%10073.91%
SetupLDAPQueryFilter(...)22.22%18024.56%
GetBaseEnterpriseDC(...)0%600%
UpdateLDAPConfig(...)100%100%
GetDomainNameFromSidLdap(...)0%600%
RequestNETBIOSNameFromComputer(...)0%800%
GetWorkstationInfo()0%600%
CreateSearchRequest(...)0%1200%
CreateGlobalCatalogConnection()12.5%16027.77%
CreateLDAPConnection()9.09%22024.39%
GetUsableDomainController()0%1600%
NormalizeDomainName(...)0%600%
ResolveDomainNetbiosToDns(...)0%600%
GetDomainRangeSize(...)75%16083.33%
DomainNameToDistinguishedName(...)100%60100%
GetConfigurationPath(...)0%600%
GetSchemaPath(...)0%600%
IsDomainController(...)0%200%

File(s)

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

#LineLine coverage
 1using System;
 2using System.Collections.Concurrent;
 3using System.Collections.Generic;
 4using System.Diagnostics;
 5using System.DirectoryServices;
 6using System.DirectoryServices.ActiveDirectory;
 7using System.DirectoryServices.Protocols;
 8using System.Linq;
 9using System.Net;
 10using System.Net.Sockets;
 11using System.Security.Principal;
 12using System.Text;
 13using System.Threading;
 14using System.Threading.Tasks;
 15using Microsoft.Extensions.Logging;
 16using SharpHoundCommonLib.Enums;
 17using SharpHoundCommonLib.Exceptions;
 18using SharpHoundCommonLib.LDAPQueries;
 19using SharpHoundCommonLib.OutputTypes;
 20using SharpHoundCommonLib.Processors;
 21using SharpHoundRPC.NetAPINative;
 22using Domain = System.DirectoryServices.ActiveDirectory.Domain;
 23using SearchScope = System.DirectoryServices.Protocols.SearchScope;
 24using SecurityMasks = System.DirectoryServices.Protocols.SecurityMasks;
 25
 26namespace SharpHoundCommonLib
 27{
 28    public class LDAPUtils : ILDAPUtils
 29    {
 30        private const string NullCacheKey = "UNIQUENULL";
 31
 32        // The following byte stream contains the necessary message to request a NetBios name from a machine
 33        // http://web.archive.org/web/20100409111218/http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.a
 134        private static readonly byte[] NameRequest =
 135        {
 136            0x80, 0x94, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
 137            0x00, 0x00, 0x00, 0x00, 0x20, 0x43, 0x4b, 0x41,
 138            0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
 139            0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
 140            0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
 141            0x41, 0x41, 0x41, 0x41, 0x41, 0x00, 0x00, 0x21,
 142            0x00, 0x01
 143        };
 44
 45
 146        private static readonly ConcurrentDictionary<string, ResolvedWellKnownPrincipal>
 147            SeenWellKnownPrincipals = new();
 48
 149        private static readonly ConcurrentDictionary<string, byte> DomainControllers = new();
 50
 6451        private readonly ConcurrentDictionary<string, Domain> _domainCache = new();
 6452        private readonly ConcurrentDictionary<string, string> _domainControllerCache = new();
 153        private static readonly TimeSpan MinBackoffDelay = TimeSpan.FromSeconds(2);
 154        private static readonly TimeSpan MaxBackoffDelay = TimeSpan.FromSeconds(20);
 55        private const int BackoffDelayMultiplier = 2;
 56        private const int MaxRetries = 3;
 57
 6458        private readonly ConcurrentDictionary<string, LdapConnection> _globalCatalogConnections = new();
 6459        private readonly ConcurrentDictionary<string, string> _hostResolutionMap = new();
 6460        private readonly ConcurrentDictionary<string, LdapConnection> _ldapConnections = new();
 6461        private readonly ConcurrentDictionary<string, int> _ldapRangeSizeCache = new();
 62        private readonly ILogger _log;
 63        private readonly NativeMethods _nativeMethods;
 6464        private readonly ConcurrentDictionary<string, string> _netbiosCache = new();
 65        private readonly PortScanner _portScanner;
 6466        private LDAPConfig _ldapConfig = new();
 6467        private readonly ManualResetEvent _connectionResetEvent = new(false);
 6468        private readonly object _lockObj = new();
 69
 70
 71        /// <summary>
 72        ///     Creates a new instance of LDAP Utils with defaults
 73        /// </summary>
 6474        public LDAPUtils()
 6475        {
 6476            _nativeMethods = new NativeMethods();
 6477            _portScanner = new PortScanner();
 6478            _log = Logging.LogProvider.CreateLogger("LDAPUtils");
 6479        }
 80
 81        /// <summary>
 82        ///     Creates a new instance of LDAP utils and allows overriding implementations
 83        /// </summary>
 84        /// <param name="nativeMethods"></param>
 85        /// <param name="scanner"></param>
 86        /// <param name="log"></param>
 087        public LDAPUtils(NativeMethods nativeMethods = null, PortScanner scanner = null, ILogger log = null)
 088        {
 089            _nativeMethods = nativeMethods ?? new NativeMethods();
 090            _portScanner = scanner ?? new PortScanner();
 091            _log = log ?? Logging.LogProvider.CreateLogger("LDAPUtils");
 092        }
 93
 94        /// <summary>
 95        ///     Sets the configuration for LDAP queries
 96        /// </summary>
 97        /// <param name="config"></param>
 98        /// <exception cref="Exception"></exception>
 99        public void SetLDAPConfig(LDAPConfig config)
 0100        {
 0101            _ldapConfig = config ?? throw new Exception("LDAP Configuration can not be null");
 0102            _domainControllerCache.Clear();
 0103            foreach (var kv in _globalCatalogConnections)
 0104            {
 0105                kv.Value.Dispose();
 0106            }
 107
 0108            _globalCatalogConnections.Clear();
 0109            foreach (var kv in _ldapConnections)
 0110            {
 0111                kv.Value.Dispose();
 0112            }
 113
 0114            _ldapConnections.Clear();
 0115        }
 116
 117        /// <summary>
 118        ///     Turns a sid into a well known principal ID.
 119        /// </summary>
 120        /// <param name="sid"></param>
 121        /// <param name="domain"></param>
 122        /// <param name="commonPrincipal"></param>
 123        /// <returns>True if a well known principal was identified, false if not</returns>
 124        public bool GetWellKnownPrincipal(string sid, string domain, out TypedPrincipal commonPrincipal)
 4125        {
 5126            if (!WellKnownPrincipal.GetWellKnownPrincipal(sid, out commonPrincipal)) return false;
 3127            var tempDomain = domain ?? GetDomain()?.Name ?? "UNKNOWN";
 3128            commonPrincipal.ObjectIdentifier = ConvertWellKnownPrincipal(sid, tempDomain);
 3129            SeenWellKnownPrincipals.TryAdd(commonPrincipal.ObjectIdentifier, new ResolvedWellKnownPrincipal
 3130            {
 3131                DomainName = domain,
 3132                WkpId = sid
 3133            });
 3134            return true;
 4135        }
 136
 137        /// <summary>
 138        ///     Adds a SID to an internal list of domain controllers
 139        /// </summary>
 140        /// <param name="domainControllerSID"></param>
 141        public void AddDomainController(string domainControllerSID)
 0142        {
 0143            DomainControllers.TryAdd(domainControllerSID, new byte());
 0144        }
 145
 146        /// <summary>
 147        ///     Gets output objects for currently observed well known principals
 148        /// </summary>
 149        /// <returns></returns>
 150        /// <exception cref="ArgumentOutOfRangeException"></exception>
 151        public IEnumerable<OutputBase> GetWellKnownPrincipalOutput(string domain)
 0152        {
 0153            foreach (var wkp in SeenWellKnownPrincipals)
 0154            {
 0155                WellKnownPrincipal.GetWellKnownPrincipal(wkp.Value.WkpId, out var principal);
 0156                OutputBase output = principal.ObjectType switch
 0157                {
 0158                    Label.User => new User(),
 0159                    Label.Computer => new Computer(),
 0160                    Label.Group => new Group(),
 0161                    Label.GPO => new GPO(),
 0162                    Label.Domain => new OutputTypes.Domain(),
 0163                    Label.OU => new OU(),
 0164                    Label.Container => new Container(),
 0165                    Label.Configuration => new Container(),
 0166                    _ => throw new ArgumentOutOfRangeException()
 0167                };
 168
 0169                output.Properties.Add("name", $"{principal.ObjectIdentifier}@{wkp.Value.DomainName}".ToUpper());
 0170                var domainSid = GetSidFromDomainName(wkp.Value.DomainName);
 0171                output.Properties.Add("domainsid", domainSid);
 0172                output.Properties.Add("domain", wkp.Value.DomainName.ToUpper());
 0173                output.ObjectIdentifier = wkp.Key;
 0174                yield return output;
 0175            }
 176
 0177            var entdc = GetBaseEnterpriseDC(domain);
 0178            entdc.Members = DomainControllers.Select(x => new TypedPrincipal(x.Key, Label.Computer)).ToArray();
 0179            yield return entdc;
 0180        }
 181
 182        /// <summary>
 183        ///     Converts a
 184        /// </summary>
 185        /// <param name="sid"></param>
 186        /// <param name="domain"></param>
 187        /// <returns></returns>
 188        public string ConvertWellKnownPrincipal(string sid, string domain)
 3189        {
 3190            if (!WellKnownPrincipal.GetWellKnownPrincipal(sid, out _)) return sid;
 191
 5192            if (sid != "S-1-5-9") return $"{domain}-{sid}".ToUpper();
 193
 1194            var forest = GetForest(domain)?.Name;
 1195            if (forest == null) _log.LogWarning("Error getting forest, ENTDC sid is likely incorrect");
 1196            return $"{forest ?? "UNKNOWN"}-{sid}".ToUpper();
 3197        }
 198
 199        /// <summary>
 200        ///     Queries the global catalog to get potential SID matches for a username in the forest
 201        /// </summary>
 202        /// <param name="name"></param>
 203        /// <returns></returns>
 204        public string[] GetUserGlobalCatalogMatches(string name)
 1205        {
 1206            var tempName = name.ToLower();
 1207            if (Cache.GetGCCache(tempName, out var sids))
 0208                return sids;
 209
 1210            var query = new LDAPFilter().AddUsers($"samaccountname={tempName}").GetFilter();
 1211            var results = QueryLDAP(query, SearchScope.Subtree, new[] { "objectsid" }, globalCatalog: true)
 1212                .Select(x => x.GetSid()).Where(x => x != null).ToArray();
 1213            Cache.AddGCCache(tempName, results);
 1214            return results;
 1215        }
 216
 217        /// <summary>
 218        ///     Uses an LDAP lookup to attempt to find the Label for a given SID
 219        ///     Will also convert to a well known principal ID if needed
 220        /// </summary>
 221        /// <param name="id"></param>
 222        /// <param name="fallbackDomain"></param>
 223        /// <returns></returns>
 224        public TypedPrincipal ResolveIDAndType(string id, string fallbackDomain)
 2225        {
 226            //This is a duplicated SID object which is weird and makes things unhappy. Throw it out
 2227            if (id.Contains("0ACNF"))
 1228                return null;
 229
 1230            if (GetWellKnownPrincipal(id, fallbackDomain, out var principal))
 1231                return principal;
 232
 0233            var type = id.StartsWith("S-") ? LookupSidType(id, fallbackDomain) : LookupGuidType(id, fallbackDomain);
 0234            return new TypedPrincipal(id, type);
 2235        }
 236
 237        public TypedPrincipal ResolveCertTemplateByProperty(string propValue, string propertyName, string containerDN,
 238            string domainName)
 0239        {
 0240            var filter = new LDAPFilter().AddCertificateTemplates().AddFilter(propertyName + "=" + propValue, true);
 0241            var res = QueryLDAP(filter.GetFilter(), SearchScope.OneLevel,
 0242                CommonProperties.TypeResolutionProps, adsPath: containerDN, domainName: domainName);
 243
 0244            if (res == null)
 0245            {
 0246                _log.LogWarning(
 0247                    "Could not find certificate template with '{propertyName}:{propValue}' under {containerDN}; null res
 0248                    propertyName, propValue, containerDN);
 0249                return null;
 250            }
 251
 0252            List<ISearchResultEntry> resList = new List<ISearchResultEntry>(res);
 0253            if (resList.Count == 0)
 0254            {
 0255                _log.LogWarning(
 0256                    "Could not find certificate template with '{propertyName}:{propValue}' under {containerDN}; empty li
 0257                    propertyName, propValue, containerDN);
 0258                return null;
 259            }
 260
 0261            if (resList.Count > 1)
 0262            {
 0263                _log.LogWarning(
 0264                    "Found more than one certificate template with '{propertyName}:{propValue}' under {containerDN}",
 0265                    propertyName, propValue, containerDN);
 0266                return null;
 267            }
 268
 0269            ISearchResultEntry searchResultEntry = resList.FirstOrDefault();
 0270            return new TypedPrincipal(searchResultEntry.GetGuid(), Label.CertTemplate);
 0271        }
 272
 273        /// <summary>
 274        ///     Attempts to lookup the Label for a sid
 275        /// </summary>
 276        /// <param name="sid"></param>
 277        /// <param name="domain"></param>
 278        /// <returns></returns>
 279        public Label LookupSidType(string sid, string domain)
 0280        {
 0281            if (Cache.GetIDType(sid, out var type))
 0282                return type;
 283
 0284            var rDomain = GetDomainNameFromSid(sid) ?? domain;
 285
 0286            var result =
 0287                QueryLDAP(CommonFilters.SpecificSID(sid), SearchScope.Subtree, CommonProperties.TypeResolutionProps,
 0288                        rDomain)
 0289                    .DefaultIfEmpty(null).FirstOrDefault();
 290
 0291            type = result?.GetLabel() ?? Label.Base;
 0292            Cache.AddType(sid, type);
 0293            return type;
 0294        }
 295
 296        /// <summary>
 297        ///     Attempts to lookup the Label for a GUID
 298        /// </summary>
 299        /// <param name="guid"></param>
 300        /// <param name="domain"></param>
 301        /// <returns></returns>
 302        public Label LookupGuidType(string guid, string domain)
 0303        {
 0304            if (Cache.GetIDType(guid, out var type))
 0305                return type;
 306
 0307            var hex = Helpers.ConvertGuidToHexGuid(guid);
 0308            if (hex == null)
 0309                return Label.Base;
 310
 0311            var result =
 0312                QueryLDAP($"(objectguid={hex})", SearchScope.Subtree, CommonProperties.TypeResolutionProps, domain)
 0313                    .DefaultIfEmpty(null).FirstOrDefault();
 314
 0315            type = result?.GetLabel() ?? Label.Base;
 0316            Cache.AddType(guid, type);
 0317            return type;
 0318        }
 319
 320        /// <summary>
 321        ///     Attempts to find the domain associated with a SID
 322        /// </summary>
 323        /// <param name="sid"></param>
 324        /// <returns></returns>
 325        public string GetDomainNameFromSid(string sid)
 0326        {
 327            try
 0328            {
 0329                var parsedSid = new SecurityIdentifier(sid);
 0330                var domainSid = parsedSid.AccountDomainSid?.Value.ToUpper();
 0331                if (domainSid == null)
 0332                    return null;
 333
 0334                _log.LogDebug("Resolving sid {DomainSid}", domainSid);
 335
 0336                if (Cache.GetDomainSidMapping(domainSid, out var domain))
 0337                    return domain;
 338
 0339                _log.LogDebug("No cache hit for {DomainSid}", domainSid);
 0340                domain = GetDomainNameFromSidLdap(domainSid);
 0341                _log.LogDebug("Resolved to {Domain}", domain);
 342
 343                //Cache both to and from so we can use this later
 0344                if (domain != null)
 0345                {
 0346                    Cache.AddSidToDomain(domainSid, domain);
 0347                    Cache.AddSidToDomain(domain, domainSid);
 0348                }
 349
 0350                return domain;
 351            }
 0352            catch
 0353            {
 0354                return null;
 355            }
 0356        }
 357
 358        /// <summary>
 359        ///     Attempts to get the SID associated with a domain name
 360        /// </summary>
 361        /// <param name="domainName"></param>
 362        /// <returns></returns>
 363        public string GetSidFromDomainName(string domainName)
 0364        {
 0365            var tempDomainName = NormalizeDomainName(domainName);
 0366            if (tempDomainName == null)
 0367                return null;
 0368            if (Cache.GetDomainSidMapping(tempDomainName, out var sid)) return sid;
 369
 0370            var domainObj = GetDomain(tempDomainName);
 371
 0372            if (domainObj != null)
 0373                sid = domainObj.GetDirectoryEntry().GetSid();
 374            else
 0375                sid = null;
 376
 0377            if (sid != null)
 0378            {
 0379                Cache.AddSidToDomain(sid, tempDomainName);
 0380                Cache.AddSidToDomain(tempDomainName, sid);
 0381            }
 382
 0383            return sid;
 0384        }
 385
 386        // Saving this code for an eventual async implementation
 387        // public async IAsyncEnumerable<string> DoRangedRetrievalAsync(string distinguishedName, string attributeName)
 388        // {
 389        //     var domainName = Helpers.DistinguishedNameToDomain(distinguishedName);
 390        //     LdapConnection conn;
 391        //     try
 392        //     {
 393        //         conn = await CreateLDAPConnection(domainName, authType: _ldapConfig.AuthType);
 394        //     }
 395        //     catch
 396        //     {
 397        //         yield break;
 398        //     }
 399        //
 400        //     if (conn == null)
 401        //         yield break;
 402        //
 403        //     var index = 0;
 404        //     var step = 0;
 405        //     var currentRange = $"{attributeName};range={index}-*";
 406        //     var complete = false;
 407        //
 408        //     var searchRequest = CreateSearchRequest($"{attributeName}=*", SearchScope.Base, new[] {currentRange},
 409        //         domainName, distinguishedName);
 410        //
 411        //     var backoffDelay = MinBackoffDelay;
 412        //     var retryCount = 0;
 413        //
 414        //     while (true)
 415        //     {
 416        //         DirectoryResponse searchResult;
 417        //         try
 418        //         {
 419        //             searchResult = await Task.Factory.FromAsync(conn.BeginSendRequest, conn.EndSendRequest,
 420        //                 searchRequest,
 421        //                 PartialResultProcessing.NoPartialResultSupport, null);
 422        //         }
 423        //         catch (LdapException le) when (le.ErrorCode == 51 && retryCount < MaxRetries)
 424        //         {
 425        //             //Allow three retries with a backoff on each one if we get a "Server is Busy" error
 426        //             retryCount++;
 427        //             await Task.Delay(backoffDelay);
 428        //             backoffDelay = TimeSpan.FromSeconds(Math.Min(
 429        //                 backoffDelay.TotalSeconds * BackoffDelayMultiplier.TotalSeconds, MaxBackoffDelay.TotalSeconds
 430        //             continue;
 431        //         }
 432        //         catch (Exception e)
 433        //         {
 434        //             _log.LogWarning(e,"Caught exception during ranged retrieval for {DN}", distinguishedName);
 435        //             yield break;
 436        //         }
 437        //
 438        //         if (searchResult is SearchResponse response && response.Entries.Count == 1)
 439        //         {
 440        //             var entry = response.Entries[0];
 441        //             var attributeNames = entry?.Attributes?.AttributeNames;
 442        //             if (attributeNames != null)
 443        //             {
 444        //                 foreach (string attr in attributeNames)
 445        //                 {
 446        //                     //Set our current range to the name of the attribute, which will tell us how far we are i
 447        //                     currentRange = attr;
 448        //                     //Check if the string has the * character in it. If it does, we've reached the end of thi
 449        //                     complete = currentRange.IndexOf("*", 0, StringComparison.Ordinal) > 0;
 450        //                     //Set our step to the number of attributes that came back.
 451        //                     step = entry.Attributes[currentRange].Count;
 452        //                 }
 453        //             }
 454        //
 455        //
 456        //             foreach (string val in entry.Attributes[currentRange].GetValues(typeof(string)))
 457        //             {
 458        //                 yield return val;
 459        //                 index++;
 460        //             }
 461        //
 462        //             if (complete) yield break;
 463        //
 464        //             currentRange = $"{attributeName};range={index}-{index + step}";
 465        //             searchRequest.Attributes.Clear();
 466        //             searchRequest.Attributes.Add(currentRange);
 467        //         }
 468        //         else
 469        //         {
 470        //             yield break;
 471        //         }
 472        //     }
 473        // }
 474
 475        /// <summary>
 476        ///     Performs Attribute Ranged Retrieval
 477        ///     https://docs.microsoft.com/en-us/windows/win32/adsi/attribute-range-retrieval
 478        ///     The function self-determines the range and internally handles the maximum step allowed by the server
 479        /// </summary>
 480        /// <param name="distinguishedName"></param>
 481        /// <param name="attributeName"></param>
 482        /// <returns></returns>
 483        public IEnumerable<string> DoRangedRetrieval(string distinguishedName, string attributeName)
 0484        {
 0485            var domainName = Helpers.DistinguishedNameToDomain(distinguishedName);
 0486            var task = Task.Run(() => CreateLDAPConnection(domainName, authType: _ldapConfig.AuthType));
 487
 488            LdapConnection conn;
 489
 490            try
 0491            {
 0492                conn = task.ConfigureAwait(false).GetAwaiter().GetResult();
 0493            }
 0494            catch
 0495            {
 0496                yield break;
 497            }
 498
 0499            if (conn == null)
 0500                yield break;
 501
 0502            var index = 0;
 0503            var step = 0;
 0504            var baseString = $"{attributeName}";
 505            //Example search string: member;range=0-1000
 0506            var currentRange = $"{baseString};range={index}-*";
 0507            var complete = false;
 508
 0509            var searchRequest = CreateSearchRequest($"{attributeName}=*", SearchScope.Base, new[] { currentRange },
 0510                domainName, distinguishedName);
 511
 0512            if (searchRequest == null)
 0513                yield break;
 514
 0515            var backoffDelay = MinBackoffDelay;
 0516            var retryCount = 0;
 517
 0518            while (true)
 0519            {
 520                SearchResponse response;
 521                try
 0522                {
 0523                    response = (SearchResponse)conn.SendRequest(searchRequest);
 0524                }
 0525                catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.Busy && retryCount < MaxRetries)
 0526                {
 527                    //Allow three retries with a backoff on each one if we get a "Server is Busy" error
 0528                    retryCount++;
 0529                    Thread.Sleep(backoffDelay);
 0530                    backoffDelay = GetNextBackoff(retryCount);
 0531                    continue;
 532                }
 0533                catch (Exception e)
 0534                {
 0535                    _log.LogError(e, "Error doing ranged retrieval for {Attribute} on {Dn}", attributeName,
 0536                        distinguishedName);
 0537                    yield break;
 538                }
 539
 540                //If we ever get more than one response from here, something is horribly wrong
 0541                if (response?.Entries.Count == 1)
 0542                {
 0543                    var entry = response.Entries[0];
 544                    //Process the attribute we get back to determine a few things
 0545                    foreach (string attr in entry.Attributes.AttributeNames)
 0546                    {
 547                        //Set our current range to the name of the attribute, which will tell us how far we are in "pagi
 0548                        currentRange = attr;
 549                        //Check if the string has the * character in it. If it does, we've reached the end of this searc
 0550                        complete = currentRange.IndexOf("*", 0, StringComparison.Ordinal) > 0;
 551                        //Set our step to the number of attributes that came back.
 0552                        step = entry.Attributes[currentRange].Count;
 0553                    }
 554
 0555                    foreach (string val in entry.Attributes[currentRange].GetValues(typeof(string)))
 0556                    {
 0557                        yield return val;
 0558                        index++;
 0559                    }
 560
 0561                    if (complete) yield break;
 562
 0563                    currentRange = $"{baseString};range={index}-{index + step}";
 0564                    searchRequest.Attributes.Clear();
 0565                    searchRequest.Attributes.Add(currentRange);
 0566                }
 567                else
 0568                {
 569                    //Something went wrong here.
 0570                    yield break;
 571                }
 0572            }
 573        }
 574
 575        /// <summary>
 576        ///     Takes a host in most applicable forms from AD and attempts to resolve it into a SID.
 577        /// </summary>
 578        /// <param name="hostname"></param>
 579        /// <param name="domain"></param>
 580        /// <returns></returns>
 581        public async Task<string> ResolveHostToSid(string hostname, string domain)
 0582        {
 0583            var strippedHost = Helpers.StripServicePrincipalName(hostname).ToUpper().TrimEnd('$');
 0584            if (string.IsNullOrEmpty(strippedHost))
 0585            {
 0586                return null;
 587            }
 588
 0589            if (_hostResolutionMap.TryGetValue(strippedHost, out var sid)) return sid;
 590
 0591            var normalDomain = NormalizeDomainName(domain);
 592
 593            string tempName;
 0594            string tempDomain = null;
 595
 596            //Step 1: Handle non-IP address values
 0597            if (!IPAddress.TryParse(strippedHost, out _))
 0598            {
 599                // Format: ABC.TESTLAB.LOCAL
 0600                if (strippedHost.Contains("."))
 0601                {
 0602                    var split = strippedHost.Split('.');
 0603                    tempName = split[0];
 0604                    tempDomain = string.Join(".", split.Skip(1).ToArray());
 0605                }
 606                // Format: WINDOWS
 607                else
 0608                {
 0609                    tempName = strippedHost;
 0610                    tempDomain = normalDomain;
 0611                }
 612
 613                // Add $ to the end of the name to match how computers are stored in AD
 0614                tempName = $"{tempName}$".ToUpper();
 0615                var principal = ResolveAccountName(tempName, tempDomain);
 0616                sid = principal?.ObjectIdentifier;
 0617                if (sid != null)
 0618                {
 0619                    _hostResolutionMap.TryAdd(strippedHost, sid);
 0620                    return sid;
 621                }
 0622            }
 623
 624            //Step 2: Try NetWkstaGetInfo
 625            //Next we'll try calling NetWkstaGetInfo in hopes of getting the NETBIOS name directly from the computer
 626            //We'll use the hostname that we started with instead of the one from our previous step
 0627            var workstationInfo = await GetWorkstationInfo(strippedHost);
 0628            if (workstationInfo.HasValue)
 0629            {
 0630                tempName = workstationInfo.Value.ComputerName;
 0631                tempDomain = workstationInfo.Value.LanGroup;
 632
 0633                if (string.IsNullOrEmpty(tempDomain))
 0634                    tempDomain = normalDomain;
 635
 0636                if (!string.IsNullOrEmpty(tempName))
 0637                {
 638                    //Append the $ to indicate this is a computer
 0639                    tempName = $"{tempName}$".ToUpper();
 0640                    var principal = ResolveAccountName(tempName, tempDomain);
 0641                    sid = principal?.ObjectIdentifier;
 0642                    if (sid != null)
 0643                    {
 0644                        _hostResolutionMap.TryAdd(strippedHost, sid);
 0645                        return sid;
 646                    }
 0647                }
 0648            }
 649
 650            //Step 3: Socket magic
 651            // Attempt to request the NETBIOS name of the computer directly
 0652            if (RequestNETBIOSNameFromComputer(strippedHost, normalDomain, out tempName))
 0653            {
 0654                tempDomain ??= normalDomain;
 0655                tempName = $"{tempName}$".ToUpper();
 656
 0657                var principal = ResolveAccountName(tempName, tempDomain);
 0658                sid = principal?.ObjectIdentifier;
 0659                if (sid != null)
 0660                {
 0661                    _hostResolutionMap.TryAdd(strippedHost, sid);
 0662                    return sid;
 663                }
 0664            }
 665
 666            //Try DNS resolution next
 667            string resolvedHostname;
 668            try
 0669            {
 0670                resolvedHostname = (await Dns.GetHostEntryAsync(strippedHost)).HostName;
 0671            }
 0672            catch
 0673            {
 0674                resolvedHostname = null;
 0675            }
 676
 0677            if (resolvedHostname != null)
 0678            {
 0679                var splitName = resolvedHostname.Split('.');
 0680                tempName = $"{splitName[0]}$".ToUpper();
 0681                tempDomain = string.Join(".", splitName.Skip(1));
 682
 0683                var principal = ResolveAccountName(tempName, tempDomain);
 0684                sid = principal?.ObjectIdentifier;
 0685                if (sid != null)
 0686                {
 0687                    _hostResolutionMap.TryAdd(strippedHost, sid);
 0688                    return sid;
 689                }
 0690            }
 691
 692            //If we get here, everything has failed, and life is very sad.
 0693            tempName = strippedHost;
 0694            tempDomain = normalDomain;
 695
 0696            if (tempName.Contains("."))
 0697            {
 0698                _hostResolutionMap.TryAdd(strippedHost, tempName);
 0699                return tempName;
 700            }
 701
 0702            tempName = $"{tempName}.{tempDomain}";
 0703            _hostResolutionMap.TryAdd(strippedHost, tempName);
 0704            return tempName;
 0705        }
 706
 707        /// <summary>
 708        ///     Attempts to convert a bare account name (usually from session enumeration) to its corresponding ID and o
 709        /// </summary>
 710        /// <param name="name"></param>
 711        /// <param name="domain"></param>
 712        /// <returns></returns>
 713        public TypedPrincipal ResolveAccountName(string name, string domain)
 0714        {
 0715            if (string.IsNullOrWhiteSpace(name))
 0716                return null;
 717
 0718            if (Cache.GetPrefixedValue(name, domain, out var id) && Cache.GetIDType(id, out var type))
 0719                return new TypedPrincipal
 0720                {
 0721                    ObjectIdentifier = id,
 0722                    ObjectType = type
 0723                };
 724
 0725            var d = NormalizeDomainName(domain);
 0726            var result = QueryLDAP($"(samaccountname={name})", SearchScope.Subtree,
 0727                CommonProperties.TypeResolutionProps,
 0728                d).DefaultIfEmpty(null).FirstOrDefault();
 729
 0730            if (result == null)
 0731            {
 0732                _log.LogDebug("ResolveAccountName - unable to get result for {Name}", name);
 0733                return null;
 734            }
 735
 0736            type = result.GetLabel();
 0737            id = result.GetObjectIdentifier();
 738
 0739            if (id == null)
 0740            {
 0741                _log.LogDebug("ResolveAccountName - could not retrieve ID on {DN} for {Name}", result.DistinguishedName,
 0742                    name);
 0743                return null;
 744            }
 745
 0746            Cache.AddPrefixedValue(name, domain, id);
 0747            Cache.AddType(id, type);
 748
 0749            id = ConvertWellKnownPrincipal(id, domain);
 750
 0751            return new TypedPrincipal
 0752            {
 0753                ObjectIdentifier = id,
 0754                ObjectType = type
 0755            };
 0756        }
 757
 758        /// <summary>
 759        ///     Attempts to convert a distinguishedname to its corresponding ID and object type.
 760        /// </summary>
 761        /// <param name="dn">DistinguishedName</param>
 762        /// <returns>A <c>TypedPrincipal</c> object with the SID and Label</returns>
 763        public TypedPrincipal ResolveDistinguishedName(string dn)
 0764        {
 0765            if (Cache.GetConvertedValue(dn, out var id) && Cache.GetIDType(id, out var type))
 0766                return new TypedPrincipal
 0767                {
 0768                    ObjectIdentifier = id,
 0769                    ObjectType = type
 0770                };
 771
 0772            var domain = Helpers.DistinguishedNameToDomain(dn);
 0773            var result = QueryLDAP("(objectclass=*)", SearchScope.Base, CommonProperties.TypeResolutionProps, domain,
 0774                    adsPath: dn)
 0775                .DefaultIfEmpty(null).FirstOrDefault();
 776
 0777            if (result == null)
 0778            {
 0779                _log.LogDebug("ResolveDistinguishedName - No result for {DN}", dn);
 0780                return null;
 781            }
 782
 0783            id = result.GetObjectIdentifier();
 0784            if (id == null)
 0785            {
 0786                _log.LogDebug("ResolveDistinguishedName - could not retrieve object identifier from {DN}", dn);
 0787                return null;
 788            }
 789
 0790            if (GetWellKnownPrincipal(id, domain, out var principal)) return principal;
 791
 0792            type = result.GetLabel();
 793
 0794            Cache.AddConvertedValue(dn, id);
 0795            Cache.AddType(id, type);
 796
 0797            id = ConvertWellKnownPrincipal(id, domain);
 798
 0799            return new TypedPrincipal
 0800            {
 0801                ObjectIdentifier = id,
 0802                ObjectType = type
 0803            };
 0804        }
 805
 806        /// <summary>
 807        ///     Queries LDAP using LDAPQueryOptions
 808        /// </summary>
 809        /// <param name="options"></param>
 810        /// <returns></returns>
 811        public IEnumerable<ISearchResultEntry> QueryLDAP(LDAPQueryOptions options)
 2812        {
 2813            return QueryLDAP(
 2814                options.Filter,
 2815                options.Scope,
 2816                options.Properties,
 2817                options.CancellationToken,
 2818                options.DomainName,
 2819                options.IncludeAcl,
 2820                options.ShowDeleted,
 2821                options.AdsPath,
 2822                options.GlobalCatalog,
 2823                options.SkipCache,
 2824                options.ThrowException
 2825            );
 2826        }
 827
 828        /// <summary>
 829        ///     Performs an LDAP query using the parameters specified by the user.
 830        /// </summary>
 831        /// <param name="ldapFilter">LDAP filter</param>
 832        /// <param name="scope">SearchScope to query</param>
 833        /// <param name="props">LDAP properties to fetch for each object</param>
 834        /// <param name="cancellationToken">Cancellation Token</param>
 835        /// <param name="includeAcl">Include the DACL and Owner values in the NTSecurityDescriptor</param>
 836        /// <param name="showDeleted">Include deleted objects</param>
 837        /// <param name="domainName">Domain to query</param>
 838        /// <param name="adsPath">ADS path to limit the query too</param>
 839        /// <param name="globalCatalog">Use the global catalog instead of the regular LDAP server</param>
 840        /// <param name="skipCache">
 841        ///     Skip the connection cache and force a new connection. You must dispose of this connection
 842        ///     yourself.
 843        /// </param>
 844        /// <param name="throwException">Throw exceptions rather than logging the errors directly</param>
 845        /// <returns>All LDAP search results matching the specified parameters</returns>
 846        /// <exception cref="LDAPQueryException">
 847        ///     Thrown when an error occurs during LDAP query (only when throwException = true)
 848        /// </exception>
 849        public IEnumerable<ISearchResultEntry> QueryLDAP(string ldapFilter, SearchScope scope,
 850            string[] props, CancellationToken cancellationToken, string domainName = null, bool includeAcl = false,
 851            bool showDeleted = false, string adsPath = null, bool globalCatalog = false, bool skipCache = false,
 852            bool throwException = false)
 5853        {
 5854            var queryParams = SetupLDAPQueryFilter(
 5855                ldapFilter, scope, props, includeAcl, domainName, includeAcl, adsPath, globalCatalog, skipCache);
 856
 5857            if (queryParams.Exception != null)
 5858            {
 5859                _log.LogWarning("Failed to setup LDAP Query Filter: {Message}", queryParams.Exception.Message);
 5860                if (throwException)
 2861                    throw new LDAPQueryException("Failed to setup LDAP Query Filter", queryParams.Exception);
 3862                yield break;
 863            }
 864
 0865            var conn = queryParams.Connection;
 0866            var request = queryParams.SearchRequest;
 0867            var pageControl = queryParams.PageControl;
 868
 0869            PageResultResponseControl pageResponse = null;
 0870            var backoffDelay = MinBackoffDelay;
 0871            var retryCount = 0;
 0872            while (true)
 0873            {
 0874                if (cancellationToken.IsCancellationRequested)
 0875                    yield break;
 876
 877                SearchResponse response;
 878                try
 0879                {
 0880                    _log.LogTrace("Sending LDAP request for {Filter}", ldapFilter);
 0881                    response = (SearchResponse)conn.SendRequest(request);
 0882                    if (response != null)
 0883                        pageResponse = (PageResultResponseControl)response.Controls
 0884                            .Where(x => x is PageResultResponseControl).DefaultIfEmpty(null).FirstOrDefault();
 0885                }
 0886                catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown &&
 0887                                               retryCount < MaxRetries)
 0888                {
 889                    /*A ServerDown exception indicates that our connection is no longer valid for one of many reasons.
 890                    However, this function is generally called by multiple threads, so we need to be careful in recreati
 891                    the connection. Using a semaphore, we can ensure that only one thread is actually recreating the con
 892                    while the other threads that hit the ServerDown exception simply wait. The initial caller will hold 
 893                    and do a backoff delay before trying to make a new connection which will replace the existing connec
 894                    _ldapConnections cache. Other threads will retrieve the new connection from the cache instead of mak
 895                    This minimizes overhead of new connections while still fixing our core problem.*/
 896
 897                    //Always increment retry count
 0898                    retryCount++;
 899
 900                    //Attempt to acquire a lock
 0901                    if (Monitor.TryEnter(_lockObj))
 0902                    {
 903                        //If we've acquired the lock, we want to immediately signal our reset event so everyone else wai
 0904                        _connectionResetEvent.Reset();
 905                        try
 0906                        {
 907                            //Sleep for our backoff
 0908                            Thread.Sleep(backoffDelay);
 909                            //Explicitly skip the cache so we don't get the same connection back
 0910                            conn = CreateNewConnection(domainName, globalCatalog, true);
 0911                            if (conn == null)
 0912                            {
 0913                                _log.LogError(
 0914                                    "Unable to create replacement ldap connection for ServerDown exception. Breaking loo
 0915                                yield break;
 916                            }
 917
 0918                            _log.LogInformation("Created new LDAP connection after receiving ServerDown from server");
 0919                        }
 920                        finally
 0921                        {
 922                            //Reset our event + release the lock
 0923                            _connectionResetEvent.Set();
 0924                            Monitor.Exit(_lockObj);
 0925                        }
 0926                    }
 927                    else
 0928                    {
 929                        //If someone else is holding the reset event, we want to just wait and then pull the newly creat
 930                        //This event will be released after the first entrant thread is done making a new connection
 931                        //The thread.sleep is to prevent a potential, very unlikely race
 0932                        Thread.Sleep(50);
 0933                        _connectionResetEvent.WaitOne();
 0934                        conn = CreateNewConnection(domainName, globalCatalog);
 0935                    }
 936
 0937                    backoffDelay = GetNextBackoff(retryCount);
 0938                    continue;
 939                }
 0940                catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.Busy && retryCount < MaxRetries)
 0941                {
 0942                    retryCount++;
 0943                    backoffDelay = GetNextBackoff(retryCount);
 0944                    continue;
 945                }
 0946                catch (LdapException le)
 0947                {
 0948                    if (le.ErrorCode != (int)LdapErrorCodes.LocalError)
 0949                    {
 0950                        if (throwException)
 0951                        {
 0952                            throw new LDAPQueryException(
 0953                                $"LDAP Exception in Loop: {le.ErrorCode}. {le.ServerErrorMessage}. {le.Message}. Filter:
 0954                                le);
 955                        }
 956
 0957                        _log.LogWarning(le,
 0958                            "LDAP Exception in Loop: {ErrorCode}. {ServerErrorMessage}. {Message}. Filter: {Filter}. Dom
 0959                            le.ErrorCode, le.ServerErrorMessage, le.Message, ldapFilter, domainName);
 0960                    }
 961
 0962                    yield break;
 963                }
 0964                catch (Exception e)
 0965                {
 0966                    _log.LogWarning(e, "Exception in LDAP loop for {Filter} and {Domain}", ldapFilter, domainName);
 0967                    if (throwException)
 0968                        throw new LDAPQueryException($"Exception in LDAP loop for {ldapFilter} and {domainName}", e);
 969
 0970                    yield break;
 971                }
 972
 0973                if (cancellationToken.IsCancellationRequested)
 0974                    yield break;
 975
 0976                if (response == null || pageResponse == null)
 0977                    continue;
 978
 0979                foreach (SearchResultEntry entry in response.Entries)
 0980                {
 0981                    if (cancellationToken.IsCancellationRequested)
 0982                        yield break;
 983
 0984                    yield return new SearchResultEntryWrapper(entry, this);
 0985                }
 986
 0987                if (pageResponse.Cookie.Length == 0 || response.Entries.Count == 0 ||
 0988                    cancellationToken.IsCancellationRequested)
 0989                    yield break;
 990
 0991                pageControl.Cookie = pageResponse.Cookie;
 0992            }
 993        }
 994
 995        private LdapConnection CreateNewConnection(string domainName = null, bool globalCatalog = false,
 996            bool skipCache = false)
 0997        {
 0998            var task = globalCatalog
 0999                ? Task.Run(() => CreateGlobalCatalogConnection(domainName, _ldapConfig.AuthType))
 01000                : Task.Run(() => CreateLDAPConnection(domainName, skipCache, _ldapConfig.AuthType));
 1001
 1002            try
 01003            {
 01004                return task.ConfigureAwait(false).GetAwaiter().GetResult();
 1005            }
 01006            catch
 01007            {
 01008                return null;
 1009            }
 01010        }
 1011
 1012        /// <summary>
 1013        ///     Performs an LDAP query using the parameters specified by the user.
 1014        /// </summary>
 1015        /// <param name="ldapFilter">LDAP filter</param>
 1016        /// <param name="scope">SearchScope to query</param>
 1017        /// <param name="props">LDAP properties to fetch for each object</param>
 1018        /// <param name="includeAcl">Include the DACL and Owner values in the NTSecurityDescriptor</param>
 1019        /// <param name="showDeleted">Include deleted objects</param>
 1020        /// <param name="domainName">Domain to query</param>
 1021        /// <param name="adsPath">ADS path to limit the query too</param>
 1022        /// <param name="globalCatalog">Use the global catalog instead of the regular LDAP server</param>
 1023        /// <param name="skipCache">
 1024        ///     Skip the connection cache and force a new connection. You must dispose of this connection
 1025        ///     yourself.
 1026        /// </param>
 1027        /// <param name="throwException">Throw exceptions rather than logging the errors directly</param>
 1028        /// <returns>All LDAP search results matching the specified parameters</returns>
 1029        /// <exception cref="LDAPQueryException">
 1030        ///     Thrown when an error occurs during LDAP query (only when throwException = true)
 1031        /// </exception>
 1032        public virtual IEnumerable<ISearchResultEntry> QueryLDAP(string ldapFilter, SearchScope scope,
 1033            string[] props, string domainName = null, bool includeAcl = false, bool showDeleted = false,
 1034            string adsPath = null, bool globalCatalog = false, bool skipCache = false, bool throwException = false)
 11035        {
 11036            return QueryLDAP(ldapFilter, scope, props, new CancellationToken(), domainName, includeAcl, showDeleted,
 11037                adsPath, globalCatalog, skipCache, throwException);
 11038        }
 1039
 1040        private static TimeSpan GetNextBackoff(int retryCount)
 01041        {
 01042            return TimeSpan.FromSeconds(Math.Min(
 01043                MinBackoffDelay.TotalSeconds * Math.Pow(BackoffDelayMultiplier, retryCount),
 01044                MaxBackoffDelay.TotalSeconds));
 01045        }
 1046
 1047        /// <summary>
 1048        ///     Gets the forest associated with a domain.
 1049        ///     If no domain is provided, defaults to current domain
 1050        /// </summary>
 1051        /// <param name="domainName"></param>
 1052        /// <returns></returns>
 1053        public virtual Forest GetForest(string domainName = null)
 351054        {
 1055            try
 351056            {
 351057                if (domainName == null && _ldapConfig.Username == null)
 351058                    return Forest.GetCurrentForest();
 1059
 01060                var domain = GetDomain(domainName);
 01061                return domain?.Forest;
 1062            }
 351063            catch
 351064            {
 351065                return null;
 1066            }
 351067        }
 1068
 1069        /// <summary>
 1070        ///     Creates a new ActiveDirectorySecurityDescriptor
 1071        ///     Function created for testing purposes
 1072        /// </summary>
 1073        /// <returns></returns>
 1074        public ActiveDirectorySecurityDescriptor MakeSecurityDescriptor()
 01075        {
 01076            return new ActiveDirectorySecurityDescriptor(new ActiveDirectorySecurity());
 01077        }
 1078
 1079        public string BuildLdapPath(string dnPath, string domainName)
 21080        {
 21081            var domain = GetDomain(domainName)?.Name;
 21082            if (domain == null)
 11083                return null;
 1084
 11085            var adPath = $"{dnPath},DC={domain.Replace(".", ",DC=")}";
 11086            return adPath;
 21087        }
 1088
 1089        /// <summary>
 1090        ///     Tests the current LDAP config to ensure its valid by pulling a domain object
 1091        /// </summary>
 1092        /// <returns>True if connection was successful, else false</returns>
 1093        public bool TestLDAPConfig(string domain)
 01094        {
 01095            var filter = new LDAPFilter();
 01096            filter.AddDomains();
 1097
 01098            var resDomain = GetDomain(domain)?.Name ?? domain;
 01099            _log.LogTrace("Testing LDAP connection for domain {Domain}", resDomain);
 1100
 01101            var result = QueryLDAP(filter.GetFilter(), SearchScope.Subtree, CommonProperties.ObjectID, resDomain,
 01102                    throwException: true)
 01103                .DefaultIfEmpty(null).FirstOrDefault();
 01104            _log.LogTrace("Result object from LDAP connection test is {DN}", result?.DistinguishedName ?? "null");
 01105            return result != null;
 01106        }
 1107
 1108        /// <summary>
 1109        ///     Gets the domain object associated with the specified domain name.
 1110        ///     Defaults to current domain if none specified
 1111        /// </summary>
 1112        /// <param name="domainName"></param>
 1113        /// <returns></returns>
 1114        public virtual Domain GetDomain(string domainName = null)
 51115        {
 51116            var cacheKey = domainName ?? NullCacheKey;
 71117            if (_domainCache.TryGetValue(cacheKey, out var domain)) return domain;
 1118
 1119            try
 31120            {
 1121                DirectoryContext context;
 31122                if (_ldapConfig.Username != null)
 01123                    context = domainName != null
 01124                        ? new DirectoryContext(DirectoryContextType.Domain, domainName, _ldapConfig.Username,
 01125                            _ldapConfig.Password)
 01126                        : new DirectoryContext(DirectoryContextType.Domain, _ldapConfig.Username,
 01127                            _ldapConfig.Password);
 1128                else
 31129                    context = domainName != null
 31130                        ? new DirectoryContext(DirectoryContextType.Domain, domainName)
 31131                        : new DirectoryContext(DirectoryContextType.Domain);
 1132
 31133                domain = Domain.GetDomain(context);
 01134            }
 31135            catch (Exception e)
 31136            {
 31137                _log.LogDebug(e, "GetDomain call failed at {StackTrace}", new StackFrame());
 31138                domain = null;
 31139            }
 1140
 31141            _domainCache.TryAdd(cacheKey, domain);
 31142            return domain;
 51143        }
 1144
 1145        /// <summary>
 1146        ///     Setup LDAP query for filter
 1147        /// </summary>
 1148        /// <param name="ldapFilter">LDAP filter</param>
 1149        /// <param name="scope">SearchScope to query</param>
 1150        /// <param name="props">LDAP properties to fetch for each object</param>
 1151        /// <param name="includeAcl">Include the DACL and Owner values in the NTSecurityDescriptor</param>
 1152        /// <param name="domainName">Domain to query</param>
 1153        /// <param name="showDeleted">Include deleted objects</param>
 1154        /// <param name="adsPath">ADS path to limit the query too</param>
 1155        /// <param name="globalCatalog">Use the global catalog instead of the regular LDAP server</param>
 1156        /// <param name="skipCache">
 1157        ///     Skip the connection cache and force a new connection. You must dispose of this connection
 1158        ///     yourself.
 1159        /// </param>
 1160        /// <returns>Tuple of LdapConnection, SearchRequest, PageResultRequestControl and LDAPQueryException</returns>
 1161        // ReSharper disable once MemberCanBePrivate.Global
 1162        internal LDAPQueryParams SetupLDAPQueryFilter(
 1163            string ldapFilter,
 1164            SearchScope scope, string[] props, bool includeAcl = false, string domainName = null,
 1165            bool showDeleted = false,
 1166            string adsPath = null, bool globalCatalog = false, bool skipCache = false)
 51167        {
 51168            _log.LogTrace("Creating ldap connection for {Target} with filter {Filter}",
 51169                globalCatalog ? "Global Catalog" : "DC", ldapFilter);
 51170            var task = globalCatalog
 11171                ? Task.Run(() => CreateGlobalCatalogConnection(domainName, _ldapConfig.AuthType))
 91172                : Task.Run(() => CreateLDAPConnection(domainName, skipCache, _ldapConfig.AuthType));
 1173
 51174            var queryParams = new LDAPQueryParams();
 1175
 1176            LdapConnection conn;
 1177            try
 51178            {
 51179                conn = task.ConfigureAwait(false).GetAwaiter().GetResult();
 01180            }
 01181            catch (LdapException ldapException)
 01182            {
 01183                var errorString =
 01184                    $"LDAP Exception {ldapException.ErrorCode} when creating connection for {ldapFilter} and domain {dom
 01185                queryParams.Exception = new LDAPQueryException(errorString, ldapException);
 01186                return queryParams;
 1187            }
 51188            catch (LDAPQueryException ldapQueryException)
 51189            {
 51190                queryParams.Exception = ldapQueryException;
 51191                return queryParams;
 1192            }
 01193            catch (Exception e)
 01194            {
 01195                var errorString =
 01196                    $"Exception getting LDAP connection for {ldapFilter} and domain {domainName ?? "Default Domain"}";
 01197                queryParams.Exception = new LDAPQueryException(errorString, e);
 01198                return queryParams;
 1199            }
 1200
 1201            //If we get a null connection, something went wrong, but we don't have an error to go with it for whatever r
 01202            if (conn == null)
 01203            {
 01204                var errorString =
 01205                    $"LDAP connection is null for filter {ldapFilter} and domain {domainName ?? "Default Domain"}";
 01206                queryParams.Exception = new LDAPQueryException(errorString);
 01207                return queryParams;
 1208            }
 1209
 1210            SearchRequest request;
 1211
 1212            try
 01213            {
 01214                request = CreateSearchRequest(ldapFilter, scope, props, domainName, adsPath, showDeleted);
 01215            }
 01216            catch (LDAPQueryException ldapQueryException)
 01217            {
 01218                queryParams.Exception = ldapQueryException;
 01219                return queryParams;
 1220            }
 1221
 01222            if (request == null)
 01223            {
 01224                var errorString =
 01225                    $"Search request is null for filter {ldapFilter} and domain {domainName ?? "Default Domain"}";
 01226                queryParams.Exception = new LDAPQueryException(errorString);
 01227                return queryParams;
 1228            }
 1229
 01230            var pageControl = new PageResultRequestControl(500);
 01231            request.Controls.Add(pageControl);
 1232
 01233            if (includeAcl)
 01234                request.Controls.Add(new SecurityDescriptorFlagControl
 01235                {
 01236                    SecurityMasks = SecurityMasks.Dacl | SecurityMasks.Owner
 01237                });
 1238
 01239            queryParams.Connection = conn;
 01240            queryParams.SearchRequest = request;
 01241            queryParams.PageControl = pageControl;
 1242
 01243            return queryParams;
 51244        }
 1245
 1246        private Group GetBaseEnterpriseDC(string domain)
 01247        {
 01248            var forest = GetForest(domain)?.Name;
 01249            if (forest == null) _log.LogWarning("Error getting forest, ENTDC sid is likely incorrect");
 01250            var g = new Group { ObjectIdentifier = $"{forest}-S-1-5-9".ToUpper() };
 01251            g.Properties.Add("name", $"ENTERPRISE DOMAIN CONTROLLERS@{forest ?? "UNKNOWN"}".ToUpper());
 01252            g.Properties.Add("domainsid", GetSidFromDomainName(forest));
 01253            g.Properties.Add("domain", forest);
 01254            return g;
 01255        }
 1256
 1257        /// <summary>
 1258        ///     Updates the config for querying LDAP
 1259        /// </summary>
 1260        /// <param name="config"></param>
 1261        public void UpdateLDAPConfig(LDAPConfig config)
 01262        {
 01263            _ldapConfig = config;
 01264        }
 1265
 1266        private string GetDomainNameFromSidLdap(string sid)
 01267        {
 01268            var hexSid = Helpers.ConvertSidToHexSid(sid);
 1269
 01270            if (hexSid == null)
 01271                return null;
 1272
 1273            //Search using objectsid first
 01274            var result =
 01275                QueryLDAP($"(&(objectclass=domain)(objectsid={hexSid}))", SearchScope.Subtree,
 01276                    new[] { "distinguishedname" }, globalCatalog: true).DefaultIfEmpty(null).FirstOrDefault();
 1277
 01278            if (result != null)
 01279            {
 01280                var domainName = Helpers.DistinguishedNameToDomain(result.DistinguishedName);
 01281                return domainName;
 1282            }
 1283
 1284            //Try trusteddomain objects with the securityidentifier attribute
 01285            result =
 01286                QueryLDAP($"(&(objectclass=trusteddomain)(securityidentifier={sid}))", SearchScope.Subtree,
 01287                    new[] { "cn" }, globalCatalog: true).DefaultIfEmpty(null).FirstOrDefault();
 1288
 01289            if (result != null)
 01290            {
 01291                var domainName = result.GetProperty(LDAPProperties.CanonicalName);
 01292                return domainName;
 1293            }
 1294
 1295            //We didn't find anything so just return null
 01296            return null;
 01297        }
 1298
 1299        /// <summary>
 1300        ///     Uses a socket and a set of bytes to request the NETBIOS name from a remote computer
 1301        /// </summary>
 1302        /// <param name="server"></param>
 1303        /// <param name="domain"></param>
 1304        /// <param name="netbios"></param>
 1305        /// <returns></returns>
 1306        private static bool RequestNETBIOSNameFromComputer(string server, string domain, out string netbios)
 01307        {
 01308            var receiveBuffer = new byte[1024];
 01309            var requestSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
 1310            try
 01311            {
 1312                //Set receive timeout to 1 second
 01313                requestSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 1000);
 1314                EndPoint remoteEndpoint;
 1315
 1316                //We need to create an endpoint to bind too. If its an IP, just use that.
 01317                if (IPAddress.TryParse(server, out var parsedAddress))
 01318                    remoteEndpoint = new IPEndPoint(parsedAddress, 137);
 1319                else
 1320                    //If its not an IP, we're going to try and resolve it from DNS
 1321                    try
 01322                    {
 1323                        IPAddress address;
 01324                        if (server.Contains("."))
 01325                            address = Dns
 01326                                .GetHostAddresses(server).First(x => x.AddressFamily == AddressFamily.InterNetwork);
 1327                        else
 01328                            address = Dns.GetHostAddresses($"{server}.{domain}")[0];
 1329
 01330                        if (address == null)
 01331                        {
 01332                            netbios = null;
 01333                            return false;
 1334                        }
 1335
 01336                        remoteEndpoint = new IPEndPoint(address, 137);
 01337                    }
 01338                    catch
 01339                    {
 1340                        //Failed to resolve an IP, so return null
 01341                        netbios = null;
 01342                        return false;
 1343                    }
 1344
 01345                var originEndpoint = new IPEndPoint(IPAddress.Any, 0);
 01346                requestSocket.Bind(originEndpoint);
 1347
 1348                try
 01349                {
 01350                    requestSocket.SendTo(NameRequest, remoteEndpoint);
 01351                    var receivedByteCount = requestSocket.ReceiveFrom(receiveBuffer, ref remoteEndpoint);
 01352                    if (receivedByteCount >= 90)
 01353                    {
 01354                        netbios = new ASCIIEncoding().GetString(receiveBuffer, 57, 16).Trim('\0', ' ');
 01355                        return true;
 1356                    }
 1357
 01358                    netbios = null;
 01359                    return false;
 1360                }
 01361                catch (SocketException)
 01362                {
 01363                    netbios = null;
 01364                    return false;
 1365                }
 1366            }
 1367            finally
 01368            {
 1369                //Make sure we close the socket if its open
 01370                requestSocket.Close();
 01371            }
 01372        }
 1373
 1374        /// <summary>
 1375        ///     Calls the NetWkstaGetInfo API on a hostname
 1376        /// </summary>
 1377        /// <param name="hostname"></param>
 1378        /// <returns></returns>
 1379        private async Task<NetAPIStructs.WorkstationInfo100?> GetWorkstationInfo(string hostname)
 01380        {
 01381            if (!await _portScanner.CheckPort(hostname))
 01382                return null;
 1383
 01384            var result = NetAPIMethods.NetWkstaGetInfo(hostname);
 01385            if (result.IsSuccess) return result.Value;
 1386
 01387            return null;
 01388        }
 1389
 1390        /// <summary>
 1391        ///     Creates a SearchRequest object for use in querying LDAP.
 1392        /// </summary>
 1393        /// <param name="filter">LDAP filter</param>
 1394        /// <param name="scope">SearchScope to query</param>
 1395        /// <param name="attributes">LDAP properties to fetch for each object</param>
 1396        /// <param name="domainName">Domain to query</param>
 1397        /// <param name="adsPath">ADS path to limit the query too</param>
 1398        /// <param name="showDeleted">Include deleted objects in results</param>
 1399        /// <returns>A built SearchRequest</returns>
 1400        private SearchRequest CreateSearchRequest(string filter, SearchScope scope, string[] attributes,
 1401            string domainName = null, string adsPath = null, bool showDeleted = false)
 01402        {
 01403            var domain = GetDomain(domainName)?.Name ?? domainName;
 1404
 01405            if (domain == null)
 01406                throw new LDAPQueryException(
 01407                    $"Unable to create search request: GetDomain call failed for {domainName}");
 1408
 01409            var adPath = adsPath?.Replace("LDAP://", "") ?? $"DC={domain.Replace(".", ",DC=")}";
 1410
 01411            var request = new SearchRequest(adPath, filter, scope, attributes);
 01412            request.Controls.Add(new SearchOptionsControl(SearchOption.DomainScope));
 01413            if (showDeleted)
 01414                request.Controls.Add(new ShowDeletedControl());
 1415
 01416            return request;
 01417        }
 1418
 1419        /// <summary>
 1420        ///     Creates a LDAP connection to a global catalog server
 1421        /// </summary>
 1422        /// <param name="domainName">Domain to connect too</param>
 1423        /// <param name="authType">Auth type to use. Defaults to Kerberos. Use Negotiate for netonly scenarios</param>
 1424        /// <returns>A connected LdapConnection or null</returns>
 1425        private async Task<LdapConnection> CreateGlobalCatalogConnection(string domainName = null,
 1426            AuthType authType = AuthType.Kerberos)
 11427        {
 1428            string targetServer;
 11429            if (_ldapConfig.Server != null)
 01430            {
 01431                targetServer = _ldapConfig.Server;
 01432            }
 1433            else
 11434            {
 11435                var domain = GetDomain(domainName);
 11436                if (domain == null)
 11437                {
 11438                    _log.LogDebug(
 11439                        "Unable to create global catalog connection for domain {DomainName}: GetDomain failed",
 11440                        domainName);
 11441                    throw new LDAPQueryException($"GetDomain call failed for {domainName}");
 1442                }
 1443
 01444                if (!_domainControllerCache.TryGetValue(domain.Name, out targetServer))
 01445                    targetServer = await GetUsableDomainController(domain);
 01446            }
 1447
 01448            if (targetServer == null)
 01449                throw new LDAPQueryException($"No usable global catalog found for {domainName}");
 1450
 01451            if (_globalCatalogConnections.TryGetValue(targetServer, out var connection))
 01452                return connection;
 1453
 01454            connection = new LdapConnection(new LdapDirectoryIdentifier(targetServer, 3268));
 1455
 01456            connection.SessionOptions.ProtocolVersion = 3;
 1457
 01458            if (_ldapConfig.Username != null)
 01459            {
 01460                var cred = new NetworkCredential(_ldapConfig.Username, _ldapConfig.Password);
 01461                connection.Credential = cred;
 01462            }
 1463
 01464            if (_ldapConfig.DisableSigning)
 01465            {
 01466                connection.SessionOptions.Sealing = false;
 01467                connection.SessionOptions.Signing = false;
 01468            }
 1469
 01470            connection.AuthType = authType;
 1471
 01472            _globalCatalogConnections.TryAdd(targetServer, connection);
 01473            return connection;
 01474        }
 1475
 1476        /// <summary>
 1477        ///     Creates an LDAP connection with appropriate options based off the ldap configuration. Caches connections
 1478        /// </summary>
 1479        /// <param name="domainName">The domain to connect too</param>
 1480        /// <param name="skipCache">Skip the connection cache</param>
 1481        /// <param name="authType">Auth type to use. Defaults to Kerberos. Use Negotiate for netonly scenarios</param>
 1482        /// <returns>A connected LDAP connection or null</returns>
 1483        private async Task<LdapConnection> CreateLDAPConnection(string domainName = null, bool skipCache = false,
 1484            AuthType authType = AuthType.Kerberos)
 41485        {
 1486            string targetServer;
 41487            if (_ldapConfig.Server != null)
 01488                targetServer = _ldapConfig.Server;
 1489            else
 41490            {
 41491                var domain = GetDomain(domainName);
 41492                if (domain == null)
 41493                {
 41494                    _log.LogDebug("Unable to create ldap connection for domain {DomainName}: GetDomain failed",
 41495                        domainName);
 41496                    throw new LDAPQueryException(
 41497                        $"Error creating LDAP connection: GetDomain call failed for {domainName}");
 1498                }
 1499
 01500                if (!_domainControllerCache.TryGetValue(domain.Name, out targetServer))
 01501                    targetServer = await GetUsableDomainController(domain);
 01502            }
 1503
 01504            if (targetServer == null)
 01505                throw new LDAPQueryException($"No usable domain controller found for {domainName}");
 1506
 01507            if (!skipCache)
 01508                if (_ldapConnections.TryGetValue(targetServer, out var conn))
 01509                    return conn;
 1510
 01511            var port = _ldapConfig.GetPort();
 01512            var ident = new LdapDirectoryIdentifier(targetServer, port, false, false);
 01513            var connection = new LdapConnection(ident) { Timeout = new TimeSpan(0, 0, 5, 0) };
 01514            if (_ldapConfig.Username != null)
 01515            {
 01516                var cred = new NetworkCredential(_ldapConfig.Username, _ldapConfig.Password);
 01517                connection.Credential = cred;
 01518            }
 1519
 1520            //These options are important!
 01521            connection.SessionOptions.ProtocolVersion = 3;
 01522            connection.SessionOptions.ReferralChasing = ReferralChasingOptions.None;
 1523
 01524            if (_ldapConfig.DisableSigning)
 01525            {
 01526                connection.SessionOptions.Sealing = false;
 01527                connection.SessionOptions.Signing = false;
 01528            }
 1529
 01530            if (_ldapConfig.SSL)
 01531                connection.SessionOptions.SecureSocketLayer = true;
 1532
 01533            if (_ldapConfig.DisableCertVerification)
 01534                connection.SessionOptions.VerifyServerCertificate = (ldapConnection, certificate) => true;
 1535
 01536            connection.AuthType = authType;
 1537
 01538            _ldapConnections.AddOrUpdate(targetServer, connection, (s, ldapConnection) =>
 01539            {
 01540                ldapConnection.Dispose();
 01541                return connection;
 01542            });
 1543
 01544            return connection;
 01545        }
 1546
 1547        private async Task<string> GetUsableDomainController(Domain domain, bool gc = false)
 01548        {
 01549            if (!gc && _domainControllerCache.TryGetValue(domain.Name.ToUpper(), out var dc))
 01550                return dc;
 1551
 01552            var port = gc ? 3268 : _ldapConfig.GetPort();
 01553            var pdc = domain.PdcRoleOwner.Name;
 01554            if (await _portScanner.CheckPort(pdc, port))
 01555            {
 01556                _domainControllerCache.TryAdd(domain.Name.ToUpper(), pdc);
 01557                _log.LogInformation("Found usable Domain Controller for {Domain} : {PDC}", domain.Name, pdc);
 01558                return pdc;
 1559            }
 1560
 1561            //If the PDC isn't reachable loop through the rest
 01562            foreach (DomainController domainController in domain.DomainControllers)
 01563            {
 01564                var name = domainController.Name;
 01565                if (!await _portScanner.CheckPort(name, port)) continue;
 01566                _log.LogInformation("Found usable Domain Controller for {Domain} : {PDC}", domain.Name, name);
 01567                _domainControllerCache.TryAdd(domain.Name.ToUpper(), name);
 01568                return name;
 1569            }
 1570
 1571            //If we get here, somehow we didn't get any usable DCs. Save it off as null
 01572            _domainControllerCache.TryAdd(domain.Name.ToUpper(), null);
 01573            _log.LogWarning("Unable to find usable domain controller for {Domain}", domain.Name);
 01574            return null;
 01575        }
 1576
 1577        /// <summary>
 1578        ///     Normalizes a domain name to its full DNS name
 1579        /// </summary>
 1580        /// <param name="domain"></param>
 1581        /// <returns></returns>
 1582        internal string NormalizeDomainName(string domain)
 01583        {
 01584            if (domain == null)
 01585                return null;
 1586
 01587            var resolved = domain;
 1588
 01589            if (resolved.Contains("."))
 01590                return domain.ToUpper();
 1591
 01592            resolved = ResolveDomainNetbiosToDns(domain) ?? domain;
 1593
 01594            return resolved.ToUpper();
 01595        }
 1596
 1597        /// <summary>
 1598        ///     Turns a domain Netbios name into its FQDN using the DsGetDcName function (TESTLAB -> TESTLAB.LOCAL)
 1599        /// </summary>
 1600        /// <param name="domainName"></param>
 1601        /// <returns></returns>
 1602        internal string ResolveDomainNetbiosToDns(string domainName)
 01603        {
 01604            var key = domainName.ToUpper();
 01605            if (_netbiosCache.TryGetValue(key, out var flatName))
 01606                return flatName;
 1607
 01608            var domain = GetDomain(domainName);
 01609            if (domain != null)
 01610            {
 01611                _netbiosCache.TryAdd(key, domain.Name);
 01612                return domain.Name;
 1613            }
 1614
 01615            var computerName = _ldapConfig.Server;
 1616
 01617            var dci = _nativeMethods.CallDsGetDcName(computerName, domainName);
 01618            if (dci.IsSuccess)
 01619            {
 01620                flatName = dci.Value.DomainName;
 01621                _netbiosCache.TryAdd(key, flatName);
 01622                return flatName;
 1623            }
 1624
 01625            return domainName.ToUpper();
 01626        }
 1627
 1628        /// <summary>
 1629        /// Gets the range retrieval limit for a domain
 1630        /// </summary>
 1631        /// <param name="domainName"></param>
 1632        /// <param name="defaultRangeSize"></param>
 1633        /// <returns></returns>
 1634        public int GetDomainRangeSize(string domainName = null, int defaultRangeSize = 750)
 41635        {
 41636            var domainPath = DomainNameToDistinguishedName(domainName);
 1637            //Default to a page size of 750 for safety
 41638            if (domainPath == null)
 21639            {
 21640                _log.LogDebug("Unable to resolve domain {Domain} to distinguishedname to get page size",
 21641                    domainName ?? "current domain");
 21642                return defaultRangeSize;
 1643            }
 1644
 21645            if (_ldapRangeSizeCache.TryGetValue(domainPath.ToUpper(), out var parsedPageSize))
 01646            {
 01647                return parsedPageSize;
 1648            }
 1649
 21650            var configPath = CommonPaths.CreateDNPath(CommonPaths.QueryPolicyPath, domainPath);
 21651            var enumerable = QueryLDAP("(objectclass=*)", SearchScope.Base, null, adsPath: configPath);
 21652            var config = enumerable.DefaultIfEmpty(null).FirstOrDefault();
 21653            var pageSize = config?.GetArrayProperty(LDAPProperties.LdapAdminLimits)
 31654                .FirstOrDefault(x => x.StartsWith("MaxPageSize", StringComparison.OrdinalIgnoreCase));
 21655            if (pageSize == null)
 11656            {
 11657                _log.LogDebug("No LDAPAdminLimits object found for {Domain}", domainName);
 11658                _ldapRangeSizeCache.TryAdd(domainPath.ToUpper(), defaultRangeSize);
 11659                return defaultRangeSize;
 1660            }
 1661
 11662            if (int.TryParse(pageSize.Split('=').Last(), out parsedPageSize))
 11663            {
 11664                _ldapRangeSizeCache.TryAdd(domainPath.ToUpper(), parsedPageSize);
 11665                _log.LogInformation("Found page size {PageSize} for {Domain}", parsedPageSize,
 11666                    domainName ?? "current domain");
 11667                return parsedPageSize;
 1668            }
 1669
 01670            _log.LogDebug("Failed to parse pagesize for {Domain}, returning default", domainName ?? "current domain");
 1671
 01672            _ldapRangeSizeCache.TryAdd(domainPath.ToUpper(), defaultRangeSize);
 01673            return defaultRangeSize;
 41674        }
 1675
 1676        private string DomainNameToDistinguishedName(string domain)
 41677        {
 41678            var resolvedDomain = GetDomain(domain)?.Name ?? domain;
 41679            return resolvedDomain == null ? null : $"DC={resolvedDomain.Replace(".", ",DC=")}";
 41680        }
 1681
 1682        private class ResolvedWellKnownPrincipal
 1683        {
 31684            public string DomainName { get; set; }
 31685            public string WkpId { get; set; }
 1686        }
 1687
 1688        public string GetConfigurationPath(string domainName = null)
 01689        {
 01690            string path = domainName == null
 01691                ? "LDAP://RootDSE"
 01692                : $"LDAP://{NormalizeDomainName(domainName)}/RootDSE";
 1693
 1694            DirectoryEntry rootDse;
 01695            if (_ldapConfig.Username != null)
 01696                rootDse = new DirectoryEntry(path, _ldapConfig.Username, _ldapConfig.Password);
 1697            else
 01698                rootDse = new DirectoryEntry(path);
 1699
 01700            return $"{rootDse.Properties["configurationNamingContext"]?[0]}";
 01701        }
 1702
 1703        public string GetSchemaPath(string domainName)
 01704        {
 01705            string path = domainName == null
 01706                ? "LDAP://RootDSE"
 01707                : $"LDAP://{NormalizeDomainName(domainName)}/RootDSE";
 1708
 1709            DirectoryEntry rootDse;
 01710            if (_ldapConfig.Username != null)
 01711                rootDse = new DirectoryEntry(path, _ldapConfig.Username, _ldapConfig.Password);
 1712            else
 01713                rootDse = new DirectoryEntry(path);
 1714
 01715            return $"{rootDse.Properties["schemaNamingContext"]?[0]}";
 01716        }
 1717
 1718        public bool IsDomainController(string computerObjectId, string domainName)
 01719        {
 01720            var filter = new LDAPFilter().AddFilter(LDAPProperties.ObjectSID + "=" + computerObjectId, true)
 01721                .AddFilter(CommonFilters.DomainControllers, true);
 01722            var res = QueryLDAP(filter.GetFilter(), SearchScope.Subtree,
 01723                CommonProperties.ObjectID, domainName: domainName);
 01724            if (res.Count() > 0)
 01725                return true;
 01726            return false;
 01727        }
 1728    }
 1729}

Methods/Properties

.cctor()
.ctor()
.ctor(SharpHoundCommonLib.NativeMethods,SharpHoundCommonLib.Processors.PortScanner,Microsoft.Extensions.Logging.ILogger)
SetLDAPConfig(SharpHoundCommonLib.LDAPConfig)
GetWellKnownPrincipal(System.String,System.String,SharpHoundCommonLib.OutputTypes.TypedPrincipal&)
AddDomainController(System.String)
GetWellKnownPrincipalOutput()
ConvertWellKnownPrincipal(System.String,System.String)
GetUserGlobalCatalogMatches(System.String)
ResolveIDAndType(System.String,System.String)
ResolveCertTemplateByProperty(System.String,System.String,System.String,System.String)
LookupSidType(System.String,System.String)
LookupGuidType(System.String,System.String)
GetDomainNameFromSid(System.String)
GetSidFromDomainName(System.String)
DoRangedRetrieval()
ResolveHostToSid()
ResolveAccountName(System.String,System.String)
ResolveDistinguishedName(System.String)
QueryLDAP(SharpHoundCommonLib.LDAPQueryOptions)
QueryLDAP()
CreateNewConnection(System.String,System.Boolean,System.Boolean)
QueryLDAP(System.String,System.DirectoryServices.Protocols.SearchScope,System.String[],System.String,System.Boolean,System.Boolean,System.String,System.Boolean,System.Boolean,System.Boolean)
GetNextBackoff(System.Int32)
GetForest(System.String)
MakeSecurityDescriptor()
BuildLdapPath(System.String,System.String)
TestLDAPConfig(System.String)
GetDomain(System.String)
SetupLDAPQueryFilter(System.String,System.DirectoryServices.Protocols.SearchScope,System.String[],System.Boolean,System.String,System.Boolean,System.String,System.Boolean,System.Boolean)
GetBaseEnterpriseDC(System.String)
UpdateLDAPConfig(SharpHoundCommonLib.LDAPConfig)
GetDomainNameFromSidLdap(System.String)
RequestNETBIOSNameFromComputer(System.String,System.String,System.String&)
GetWorkstationInfo()
CreateSearchRequest(System.String,System.DirectoryServices.Protocols.SearchScope,System.String[],System.String,System.String,System.Boolean)
CreateGlobalCatalogConnection()
CreateLDAPConnection()
GetUsableDomainController()
NormalizeDomainName(System.String)
ResolveDomainNetbiosToDns(System.String)
GetDomainRangeSize(System.String,System.Int32)
DomainNameToDistinguishedName(System.String)
DomainName()
WkpId()
GetConfigurationPath(System.String)
GetSchemaPath(System.String)
IsDomainController(System.String,System.String)