< Summary

Class:SharpHoundCommonLib.Processors.ComputerAvailability
Assembly:SharpHoundCommonLib
File(s):D:\a\SharpHoundCommon\SharpHoundCommon\src\CommonLib\Processors\ComputerAvailability.cs
Covered lines:90
Uncovered lines:12
Coverable lines:102
Total lines:167
Line coverage:88.2% (90 of 102)
Covered branches:21
Total branches:32
Branch coverage:65.6% (21 of 32)

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%20100%
.ctor(...)100%20100%
IsComputerAvailable(...)100%100%
IsComputerAvailable()63.63%22092.3%
IsComputerActive(...)100%20100%
SendComputerStatus()25%40100%

File(s)

D:\a\SharpHoundCommon\SharpHoundCommon\src\CommonLib\Processors\ComputerAvailability.cs

#LineLine coverage
 1using System;
 2using System.Threading.Tasks;
 3using Microsoft.Extensions.Logging;
 4using SharpHoundCommonLib.OutputTypes;
 5
 6namespace SharpHoundCommonLib.Processors
 7{
 8    public class ComputerAvailability
 9    {
 10        public delegate Task ComputerStatusDelegate(CSVComputerStatus status);
 11
 12        private readonly int _computerExpiryDays;
 13        private readonly ILogger _log;
 14        private readonly PortScanner _scanner;
 15        private readonly int _scanTimeout;
 16        private readonly bool _skipPasswordCheck;
 17        private readonly bool _skipPortScan;
 18
 219        public ComputerAvailability(int timeout = 10000, int computerExpiryDays = 60, bool skipPortScan = false,
 220            bool skipPasswordCheck = false, ILogger log = null)
 221        {
 222            _scanner = new PortScanner();
 223            _scanTimeout = timeout;
 224            _skipPortScan = skipPortScan;
 225            _log = log ?? Logging.LogProvider.CreateLogger("CompAvail");
 226            _computerExpiryDays = computerExpiryDays;
 227            _skipPasswordCheck = skipPasswordCheck;
 228        }
 29
 230        public ComputerAvailability(PortScanner scanner, int timeout = 500, int computerExpiryDays = 60,
 231            bool skipPortScan = false, bool skipPasswordCheck = false,
 232            ILogger log = null)
 233        {
 234            _scanner = scanner;
 235            _scanTimeout = timeout;
 236            _skipPortScan = skipPortScan;
 237            _log = log ?? Logging.LogProvider.CreateLogger("CompAvail");
 238            _computerExpiryDays = computerExpiryDays;
 239            _skipPasswordCheck = skipPasswordCheck;
 240        }
 41
 42        public event ComputerStatusDelegate ComputerStatusEvent;
 43
 44        /// <summary>
 45        ///     Helper function to use commonlib types for IsComputerAvailable
 46        /// </summary>
 47        /// <param name="result"></param>
 48        /// <param name="entry"></param>
 49        /// <returns></returns>
 50        public Task<ComputerStatus> IsComputerAvailable(ResolvedSearchResult result, IDirectoryObject entry)
 051        {
 052            var name = result.DisplayName;
 053            var os = entry.GetProperty(LDAPProperties.OperatingSystem);
 054            var pwdlastset = entry.GetProperty(LDAPProperties.PasswordLastSet);
 055            var lastLogon = entry.GetProperty(LDAPProperties.LastLogonTimestamp);
 56
 057            return IsComputerAvailable(name, os, pwdlastset, lastLogon);
 058        }
 59
 60        /// <summary>
 61        ///     Checks if a computer is available for SharpHound enumeration using the following criteria:
 62        ///     The "operatingsystem" LDAP attribute must contain the string "Windows"
 63        ///     The "pwdlastset" LDAP attribute must be within 60 days of the current date by default.
 64        ///     Port 445 must be open to allow API calls to succeed
 65        /// </summary>
 66        /// <param name="computerName">The computer to check availability for</param>
 67        /// <param name="operatingSystem">The LDAP operatingsystem attribute value</param>
 68        /// <param name="pwdLastSet">The LDAP pwdlastset attribute value</param>
 69        /// <param name="lastLogon">The LDAP lastlogontimestamp attribute value</param>
 70        /// <returns>A <cref>ComputerStatus</cref> object that represents the availability of the computer</returns>
 71        public async Task<ComputerStatus> IsComputerAvailable(string computerName, string operatingSystem,
 72            string pwdLastSet, string lastLogon)
 473        {
 474            if (operatingSystem != null && !operatingSystem.StartsWith("Windows", StringComparison.OrdinalIgnoreCase))
 175            {
 176                _log.LogTrace("{ComputerName} is not available because operating system {OperatingSystem} is not valid",
 177                    computerName, operatingSystem);
 178                await SendComputerStatus(new CSVComputerStatus
 179                {
 180                    Status = ComputerStatus.NonWindowsOS,
 181                    Task = "ComputerAvailability",
 182                    ComputerName = computerName
 183                });
 184                return new ComputerStatus
 185                {
 186                    Connectable = false,
 187                    Error = ComputerStatus.NonWindowsOS
 188                };
 89            }
 90
 391            if (!_skipPasswordCheck && !IsComputerActive(pwdLastSet, lastLogon))
 192            {
 193                _log.LogTrace(
 194                    "{ComputerName} is not available because password last set and lastlogontimestamp are out of range",
 195                    computerName);
 196                await SendComputerStatus(new CSVComputerStatus
 197                {
 198                    Status = ComputerStatus.NotActive,
 199                    Task = "ComputerAvailability",
 1100                    ComputerName = computerName
 1101                });
 1102                return new ComputerStatus
 1103                {
 1104                    Connectable = false,
 1105                    Error = ComputerStatus.NotActive
 1106                };
 107            }
 108
 2109            if (_skipPortScan)
 0110                return new ComputerStatus
 0111                {
 0112                    Connectable = true,
 0113                    Error = null
 0114                };
 115
 2116            if (!await _scanner.CheckPort(computerName, timeout: _scanTimeout))
 1117            {
 1118                _log.LogTrace("{ComputerName} is not available because port 445 is unavailable", computerName);
 1119                await SendComputerStatus(new CSVComputerStatus
 1120                {
 1121                    Status = ComputerStatus.PortNotOpen,
 1122                    Task = "ComputerAvailability",
 1123                    ComputerName = computerName
 1124                });
 1125                return new ComputerStatus
 1126                {
 1127                    Connectable = false,
 1128                    Error = ComputerStatus.PortNotOpen
 1129                };
 130            }
 131
 1132            _log.LogTrace("{ComputerName} is available for enumeration", computerName);
 133
 1134            await SendComputerStatus(new CSVComputerStatus
 1135            {
 1136                Status = CSVComputerStatus.StatusSuccess,
 1137                Task = "ComputerAvailability",
 1138                ComputerName = computerName
 1139            });
 140
 1141            return new ComputerStatus
 1142            {
 1143                Connectable = true,
 1144                Error = null
 1145            };
 4146        }
 147
 148        /// <summary>
 149        /// Checks if a computer's passwordlastset/lastlogontimestamp attributes are within a certain range
 150        /// </summary>
 151        /// <param name="pwdLastSet"></param>
 152        /// <param name="lastLogonTimestamp"></param>
 153        /// <returns></returns>
 3154        private bool IsComputerActive(string pwdLastSet, string lastLogonTimestamp) {
 3155            var passwordLastSet = Helpers.ConvertLdapTimeToLong(pwdLastSet);
 3156            var lastLogonTimeStamp = Helpers.ConvertLdapTimeToLong(lastLogonTimestamp);
 3157            var threshold = DateTime.Now.AddDays(_computerExpiryDays * -1).ToFileTimeUtc();
 158
 3159            return passwordLastSet >= threshold || lastLogonTimeStamp >= threshold;
 3160        }
 161
 162        private async Task SendComputerStatus(CSVComputerStatus status)
 4163        {
 4164            if (ComputerStatusEvent is not null) await ComputerStatusEvent.Invoke(status);
 4165        }
 166    }
 167}