using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Principal;
using Vanara.Extensions;
using Vanara.PInvoke;
using static Vanara.PInvoke.AdvApi32;
namespace Vanara.Security.AccessControl
{
/// Provides access to the local security authority on a given server.
public class SystemSecurity : IDisposable
{
private readonly string svr;
/// Initializes a new instance of the class.
/// The access rights mask for the actions to be taken.
/// The server. Use null for the local server.
public SystemSecurity(DesiredAccess access = DesiredAccess.AllAccess, string server = null)
{
Handle = LsaOpenPolicy((LsaPolicyRights)(ACCESS_MASK.STANDARD_RIGHTS_REQUIRED | (uint)access), server);
svr = server;
CurrentUserLogonRights = new LogonRights(this);
CurrentUserPrivileges = new AccountPrivileges(this);
}
///
/// Account rights determine the type of logon that a user account can perform. An administrator assigns account rights to user and
/// group accounts. Each user's account rights include those granted to the user and to the groups to which the user belongs.
///
[Flags]
public enum AccountLogonRights
{
/// Required for an account to log on using the interactive logon type.
InteractiveLogon = 0x00000001,
/// Required for an account to log on using the network logon type.
NetworkLogon = 0x00000002,
/// Required for an account to log on using the batch logon type.
BatchLogon = 0x00000004,
/// Required for an account to log on using the service logon type.
ServiceLogon = 0x00000010,
/// Explicitly denies an account the right to log on using the interactive logon type.
DenyInteractiveLogon = 0x00000040,
/// Explicitly denies an account the right to log on using the network logon type.
DenyNetworkLogon = 0x00000080,
/// Explicitly denies an account the right to log on using the batch logon type.
DenyBatchLogon = 0x00000100,
/// Explicitly denies an account the right to log on using the service logon type.
DenyServiceLogon = 0x00000200,
/// Remote interactive logon
RemoteInteractiveLogon = 0x00000400,
/// Explicitly denies an account the right to log on remotely using the interactive logon type.
DenyRemoteInteractiveLogon = 0x00000800,
}
/// Access rights for a local security policy.
[Flags]
public enum DesiredAccess
{
///
/// This access type is needed to read the target system's miscellaneous security policy information. This includes the default
/// quota, auditing, server state and role information, and trust information. This access type is also needed to enumerate
/// trusted domains, accounts, and privileges.
///
ViewLocalInformation = 1,
/// This access type is needed to view audit trail or audit requirements information.
ViewAuditInformation = 2,
///
/// This access type is needed to view sensitive information, such as the names of accounts established for trusted domain relationships.
///
GetPrivateInformation = 4,
/// This access type is needed to change the account domain or primary domain information.
TrustAdmin = 8,
/// This access type is needed to create a new Account object.
CreateAccount = 0x10,
/// This access type is needed to create a new Private Data object.
CreateSecret = 0x20,
/// Set the default system quotas that are applied to user accounts.
SetDefaultQuotaLimits = 0x80,
/// This access type is needed to update the auditing requirements of the system.
SetAuditRequirements = 0x100,
///
/// This access type is needed to change the characteristics of the audit trail such as its maximum size or the retention period
/// for audit records, or to clear the log.
///
AuditLogAdmin = 0x200,
///
/// This access type is needed to modify the server state or role (master/replica) information. It is also needed to change the
/// replica source and account name information.
///
ServerAdmin = 0x400,
/// This access type is needed to translate between names and SIDs.
LookupNames = 0x800,
/// All access.
AllAccess = 0xFFF,
}
/// Gets a instance for the local server and rights to lookup names.
/// A instance for the local server.
public static SystemSecurity Local => new SystemSecurity();
/// Gets the current user's system access.
/// The current user's system access.
public LogonRights CurrentUserLogonRights { get; }
/// Gets the current user's account rights.
/// The current user's account rights.
public AccountPrivileges CurrentUserPrivileges { get; }
/// Gets the for this instance.
/// The .
private SafeLSA_HANDLE Handle { get; }
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
public void Dispose() => Handle?.Dispose();
///
/// Enumerates the accounts with the specified privilege. Requires the and
/// rights.
///
/// The privilege name.
/// An array of objects representing all accounts with the specified privilege.
public IEnumerable EnumerateAccountsWithRight(string privilege)
{
NTStatus ret = LsaEnumerateAccountsWithUserRight(Handle, privilege, out SafeLsaMemoryHandle buffer, out var count);
if (ret == NTStatus.STATUS_NO_MORE_ENTRIES)
return new SecurityIdentifier[0];
ThrowIfLsaError(ret);
return buffer.DangerousGetHandle().ToIEnum(count).Select(i => new SecurityIdentifier((IntPtr)i.Sid));
}
///
/// Enumerates the accounts with the specified privilege. Requires the and
/// rights.
///
/// The privilege.
/// An array of objects representing all accounts with the specified privilege.
public IEnumerable EnumerateAccountsWithRight(SystemPrivilege privilege) => EnumerateAccountsWithRight(FromPriv(privilege));
///
/// Looks up an account name and returns information about it in a class. Requires the
/// right.
///
///
/// The name of the account. This string can be the name of a user, group, or local group account, or the name of a domain. Domain
/// names can be DNS domain names or NetBIOS domain names.
///
/// A for the .
public SystemAccountInfo GetAccountInfo(string name) => GetAccountInfo(false, name).FirstOrDefault();
///
/// Looks up a list of account names and returns information about each in a class. Requires the
/// right.
///
/// If set to true restrict the search to only local accounts.
///
/// The account names to lookup. These strings can be the names of user, group, or local group accounts, or the names of domains.
/// Domain names can be DNS domain names or NetBIOS domain names.
///
/// Contains a corresponding result for each name provided in .
/// At least one user name must be supplied.
public IList GetAccountInfo(bool localOnly, params string[] names)
{
if (names == null || names.Length == 0)
throw new ArgumentException(@"At least one user name must be supplied.", nameof(names));
NTStatus ret = LsaLookupNames2(Handle, localOnly ? LsaLookupNamesFlags.LSA_LOOKUP_ISOLATED_AS_LOCAL : 0, (uint)names.Length, names, out SafeLsaMemoryHandle domains, out SafeLsaMemoryHandle sids);
if (ret != NTStatus.STATUS_SUCCESS && ret != NTStatus.STATUS_SOME_NOT_MAPPED)
ThrowIfLsaError(ret);
LSA_TRUST_INFORMATION[] d = domains.DangerousGetHandle().ToStructure().DomainList.ToArray();
LSA_TRANSLATED_SID2[] ts = sids.DangerousGetHandle().ToIEnum(names.Length).ToArray();
var retVal = new SystemAccountInfo[names.Length];
for (var i = 0; i < names.Length; i++)
retVal[i] = new SystemAccountInfo(names[i], ts[i].Use, IsValidSid(ts[i].Use) ? new SecurityIdentifier((IntPtr)ts[i].Sid) : null, ts[i].DomainIndex, d);
return retVal;
}
///
/// Looks up a list of account names and returns information about each in a class. Requires the
/// right.
///
/// Contains a corresponding result for each name provided in .
/// At least one user name must be supplied.
public IList GetAccountInfo(bool preferInternetNames, bool disallowConnectedAccts, params SecurityIdentifier[] sids)
{
if (sids == null || sids.Length == 0)
throw new ArgumentException(@"At least one SecurityIdentifier must be supplied.", nameof(sids));
LsaLookupSidsFlags opts = (preferInternetNames ? LsaLookupSidsFlags.LSA_LOOKUP_PREFER_INTERNET_NAMES : 0) |
(disallowConnectedAccts ? LsaLookupSidsFlags.LSA_LOOKUP_DISALLOW_CONNECTED_ACCOUNT_INTERNET_SID : 0);
IEnumerable psids = sids.Select(s => new PinnedSid(s));
NTStatus ret = LsaLookupSids2(Handle, opts, (uint)sids.Length, psids.Select(s => s.PSID).ToArray(), out SafeLsaMemoryHandle domains, out SafeLsaMemoryHandle names);
if (ret != NTStatus.STATUS_SUCCESS && ret != NTStatus.STATUS_SOME_NOT_MAPPED)
ThrowIfLsaError(ret);
LSA_TRUST_INFORMATION[] d = domains.DangerousGetHandle().ToStructure().DomainList.ToArray();
LSA_TRANSLATED_NAME[] tn = names.DangerousGetHandle().ToIEnum(sids.Length).ToArray();
var retVal = new SystemAccountInfo[sids.Length];
for (var i = 0; i < sids.Length; i++)
retVal[i] = new SystemAccountInfo(tn[i].Name.ToString(), tn[i].Use, sids[i], tn[i].DomainIndex, d);
return retVal;
}
/// Gets an enumeration of central access policies (CAPs) identifiers (CAPIDs) of all the CAPs applied on this system.
/// An enumeration of CAPIDs.
public IEnumerable GetAvailableCAPIDs()
{
ThrowIfLsaError(LsaGetAppliedCAPIDs(svr, out SafeLsaMemoryHandle capIdArray, out var capCount));
return capCount == 0 || capIdArray.IsInvalid ? new SecurityIdentifier[0] : capIdArray.DangerousGetHandle().ToIEnum((int)capCount).Select(p => new SecurityIdentifier(p));
}
/// Gets the system access for the specified user.
/// The user name of the account for which to manage privileges.
/// A instance for the specified user.
public LogonRights UserLogonRights(string user) => new LogonRights(this, user);
/// Gets the account rights for the specified user.
/// The user name of the account for which to manage privileges.
/// A instance for the specified user.
public AccountPrivileges UserPrivileges(string user) => new AccountPrivileges(this, user);
private static string FromPriv(SystemPrivilege priv) => SystemPrivilegeTypeConverter.PrivLookup[priv];
private static AccountLogonRights GetSystemAccess(SafeLSA_HANDLE hAcct)
{
ThrowIfLsaError(LsaGetSystemAccessAccount(hAcct, out var rights));
return (AccountLogonRights)rights;
}
private static bool IsValidSid(SID_NAME_USE use) => Array.IndexOf(new[] { 1, 2, 4, 5, 9 }, (int)use) != -1;
private static void SetSystemAccess(SafeLSA_HANDLE hAcct, AccountLogonRights rights)
{
AccountLogonRights cur = GetSystemAccess(hAcct);
ThrowIfLsaError(LsaSetSystemAccessAccount(hAcct, (int)(cur | rights)));
}
private static void ThrowIfLsaError(NTStatus lsaRetVal) => LsaNtStatusToWinError(lsaRetVal).ThrowIfFailed();
private void AddRights(string accountName, params string[] privilegeNames) => ThrowIfLsaError(LsaAddAccountRights(Handle, GetSid(accountName), privilegeNames, (uint)privilegeNames.Length));
private SafeLSA_HANDLE GetAccount(string accountName, LsaAccountAccessMask mask = LsaAccountAccessMask.ACCOUNT_VIEW)
{
PSID sid = GetSid(accountName);
Win32Error res = LsaNtStatusToWinError(LsaOpenAccount(Handle, sid, mask, out SafeLSA_HANDLE hAcct));
if (res == Win32Error.ERROR_FILE_NOT_FOUND)
ThrowIfLsaError(LsaCreateAccount(Handle, sid, mask, out hAcct));
else
res.ThrowIfFailed();
return hAcct;
}
private IEnumerable GetRights(string accountName)
{
try
{
return LsaEnumerateAccountRights(Handle, GetSid(accountName));
}
catch (FileNotFoundException)
{
using (GetAccount(accountName))
return LsaEnumerateAccountRights(Handle, GetSid(accountName));
}
}
private PSID GetSid(string accountName)
{
int sidSize = 0, nameSize = 0;
LookupAccountName(svr, accountName, SafePSID.Null, ref sidSize, null, ref nameSize, out SID_NAME_USE accountType);
var domainName = new System.Text.StringBuilder(nameSize);
var sid = new SafePSID(sidSize);
if (!LookupAccountName(string.Empty, accountName, sid, ref sidSize, domainName, ref nameSize, out accountType))
throw new System.ComponentModel.Win32Exception();
return sid;
}
private void RemoveAllRights(string accountName) => ThrowIfLsaError(LsaRemoveAccountRights(Handle, GetSid(accountName), true, null, 0));
private void RemoveRights(string accountName, params string[] privilegeNames) => ThrowIfLsaError(LsaRemoveAccountRights(Handle, GetSid(accountName), false, privilegeNames, (uint)privilegeNames.Length));
/// Allows for the privileges of a user to be retrieved, enumerated and set.
public class AccountPrivileges : IEnumerable
{
private readonly SystemSecurity ctrl;
private readonly string user;
/// Initializes a new instance of the class.
/// The parent.
/// Name of the user.
public AccountPrivileges(SystemSecurity parent, string userName = null)
{
ctrl = parent;
if (!string.IsNullOrEmpty(userName))
{
user = userName;
}
else
{
using var identity = WindowsIdentity.GetCurrent();
user = identity.Name;
}
}
/// Gets or sets the enablement of the specified privilege.
/// true if the specified privilege is enabled; otherwise false.
/// The name of the privilege.
/// A value that represents the enablement of the specified privilege.
public bool this[string privilege]
{
get => ctrl.GetRights(user).Contains(privilege);
set { if (value) ctrl.AddRights(user, privilege); else ctrl.RemoveRights(user, privilege); }
}
/// Gets or sets the enablement of the specified privilege.
/// true if the specified privilege is enabled; otherwise false.
/// The name of the privilege.
/// A value that represents the enablement of the specified privilege.
public bool this[SystemPrivilege privilege]
{
get => ctrl.GetRights(user).Contains(FromPriv(privilege));
set { if (value) ctrl.AddRights(user, FromPriv(privilege)); else ctrl.RemoveRights(user, FromPriv(privilege)); }
}
/// Returns an enumerator that iterates all of the user's current privileges.
/// A that can be used to iterate through the collection.
public IEnumerator GetEnumerator() => ctrl.GetRights(user).Select(SystemPrivilegeTypeConverter.ConvertKnownString).GetEnumerator();
/// Removes all privileges.
public void RemoveAll() => ctrl.RemoveAllRights(user);
/// Returns a that represents this instance.
/// A that represents this instance.
public override string ToString() => $"{string.Join(",", ctrl.GetRights(user).ToArray())}";
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
/// Allows for the privileges of a user to be retrieved, enumerated and set.
public class LogonRights : IEnumerable
{
private readonly SystemSecurity ctrl;
private readonly string user;
/// Initializes a new instance of the class.
/// The parent.
/// Name of the user.
public LogonRights(SystemSecurity parent, string userName = null)
{
ctrl = parent;
if (!string.IsNullOrEmpty(userName))
{
user = userName;
}
else
{
using var identity = WindowsIdentity.GetCurrent();
user = identity.Name;
}
}
/// Gets the logon rights for the current user.
/// The rights.
private AccountLogonRights Rights => GetSystemAccess(ctrl.GetAccount(user));
/// Gets or sets the enablement of the specified privilege.
/// true if the specified privilege is enabled; otherwise false.
/// The name of the privilege.
/// A value that represents the enablement of the specified privilege.
public bool this[AccountLogonRights right]
{
get => (Rights & right) == right;
set
{
SafeLSA_HANDLE hAcct = ctrl.GetAccount(user, LsaAccountAccessMask.ACCOUNT_VIEW | LsaAccountAccessMask.ACCOUNT_ADJUST_SYSTEM_ACCESS);
EnumFlagIndexer cur = GetSystemAccess(hAcct);
if (cur[right] == value) return;
cur[right] = value;
SetSystemAccess(hAcct, cur);
}
}
/// Returns an enumerator that iterates through the assigned rights.
/// A that can be used to iterate through the assigned rights.
public IEnumerator GetEnumerator()
{
EnumFlagIndexer r = Rights;
return r.GetEnumerator();
}
/// Returns a that represents this instance.
/// A that represents this instance.
public override string ToString() => $"{Rights}";
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
///
/// Contains a corresponding result for each name provided to the method.
///
public class SystemAccountInfo
{
internal SystemAccountInfo(string name, SID_NAME_USE use, SecurityIdentifier sid, int domainIndex, IList domains)
{
Name = name;
SidType = use;
Sid = IsValidSid(use) ? sid : null;
Domain = domainIndex >= 0 && domainIndex < domains.Count ? domains[domainIndex].Name.ToString() : null;
}
/// Gets the domain associated with the supplied .
public string Domain { get; }
/// Gets the corresponding lookup name.
public string Name { get; }
/// Gets the associated with the supplied .
public SecurityIdentifier Sid { get; }
/// Gets the associated with the supplied .
public SID_NAME_USE SidType { get; }
/// Returns a that represents this instance.
/// A that represents this instance.
public override string ToString() => $"Sid: {Sid?.Value} ({SidType}); Domain: {Domain}";
}
}
}