You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1433 lines
37 KiB
1433 lines
37 KiB
/*++
|
|
|
|
Copyright (c) 1991 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
eztrust.c
|
|
|
|
Abstract:
|
|
|
|
This file contains routines to support the Easy Trust creation DCR.
|
|
|
|
Author:
|
|
|
|
Colin Brace (ColinBr) May 19, 2001
|
|
|
|
Environment:
|
|
|
|
User Mode
|
|
|
|
Revision History:
|
|
|
|
|
|
--*/
|
|
#include <lsapch2.h>
|
|
#include "dbp.h"
|
|
#include <permit.h>
|
|
|
|
//
|
|
// Forwards
|
|
//
|
|
|
|
NTSTATUS
|
|
LsapGetDelegatedTDOQuotas(
|
|
OUT ULONG *PerUserQuota OPTIONAL,
|
|
OUT ULONG *GlobalQuota OPTIONAL,
|
|
OUT ULONG *PerUserDeletedQuota OPTIONAL
|
|
);
|
|
|
|
//
|
|
// This flags indicates to only search for deleted TDO's with
|
|
// the mdds-CreatorSid attribute equal to CreatorSid
|
|
//
|
|
#define LSAP_GET_DELEGATED_TDO_DELETED_ONLY 0x00000001
|
|
|
|
NTSTATUS
|
|
LsapGetDelegatedTDOCount(
|
|
IN ULONG Flags,
|
|
IN PSID CreatorSid OPTIONAL,
|
|
OUT ULONG *Count
|
|
);
|
|
|
|
|
|
//
|
|
// Definitions
|
|
//
|
|
|
|
|
|
NTSTATUS
|
|
LsapGetCurrentOwnerAndPrimaryGroup(
|
|
OUT PTOKEN_OWNER * Owner,
|
|
OUT PTOKEN_PRIMARY_GROUP * PrimaryGroup OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description
|
|
|
|
This routine Impersonates the Client and obtains the owner and
|
|
its primary group from the Token
|
|
|
|
Parameters:
|
|
|
|
Owner -- The Owner sid is returned in here
|
|
PrimaryGroup The User's Primary Group is returned in here
|
|
|
|
Return Values:
|
|
|
|
STATUS_SUCCESS
|
|
STATUS_INSUFFICIENT_RESOURCES
|
|
|
|
--*/
|
|
{
|
|
|
|
HANDLE ClientToken = INVALID_HANDLE_VALUE;
|
|
BOOLEAN fImpersonating = FALSE;
|
|
ULONG RequiredLength=0;
|
|
NTSTATUS NtStatus = STATUS_SUCCESS;
|
|
BOOLEAN ImpersonatingNullSession = FALSE;
|
|
|
|
|
|
//
|
|
// Initialize Return Values
|
|
//
|
|
|
|
*Owner = NULL;
|
|
if (PrimaryGroup) {
|
|
*PrimaryGroup = NULL;
|
|
}
|
|
|
|
//
|
|
// Impersonate the Client
|
|
//
|
|
|
|
NtStatus = I_RpcMapWin32Status(RpcImpersonateClient(0));
|
|
if (!NT_SUCCESS(NtStatus))
|
|
goto Error;
|
|
|
|
fImpersonating = TRUE;
|
|
|
|
//
|
|
// Grab the User's Sid
|
|
//
|
|
|
|
NtStatus = NtOpenThreadToken(
|
|
NtCurrentThread(),
|
|
TOKEN_QUERY,
|
|
TRUE, //OpenAsSelf
|
|
&ClientToken
|
|
);
|
|
|
|
if (!NT_SUCCESS(NtStatus))
|
|
goto Error;
|
|
|
|
//
|
|
// Query the Client Token For User's SID
|
|
//
|
|
|
|
NtStatus = NtQueryInformationToken(
|
|
ClientToken,
|
|
TokenOwner,
|
|
NULL,
|
|
0,
|
|
&RequiredLength
|
|
);
|
|
|
|
if ((STATUS_BUFFER_TOO_SMALL == NtStatus) && ( RequiredLength > 0))
|
|
{
|
|
//
|
|
// Alloc Memory
|
|
//
|
|
|
|
*Owner = LsapAllocateLsaHeap(RequiredLength);
|
|
if (NULL==*Owner)
|
|
{
|
|
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Error;
|
|
}
|
|
|
|
//
|
|
// Query the Token
|
|
//
|
|
|
|
NtStatus = NtQueryInformationToken(
|
|
ClientToken,
|
|
TokenOwner,
|
|
*Owner,
|
|
RequiredLength,
|
|
&RequiredLength
|
|
);
|
|
|
|
}
|
|
if (!NT_SUCCESS(NtStatus))
|
|
{
|
|
goto Error;
|
|
}
|
|
|
|
//
|
|
// Query the Client Token For User's PrimaryGroup
|
|
//
|
|
if (PrimaryGroup) {
|
|
|
|
RequiredLength = 0;
|
|
|
|
NtStatus = NtQueryInformationToken(
|
|
ClientToken,
|
|
TokenPrimaryGroup,
|
|
NULL,
|
|
0,
|
|
&RequiredLength
|
|
);
|
|
|
|
if ((STATUS_BUFFER_TOO_SMALL == NtStatus) && ( RequiredLength > 0))
|
|
{
|
|
//
|
|
// Alloc Memory
|
|
//
|
|
|
|
*PrimaryGroup = LsapAllocateLsaHeap(RequiredLength);
|
|
if (NULL==*PrimaryGroup)
|
|
{
|
|
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Error;
|
|
}
|
|
|
|
//
|
|
// Query the Token
|
|
//
|
|
|
|
NtStatus = NtQueryInformationToken(
|
|
ClientToken,
|
|
TokenPrimaryGroup,
|
|
*PrimaryGroup,
|
|
RequiredLength,
|
|
&RequiredLength
|
|
);
|
|
|
|
}
|
|
if (!NT_SUCCESS(NtStatus))
|
|
{
|
|
goto Error;
|
|
}
|
|
}
|
|
|
|
|
|
Error:
|
|
|
|
//
|
|
// Clean up on Error
|
|
//
|
|
|
|
if (!NT_SUCCESS(NtStatus))
|
|
{
|
|
if (*Owner)
|
|
{
|
|
LsapFreeLsaHeap(*Owner);
|
|
*Owner = NULL;
|
|
}
|
|
|
|
if (PrimaryGroup && *PrimaryGroup)
|
|
{
|
|
LsapFreeLsaHeap(*PrimaryGroup);
|
|
*PrimaryGroup = NULL;
|
|
}
|
|
}
|
|
|
|
if (fImpersonating)
|
|
I_RpcMapWin32Status(RpcRevertToSelf());
|
|
|
|
if (INVALID_HANDLE_VALUE!=ClientToken)
|
|
NtClose(ClientToken);
|
|
|
|
return NtStatus;
|
|
|
|
}
|
|
|
|
NTSTATUS
|
|
LsapIsAccessControlGranted(
|
|
IN DSNAME* DsObject,
|
|
IN ULONG ClassId,
|
|
IN GUID* ControlAccessRight,
|
|
IN LSAP_DB_OBJECT_TYPE_ID ObjectTypeId
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine checks if the network client has ControlAccessRight
|
|
on the DsObject for the class ClassId.
|
|
|
|
Arguments:
|
|
|
|
DsObject -- an object in DS whose security descriptor will be used
|
|
for the access check.
|
|
|
|
ClassId -- which class of DsObject the ControlAccessRight should be
|
|
checked agaist.
|
|
|
|
ControlAccessRight -- a GUID
|
|
|
|
ObjectTypeId -- the LSA object for which this access check applies (not
|
|
necessarily the same type of object as DsObject).
|
|
|
|
Return Values:
|
|
|
|
STATUS_SUCCESS,
|
|
STATUS_ACCESS_DENIED,
|
|
STATUS_NO_SECURITY_ON_OBJECT (if DsObject doesn't have a security
|
|
descriptor)
|
|
resource error otherwise.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS NtStatus = STATUS_SUCCESS;
|
|
NTSTATUS SecondaryStatus = STATUS_SUCCESS;
|
|
OBJECT_TYPE_LIST ObjList[2];
|
|
DWORD Results[2];
|
|
DWORD GrantedAccess[2];
|
|
PSECURITY_DESCRIPTOR pSD = NULL;
|
|
GENERIC_MAPPING GenericMapping = DS_GENERIC_MAPPING;
|
|
ACCESS_MASK DesiredAccess;
|
|
GUID ClassGuid;
|
|
ULONG ClassGuidLength = sizeof(GUID);
|
|
BOOLEAN bTemp = FALSE;
|
|
UNICODE_STRING ObjectName;
|
|
|
|
ASSERT(DsObject && DsObject->NameLen > 0);
|
|
|
|
//
|
|
// Setup the object name
|
|
//
|
|
ObjectName.Length = ObjectName.MaximumLength = (USHORT)(DsObject->NameLen * sizeof(WCHAR));
|
|
ObjectName.Buffer = DsObject->StringName;
|
|
|
|
//
|
|
// Obtain the security descriptor
|
|
//
|
|
NtStatus = LsapDsReadObjectSDByDsName(DsObject,
|
|
&pSD);
|
|
|
|
if (!NT_SUCCESS(NtStatus)) {
|
|
goto IsAccessControlGrantedError;
|
|
}
|
|
|
|
//
|
|
// Get the Class GUID of the object
|
|
//
|
|
NtStatus = SampGetClassAttribute(ClassId,
|
|
ATT_SCHEMA_ID_GUID,
|
|
&ClassGuidLength,
|
|
&ClassGuid);
|
|
|
|
if (!NT_SUCCESS(NtStatus)) {
|
|
goto IsAccessControlGrantedError;
|
|
}
|
|
|
|
//
|
|
// Setup Object List
|
|
//
|
|
|
|
ObjList[0].Level = ACCESS_OBJECT_GUID;
|
|
ObjList[0].Sbz = 0;
|
|
ObjList[0].ObjectType = &ClassGuid;
|
|
|
|
//
|
|
// Every control access guid is considered to be in it's own property
|
|
// set. To achieve this, we treat control access guids as property set
|
|
// guids.
|
|
//
|
|
ObjList[1].Level = ACCESS_PROPERTY_SET_GUID;
|
|
ObjList[1].Sbz = 0;
|
|
ObjList[1].ObjectType = ControlAccessRight;
|
|
|
|
|
|
//
|
|
// Assume full access
|
|
//
|
|
|
|
Results[0] = 0;
|
|
Results[1] = 0;
|
|
|
|
//
|
|
// Impersonate the client
|
|
//
|
|
|
|
NtStatus = I_RpcMapWin32Status(RpcImpersonateClient(0));
|
|
|
|
if (!NT_SUCCESS(NtStatus)) {
|
|
goto IsAccessControlGrantedError;
|
|
}
|
|
|
|
//
|
|
// Set the desired access
|
|
//
|
|
|
|
DesiredAccess = RIGHT_DS_CONTROL_ACCESS;
|
|
|
|
//
|
|
// Map the desired access to contain no
|
|
// generic accesses.
|
|
//
|
|
|
|
MapGenericMask(&DesiredAccess, &GenericMapping);
|
|
|
|
|
|
NtStatus = NtAccessCheckByTypeResultListAndAuditAlarm(
|
|
&LsapState.SubsystemName, // SubSystemName
|
|
NULL, // HandleId or NULL
|
|
&LsapDbObjectTypeNames[ObjectTypeId], // ObjectTypeName
|
|
&ObjectName, // ObjectName
|
|
pSD, // Domain NC head's SD
|
|
NULL, // Self SID
|
|
DesiredAccess, // Desired Access
|
|
AuditEventDirectoryServiceAccess, // Audit Type
|
|
0, // Flags
|
|
ObjList, // Object Type List
|
|
2, // Object Type List Length
|
|
&GenericMapping, // Generic Mapping
|
|
FALSE, // Object Creation
|
|
GrantedAccess, // Granted Status
|
|
Results, // Access Status
|
|
&bTemp); // Generate On Close
|
|
|
|
//
|
|
// Stop impersonating the client
|
|
//
|
|
SecondaryStatus = I_RpcMapWin32Status(RpcRevertToSelf());
|
|
if (NT_SUCCESS(NtStatus)) {
|
|
NtStatus = SecondaryStatus;
|
|
}
|
|
|
|
if (NT_SUCCESS(NtStatus)) {
|
|
//
|
|
// Ok, we checked access, Now, access is granted if either
|
|
// we were granted access on the entire object (i.e. Results[0]
|
|
// is NULL ) or we were granted explicit rights on the access
|
|
// guid (i.e. Results[1] is NULL).
|
|
//
|
|
if ( Results[0] && Results[1] ) {
|
|
NtStatus = STATUS_ACCESS_DENIED;
|
|
}
|
|
}
|
|
|
|
|
|
IsAccessControlGrantedError:
|
|
|
|
if (pSD) {
|
|
LsapFreeLsaHeap(pSD);
|
|
}
|
|
|
|
return NtStatus;
|
|
|
|
}
|
|
|
|
NTSTATUS
|
|
LsapMakeNewSelfRelativeSecurityDescriptor(
|
|
IN PSID Owner,
|
|
IN PSID Group,
|
|
IN PACL Dacl,
|
|
IN PACL Sacl,
|
|
OUT PULONG SecurityDescriptorLength,
|
|
OUT PSECURITY_DESCRIPTOR * SecurityDescriptor
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Given the 4 components of a security descriptor this routine makes a new
|
|
self relative Security descriptor.
|
|
|
|
Parameters:
|
|
|
|
Owner -- The Sid of the owner
|
|
Group -- The Sid of the group
|
|
Dacl -- The Dacl to Use
|
|
Sacl -- The Sacl to Use
|
|
|
|
Return Values:
|
|
|
|
STATUS_SUCCESS
|
|
STATUS_INSUFFICIENT_RESOURCES
|
|
STATUS_UNSUCCESSFUL
|
|
|
|
--*/
|
|
{
|
|
|
|
SECURITY_DESCRIPTOR SdAbsolute;
|
|
NTSTATUS NtStatus = STATUS_SUCCESS;
|
|
|
|
*SecurityDescriptorLength = 0;
|
|
*SecurityDescriptor = NULL;
|
|
|
|
if (!InitializeSecurityDescriptor(&SdAbsolute,SECURITY_DESCRIPTOR_REVISION))
|
|
{
|
|
NtStatus = STATUS_UNSUCCESSFUL;
|
|
goto Error;
|
|
}
|
|
|
|
|
|
//
|
|
// Set the owner, default the owner to administrators alias
|
|
//
|
|
|
|
|
|
if (NULL!=Owner)
|
|
{
|
|
if (!SetSecurityDescriptorOwner(&SdAbsolute,Owner,FALSE))
|
|
{
|
|
NtStatus = STATUS_UNSUCCESSFUL;
|
|
goto Error;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
if (NULL!=Group)
|
|
{
|
|
if (!SetSecurityDescriptorGroup(&SdAbsolute,Group,FALSE))
|
|
{
|
|
NtStatus = STATUS_UNSUCCESSFUL;
|
|
goto Error;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Set the Dacl if there is one
|
|
//
|
|
|
|
if (NULL!=Dacl)
|
|
{
|
|
if (!SetSecurityDescriptorDacl(&SdAbsolute,TRUE,Dacl,FALSE))
|
|
{
|
|
NtStatus = STATUS_UNSUCCESSFUL;
|
|
goto Error;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Set the Sacl if there is one
|
|
//
|
|
|
|
if (NULL!=Sacl)
|
|
{
|
|
if (!SetSecurityDescriptorSacl(&SdAbsolute,TRUE,Sacl,FALSE))
|
|
{
|
|
NtStatus = STATUS_UNSUCCESSFUL;
|
|
goto Error;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Make a new security Descriptor
|
|
//
|
|
|
|
*SecurityDescriptorLength = GetSecurityDescriptorLength(&SdAbsolute);
|
|
*SecurityDescriptor = LsapAllocateLsaHeap(*SecurityDescriptorLength);
|
|
if (NULL==*SecurityDescriptor)
|
|
{
|
|
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Error;
|
|
}
|
|
|
|
|
|
if (!MakeSelfRelativeSD(&SdAbsolute,*SecurityDescriptor,SecurityDescriptorLength))
|
|
{
|
|
NtStatus = STATUS_UNSUCCESSFUL;
|
|
if (*SecurityDescriptor)
|
|
{
|
|
LsapFreeLsaHeap(*SecurityDescriptor);
|
|
*SecurityDescriptor = NULL;
|
|
}
|
|
}
|
|
|
|
Error:
|
|
|
|
|
|
return NtStatus;
|
|
}
|
|
|
|
|
|
//
|
|
// The per user trust quota
|
|
//
|
|
#define LSAP_CHECK_TDO_QUOTA_USER 0x00000001
|
|
|
|
//
|
|
// The all users trust quota
|
|
//
|
|
#define LSAP_CHECK_TDO_QUOTA_GLOBAL 0x00000002
|
|
|
|
//
|
|
// The per user tombstone deletion check
|
|
//
|
|
#define LSAP_CHECK_TDO_QUOTA_USER_DELETED 0x00000004
|
|
|
|
NTSTATUS
|
|
LsapCheckDelegatedTDOQuotas(
|
|
IN PSID ClientSid OPTIONAL,
|
|
IN ULONG Flags
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine verifies the three quota relating to delegated trust
|
|
creation and deletion.
|
|
|
|
Arguments:
|
|
|
|
ClientSid -- the SID to check against
|
|
|
|
Flags -- see defn's above functions
|
|
|
|
Return Values:
|
|
|
|
STATUS_SUCCESS, a resource error otherwise
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS NtStatus = STATUS_SUCCESS;
|
|
ULONG GlobalQuota = 0, PerUserQuota = 0, DeletedQuota = 0;
|
|
ULONG GlobalCount = 0, UserCount = 0, DeletedCount = 0;
|
|
PTOKEN_OWNER TokenOwnerInformation = NULL;
|
|
PSID CreatorSid;
|
|
|
|
//
|
|
// Get the SID of the user
|
|
//
|
|
if (NULL == ClientSid) {
|
|
|
|
NtStatus = LsapGetCurrentOwnerAndPrimaryGroup(&TokenOwnerInformation,
|
|
NULL);
|
|
if (!NT_SUCCESS(NtStatus)) {
|
|
goto CheckDelegatedTDOCreationQuotasExit;
|
|
}
|
|
CreatorSid = TokenOwnerInformation->Owner;
|
|
|
|
} else {
|
|
|
|
CreatorSid = ClientSid;
|
|
|
|
}
|
|
|
|
//
|
|
// Get the domain wide quota settings
|
|
//
|
|
NtStatus = LsapGetDelegatedTDOQuotas(&PerUserQuota,
|
|
&GlobalQuota,
|
|
&DeletedQuota);
|
|
if (!NT_SUCCESS(NtStatus)) {
|
|
goto CheckDelegatedTDOCreationQuotasExit;
|
|
}
|
|
|
|
//
|
|
// Do the checks
|
|
//
|
|
if (Flags & LSAP_CHECK_TDO_QUOTA_USER) {
|
|
|
|
NtStatus = LsapGetDelegatedTDOCount(0, // no flags
|
|
CreatorSid,
|
|
&UserCount);
|
|
if ( NT_SUCCESS(NtStatus)
|
|
&& (UserCount >= PerUserQuota) ) {
|
|
|
|
NtStatus = STATUS_PER_USER_TRUST_QUOTA_EXCEEDED;
|
|
}
|
|
|
|
if (!NT_SUCCESS(NtStatus)) {
|
|
goto CheckDelegatedTDOCreationQuotasExit;
|
|
}
|
|
}
|
|
|
|
if (Flags & LSAP_CHECK_TDO_QUOTA_GLOBAL) {
|
|
|
|
NtStatus = LsapGetDelegatedTDOCount(0, // no flags
|
|
NULL, // all delegated TDO's
|
|
&GlobalCount);
|
|
if ( NT_SUCCESS(NtStatus)
|
|
&& (GlobalCount >= GlobalQuota) ) {
|
|
|
|
NtStatus = STATUS_ALL_USER_TRUST_QUOTA_EXCEEDED;
|
|
}
|
|
if (!NT_SUCCESS(NtStatus)) {
|
|
goto CheckDelegatedTDOCreationQuotasExit;
|
|
}
|
|
}
|
|
|
|
if (Flags & LSAP_CHECK_TDO_QUOTA_USER_DELETED) {
|
|
NtStatus = LsapGetDelegatedTDOCount(LSAP_GET_DELEGATED_TDO_DELETED_ONLY,
|
|
CreatorSid,
|
|
&DeletedCount);
|
|
if ( NT_SUCCESS(NtStatus)
|
|
&& (DeletedCount >= DeletedQuota) ) {
|
|
|
|
|
|
NtStatus = STATUS_USER_DELETE_TRUST_QUOTA_EXCEEDED;
|
|
}
|
|
if (!NT_SUCCESS(NtStatus)) {
|
|
goto CheckDelegatedTDOCreationQuotasExit;
|
|
}
|
|
}
|
|
|
|
CheckDelegatedTDOCreationQuotasExit:
|
|
|
|
if (TokenOwnerInformation) {
|
|
LsapFreeLsaHeap( TokenOwnerInformation );
|
|
}
|
|
|
|
return NtStatus;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
LsapCheckTDOCreationByControlAccess(
|
|
IN PLSAP_DB_OBJECT_INFORMATION ObjectInformation,
|
|
IN PLSAP_DB_ATTRIBUTE Attributes,
|
|
IN ULONG AttributeCount
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine determines if the caller has access to create a trusted
|
|
domain object by "right", as opposed to the standard DS access check
|
|
model which is assumed to have failed at this point.
|
|
|
|
The determination is made by the following rules:
|
|
|
|
1) If the trust is an inbound-only forest trust and
|
|
2) If the caller has the "right to create an inbound trust" on the
|
|
domain object and
|
|
3) the user's quota for trust object creations hasn't been exceeded and
|
|
4) the global quota for trust object creations, created in this manner,
|
|
hasn't been exceeded
|
|
|
|
Then return STATUS_SUCCESS.
|
|
|
|
Else, return a processing error, or STATUS_ACCESS_DENIED.
|
|
|
|
Arguments:
|
|
|
|
ObjectInformation -- information about the trust being created (the name,
|
|
etc)
|
|
|
|
Attributes -- the requested attributes of the trust (inbound, forest, etc)
|
|
|
|
AttributeCount -- the number of attributes
|
|
|
|
Return Values:
|
|
|
|
NTSTATUS, see routine description.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
ULONG i;
|
|
BOOLEAN ForestTrust = FALSE;
|
|
BOOLEAN InboundOnlyTrust = FALSE;
|
|
|
|
//
|
|
// Is this an inbound only forest trust?
|
|
//
|
|
for (i = 0; i < AttributeCount; i++) {
|
|
ULONG BitMask;
|
|
if (Attributes[i].DbNameIndex == TrDmTrLA) {
|
|
ASSERT(Attributes[i].AttribType == LsapDbAttribULong);
|
|
BitMask = *((ULONG*)Attributes[i].AttributeValue);
|
|
if (BitMask & TRUST_ATTRIBUTE_FOREST_TRANSITIVE) {
|
|
ForestTrust = TRUE;
|
|
}
|
|
}
|
|
if (Attributes[i].DbNameIndex == TrDmTrDi) {
|
|
ASSERT(Attributes[i].AttribType == LsapDbAttribULong);
|
|
BitMask = *((ULONG*)Attributes[i].AttributeValue);
|
|
if ( (BitMask & TRUST_DIRECTION_INBOUND)
|
|
&& ((BitMask & TRUST_DIRECTION_OUTBOUND) == 0) ) {
|
|
InboundOnlyTrust = TRUE;
|
|
}
|
|
}
|
|
}
|
|
if (!(ForestTrust && InboundOnlyTrust)) {
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto CheckTDOCreationByControlAccessError;
|
|
}
|
|
|
|
|
|
//
|
|
// Does the caller have the control access right?
|
|
//
|
|
Status = LsapIsAccessControlGranted(LsaDsStateInfo.DsRoot,
|
|
CLASS_DOMAIN_DNS,
|
|
&LsapDsGuidList[LsapDsGuidDelegatedTrustCreation],
|
|
ObjectInformation->ObjectTypeId
|
|
);
|
|
if (!NT_SUCCESS(Status)) {
|
|
goto CheckTDOCreationByControlAccessError;
|
|
}
|
|
|
|
//
|
|
// Are the quota restrictions satisfied?
|
|
//
|
|
Status = LsapCheckDelegatedTDOQuotas(NULL, // we don't have the client sid
|
|
LSAP_CHECK_TDO_QUOTA_GLOBAL |
|
|
LSAP_CHECK_TDO_QUOTA_USER);
|
|
if (!NT_SUCCESS(Status)) {
|
|
goto CheckTDOCreationByControlAccessError;
|
|
}
|
|
|
|
CheckTDOCreationByControlAccessError:
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NTSTATUS
|
|
LsapUpdateTDOAttributesForCreation(
|
|
IN PUNICODE_STRING ObjectName,
|
|
IN PLSAP_DB_ATTRIBUTE Attributes,
|
|
IN OUT ULONG* AttributeCount,
|
|
IN ULONG AttributesAllocated
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine adds the necessary attributes to a trusted domain object
|
|
that is created by the control access right (aka easy trust).
|
|
Specifically, it adds the DS-Creator-Sid (as the network caller) and
|
|
gives the network caller access to write to the incoming auth blob.
|
|
|
|
Arguments:
|
|
|
|
ObjectName -- the name of the trust object in the DS
|
|
|
|
Attributes -- the list of statically declared attributes to be set
|
|
on the trust object.
|
|
|
|
AttributeCount -- in, the number of attributes; out, the updated count
|
|
|
|
AttributesAllocated -- the total number of attributes allocated, but
|
|
not necessarily used.
|
|
|
|
Return Values:
|
|
|
|
STATUS_SUCCESS, a resource error otherwise
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS NtStatus = STATUS_SUCCESS;
|
|
PLSAP_DB_ATTRIBUTE NextAttribute;
|
|
PTOKEN_OWNER TokenOwnerInformation = NULL;
|
|
DSNAME *DsName = NULL;
|
|
PSECURITY_DESCRIPTOR pSD = NULL, pNewSD = NULL, pNewSDForAttr = NULL;
|
|
ULONG cbNewSD = 0;
|
|
ULONG Size;
|
|
PSID CreatorSid = 0;
|
|
PPOLICY_DNS_DOMAIN_INFO LocalDnsDomainInfo = NULL;
|
|
BOOL fSuccess;
|
|
ULONG DomainAdminsSidBuffer[SECURITY_MAX_SID_SIZE/sizeof( ULONG ) + 1 ];
|
|
PSID DomainAdminsSid = (PSID) DomainAdminsSidBuffer;
|
|
|
|
|
|
ASSERT(((*AttributeCount + 2) <= AttributesAllocated)
|
|
&& "Must preallocate more attributes for trusted domain creation");
|
|
|
|
//
|
|
// Generate the Domain Admin's SID to use as the security descriptor
|
|
// owner.
|
|
//
|
|
NtStatus = LsaIQueryInformationPolicyTrusted(PolicyDnsDomainInformation,
|
|
(PLSAPR_POLICY_INFORMATION *) &LocalDnsDomainInfo);
|
|
if (!NT_SUCCESS(NtStatus)) {
|
|
goto UpdateTdoAttributesForCreationExit;
|
|
}
|
|
|
|
Size = sizeof(DomainAdminsSidBuffer);
|
|
fSuccess = CreateWellKnownSid(WinAccountDomainAdminsSid,
|
|
LocalDnsDomainInfo->Sid,
|
|
DomainAdminsSid,
|
|
&Size);
|
|
if (!fSuccess) {
|
|
NtStatus = STATUS_NO_MEMORY;
|
|
goto UpdateTdoAttributesForCreationExit;
|
|
}
|
|
|
|
|
|
//
|
|
// Init the start of the new attributes
|
|
//
|
|
NextAttribute = &Attributes[(*AttributeCount)];
|
|
|
|
//
|
|
// Get the SID of the creator
|
|
//
|
|
NtStatus = LsapGetCurrentOwnerAndPrimaryGroup(&TokenOwnerInformation,
|
|
NULL);
|
|
if (!NT_SUCCESS(NtStatus)) {
|
|
goto UpdateTdoAttributesForCreationExit;
|
|
}
|
|
Size = RtlLengthSid(TokenOwnerInformation->Owner);
|
|
CreatorSid = midl_user_allocate(Size);
|
|
if (NULL == CreatorSid) {
|
|
NtStatus = STATUS_NO_MEMORY;
|
|
goto UpdateTdoAttributesForCreationExit;
|
|
}
|
|
RtlCopySid(Size, CreatorSid, TokenOwnerInformation->Owner);
|
|
|
|
//
|
|
// Add the creator sid
|
|
//
|
|
LsapDbInitializeAttributeDs(
|
|
NextAttribute,
|
|
TrDmCrSid,
|
|
CreatorSid,
|
|
RtlLengthSid(CreatorSid),
|
|
TRUE // to be freed
|
|
);
|
|
|
|
NextAttribute++;
|
|
(*AttributeCount)++;
|
|
CreatorSid = NULL;
|
|
|
|
|
|
//
|
|
// Generate the new security descriptor
|
|
//
|
|
|
|
//
|
|
// Build the DSName
|
|
//
|
|
NtStatus = LsapAllocAndInitializeDsNameFromUnicode(
|
|
ObjectName,
|
|
&DsName);
|
|
|
|
if (NT_SUCCESS(NtStatus)) {
|
|
|
|
NtStatus = LsapDsReadObjectSDByDsName(DsName,
|
|
&pSD);
|
|
|
|
if (NT_SUCCESS(NtStatus)) {
|
|
|
|
//
|
|
// Set the owner to Administrators
|
|
//
|
|
NtStatus = LsapMakeNewSelfRelativeSecurityDescriptor(
|
|
DomainAdminsSid,
|
|
DomainAdminsSid,
|
|
LsapGetDacl(pSD),
|
|
LsapGetSacl(pSD),
|
|
&cbNewSD,
|
|
&pNewSD
|
|
);
|
|
|
|
}
|
|
}
|
|
|
|
if (!NT_SUCCESS(NtStatus)) {
|
|
goto UpdateTdoAttributesForCreationExit;
|
|
}
|
|
|
|
//
|
|
// Need to realloc
|
|
//
|
|
pNewSDForAttr = midl_user_allocate(cbNewSD);
|
|
if (NULL == pNewSDForAttr) {
|
|
NtStatus = STATUS_NO_MEMORY;
|
|
goto UpdateTdoAttributesForCreationExit;
|
|
}
|
|
RtlCopyMemory(pNewSDForAttr, pNewSD, cbNewSD);
|
|
|
|
//
|
|
// Add the new security descriptor
|
|
//
|
|
LsapDbInitializeAttributeDs(
|
|
NextAttribute,
|
|
SecDesc,
|
|
pNewSDForAttr,
|
|
cbNewSD,
|
|
TRUE // to be freed
|
|
);
|
|
|
|
pNewSDForAttr = NULL;
|
|
|
|
NextAttribute++;
|
|
(*AttributeCount)++;
|
|
|
|
|
|
//
|
|
// Done
|
|
//
|
|
|
|
UpdateTdoAttributesForCreationExit:
|
|
|
|
//
|
|
// We shouldn't have added more attributes that are allocated
|
|
//
|
|
ASSERT((*AttributeCount) <= AttributesAllocated);
|
|
|
|
if (TokenOwnerInformation) {
|
|
LsapFreeLsaHeap( TokenOwnerInformation );
|
|
}
|
|
if (LocalDnsDomainInfo) {
|
|
LsaIFree_LSAPR_POLICY_INFORMATION(PolicyDnsDomainInformation,
|
|
(PLSAPR_POLICY_INFORMATION) LocalDnsDomainInfo);
|
|
}
|
|
if (DsName) {
|
|
LsapDsFree(DsName);
|
|
}
|
|
if (pSD) {
|
|
LsapFreeLsaHeap(pSD);
|
|
}
|
|
if (CreatorSid) {
|
|
midl_user_free(CreatorSid);
|
|
}
|
|
if (pNewSD) {
|
|
LsapFreeLsaHeap(pNewSD);
|
|
}
|
|
if (pNewSDForAttr) {
|
|
midl_user_free(pNewSDForAttr);
|
|
}
|
|
|
|
return NtStatus;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
LsapCheckTDODeletionQuotas(
|
|
IN LSAP_DB_HANDLE Handle
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine checks to make sure that the client has not exceeded the
|
|
number of deleted trusts they are allowed.
|
|
|
|
Arguments:
|
|
|
|
Handle -- the handle to the trust object
|
|
|
|
Return Values:
|
|
|
|
STATUS_SUCCESS, a resource error otherwise
|
|
STATUS_QUOTA_EXCEEDED
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PSID ClientSid = NULL;
|
|
PSID CreatorSid = NULL;
|
|
PTOKEN_OWNER TokenOwnerInformation = NULL;
|
|
ULONG Quota = 0, ClientUsage = 0;
|
|
LSAP_DB_ATTRIBUTE Attribute;
|
|
|
|
//
|
|
// Get the trust creator SID, if any
|
|
//
|
|
LsapDbInitializeAttributeDs(
|
|
&Attribute,
|
|
TrDmCrSid,
|
|
NULL,
|
|
0,
|
|
FALSE
|
|
);
|
|
|
|
Status = LsapDsReadAttributes(&Handle->PhysicalNameDs,
|
|
LSAPDS_OP_NO_LOCK,
|
|
&Attribute,
|
|
1);
|
|
|
|
if (STATUS_NOT_FOUND == Status) {
|
|
//
|
|
// No creator? No quota
|
|
//
|
|
Status = STATUS_SUCCESS;
|
|
goto CheckTDODeletionQuotasExit;
|
|
}
|
|
if (!NT_SUCCESS(Status)) {
|
|
goto CheckTDODeletionQuotasExit;
|
|
}
|
|
CreatorSid = (PSID) Attribute.AttributeValue;
|
|
|
|
|
|
//
|
|
// Get the client SID
|
|
//
|
|
Status = LsapGetCurrentOwnerAndPrimaryGroup(&TokenOwnerInformation,
|
|
NULL);
|
|
if (!NT_SUCCESS(Status)) {
|
|
goto CheckTDODeletionQuotasExit;
|
|
}
|
|
ClientSid = TokenOwnerInformation->Owner;
|
|
|
|
|
|
//
|
|
// Does it match the caller?
|
|
//
|
|
if (!RtlEqualSid(ClientSid, CreatorSid)) {
|
|
//
|
|
// No quota enforced
|
|
//
|
|
goto CheckTDODeletionQuotasExit;
|
|
}
|
|
|
|
//
|
|
// Do the quota check
|
|
//
|
|
Status = LsapCheckDelegatedTDOQuotas(ClientSid,
|
|
LSAP_CHECK_TDO_QUOTA_USER_DELETED);
|
|
|
|
|
|
//
|
|
// Fall through to exit
|
|
//
|
|
|
|
CheckTDODeletionQuotasExit:
|
|
|
|
if (TokenOwnerInformation) {
|
|
LsapFreeLsaHeap( TokenOwnerInformation );
|
|
}
|
|
|
|
if (CreatorSid) {
|
|
MIDL_user_free( CreatorSid );
|
|
}
|
|
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
|
|
|
|
NTSTATUS
|
|
LsapGetDelegatedTDOQuotas(
|
|
OUT ULONG *PerUserQuota OPTIONAL,
|
|
OUT ULONG *GlobalQuota OPTIONAL,
|
|
OUT ULONG *PerUserDeletedQuota OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine returns the domain-wide delegatable TDO quotas.
|
|
|
|
Arguments:
|
|
|
|
PerUserQuota, GlobalQuota, PerUserDeletedQuota -- the quota to be filled in
|
|
|
|
Return Values:
|
|
|
|
STATUS_SUCCESS, a resource error otherwise
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS NtStatus = STATUS_SUCCESS;
|
|
BOOLEAN ReleaseState = FALSE;
|
|
ULONG LocalGlobalQuota = 0,
|
|
LocalPerUserQuota = 0,
|
|
LocalTombstoneQuota = 0;
|
|
ATTRBLOCK ReadResAttrBlock = {0, NULL};
|
|
ATTRBLOCK ReadAttrBlock = {0, NULL};
|
|
ULONG i;
|
|
|
|
//
|
|
// Start a transaction, if necessary
|
|
//
|
|
NtStatus = LsapDsInitAllocAsNeededEx( LSAP_DB_READ_ONLY_TRANSACTION |
|
|
LSAP_DB_DS_OP_TRANSACTION,
|
|
NullObject,
|
|
&ReleaseState );
|
|
|
|
if ( !NT_SUCCESS( NtStatus ) ) {
|
|
goto GetDelegatedTDOQuotasError;
|
|
}
|
|
|
|
//
|
|
// Read the attributes
|
|
//
|
|
ReadAttrBlock.attrCount = LsapDsTDOQuotaAttributesCount;
|
|
ReadAttrBlock.pAttr = LsapDsTDOQuotaAttributes;
|
|
NtStatus = LsapDsReadByDsName(LsaDsStateInfo.DsRoot,
|
|
0,
|
|
&ReadAttrBlock,
|
|
&ReadResAttrBlock);
|
|
|
|
if (NtStatus == STATUS_NOT_FOUND) {
|
|
//
|
|
// Attributes aren't present; that's ok
|
|
//
|
|
NtStatus = STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
if (!NT_SUCCESS(NtStatus)) {
|
|
goto GetDelegatedTDOQuotasError;
|
|
}
|
|
|
|
//
|
|
// Extract the attribute
|
|
//
|
|
for (i = 0; i < ReadResAttrBlock.attrCount; i++) {
|
|
|
|
DWORD Value;
|
|
|
|
ASSERT(ReadResAttrBlock.pAttr[i].AttrVal.valCount == 1);
|
|
ASSERT(ReadResAttrBlock.pAttr[i].AttrVal.pAVal[0].valLen == sizeof(DWORD));
|
|
|
|
Value = *((ULONG*)ReadResAttrBlock.pAttr[i].AttrVal.pAVal[0].pVal);
|
|
|
|
switch (ReadResAttrBlock.pAttr[i].attrTyp) {
|
|
|
|
case ATT_MS_DS_PER_USER_TRUST_QUOTA:
|
|
LocalPerUserQuota = Value;
|
|
break;
|
|
|
|
case ATT_MS_DS_ALL_USERS_TRUST_QUOTA:
|
|
LocalGlobalQuota = Value;
|
|
break;
|
|
|
|
case ATT_MS_DS_PER_USER_TRUST_TOMBSTONES_QUOTA:
|
|
LocalTombstoneQuota = Value;
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
if (PerUserQuota) {
|
|
*PerUserQuota = LocalPerUserQuota;
|
|
}
|
|
if (GlobalQuota) {
|
|
*GlobalQuota = LocalGlobalQuota;
|
|
}
|
|
if (PerUserDeletedQuota) {
|
|
*PerUserDeletedQuota = LocalTombstoneQuota;
|
|
}
|
|
|
|
GetDelegatedTDOQuotasError:
|
|
|
|
if (ReleaseState) {
|
|
|
|
LsapDsDeleteAllocAsNeededEx( LSAP_DB_READ_ONLY_TRANSACTION |
|
|
LSAP_DB_DS_OP_TRANSACTION,
|
|
NullObject,
|
|
ReleaseState );
|
|
|
|
}
|
|
|
|
return NtStatus;
|
|
}
|
|
|
|
|
|
|
|
NTSTATUS
|
|
LsapGetDelegatedTDOCount(
|
|
IN ULONG Flags,
|
|
IN PSID CreatorSid OPTIONAL,
|
|
OUT ULONG *Count
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine returns the number of TDO's that satisfy the input parameters.
|
|
|
|
Arguments:
|
|
|
|
Flags -- LSAP_GET_DELEGATED_TDO_DELETED_ONLY return only deleted objects
|
|
|
|
CreatorSid -- if present, the TDO must have a msds-creator-sid attribute
|
|
equal to this value
|
|
|
|
Count -- the number of objects that match the request
|
|
|
|
Return Values:
|
|
|
|
STATUS_SUCCESS, a resource error otherwise
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
SEARCHARG SearchArg;
|
|
FILTER Filters[ 3 ], RootFilter;
|
|
ENTINFSEL EntInfSel;
|
|
ENTINFLIST *EntInfList;
|
|
ULONG ClassId, FlagValue, i;
|
|
SEARCHRES *SearchRes = NULL;
|
|
BOOLEAN CloseTransaction = FALSE;
|
|
ATTR AttrsToRead[] = {{ATT_OBJECT_GUID, {0, NULL}}};
|
|
ULONG LocalCount = 0;
|
|
USHORT FilterCount = 0;
|
|
BOOL True = TRUE;
|
|
|
|
RtlZeroMemory( &SearchArg, sizeof( SEARCHARG ) );
|
|
|
|
//
|
|
// See if we already have a transaction going
|
|
//
|
|
// If one already exists, we'll use the existing transaction and not
|
|
// delete the thread state at the end.
|
|
//
|
|
|
|
Status = LsapDsInitAllocAsNeededEx( LSAP_DB_READ_ONLY_TRANSACTION |
|
|
LSAP_DB_DS_OP_TRANSACTION,
|
|
NullObject,
|
|
&CloseTransaction );
|
|
if (!NT_SUCCESS( Status)) {
|
|
goto GetDelegatedTDOCountExit;
|
|
}
|
|
|
|
//
|
|
// Build the filter.
|
|
//
|
|
ClassId = CLASS_TRUSTED_DOMAIN;
|
|
|
|
RtlZeroMemory( Filters, sizeof (Filters) );
|
|
RtlZeroMemory( &RootFilter, sizeof (RootFilter) );
|
|
|
|
//
|
|
// Match the msds-Creator-Sid
|
|
//
|
|
Filters[ 0 ].choice = FILTER_CHOICE_ITEM;
|
|
if (CreatorSid) {
|
|
Filters[ 0 ].FilterTypes.Item.choice = FI_CHOICE_EQUALITY;
|
|
Filters[ 0 ].FilterTypes.Item.FilTypes.ava.type = ATT_MS_DS_CREATOR_SID;
|
|
Filters[ 0 ].FilterTypes.Item.FilTypes.ava.Value.valLen = RtlLengthSid(CreatorSid);
|
|
Filters[ 0 ].FilterTypes.Item.FilTypes.ava.Value.pVal = ( PUCHAR )CreatorSid;
|
|
} else {
|
|
Filters[ 0 ].FilterTypes.Item.choice = FI_CHOICE_PRESENT;
|
|
Filters[ 0 ].FilterTypes.Item.FilTypes.present = ATT_MS_DS_CREATOR_SID;
|
|
}
|
|
FilterCount++;
|
|
|
|
//
|
|
// Only TDO's
|
|
//
|
|
Filters[ 0 ].pNextFilter = &Filters[ 1 ];
|
|
Filters[ 1 ].choice = FILTER_CHOICE_ITEM;
|
|
Filters[ 1 ].FilterTypes.Item.choice = FI_CHOICE_EQUALITY;
|
|
Filters[ 1 ].FilterTypes.Item.FilTypes.ava.type = ATT_OBJECT_CLASS;
|
|
Filters[ 1 ].FilterTypes.Item.FilTypes.ava.Value.valLen = sizeof( ULONG );
|
|
Filters[ 1 ].FilterTypes.Item.FilTypes.ava.Value.pVal = ( PUCHAR )&ClassId;
|
|
FilterCount++;
|
|
|
|
if (Flags & LSAP_GET_DELEGATED_TDO_DELETED_ONLY) {
|
|
|
|
//
|
|
// Only deleted TDO's
|
|
//
|
|
Filters[ 1 ].pNextFilter = &Filters[ 2 ];
|
|
Filters[ 2 ].choice = FILTER_CHOICE_ITEM;
|
|
Filters[ 2 ].FilterTypes.Item.choice = FI_CHOICE_EQUALITY;
|
|
Filters[ 2 ].FilterTypes.Item.FilTypes.ava.type = ATT_IS_DELETED;
|
|
Filters[ 2 ].FilterTypes.Item.FilTypes.ava.Value.valLen = sizeof( BOOL );
|
|
Filters[ 2 ].FilterTypes.Item.FilTypes.ava.Value.pVal = ( PUCHAR )&True;
|
|
FilterCount++;
|
|
|
|
//
|
|
// Search the NC, since deleted objects are moved in to the
|
|
// deleted objects container
|
|
//
|
|
SearchArg.pObject = LsaDsStateInfo.DsRoot;
|
|
SearchArg.choice = SE_CHOICE_WHOLE_SUBTREE;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Search just the system container
|
|
//
|
|
|
|
SearchArg.pObject = LsaDsStateInfo.DsSystemContainer;
|
|
SearchArg.choice = SE_CHOICE_IMMED_CHLDRN;
|
|
}
|
|
|
|
RootFilter.choice = FILTER_CHOICE_AND;
|
|
RootFilter.FilterTypes.And.count = FilterCount;
|
|
RootFilter.FilterTypes.And.pFirstFilter = Filters;
|
|
|
|
SearchArg.bOneNC = TRUE;
|
|
SearchArg.pFilter = &RootFilter;
|
|
SearchArg.searchAliases = FALSE;
|
|
SearchArg.pSelection = &EntInfSel;
|
|
|
|
//
|
|
// Build the list of attributes to return
|
|
//
|
|
EntInfSel.attSel = EN_ATTSET_LIST;
|
|
EntInfSel.AttrTypBlock.attrCount = 1;
|
|
EntInfSel.AttrTypBlock.pAttr = AttrsToRead;
|
|
EntInfSel.infoTypes = EN_INFOTYPES_TYPES_VALS;
|
|
|
|
//
|
|
// Build the Commarg structure
|
|
//
|
|
LsapDsInitializeStdCommArg( &( SearchArg.CommArg ), 0 );
|
|
if (Flags & LSAP_GET_DELEGATED_TDO_DELETED_ONLY) {
|
|
SearchArg.CommArg.Svccntl.makeDeletionsAvail = TRUE;
|
|
}
|
|
|
|
//
|
|
// There could be thousands of trusts; make a paged search
|
|
// to scale
|
|
//
|
|
SearchArg.CommArg.PagedResult.fPresent = TRUE;
|
|
SearchArg.CommArg.ulSizeLimit = 100;
|
|
|
|
LsapDsSetDsaFlags( TRUE );
|
|
|
|
while (NT_SUCCESS(Status)
|
|
&& SearchArg.CommArg.PagedResult.fPresent) {
|
|
|
|
DirSearch( &SearchArg, &SearchRes );
|
|
LsapDsContinueTransaction();
|
|
|
|
if ( SearchRes ) {
|
|
|
|
Status = LsapDsMapDsReturnToStatusEx( &SearchRes->CommRes );
|
|
|
|
} else {
|
|
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
|
|
//
|
|
// Increment the count
|
|
//
|
|
LocalCount += SearchRes->count;
|
|
|
|
//
|
|
// See if we have to search again
|
|
//
|
|
SearchArg.CommArg.PagedResult.fPresent =
|
|
SearchRes->PagedResult.fPresent;
|
|
|
|
SearchArg.CommArg.PagedResult.pRestart =
|
|
SearchRes->PagedResult.pRestart;
|
|
|
|
}
|
|
}
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
*Count = LocalCount;
|
|
}
|
|
|
|
GetDelegatedTDOCountExit:
|
|
|
|
if (CloseTransaction) {
|
|
|
|
LsapDsDeleteAllocAsNeededEx( LSAP_DB_READ_ONLY_TRANSACTION |
|
|
LSAP_DB_DS_OP_TRANSACTION,
|
|
NullObject,
|
|
CloseTransaction );
|
|
}
|
|
|
|
return( Status );
|
|
|
|
}
|
|
|