390 lines
14 KiB
C#
390 lines
14 KiB
C#
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.DirectoryServices;
|
|
using System.DirectoryServices.ActiveDirectory;
|
|
using System.DirectoryServices.AccountManagement;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Reflection;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
using System.Security.AccessControl;
|
|
using System.Security.Principal;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading.Tasks;
|
|
using C4IT.Logging;
|
|
using static C4IT.Logging.cLogManager;
|
|
namespace LiamNtfs
|
|
{
|
|
public class cActiveDirectoryBase
|
|
{
|
|
private cNtfsLogonInfo privLogonInfo = null;
|
|
public PrincipalContext adContext = null;
|
|
public DirectoryEntry directoryEntry = null;
|
|
public string EffectiveDomainController { get; private set; } = null;
|
|
public Exception LastException { get; private set; } = null;
|
|
public string LastErrorMessage { get; private set; } = null;
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void ResetError()
|
|
{
|
|
LastException = null;
|
|
LastErrorMessage = null;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void SetErrorException(string Action, Exception E, LogLevels lev = LogLevels.Error)
|
|
{
|
|
LastException = E;
|
|
LastErrorMessage = Action + ": " + E.Message;
|
|
cLogManager.LogEntry(Action, lev);
|
|
}
|
|
|
|
private async Task<bool> privLogonAsync(cNtfsLogonInfo LogonInfo)
|
|
{
|
|
try
|
|
{
|
|
//TODO: remove dummy delay?
|
|
await Task.Delay(0);
|
|
ResetError();
|
|
var adServer = ResolveEffectiveDomainController(LogonInfo);
|
|
adContext = new PrincipalContext(ContextType.Domain, adServer, LogonInfo.User, new NetworkCredential("", LogonInfo.UserSecret).Password);
|
|
EffectiveDomainController = adContext.ConnectedServer ?? adServer;
|
|
LogEntry($"NTFS AD domain controller pinned to '{EffectiveDomainController}' for domain '{LogonInfo.Domain}'.", LogLevels.Debug);
|
|
|
|
var ldapPath = $"LDAP://{EffectiveDomainController}/{LogonInfo.TargetGroupPath}";
|
|
directoryEntry = new DirectoryEntry
|
|
{
|
|
Path = ldapPath,
|
|
Username = LogonInfo.User,
|
|
Password = new NetworkCredential(LogonInfo.User, LogonInfo.UserSecret).Password,
|
|
AuthenticationType = AuthenticationTypes.Secure | AuthenticationTypes.Sealing
|
|
};
|
|
return adContext != null;
|
|
}
|
|
catch (Exception E)
|
|
{
|
|
SetErrorException("exception error while ad login", E, LogLevels.Debug);
|
|
cLogManager.LogException(E, LogLevels.Debug);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private string ResolveEffectiveDomainController(cNtfsLogonInfo logonInfo)
|
|
{
|
|
var configuredDomainControllers = ParseDomainControllers(logonInfo?.DomainControllers);
|
|
foreach (var domainController in configuredDomainControllers)
|
|
{
|
|
if (CanConnectToDomainController(domainController, logonInfo))
|
|
return domainController;
|
|
|
|
LogEntry($"Configured NTFS AD domain controller '{domainController}' is not reachable. Trying next candidate.", LogLevels.Warning);
|
|
}
|
|
|
|
var pdc = TryGetPdcRoleOwner(logonInfo);
|
|
if (!string.IsNullOrWhiteSpace(pdc))
|
|
return pdc;
|
|
|
|
LogEntry($"Could not determine PDC emulator for domain '{logonInfo?.Domain}'. Falling back to domain locator.", LogLevels.Warning);
|
|
return logonInfo?.Domain;
|
|
}
|
|
|
|
private static IEnumerable<string> ParseDomainControllers(string domainControllers)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(domainControllers))
|
|
return Enumerable.Empty<string>();
|
|
|
|
return domainControllers
|
|
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
|
|
.Select(i => i.Trim())
|
|
.Where(i => !string.IsNullOrWhiteSpace(i));
|
|
}
|
|
|
|
private bool CanConnectToDomainController(string domainController, cNtfsLogonInfo logonInfo)
|
|
{
|
|
try
|
|
{
|
|
using (var context = new PrincipalContext(ContextType.Domain, domainController, logonInfo.User, new NetworkCredential("", logonInfo.UserSecret).Password))
|
|
return !string.IsNullOrWhiteSpace(context.ConnectedServer);
|
|
}
|
|
catch (Exception E)
|
|
{
|
|
LogException(E, LogLevels.Debug);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private string TryGetPdcRoleOwner(cNtfsLogonInfo logonInfo)
|
|
{
|
|
try
|
|
{
|
|
var credentials = new DirectoryContext(
|
|
DirectoryContextType.Domain,
|
|
logonInfo.Domain,
|
|
logonInfo.User,
|
|
new NetworkCredential("", logonInfo.UserSecret).Password);
|
|
|
|
using (var domain = Domain.GetDomain(credentials))
|
|
return domain?.PdcRoleOwner?.Name;
|
|
}
|
|
catch (Exception E)
|
|
{
|
|
LogException(E, LogLevels.Debug);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private async Task<bool> privRelogon()
|
|
{
|
|
if (privLogonInfo == null)
|
|
return false;
|
|
var RetVal = await privLogonAsync(privLogonInfo);
|
|
return RetVal;
|
|
}
|
|
|
|
public async Task<bool> LogonAsync(cNtfsLogonInfo LogonInfo)
|
|
{
|
|
var RetVal = await privLogonAsync(LogonInfo);
|
|
if (RetVal == true)
|
|
privLogonInfo = LogonInfo;
|
|
return RetVal;
|
|
}
|
|
|
|
internal AuthorizationRuleCollection GetAccessControlList(string path)
|
|
{
|
|
var CM = MethodBase.GetCurrentMethod();
|
|
LogMethodBegin(CM);
|
|
try
|
|
{
|
|
DirectoryInfo dADir = new DirectoryInfo(path);
|
|
var dAACL = dADir.GetAccessControl();
|
|
return dAACL.GetAccessRules(true, false, typeof(System.Security.Principal.SecurityIdentifier));
|
|
}
|
|
catch (Exception E)
|
|
{
|
|
LogException(E);
|
|
return null;
|
|
}
|
|
finally
|
|
{
|
|
LogMethodEnd(CM);
|
|
}
|
|
|
|
}
|
|
|
|
internal string resolveSid(string sid)
|
|
{
|
|
|
|
try
|
|
{
|
|
return new System.Security.Principal.SecurityIdentifier(sid).Translate(typeof(System.Security.Principal.NTAccount)).ToString();
|
|
}
|
|
catch (Exception E)
|
|
{
|
|
LogException(E);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
internal async Task<cADCollectionBase> RequestSecurityGroupsListAsync(string groupFilter)
|
|
{
|
|
var CM = MethodBase.GetCurrentMethod();
|
|
LogMethodBegin(CM);
|
|
try
|
|
{
|
|
await Task.Delay(0);
|
|
|
|
var Result = privRequestSecurityGroupsListAsync(groupFilter);
|
|
if (Result != null)
|
|
{
|
|
var RetVal = new cADCollectionBase(Result.Count);
|
|
foreach (var Entry in Result)
|
|
{
|
|
var res = new cSecurityGroupResult(Entry);
|
|
RetVal.Add(res);
|
|
}
|
|
return RetVal;
|
|
}
|
|
}
|
|
catch (Exception E)
|
|
{
|
|
LogException(E);
|
|
return null;
|
|
}
|
|
finally
|
|
{
|
|
LogMethodEnd(CM);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private List<cSecurityGroupResult> privRequestSecurityGroupsListAsync(string groupFilter = null, string rawLDAPFilter = null)
|
|
{
|
|
ResetError();
|
|
List<cSecurityGroupResult> securityGroups = new List<cSecurityGroupResult>();
|
|
try
|
|
{
|
|
var res = new List<cSecurityGroupResult>();
|
|
if (String.IsNullOrEmpty(privLogonInfo.TargetGroupPath) || string.IsNullOrEmpty(groupFilter) && string.IsNullOrEmpty(rawLDAPFilter))
|
|
return res;
|
|
try
|
|
{
|
|
var ctx = adContext;
|
|
var entry = directoryEntry;
|
|
DirectorySearcher dSearch = new DirectorySearcher(entry)
|
|
{
|
|
Filter = string.IsNullOrEmpty(rawLDAPFilter) ? "(&(" + groupFilter + ")(objectClass=group))" : rawLDAPFilter
|
|
};
|
|
dSearch.PageSize = 100000;
|
|
SearchResultCollection sr = dSearch.FindAll();
|
|
if (sr.Count > 0)
|
|
{
|
|
foreach (SearchResult k in sr)
|
|
{
|
|
var sid = new SecurityIdentifier(k.Properties["objectSid"][0] as byte[], 0).Value;
|
|
var dn = k.Properties["distinguishedname"][0].ToString();
|
|
cSecurityGroupResult group = new cSecurityGroupResult()
|
|
{
|
|
ID = sid,
|
|
Path = k.Properties["distinguishedname"][0].ToString(),
|
|
DisplayName = k.Properties["Name"][0].ToString(),
|
|
Scope = (GroupScope)GroupPrincipal.FindByIdentity(ctx, IdentityType.Sid, sid).GroupScope
|
|
};
|
|
securityGroups.Add(group);
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
return new List<cSecurityGroupResult>(securityGroups);
|
|
}
|
|
return new List<cSecurityGroupResult>(securityGroups);
|
|
}
|
|
catch (Exception E)
|
|
{
|
|
cLogManager.LogException(E);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
public class cADCollectionBase : SortedList<string, cADResultBase>
|
|
{
|
|
public cADCollectionBase() { }
|
|
public cADCollectionBase(int n) : base(n) { }
|
|
|
|
public void Add(cADResultBase adr)
|
|
{
|
|
if (!this.ContainsKey(adr.ID))
|
|
this.Add(adr.ID, adr);
|
|
}
|
|
}
|
|
|
|
public class cSecurityGroupResult : cADResultBase
|
|
{
|
|
public cSecurityGroupResult() { }
|
|
public cSecurityGroupResult(cADResultBase b) : base(b) { }
|
|
public GroupScope Scope { get; internal set; }
|
|
}
|
|
public class cADUserResult : cADResultBase
|
|
{
|
|
public string GivenName { get; internal set; }
|
|
public string SurName { get; internal set; }
|
|
public string UserPrincipalName { get; internal set; }
|
|
public string Email { get; internal set; }
|
|
public cADUserResult() { }
|
|
public cADUserResult(cADResultBase b) : base(b) {
|
|
|
|
}
|
|
|
|
public cADUserResult(Principal Result) : base(Result)
|
|
{
|
|
UserPrincipalName = Result.UserPrincipalName;
|
|
}
|
|
|
|
public GroupScope Scope { get; internal set; }
|
|
}
|
|
public class cADResultBase
|
|
{
|
|
public string ID { get; set; } = null;
|
|
public string DisplayName { get; set; } = null;
|
|
public string Path { get; set; } = null;
|
|
|
|
public cADResultBase()
|
|
{ }
|
|
public cADResultBase(cADResultBase Result)
|
|
{
|
|
if (Result == null)
|
|
return;
|
|
|
|
ID = Result.ID;
|
|
DisplayName = Result.DisplayName;
|
|
Path = Result.Path;
|
|
}
|
|
public cADResultBase(Principal Result)
|
|
{
|
|
if (Result == null)
|
|
return;
|
|
|
|
ID = Result.Sid.ToString();
|
|
DisplayName = Result.DisplayName;
|
|
Path = Result.DistinguishedName;
|
|
}
|
|
}
|
|
|
|
internal async Task<cADCollectionBase> GetMembersAsync(string Sid)
|
|
{
|
|
try
|
|
{
|
|
await Task.Delay(0);
|
|
var Result = privGetMembersAsync(Sid).ToList();
|
|
if (Result != null)
|
|
{
|
|
var RetVal = new cADCollectionBase(Result.Count);
|
|
foreach (var Entry in Result)
|
|
{
|
|
var res = new cADUserResult(Entry);
|
|
if (!string.IsNullOrEmpty(res.Path))
|
|
RetVal.Add(res);
|
|
}
|
|
return RetVal;
|
|
}
|
|
}
|
|
catch (Exception E)
|
|
{
|
|
cLogManager.LogException(E);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private PrincipalSearchResult<Principal> privGetMembersAsync(string sid)
|
|
{
|
|
try
|
|
{
|
|
using (var group = GroupPrincipal.FindByIdentity(adContext,sid))
|
|
{
|
|
if (group == null)
|
|
{
|
|
return null;
|
|
}
|
|
else
|
|
{
|
|
return group.GetMembers(true);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception E)
|
|
{
|
|
cLogManager.LogException(E);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
}
|