diff --git a/src/CommonLib/Enums/CollectionMethod.cs b/src/CommonLib/Enums/CollectionMethod.cs index 0b5959e1..84c8ec91 100644 --- a/src/CommonLib/Enums/CollectionMethod.cs +++ b/src/CommonLib/Enums/CollectionMethod.cs @@ -1,10 +1,8 @@ using System; -namespace SharpHoundCommonLib.Enums -{ +namespace SharpHoundCommonLib.Enums { [Flags] - public enum CollectionMethod - { + public enum CollectionMethod { None = 0, Group = 1, LocalAdmin = 1 << 1, @@ -25,10 +23,18 @@ public enum CollectionMethod CARegistry = 1 << 16, DCRegistry = 1 << 17, CertServices = 1 << 18, + LdapServices = 1 << 19, + WebClientService = 1 << 21, + SmbInfo = 1 << 22, + EventLogs = 1 << 23, LocalGroups = DCOM | RDP | LocalAdmin | PSRemote, - ComputerOnly = LocalGroups | Session | UserRights | CARegistry | DCRegistry, - DCOnly = ACL | Container | Group | ObjectProps | Trusts | GPOLocalGroup | CertServices, - Default = Group | Session | Trusts | ACL | ObjectProps | LocalGroups | SPNTargets | Container | CertServices, - All = Default | LoggedOn | GPOLocalGroup | UserRights | CARegistry | DCRegistry + ComputerOnly = LocalGroups | Session | UserRights | CARegistry | DCRegistry | WebClientService | SmbInfo, + DCOnly = ACL | Container | Group | ObjectProps | Trusts | GPOLocalGroup | CertServices | LdapServices | SmbInfo, + + Default = Group | Session | Trusts | ACL | ObjectProps | LocalGroups | SPNTargets | Container | CertServices | + LdapServices | SmbInfo, + + All = Default | LoggedOn | GPOLocalGroup | UserRights | CARegistry | DCRegistry | WebClientService | + LdapServices } } \ No newline at end of file diff --git a/src/CommonLib/Enums/EventIds.cs b/src/CommonLib/Enums/EventIds.cs new file mode 100644 index 00000000..ee93ed5c --- /dev/null +++ b/src/CommonLib/Enums/EventIds.cs @@ -0,0 +1,6 @@ +namespace SharpHoundCommonLib.Enums; + +public class EventIds { + public static int LogonEvent = 4624; + public static int ValidateCredentialsEvent = 4776; +} \ No newline at end of file diff --git a/src/CommonLib/Enums/LdapErrorCodes.cs b/src/CommonLib/Enums/LdapErrorCodes.cs index becf71c4..c6f81b62 100644 --- a/src/CommonLib/Enums/LdapErrorCodes.cs +++ b/src/CommonLib/Enums/LdapErrorCodes.cs @@ -1,12 +1,12 @@ -namespace SharpHoundCommonLib.Enums -{ - public enum LdapErrorCodes : int - { +namespace SharpHoundCommonLib.Enums { + public enum LdapErrorCodes : int { Success = 0, + StrongAuthRequired = 8, + SaslBindInProgress = 14, InvalidCredentials = 49, Busy = 51, ServerDown = 81, LocalError = 82, - KerberosAuthType = 83 + KerberosAuthType = 83, } } \ No newline at end of file diff --git a/src/CommonLib/Enums/LdapOption.cs b/src/CommonLib/Enums/LdapOption.cs new file mode 100644 index 00000000..a0403253 --- /dev/null +++ b/src/CommonLib/Enums/LdapOption.cs @@ -0,0 +1,12 @@ +namespace SharpHoundCommonLib.Enums { + public enum LdapOption : int { + Ssl = 0x0A, + ProtocolVersion = 0x11, + ResultCode = 0x31, + ServerError = 0x33, + ServerCertificate = 0x81, + Sign = 0x95, + Encrypt = 0x96, + Timeout = 0x5002, + } +} \ No newline at end of file diff --git a/src/CommonLib/Enums/LdapOptionValue.cs b/src/CommonLib/Enums/LdapOptionValue.cs new file mode 100644 index 00000000..16e5fb9a --- /dev/null +++ b/src/CommonLib/Enums/LdapOptionValue.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SharpHoundCommonLib.Enums { + public enum LdapOptionValue : int { + Off = 0, + On = 1, + Version3 = 3, + }; +} \ No newline at end of file diff --git a/src/CommonLib/Enums/LdapSupportedSaslMechanisms.cs b/src/CommonLib/Enums/LdapSupportedSaslMechanisms.cs new file mode 100644 index 00000000..37736c70 --- /dev/null +++ b/src/CommonLib/Enums/LdapSupportedSaslMechanisms.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SharpHoundCommonLib.Enums { + public static class LdapSupportedSaslMechanisms { + public const string GSSAPI = "GSSAPI"; + public const string GSS_SPNEGO = "GSS-SPNEGO"; + public const string EXTERNAL = "EXTERNAL"; + public const string DIGEST_MD5 = "DIGEST_MD5"; + } +} \ No newline at end of file diff --git a/src/CommonLib/Extensions.cs b/src/CommonLib/Extensions.cs index be102fd9..e582b273 100644 --- a/src/CommonLib/Extensions.cs +++ b/src/CommonLib/Extensions.cs @@ -186,7 +186,7 @@ public static string LdapValue(this Guid s) /// /// public static bool IsComputerCollectionSet(this CollectionMethod methods) { - const CollectionMethod test = CollectionMethod.ComputerOnly | CollectionMethod.LoggedOn; + const CollectionMethod test = CollectionMethod.ComputerOnly | CollectionMethod.LoggedOn | CollectionMethod.SmbInfo | CollectionMethod.WebClientService; return (methods & test) != 0; } diff --git a/src/CommonLib/Impersonate.cs b/src/CommonLib/Impersonate.cs index f6e03aae..5c8ba694 100644 --- a/src/CommonLib/Impersonate.cs +++ b/src/CommonLib/Impersonate.cs @@ -86,11 +86,11 @@ public class Impersonator : IDisposable { /// /// Begins impersonation with the given credentials, Logon type and Logon provider. /// - /// Name of the user. - /// Name of the domain. - /// The password. - ///< param name="logonType">Type of the logon. - /// The logon provider. + /// Name of the user. + /// Name of the domain. + /// The password. + ///Type of the logon. + /// The logon provider. public Impersonator(string userName, string domainName, string password, LogonType logonType, LogonProvider logonProvider) { Impersonate(userName, domainName, password, logonType, logonProvider); @@ -125,10 +125,11 @@ public void Dispose() { /// /// Name of the user. /// Name of the domain. - /// The password. - ///< param name="logonType">Type of the logon. + /// The password. + ///Type of the logon. /// The logon provider. - public void Impersonate(string userName, string domainName, string password, LogonType logonType = LogonType.LOGON32_LOGON_INTERACTIVE, + public void Impersonate(string userName, string domainName, string password, + LogonType logonType = LogonType.LOGON32_LOGON_INTERACTIVE, LogonProvider logonProvider = LogonProvider.LOGON32_PROVIDER_DEFAULT) { UndoImpersonation(); @@ -149,14 +150,11 @@ public void Impersonate(string userName, string domainName, string password, Log ref logonTokenDuplicate) != 0) { var wi = new WindowsIdentity(logonTokenDuplicate); wi.Impersonate(); // discard the returned identity context (which is the context of the application pool) - } - else + } else throw new Win32Exception(Marshal.GetLastWin32Error()); - } - else + } else throw new Win32Exception(Marshal.GetLastWin32Error()); - } - finally { + } finally { if (logonToken != IntPtr.Zero) Win32NativeMethods.CloseHandle(logonToken); diff --git a/src/CommonLib/LdapConnectionPool.cs b/src/CommonLib/LdapConnectionPool.cs index 5ec804e5..1073f960 100644 --- a/src/CommonLib/LdapConnectionPool.cs +++ b/src/CommonLib/LdapConnectionPool.cs @@ -16,10 +16,10 @@ using SharpHoundRPC.NetAPINative; namespace SharpHoundCommonLib { - internal class LdapConnectionPool : IDisposable{ + internal class LdapConnectionPool : IDisposable { private readonly ConcurrentBag _connections; private readonly ConcurrentBag _globalCatalogConnection; - private readonly SemaphoreSlim _semaphore; + private readonly SemaphoreSlim _semaphore = null; private readonly string _identifier; private readonly string _poolIdentifier; private readonly LdapConfig _ldapConfig; @@ -45,7 +45,7 @@ public LdapConnectionPool(string identifier, string poolIdentifier, LdapConfig c // //If MaxConcurrentQueries is 0, we'll just disable the semaphore entirely // _semaphore = null; // } - + _identifier = identifier; _poolIdentifier = poolIdentifier; _ldapConfig = config; @@ -53,14 +53,16 @@ public LdapConnectionPool(string identifier, string poolIdentifier, LdapConfig c _portScanner = scanner ?? new PortScanner(); _nativeMethods = nativeMethods ?? new NativeMethods(); } - - private async Task<(bool Success, LdapConnectionWrapper ConnectionWrapper, string Message)> GetLdapConnection(bool globalCatalog) { + + private async Task<(bool Success, LdapConnectionWrapper ConnectionWrapper, string Message)> GetLdapConnection( + bool globalCatalog) { if (globalCatalog) { return await GetGlobalCatalogConnectionAsync(); } + return await GetConnectionAsync(); } - + public async IAsyncEnumerable> Query(LdapQueryParameters queryParameters, [EnumeratorCancellation] CancellationToken cancellationToken = new()) { var setupResult = await SetupLdapQuery(queryParameters); @@ -86,11 +88,14 @@ public async IAsyncEnumerable> Query(LdapQueryParam SearchResponse response = null; while (!cancellationToken.IsCancellationRequested) { //Grab our semaphore here to take one of our query slots - if (_semaphore != null){ - _log.LogTrace("Query entering semaphore with {Count} remaining for query {Info}", _semaphore.CurrentCount, queryParameters.GetQueryInfo()); + if (_semaphore != null) { + _log.LogTrace("Query entering semaphore with {Count} remaining for query {Info}", + _semaphore.CurrentCount, queryParameters.GetQueryInfo()); await _semaphore.WaitAsync(cancellationToken); - _log.LogTrace("Query entered semaphore with {Count} remaining for query {Info}", _semaphore.CurrentCount, queryParameters.GetQueryInfo()); + _log.LogTrace("Query entered semaphore with {Count} remaining for query {Info}", + _semaphore.CurrentCount, queryParameters.GetQueryInfo()); } + try { _log.LogTrace("Sending ldap request - {Info}", queryParameters.GetQueryInfo()); response = (SearchResponse)connectionWrapper.Connection.SendRequest(searchRequest); @@ -117,7 +122,8 @@ public async IAsyncEnumerable> Query(LdapQueryParam * since non-paged queries do not require same server connections */ queryRetryCount++; - _log.LogDebug("Query - Attempting to recover from ServerDown for query {Info} (Attempt {Count})", queryParameters.GetQueryInfo(), queryRetryCount); + _log.LogDebug("Query - Attempting to recover from ServerDown for query {Info} (Attempt {Count})", + queryParameters.GetQueryInfo(), queryRetryCount); ReleaseConnection(connectionWrapper, true); for (var retryCount = 0; retryCount < MaxRetries; retryCount++) { @@ -148,7 +154,8 @@ public async IAsyncEnumerable> Query(LdapQueryParam * The expectation is that given enough time, the server should stop being busy and service our query appropriately */ busyRetryCount++; - _log.LogDebug("Query - Executing busy backoff for query {Info} (Attempt {Count})", queryParameters.GetQueryInfo(), busyRetryCount); + _log.LogDebug("Query - Executing busy backoff for query {Info} (Attempt {Count})", + queryParameters.GetQueryInfo(), busyRetryCount); var backoffDelay = GetNextBackoff(busyRetryCount); await Task.Delay(backoffDelay, cancellationToken); } catch (LdapException le) { @@ -168,9 +175,11 @@ public async IAsyncEnumerable> Query(LdapQueryParam } finally { // Always release our semaphore to prevent deadlocks if (_semaphore != null) { - _log.LogTrace("Query releasing semaphore with {Count} remaining for query {Info}", _semaphore.CurrentCount, queryParameters.GetQueryInfo()); + _log.LogTrace("Query releasing semaphore with {Count} remaining for query {Info}", + _semaphore.CurrentCount, queryParameters.GetQueryInfo()); _semaphore.Release(); - _log.LogTrace("Query released semaphore with {Count} remaining for query {Info}", _semaphore.CurrentCount, queryParameters.GetQueryInfo()); + _log.LogTrace("Query released semaphore with {Count} remaining for query {Info}", + _semaphore.CurrentCount, queryParameters.GetQueryInfo()); } } @@ -197,7 +206,7 @@ public async IAsyncEnumerable> Query(LdapQueryParam yield return LdapResult.Ok(new SearchResultEntryWrapper(entry)); } } - + public async IAsyncEnumerable> PagedQuery(LdapQueryParameters queryParameters, [EnumeratorCancellation] CancellationToken cancellationToken = new()) { var setupResult = await SetupLdapQuery(queryParameters); @@ -225,11 +234,14 @@ public async IAsyncEnumerable> PagedQuery(LdapQuery LdapResult tempResult = null; while (!cancellationToken.IsCancellationRequested) { - if (_semaphore != null){ - _log.LogTrace("PagedQuery entering semaphore with {Count} remaining for query {Info}", _semaphore.CurrentCount, queryParameters.GetQueryInfo()); + if (_semaphore != null) { + _log.LogTrace("PagedQuery entering semaphore with {Count} remaining for query {Info}", + _semaphore.CurrentCount, queryParameters.GetQueryInfo()); await _semaphore.WaitAsync(cancellationToken); - _log.LogTrace("PagedQuery entered semaphore with {Count} remaining for query {Info}", _semaphore.CurrentCount, queryParameters.GetQueryInfo()); + _log.LogTrace("PagedQuery entered semaphore with {Count} remaining for query {Info}", + _semaphore.CurrentCount, queryParameters.GetQueryInfo()); } + SearchResponse response = null; try { _log.LogTrace("Sending paged ldap request - {Info}", queryParameters.GetQueryInfo()); @@ -247,15 +259,15 @@ public async IAsyncEnumerable> PagedQuery(LdapQuery } } catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown) { /* - * A ServerDown exception indicates that our connection is no longer valid for one of many reasons. - * We'll want to release our connection back to the pool, but dispose it. We need a new connection, - * and because this is not a paged query, we can get this connection from anywhere. - * - * We use queryRetryCount here to prevent an infinite retry loop from occurring - * - * Release our connection in a faulted state since the connection is defunct. - * Paged queries require a connection to be made to the same server which we started the paged query on - */ + * A ServerDown exception indicates that our connection is no longer valid for one of many reasons. + * We'll want to release our connection back to the pool, but dispose it. We need a new connection, + * and because this is not a paged query, we can get this connection from anywhere. + * + * We use queryRetryCount here to prevent an infinite retry loop from occurring + * + * Release our connection in a faulted state since the connection is defunct. + * Paged queries require a connection to be made to the same server which we started the paged query on + */ if (serverName == null) { _log.LogError( "PagedQuery - Received server down exception without a known servername. Unable to generate new connection\n{Info}", @@ -263,9 +275,11 @@ public async IAsyncEnumerable> PagedQuery(LdapQuery ReleaseConnection(connectionWrapper, true); yield break; } - - _log.LogDebug("PagedQuery - Attempting to recover from ServerDown for query {Info} (Attempt {Count})", queryParameters.GetQueryInfo(), queryRetryCount); - + + _log.LogDebug( + "PagedQuery - Attempting to recover from ServerDown for query {Info} (Attempt {Count})", + queryParameters.GetQueryInfo(), queryRetryCount); + ReleaseConnection(connectionWrapper, true); for (var retryCount = 0; retryCount < MaxRetries; retryCount++) { var backoffDelay = GetNextBackoff(retryCount); @@ -293,7 +307,8 @@ public async IAsyncEnumerable> PagedQuery(LdapQuery * The expectation is that given enough time, the server should stop being busy and service our query appropriately */ busyRetryCount++; - _log.LogDebug("PagedQuery - Executing busy backoff for query {Info} (Attempt {Count})", queryParameters.GetQueryInfo(), busyRetryCount); + _log.LogDebug("PagedQuery - Executing busy backoff for query {Info} (Attempt {Count})", + queryParameters.GetQueryInfo(), busyRetryCount); var backoffDelay = GetNextBackoff(busyRetryCount); await Task.Delay(backoffDelay, cancellationToken); } catch (LdapException le) { @@ -306,9 +321,11 @@ public async IAsyncEnumerable> PagedQuery(LdapQuery queryParameters); } finally { if (_semaphore != null) { - _log.LogTrace("PagedQuery releasing semaphore with {Count} remaining for query {Info}", _semaphore.CurrentCount, queryParameters.GetQueryInfo()); + _log.LogTrace("PagedQuery releasing semaphore with {Count} remaining for query {Info}", + _semaphore.CurrentCount, queryParameters.GetQueryInfo()); _semaphore.Release(); - _log.LogTrace("PagedQuery released semaphore with {Count} remaining for query {Info}", _semaphore.CurrentCount, queryParameters.GetQueryInfo()); + _log.LogTrace("PagedQuery released semaphore with {Count} remaining for query {Info}", + _semaphore.CurrentCount, queryParameters.GetQueryInfo()); } } @@ -351,7 +368,7 @@ public async IAsyncEnumerable> PagedQuery(LdapQuery pageControl.Cookie = pageResponse.Cookie; } } - + private async Task SetupLdapQuery(LdapQueryParameters queryParameters) { var result = new LdapQuerySetupResult(); var (success, connectionWrapper, message) = @@ -392,10 +409,10 @@ public async IAsyncEnumerable> RangedRetrieval(string distinguish yield return Result.Fail(connectionResult.Message); yield break; } - + var index = 0; var step = 0; - + //Start by using * as our upper index, which will automatically give us the range size var currentRange = $"{attributeName};range={index}-*"; var complete = false; @@ -414,7 +431,7 @@ public async IAsyncEnumerable> RangedRetrieval(string distinguish yield return Result.Fail("Failed to create search request"); yield break; } - + var queryRetryCount = 0; var busyRetryCount = 0; @@ -422,22 +439,28 @@ public async IAsyncEnumerable> RangedRetrieval(string distinguish while (!cancellationToken.IsCancellationRequested) { SearchResponse response = null; - if (_semaphore != null){ - _log.LogTrace("RangedRetrieval entering semaphore with {Count} remaining for query {Info}", _semaphore.CurrentCount, queryParameters.GetQueryInfo()); + if (_semaphore != null) { + _log.LogTrace("RangedRetrieval entering semaphore with {Count} remaining for query {Info}", + _semaphore.CurrentCount, queryParameters.GetQueryInfo()); await _semaphore.WaitAsync(cancellationToken); - _log.LogTrace("RangedRetrieval entered semaphore with {Count} remaining for query {Info}", _semaphore.CurrentCount, queryParameters.GetQueryInfo()); + _log.LogTrace("RangedRetrieval entered semaphore with {Count} remaining for query {Info}", + _semaphore.CurrentCount, queryParameters.GetQueryInfo()); } + try { response = (SearchResponse)connectionWrapper.Connection.SendRequest(searchRequest); } catch (LdapException le) when (le.ErrorCode == (int)ResultCode.Busy && busyRetryCount < MaxRetries) { busyRetryCount++; - _log.LogDebug("RangedRetrieval - Executing busy backoff for query {Info} (Attempt {Count})", queryParameters.GetQueryInfo(), busyRetryCount); + _log.LogDebug("RangedRetrieval - Executing busy backoff for query {Info} (Attempt {Count})", + queryParameters.GetQueryInfo(), busyRetryCount); var backoffDelay = GetNextBackoff(busyRetryCount); await Task.Delay(backoffDelay, cancellationToken); } catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown && queryRetryCount < MaxRetries) { queryRetryCount++; - _log.LogDebug("RangedRetrieval - Attempting to recover from ServerDown for query {Info} (Attempt {Count})", queryParameters.GetQueryInfo(), queryRetryCount); + _log.LogDebug( + "RangedRetrieval - Attempting to recover from ServerDown for query {Info} (Attempt {Count})", + queryParameters.GetQueryInfo(), queryRetryCount); ReleaseConnection(connectionWrapper, true); for (var retryCount = 0; retryCount < MaxRetries; retryCount++) { var backoffDelay = GetNextBackoff(retryCount); @@ -472,9 +495,11 @@ public async IAsyncEnumerable> RangedRetrieval(string distinguish LdapResult.Fail($"Caught unrecoverable exception: {e.Message}", queryParameters); } finally { if (_semaphore != null) { - _log.LogTrace("RangedRetrieval releasing semaphore with {Count} remaining for query {Info}", _semaphore.CurrentCount, queryParameters.GetQueryInfo()); + _log.LogTrace("RangedRetrieval releasing semaphore with {Count} remaining for query {Info}", + _semaphore.CurrentCount, queryParameters.GetQueryInfo()); _semaphore.Release(); - _log.LogTrace("RangedRetrieval released semaphore with {Count} remaining for query {Info}", _semaphore.CurrentCount, queryParameters.GetQueryInfo()); + _log.LogTrace("RangedRetrieval released semaphore with {Count} remaining for query {Info}", + _semaphore.CurrentCount, queryParameters.GetQueryInfo()); } } @@ -526,13 +551,13 @@ public async IAsyncEnumerable> RangedRetrieval(string distinguish ReleaseConnection(connectionWrapper); } - + private static TimeSpan GetNextBackoff(int retryCount) { return TimeSpan.FromSeconds(Math.Min( MinBackoffDelay.TotalSeconds * Math.Pow(BackoffDelayMultiplier, retryCount), MaxBackoffDelay.TotalSeconds)); } - + private bool CreateSearchRequest(LdapQueryParameters queryParameters, LdapConnectionWrapper connectionWrapper, out SearchRequest searchRequest) { string basePath; @@ -543,7 +568,7 @@ private bool CreateSearchRequest(LdapQueryParameters queryParameters, if (CallDsGetDcName(queryParameters.DomainName, out var info) && info != null) { tempPath = Helpers.DomainNameToDistinguishedName(info.Value.DomainName); connectionWrapper.SaveContext(queryParameters.NamingContext, basePath); - } else if (LdapUtils.GetDomain(queryParameters.DomainName,_ldapConfig, out var domainObject)) { + } else if (LdapUtils.GetDomain(queryParameters.DomainName, _ldapConfig, out var domainObject)) { tempPath = Helpers.DomainNameToDistinguishedName(domainObject.Name); } else { searchRequest = null; @@ -559,8 +584,9 @@ private bool CreateSearchRequest(LdapQueryParameters queryParameters, connectionWrapper.SaveContext(queryParameters.NamingContext, basePath); } - - if (string.IsNullOrWhiteSpace(queryParameters.SearchBase) && !string.IsNullOrWhiteSpace(queryParameters.RelativeSearchBase)) { + + if (string.IsNullOrWhiteSpace(queryParameters.SearchBase) && + !string.IsNullOrWhiteSpace(queryParameters.RelativeSearchBase)) { basePath = $"{queryParameters.RelativeSearchBase},{basePath}"; } @@ -579,7 +605,7 @@ private bool CreateSearchRequest(LdapQueryParameters queryParameters, return true; } - + private bool CallDsGetDcName(string domainName, out NetAPIStructs.DomainControllerInfo? info) { if (DCInfoCache.TryGetValue(domainName.ToUpper().Trim(), out info)) return info != null; @@ -607,7 +633,7 @@ private bool CallDsGetDcName(string domainName, out NetAPIStructs.DomainControll if (!success) { return (false, null, message); } - + connectionWrapper = connection; } @@ -641,39 +667,40 @@ public void ReleaseConnection(LdapConnectionWrapper connectionWrapper, bool conn if (!connectionFaulted) { if (connectionWrapper.GlobalCatalog) { _globalCatalogConnection.Add(connectionWrapper); + } else { + _connections.Add(connectionWrapper); } - else { - _connections.Add(connectionWrapper); - } - } - else { + } else { connectionWrapper.Connection.Dispose(); } } - + public void Dispose() { while (_connections.TryTake(out var wrapper)) { wrapper.Connection.Dispose(); } } - private async Task<(bool Success, LdapConnectionWrapper Connection, string Message)> CreateNewConnection(bool globalCatalog = false) { + private async Task<(bool Success, LdapConnectionWrapper Connection, string Message)> CreateNewConnection( + bool globalCatalog = false) { try { if (!string.IsNullOrWhiteSpace(_ldapConfig.Server)) { return CreateNewConnectionForServer(_ldapConfig.Server, globalCatalog); } if (CreateLdapConnection(_identifier.ToUpper().Trim(), globalCatalog, out var connectionWrapper)) { - _log.LogDebug("Successfully created ldap connection for domain: {Domain} using strategy 1. SSL: {SSl}", _identifier, connectionWrapper.Connection.SessionOptions.SecureSocketLayer); + _log.LogDebug( + "Successfully created ldap connection for domain: {Domain} using strategy 1. SSL: {SSl}", + _identifier, connectionWrapper.Connection.SessionOptions.SecureSocketLayer); return (true, connectionWrapper, ""); } - + string tempDomainName; - + var dsGetDcNameResult = _nativeMethods.CallDsGetDcName(null, _identifier, (uint)(NetAPIEnums.DSGETDCNAME_FLAGS.DS_FORCE_REDISCOVERY | - NetAPIEnums.DSGETDCNAME_FLAGS.DS_RETURN_DNS_NAME | - NetAPIEnums.DSGETDCNAME_FLAGS.DS_DIRECTORY_SERVICE_REQUIRED)); + NetAPIEnums.DSGETDCNAME_FLAGS.DS_RETURN_DNS_NAME | + NetAPIEnums.DSGETDCNAME_FLAGS.DS_DIRECTORY_SERVICE_REQUIRED)); if (dsGetDcNameResult.IsSuccess) { tempDomainName = dsGetDcNameResult.Value.DomainName; @@ -684,7 +711,7 @@ public void Dispose() { _identifier, tempDomainName); return (true, connectionWrapper, ""); } - + var server = dsGetDcNameResult.Value.DomainControllerName.TrimStart('\\'); var result = @@ -696,7 +723,7 @@ public void Dispose() { return (true, result.connection, ""); } } - + if (!LdapUtils.GetDomain(_identifier, _ldapConfig, out var domainObject) || domainObject.Name == null) { //If we don't get a result here, we effectively have no other ways to resolve this domain, so we'll just have to exit out _log.LogDebug( @@ -705,8 +732,9 @@ public void Dispose() { _excludedDomains.Add(_identifier); return (false, null, "Unable to get domain object for further strategies"); } + tempDomainName = domainObject.Name.ToUpper().Trim(); - + if (!tempDomainName.Equals(_identifier, StringComparison.OrdinalIgnoreCase) && CreateLdapConnection(tempDomainName, globalCatalog, out connectionWrapper)) { _log.LogDebug( @@ -714,7 +742,7 @@ public void Dispose() { _identifier, tempDomainName); return (true, connectionWrapper, ""); } - + var primaryDomainController = domainObject.PdcRoleOwner.Name; var portConnectionResult = await CreateLDAPConnectionWithPortCheck(primaryDomainController, globalCatalog); @@ -724,7 +752,7 @@ public void Dispose() { _identifier, primaryDomainController); return (true, portConnectionResult.connection, ""); } - + foreach (DomainController dc in domainObject.DomainControllers) { portConnectionResult = await CreateLDAPConnectionWithPortCheck(dc.Name, globalCatalog); @@ -742,27 +770,28 @@ public void Dispose() { return (false, null, "All attempted connections failed"); } - - private (bool Success, LdapConnectionWrapper Connection, string Message ) CreateNewConnectionForServer(string identifier, bool globalCatalog = false) { + + private (bool Success, LdapConnectionWrapper Connection, string Message ) CreateNewConnectionForServer( + string identifier, bool globalCatalog = false) { if (CreateLdapConnection(identifier, globalCatalog, out var serverConnection)) { return (true, serverConnection, ""); } return (false, null, $"Failed to create ldap connection for {identifier}"); } - + private bool CreateLdapConnection(string target, bool globalCatalog, out LdapConnectionWrapper connection) { var baseConnection = CreateBaseConnection(target, true, globalCatalog); if (TestLdapConnection(baseConnection, out var result)) { - connection = new LdapConnectionWrapper(baseConnection, result.SearchResultEntry, globalCatalog, _poolIdentifier); + connection = new LdapConnectionWrapper(baseConnection, result.SearchResultEntry, globalCatalog, + _poolIdentifier); return true; } try { baseConnection.Dispose(); - } - catch { + } catch { //this is just in case } @@ -773,43 +802,42 @@ private bool CreateLdapConnection(string target, bool globalCatalog, baseConnection = CreateBaseConnection(target, false, globalCatalog); if (TestLdapConnection(baseConnection, out result)) { - connection = new LdapConnectionWrapper(baseConnection, result.SearchResultEntry, globalCatalog, _poolIdentifier); + connection = new LdapConnectionWrapper(baseConnection, result.SearchResultEntry, globalCatalog, + _poolIdentifier); return true; } try { baseConnection.Dispose(); - } - catch { + } catch { //this is just in case } connection = null; return false; } - + private LdapConnection CreateBaseConnection(string directoryIdentifier, bool ssl, bool globalCatalog) { _log.LogDebug("Creating connection for identifier {Identifier}", directoryIdentifier); var port = globalCatalog ? _ldapConfig.GetGCPort(ssl) : _ldapConfig.GetPort(ssl); var identifier = new LdapDirectoryIdentifier(directoryIdentifier, port, false, false); var connection = new LdapConnection(identifier) { Timeout = new TimeSpan(0, 0, 5, 0) }; - + //These options are important! connection.SessionOptions.ProtocolVersion = 3; //Referral chasing does not work with paged searches connection.SessionOptions.ReferralChasing = ReferralChasingOptions.None; if (ssl) connection.SessionOptions.SecureSocketLayer = true; - + if (_ldapConfig.DisableSigning || ssl) { connection.SessionOptions.Signing = false; connection.SessionOptions.Sealing = false; - } - else { + } else { connection.SessionOptions.Signing = true; connection.SessionOptions.Sealing = true; } - + if (_ldapConfig.DisableCertVerification) connection.SessionOptions.VerifyServerCertificate = (_, _) => true; @@ -839,10 +867,10 @@ private bool TestLdapConnection(LdapConnection connection, out LdapConnectionTes try { //Attempt an initial bind. If this fails, likely auth is invalid, or its not a valid target connection.Bind(); - } - catch (LdapException e) { + } catch (LdapException e) { //TODO: Maybe look at this and find a better way? - if (e.ErrorCode is (int)LdapErrorCodes.InvalidCredentials or (int)ResultCode.InappropriateAuthentication) { + if (e.ErrorCode is (int)LdapErrorCodes.InvalidCredentials + or (int)ResultCode.InappropriateAuthentication) { connection.Dispose(); throw new LdapAuthenticationException(e); } @@ -850,8 +878,7 @@ private bool TestLdapConnection(LdapConnection connection, out LdapConnectionTes testResult.Message = e.Message; testResult.ErrorCode = e.ErrorCode; return false; - } - catch (Exception e) { + } catch (Exception e) { testResult.Message = e.Message; return false; } @@ -864,8 +891,7 @@ private bool TestLdapConnection(LdapConnection connection, out LdapConnectionTes SearchScope.Base, null); response = (SearchResponse)connection.SendRequest(searchRequest); - } - catch (LdapException e) { + } catch (LdapException e) { /* * If we can't send the initial search request, its unlikely any other search requests will work so we will immediately return false */ @@ -880,7 +906,7 @@ private bool TestLdapConnection(LdapConnection connection, out LdapConnectionTes * across external trusts with kerberos authentication without Forest Search Order properly configured. * Either way, this connection isn't useful for us because we're not going to get data, so return false */ - + connection.Dispose(); throw new NoLdapDataException(); } @@ -895,15 +921,14 @@ private class LdapConnectionTestResult { public IDirectoryObject SearchResultEntry { get; set; } public int ErrorCode { get; set; } } - + private async Task<(bool success, LdapConnectionWrapper connection)> CreateLDAPConnectionWithPortCheck( string target, bool globalCatalog) { if (globalCatalog) { if (await _portScanner.CheckPort(target, _ldapConfig.GetGCPort(true)) || (!_ldapConfig.ForceSSL && await _portScanner.CheckPort(target, _ldapConfig.GetGCPort(false)))) return (CreateLdapConnection(target, true, out var connection), connection); - } - else { + } else { if (await _portScanner.CheckPort(target, _ldapConfig.GetPort(true)) || (!_ldapConfig.ForceSSL && await _portScanner.CheckPort(target, _ldapConfig.GetPort(false)))) return (CreateLdapConnection(target, true, out var connection), connection); @@ -911,7 +936,7 @@ await _portScanner.CheckPort(target, _ldapConfig.GetPort(false)))) return (false, null); } - + private SearchRequest CreateSearchRequest(string distinguishedName, string ldapFilter, SearchScope searchScope, string[] attributes) { diff --git a/src/CommonLib/LdapProducerQueryGenerator.cs b/src/CommonLib/LdapProducerQueryGenerator.cs index 0d6580f8..25c6cb9b 100644 --- a/src/CommonLib/LdapProducerQueryGenerator.cs +++ b/src/CommonLib/LdapProducerQueryGenerator.cs @@ -9,11 +9,12 @@ public class LdapProducerQueryGenerator { public static GeneratedLdapParameters GenerateDefaultPartitionParameters(CollectionMethod methods) { var filter = new LdapFilter(); var properties = new List(); - + properties.AddRange(CommonProperties.BaseQueryProps); properties.AddRange(CommonProperties.TypeResolutionProps); - if (methods.HasFlag(CollectionMethod.ObjectProps) || methods.HasFlag(CollectionMethod.ACL) || methods.HasFlag(CollectionMethod.Container)) { + if (methods.HasFlag(CollectionMethod.ObjectProps) || methods.HasFlag(CollectionMethod.ACL) || + methods.HasFlag(CollectionMethod.Container)) { filter = filter.AddComputers().AddDomains().AddUsers().AddContainers().AddGPOs().AddOUs().AddGroups(); if (methods.HasFlag(CollectionMethod.Container)) { @@ -39,14 +40,14 @@ public static GeneratedLdapParameters GenerateDefaultPartitionParameters(Collect if (methods.HasFlag(CollectionMethod.Trusts)) { properties.AddRange(CommonProperties.DomainTrustProps); } - + if (methods.HasFlag(CollectionMethod.GPOLocalGroup)) properties.AddRange(CommonProperties.GPOLocalGroupProps); if (methods.HasFlag(CollectionMethod.SPNTargets)) properties.AddRange(CommonProperties.SPNTargetProps); - if (methods.HasFlag(CollectionMethod.DCRegistry)) + if (methods.IsComputerCollectionSet()) properties.AddRange(CommonProperties.ComputerMethodProps); if (methods.HasFlag(CollectionMethod.SPNTargets)) { @@ -79,16 +80,16 @@ public static GeneratedLdapParameters GenerateDefaultPartitionParameters(Collect properties.AddRange(CommonProperties.GPOLocalGroupProps); } - if (methods.HasFlag(CollectionMethod.DCRegistry)) { + if (methods.HasFlag(CollectionMethod.DCRegistry) || methods.HasFlag(CollectionMethod.LdapServices)) { filter = filter.AddComputers(CommonFilters.DomainControllers); properties.AddRange(CommonProperties.ComputerMethodProps); } - + if (methods.HasFlag(CollectionMethod.Group)) { filter = filter.AddGroups(); properties.AddRange(CommonProperties.GroupResolutionProps); } - + return new GeneratedLdapParameters { Filter = filter, Attributes = properties.Distinct().ToArray() @@ -98,7 +99,7 @@ public static GeneratedLdapParameters GenerateDefaultPartitionParameters(Collect public static GeneratedLdapParameters GenerateConfigurationPartitionParameters(CollectionMethod methods) { var filter = new LdapFilter(); var properties = new List(); - + properties.AddRange(CommonProperties.BaseQueryProps); properties.AddRange(CommonProperties.TypeResolutionProps); @@ -106,9 +107,8 @@ public static GeneratedLdapParameters GenerateConfigurationPartitionParameters(C methods.HasFlag(CollectionMethod.Container) || methods.HasFlag(CollectionMethod.CertServices)) { filter = filter.AddContainers().AddConfiguration().AddCertificateTemplates().AddCertificateAuthorities() .AddEnterpriseCertificationAuthorities().AddIssuancePolicies(); - - if (methods.HasFlag(CollectionMethod.ObjectProps)) - { + + if (methods.HasFlag(CollectionMethod.ObjectProps)) { properties.AddRange(CommonProperties.ObjectPropsProps); } diff --git a/src/CommonLib/LdapUtils.cs b/src/CommonLib/LdapUtils.cs index a9166aff..a805d54d 100644 --- a/src/CommonLib/LdapUtils.cs +++ b/src/CommonLib/LdapUtils.cs @@ -182,7 +182,7 @@ public IAsyncEnumerable> PagedQuery(LdapQueryParame } catch { //pass } - + return (false, Label.Base); } @@ -227,7 +227,7 @@ public IAsyncEnumerable> PagedQuery(LdapQueryParame } catch { //pass } - + return (false, Label.Base); } @@ -361,7 +361,7 @@ public IAsyncEnumerable> PagedQuery(LdapQueryParame } catch { //pass } - + return (false, string.Empty); } @@ -904,7 +904,6 @@ public async Task IsDomainController(string computerObjectId, string domai _unresolvablePrincipals.Add(distinguishedName); return (false, default); } - } public async Task<(bool Success, string DSHeuristics)> GetDSHueristics(string domain, string dn) { @@ -961,7 +960,7 @@ public async IAsyncEnumerable GetWellKnownPrincipalOutput() { yield return entdc; } } - + private async IAsyncEnumerable GetEnterpriseDCGroups() { var grouped = new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase); var forestSidToName = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); @@ -985,6 +984,7 @@ await GetDomainSidFromDomainName(forestName) is (true, var forestDomainSid)) { if (!forestSidToName.TryGetValue(f.Key, out var forestName)) { continue; } + var group = new Group { ObjectIdentifier = $"{forestName}-S-1-5-9" }; group.Properties.Add("name", $"ENTERPRISE DOMAIN CONTROLLERS@{forestName}".ToUpper()); group.Properties.Add("domainsid", f.Key); @@ -1203,6 +1203,15 @@ await utils.GetDomainNameFromSid(objectIdentifier) is (true, var domainName)) { return (true, res); } + res.ObjectType = await ComputeLabel(directoryObject, objectIdentifier, domain, utils); + + directoryObject.TryGetProperty(LDAPProperties.SAMAccountName, out var samAccountName); + res.DisplayName = ComputeDisplayName(directoryObject, domain, res.ObjectType, samAccountName); + return (true, res); + } + + private static async Task