2025-11-28 00:35:46 +09:00

339 lines
11 KiB
C#

// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.DirectoryServices;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Text;
namespace Microsoft.Samples.DynamicAccessControl
{
using DWORD = System.UInt32;
using ULONG = System.UInt32;
using PSID = System.IntPtr;
using PGUID = System.IntPtr;
using PSECURITY_DESCRIPTOR = System.IntPtr;
using PDOMAIN_CONTROLLER_INFO = System.IntPtr;
using Utility;
/// <summary>
/// Class to extract properties from a Central Access Rule object from AD
/// </summary>
internal class CentralAccessRule : IDisposable
{
#region Constructor
public CentralAccessRule(string dnName)
{
carEntry = new DirectoryEntry("LDAP://" + dnName);
}
~CentralAccessRule()
{
Dispose(false);
}
#endregion
#region Public properties
public string Name
{
get
{
return carEntry.Properties["cn"].Value as string;
}
}
public RawSecurityDescriptor EffectivePolicy
{
get
{
return new RawSecurityDescriptor(carEntry.Properties["msAuthz-EffectiveSecurityPolicy"].Value as string);
}
}
public string AppliesToCondition
{
get
{
return carEntry.Properties["msAuthz-ResourceCondition"].Value as string;
}
}
#endregion
#region IDisposable implementation
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
void Dispose(bool disposing)
{
if (disposing)
{
if (carEntry != null)
{
carEntry.Dispose();
carEntry = null;
}
}
}
#endregion
#region Private members
DirectoryEntry carEntry;
#endregion
}
/// <summary>
/// Class to query for the Central Policies that are enforced on a target computer
/// </summary>
internal sealed class AvailableCentralPolicies
{
#region Constructor
/// <summary>
/// Determines the domain and the LDAP paths for Central Policy and Central Access Rule in AD
/// to facilitate subsequent queries.
/// </summary>
/// <param name="target">Name of the computer from which to query</param>
public AvailableCentralPolicies(string target)
{
PDOMAIN_CONTROLLER_INFO dsInfoPtr = PDOMAIN_CONTROLLER_INFO.Zero;
try
{
DWORD result = NativeMethods.DsGetDcName(target,
null,
PGUID.Zero,
null,
NativeMethods.DsGetDcNameFlags.DirectoryServiceRequired,
out dsInfoPtr);
if (result != Win32Error.ERROR_SUCCESS)
{
throw new Win32Exception((int)result);
}
NativeMethods.DOMAIN_CONTROLLER_INFO dsInfo = (NativeMethods.DOMAIN_CONTROLLER_INFO)
Marshal.PtrToStructure(
dsInfoPtr,
typeof(NativeMethods.DOMAIN_CONTROLLER_INFO));
string domainDN = "DC=" + string.Join(",DC=", dsInfo.domainName.Split('.'));
capContainerDN = "CN=Central Access Policies," +
"CN=Claims Configuration," +
"CN=Services," +
"CN=Configuration," +
domainDN;
availableCaps = GetAvailableCaps(target);
}
finally
{
NativeMethods.NetApiBufferFree(dsInfoPtr);
dsInfoPtr = PDOMAIN_CONTROLLER_INFO.Zero;
}
}
#endregion
#region Public methods and properties
/// <summary>
/// The number of CAPs available on the target computer as reported by LsaGetAppliedCAPIDs
/// </summary>
public ULONG CapCount
{
get
{
return availableCaps != null? (ULONG)availableCaps.Count : 0;
}
}
/// <summary>
/// Retrieves the Central Access Rules for the Central Policy
/// </summary>
/// <param name="capId">Unique Identifier for the Central Policy</param>
/// <returns>A Dictionary of CAR names and the Central Access Rule object</returns>
public Dictionary<string, CentralAccessRule> FetchCentralAccessRules(SecurityIdentifier capId)
{
Dictionary<string, CentralAccessRule> carInfo = null;
if (CapCount == 0 || !availableCaps.Contains(capId))
{
return null;
}
using (var CapContainer = new DirectoryEntry("LDAP://" + capContainerDN))
{
foreach (DirectoryEntry capEntry in CapContainer.Children)
{
var entryId = capEntry.Properties["msAuthz-CentralAccessPolicyID"].Value;
if (entryId == null)
{
continue;
}
byte[] rawCapId = entryId as byte[];
if (rawCapId == null)
{
continue;
}
var entrySid = new SecurityIdentifier(rawCapId, 0);
if (capId == entrySid)
{
PropertyValueCollection CARs = capEntry.Properties["msAuthz-MemberRulesInCentralAccessPolicy"];
carInfo = new Dictionary<string, CentralAccessRule>(CARs.Count);
foreach (string carDN in CARs)
{
CentralAccessRule CAR = new CentralAccessRule(carDN);
carInfo.Add(CAR.Name, CAR);
}
}
}
}
return carInfo;
}
#endregion
#region Private methods
static ICollection<SecurityIdentifier> GetAvailableCaps(string targetName)
{
var result = new List<SecurityIdentifier>();
PSID capIdArray = PSID.Zero;
try
{
var targetMachine = new NativeMethods.LSA_UNICODE_STRING(targetName);
ULONG capCount;
int ntStatus = NativeMethods.LsaGetAppliedCAPIDs(ref targetMachine, ref capIdArray, out capCount);
if (!Win32.NT_SUCCESS(ntStatus))
{
throw new Win32Exception(NativeMethods.LsaNtStatusToWinError(ntStatus));
}
if (capCount != 0 && capIdArray != PSID.Zero)
{
PSID nextSid = capIdArray;
while(0 != capCount)
{
result.Add(new SecurityIdentifier(Marshal.ReadIntPtr(nextSid)));
nextSid += Marshal.SizeOf(typeof(IntPtr));
--capCount;
}
}
}
finally
{
Marshal.FreeHGlobal(capIdArray);
}
return result;
}
#endregion
#region Private members
ICollection<SecurityIdentifier> availableCaps;
string capContainerDN;
#endregion
#region Nested class for P/Invokes and native (Win32) structures
static class NativeMethods
{
[Flags]
public enum DsGetDcNameFlags : uint
{
DirectoryServiceRequired = 0x00000010,
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct DOMAIN_CONTROLLER_INFO
{
public string dcName;
public string dcAddress;
public ULONG dcAddressType;
public Guid domainGuid;
public string domainName;
public string dnsForestName;
public DsGetDcNameFlags flags;
public string dcSiteName;
public string clientSiteName;
}
[DllImport(Win32.NETAPI32_DLL, CharSet = CharSet.Unicode, SetLastError = true)]
public static extern DWORD DsGetDcName(string computerName,
string domainName,
PGUID domainGuid,
string siteName,
DsGetDcNameFlags flags,
out PDOMAIN_CONTROLLER_INFO dsInfo);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct LSA_UNICODE_STRING
{
public ushort Length;
public ushort MaximumLength;
public string Buffer;
public LSA_UNICODE_STRING(string str)
{
if (str == null)
{
throw new ArgumentException("Null argument invalid", "str");
}
Debug.Assert(str.Length < ushort.MaxValue);
Length = MaximumLength = (ushort)(str.Length * UnicodeEncoding.CharSize);
Buffer = str;
}
}
[DllImport(Win32.ADVAPI32_DLL, SetLastError = true, CharSet = CharSet.Unicode)]
public static extern int LsaGetAppliedCAPIDs(
ref LSA_UNICODE_STRING systemName,
ref IntPtr CAPIDs,
out ULONG CAPIDCount
);
[DllImport(Win32.ADVAPI32_DLL, SetLastError = false)]
public static extern int LsaNtStatusToWinError(int status);
public static void NetApiBufferFree(IntPtr buffer)
{
const int NERR_Success = 0;
int nResult = NetApiFreeBuffer(buffer);
//
// A failure here is unexpected, but nevertheless nothing much
// can be done to recover from this error when this does happen.
//
// Just assert the failure to flag any case of invalid argument.
//
Debug.Assert(nResult == NERR_Success);
}
[DllImport(Win32.NETAPI32_DLL, EntryPoint = "NetApiBufferFree")]
private static extern int NetApiFreeBuffer(IntPtr Buffer);
}
#endregion
}
}