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();
}
}
}
}