using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Security.Principal; using Vanara.Extensions; using Vanara.InteropServices; using static Vanara.PInvoke.Kernel32; namespace Vanara.PInvoke { public static partial class AdvApi32 { /// Class representation of the native SID structure. /// [DebuggerDisplay("{DebugString}")] public class SafePSID : SafeMemoryHandle, IEquatable, IEquatable, IEquatable, ICloneable, ISecurityObject { /// Equivalent to a NULL pointer to a SID. public static readonly SafePSID Null = new SafePSID(0); /// Initializes a new instance of the class. /// The existing instance to duplicate. public SafePSID(PSID psid) : base(psid.IsNull ? 0 : GetLengthSid(psid)) { if (!psid.IsNull) CopySid(Size, handle, psid); } /// Initializes a new instance of the class. /// The size of memory to allocate, in bytes. public SafePSID(SizeT size) : base(size) { } /// Initializes a new instance of the class. /// An array of bytes that contain a valid Sid. public SafePSID(byte[] sidBytes) : base(sidBytes?.Length ?? 0) => Marshal.Copy(sidBytes, 0, handle, Size); /// Initializes a new instance of the class. /// The string SID value. public SafePSID(string sidValue) : this(ConvertStringSidToSid(sidValue)) { } /// /// Initializes a new instance of the class from a /// instance. /// /// The instance. public SafePSID(System.Security.Principal.SecurityIdentifier si) : this(si is null ? null : GetBytes(si)) { } /// Initializes a new instance of the class. private SafePSID() : base() { } /// Gets a pointer to the SID_IDENTIFIER_AUTHORITY structure in this security identifier (SID). /// A pointer to the SID_IDENTIFIER_AUTHORITY structure for this SID structure. public PSID_IDENTIFIER_AUTHORITY Authority => GetSidIdentifierAuthority(this); /// Gets the SID for the current user /// The current user's SID. public static SafePSID Current { get { using var identity = WindowsIdentity.GetCurrent(); return new SafePSID(identity.User); } } /// A SID representing the Everyone Group (S-1-1-0). public static SafePSID Everyone => CreateWellKnown(WELL_KNOWN_SID_TYPE.WinWorldSid); /// /// Verifies that the revision number is within a known range, and that the number of subauthorities is less than the maximum. /// /// true if this instance is a valid SID; otherwise, false. public bool IsValidSid => IsValidSid(this); /// Gets the length, in bytes, of the SID. /// The SID length, in bytes. public int Length => IsValidSid ? GetLengthSid(this) : 0; /// Enumerates the subauthorities in a security identifier (SID). The subauthority values are relative identifiers (RID). /// A sequence of subauthorities. public IEnumerable SubAuthorities => ((PSID)this).GetSubAuthorities(); /// Gets the string used to display in the debugger. internal string DebugString { get { if (IsInvalid || IsClosed) return "NULL"; if (!IsValidSid) return "Invalid"; var d = ToString("D"); var n = ToString("P"); return d == n ? $"({d})" : $"{n} ({d})"; } } /// Creates a SID for predefined capability. /// /// Member of the KnownSIDCapability enumeration that specifies which capability the SID will identify. /// /// A instance. public static SafePSID CreateCapability(KnownSIDCapability capability) => Init(KnownSIDAuthority.SECURITY_APP_PACKAGE_AUTHORITY, KnownSIDRelativeID.SECURITY_CAPABILITY_BASE_RID, (int)capability); /// Copies the specified SID from a memory pointer to a instance. /// The SID pointer. This value remains the responsibility of the caller to release. /// A instance. public static SafePSID CreateFromPtr(IntPtr psid) => new SafePSID(psid); /// Creates a SID for predefined aliases. /// Member of the WELL_KNOWN_SID_TYPE enumeration that specifies what the SID will identify. /// /// A pointer to a SID that identifies the domain to use when creating the SID. Pass PSID.NULL to use the local computer. /// /// A instance. public static SafePSID CreateWellKnown(WELL_KNOWN_SID_TYPE WellKnownSidType, PSID DomainSid = default) { var sz = 0U; CreateWellKnownSid(WellKnownSidType, DomainSid, Null, ref sz); if (sz == 0) Win32Error.ThrowLastError(); var newSid = new SafePSID((int)sz); if (!CreateWellKnownSid(WellKnownSidType, DomainSid, newSid, ref sz)) Win32Error.ThrowLastError(); return newSid; } /// Extracts the user account SID associated with a security token. /// The security token handle. /// A instance of the user account SID associated with a security token. public static SafePSID FromToken(HTOKEN hToken) { using var hTok = new SafeHTOKEN((IntPtr)hToken, false); return hTok.GetInfo(TOKEN_INFORMATION_CLASS.TokenUser).User.Sid; } /// Performs an explicit conversion from to . /// The SafePSID instance. /// The result of the conversion. public static explicit operator IntPtr(SafePSID psid) => psid.DangerousGetHandle(); /// Performs an explicit conversion from to . /// The SafePSID instance. /// The result of the conversion. public static implicit operator PSID(SafePSID psid) => psid.DangerousGetHandle(); /// Performs an implicit conversion from to . /// The psid. /// The result of the conversion. public static implicit operator SafePSID(PSID psid) => new SafePSID(psid); /// Initializes a new instance from a SID authority and subauthorities. /// The SID authority. /// The first subauthority. /// Up to seven other subauthorities. /// A new instance. /// /// is null or an invalid length or more than 8 total subauthorities were submitted. /// public static SafePSID Init(PSID_IDENTIFIER_AUTHORITY sidAuthority, int subAuth0, params int[] subAuthorities1to7) { if (sidAuthority == null) throw new ArgumentOutOfRangeException(nameof(sidAuthority)); if (subAuthorities1to7.Length > 7) throw new ArgumentOutOfRangeException(nameof(subAuthorities1to7)); AllocateAndInitializeSid(sidAuthority, (byte)(subAuthorities1to7.Length + 1), subAuth0, subAuthorities1to7.Length > 0 ? subAuthorities1to7[0] : 0, subAuthorities1to7.Length > 1 ? subAuthorities1to7[1] : 0, subAuthorities1to7.Length > 2 ? subAuthorities1to7[2] : 0, subAuthorities1to7.Length > 3 ? subAuthorities1to7[3] : 0, subAuthorities1to7.Length > 4 ? subAuthorities1to7[4] : 0, subAuthorities1to7.Length > 5 ? subAuthorities1to7[5] : 0, subAuthorities1to7.Length > 6 ? subAuthorities1to7[6] : 0, out var res); return new SafePSID(res); } /// Implements the operator !=. /// The psid1. /// The psid2. /// The result of the operator. public static bool operator !=(SafePSID psid1, SafePSID psid2) => !(psid1 == psid2); /// Implements the operator ==. /// The psid1. /// The psid2. /// The result of the operator. public static bool operator ==(SafePSID psid1, SafePSID psid2) { if (ReferenceEquals(psid1, psid2)) return true; if (Equals(null, psid1) || Equals(null, psid2)) return false; return psid1.Equals(psid2); } /// Clones this instance. /// A copy of the current . public SafePSID Clone() => CreateFromPtr(handle); /// Indicates whether the current object is equal to another object of the same type. /// An object to compare with this object. /// true if the current object is equal to the parameter; otherwise, false. public bool Equals(SafePSID other) => other != null && (ReferenceEquals(this, other) || EqualSid(this, other)); /// Indicates whether the current object is equal to another object of the same type. /// An object to compare with this object. /// true if the current object is equal to the parameter; otherwise, false. public bool Equals(PSID other) => Equals(other.DangerousGetHandle()); /// Indicates whether the current object is equal to another object of the same type. /// An object to compare with this object. /// true if the current object is equal to the parameter; otherwise, false. public bool Equals(IntPtr other) => EqualSid(handle, other); /// Determines whether the specified is equal to the current . /// The object to compare with the current object. /// true if the specified is equal to the current ; otherwise, false. public override bool Equals(object obj) => obj switch { SafePSID psid2 => Equals(psid2), PSID psidh => Equals(psidh), IntPtr ptr => Equals(ptr), _ => false }; /// Gets the binary form of this SafePSID. /// An array of bytes containing the Sid. public byte[] GetBinaryForm() => GetBytes(0, Size); /// Returns a hash code for this instance. /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. public override int GetHashCode() => base.GetHashCode(); /// Returns a that represents this instance. /// A that represents this instance. public override string ToString() => ToString(null); /// Converts the value of this security identifier (SID) to its equivalent string representation according to the provided format specifier. /// /// A single format specifier that indicates how to format the value of this security identifier (SID). The format parameter can be /// "B" (binary), "D" (sddl), "N" (name), or "P" (upn). If format is null or an empty string (""), "D" is used. /// /// The value of this security identifier (SID), in the specified format. /// SID value is not a valid SID. - pSid /// The value of format is not null, an empty string (""), "B" (binary), "D" (sddl), "N" (name), or "P" (upn). /// /// The following table shows the accepted format specifiers for the format parameter. /// /// /// Specifier /// Format of return value /// /// /// "B" /// /// Binary hex dump representation of the SID. /// /// /// /// "D" /// SDDL representation of the SID. /// /// /// "N" /// The NT4 style name (domain\username) corresponding to the SID. /// /// /// "P" /// The internet style name (UPN) corresponding to the SID. /// /// /// public string ToString(string format) { try { return ((PSID)handle).ToString(format); } catch { return !IsInvalid && !IsClosed ? "Invalid" : "0"; } } /// Creates a new object that is a copy of the current instance. /// A new object that is a copy of this instance. object ICloneable.Clone() => Clone(); private static byte[] GetBytes(System.Security.Principal.SecurityIdentifier si) { var b = new byte[si.BinaryLength]; si.GetBinaryForm(b, 0); return b; } } /// Provides an array of SID pointers whose memory is disposed after use. /// /// public class SafePSIDArray : SafeHANDLE, IReadOnlyList { private List items; /// Initializes a new instance of the class and assigns an existing handle. /// An object that represents the pre-existing handle to use. /// The count of PSID array values pointed to by . /// /// to reliably release the handle during the finalization phase; otherwise, (not /// recommended). If , the individually allocated values for each PSID will also be released. /// public SafePSIDArray(IntPtr preexistingHandle, int count, bool ownsHandle = true) : base(preexistingHandle, ownsHandle) { if (ownsHandle) Count = count; else items = new List(handle.ToIEnum(count).Select(p => new SafePSID(p))); } /// Initializes a new instance of the class. /// A list of instances. public SafePSIDArray(IEnumerable pSIDs) : this(pSIDs?.Select(p => (PSID)p)) { } /// Initializes a new instance of the class. /// A list of instances. public SafePSIDArray(IEnumerable pSIDs) : base() { if (pSIDs is null) throw new ArgumentNullException(nameof(pSIDs)); items = pSIDs.Select(p => new SafePSID(p)).ToList(); SetHandle(items.Select(p => (IntPtr)p).MarshalToPtr(i => LocalAlloc(LMEM.LPTR, i).DangerousGetHandle(), out _)); } /// Initializes a new instance of the class. private SafePSIDArray() : base() { } /// Gets or sets the length of the array. This value must be set in order to interact with the elements. /// The length. public int Count { get => items?.Count ?? throw new InvalidOperationException("The length must be set before using this function."); set { if (items != null) throw new InvalidOperationException("The length can only be set for partially initialized arrays."); items = new List(); foreach (var psid in handle.ToIEnum(value)) { items.Add(new SafePSID(psid)); LocalFree(psid); } } } /// Gets the at the specified index. /// The . /// The index. /// The PSID at the specified index. /// The length must be set before using this function. public PSID this[int index] => items?[index] ?? throw new InvalidOperationException("The length must be set before using this function."); /// Performs an implicit conversion from to []. /// The instance. /// The result of the conversion. public static implicit operator PSID[](SafePSIDArray a) => a.items.ConvertAll(p => (PSID)p).ToArray(); /// Returns an enumerator that iterates through the collection. /// A that can be used to iterate through the collection. public IEnumerator GetEnumerator() => items.ConvertAll(p => (PSID)p).GetEnumerator(); /// IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)items).GetEnumerator(); /// protected override bool InternalReleaseHandle() { if (items != null) foreach (var p in items) p.Dispose(); return LocalFree(handle) == HLOCAL.NULL; } } } /// Extension methods for PSID instances. public static class PSIDExtensions { /// Determines equality of two PSID instances. /// The first PSID. /// The second PSID. /// if the SID structures are equal; otherwise. public static bool Equals(this PSID psid1, PSID psid2) => AdvApi32.EqualSid(psid1, psid2); /// /// The GetAuthority function returns a pointer to the SID_IDENTIFIER_AUTHORITY structure in a specified security identifier (SID). /// /// /// A pointer to the SID structure for which a pointer to the SID_IDENTIFIER_AUTHORITY structure is returned. /// /// This function does not handle SID structures that are not valid. Call the IsValidSid function to verify that the SID /// structure is valid before you call this function. /// /// /// /// /// If the function succeeds, the return value is a pointer to the SID_IDENTIFIER_AUTHORITY structure for the specified SID structure. /// /// /// If the function fails, the return value is undefined. The function fails if the SID structure pointed to by the pSid parameter /// is not valid. To get extended error information, call GetLastError. /// /// /// /// This function uses a 32-bit RID value. For applications that require a larger RID value, use CreateWellKnownSid and related functions. /// public static AdvApi32.PSID_IDENTIFIER_AUTHORITY GetAuthority(this PSID pSid) => AdvApi32.GetSidIdentifierAuthority(pSid); /// Gets the binary form of the SID structure. /// The SID structure pointer. /// The binary form (byte array) of the SID structure. public static byte[] GetBinaryForm(this PSID pSid) => pSid.IsValidSid() ? ((IntPtr)pSid).ToArray(pSid.Length()) : (new byte[0]); /// /// The GetDomainSid function receives a security identifier (SID) and returns a SID representing the domain of that SID. /// /// A pointer to the SID to examine. /// An allocated safe pointer to a SID representing the domain. public static AdvApi32.SafePSID GetDomainSid(this PSID pSid) { Win32Error.ThrowLastErrorIfFalse(AdvApi32.GetWindowsAccountDomainSid(pSid, out var pDomSid)); return pDomSid; } /// Enumerates the subauthorities in a security identifier (SID). The subauthority values are relative identifiers (RID). /// A pointer to the SID structure from which a pointer to a subauthority is to be returned. /// A sequence of subauthorities. public static IEnumerable GetSubAuthorities(this PSID pSid) { for (uint i = 0; i < AdvApi32.GetSidSubAuthorityCount(pSid); i++) yield return AdvApi32.GetSidSubAuthority(pSid, i); } /// /// Validates a security identifier (SID) by verifying that the revision number is within a known range, and that the number of /// subauthorities is less than the maximum. /// /// A pointer to the SID structure to validate. This parameter cannot be NULL. /// /// If the SID structure is valid, the return value is . If the SID structure is not valid, the return value is . /// public static bool IsValidSid(this PSID pSid) => AdvApi32.IsValidSid(pSid); /// Returns the length, in bytes, of a valid security identifier (SID). /// A pointer to the SID structure whose length is returned. The structure is assumed to be valid. /// /// If the SID structure is valid, the return value is the length, in bytes, of the SID structure. If the SID structure is not valid, /// the return value is 0. /// public static int Length(this PSID pSid) => AdvApi32.IsValidSid(pSid) ? AdvApi32.GetLengthSid(pSid) : 0; /// Converts the value of a security identifier (SID) to its equivalent string representation according to the provided format specifier. /// A pointer to a valid SID structure. /// /// A single format specifier that indicates how to format the value of this security identifier (SID). The format parameter can be /// "B" (binary), "D" (sddl), "N" (name), or "P" (upn). If format is null or an empty string (""), "D" is used. /// /// The value of this security identifier (SID), in the specified format. /// SID value is not a valid SID. - pSid /// The value of format is not null, an empty string (""), "B", "D", "N", or "P". /// /// The following table shows the accepted format specifiers for the format parameter. /// /// /// Specifier /// Format of return value /// /// /// "B" /// /// Binary hex dump representation of the SID. /// /// /// /// "D" /// SDDL representation of the SID. /// /// /// "N" /// The NT4 style name (domain\username) corresponding to the SID. /// /// /// "P" /// The internet style name (UPN) corresponding to the SID. /// /// /// public static string ToString(this PSID pSid, string format) { if (!pSid.IsValidSid()) throw new ArgumentException("SID value is not a valid SID.", nameof(pSid)); switch (format) { case "B": var len = pSid.Length(); return pSid.GetBinaryForm().ToHexDumpString(len, len, 0).Trim(' ', '\r', '\n'); case "D": case null: case "": try { return AdvApi32.ConvertSidToStringSid(pSid); } catch { goto case "B"; } case "N": case "P": using (var hPol = AdvApi32.LsaOpenPolicy(AdvApi32.LsaPolicyRights.POLICY_ALL_ACCESS)) { var flag = format == "P" ? AdvApi32.LsaLookupSidsFlags.LSA_LOOKUP_PREFER_INTERNET_NAMES : 0; try { AdvApi32.LsaLookupSids2(hPol, flag, 1, new[] { pSid }, out var memDoms, out var memNames).ThrowIfFailed(); memDoms.Dispose(); using (memNames) { return memNames.ToStructure().Name; } } catch (Exception) { goto case "D"; } } default: throw new FormatException(); } } } }