< Summary

Class:SharpHoundCommonLib.ConnectionPoolManager
Assembly:SharpHoundCommonLib
File(s):D:\a\SharpHoundCommon\SharpHoundCommon\src\CommonLib\ConnectionPoolManager.cs
Covered lines:41
Uncovered lines:66
Coverable lines:107
Total lines:175
Line coverage:38.3% (41 of 107)
Covered branches:14
Total branches:46
Branch coverage:30.4% (14 of 46)

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)75%40100%
RangedRetrieval(...)0%200%
PagedQuery(...)0%200%
Query(...)50%2080%
ReleaseConnection(...)0%400%
TestDomainConnection()0%200%
GetPool(...)75%4081.81%
GetLdapConnection()0%800%
GetLdapConnectionForServer(...)0%200%
ResolveIdentifier(...)50%4055.55%
GetDomainSidFromDomainName(...)50%10050%
Dispose()0%200%

File(s)

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

#LineLine coverage
 1using System;
 2using System.Collections.Concurrent;
 3using System.Collections.Generic;
 4using System.DirectoryServices;
 5using System.Security.Principal;
 6using System.Threading;
 7using System.Threading.Tasks;
 8using Microsoft.Extensions.Logging;
 9using SharpHoundCommonLib.Processors;
 10
 11namespace SharpHoundCommonLib {
 12    internal class ConnectionPoolManager : IDisposable{
 6013        private readonly ConcurrentDictionary<string, LdapConnectionPool> _pools = new();
 14        private readonly LdapConfig _ldapConfig;
 6015        private readonly string[] _translateNames = { "Administrator", "admin" };
 6016        private readonly ConcurrentDictionary<string, string> _resolvedIdentifiers = new(StringComparer.OrdinalIgnoreCas
 17        private readonly ILogger _log;
 18        private readonly PortScanner _portScanner;
 19
 12020        public ConnectionPoolManager(LdapConfig config, ILogger log = null, PortScanner scanner = null) {
 6021            _ldapConfig = config;
 6022            _log = log ?? Logging.LogProvider.CreateLogger("ConnectionPoolManager");
 6023            _portScanner = scanner ?? new PortScanner();
 6024        }
 25
 26        public IAsyncEnumerable<Result<string>> RangedRetrieval(string distinguishedName,
 027            string attributeName, CancellationToken cancellationToken = new()) {
 028            var domain = Helpers.DistinguishedNameToDomain(distinguishedName);
 29
 030            if (!GetPool(domain, out var pool)) {
 031                return new List<Result<string>> {Result<string>.Fail("Failed to resolve a connection pool")}.ToAsyncEnum
 32            }
 33
 034            return pool.RangedRetrieval(distinguishedName, attributeName, cancellationToken);
 035        }
 36
 37        public IAsyncEnumerable<LdapResult<IDirectoryObject>> PagedQuery(LdapQueryParameters queryParameters,
 038            CancellationToken cancellationToken = new()) {
 039            if (!GetPool(queryParameters.DomainName, out var pool)) {
 040                return new List<LdapResult<IDirectoryObject>> {LdapResult<IDirectoryObject>.Fail("Failed to resolve a co
 41            }
 42
 043            return pool.PagedQuery(queryParameters, cancellationToken);
 044        }
 45
 46        public IAsyncEnumerable<LdapResult<IDirectoryObject>> Query(LdapQueryParameters queryParameters,
 147            CancellationToken cancellationToken = new()) {
 148            if (!GetPool(queryParameters.DomainName, out var pool)) {
 049                return new List<LdapResult<IDirectoryObject>> {LdapResult<IDirectoryObject>.Fail("Failed to resolve a co
 50            }
 51
 152            return pool.Query(queryParameters, cancellationToken);
 153        }
 54
 055        public void ReleaseConnection(LdapConnectionWrapper connectionWrapper, bool connectionFaulted = false) {
 056            if (connectionWrapper == null) {
 057                return;
 58            }
 59            //I don't think this is possible, but at least account for it
 060            if (!_pools.TryGetValue(connectionWrapper.PoolIdentifier, out var pool)) {
 061                _log.LogWarning("Could not find pool for {Identifier}", connectionWrapper.PoolIdentifier);
 062                connectionWrapper.Connection.Dispose();
 063                return;
 64            }
 65
 066            pool.ReleaseConnection(connectionWrapper, connectionFaulted);
 067        }
 68
 069        public async Task<(bool Success, string Message)> TestDomainConnection(string identifier, bool globalCatalog) {
 070            var (success, connection, message) = await GetLdapConnection(identifier, globalCatalog);
 071            ReleaseConnection(connection);
 072            return (success, message);
 073        }
 74
 175        private bool GetPool(string identifier, out LdapConnectionPool pool) {
 176            if (identifier == null) {
 077                pool = default;
 078                return false;
 79            }
 80
 181            var resolved = ResolveIdentifier(identifier);
 282            if (!_pools.TryGetValue(resolved, out pool)) {
 183                pool = new LdapConnectionPool(identifier, resolved, _ldapConfig,scanner: _portScanner);
 184                _pools.TryAdd(resolved, pool);
 185            }
 86
 187            return true;
 188        }
 89
 90        public async Task<(bool Success, LdapConnectionWrapper ConnectionWrapper, string Message)> GetLdapConnection(
 091            string identifier, bool globalCatalog) {
 092            if (!GetPool(identifier, out var pool)) {
 093                return (false, default, $"Unable to resolve a pool for {identifier}");
 94            }
 95
 096            if (globalCatalog) {
 097                return await pool.GetGlobalCatalogConnectionAsync();
 98            }
 099            return await pool.GetConnectionAsync();
 0100        }
 101
 102        public (bool Success, LdapConnectionWrapper connectionWrapper, string Message) GetLdapConnectionForServer(
 0103            string identifier, string server, bool globalCatalog) {
 0104            if (!GetPool(identifier, out var pool)) {
 0105                return (false, default, $"Unable to resolve a pool for {identifier}");
 106            }
 107
 0108            return pool.GetConnectionForSpecificServerAsync(server, globalCatalog);
 0109        }
 110
 1111        private string ResolveIdentifier(string identifier) {
 1112            if (_resolvedIdentifiers.TryGetValue(identifier, out var resolved)) {
 0113                return resolved;
 114            }
 115
 1116            if (GetDomainSidFromDomainName(identifier, out var sid)) {
 0117                _log.LogDebug("Resolved identifier {Identifier} to {Resolved}", identifier, sid);
 0118                _resolvedIdentifiers.TryAdd(identifier, sid);
 0119                return sid;
 120            }
 121
 1122            return identifier;
 1123        }
 124
 1125        private bool GetDomainSidFromDomainName(string domainName, out string domainSid) {
 1126            if (Cache.GetDomainSidMapping(domainName, out domainSid)) return true;
 127
 1128            try {
 1129                var entry = new DirectoryEntry($"LDAP://{domainName}").ToDirectoryObject();
 1130                if (entry.TryGetSecurityIdentifier(out var sid)) {
 0131                    Cache.AddDomainSidMapping(domainName, sid);
 0132                    domainSid = sid;
 0133                    return true;
 134                }
 1135            }
 0136            catch {
 137                //we expect this to fail sometimes
 0138            }
 139
 1140            if (LdapUtils.GetDomain(domainName, _ldapConfig, out var domainObject))
 0141                try {
 0142                    if (domainObject.GetDirectoryEntry().ToDirectoryObject().TryGetSecurityIdentifier(out domainSid)) {
 0143                        Cache.AddDomainSidMapping(domainName, domainSid);
 0144                        return true;
 145                    }
 0146                }
 0147                catch {
 148                    //we expect this to fail sometimes (not sure why, but better safe than sorry)
 0149                }
 150
 7151            foreach (var name in _translateNames)
 2152                try {
 2153                    var account = new NTAccount(domainName, name);
 2154                    var sid = (SecurityIdentifier)account.Translate(typeof(SecurityIdentifier));
 0155                    domainSid = sid.AccountDomainSid.ToString();
 0156                    Cache.AddDomainSidMapping(domainName, domainSid);
 0157                    return true;
 158                }
 4159                catch {
 160                    //We expect this to fail if the username doesn't exist in the domain
 2161                }
 162
 1163            return false;
 1164        }
 165
 0166        public void Dispose() {
 0167            foreach (var kv in _pools)
 0168            {
 0169                kv.Value.Dispose();
 0170            }
 171
 0172            _pools.Clear();
 0173        }
 174    }
 175}