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}"; } } }