Leaked source code of windows server 2003
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.
 
 
 
 
 
 

7068 lines
210 KiB

/*++
Copyright (c) 2001 Microsoft Corporation
Module Name:
context.cxx
Abstract:
Routines implementing the client context API
Author:
Cliff Van Dyke (cliffv) 22-May-2001
--*/
#include "pch.hxx"
#include <winber.h>
#include <ntseapi.h>
#include <kerberos.h>
#include <azroles.h>
//
// Structure definitions
//
//
// Structure representing an APP group that a client context may be a member of
//
typedef struct _AZP_MEMBER_EVALUATION {
//
// Link to next entry in the list of member evaluation structs for this
// client context.
//
LIST_ENTRY Next;
//
// Group being evaluated
//
PAZP_GROUP Group;
//
// Status of the evaluation
// NO_ERROR: Membership has been determined
// NOT_YET_DONE: Membership has not yet been determined
// ERROR_NO_SUCH_DOMAIN: We couldn't contact the domain controller
//
DWORD WinStatus;
#define NOT_YET_DONE 0xFFFFFFFF
//
// Indicates whether the client is a member of the specified group.
// This field is valid only if WinStatus is NO_ERROR.
//
BOOLEAN IsMember;
} AZP_MEMBER_EVALUATION, *PAZP_MEMBER_EVALUATION;
//
// Macros
//
// PopUlong: remove a ULONG from an array of ULONGs
// Simply replace the element by the last element in the array and
// make the array shorter
//
#define PopUlong( _Array, _Index, _Count ) { \
(_Count)--; \
(_Array)[_Index] = (_Array)[_Count]; \
}
extern BOOL AzIsDC;
extern PSID AzAccountDomainSid;
extern BOOL AzAccountDomainSidInitialized;
//
// Procedure forwards
//
DWORD
AzpCheckGroupMembershipOne(
IN PAZP_CLIENT_CONTEXT ClientContext,
IN PAZP_GROUP Group,
IN BOOLEAN LocalOnly,
IN DWORD RecursionLevel,
OUT PBOOLEAN RetIsMember,
OUT LPDWORD ExtendedStatus
);
DWORD
AzpAccessCheckGenerateAudit(
IN PACCESS_CHECK_CONTEXT AcContext
)
/*++
Routine Description:
This routine generates run-time access check audits. One success or failure
audit is generated per operation.
Arguments:
AcContext - Specifies the context of the user who will be audited.
Return Value:
NO_ERROR - The operation was successful
Other exception status codes
--*/
{
BOOL b;
ULONG i;
DWORD WinStatus = NO_ERROR;
PAUDIT_PARAMS pAuditParams = NULL;
PAZP_AZSTORE AzAuthorizationStore;
AUTHZ_AUDIT_EVENT_HANDLE hAuditEvent = NULL;
PAZP_APPLICATION Application = AcContext->Application;
AUTHZ_AUDIT_EVENT_TYPE_HANDLE hAuditHandle = NULL;
//
// Get the authorization store pointer for the application
//
AzAuthorizationStore = (PAZP_AZSTORE) ParentOfChild(&AcContext->Application->GenericObject);
//
// If audit handle is null, then do not generate any audits
//
if ( (AzAuthorizationStore->hAccessCheckAuditEventType == NULL) &&
(AzAuthorizationStore->hAccessCheckNameAuditEventType == NULL) ) {
return NO_ERROR;
}
if (!AuthziAllocateAuditParams(
&pAuditParams,
AZP_ACCESSCHECK_AUDITPARAMS_NO+2
)) {
goto Cleanup;
}
//
// Loop thru all the operations and generate one audit per operation
//
for ( i=0; i < AcContext->OperationCount; i++ ) {
//
// Decide on whether this is a success or a failure audit
//
DWORD Flags = (AcContext->Results[i] == NO_ERROR) ?
APF_AuditSuccess :
APF_AuditFailure;
//
// The structure of the audit is as follows
// %tApplication Name:%t%1%n
// %tApplication Instance ID
// %tObject Name:%t%3%n
// %tScope Names:%t%4%n
// %tClient Name:%t%5%n
// %tClient Domain:%t%6%n
// %tClient Context ID:%t%7%
// %tRole:%t%8%n
// %tGroups:%t%9%n
// %tOperation:%t%10%n
// %tOperation ID:%t%11%n
//
//
// Fill in the audit parameters array
//
if ( AcContext->ClientContext->CreationType == AZP_CONTEXT_CREATED_FROM_TOKEN ) {
hAuditHandle = AzAuthorizationStore->hAccessCheckAuditEventType;
b = AuthziInitializeAuditParamsWithRM( Flags,
Application->AuthzResourceManager,
AZP_ACCESSCHECK_AUDITPARAMS_NO,
pAuditParams,
APT_String, Application->GenericObject.ObjectName->ObjectName.String,
APT_Luid, Application->InstanceId,
APT_String, AcContext->ObjectNameString.String,
APT_String, AcContext->ScopeNameString.String ? AcContext->ScopeNameString.String : L"-",
APT_LogonId, AcContext->ClientContext->LogonId,
APT_String, L"Role",
APT_String, L"Group",
APT_String, AcContext->OperationObjects[i]->GenericObject.ObjectName->ObjectName.String,
APT_Ulong, AcContext->OperationObjects[i]->OperationId );
} else if ( AcContext->ClientContext->CreationType == AZP_CONTEXT_CREATED_FROM_NAME ||
AcContext->ClientContext->CreationType == AZP_CONTEXT_CREATED_FROM_SID ) {
hAuditHandle = AzAuthorizationStore->hAccessCheckNameAuditEventType;
//
// both from name and from SID audit share the same audit layout
//
// We do not have the client Logon Id here since the context was created
// from name or SID. So, we pass the Logon id that we allocated as a LUID and
// the Domain Name, Client Name as separate strings. LSA can not lookup
// the names from the LUID since no logon session exists.
//
b = AuthziInitializeAuditParamsWithRM( Flags,
Application->AuthzResourceManager,
AZP_ACCESSCHECK_AUDITPARAMS_NO+2,
pAuditParams,
APT_String, Application->GenericObject.ObjectName->ObjectName.String,
APT_Luid, Application->InstanceId,
APT_String, AcContext->ObjectNameString.String,
APT_String, AcContext->ScopeNameString.String ? AcContext->ScopeNameString.String : L"-",
APT_String, AcContext->ClientContext->ClientName,
APT_String, AcContext->ClientContext->DomainName ? AcContext->ClientContext->DomainName : L"",
APT_Luid, AcContext->ClientContext->LogonId,
APT_String, L"Role",
APT_String, L"Group",
APT_String, AcContext->OperationObjects[i]->GenericObject.ObjectName->ObjectName.String,
APT_Ulong, AcContext->OperationObjects[i]->OperationId );
} else {
ASSERT( FALSE );
b = FALSE;
WinStatus = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
if (!b) {
WinStatus = GetLastError();
goto Cleanup;
}
//
// Fill in the Audit event handle
//
b = AuthziInitializeAuditEvent( 0,
NULL,
hAuditHandle,
pAuditParams,
NULL,
INFINITE,
L"", L"", L"", L"",
&hAuditEvent );
if (!b) {
WinStatus = GetLastError();
goto Cleanup;
}
//
// Send off the audit to LSA
//
b = AuthziLogAuditEvent( 0,
hAuditEvent,
NULL );
if (!b) {
WinStatus = GetLastError();
goto Cleanup;
}
//
// Free the audit event sructure and set it to NULL
//
AuthzFreeAuditEvent( hAuditEvent );
hAuditEvent = NULL;
}
Cleanup:
//
// Free the audit event handle
//
if ( hAuditEvent != NULL ) {
AuthzFreeAuditEvent( hAuditEvent );
}
//
// Free the PAUDIT_PARAMS structure
//
if ( pAuditParams ) {
AuthziFreeAuditParams( pAuditParams );
}
return WinStatus;
}
DWORD
AzpClientContextGenerateCreateSuccessAudit(
IN PAZP_CLIENT_CONTEXT ClientContext,
IN PAZP_APPLICATION Application
)
/*++
Routine Description:
This routine generates success audit for client context creation.
Arguments:
ClientContext - Specifies the context of the user who will be audited.
Application - Specifies the application in whose scope the client context
has been created.
Return Value:
NO_ERROR - The operation was successful
Other exception status codes
--*/
{
BOOL b;
DWORD WinStatus = NO_ERROR;
PAUDIT_PARAMS pAuditParams = NULL;
PAZP_AZSTORE AzAuthorizationStore;
AUTHZ_AUDIT_EVENT_HANDLE hAuditEvent = NULL;
AUTHZ_AUDIT_EVENT_TYPE_HANDLE hAuditHandle = NULL;
//
// Get the authorization store pointer
//
AzAuthorizationStore = (PAZP_AZSTORE) ParentOfChild(&Application->GenericObject);
//
// If audit handle is null, then do not generate any audits
//
if ( (AzAuthorizationStore->hClientContextCreateAuditEventType == NULL) &&
(AzAuthorizationStore->hClientContextCreateNameAuditEventType == NULL) ) {
return NO_ERROR;
}
if (!AuthziAllocateAuditParams(
&pAuditParams,
AZP_ACCESSCHECK_AUDITPARAMS_NO+2
)) {
goto Cleanup;
}
//
// The structure of the audit is as follows
// %tApplication Name:%t%1%n
// %tApplication Instance ID:%t%2%n
// %tClient Name:%t%3%n
// %tClient Domain:%t%4%n
// %tClient Context ID:%t%5%n
// %tStatus:%t%6%n
//
//
// Fill in the audit parameters array
//
if ( ClientContext->CreationType == AZP_CONTEXT_CREATED_FROM_TOKEN ) {
hAuditHandle = AzAuthorizationStore->hClientContextCreateAuditEventType;
b = AuthziInitializeAuditParamsWithRM( APF_AuditSuccess,
Application->AuthzResourceManager,
AZP_CLIENTCREATE_AUDITPARAMS_NO,
pAuditParams,
APT_String, Application->GenericObject.ObjectName->ObjectName.String,
APT_Luid, Application->InstanceId,
APT_LogonId, ClientContext->LogonId,
APT_Ulong, NO_ERROR );
} else if ( ClientContext->CreationType == AZP_CONTEXT_CREATED_FROM_NAME ||
ClientContext->CreationType == AZP_CONTEXT_CREATED_FROM_SID ) {
hAuditHandle = AzAuthorizationStore->hClientContextCreateNameAuditEventType;
//
// both from name or from SID contexts share the same audit layout
//
// We do not have the client Logon Id here since the context was created
// from name or SID. So, we pass the Logon id that we allocated as a LUID and
// the Domain Name, Client Name as separate strings. LSA can not lookup
// the names from the LUID since no logon session exists.
//
b = AuthziInitializeAuditParamsWithRM( APF_AuditSuccess,
Application->AuthzResourceManager,
AZP_CLIENTCREATE_AUDITPARAMS_NO+2,
pAuditParams,
APT_String, Application->GenericObject.ObjectName->ObjectName.String,
APT_Luid, Application->InstanceId,
APT_String, ClientContext->ClientName,
APT_String, ClientContext->DomainName ? ClientContext->DomainName : L"",
APT_Luid, ClientContext->LogonId,
APT_Ulong, NO_ERROR );
} else {
ASSERT( FALSE );
b = FALSE;
WinStatus = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
if (!b) {
WinStatus = GetLastError();
goto Cleanup;
}
//
// Fill in the Audit event handle
//
b = AuthziInitializeAuditEvent( 0,
NULL,
hAuditHandle,
pAuditParams,
NULL,
INFINITE,
L"", L"", L"", L"",
&hAuditEvent );
if (!b) {
WinStatus = GetLastError();
goto Cleanup;
}
//
// Send off the audit to LSA
//
b = AuthziLogAuditEvent( 0,
hAuditEvent,
NULL );
if (!b) {
WinStatus = GetLastError();
goto Cleanup;
}
Cleanup:
//
// Free the audit event handle
//
if ( hAuditEvent != NULL ) {
AuthzFreeAuditEvent( hAuditEvent );
}
//
// Free the PAUDIT_PARAMS structure
//
if ( pAuditParams ) {
AuthziFreeAuditParams( pAuditParams );
}
return WinStatus;
}
DWORD
AzpClientContextGenerateDeleteAudit(
IN PAZP_CLIENT_CONTEXT ClientContext,
IN PAZP_APPLICATION Application
)
/*++
Routine Description:
This routine generates audit for client context deletion.
On entry, AzGlResource must be locked exclusively.
Arguments:
ClientContext - Specifies the context of the user who will be audited.
Application - Specifies the application in whose scope the client context
has been created.
Return Value:
NO_ERROR - The operation was successful
Other exception status codes
--*/
{
DWORD WinStatus = NO_ERROR;
BOOL b;
AUTHZ_AUDIT_EVENT_HANDLE hAuditEvent = NULL;
PAUDIT_PARAMS pAuditParams = NULL;
PAZP_AZSTORE AzAuthorizationStore;
AUTHZ_AUDIT_EVENT_TYPE_HANDLE hAuditHandle = NULL;
//
// Get the authorization store pointer
//
AzAuthorizationStore = (PAZP_AZSTORE) ParentOfChild(&Application->GenericObject);
//
// If audit handles are null, then do not generate any audtis
//
if ( (AzAuthorizationStore->hClientContextDeleteAuditEventType == NULL) &&
(AzAuthorizationStore->hClientContextDeleteNameAuditEventType == NULL) ) {
return NO_ERROR;
}
if (!AuthziAllocateAuditParams(
&pAuditParams,
AZP_ACCESSCHECK_AUDITPARAMS_NO+2
)) {
goto Cleanup;
}
//
// The structure of the audit is as follows
// %tApplication Name:%t%1%n
// %tApplication Instance ID:%t%2%n
// %tClient Name:%t%3%n
// %tClient Domain:%t%4%n
// %tClient Context ID:%t%5%n
//
//
// Fill in the audit parameters array
//
if ( ClientContext->CreationType == AZP_CONTEXT_CREATED_FROM_TOKEN ) {
hAuditHandle = AzAuthorizationStore->hClientContextDeleteAuditEventType;
b = AuthziInitializeAuditParamsWithRM( APF_AuditSuccess,
Application->AuthzResourceManager,
AZP_CLIENTDELETE_AUDITPARAMS_NO,
pAuditParams,
APT_String, Application->GenericObject.ObjectName->ObjectName.String,
APT_Luid, Application->InstanceId,
APT_LogonId, ClientContext->LogonId );
} else if ( ClientContext->CreationType == AZP_CONTEXT_CREATED_FROM_NAME ||
ClientContext->CreationType == AZP_CONTEXT_CREATED_FROM_SID ) {
hAuditHandle = AzAuthorizationStore->hClientContextDeleteNameAuditEventType;
//
// both from name or from SID contexts share the same audit layout
//
// We do not have the client Logon Id here since the context was created
// from name or SID. So, we pass the Logon id that we allocated as a LUID and
// the Domain Name, Client Name as separate strings. LSA can not lookup
// the names from the LUID since no logon session exists.
//
b = AuthziInitializeAuditParamsWithRM( APF_AuditSuccess,
Application->AuthzResourceManager,
AZP_CLIENTDELETE_AUDITPARAMS_NO+2,
pAuditParams,
APT_String, Application->GenericObject.ObjectName->ObjectName.String,
APT_Luid, Application->InstanceId,
APT_String, ClientContext->ClientName,
APT_String, ClientContext->DomainName ? ClientContext->DomainName : L"",
APT_Luid, ClientContext->LogonId);
} else {
ASSERT( FALSE );
b = FALSE;
WinStatus = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
if (!b) {
WinStatus = GetLastError();
goto Cleanup;
}
//
// Fill in the Audit event handle
//
b = AuthziInitializeAuditEvent( 0,
NULL,
hAuditHandle,
pAuditParams,
NULL,
INFINITE,
L"", L"", L"", L"",
&hAuditEvent );
if (!b) {
WinStatus = GetLastError();
goto Cleanup;
}
//
// Send off the audit to LSA
//
b = AuthziLogAuditEvent( 0,
hAuditEvent,
NULL );
if (!b) {
WinStatus = GetLastError();
goto Cleanup;
}
Cleanup:
//
// Free the audit event handle
//
if ( hAuditEvent != NULL ) {
AuthzFreeAuditEvent( hAuditEvent );
}
//
// Free the PAUDIT_PARAMS structure
//
if ( pAuditParams ) {
AuthziFreeAuditParams( pAuditParams );
}
return WinStatus;
}
VOID
AzpFlushGroupEval(
IN PAZP_CLIENT_CONTEXT ClientContext
)
/*++
Routine Description:
This routine flushes the group evaluation for the specified client context
On entry, AzGlResource must be locked shared
Arguments:
ClientContext - Specifies the client context for which all group evaluation is
to be flushed.
Return Value:
None.
--*/
{
//
// Initialization
//
ASSERT( AzpIsLockedShared( &AzGlResource ) );
//
// Free the cache of group membership evaluations
//
while ( !IsListEmpty( &ClientContext->MemEval ) ) {
PAZP_MEMBER_EVALUATION MemEval;
PLIST_ENTRY ListEntry;
//
// Remove the entry from the list
//
ListEntry = RemoveHeadList( &ClientContext->MemEval );
MemEval = CONTAINING_RECORD( ListEntry,
AZP_MEMBER_EVALUATION,
Next );
AzpFreeHeap( MemEval );
}
}
DWORD
AzpClientContextInit(
IN PGENERIC_OBJECT ParentGenericObject,
IN PGENERIC_OBJECT ChildGenericObject
)
/*++
Routine Description:
This routine is a worker routine for AzInitializeClientContextFrom*. It does any object specific
initialization that needs to be done.
On entry, AzGlResource must be locked exclusively.
Arguments:
ParentGenericObject - Specifies the parent object to add the child object onto.
The reference count has been incremented on this object.
ChildGenericObject - Specifies the newly allocated child object.
The reference count has been incremented on this object.
Return Value:
NO_ERROR - The operation was successful
ERROR_NOT_ENOUGH_MEMORY - not enough memory
Other exception status codes
--*/
{
NTSTATUS Status;
PAZP_CLIENT_CONTEXT ClientContext = (PAZP_CLIENT_CONTEXT) ChildGenericObject;
UNREFERENCED_PARAMETER( ParentGenericObject );
//
// Initialization
//
ASSERT( AzpIsLockedExclusive( &AzGlResource ) );
//
// Initialize the client context critical section
//
Status = SafeInitializeCriticalSection( &ClientContext->CritSect, SAFE_CLIENT_CONTEXT );
if ( !NT_SUCCESS( Status )) {
return RtlNtStatusToDosError( Status );
}
ClientContext->CritSectInitialized = TRUE;
//
// ClientContexts are referenced by "Applications"
// Let the generic object manager know all of the lists we support
// This is a "back" link so we don't need to define which applications can reference this client context.
//
ChildGenericObject->GenericObjectLists = &ClientContext->backApplications;
// Back link to applications
ObInitObjectList( &ClientContext->backApplications,
NULL,
TRUE, // Backward link
0, // No link pair id
0, // No dirty bit on back link
NULL,
NULL,
NULL );
//
// Maintain a handle to the user's token / identity.
//
ClientContext->TokenHandle = INVALID_HANDLE_VALUE;
ClientContext->ClientName = NULL;
ClientContext->DomainName = NULL;
AzpInitString(&ClientContext->RoleName, NULL);
//
// Initialize the cache of group membership evaluations
//
InitializeListHead( &ClientContext->MemEval );
//
// Store the serial number of that cache
//
ClientContext->GroupEvalSerialNumber =
ClientContext->GenericObject.AzStoreObject->GroupEvalSerialNumber;
//
// Initialize the operation cache
//
AzpInitOperationCache( ClientContext );
return NO_ERROR;
}
VOID
AzpClientContextFree(
IN PGENERIC_OBJECT GenericObject
)
/*++
Routine Description:
This routine is a worker routine for ClientContext object free. It does any object specific
cleanup that needs to be done.
On entry, AzGlResource must be locked exclusively.
Arguments:
GenericObject - Specifies a pointer to the object to be deleted.
Return Value:
None
--*/
{
PAZP_CLIENT_CONTEXT ClientContext = (PAZP_CLIENT_CONTEXT) GenericObject;
PAZP_APPLICATION Application = (PAZP_APPLICATION) ParentOfChild( &ClientContext->GenericObject );
PAZP_AZSTORE AzAuthorizationStore = (PAZP_AZSTORE) ParentOfChild( &Application->GenericObject );
DWORD WinStatus;
//
// Initialization
//
ASSERT( AzpIsLockedExclusive( &AzGlResource ) );
//
// Generate a Client context deletion audit if needed.
//
if ( Application->GenericObject.IsGeneratingAudits &&
!AzpOpenToManageStore(AzAuthorizationStore) ) {
WinStatus = AzpClientContextGenerateDeleteAudit( ClientContext,
Application );
if ( WinStatus != NO_ERROR ) {
AzPrint(( AZD_ACCESS_MORE, "AzpClientContextFree: AzpClientContextGenerateDeleteAudit failed with %ld\n", WinStatus ));
}
}
//
// Free any local strings
//
if ( ClientContext->AccountDn != NULL ) {
AzpFreeHeap( ClientContext->AccountDn );
}
//
// Free the reference to the account domain
//
if ( ClientContext->Domain != NULL ) {
AzpDereferenceDomain( ClientContext->Domain );
}
//
// Free any authz context
//
if ( ClientContext->AuthzClientContext != NULL ) {
if ( !AuthzFreeContext( ClientContext->AuthzClientContext ) ) {
ASSERT( FALSE );
}
}
//
// Close the user's token
//
if ( ClientContext->TokenHandle != INVALID_HANDLE_VALUE ) {
CloseHandle( ClientContext->TokenHandle );
ClientContext->TokenHandle = INVALID_HANDLE_VALUE;
ASSERT( ClientContext->CreationType == AZP_CONTEXT_CREATED_FROM_TOKEN );
}
//
// Free the client name and the domain name.
//
if ( ClientContext->DomainName != NULL ) {
AzpFreeHeap( ClientContext->DomainName );
ClientContext->DomainName = NULL;
ASSERT( ClientContext->CreationType == AZP_CONTEXT_CREATED_FROM_NAME );
}
if ( ClientContext->ClientName != NULL ) {
AzpFreeHeap( ClientContext->ClientName );
ClientContext->ClientName = NULL;
ASSERT( ClientContext->CreationType == AZP_CONTEXT_CREATED_FROM_NAME ||
ClientContext->CreationType == AZP_CONTEXT_CREATED_FROM_SID );
}
//
// free the role name if it's specified in the context
//
AzpFreeString( &ClientContext->RoleName );
//
// Free the cache of group membership evaluations
//
AzpFlushGroupEval( ClientContext );
//
// Free the cache of previously evaluated operations
//
AzpFlushOperationCache( ClientContext );
//
// Delete the client context critical section
//
if ( ClientContext->CritSectInitialized ) {
SafeDeleteCriticalSection( &ClientContext->CritSect );
ClientContext->CritSectInitialized = FALSE;
}
}
DWORD
AzpGetUserNameEx(
IN PAZP_CLIENT_CONTEXT ClientContext,
IN EXTENDED_NAME_FORMAT NameFormat,
IN LPWSTR *NameBuffer,
OUT BOOLEAN *pbIsDomainDnsName
)
/*++
Routine Description:
This routine is a wrapper around GetUserNameEx.
It impersonates the token implied by ClientContext,
It handles allocating the buffer so the caller doesn't have to guess.
On entry, ClientContext->ReferenceCount must be incremented.
Arguments:
ClientContext - Specifies the context of the user to check group membership of.
NameFormat - Any name format that is valid for GetUserNameEx
NameBuffer - On success, returns a buffer in the same format as GetUserNameEx
The returned buffer should be freed using AzpFreeHeap,
pbIsDomainDnsName - set to TRUE if the returned Domain names is in DNS format
Return Value:
Same as GetUserNameEx.
--*/
{
DWORD WinStatus;
LPWSTR Buffer = NULL;
ULONG BufferSize;
HANDLE CurrentToken = NULL;
BOOL Impersonating = FALSE;
TOKEN_STATISTICS TokenStats = {0};
PTOKEN_USER pUserToken = NULL;
DWORD RetSize = 0;
LPWSTR ClientName = NULL;
//
// Initialization
//
ASSERT( ClientContext->GenericObject.ReferenceCount != 0 );
//
// Make sure that we have a valid token.
//
if ( ClientContext->TokenHandle == INVALID_HANDLE_VALUE ) {
AzPrint(( AZD_INVPARM, "AzpGetUserNameEx: no cached token handle\n" ));
return ERROR_NOT_SUPPORTED;
}
//
// Get the token stats to retreive the impersonation level
//
if ( !GetTokenInformation( ClientContext->TokenHandle,
TokenStatistics,
&TokenStats,
sizeof(TOKEN_STATISTICS),
&RetSize ) ) {
WinStatus = GetLastError();
AzPrint(( AZD_CRITICAL,
"AzpGetUserNameEx: Cannot get token statistics: %ld\n",
WinStatus
));
goto Cleanup;
}
//
// Check to see if we're already impersonating
//
if ( !OpenThreadToken(
GetCurrentThread(),
TOKEN_IMPERSONATE,
TRUE, // as self to ensure we never fail
&CurrentToken
) ) {
WinStatus = GetLastError();
if ( WinStatus != ERROR_NO_TOKEN ) {
AzPrint(( AZD_CRITICAL, "AzpGetUserNameEx: Cannot GetThreadToken %ld\n", WinStatus ));
goto Cleanup;
}
CurrentToken = NULL;
}
//
// Impersonate the user's token
//
if ( !SetThreadToken( NULL, ClientContext->TokenHandle ) ) {
WinStatus = GetLastError();
AzPrint(( AZD_CRITICAL, "AzpGetUserNameEx: Cannot SetThreadToken %ld\n", WinStatus ));
goto Cleanup;
}
Impersonating = TRUE;
//
// Determine the size of the buffer
//
BufferSize = 0;
if ( GetUserNameExW( NameFormat, NULL, &BufferSize ) ) {
WinStatus = ERROR_INTERNAL_ERROR;
goto Cleanup;
}
WinStatus = GetLastError();
if ( WinStatus != ERROR_MORE_DATA ) {
AzPrint(( AZD_CRITICAL, "AzpGetUserNameEx: Cannot GetUserNameExW %ld\n", WinStatus ));
goto Cleanup;
}
//
// Allocate a buffer
//
Buffer = (LPWSTR) AzpAllocateHeap( BufferSize * sizeof(WCHAR), "CNGUSER" );
if ( Buffer == NULL ) {
WinStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
//
// Determine the DNS domain name
//
if ( !GetUserNameExW( NameFormat, Buffer, &BufferSize ) ) {
WinStatus = GetLastError();
AzPrint(( AZD_CRITICAL, "AzpGetUserNameEx: Cannot GetUserNameExW %ld\n", WinStatus ));
goto Cleanup;
}
*pbIsDomainDnsName = TRUE;
WinStatus = NO_ERROR;
//
// Free any local resources
//
Cleanup:
if ( Impersonating ) {
//
// Revert to self
//
if ( !SetThreadToken( NULL, CurrentToken ) ) {
DWORD TempStatus = GetLastError();
AzPrint(( AZD_CRITICAL, "AzpGetUserNameEx: Cannot SetThreadToken (revert) %ld\n", TempStatus ));
if ( WinStatus == NO_ERROR ) {
WinStatus = TempStatus;
}
}
}
if ( WinStatus == NO_ERROR ) {
//
// Return the buffer to the caller
//
*NameBuffer = Buffer;
Buffer = NULL;
}
if ( Buffer != NULL ) {
AzpFreeHeap( Buffer );
}
if ( pUserToken != NULL ) {
AzpFreeHeap( pUserToken );
}
if ( ClientName != NULL ) {
AzpFreeHeap( ClientName );
}
//
// Close the handle to the current token to prevent any leaks
//
if ( CurrentToken != NULL ) {
CloseHandle( CurrentToken );
}
return WinStatus;
}
DWORD
AzpClientContextGetProperty(
IN PGENERIC_OBJECT GenericObject,
IN ULONG Flags,
IN ULONG PropertyId,
OUT PVOID *PropertyValue
)
/*++
Routine Description:
This routine is the ClientContext specific worker routine for AzGetProperty.
It does any object specific property gets. If the client context has
AZP_CONTEXT_CREATED_FROM_SID flag set, then only
RoleForAccessCheck property will be returned. All the rest will be returned
for AZP_CONTEXT_CREATED_FROM_TOKEN and AZP_CONTEXT_CREATED_FROM_NAME.
On entry, AzGlResource must be locked shared.
Arguments:
GenericObject - Specifies a pointer to the object to be queried
Flags - Specifies internal flags
AZP_FLAGS_BY_GUID - name lists should be returned as GUID lists
AZP_FLAGS_PERSIST_* - Call is from the persistence provider
PropertyId - Specifies which property to return.
PropertyValue - Specifies a pointer to return the property in.
The returned pointer must be freed using AzFreeMemory.
The returned value and type depends in PropertyId. The valid values are:
AZ_PROP_CLIENT_CONTEXT_USER_DN LPWSTR - DN of the user
AZ_PROP_CLIENT_CONTEXT_USER_SAM_COMPAT LPWSTR - Sam compatible name of the user
AZ_PROP_CLIENT_CONTEXT_USER_DISPLAY LPWSTR - Display name of the user
AZ_PROP_CLIENT_CONTEXT_USER_GUID LPWSTR - GUID of the user
AZ_PROP_CLIENT_CONTEXT_USER_CANONICAL LPWSTR - Canonical name of the user
AZ_PROP_CLIENT_CONTEXT_USER_UPN LPWSTR - UPN of the user
AZ_PROP_CLIENT_CONTEXT_USER_DNS_SAM_COMPAT LPWSTR - DNS same compat name of the user
AZ_PROP_CLIENT_CONTEXT_ROLE_FOR_ACCESS_CHECK LPWSTR - role name (may be NULL) for the access check
Return Value:
Status of the operation
--*/
{
DWORD WinStatus = NO_ERROR;
PAZP_CLIENT_CONTEXT ClientContext = (PAZP_CLIENT_CONTEXT) GenericObject;
EXTENDED_NAME_FORMAT NameFormat;
LPWSTR PropertyString = NULL;
AZP_STRING PropertyAzpString;
BOOLEAN Ignore = FALSE;
DWORD RetSize = 0;
DWORD UserNameSize = 0;
TOKEN_STATISTICS TokenStats = {0};
PWSTR UserName = NULL;
UNREFERENCED_PARAMETER(Flags); //ignore
//
// Initialization
//
ASSERT( AzpIsLockedShared( &AzGlResource ) );
//
// Return any object specific attribute
//
switch ( PropertyId ) {
case AZ_PROP_CLIENT_CONTEXT_USER_DN:
NameFormat = NameFullyQualifiedDN; break;
case AZ_PROP_CLIENT_CONTEXT_USER_SAM_COMPAT:
NameFormat = NameSamCompatible; break;
case AZ_PROP_CLIENT_CONTEXT_USER_DISPLAY:
NameFormat = NameDisplay; break;
case AZ_PROP_CLIENT_CONTEXT_USER_GUID:
NameFormat = NameUniqueId; break;
case AZ_PROP_CLIENT_CONTEXT_USER_CANONICAL:
NameFormat = NameCanonical; break;
case AZ_PROP_CLIENT_CONTEXT_USER_UPN:
NameFormat = NameUserPrincipal; break;
case AZ_PROP_CLIENT_CONTEXT_USER_DNS_SAM_COMPAT:
NameFormat = NameDnsDomain; break;
case AZ_PROP_CLIENT_CONTEXT_ROLE_FOR_ACCESS_CHECK:
//
// this is a property that stored in the client context structure
// do not need to go through getusernameex
//
*PropertyValue = AzpGetStringProperty( &ClientContext->RoleName );
if ( *PropertyValue == NULL ) {
WinStatus = ERROR_NOT_ENOUGH_MEMORY;
}
return WinStatus;
default:
AzPrint(( AZD_CRITICAL, "AzpClientContextGetProperty: invalid opcode %ld\n", PropertyId ));
WinStatus = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
//
// If the client context has been created from token, then use GetUserNameEx
// to retrieve the different name properties.
// If the client was created from Name, then we need to use TranslateName API
// to retrieve the various name information properties (since no token is available).
// However, TranslateName does not support NameDnsDomain format, and will return a
// ERROR_NO_SUCH_USER - this needs to be returned to the caller as ERROR_NOT_SUPPORTED.
// If the client was created from a StringSID with AZ_CLIENT_CONTEXT_SKIP_GROUP flag set,
// then we return ERROR_NOT_SUPPORTED
//
if ( ClientContext->CreationType == AZP_CONTEXT_CREATED_FROM_SID ) {
WinStatus = ERROR_NOT_SUPPORTED;
goto Cleanup;
} else if ( ClientContext->CreationType == AZP_CONTEXT_CREATED_FROM_TOKEN ) {
//
// Get the token stats to retreive the impersonation level
//
if ( !GetTokenInformation( ClientContext->TokenHandle,
TokenStatistics,
&TokenStats,
sizeof(TOKEN_STATISTICS),
&RetSize ) ) {
WinStatus = GetLastError();
AzPrint(( AZD_CRITICAL,
"AzpClientContextGetProperty: Cannot get token statistics: %ld\n",
WinStatus
));
goto Cleanup;
}
//
// Get the attribute from the LSA
//
WinStatus = AzpGetUserNameEx( ClientContext,
NameFormat,
&PropertyString,
&Ignore );
if ( WinStatus != NO_ERROR ) {
goto Cleanup;
}
} else if ( ClientContext->CreationType == AZP_CONTEXT_CREATED_FROM_NAME ) {
//
// Get the size required for the return string value
//
UserNameSize = (DWORD) (wcslen(ClientContext->ClientName)+wcslen(ClientContext->DomainName)+2)*
sizeof(WCHAR);
UserName = (PWSTR) AzpAllocateHeap( UserNameSize, "USRNAME" );
if ( UserName == NULL ) {
WinStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
wnsprintf( UserName, UserNameSize, L"%ws\\%ws", ClientContext->DomainName, ClientContext->ClientName );
TranslateName( UserName,
NameSamCompatible,
NameFormat,
NULL, // get size for return buffer
&RetSize
);
if ( (GetLastError() == ERROR_NO_SUCH_USER) &&
(PropertyId == AZ_PROP_CLIENT_CONTEXT_USER_DNS_SAM_COMPAT) ) {
WinStatus = ERROR_NOT_SUPPORTED;
goto Cleanup;
}
//
// Now that we have the size of the buffer we want, allocate
// and call TranslateName. The buffer will have the name in
// required format
//
PropertyString = (LPWSTR) AzpAllocateHeap( RetSize*sizeof(WCHAR), "CLNTNAM" );
if ( PropertyString == NULL ) {
WinStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
if ( !TranslateName( UserName,
NameSamCompatible,
NameFormat,
PropertyString,
&RetSize
) ) {
WinStatus = GetLastError();
AzPrint(( AZD_CRITICAL,
"AzpClientContextGetProperty: Cannot translate name: %ld\n",
WinStatus
));
goto Cleanup;
}
}
//
// Copy the string back to the caller
//
AzpInitString( &PropertyAzpString, PropertyString );
*PropertyValue = AzpGetStringProperty( &PropertyAzpString );
if ( *PropertyValue == NULL ) {
WinStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
WinStatus = NO_ERROR;
//
// Free any local resources
//
Cleanup:
if ( PropertyString != NULL ) {
AzpFreeHeap( PropertyString );
}
if ( UserName != NULL ) {
AzpFreeHeap( UserName );
}
return WinStatus;
}
DWORD
AzpClientContextSetProperty(
IN PGENERIC_OBJECT GenericObject,
IN ULONG Flags,
IN ULONG PropertyId,
IN PVOID PropertyValue
)
/*++
Routine Description:
This routine is the ClientContext object specific worker routine for AzSetProperty.
It does any object specific property sets.
On entry, AzGlResource must be locked exclusive.
Arguments:
GenericObject - Specifies a pointer to the object to be modified
Flags - Specifies flags controlling to operation of the routine
AZP_FLAGS_SETTING_TO_DEFAULT - Property is being set to default value
AZP_FLAGS_PERSIST_* - Call is from the persistence provider
PropertyId - Specifies which property to set.
PropertyValue - Specifies a pointer to the property.
The specified value and type depends in PropertyId. The valid values are:
AZ_PROP_CLIENT_CONTEXT_ROLE_FOR_ACCESS_CHECK LPWSTR - role specified for this access check
Return Value:
Status of the operation
--*/
{
DWORD WinStatus = NO_ERROR;
PAZP_CLIENT_CONTEXT ClientContext = (PAZP_CLIENT_CONTEXT) GenericObject;
//
// Initialization
//
UNREFERENCED_PARAMETER( Flags );
ASSERT( AzpIsLockedExclusive( &AzGlResource ) );
//
// Set any object specific attribute
//
// Set role name
//
switch ( PropertyId ) {
case AZ_PROP_CLIENT_CONTEXT_ROLE_FOR_ACCESS_CHECK:
//
// role name set to the client context is not persisted (via submit)
// so there is no need to define a dirty bit for this property.
//
// It's only temporarialy set in the client context structure,
// which will be used by AccessCheck only
//
AZP_STRING TempString;
WinStatus = AzpCaptureString(&TempString,
(LPCWSTR)PropertyValue,
AZ_MAX_ROLE_NAME_LENGTH,
TRUE
);
if ( WinStatus != NO_ERROR ) {
goto Cleanup;
}
//
// swap the new role name with existing role name and free the existing one
//
AzpSwapStrings(&ClientContext->RoleName,&TempString);
//
// free the existing role name specifed in the context, if any
//
AzpFreeString(&TempString);
break;
default:
AzPrint(( AZD_INVPARM, "AzpClientContextSetProperty: invalid prop id %ld\n", PropertyId ));
WinStatus = ERROR_INVALID_PARAMETER;
break;
}
Cleanup:
return WinStatus;
}
DWORD
AzpCheckSidMembership(
IN PAZP_CLIENT_CONTEXT ClientContext,
IN PGENERIC_OBJECT_LIST SidList,
OUT PBOOLEAN IsMember
)
/*++
Routine Description:
This routine checks to see if client context contains any of the Sids in SidList.
Do this be creating a security descriptor with all of the sids in a DACL and doing
and access check.
??? Consider caching the SecurityDescriptors
On entry, AzGlResource must be locked Shared.
Arguments:
ClientContext - Specifies the context of the user to check group membership of.
SidList - Specifies the list of sids to check membership for
IsMember - Returns TRUE if the user has one or more of the listed sids in his token.
Returns FALSE if the user has none of the listed sids in his token.
Return Value:
NO_ERROR - The function was successful
ERROR_NOT_ENOUGH_MEMORY - not enough memory
Any failure returned here should be returned to the caller.
--*/
{
DWORD WinStatus;
#define CHECK_SID_ACCESS_MASK 1
ULONG FirstSid;
ULONG LastSid;
BOOLEAN UseBiggest = FALSE;
ULONG i;
PSID Sid = NULL;
DWORD AclSize;
PACL Acl = NULL;
AUTHZ_ACCESS_REQUEST AccessRequest = { CHECK_SID_ACCESS_MASK };
AUTHZ_ACCESS_REPLY AccessReply;
DWORD GrantedAccess;
DWORD AuthStatus;
SECURITY_DESCRIPTOR SecurityDescriptor;
//
// Initialization
//
ASSERT( AzpIsLockedShared( &AzGlResource ) );
ASSERT( ClientContext->GenericObject.ReferenceCount != 0 );
*IsMember = FALSE;
//
// If there isn't at least one sid,
// then we're not a member.
//
if ( SidList->GenericObjects.UsedCount == 0 ) {
WinStatus = NO_ERROR;
goto Cleanup;
}
//
// Loop doing at most 64KB at a time since that's the ACL limit
//
#define BIGGEST_ACL 0xFFFF
for ( FirstSid=0; FirstSid<SidList->GenericObjects.UsedCount; FirstSid=LastSid ) {
//
// Loop through the list of sids computing the ACL size
//
AclSize = sizeof(ACL);
LastSid = SidList->GenericObjects.UsedCount;
for ( i=FirstSid; i<LastSid; i++ ) {
DWORD AceSize;
ASSERT(((PGENERIC_OBJECT)(SidList->GenericObjects.Array[i]))->ObjectType == OBJECT_TYPE_SID );
Sid = (PSID)((PAZP_SID)(SidList->GenericObjects.Array[i]))->GenericObject.ObjectName->ObjectName.String;
AceSize = ( sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) ) +
RtlLengthSid( Sid );
if ( AclSize + AceSize >= BIGGEST_ACL) {
LastSid = i;
UseBiggest = TRUE;
break;
}
AclSize += AceSize;
}
AzPrint(( AZD_ACCESS_MORE,
"AzpCheckSidMembership: Process sids %ld to %ld with %ld byte ACL\n",
FirstSid,
LastSid,
AclSize ));
//
// Allocate memory for Acl
//
if ( Acl == NULL ) {
SafeAllocaAllocate( Acl, UseBiggest ? BIGGEST_ACL : AclSize );
if ( Acl == NULL ) {
WinStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
}
//
// Initialize the buffer
//
if (!InitializeAcl( Acl, AclSize, ACL_REVISION)) {
WinStatus = GetLastError();
goto Cleanup;
}
//
// Loop through the list of sids adding them to the ACL
//
for ( i=FirstSid; i<LastSid; i++ ) {
Sid = (PSID)((PAZP_SID)(SidList->GenericObjects.Array[i]))->GenericObject.ObjectName->ObjectName.String;
if ( !AddAccessAllowedAce(
Acl,
ACL_REVISION,
CHECK_SID_ACCESS_MASK,
Sid ) ) {
WinStatus = GetLastError();
goto Cleanup;
}
}
//
// Initialize the security descriptor
//
if ( !InitializeSecurityDescriptor(
&SecurityDescriptor,
SECURITY_DESCRIPTOR_REVISION ) ) {
WinStatus = GetLastError();
goto Cleanup;
}
if ( !SetSecurityDescriptorDacl(
&SecurityDescriptor,
TRUE,
Acl,
FALSE ) ) {
WinStatus = GetLastError();
goto Cleanup;
}
//
// Use an arbitrary SID as the "owner".
//
// AuthzAccessCheck uses it to replace "CreatorOwner" and to determine
// ReadControl/WriteDac. None of those apply to us. But we need to placate
// AuthzAccessCheck.
//
if ( !SetSecurityDescriptorOwner(
&SecurityDescriptor,
Sid,
FALSE ) ) {
WinStatus = GetLastError();
goto Cleanup;
}
//
// Check if the client has any of the sids in his context
//
AccessReply.ResultListLength = 1;
AccessReply.GrantedAccessMask = &GrantedAccess;
AccessReply.Error = &AuthStatus;
if ( !AuthzAccessCheck(
0, // No Flags
ClientContext->AuthzClientContext,
&AccessRequest,
NULL, // No auditing
&SecurityDescriptor,
NULL, // No extra security descriptors
0, // No extra security descriptors
&AccessReply,
NULL ) ) { // No Cached results
WinStatus = GetLastError();
goto Cleanup;
}
if ( GrantedAccess & CHECK_SID_ACCESS_MASK ) {
*IsMember = TRUE;
break;
}
}
WinStatus = NO_ERROR;
//
// Free any local resources
//
Cleanup:
if ( Acl != NULL ) {
SafeAllocaFree( Acl );
}
return WinStatus;
}
DWORD
AzpCheckGroupMembership(
IN PAZP_CLIENT_CONTEXT ClientContext,
IN PGENERIC_OBJECT_LIST GroupList,
IN BOOLEAN LocalOnly,
IN DWORD RecursionLevel,
OUT PBOOLEAN RetIsMember,
OUT LPDWORD RetExtendedStatus
)
/*++
Routine Description:
This routine checks to see if the user specified by ClientContext is a member of
any of the groups specified by GroupList.
On entry, AzGlResource must be locked Shared.
*** Note: this routine will temporarily drop AzGlResource in cases where it hits the wire
Arguments:
ClientContext - Specifies the context of the user to check group membership of.
GroupList - Specifies the list of groups to check group membership of
LocalOnly - Specifies that the caller doesn't want to go off machine to determine
the membership.
RecursionLevel - Indicates the level of recursion.
Used to prevent infinite recursion.
RetIsMember - Returns whether the caller is a member of the specified group.
RetExtendedStatus - Returns extended status information about the operation.
NO_ERROR is returned if the group membership was determined.
The Caller may use the value returned in IsMember.
If LocalOnly is TRUE, NOT_YET_DONE means that the caller must call again with
with LocalOnly set to false to get the group membership.
If LocalOnly is FALSE, an error value indicates that the LDAP server returned
an error while evaluating the request. The caller may return this error
to the original API caller if this group membership is required to determine
whether the access check worked or not.
Return Value:
NO_ERROR - The function was successful
ERROR_NOT_ENOUGH_MEMORY - not enough memory
Any failure returned here should be returned to the caller.
--*/
{
DWORD WinStatus;
ULONG i;
DWORD SavedExtendedStatus = NO_ERROR;
//
// Initialization
//
ASSERT( AzpIsLockedShared( &AzGlResource ) );
if ( RecursionLevel > 100 ) {
return ERROR_DS_LOOP_DETECT;
}
//
// Loop through the list of groups evaluating each
//
for ( i=0; i<GroupList->GenericObjects.UsedCount; i++ ) {
PAZP_GROUP Group;
BOOLEAN IsMember;
DWORD ExtendedStatus;
//
// Check the membership of one group
//
ASSERT(((PGENERIC_OBJECT)(GroupList->GenericObjects.Array[i]))->ObjectType == OBJECT_TYPE_GROUP );
Group = (PAZP_GROUP) GroupList->GenericObjects.Array[i];
WinStatus = AzpCheckGroupMembershipOne(
ClientContext,
Group,
LocalOnly,
RecursionLevel,
&IsMember,
&ExtendedStatus );
if ( WinStatus != NO_ERROR ) {
return WinStatus;
}
//
// If we're definitively a member,
// tell our caller.
//
if ( ExtendedStatus == NO_ERROR ) {
if ( IsMember ) {
*RetIsMember = TRUE;
*RetExtendedStatus = NO_ERROR;
return NO_ERROR;
}
//
// If we don't have a definitive answer,
// remember the answer hoping that we can get a definitive answer.
//
} else {
SavedExtendedStatus = ExtendedStatus;
}
}
//
// ASSERT: we couldn't prove we're a member
// Return any defered status we may have
//
*RetExtendedStatus = SavedExtendedStatus;
*RetIsMember = FALSE;
return NO_ERROR;
}
DWORD
AzpComputeAccountDn(
IN AUTHZ_CLIENT_CONTEXT_HANDLE AuthzClientContext,
OUT LPWSTR *RetAccountDn
)
/*++
Routine Description:
This routine computes the DN of the account to query.
Arguments:
AuthzClientContext - Client context for the account
RetAccountDn - Returns a pointer to the string containing the DN for the account
The caller must free this string using AzpFreeHeap.
Return Value:
NO_ERROR - The function was successful.
ERROR_NOT_ENOUGH_MEMORY - not enough memory
Any failure returned here should be returned to the caller.
--*/
{
DWORD WinStatus;
ULONG BufferSize;
PTOKEN_USER UserSid = NULL;
ULONG UserSidSize;
LPWSTR AccountDn = NULL;
ULONG i;
LPBYTE InBuffer;
WCHAR *OutBuffer;
//
// Determine the length of the sid
//
if ( AuthzGetInformationFromContext( AuthzClientContext,
AuthzContextInfoUserSid,
0,
&BufferSize,
NULL ) ) {
WinStatus = ERROR_INTERNAL_ERROR;
AzPrint(( AZD_CRITICAL,
"AzpComputeAccountDn: AuthzGetInformationFromContext failed %ld\n",
WinStatus ));
goto Cleanup;
}
WinStatus = GetLastError();
if ( WinStatus != ERROR_INSUFFICIENT_BUFFER ) {
AzPrint(( AZD_CRITICAL,
"AzpComputeAccountDn: AuthzGetInformationFromContext failed %ld\n",
WinStatus ));
goto Cleanup;
}
//
// Allocate a buffer for the SID
//
SafeAllocaAllocate( UserSid, BufferSize );
if ( UserSid == NULL ) {
WinStatus = ERROR_NOT_ENOUGH_MEMORY;
AzPrint(( AZD_CRITICAL,
"AzpComputeAccountDn: SafeAllocaAllocate failed %ld\n",
WinStatus ));
goto Cleanup;
}
//
// Read the user sid into the buffer.
//
if ( !AuthzGetInformationFromContext( AuthzClientContext,
AuthzContextInfoUserSid,
BufferSize,
&BufferSize,
UserSid ) ) {
WinStatus = GetLastError();
AzPrint(( AZD_CRITICAL,
"AzpComputeAccountDn: AuthzGetInformationFromContext failed %ld\n",
WinStatus ));
goto Cleanup;
}
//
// Convert the Sid to a DN
//
// Allocate a buffer for the DN
//
#define DN_PREFIX L"<Sid="
#define DN_PREFIX_LENGTH ((sizeof(DN_PREFIX)/sizeof(WCHAR))-1)
#define DN_SUFFIX L">"
#define DN_SUFFIX_LENGTH ((sizeof(DN_SUFFIX)/sizeof(WCHAR))-1)
UserSidSize = RtlLengthSid( UserSid->User.Sid );
AccountDn = (LPWSTR) AzpAllocateHeap(
(DN_PREFIX_LENGTH +
UserSidSize * 2 +
DN_SUFFIX_LENGTH +
1) * sizeof(WCHAR), "CNACCDN" );
if ( AccountDn == NULL ) {
WinStatus = ERROR_NOT_ENOUGH_MEMORY;
AzPrint(( AZD_CRITICAL,
"AzpComputeAccountDn: AzpAllocateHeap failed %ld\n",
WinStatus ));
goto Cleanup;
}
//
// Build the DN.
// The form is <SID=0104000000000005150000005951B81766725D2564633B0B>
// Where each byte of the SID has been turned into two ASCII hex digits.
// This format will work on both win2k and whistler.
//
RtlCopyMemory( AccountDn, DN_PREFIX, DN_PREFIX_LENGTH*sizeof(WCHAR) );
InBuffer = (LPBYTE) UserSid->User.Sid;
OutBuffer = &AccountDn[DN_PREFIX_LENGTH];
CHAR XlateArray[] = { '0', '1', '2', '3', '4' ,'5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
for ( i=0; i<UserSidSize; i++ ) {
*OutBuffer++ = XlateArray[ (InBuffer[i] >> 4) & 0xF ];
*OutBuffer++ = XlateArray[ InBuffer[i] & 0xF ];
}
RtlCopyMemory( OutBuffer,
DN_SUFFIX,
(DN_SUFFIX_LENGTH+1)*sizeof(WCHAR) );
//
// Save it away
//
*RetAccountDn = AccountDn;
AccountDn = NULL;
WinStatus = NO_ERROR;
//
// Free locally used resources
//
Cleanup:
if ( UserSid != NULL ) {
SafeAllocaFree( UserSid );
}
if ( AccountDn != NULL ) {
AzpFreeHeap( AccountDn );
}
return WinStatus;
}
DWORD
AzpLdapSupported(
IN PSID UserSid,
OUT PBOOLEAN LdapSupported
)
/*++
Routine description:
This routine decides whether a given user is a domain user or a local
machine user.
On a DC, we support LdapQueries since the user account is surely a domain
account.
On a non-dc, we compare the User sid with the AccountDomainSid. If the two
are equal then this is a local user on a non-DC and we do not support Ldap
queries.
Arguments:
UserSid - Sid of the client.
LdapSupported - Returns whether or not we support LdapQueries.
Return Value:
Returns ERROR_SUCCESS on success, appropriate failure value otherwise.
--*/
{
BOOL Equal = FALSE;
*LdapSupported = TRUE;
//
// If this is a DC, we support LdapQueries.
//
if ( AzIsDC ) {
return NO_ERROR;
}
ASSERT( AzAccountDomainSidInitialized );
//
// Check whether the user belongs to the current machine account domain.
//
if ( !EqualDomainSid( AzAccountDomainSid, UserSid, &Equal ) ) {
return GetLastError();
}
//
// If they are equal this is a local account.
//
if ( Equal ) {
*LdapSupported = FALSE;
}
return NO_ERROR;
}
DWORD
AzpCheckGroupMembershipLdap(
IN PAZP_CLIENT_CONTEXT ClientContext,
IN OUT PAZP_MEMBER_EVALUATION MemEval
)
/*++
Routine Description:
This routine checks to see if the user specified by ClientContext is a member of
the specified LDAP_QUERY AppGroup.
Client context created from SID (to skip ldap group check) will not be allowed
in this routine (ERROR_INVALID_PARAMETER will be returned)
This routine goes over the wire so AzGlResource must not be locked.
On entry, ClientContext.CritSect must be locked.
Arguments:
ClientContext - Specifies the context of the user to check group membership of.
MemEval - Membership evaluation cache entry for this group
The cache entry is updated to reflect group membership or
the reason for failure to find group membership.
Return Value:
NO_ERROR - The function was successful. (MemEval was updated.)
MemEval->WinStatus is either set to NO_ERROR or ERROR_NO_SUCH_DOMAIN.
ERROR_NOT_ENOUGH_MEMORY - not enough memory
Any failure returned here should be returned to the caller.
--*/
{
DWORD WinStatus;
PAZP_AZSTORE AzAuthorizationStore = ClientContext->GenericObject.AzStoreObject;
LPWSTR DnsDomainName = NULL;
ULONG GetDcContext;
PAZP_DC Dc = NULL;
WCHAR *p;
ULONG LdapStatus;
LDAP_TIMEVAL LdapTimeout;
LPWSTR Attributes[2];
PLDAPMessage LdapMessage = NULL;
ULONG EntryCount;
BOOLEAN IsDomainDnsName = FALSE;
//
// Initialization
//
ASSERT( AzpIsCritsectLocked( &ClientContext->CritSect ) );
AzPrint(( AZD_ACCESS_MORE, "AzpCheckGroupMembershipLdap: %ws\n", MemEval->Group->GenericObject.ObjectName->ObjectName.String ));
//
// If we don't yet know what domain this user is in,
// find out.
//
if ( ClientContext->Domain == NULL ) {
//
// If we know the domain doesn't support LDAP,
// we're sure that the user isn't a member of the group.
//
if ( ClientContext->LdapNotSupported ) {
MemEval->IsMember = FALSE;
MemEval->WinStatus = NO_ERROR;
AzPrint(( AZD_ACCESS_MORE,
"AzpCheckGroupMembershipLdap: %ws: User is in NT 4 domain or local account: Membership is %ld\n",
MemEval->Group->GenericObject.ObjectName->ObjectName.String,
MemEval->IsMember ));
WinStatus = NO_ERROR;
goto Cleanup;
}
if ( ClientContext->CreationType == AZP_CONTEXT_CREATED_FROM_TOKEN ) {
//
// Get the dns domain name from the LSA
//
WinStatus = AzpGetUserNameEx( ClientContext,
NameDnsDomain,
&DnsDomainName,
&IsDomainDnsName );
if ( WinStatus != NO_ERROR ) {
//
// If the account is a local account,
// or the domain is an NT 4.0 (or older domain),
// then the user isn't a member of this ldap group.
//
if ( WinStatus == ERROR_NONE_MAPPED ) {
ClientContext->LdapNotSupported = TRUE;
MemEval->IsMember = FALSE;
MemEval->WinStatus = NO_ERROR;
AzPrint(( AZD_ACCESS_MORE,
"AzpCheckGroupMembershipLdap: %ws: User is in NT 4 domain or local account: Membership is %ld\n",
MemEval->Group->GenericObject.ObjectName->ObjectName.String,
MemEval->IsMember ));
WinStatus = NO_ERROR;
goto Cleanup;
}
AzPrint(( AZD_CRITICAL,
"AzpCheckGroupMembershipLdap: %ws: AzpGetUserNameEx failed %ld\n",
MemEval->Group->GenericObject.ObjectName->ObjectName.String,
WinStatus ));
//
// The DC can be down.
// In that case, ERROR_NO_SUCH_DOMAIN is returned above and
// Cleanup puts it in MemEval->WinStatus
//
goto Cleanup;
}
//
// AzpGetUserNameEx may return the DnsDomainName with the user name
// Trim it off if it exists
//
p = wcschr( DnsDomainName, L'\\' );
if ( p != NULL ) {
*p = '\0';
}
//
// Get the domain handle for this domain
//
ClientContext->Domain = AzpReferenceDomain( AzAuthorizationStore,
DnsDomainName,
IsDomainDnsName );
} else if ( ClientContext->CreationType == AZP_CONTEXT_CREATED_FROM_NAME ) {
//
// Check whether we support Ldap Queries.
// We do not support Ldap queries if the account is local.
//
// Note, AZP_CONTEXT_CREATED_FROM_SID will not get into this code path
// because SKIP_GROUP is set so ldap group evaluation is skipped.
WinStatus = AzpLdapSupported( (PSID) ClientContext->SidBuffer, &ClientContext->LdapNotSupported );
if ( WinStatus != NO_ERROR ) {
goto Cleanup;
}
if ( !ClientContext->LdapNotSupported ) {
MemEval->IsMember = FALSE;
MemEval->WinStatus = NO_ERROR;
goto Cleanup;
}
//
// We have the domain name available already. It's in NetBios name
// format.
//
ClientContext->Domain = AzpReferenceDomain( AzAuthorizationStore,
ClientContext->DomainName,
FALSE );
} else {
ASSERT( FALSE );
WinStatus = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
if ( ClientContext->Domain == NULL ) {
WinStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
}
//
// Loop handling failed DCs
//
GetDcContext = 0;
for ( ;; ) {
//
// Free the name from the previous iteration
//
if ( Dc != NULL ) {
AzpDereferenceDc( Dc );
Dc = NULL;
}
//
// Get the name of a DC to try
//
WinStatus = AzpGetDc( AzAuthorizationStore,
ClientContext->Domain,
&GetDcContext,
&Dc );
if ( WinStatus != NO_ERROR ) {
AzPrint(( AZD_ACCESS,
"AzpCheckGroupMembershipLdap: %ws: DsGetDcName failed %ld\n",
MemEval->Group->GenericObject.ObjectName->ObjectName.String,
WinStatus ));
goto Cleanup;
}
//
// Free up resources from the previous iteration
//
if ( LdapMessage != NULL ) {
ldap_msgfree( LdapMessage );
LdapMessage = NULL;
}
//
// Ensure we have an DN of the user object
//
if ( ClientContext->AccountDn == NULL ) {
WinStatus = AzpComputeAccountDn(
ClientContext->AuthzClientContext,
&ClientContext->AccountDn );
if ( WinStatus != NO_ERROR ) {
goto Cleanup;
}
}
//
// Read the user object
//
LdapTimeout.tv_sec = 30; // Wait at most 30 seconds
LdapTimeout.tv_usec = 0;
Attributes[0] = L"ObjectClass"; // Pick a random attribute to ask for
Attributes[1] = NULL;
LdapStatus = ldap_search_ext_sW(
Dc->LdapHandle,
ClientContext->AccountDn,
LDAP_SCOPE_BASE,
MemEval->Group->LdapQuery.String,
Attributes, // Ask for only one attribute
TRUE, // Only attribute types (not values)
NULL, // No server controls
NULL, // No client controls
&LdapTimeout,
0, // No limit on size of response
&LdapMessage );
if ( LdapStatus != LDAP_SUCCESS ) {
AzPrint(( AZD_ACCESS,
"AzpCheckGroupMembershipLdap: %ws: ldap_search failed on %ws: %ld: %s\n",
MemEval->Group->GenericObject.ObjectName->ObjectName.String,
Dc->DcName.String,
LdapStatus,
ldap_err2stringA( LdapStatus )));
WinStatus = AzpLdapErrorToWin32Error(LdapStatus);
//
// If the DC is down,
// find another one.
//
if ( WinStatus == ERROR_NO_SUCH_DOMAIN ) {
continue;
}
goto Cleanup;
}
//
// Loop through the list of objects returned
//
EntryCount = ldap_count_entries( Dc->LdapHandle, LdapMessage );
if ( EntryCount == 0xFFFFFFFF ) {
LdapStatus = LdapGetLastError();
AzPrint(( AZD_CRITICAL,
"AzpCheckGroupMembershipLdap: %ws: ldap_count_entries failed on %ws: %ld: %s\n",
MemEval->Group->GenericObject.ObjectName->ObjectName.String,
Dc->DcName.String,
LdapStatus,
ldap_err2stringA( LdapStatus )));
WinStatus = AzpLdapErrorToWin32Error(LdapStatus);
goto Cleanup;
}
#if 0 // Run dlcheck.exe if this is ever turned on
//
// Perhaps just debug code, but display all of the attributes returned
//
ULONG EntryIndex;
LPWSTR AttributeName;
BerElement *BerState;
PLDAPMessage CurrentEntry;
CurrentEntry = NULL;
for ( EntryIndex = 0; EntryIndex < EntryCount; EntryIndex ++ ) {
//
// Get the next entry
//
if ( EntryIndex == 0) {
CurrentEntry = ldap_first_entry( Dc->LdapHandle, LdapMessage );
} else {
CurrentEntry = ldap_next_entry( Dc->LdapHandle, CurrentEntry );
}
if ( CurrentEntry == NULL ) {
LdapStatus = LdapGetLastError();
if ( LdapStatus != LDAP_SUCCESS ) {
AzPrint(( AZD_CRITICAL,
"AzpCheckGroupMembershipLdap: %ws: ldap_xxx_entry failed on %ws: %ld: %s\n",
MemEval->Group->GenericObject.ObjectName->ObjectName.String,
Dc->DcName.String,
LdapStatus,
ldap_err2stringA( LdapStatus )));
}
WinStatus = AzpLdapErrorToWin32Error(LdapStatus);
goto Cleanup;
}
//
// Loop through the attributes
//
AttributeName = ldap_first_attributeW( Dc->LdapHandle, CurrentEntry, &BerState );
AzPrint(( AZD_ACCESS_MORE, "Object %ld\n", EntryIndex ));
while ( AttributeName != NULL ) {
AzPrint(( AZD_ACCESS_MORE, " %ws\n", AttributeName ));
ldap_memfree( AttributeName );
AttributeName = ldap_next_attributeW( Dc->LdapHandle, CurrentEntry, BerState );
}
if ( BerState != NULL ) {
ber_free( BerState, 0 );
}
LdapStatus = LdapGetLastError();
if ( LdapStatus != LDAP_SUCCESS ) {
AzPrint(( AZD_CRITICAL,
"AzpCheckGroupMembershipLdap: %ws: ldap_xxx_attribute failed on %ws: %ld: %s\n",
MemEval->Group->GenericObject.ObjectName->ObjectName.String,
Dc->DcName.String,
LdapStatus,
ldap_err2stringA( LdapStatus )));
WinStatus = AzpLdapErrorToWin32Error(LdapStatus);
goto Cleanup;
}
}
#endif // 0
//
// The query worked.
// We're a member of the group depending on whether the entry was actually returned.
//
MemEval->IsMember = (EntryCount != 0);
MemEval->WinStatus = NO_ERROR;
AzPrint(( AZD_ACCESS_MORE,
"AzpCheckGroupMembershipLdap: %ws: ldap_search worked on %ws: Membership is %ld\n",
MemEval->Group->GenericObject.ObjectName->ObjectName.String,
Dc->DcName.String,
MemEval->IsMember ));
WinStatus = NO_ERROR;
goto Cleanup;
}
// ASSERT: Not reached
//
// Free locally used resources
//
Cleanup:
if ( LdapMessage != NULL ) {
ldap_msgfree( LdapMessage );
LdapMessage = NULL;
}
if ( Dc != NULL ) {
AzpDereferenceDc( Dc );
}
if ( DnsDomainName != NULL ) {
AzpFreeHeap( DnsDomainName );
}
//
// If the failure came from the DC,
// indicate that we need to try again sometime.
//
if ( WinStatus == ERROR_NO_SUCH_DOMAIN ) {
MemEval->WinStatus = WinStatus;
WinStatus = NO_ERROR;
}
return WinStatus;
}
DWORD
AzpCheckGroupMembershipOne(
IN PAZP_CLIENT_CONTEXT ClientContext,
IN PAZP_GROUP Group,
IN BOOLEAN LocalOnly,
IN DWORD RecursionLevel,
OUT PBOOLEAN RetIsMember,
OUT LPDWORD ExtendedStatus
)
/*++
Routine Description:
This routine checks to see if the user specified by ClientContext is a member of
the specified AppGroup.
On entry, ClientContext.CritSect must be locked.
On entry, AzGlResource must be locked Shared.
*** Note: this routine will temporarily drop AzGlResource in cases where it hits the wire
Arguments:
ClientContext - Specifies the context of the user to check group membership of.
Group - Specifies the group to check group membership of
LocalOnly - Specifies that the caller doesn't want to go off machine to determine
the membership.
RecursionLevel - Indicates the level of recursion.
Used to prevent infinite recursion.
RetIsMember - Returns whether the caller is a member of the specified group.
ExtendedStatus - Returns extended status information about the operation.
NO_ERROR is returned if the group membership was determined.
The Caller may use the value returned in IsMember.
If LocalOnly is TRUE, NOT_YET_DONE means that the caller must call again with
with LocalOnly set to false to get the group membership.
If LocalOnly is FALSE, an error value indicates that the LDAP server returned
an error while evaluating the request. The caller may return this error
to the original API caller if this group membership is required to determine
whether the access check worked or not.
Return Value:
NO_ERROR - The function was successful
ERROR_NOT_ENOUGH_MEMORY - not enough memory
Any failure returned here should be returned to the caller.
--*/
{
DWORD WinStatus;
PLIST_ENTRY ListEntry;
PAZP_MEMBER_EVALUATION MemEval;
BOOLEAN IsMember;
BOOLEAN IsNonMember;
//
// Initialization
//
ASSERT( AzpIsLockedShared( &AzGlResource ) );
ASSERT( AzpIsCritsectLocked( &ClientContext->CritSect ) );
*RetIsMember = FALSE;
*ExtendedStatus = NO_ERROR;
AzPrint(( AZD_ACCESS_MORE, "AzpCheckGroupMembershipOne: %ws\n", Group->GenericObject.ObjectName->ObjectName.String ));
//
// Loop through the list of groups that have previously been evaluated
//
MemEval = NULL;
for ( ListEntry = ClientContext->MemEval.Flink;
ListEntry != &ClientContext->MemEval;
ListEntry = ListEntry->Flink) {
MemEval = CONTAINING_RECORD( ListEntry,
AZP_MEMBER_EVALUATION,
Next );
if ( MemEval->Group == Group ) {
break;
}
MemEval = NULL;
}
//
// If we didn't find one,
// create one.
//
if ( MemEval == NULL ) {
//
// Allocate it
//
MemEval = (PAZP_MEMBER_EVALUATION) AzpAllocateHeap( sizeof( AZP_MEMBER_EVALUATION ), "CNMEMEVL");
if ( MemEval == NULL ) {
WinStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
//
// Initialize it
//
MemEval->Group = Group;
MemEval->WinStatus = NOT_YET_DONE;
MemEval->IsMember = FALSE;
//
// Link it in
//
InsertHeadList( &ClientContext->MemEval,
&MemEval->Next );
AzPrint(( AZD_ACCESS_MORE, "AzpCheckGroupMembershipOne: %ws: Create cache entry\n", Group->GenericObject.ObjectName->ObjectName.String ));
}
//
// If we already know the membership,
// return the cached answer.
//
if ( MemEval->WinStatus == NO_ERROR ) {
WinStatus = NO_ERROR;
*ExtendedStatus = NO_ERROR;
AzPrint(( AZD_ACCESS_MORE, "AzpCheckGroupMembershipOne: %ws: %ld: %ld: Answer found in cache\n", Group->GenericObject.ObjectName->ObjectName.String, *ExtendedStatus, MemEval->IsMember ));
goto Cleanup;
}
//
// Handle a membership group,
//
if ( Group->GroupType == AZ_GROUPTYPE_BASIC ) {
DWORD AppNonMemberStatus;
DWORD AppMemberStatus;
AzPrint(( AZD_ACCESS_MORE, "AzpCheckGroupMembershipOne: %ws: Is a basic group\n", Group->GenericObject.ObjectName->ObjectName.String ));
//
// Check the NT Group non-membership
//
WinStatus = AzpCheckSidMembership(
ClientContext,
&Group->SidNonMembers,
&IsNonMember );
if ( WinStatus != NO_ERROR ) {
AzPrint(( AZD_ACCESS_MORE, "AzpCheckGroupMembershipOne: %ws: Cannot AzpCheckSidMembership (non member) %ld\n", Group->GenericObject.ObjectName->ObjectName.String, WinStatus ));
goto Cleanup;
}
//
// If we're not a member,
// that's definitive
//
if ( IsNonMember ) {
// Cache the answer
MemEval->IsMember = FALSE;
MemEval->WinStatus = NO_ERROR;
// Return the cached answer
WinStatus = NO_ERROR;
*ExtendedStatus = MemEval->WinStatus;
AzPrint(( AZD_ACCESS_MORE, "AzpCheckGroupMembershipOne: %ws: Is non member via NT Sid\n", Group->GenericObject.ObjectName->ObjectName.String ));
goto Cleanup;
}
//
// Check the app group non-membership
//
WinStatus = AzpCheckGroupMembership(
ClientContext,
&Group->AppNonMembers,
LocalOnly,
RecursionLevel+1, // Increment recursion level
&IsNonMember,
&AppNonMemberStatus );
if ( WinStatus != NO_ERROR ) {
AzPrint(( AZD_ACCESS_MORE, "AzpCheckGroupMembershipOne: %ws: Cannot AzpCheckGroupMembership (non member) %ld\n", Group->GenericObject.ObjectName->ObjectName.String, WinStatus ));
goto Cleanup;
}
//
// If we're not a member,
// that's definitive.
//
// Note that if we couldn't determine non-membership,
// we wait to report back to the caller until we find out if we're a member.
// No use worrying the caller if we definitely aren't a member.
//
if ( AppNonMemberStatus == NO_ERROR ) {
if ( IsNonMember ) {
// Cache the answer
MemEval->IsMember = FALSE;
MemEval->WinStatus = NO_ERROR;
// Return the cached answer
WinStatus = NO_ERROR;
*ExtendedStatus = MemEval->WinStatus;
AzPrint(( AZD_ACCESS_MORE, "AzpCheckGroupMembershipOne: %ws: Is non member via app group\n", Group->GenericObject.ObjectName->ObjectName.String ));
goto Cleanup;
}
}
//
// Check the NT Group membership
//
WinStatus = AzpCheckSidMembership(
ClientContext,
&Group->SidMembers,
&IsMember );
if ( WinStatus != NO_ERROR ) {
AzPrint(( AZD_ACCESS_MORE, "AzpCheckGroupMembershipOne: %ws: Cannot AzpCheckSidMembership (member) %ld\n", Group->GenericObject.ObjectName->ObjectName.String, WinStatus ));
goto Cleanup;
}
//
// If we couldn't determine membership based on SIDs,
// try via app groups
//
if ( !IsMember ) {
//
// Check the app group membership
//
WinStatus = AzpCheckGroupMembership(
ClientContext,
&Group->AppMembers,
LocalOnly,
RecursionLevel+1, // Increment recursion level
&IsMember,
&AppMemberStatus );
if ( WinStatus != NO_ERROR ) {
AzPrint(( AZD_ACCESS_MORE, "AzpCheckGroupMembershipOne: %ws: Cannot AzpCheckGroupMembership (member) %ld\n", Group->GenericObject.ObjectName->ObjectName.String, WinStatus ));
goto Cleanup;
}
//
// If we couldn't determine membership,
// return that status to our caller.
//
if ( AppMemberStatus != NO_ERROR ) {
// We cache this failure only on the LDAP_QUERY group that
// caused it.
MemEval->IsMember = FALSE;
MemEval->WinStatus = NOT_YET_DONE;
// Return the cached answer
WinStatus = NO_ERROR;
*ExtendedStatus = AppMemberStatus;
AzPrint(( AZD_ACCESS_MORE, "AzpCheckGroupMembershipOne: %ws: Cannot AzpCheckGroupMembership (member) extended status: %ld\n", Group->GenericObject.ObjectName->ObjectName.String, AppMemberStatus ));
goto Cleanup;
}
}
//
// ASSERT: We've determined membership via the SID or APP group mechanisms
//
// If we're a member and we couldn't find non-membership,
// tell the caller now.
//
if ( IsMember ) {
if ( AppNonMemberStatus != NO_ERROR ) {
// We cache this failure only on the LDAP_QUERY group that
// caused it.
MemEval->IsMember = FALSE;
MemEval->WinStatus = NOT_YET_DONE;
// Return the cached answer
WinStatus = NO_ERROR;
*ExtendedStatus = AppNonMemberStatus;
AzPrint(( AZD_ACCESS_MORE, "AzpCheckGroupMembershipOne: %ws: Cannot AzpCheckGroupMembership (non member) extended status: %ld\n", Group->GenericObject.ObjectName->ObjectName.String, AppNonMemberStatus ));
goto Cleanup;
}
}
//
// Finally, we've found out whether we're a member
// Tell the caller
//
MemEval->IsMember = IsMember;
MemEval->WinStatus = NO_ERROR;
*ExtendedStatus = NO_ERROR;
WinStatus = NO_ERROR;
AzPrint(( AZD_ACCESS_MORE, "AzpCheckGroupMembershipOne: %ws: %ld: %ld: Answer computed\n", Group->GenericObject.ObjectName->ObjectName.String, *ExtendedStatus, MemEval->IsMember ));
//
// Handle an LDAP_QUERY group.
//
} else if ( Group->GroupType == AZ_GROUPTYPE_LDAP_QUERY ) {
//
// If the caller only wants local processing,
// or skip ldap group in which case the context is created from SID,
// We're done for now.
//
if ( LocalOnly ||
(ClientContext->CreationType == AZP_CONTEXT_CREATED_FROM_SID) ) {
WinStatus = NO_ERROR;
//
// ERROR_NO_SUCH_DOMAIN isn't definitive.
// Convert it to NOT_YET_DONE to ensure we try again.
//
ASSERT( MemEval->WinStatus == ERROR_NO_SUCH_DOMAIN || MemEval->WinStatus == NOT_YET_DONE );
*ExtendedStatus = NOT_YET_DONE;
AzPrint(( AZD_ACCESS_MORE, "AzpCheckGroupMembershipOne: %ws: Avoid ldapquery group\n", Group->GenericObject.ObjectName->ObjectName.String ));
goto Cleanup;
}
//
// Query the DC to determine group membership
//
// Drop AzGlResource while going over the wire
//
AzPrint(( AZD_ACCESS_MORE, "AzpCheckGroupMembershipOne: %ws: Is an ldapquery group\n", Group->GenericObject.ObjectName->ObjectName.String ));
AzpUnlockResource( &AzGlResource );
WinStatus = AzpCheckGroupMembershipLdap(
ClientContext,
MemEval );
AzpLockResourceShared( &AzGlResource );
if ( WinStatus != NO_ERROR ) {
goto Cleanup;
}
*ExtendedStatus = MemEval->WinStatus;
//
// Handle invalid group types
//
} else {
AzPrint(( AZD_ACCESS_MORE, "AzpCheckGroupMembershipOne: %ws: Is an invalid group type\n", Group->GenericObject.ObjectName->ObjectName.String, Group->GroupType ));
//
// Don't fail the AccessCheck because of malformed policy data
//
MemEval->IsMember = FALSE;
MemEval->WinStatus = NO_ERROR;
// Return the cached answer
WinStatus = NO_ERROR;
*ExtendedStatus = MemEval->WinStatus;
}
//
// Free any local resources
//
Cleanup:
if ( WinStatus == NO_ERROR &&
*ExtendedStatus == NO_ERROR ) {
*RetIsMember = MemEval->IsMember;
}
return WinStatus;
}
DWORD
AzpWalkTaskTree(
IN PACCESS_CHECK_CONTEXT AcContext,
IN PGENERIC_OBJECT_LIST OperationObjectList,
IN PGENERIC_OBJECT_LIST TaskObjectList,
IN DWORD RecursionLevel,
IN OUT LPDWORD *AllocatedMemory OPTIONAL,
IN OUT LPDWORD MemoryRequired,
IN OUT LPDWORD TaskInfoArraySize,
OUT PAZ_OPS_AND_TASKS OpsAndTasks OPTIONAL,
OUT PBOOLEAN CallAgain
)
/*++
Routine Description:
This routine walks a tree of tasks collecting information about the tasks. The caller should
call this routine twice. Once to determine how much memory to allocate. The second to initialize
that memory.
On the first pass, AllocatedMemory and AcContext->TaskInfo should be passed in as null.
On the first pass, this routine simply counts the number of tasks and operations in the tree.
Between the first and second passes, the caller should allocate two buffers.
The first should be MemoryRequired bytes long and should be passed in AllocatedMemory on the
second pass. The second should be TaskInfoArraySize bytes long and should
be passed in AcContext->TaskInfo on the second pass. (The caller is responsible for freeing
this memory.)
On the second pass, this routine fills in the allocated arrays. AcContext->TaskInfo is
filled in with each task found. However, this routine weeds out duplicates. By weeding
out duplicates, the TaskInfo array has a single entry for each applicable task and we can
ensure that each task is processed at most one time.
Since duplicates are weeded out, the TaskInfo array may be larger than the number of entries used.
On entry, AcContext->ClientContext.CritSect must be locked.
On entry, AzGlResource must be locked Shared. (The AzGlResource should not be dropped between
the two calls to AzpWalkTaskTree mentioned above.)
Arguments:
AcContext - Specifies the context of the user to check group membership of.
OperationObjectList - Specifies a list of operations referenced by the parent object.
TaskObjectList - Specifies the list of tasks referenced by the parent object.
RecursionLevel - Indicates the level of recursion.
Used to prevent infinite recursion.
AllocatedMemory - On the first pass, this pointer should be NULL.
On the second pass, this is a pointer to the memory allocated by the caller.
MemoryRequired - This variable is incremented by the amount of memory required on the second pass.
TaskInfoArraySize - This variable is incremented by the amount of memory required on the second pass.
OpsAndTasks - On the second pass, this structure is filled in with the indices to all
of the applicable operations from OperationObjectList and tasks from TaskObjectList.
CallAgain - Set to TRUE if an operation was found in OperationObjectList or
recursively in TaskObjectList.
FALSE if the OperationObjectList and TaskObjectList should never be processed again.
Return Value:
NO_ERROR - The function was successful
--*/
{
DWORD WinStatus;
BOOLEAN FirstPass = (AllocatedMemory==NULL);
ULONG Size;
ULONG i;
//
// Initialization
//
ASSERT( AzpIsLockedShared( &AzGlResource ) );
ASSERT( AzpIsCritsectLocked( &AcContext->ClientContext->CritSect ) );
if ( RecursionLevel > 100 ) {
return ERROR_DS_LOOP_DETECT;
}
if ( FirstPass ) {
ASSERT( OpsAndTasks == NULL );
ASSERT( AcContext->TaskInfo == NULL );
} else {
ASSERT( OpsAndTasks != NULL );
ASSERT( AcContext->TaskInfo != NULL );
}
*CallAgain = FALSE;
if ( OpsAndTasks != NULL ) {
RtlZeroMemory( OpsAndTasks, sizeof(*OpsAndTasks) );
}
//
// If the caller passed in any operations,
// see which are applicable
//
if ( OperationObjectList->GenericObjects.UsedCount ) {
ULONG OpIndex;
//
// Allocate a buffer for the array of indices to the operations
//
Size = OperationObjectList->GenericObjects.UsedCount*sizeof(ULONG);
*MemoryRequired += Size;
if ( !FirstPass ) {
// AzPrint(( AZD_ACCESS_MORE, "Used: 0x%lx (0x%lx)\n", *AllocatedMemory, Size ));
OpsAndTasks->OpIndexes = *AllocatedMemory;
*AllocatedMemory = (LPDWORD)(((LPBYTE)(*AllocatedMemory)) + Size);
}
//
// Determine which operations are applicable
//
for ( OpIndex = 0;
OpIndex < OperationObjectList->GenericObjects.UsedCount;
OpIndex++ ) {
PAZP_OPERATION Operation;
Operation = (PAZP_OPERATION)(OperationObjectList->GenericObjects.Array[OpIndex]);
ASSERT( Operation->GenericObject.ObjectType == OBJECT_TYPE_OPERATION );
//
// Find this operation in the list of operations requested by the caller.
//
for ( i=0; i<AcContext->OperationCount; i++ ) {
//
// If the Task operation matches a requested operation,
// then we have a match.
//
if ( Operation->OperationId == AcContext->OperationObjects[i]->OperationId ) {
//
// Indicate that we've found an operation
//
*CallAgain = TRUE;
//
// On the second pass,
// Remember an index for this operation.
//
if ( !FirstPass ) {
OpsAndTasks->OpIndexes[OpsAndTasks->OpCount] = i;
OpsAndTasks->OpCount++;
}
break;
}
}
}
}
//
// If the caller passed in any tasks,
// see which are applicable
//
if ( TaskObjectList->GenericObjects.UsedCount ) {
ULONG TaskIndex;
PAZ_TASK_INFO TaskInfo;
//
// Allocate a buffer for the array of indices to the tasks
//
Size = TaskObjectList->GenericObjects.UsedCount*sizeof(ULONG);
*MemoryRequired += Size;
if ( !FirstPass ) {
// AzPrint(( AZD_ACCESS_MORE, "Used: 0x%lx (0x%lx)\n", *AllocatedMemory, Size ));
OpsAndTasks->TaskIndexes = *AllocatedMemory;
*AllocatedMemory = (LPDWORD)(((LPBYTE)(*AllocatedMemory)) + Size);
}
//
// Walk the task object list
//
for ( TaskIndex = 0;
TaskIndex < TaskObjectList->GenericObjects.UsedCount;
TaskIndex ++ ) {
PAZP_TASK Task;
BOOLEAN LocalCallAgain;
Task = (PAZP_TASK)(TaskObjectList->GenericObjects.Array[TaskIndex]);
ASSERT( Task->GenericObject.ObjectType == OBJECT_TYPE_TASK );
//
// If this is the not the first pass,
// find a TaskInfo.
//
*TaskInfoArraySize += sizeof(AZ_TASK_INFO);
TaskInfo = NULL;
if ( !FirstPass ) {
//
// Determine if this task already has a taskinfo
//
for ( i=0; i<AcContext->TaskCount; i++ ) {
if ( Task == AcContext->TaskInfo[i].Task ) {
TaskInfo = &AcContext->TaskInfo[i];
break;
}
}
//
// If there isn't already a task info,
// allocate one
//
if ( TaskInfo == NULL ) {
//
// Grab the next TaskInfo and initialize to zero.
//
TaskInfo = &AcContext->TaskInfo[AcContext->TaskCount];
AcContext->TaskCount++;
RtlZeroMemory( TaskInfo, sizeof(*TaskInfo) );
//
// Reference the task
// Need to grab a reference since the global lock will be dropped during the
// lifetime of this task info.
//
InterlockedIncrement( &Task->GenericObject.ReferenceCount );
AzpDumpGoRef( "Task reference", &Task->GenericObject );
TaskInfo->Task = Task;
}
}
//
// Recurse.
//
WinStatus = AzpWalkTaskTree( AcContext,
&Task->Operations,
&Task->Tasks,
RecursionLevel+1,
AllocatedMemory,
MemoryRequired,
TaskInfoArraySize,
FirstPass ? NULL : &TaskInfo->OpsAndTasks,
&LocalCallAgain );
if ( WinStatus != NO_ERROR ) {
return WinStatus;
}
//
// If operations were found in the subtree,
// then process the subtree.
//
if ( LocalCallAgain ) {
*CallAgain = TRUE;
//
// On the second pass,
// Remember an index for this task
//
if ( !FirstPass ) {
OpsAndTasks->TaskIndexes[OpsAndTasks->TaskCount] =
(ULONG)(TaskInfo - AcContext->TaskInfo);
OpsAndTasks->TaskCount++;
}
//
// If no applicable operations were found anywhere,
// ditch this task.
//
} else {
if ( !FirstPass ) {
TaskInfo->TaskProcessed = TRUE;
}
}
}
}
return NO_ERROR;
}
DWORD
AzpCaptureBizRuleParameters(
IN PACCESS_CHECK_CONTEXT AcContext,
IN VARIANT *ParameterNames OPTIONAL,
IN VARIANT *ParameterValues OPTIONAL
)
/*++
Routine Description:
This routine captures the access check parameters related to BizRule evaluation.
The captured parameters are remembered in the AcContext.
Arguments:
AcContext - Specifies the access check context
ParameterNames - See AzContextAccessCheck
ParameterValues - See AzContextAccessCheck
Return Value:
NO_ERROR - The function was successful
ERROR_INVALID_PARAMETER - One of the parameters are invalid
Any failure returned here should be returned to the caller.
--*/
{
DWORD WinStatus;
HRESULT hr;
SAFEARRAY* SaNames;
SAFEARRAY* SaValues;
VARIANT HUGEP *Names = NULL;
VARIANT HUGEP *Values = NULL;
LONG NamesLower;
LONG NamesUpper;
LONG ValuesLower;
LONG ValuesUpper;
ULONG ParameterCount;
ULONG Index;
//
// We don't actually capture. But we do reference several fields. Do those
// references under a try/except.
//
// All uses of these parameters are done under try/except.
//
__try {
//
// Canonicalize the array references
//
WinStatus = AzpSafeArrayPointerFromVariant( ParameterNames,
TRUE,
&SaNames );
if ( WinStatus != NO_ERROR ) {
goto Cleanup;
}
WinStatus = AzpSafeArrayPointerFromVariant( ParameterValues,
TRUE,
&SaValues );
if ( WinStatus != NO_ERROR ) {
goto Cleanup;
}
//
// If one is null, both must be.
//
if ( SaNames == NULL ) {
if ( SaValues == NULL ) {
WinStatus = NO_ERROR;
AcContext->ParameterNames = NULL;
AcContext->ParameterValues = NULL;
AcContext->ParameterCount = 0;
} else {
AzPrint(( AZD_INVPARM, "AzpCaptureBizRuleParameters: Names is NULL but Values isn't\n" ));
WinStatus = ERROR_INVALID_PARAMETER;
}
goto Cleanup;
} else if ( SaValues == NULL ) {
AzPrint(( AZD_INVPARM, "AzpCaptureBizRuleParameters: Values is NULL but Names isn't\n" ));
WinStatus = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
//
// Both must have the same upper and lower bounds
//
hr = SafeArrayGetLBound( SaNames, 1, &NamesLower );
if ( FAILED(hr)) {
AzPrint(( AZD_INVPARM, "AzpCaptureBizRuleParameters: Can't get name lbound 0x%lx\n", hr ));
WinStatus = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
hr = SafeArrayGetLBound( SaValues, 1, &ValuesLower );
if ( FAILED(hr)) {
AzPrint(( AZD_INVPARM, "AzpCaptureBizRuleParameters: Can't get value lbound 0x%lx\n", hr ));
WinStatus = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
hr = SafeArrayGetUBound( SaNames, 1, &NamesUpper );
if ( FAILED(hr)) {
AzPrint(( AZD_INVPARM, "AzpCaptureBizRuleParameters: Can't get name ubound 0x%lx\n", hr ));
WinStatus = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
hr = SafeArrayGetUBound( SaValues, 1, &ValuesUpper );
if ( FAILED(hr)) {
AzPrint(( AZD_INVPARM, "AzpCaptureBizRuleParameters: Can't get value ubound 0x%lx\n", hr ));
WinStatus = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
if ( NamesLower != ValuesLower ||
NamesUpper != ValuesUpper ) {
AzPrint(( AZD_INVPARM, "AzpCaptureBizRuleParameters: Array bounds don't match %ld %ld %ld %ld\n", NamesLower, ValuesLower, NamesUpper, ValuesUpper ));
WinStatus = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
//
// Lock the arrays
//
hr = SafeArrayAccessData( SaNames, (void HUGEP**)&Names);
if (FAILED(hr)) {
AzPrint(( AZD_INVPARM, "AzpCaptureBizRuleParameters: Can't access ParameterNames 0x%lx\n", hr ));
WinStatus = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
hr = SafeArrayAccessData( SaValues, (void HUGEP**)&Values);
if (FAILED(hr)) {
SafeArrayUnaccessData( SaNames );
AzPrint(( AZD_INVPARM, "AzpCaptureBizRuleParameters: Can't access ParameterValues 0x%lx\n", hr ));
WinStatus = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
//
// Loop validating the parameter arrays
//
ParameterCount = NamesUpper - NamesLower + 1;
for ( Index=0; Index<ParameterCount; Index++ ) {
//
// Stop at the end of the array
//
if ( V_VT( &Names[Index] ) == VT_EMPTY ) {
ParameterCount = Index;
break;
}
//
// Only allow BSTRs
//
if ( V_VT( &Names[Index] ) != VT_BSTR ) {
AzPrint(( AZD_INVPARM, "AzpCaptureBizRuleParameters: Parameter %ld isn't a VT_BSTR\n", Index ));
WinStatus = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
//
// Value cannot be an interface
//
if ( V_VT( &Values[Index] ) == VT_DISPATCH ||
V_VT( &Values[Index] ) == VT_UNKNOWN ) {
AzPrint(( AZD_INVPARM, "AzpCaptureBizRuleParameters: Parameter %ws should have an interface value\n",
V_BSTR(&Names[Index] ) ));
WinStatus = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
#if DBG
//
// On a checked build, ensure the parameters are already sorted
//
// Only compare after the first iteration
//
if ( Index != 0 ) {
if ( AzpCompareParameterNames(&Names[Index-1], &Names[Index] ) >= 0 ) {
AzPrint(( AZD_INVPARM,
"AzpBuildParameterDescriptor: Parameters not sorted: %ws: %ws\n",
V_BSTR(&Names[Index-1] ),
V_BSTR(&Names[Index] ) ));
WinStatus = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
}
#endif // DBG
}
//
// Remember the data
//
AcContext->SaParameterNames = SaNames;
AcContext->ParameterNames = Names;
AcContext->SaParameterValues = SaValues;
AcContext->ParameterValues = Values;
AcContext->ParameterCount = ParameterCount;
WinStatus = NO_ERROR;
Cleanup:;
} __except( EXCEPTION_EXECUTE_HANDLER ) {
WinStatus = RtlNtStatusToDosError( GetExceptionCode());
}
return WinStatus;
}
INT __cdecl
AzpCompareParameterNames(
IN const void *pArg1,
IN const void *pArg2
)
/*++
Routine Description:
This routine compares two parameter names for the qsort/bsearch API
Arguments:
pArg1 - First string for comparison
pArg2 - Second String for comparison
Return Values:
<0 - First string is smaller
0 - strings are same
>0 - First string is larger
--*/
{
HRESULT hr;
VARIANT *var1 = (VARIANT *) pArg1;
VARIANT *var2 = (VARIANT *) pArg2;
//
// Compare the parameter names
// This comparison has proven to be a bottleneck for AccessCheck performance.
// Consider replacing this with some combination of SysStringByteLen and memcmp.
// I'm not sure that all callers pass in strings when SysStringByteLen isn't longer
// than the useful part of the string.
// I'm not sure that memcmp returns the same sort order as a case sensitive VarCmp.
//
ASSERT( V_VT(var1) == VT_BSTR );
ASSERT( V_VT(var2) == VT_BSTR );
hr = VarCmp( var1, var2, LOCALE_USER_DEFAULT, 0);
if ( hr == VARCMP_LT ) {
return -1;
} else if ( hr == (HRESULT)VARCMP_GT ) {
return 1;
} else {
return 0;
}
}
INT __cdecl
AzpCaseInsensitiveCompareParameterNames(
IN const void *pArg1,
IN const void *pArg2
)
/*++
Routine Description:
This routine compares case-insensitively two parameter
names for the qsort/bsearch API
Arguments:
pArg1 - First string for comparison
pArg2 - Second String for comparison
Return Values:
<0 - First string is smaller
0 - strings are same
>0 - First string is larger
--*/
{
HRESULT hr;
VARIANT *var1 = (VARIANT *) pArg1;
VARIANT *var2 = (VARIANT *) pArg2;
//
// See comments in AzpCompareParameterNames
//
ASSERT( V_VT(var1) == VT_BSTR );
ASSERT( V_VT(var2) == VT_BSTR );
hr = VarCmp( var1, var2, LOCALE_USER_DEFAULT, NORM_IGNORECASE);
if ( hr == VARCMP_LT ) {
return -1;
} else if ( hr == (HRESULT)VARCMP_GT ) {
return 1;
} else {
return 0;
}
}
DWORD
AzpCaptureBizRuleInterfaces(
IN PACCESS_CHECK_CONTEXT AcContext,
IN VARIANT *InterfaceNames OPTIONAL,
IN VARIANT *InterfaceFlags OPTIONAL,
IN VARIANT *Interfaces OPTIONAL
)
/*++
Routine Description:
This routine captures the access check interfaces related to BizRule evaluation.
The captured parameters are remembered in the AcContext.
On entry, AzGlResource must be locked Shared.
Arguments:
AcContext - Specifies the access check context
InterfaceNames - See AzContextAccessCheck
InterfaceFlags - See AzContextAccessCheck
Interfaces - See AzContextAccessCheck
Return Value:
NO_ERROR - The function was successful
ERROR_INVALID_PARAMETER - One of the parameters are invalid
Any failure returned here should be returned to the caller.
--*/
{
DWORD WinStatus;
HRESULT hr;
SAFEARRAY* Names;
SAFEARRAY* Flags;
SAFEARRAY* SaInterfaces;
LONG NamesLower;
LONG NamesUpper;
LONG FlagsLower;
LONG FlagsUpper;
LONG InterfacesLower;
LONG InterfacesUpper;
//
// We don't actually capture. But we do reference several fields. Do those
// references under a try/except.
//
// All uses of these parameters are done under try/except.
//
__try {
//
// Canonicalize the array references
//
WinStatus = AzpSafeArrayPointerFromVariant( InterfaceNames,
TRUE,
&Names );
if ( WinStatus != NO_ERROR ) {
goto Cleanup;
}
WinStatus = AzpSafeArrayPointerFromVariant( InterfaceFlags,
TRUE,
&Flags );
if ( WinStatus != NO_ERROR ) {
goto Cleanup;
}
WinStatus = AzpSafeArrayPointerFromVariant( Interfaces,
TRUE,
&SaInterfaces );
if ( WinStatus != NO_ERROR ) {
goto Cleanup;
}
//
// If one is null, all must be.
//
if ( Names == NULL ) {
if ( Flags == NULL && SaInterfaces == NULL ) {
WinStatus = NO_ERROR;
AcContext->InterfaceNames = NULL;
AcContext->InterfaceFlags = NULL;
AcContext->Interfaces = NULL;
} else {
AzPrint(( AZD_INVPARM, "AzpCaptureBizRuleInterfaces: Names is NULL but Flags or Interfaces isn't\n" ));
WinStatus = ERROR_INVALID_PARAMETER;
}
goto Cleanup;
} else if ( Flags == NULL || SaInterfaces == NULL ) {
AzPrint(( AZD_INVPARM, "AzpCaptureBizRuleInterfaces: Flags or Interfaces is NULL but Names isn't\n" ));
WinStatus = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
//
// All must have the same upper and lower bounds
//
hr = SafeArrayGetLBound( Names, 1, &NamesLower );
if ( FAILED(hr)) {
AzPrint(( AZD_INVPARM, "AzpCaptureBizRuleInterfaces: Can't get name lbound 0x%lx\n", hr ));
WinStatus = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
hr = SafeArrayGetLBound( Flags, 1, &FlagsLower );
if ( FAILED(hr)) {
AzPrint(( AZD_INVPARM, "AzpCaptureBizRuleInterfaces: Can't get value lbound 0x%lx\n", hr ));
WinStatus = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
hr = SafeArrayGetLBound( SaInterfaces, 1, &InterfacesLower );
if ( FAILED(hr)) {
AzPrint(( AZD_INVPARM, "AzpCaptureBizRuleInterfaces: Can't get interfaces lbound 0x%lx\n", hr ));
WinStatus = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
hr = SafeArrayGetUBound( Names, 1, &NamesUpper );
if ( FAILED(hr)) {
AzPrint(( AZD_INVPARM, "AzpCaptureBizRuleInterfaces: Can't get name ubound 0x%lx\n", hr ));
WinStatus = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
hr = SafeArrayGetUBound( Flags, 1, &FlagsUpper );
if ( FAILED(hr)) {
AzPrint(( AZD_INVPARM, "AzpCaptureBizRuleInterfaces: Can't get value ubound 0x%lx\n", hr ));
WinStatus = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
hr = SafeArrayGetUBound( SaInterfaces, 1, &InterfacesUpper );
if ( FAILED(hr)) {
AzPrint(( AZD_INVPARM, "AzpCaptureBizRuleInterfaces: Can't get interfaces ubound 0x%lx\n", hr ));
WinStatus = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
if ( NamesLower != FlagsLower ||
NamesLower != InterfacesLower ||
NamesUpper != FlagsUpper ||
NamesUpper != InterfacesUpper ) {
AzPrint(( AZD_INVPARM,
"AzpCaptureBizRuleInterfaces: Array bounds don't match %ld %ld %ld %ld %ld %ld\n",
NamesLower, FlagsLower, InterfacesLower,
NamesUpper, FlagsUpper, InterfacesUpper ));
WinStatus = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
//
// Remember the data
//
AcContext->InterfaceNames = Names;
AcContext->InterfaceFlags = Flags;
AcContext->Interfaces = SaInterfaces;
AcContext->InterfaceLower = NamesLower;
AcContext->InterfaceUpper = NamesUpper;
WinStatus = NO_ERROR;
Cleanup:;
} __except( EXCEPTION_EXECUTE_HANDLER ) {
WinStatus = RtlNtStatusToDosError( GetExceptionCode());
}
//
// Validate the parameters
//
return WinStatus;
}
typedef enum {
WeedOutFinishedOps,
SetResultStatus
} OPS_AND_TASKS_OPCODE;
DWORD
AzpWalkOpsAndTasks(
IN PACCESS_CHECK_CONTEXT AcContext,
IN PAZP_STRING Name,
IN PAZ_OPS_AND_TASKS OpsAndTasks,
IN OPS_AND_TASKS_OPCODE Opcode,
IN DWORD ResultStatus,
IN BOOLEAN BizrulesOk,
OUT PBOOLEAN CallAgain
)
/*++
Routine Description:
This routine walks the OpsAndTasks tree built by AzpWalkTaskTree performing the function
specified by Opcode.
On entry, AcContext->ClientContext.CritSect must be locked.
On entry, AzGlResource must be locked Shared.
*** Note: this routine will temporarily drop AzGlResource in cases where it hits the wire
Arguments:
AcContext - Specifies the context of the user to check group membership of.
Name - Name of the object containing OpsAndTasks.
OpsAndTasks - Specifies the set of operations and tasks for a particular role or task.
Opcode - Specifies the operation to perform.
WeedOutFinishedOps: update OpsAndTasks to remove operations that are already finished
and return CallAgain if there are any operations left.
SetResultStatus: Set ResultStatus on all operations that haven't yet been processed
ResultStatus - Specifies the ResultStatus to be set for the SetResultStatus opcode.
BizrulesOk - Specifies TRUE if it is OK to process Bizrules.
If FALSE, only Ops and Tasks without bizrules are processed.
This boolean is ignored for the WeedOutFinishedOps opcode.
CallAgain - Set to TRUE if the caller should call again for this OpsAndTasks.
FALSE if the OpsAndTasks should never be processed again.
Return Value:
NO_ERROR - The function was successful
--*/
{
DWORD WinStatus;
ULONG i;
ULONG OpIndex;
//
// Initialization
//
UNREFERENCED_PARAMETER( Name ); // Not referenced on a free build
ASSERT( AzpIsLockedShared( &AzGlResource ) );
ASSERT( AzpIsCritsectLocked( &AcContext->ClientContext->CritSect ) );
*CallAgain = FALSE;
//
// Loop through the list of operations seeing if they're still applicable.
//
for ( i = 0; i < OpsAndTasks->OpCount; ) {
ASSERT( i < AcContext->OperationCount );
//
// If all operations have now been processed,
// we're done.
//
if ( AcContext->ProcessedOperationCount == AcContext->OperationCount ) {
AzPrint(( AZD_ACCESS_MORE,
"AzpWalkOpsAndTasks: %ws: %ws: All operations have been processed %ld.\n",
AcContext->ObjectNameString.String,
AcContext->ScopeNameString.String ? AcContext->ScopeNameString.String : L"",
AcContext->OperationCount ));
break;
}
//
// If the operation result is already known,
// ditch it from this list.
//
OpIndex = OpsAndTasks->OpIndexes[i];
if ( AcContext->OperationWasProcessed[OpIndex] ) {
PopUlong( OpsAndTasks->OpIndexes, i, OpsAndTasks->OpCount );
//
// If the operation still needs to be processed,
// note the fact and move on
//
} else {
//
// Update the Results array
//
if ( Opcode == SetResultStatus ) {
//
// If permission is now granted to the operation,
// mark it so.
//
if ( ResultStatus == NO_ERROR ) {
AzPrint(( AZD_ACCESS_MORE,
"AzpWalkOpsAndTasks: %ws: %ws: %ws: Operation granted\n",
AcContext->ObjectNameString.String,
Name->String,
AcContext->OperationObjects[OpIndex]->GenericObject.ObjectName->ObjectName.String ));
AcContext->Results[OpIndex] = NO_ERROR;
AcContext->OperationWasProcessed[OpIndex] = TRUE;
AcContext->ProcessedOperationCount++;
//
// If we don't already know that result,
// update it.
//
// If the result is already ERROR_ACCESS_DENIED, any status other than NO_ERROR is
// less informative than the one we already have.
//
} else if ( AcContext->Results[OpIndex] != ERROR_ACCESS_DENIED ) {
AcContext->Results[OpIndex] = ResultStatus;
AzPrint(( AZD_ACCESS_MORE,
"AzpWalkOpsAndTasks: %ws: %ws: %ws: Operation extended failure %ld\n",
AcContext->ObjectNameString.String,
Name->String,
AcContext->OperationObjects[OpIndex]->GenericObject.ObjectName->ObjectName.String,
ResultStatus ));
}
}
//
// If the operation has now been granted access,
// ditch it from this list.
//
if ( AcContext->Results[OpIndex] == NO_ERROR ) {
PopUlong( OpsAndTasks->OpIndexes, i, OpsAndTasks->OpCount );
} else {
*CallAgain = TRUE;
i++;
}
}
}
//
// Loop through the list of tasks recursing
//
for ( i=0; i<OpsAndTasks->TaskCount; ) {
PAZ_TASK_INFO TaskInfo;
BOOLEAN LocalCallAgain;
//
// If the task has already been processed,
// ditch it from the list.
//
ASSERT( i < AcContext->TaskCount );
TaskInfo = &AcContext->TaskInfo[OpsAndTasks->TaskIndexes[i]];
if ( TaskInfo->TaskProcessed ) {
PopUlong( OpsAndTasks->TaskIndexes, i, OpsAndTasks->TaskCount );
//
// If the task still needs to be processed,
// do so now.
//
} else {
BOOLEAN WalkTree;
//
// If the task has a BizRule,
// process the BizRule
//
//
WalkTree = TRUE;
if ( TaskInfo->Task->BizRule.StringSize != 0 ) {
//
// If we're just weeding out operations
// don't process the BizRule and continue walking the tree
//
if ( Opcode == WeedOutFinishedOps ) {
WalkTree = TRUE;
//
// If we're setting ResultStatus,
// and we're not yet processing BizRules,
// we're done for now
//
} else if ( !BizrulesOk ) {
WalkTree = FALSE;
//
// If we're setting ResultStatus,
// and we're allowed to process the bizrule,
// do so now.
//
} else {
//
// If we haven't yet cached the result of the BizRule,
// process the BizRule now.
//
if ( !TaskInfo->BizRuleProcessed ) {
BOOL BizRuleResult = FALSE;
//
// If there exists a bizrule in a task under a delegated scope, we need to return
// an error back to the user telling them that the store is in an inconsistent state.
// Tasks under delegated scopes are not allowed to have any bizrules. If they do, then
// the store has either been compromised, or changed not using azroles.dll. The business
// rule then should be evaluated to a FALSE, and a reason returned to the user.
//
if ( (((PGENERIC_OBJECT)
(TaskInfo->Task))->ParentGenericObjectHead->ParentGenericObject)->ObjectType
== OBJECT_TYPE_SCOPE &&
(((PGENERIC_OBJECT)
(TaskInfo->Task))->ParentGenericObjectHead->ParentGenericObject)->PolicyAdmins.GenericObjects.UsedCount
!= 0 ) {
WinStatus = ERROR_FILE_CORRUPT;
return WinStatus;
}
// Drop AzGlResource while going over the wire
AzpUnlockResource( &AzGlResource );
WinStatus = AzpProcessBizRule( AcContext, TaskInfo->Task, &BizRuleResult );
AzpLockResourceShared( &AzGlResource );
if ( WinStatus != NO_ERROR ) {
return WinStatus;
}
TaskInfo->BizRuleProcessed = TRUE;
TaskInfo->BizRuleResult = (BOOLEAN)BizRuleResult;
}
//
// If the BizRule granted access,
// walk the tree.
//
if ( TaskInfo->BizRuleResult ) {
WalkTree = TRUE;
//
// If the BizRule didn't grant access,
// don't walk the tree and never process this task again
//
} else {
WalkTree = FALSE;
TaskInfo->TaskProcessed = TRUE;
}
}
}
//
// If we need to recurse,
// do so now
//
if ( WalkTree ) {
//
// Process all of the operations and tasks for this task
//
WinStatus = AzpWalkOpsAndTasks( AcContext,
&TaskInfo->Task->GenericObject.ObjectName->ObjectName,
&TaskInfo->OpsAndTasks,
Opcode,
ResultStatus,
BizrulesOk,
&LocalCallAgain );
if ( WinStatus != NO_ERROR ) {
return WinStatus;
}
//
// If we're not to walk the tree again,
// mark it so.
//
if ( !LocalCallAgain ) {
TaskInfo->TaskProcessed = TRUE;
}
}
//
// If the task is now marked as processed,
// ditch it from the list.
//
if ( TaskInfo->TaskProcessed ) {
PopUlong( OpsAndTasks->TaskIndexes, i, OpsAndTasks->TaskCount );
//
// Otherwise process it again
//
} else {
*CallAgain = TRUE;
i++;
}
}
}
return NO_ERROR;
}
DWORD
WINAPI
AzInitializeContextFromToken(
IN AZ_HANDLE ApplicationHandle,
IN HANDLE TokenHandle OPTIONAL,
IN DWORD Reserved,
OUT PAZ_HANDLE ClientContextHandle
)
/*++
Routine Description:
This routine creates a client context based on a passed in token handle or on
the impersonation token of the caller's thread.
Arguments:
ApplicationHandle - Specifies a handle to the application object that
is this client context applies to.
TokenHandle - Handle to the NT token describing the cleint.
NULL implies the impersonation token of the caller's thread.
The token mast have been opened for TOKEN_QUERY, TOKEN_IMPERSONATION, and
TOKEN_DUPLICATE access.
Reserved - Reserved. Must by zero.
ClientContextHandle - Return a handle to the client context
The caller must close this handle by calling AzCloseHandle.
Return Value:
NO_ERROR - The operation was successful
ERROR_NOT_SUPPORTED - Function not supported in current store mode (ie, manage store)
ERROR_NOT_ENOUGH_MEMORY - not enough memory
Other exception status codes
--*/
{
DWORD WinStatus;
LUID Identifier = {0};
PAZP_CLIENT_CONTEXT ClientContext = NULL;
HANDLE TempTokenHandle = INVALID_HANDLE_VALUE;
TOKEN_STATISTICS TokenStats = {0};
PAZP_AZSTORE AzAuthorizationStore = (PAZP_AZSTORE) ParentOfChild(
&((PAZP_APPLICATION)ApplicationHandle)->GenericObject );
DWORD Ignore;
DWORD dwDesiredAccess; // Desired access for DuplicateTokenEx
SECURITY_IMPERSONATION_LEVEL ImpersonationLevel; // Impersonation level for DuplicateTokenEx
//
// If the store is in manage mode only, then no runtime is allowed
// Return ERROR_NOT_SUPPORTED
//
if ( AzpOpenToManageStore(AzAuthorizationStore) ) {
WinStatus = ERROR_NOT_SUPPORTED;
AzPrint(( AZD_CRITICAL, "AzInitializeContextFromToken: Cannot initialize context since store is in manage mode %ld\n",
WinStatus
));
goto Cleanup;
}
//
// Call the common routine to create our client context object
//
WinStatus = ObCommonCreateObject(
(PGENERIC_OBJECT) ApplicationHandle,
OBJECT_TYPE_APPLICATION,
&(((PAZP_APPLICATION)ApplicationHandle)->ClientContexts),
OBJECT_TYPE_CLIENT_CONTEXT,
NULL,
Reserved,
(PGENERIC_OBJECT *) &ClientContext );
if ( WinStatus != NO_ERROR ) {
AzPrint(( AZD_CRITICAL, "AzInitializeContextFromToken: Cannot ObCommonCreateObject %ld\n", WinStatus ));
goto Cleanup;
}
//
// Client context isn't persisted.
//
ASSERT( ClientContext->GenericObject.DirtyBits == 0);
//
// If the user didn't pass a handle in,
// open a thread or process token handle.
//
ClientContext->CreationType = AZP_CONTEXT_CREATED_FROM_TOKEN;
if ( TokenHandle == NULL ) {
if ( !OpenThreadToken( GetCurrentThread(),
TOKEN_QUERY | TOKEN_IMPERSONATE,
TRUE,
&ClientContext->TokenHandle ) ) {
WinStatus = GetLastError();
//
// If there is not thread token,
// use the process token.
//
if ( WinStatus == ERROR_NO_TOKEN ) {
if ( !OpenProcessToken( GetCurrentProcess(),
TOKEN_DUPLICATE,
&TempTokenHandle ) ) {
WinStatus = GetLastError();
AzPrint(( AZD_CRITICAL, "AzInitializeContextFromToken: Cannot OpenProcessToken %ld\n", WinStatus ));
goto Cleanup;
}
//
// Need an impersonation token
//
if ( !DuplicateTokenEx( TempTokenHandle,
TOKEN_QUERY | TOKEN_IMPERSONATE,
NULL,
SecurityImpersonation,
TokenImpersonation,
&ClientContext->TokenHandle ) ) {
WinStatus = GetLastError();
AzPrint(( AZD_CRITICAL, "AzInitializeContextFromToken: Cannot DuplicateTokenEx %ld\n", WinStatus ));
goto Cleanup;
}
} else {
AzPrint(( AZD_CRITICAL, "AzInitializeContextFromToken: Cannot OpenThreadToken %ld\n", WinStatus ));
goto Cleanup;
}
}
//
// If the user did pass a handle in,
// dup it
//
} else {
if ( !GetTokenInformation( TokenHandle,
TokenStatistics,
&TokenStats,
sizeof(TOKEN_STATISTICS),
&Ignore ) ) {
WinStatus = GetLastError();
AzPrint(( AZD_ACCESS, "AzInitializeContextFromToken: GetTokenInformation failed with %ld\n", WinStatus ));
goto Cleanup;
}
dwDesiredAccess = TOKEN_QUERY | TOKEN_IMPERSONATE;
if ( TokenStats.ImpersonationLevel > SecurityIdentification ) {
ImpersonationLevel = SecurityImpersonation;
} else {
ImpersonationLevel = SecurityIdentification;
}
if ( !DuplicateTokenEx( TokenHandle,
dwDesiredAccess,
NULL,
ImpersonationLevel,
TokenImpersonation,
&ClientContext->TokenHandle ) ) {
WinStatus = GetLastError();
AzPrint(( AZD_CRITICAL, "AzInitializeContextFromToken: Cannot DuplicateTokenEx %ld\n", WinStatus ));
goto Cleanup;
}
}
//
// Read the token authentication Id
//
if ( !GetTokenInformation( ClientContext->TokenHandle,
TokenStatistics,
&TokenStats,
sizeof(TOKEN_STATISTICS),
&Ignore ) ) {
WinStatus = GetLastError();
AzPrint(( AZD_ACCESS, "AzInitializeContextFromToken: GetTokenInformation failed with %ld\n", WinStatus ));
goto Cleanup;
}
ClientContext->LogonId = TokenStats.AuthenticationId;
//
// Initialize Authz
//
if ( !AuthzInitializeContextFromToken(
0, // No Flags
ClientContext->TokenHandle,
(((PAZP_APPLICATION)ApplicationHandle)->AuthzResourceManager),
NULL, // No expiration time
Identifier,
NULL, // No dynamic group args
&ClientContext->AuthzClientContext ) ) {
WinStatus = GetLastError();
AzPrint(( AZD_CRITICAL, "AzInitializeContextFromToken: Cannot AuthzInitializeContextFromToken %ld\n", WinStatus ));
goto Cleanup;
}
//
// Generate a client context creation audit based on the audit boolean.
//
if ( ((PAZP_APPLICATION) ApplicationHandle)->GenericObject.IsGeneratingAudits &&
!AzpOpenToManageStore(AzAuthorizationStore) ) {
WinStatus = AzpClientContextGenerateCreateSuccessAudit( ClientContext,
(PAZP_APPLICATION) ApplicationHandle );
if ( WinStatus != NO_ERROR ) {
AzPrint(( AZD_ACCESS, "AzpGenerateContextCreateAudit: Cannot ObCommonCreateObject %ld\n", WinStatus ));
goto Cleanup;
}
}
WinStatus = NO_ERROR;
*ClientContextHandle = ClientContext;
ClientContext = NULL;
//
// Free any local resources
//
Cleanup:
if ( ClientContext != NULL ) {
AzCloseHandle( ClientContext, 0 );
}
if ( TempTokenHandle != INVALID_HANDLE_VALUE ) {
CloseHandle( TempTokenHandle );
}
return WinStatus;
}
DWORD
AzpGetClientContextTokenS4U(
IN LPCWSTR ClientName,
IN LPCWSTR DomainName OPTIONAL,
OUT PHANDLE pTokenHandle
)
/*++
Routine Description:
This routine tries to get an identify-level token for the user
using KERB_S4U_LOGON authentication package.
Arguments:
ClientName - Name of the user
DomainName - An optional domain name for the user
pTokenHandle - Handle to the acquired identify-level token
Return Value:
NO_ERROR - The operation was successful
ERROR_NOT_ENOUGH_MEMORY - lack of memory resources
Other Status code returned from LsaLogonUser specific calls.
--*/
{
DWORD WinStatus = NO_ERROR;
NTSTATUS Status;
NTSTATUS subStatus;
HANDLE hLsa = INVALID_HANDLE_VALUE;
LSA_STRING asProcessName;
LSA_STRING asPackageName;
ULONG ulAuthPackage;
LUID luid;
DWORD ClientNameLength = 0;
DWORD DomainNameLength = 0;
PKERB_S4U_LOGON pPackage = NULL;
ULONG ulPackageSize = 0;
TOKEN_SOURCE sourceContext;
PVOID pProfileBuffer = NULL;
ULONG ulProfileLength = 0;
QUOTA_LIMITS quota;
//
// Validation
//
ASSERT( ClientName != NULL );
ClientNameLength = (DWORD)(wcslen( ClientName )*sizeof(WCHAR));
//
// Setup the Authentication package
//
ulPackageSize = sizeof( KERB_S4U_LOGON );
ulPackageSize += ClientNameLength;
if ( DomainName ) {
DomainNameLength = (DWORD)(wcslen( DomainName )*sizeof(WCHAR));
ulPackageSize += DomainNameLength;
}
pPackage = (PKERB_S4U_LOGON) LocalAlloc(LMEM_FIXED, ulPackageSize);
if ( pPackage == NULL ) {
WinStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
pPackage->MessageType = KerbS4ULogon;
pPackage->Flags = 0;
pPackage->ClientUpn.Length = (USHORT)ClientNameLength;
pPackage->ClientUpn.MaximumLength = (USHORT)ClientNameLength;
pPackage->ClientUpn.Buffer = (LPWSTR)(pPackage + 1);
RtlCopyMemory(
pPackage->ClientUpn.Buffer,
ClientName,
ClientNameLength
);
if ( DomainName ) {
pPackage->ClientRealm.Length = (USHORT)DomainNameLength;
pPackage->ClientRealm.MaximumLength = (USHORT)DomainNameLength;
pPackage->ClientRealm.Buffer = (LPWSTR)
(((PBYTE)(pPackage->ClientUpn.Buffer)) + pPackage->ClientUpn.Length);
RtlCopyMemory(
pPackage->ClientRealm.Buffer,
DomainName,
DomainNameLength
);
} else {
pPackage->ClientRealm.Length = 0;
pPackage->ClientRealm.MaximumLength = 0;
pPackage->ClientRealm.Buffer = NULL;
}
//
// Initialize process name
//
RtlInitString(
&asProcessName,
"AzManAPI"
);
//
// Register with Lsa
//
Status = LsaConnectUntrusted(
&hLsa
);
if ( !NT_SUCCESS( Status ) ) {
WinStatus = LsaNtStatusToWinError( Status );
goto Cleanup;
}
//
// Get the Autherntication package
//
RtlInitString( &asPackageName, MICROSOFT_KERBEROS_NAME_A );
Status = LsaLookupAuthenticationPackage(
hLsa,
&asPackageName,
&ulAuthPackage
);
if ( !NT_SUCCESS( Status ) ) {
WinStatus = LsaNtStatusToWinError( Status );
goto Cleanup;
}
//
// Prepare the source context
//
RtlCopyMemory(
sourceContext.SourceName,
"AzRoles ",
sizeof(sourceContext.SourceName)
);
Status = NtAllocateLocallyUniqueId(
&sourceContext.SourceIdentifier
);
if ( !NT_SUCCESS( Status ) ) {
WinStatus = RtlNtStatusToDosError( Status );
goto Cleanup;
}
//
// Now that everything is set up, do the actual logon
//
Status = LsaLogonUser(
hLsa,
&asPackageName,
Network,
ulAuthPackage,
pPackage,
ulPackageSize,
0,
&sourceContext,
&pProfileBuffer,
&ulProfileLength,
&luid,
pTokenHandle,
&quota,
&subStatus
);
if ( !NT_SUCCESS( Status ) ) {
WinStatus = LsaNtStatusToWinError( Status );
goto Cleanup;
}
WinStatus = NO_ERROR;
Cleanup:
if ( pPackage ) {
LocalFree( pPackage );
}
if ( pProfileBuffer ) {
LsaFreeReturnBuffer( pProfileBuffer );
}
if ( hLsa != INVALID_HANDLE_VALUE ) {
LsaDeregisterLogonProcess( hLsa );
}
return WinStatus;
}
DWORD
WINAPI
AzInitializeContextFromName(
IN AZ_HANDLE ApplicationHandle,
IN LPWSTR DomainName OPTIONAL,
IN LPWSTR ClientName,
IN DWORD Reserved,
OUT PAZ_HANDLE ClientContextHandle
)
/*++
Routine Description:
This routine creates a client context based on a passed in token handle or on
the impersonation token of the caller's thread.
Arguments:
ApplicationHandle - Specifies a handle to the application object that
is this client context applies to.
DomainName - Domain in which the user account lives. This may be NULL.
ClientName - Name of the user account. Together these two represent a user
account which may be looked up using LookupAccountName.
Reserved - Reserved. Must by zero.
ClientContextHandle - Return a handle to the client context
The caller must close this handle by calling AzCloseHandle.
Return Value:
NO_ERROR - The operation was successful
ERROR_NOT_SUPPORTED - Function not supported in current store mode (ie, manage store)
ERROR_NOT_ENOUGH_MEMORY - not enough memory
Other exception status codes
--*/
{
DWORD WinStatus;
LUID Identifier = {0};
PAZP_CLIENT_CONTEXT ClientContext = NULL;
DWORD SidSize = SECURITY_MAX_SID_SIZE;
PWCHAR LocalDomainBuffer[MAX_COMPUTERNAME_LENGTH+1];
DWORD DomainSize = MAX_COMPUTERNAME_LENGTH;
SID_NAME_USE SidType = SidTypeUnknown;
DWORD ClientNameSize = 0;
DWORD DomainNameSize = 0;
PAZP_AZSTORE AzAuthorizationStore = (PAZP_AZSTORE) ParentOfChild(
&((PAZP_APPLICATION)ApplicationHandle)->GenericObject );
NTSTATUS Status;
DWORD Ignore = 0;
PWCHAR pSamCompatName = NULL;
DWORD SamCompatNameSize = 0;
HANDLE hToken = INVALID_HANDLE_VALUE;
//
// If the store is in manage mode only, then no runtime is allowed
// Return ERROR_NOT_SUPPORTED
//
if ( AzpOpenToManageStore(AzAuthorizationStore) ) {
WinStatus = ERROR_NOT_SUPPORTED;
AzPrint(( AZD_CRITICAL, "AzInitializeContextFromName: Cannot initialize context since store is in manage mode %ld\n",
WinStatus
));
goto Cleanup;
}
//
// Try to logon user using KERB_S4U_LOGON. This will give
// us an identify-level token for the user. Use this to
// create the client context from token. If S4U is not supported
// then use LookupAccountName/SID to intialize Authz using the SID
//
WinStatus = AzpGetClientContextTokenS4U(
ClientName,
((DomainName == NULL)? NULL:((wcslen(DomainName) == 0)?NULL:DomainName)),
&hToken
);
if ( WinStatus == NO_ERROR ) {
WinStatus = AzInitializeContextFromToken(
ApplicationHandle,
hToken,
Ignore,
ClientContextHandle
);
if ( WinStatus != NO_ERROR ) {
AzPrint(( AZD_CRITICAL,
"AzInitializeContextFromName: Cannot AzInitializeFromToken %ld\n",
WinStatus
));
goto Cleanup;
}
return WinStatus;
}
//
// Call the common routine to create our client context object
//
WinStatus = ObCommonCreateObject(
(PGENERIC_OBJECT) ApplicationHandle,
OBJECT_TYPE_APPLICATION,
&(((PAZP_APPLICATION)ApplicationHandle)->ClientContexts),
OBJECT_TYPE_CLIENT_CONTEXT,
NULL,
Reserved,
(PGENERIC_OBJECT *) &ClientContext );
if ( WinStatus != NO_ERROR ) {
AzPrint(( AZD_CRITICAL, "AzInitializeContextFromName: Cannot ObCommonCreateObject %ld\n", WinStatus ));
goto Cleanup;
}
//
// Client context isn't persisted.
//
ASSERT( ClientContext->GenericObject.DirtyBits == 0);
ClientContext->CreationType = AZP_CONTEXT_CREATED_FROM_NAME;
//
// Allocate a LUID to represent out client. We need this for linking
// audits.
//
Status = NtAllocateLocallyUniqueId(&ClientContext->LogonId);
if ( !NT_SUCCESS( Status )) {
WinStatus = RtlNtStatusToDosError( Status );
AzPrint(( AZD_CRITICAL, "AzInitializeContextFromName: Cannot allocate LUID %ld\n", WinStatus ));
goto Cleanup;
}
//
// If passed in name is not in UPN (detected by presence of "@") or SamCompat (detected by
// presence of "\") format, then we need to append it to the passed in domain name.
//
if ( ((DomainName != NULL) && (*DomainName != NULL)) &&
((wcschr( ClientName, L'@' ) == NULL) && (wcschr( ClientName, L'\\' ) == NULL))
) {
SamCompatNameSize = (DWORD)((wcslen(DomainName) + wcslen(ClientName) + 2) * sizeof(WCHAR));
pSamCompatName = (PWCHAR) AzpAllocateHeap( SamCompatNameSize, "TCLNTNAM" );
if ( pSamCompatName == NULL ) {
WinStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
wnsprintf( pSamCompatName, SamCompatNameSize, L"%ws\\%ws", DomainName, ClientName );
}
//
// Lookup the name of client name.
//
if ( !LookupAccountNameW(
NULL,
pSamCompatName?pSamCompatName:ClientName,
ClientContext->SidBuffer,
&SidSize,
(LPWSTR) LocalDomainBuffer,
&DomainSize,
&SidType ) ) {
WinStatus = GetLastError();
ASSERT( WinStatus != ERROR_INSUFFICIENT_BUFFER );
AzPrint(( AZD_CRITICAL, "AzInitializeContextFromName: LookupAccoutName failed with %ld\n", WinStatus ));
}
//
// We only support SidTypeUser account type.
//
if ( SidType != SidTypeUser ) {
WinStatus = ERROR_INVALID_PARAMETER;
AzPrint(( AZD_CRITICAL, "AzInitializeContextFromName: Invalid user type - expected SIdTypeUser, got %ld\n", SidType ));
goto Cleanup;
}
//
// Do the reverse lookup and store the domain name and the client name.
// We have to do this since the domain name is optional.
// Get the size of the buffer in the first call.
//
if ( !LookupAccountSidW(
NULL,
ClientContext->SidBuffer,
NULL,
&ClientNameSize,
NULL,
&DomainNameSize,
&SidType ) ) {
WinStatus = GetLastError();
if ( WinStatus != ERROR_INSUFFICIENT_BUFFER ) {
goto Cleanup;
}
//
// Note that these get freed when then client context is released - even in
// error cases in this routine itself.
//
//
// Since LookupAccountSid returns size in characters and not bytes...
//
DomainNameSize *= sizeof( WCHAR );
ClientNameSize *= sizeof( WCHAR );
ClientContext->DomainName = (LPWSTR) AzpAllocateHeap( DomainNameSize, "CNDOMNM" );
if ( ClientContext->DomainName == NULL ) {
WinStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
ClientContext->ClientName = (LPWSTR) AzpAllocateHeap( ClientNameSize, "CNCLNTNM" );
if ( ClientContext->ClientName == NULL ) {
WinStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
if ( !LookupAccountSidW(
NULL,
ClientContext->SidBuffer,
ClientContext->ClientName,
&ClientNameSize,
ClientContext->DomainName,
&DomainNameSize,
&SidType ) ) {
WinStatus = GetLastError();
goto Cleanup;
}
} else {
//
// This can not happen since we supplied a NULL buffer.
//
ASSERT( FALSE );
WinStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
//
// Initialize Authz
//
if ( !AuthzInitializeContextFromSid(
0, // No Flags
ClientContext->SidBuffer,
(((PAZP_APPLICATION)ApplicationHandle)->AuthzResourceManager),
NULL, // No expiration time
Identifier,
NULL, // No dynamic group args
&ClientContext->AuthzClientContext ) ) {
WinStatus = GetLastError();
AzPrint(( AZD_CRITICAL, "AzInitializeContextFromToken: Cannot AuthzInitializeContextFromToken %ld\n", WinStatus ));
goto Cleanup;
}
//
// Generate a client context creation audit based on the audit boolean.
//
if ( ((PAZP_APPLICATION) ApplicationHandle)->GenericObject.IsGeneratingAudits &&
!AzpOpenToManageStore(AzAuthorizationStore) ) {
WinStatus = AzpClientContextGenerateCreateSuccessAudit( ClientContext,
(PAZP_APPLICATION) ApplicationHandle );
if ( WinStatus != NO_ERROR ) {
AzPrint(( AZD_ACCESS, "AzpGenerateContextCreateAudit: Cannot ObCommonCreateObject %ld\n", WinStatus ));
goto Cleanup;
}
}
WinStatus = NO_ERROR;
*ClientContextHandle = ClientContext;
ClientContext = NULL;
//
// Free any local resources
//
Cleanup:
if ( pSamCompatName != NULL ) {
AzpFreeHeap( pSamCompatName ) ;
}
if ( hToken != INVALID_HANDLE_VALUE ) {
CloseHandle( hToken );
}
if ( ClientContext != NULL ) {
AzCloseHandle( ClientContext, 0 );
}
return WinStatus;
}
DWORD
WINAPI
AzInitializeContextFromStringSid(
IN AZ_HANDLE ApplicationHandle,
IN LPCWSTR SidString,
IN DWORD lOptions,
OUT PAZ_HANDLE ClientContextHandle
)
/*++
Routine Description:
This routine creates a client context based on a passed in SID string.
Note, the sid string can be any arbitary SID and the lOptions will indicate
if Lldap group should be skipped (for the arbitary SID)
if AZ_CLIENT_CONTEXT_SKIP_GROUP is true, we will skip the ldap group check
even if this is a valid NT user sid.
if AZ_CLIENT_CONTEXT_SKIP_GROUP is false, we will require the SID string is
a valid NT user sid (LookupSid must be able to find the domain name and
client name).
Arguments:
ApplicationHandle - Specifies a handle to the application object that
is this client context applies to.
SidString - Sid string of the account.
lOptions - options for the method
AZ_CLIENT_CONTEXT_SKIP_GROUP
ClientContextHandle - Return a handle to the client context
The caller must close this handle by calling AzCloseHandle.
Return Value:
NO_ERROR - The operation was successful
ERROR_NOT_ENOUGH_MEMORY - not enough memory
Other exception status codes
--*/
{
DWORD WinStatus;
LUID Identifier = {0};
PAZP_CLIENT_CONTEXT ClientContext = NULL;
PSID pAccountSid = NULL;
NTSTATUS Status;
LPWSTR DomainName = NULL;
LPWSTR ClientName = NULL;
DWORD DomainNameSize = 0;
DWORD ClientNameSize = 0;
SID_NAME_USE SidType = SidTypeUnknown;
PAZP_AZSTORE AzAuthorizationStore = (PAZP_AZSTORE) ParentOfChild(
&((PAZP_APPLICATION)ApplicationHandle)->GenericObject );
//
// check parameter
//
if ( SidString == NULL ) {
return ERROR_INVALID_PARAMETER;
}
//
// If the store is in manage mode only, then no runtime is allowed
// Return ERROR_NOT_SUPPORTED
//
if ( AzpOpenToManageStore(AzAuthorizationStore) ) {
WinStatus = ERROR_NOT_SUPPORTED;
AzPrint(( AZD_CRITICAL, "AzInitializeContextFromStringSid: Cannot initialize context since store is in manage mode %ld\n",
WinStatus
));
goto Cleanup;
}
//
// Lookup the SID (binary) of the SID string.
//
if ( !ConvertStringSidToSidW(
SidString,
&pAccountSid) ) {
WinStatus = GetLastError();
AzPrint(( AZD_CRITICAL, "AzInitializeContextFromStringSid: ConvertSidStringToSid failed with %ld\n", WinStatus ));
goto Cleanup;
}
//
// if flag AZ_CLIENT_CONTEXT_SKIP_GROUP is not set
// we assume this ia a valid NT user account
// check if the SID is a valid NT account
//
if ( !(lOptions & AZ_CLIENT_CONTEXT_SKIP_GROUP) ) {
if ( !LookupAccountSidW(
NULL,
pAccountSid,
NULL,
&ClientNameSize,
NULL,
&DomainNameSize,
&SidType ) ) {
//
// should get ERROR_INSUFFICIENT_BUFFER if the account is resolved
//
WinStatus = GetLastError();
if ( WinStatus != ERROR_INSUFFICIENT_BUFFER ) {
//
// the SID string cannot be resolved
//
goto Cleanup;
}
//
// Since LookupAccountSid returns size in characters and not bytes...
//
DomainNameSize *= sizeof( WCHAR );
ClientNameSize *= sizeof( WCHAR );
DomainName = (LPWSTR) AzpAllocateHeap( DomainNameSize, "CNDOMNM" );
if ( DomainName == NULL ) {
WinStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
ClientName = (LPWSTR) AzpAllocateHeap( ClientNameSize, "CNCLNTNM" );
if ( ClientName == NULL ) {
WinStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
if ( !LookupAccountSidW(
NULL,
pAccountSid,
ClientName,
&ClientNameSize,
DomainName,
&DomainNameSize,
&SidType ) ) {
WinStatus = GetLastError();
AzPrint(( AZD_CRITICAL, "AzInitializeContextFromStringSid: LookupAccountSid failed with %ld\n", WinStatus ));
goto Cleanup;
}
//
// We only support SidTypeUser account type.
//
if ( SidType != SidTypeUser ) {
WinStatus = ERROR_INVALID_PARAMETER;
AzPrint(( AZD_CRITICAL, "AzInitializeContextFromStringSid: Invalid user type - expected SIdTypeUser, got %ld\n", SidType ));
goto Cleanup;
}
} else {
//
// This can not happen since we supplied a NULL buffer.
//
ASSERT( FALSE );
WinStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
//
// hand the call to InitializeContextFromName
//
WinStatus = AzInitializeContextFromName(
ApplicationHandle,
DomainName,
ClientName,
0,
ClientContextHandle
);
//
// the client context is handled by FromName
// we are done now so go to clean up
//
goto Cleanup;
}
//
// when it's get here, the client context will be initialzied from SID
// and ldap group check will be skipped later on during access check
//
// Call the common routine to create our client context object
//
WinStatus = ObCommonCreateObject(
(PGENERIC_OBJECT) ApplicationHandle,
OBJECT_TYPE_APPLICATION,
&(((PAZP_APPLICATION)ApplicationHandle)->ClientContexts),
OBJECT_TYPE_CLIENT_CONTEXT,
NULL,
0,
(PGENERIC_OBJECT *) &ClientContext );
if ( WinStatus != NO_ERROR ) {
AzPrint(( AZD_CRITICAL, "AzInitializeContextFromStringSid: Cannot ObCommonCreateObject %ld\n", WinStatus ));
goto Cleanup;
}
//
// Client context isn't persisted.
//
ASSERT( ClientContext->GenericObject.DirtyBits == 0);
//
// Allocate a LUID to represent out client. We need this for linking
// audits.
//
Status = NtAllocateLocallyUniqueId(&ClientContext->LogonId);
if ( !NT_SUCCESS( Status )) {
WinStatus = RtlNtStatusToDosError( Status );
AzPrint(( AZD_CRITICAL, "AzInitializeContextFromStringSid: Cannot allocate LUID %ld\n", WinStatus ));
goto Cleanup;
}
ClientContext->CreationType = AZP_CONTEXT_CREATED_FROM_SID;
//
// copy the SID to sid buffer
//
if ( !CopySid (
SECURITY_MAX_SID_SIZE,
(PSID)(ClientContext->SidBuffer),
pAccountSid
) ) {
WinStatus = GetLastError();
AzPrint(( AZD_CRITICAL, "AzInitializeContextFromStringSid: CopySid failed with %ld\n", WinStatus ));
goto Cleanup;
}
//
// save the SID string in the client name buffer (for auditing purpose)
//
ClientNameSize = (DWORD)wcslen(SidString);
ClientContext->ClientName = (LPWSTR) AzpAllocateHeap( (ClientNameSize+1)*sizeof(WCHAR), "CNCLNTNM" );
if ( ClientContext->ClientName == NULL ) {
WinStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
wcsncpy(ClientContext->ClientName, SidString, ClientNameSize);
ClientContext->ClientName[ClientNameSize] = L'\0';
//
// Initialize Authz
//
if ( !AuthzInitializeContextFromSid(
AUTHZ_SKIP_TOKEN_GROUPS,
ClientContext->SidBuffer,
(((PAZP_APPLICATION)ApplicationHandle)->AuthzResourceManager),
NULL, // No expiration time
Identifier,
NULL, // No dynamic group args
&ClientContext->AuthzClientContext ) ) {
WinStatus = GetLastError();
AzPrint(( AZD_CRITICAL, "AzInitializeContextFromToken: Cannot AuthzInitializeContextFromToken %ld\n", WinStatus ));
goto Cleanup;
}
//
// Generate a client context creation audit based on the audit boolean.
//
if ( ((PAZP_APPLICATION) ApplicationHandle)->GenericObject.IsGeneratingAudits ) {
WinStatus = AzpClientContextGenerateCreateSuccessAudit( ClientContext,
(PAZP_APPLICATION) ApplicationHandle );
if ( WinStatus != NO_ERROR ) {
AzPrint(( AZD_ACCESS, "AzpGenerateContextCreateAudit: Cannot ObCommonCreateObject %ld\n", WinStatus ));
goto Cleanup;
}
}
WinStatus = NO_ERROR;
*ClientContextHandle = ClientContext;
ClientContext = NULL;
//
// Free any local resources
//
Cleanup:
if ( ClientContext != NULL ) {
AzCloseHandle( ClientContext, 0 );
}
if ( pAccountSid ) {
LocalFree(pAccountSid);
}
if ( DomainName ) {
AzpFreeHeap( DomainName );
}
if ( ClientName ) {
AzpFreeHeap( ClientName );
}
return WinStatus;
}
DWORD
WINAPI
AzContextAccessCheck(
IN AZ_HANDLE ApplicationObjectHandle,
IN DWORD ApplicationSequenceNumber,
IN AZ_HANDLE ClientContextHandle,
IN LPCWSTR ObjectName,
IN ULONG ScopeCount,
IN LPCWSTR * ScopeNames OPTIONAL,
IN ULONG OperationCount,
IN PLONG Operations,
OUT ULONG *Results,
OUT LPWSTR *BusinessRuleString OPTIONAL,
IN VARIANT *ParameterNames OPTIONAL,
IN VARIANT *ParameterValues OPTIONAL,
IN VARIANT *InterfaceNames OPTIONAL,
IN VARIANT *InterfaceFlags OPTIONAL,
IN VARIANT *Interfaces OPTIONAL
)
/*++
Routine Description:
The application calls the AccessCheck function whenever it wants to check if a
particular operation is allowed to the client.
The specified Operation may require application group membership to be evaluated.
The application group membership is added to the client context so that it need not
be evaluated again on subsequent AccessCheck calls on this same client context.
The AccessCheck method may not be called by a BizRule.
The AccessCheck function does not directly support the concept of "maximum allowed".
If the script engine timeout is set to 0, then biz rules evaluated as returning FALSE.
Arguments:
ApplicationObjectHandle - Handle to the parent application object of the client context object
ApplicationSequenceNumber - Sequence number on the parent application object
ClientContextHandle - Specifies a handle to the client context object indentifying
the client.
ObjectName - Specifies the object being accessed. (Meant for auditing purposes)
ScopeCount - Specifies the number of elements in the ScopeNames array.
Some application do not create scopes, and treat al objects as if in the
first scope. 0 is passed in if there are no scopes defined
ScopeNames - Specifies an optional list of scope names. Must be
NULL if ScopeCount is 0. ERROR_INVALID_PARAMETER is returned otherwise.
OperationCount - Specifies the number of elements in the Operations array.
Operations - Specifies which operations to check access for. Each element
is the Operation Id of an operation in the AzApplication policy.
An operation should apear only once in this array. If not, the first occurrence
in the matching Results array will have the result set correctly. The
value in the Results array is undefined for the other occurrences.
Results - Returns an array of results of the access check. Each element in
this array corresponds to the corresponding element in the Operations array.
A value of NO_ERROR indicates that the access is granted.
A value of ERROR_ACCESS_DENIED indicates that the access is not granted.
Other values indicate that access should not be granted and describes the
reason. For instance, permission to perform this operation may have required
the evaluation of an LDAP_QUERY group and the domain controller may have been
unavailable.
BusinessRuleString - Returns the string set by any BizRules via the
SetBusinessRuleReturnString method.
Any returned sting must be freed by calling AzFreeMemory.
ParameterNames - Specifies an array of names of the parameters.
There are ParameterCount elements in the array. The name defines a way for the
BizRule script to reference the corresponding element in the ParameterValues array.
See IAzBizRuleContext::GetParameter for more information.
This parameter may be NULL. This parameter may be a pointer to an empty variant. This
parameter may be a pointer to a single dimension safe array variant where each element is a
BSTR.
ParameterValues - Specifies an array of values of the parameters. Each element in this array
contains the value of the parameter named by the corresponding element in ParameterNames.
Each element in this array may be of any type.
InterfaceNames - Specifies an array of names. Each element specifies the name that
the interface will be known by in the script. AccessCheck will call the
IActiveScript::AddNamedItem method for each name.
InterfaceFlags - Specifies an array of flags that will be passed to
IActiveScript::AddNamedItem. Each element matches the corresponding element
in the InterfaceNames array.
Interfaces - Specifies an array of IUnknown interfaces. These interfaces will be
made available to the BizRule script. In addition, these interfaces should
support the IActiveScriptSite interface. During BizRule script evaluation,
anytime the script host engine calls the IActiveScriptSite interface supplied
by AccessCheck, AccessCheck will forward those calls to the IActiveScriptSite
interface specified here. For instance, AccessCheck calls the application's
IActiveScriptSite::GetItemInfo in AccessChecks GetItemInfo routine if the
ItemName string passed to me was an InterfaceNames from above.
Each element matches the corresponding element in the InterfaceNames array.
Return Value:
Returns the status of the function. Does not indicate where the operation is permitted. NO_ERROR is returned whether permission is granted or not.
NO_ERROR - The operation was successful
ERROR_INVALID_PARAMETER - If ScopeCount is 0, but there are ScopeName present
ERROR_NOT_ENOUGH_MEMORY - not enough memory
ERROR_NOT_FOUND - One of the ScopeNames or Operations is not defined in the
AZ policy database.
OLESCRIPT_E_SYNTAX - The syntax of the bizrule is invalid
Other status codes
--*/
{
DWORD WinStatus;
ULONG i;
BOOLEAN CritSectLocked = FALSE;
ACCESS_CHECK_CONTEXT AcContext;
ULONG LocalIndex;
BOOLEAN LocalOnly;
BOOLEAN BizrulesOk;
LPBYTE AllocatedBuffer = NULL;
PULONG TempAllocatedMemory;
ULONG MemoryRequired;
ULONG TaskInfoArraySize;
ULONG RoleIndex;
PAZP_ROLE Role;
PAZ_ROLE_INFO RoleInfo;
ULONG RoleListIndex;
#define ROLE_LIST_COUNT 2
PGENERIC_OBJECT_HEAD RoleLists[ROLE_LIST_COUNT];
ULONG RoleListCount;
ULONG ProcessedRoleCount = 0;
BOOLEAN CallAgain;
//
// Initialization
//
RtlZeroMemory( &AcContext, sizeof( AcContext ) );
//
// Grab the locks needed.
//
// Note, that even though the global lock is grabbed here, it is dropped during
// BizRule and LDAP Queries. By ensuring that the global lock isn't held while
// going off-machine, the policy cache can be updated via the change notification
// or the AzUpdateCache mechanisms.
//
//
if ( ApplicationSequenceNumber != AzpRetrieveApplicationSequenceNumber( ApplicationObjectHandle ) ) {
WinStatus = ERROR_INVALID_HANDLE;
goto Cleanup;
}
AzpLockResourceShared( &AzGlResource );
//
// validate that the ScopeCount and ScopeNames match
//
if ( ScopeCount == 0 &&
ScopeNames != NULL ) {
WinStatus = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
//
// Validate the passed in handle
//
WinStatus = ObReferenceObjectByHandle( (PGENERIC_OBJECT)ClientContextHandle,
FALSE, // Don't allow deleted objects
FALSE, // No cache to refresh
OBJECT_TYPE_CLIENT_CONTEXT );
if ( WinStatus != NO_ERROR ) {
goto Cleanup;
}
AcContext.ClientContext = (PAZP_CLIENT_CONTEXT) ClientContextHandle;
AcContext.Application = (PAZP_APPLICATION) ParentOfChild( &AcContext.ClientContext->GenericObject );
//
// Grab the lock on the client context (observing locking order)
//
// This locking order allows the context crit sect to be locked for the life of the
// access check call and for the global lock to be periodically dropped when we
// go off-machine.
//
AzpUnlockResource( &AzGlResource );
SafeEnterCriticalSection( &AcContext.ClientContext->CritSect );
CritSectLocked = TRUE;
AzpLockResourceShared( &AzGlResource );
//
// If group memberships have changed,
// flush the cache.
//
// This code doesn't prevent the group membership from changing *during* the access check
// call. That's fine. It does protect against changes made prior to the access check call.
//
if ( AcContext.ClientContext->GroupEvalSerialNumber !=
AcContext.ClientContext->GenericObject.AzStoreObject->GroupEvalSerialNumber ) {
AzpFlushGroupEval( AcContext.ClientContext );
//
// Update the serial number to the new serial number
//
AzPrint(( AZD_ACCESS_MORE, "AzAccessCheck: %ws: GroupEvalSerialNumber changed from %ld to %ld\n",
AcContext.ObjectNameString.String,
AcContext.ClientContext->GroupEvalSerialNumber,
AcContext.ClientContext->GenericObject.AzStoreObject->GroupEvalSerialNumber ));
AcContext.ClientContext->GroupEvalSerialNumber =
AcContext.ClientContext->GenericObject.AzStoreObject->GroupEvalSerialNumber;
}
//
// Capture the object name
//
WinStatus = AzpCaptureString( &AcContext.ObjectNameString,
ObjectName,
AZ_MAX_SCOPE_NAME_LENGTH,
FALSE ); // NULL not ok
if ( WinStatus != NO_ERROR ) {
goto Cleanup;
}
//
// Capture the scopes
//
if ( ScopeCount > 0 ) {
WinStatus = AzpCaptureString( &AcContext.ScopeNameString,
*ScopeNames,
AZ_MAX_SCOPE_NAME_LENGTH,
FALSE ); // NULL not ok
if ( WinStatus != NO_ERROR ) {
goto Cleanup;
}
WinStatus = ObReferenceObjectByName( &AcContext.Application->Scopes,
&AcContext.ScopeNameString,
AZP_FLAGS_REFRESH_CACHE,
(PGENERIC_OBJECT *)&AcContext.Scope );
if ( WinStatus != NO_ERROR ) {
//
// if a scope cannot be found, ObReferenceObjectByName will
// return ERROR_NOT_FOUND. We need to mask it to more specific error
// code before returning to the caller
//
if ( WinStatus == ERROR_NOT_FOUND ) {
WinStatus = ERROR_SCOPE_NOT_FOUND;
}
goto Cleanup;
}
//
// If the scope referenced does not have its children loaded into cache,
// then do so now. However, if the scope has not been submitted, then neither
// have its children, and they are already in cache
//
if ( !((PGENERIC_OBJECT)(AcContext.Scope))->AreChildrenLoaded ) {
//
// grab the resource lock exclusively
//
AzpLockResourceSharedToExclusive( &AzGlResource );
WinStatus = AzPersistUpdateChildrenCache(
(PGENERIC_OBJECT)AcContext.Scope
);
AzpLockResourceExclusiveToShared( &AzGlResource );
if ( WinStatus != NO_ERROR ) {
AzPrint(( AZD_ACCESS_MORE,
"AzAccessCheck: Failed to load children for %ws: %ld",
((PGENERIC_OBJECT)AcContext.Scope)->ObjectName,
WinStatus
));
goto Cleanup;
}
}
} else {
AzpInitString( &AcContext.ScopeNameString, NULL );
}
//
// Capture the Operations
//
if ( OperationCount == 0 ) {
AzPrint(( AZD_INVPARM, "AzAccessCheck: %ws: invalid OperationCount %ld\n", AcContext.ObjectNameString.String, OperationCount ));
WinStatus = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
SafeAllocaAllocate( AcContext.OperationObjects,
OperationCount*(sizeof(PAZP_OPERATION) + sizeof(BOOLEAN)) );
if ( AcContext.OperationObjects == NULL ) {
WinStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
RtlZeroMemory( AcContext.OperationObjects,
OperationCount*(sizeof(PAZP_OPERATION) + sizeof(BOOLEAN)) );
AcContext.OperationWasProcessed = (PBOOLEAN)(&AcContext.OperationObjects[OperationCount]);
AcContext.OperationCount = OperationCount;
AcContext.Results = Results;
for ( i=0; i<AcContext.OperationCount; i++ ) {
//
// The operation isn't allowed until proven otherwise
//
Results[i] = NOT_YET_DONE;
//
// Find the specific operation
//
WinStatus = AzpReferenceOperationByOpId( AcContext.Application,
Operations[i],
TRUE, // Refresh the cache if it needs it
&AcContext.OperationObjects[i] );
if ( WinStatus != NO_ERROR ) {
//
// if an operation cannot be found, AzpReferenceOperationByOpId will
// return ERROR_NOT_FOUND. We need to mask it to more specific error
// code before returning to the caller
//
if ( WinStatus == ERROR_NOT_FOUND ) {
WinStatus = ERROR_INVALID_OPERATION;
}
goto Cleanup;
}
}
//
// Capture the Bizrule parameters
//
if ( BusinessRuleString != NULL ) {
*BusinessRuleString = NULL;
}
WinStatus = AzpCaptureBizRuleParameters( &AcContext, ParameterNames, ParameterValues );
if ( WinStatus != NO_ERROR ) {
goto Cleanup;
}
//
// Allocate an array to hold the list of used paramaters
//
if ( AcContext.ParameterCount != 0 ) {
// Allocate the array on the stack
SafeAllocaAllocate( AcContext.UsedParameters, AcContext.ParameterCount * sizeof(*AcContext.UsedParameters) );
if ( AcContext.UsedParameters == NULL ) {
WinStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
RtlZeroMemory( AcContext.UsedParameters, AcContext.ParameterCount * sizeof(*AcContext.UsedParameters) );
}
//
// Capture the Interfaces parameters
//
WinStatus = AzpCaptureBizRuleInterfaces( &AcContext, InterfaceNames, InterfaceFlags, Interfaces );
if ( WinStatus != NO_ERROR ) {
goto Cleanup;
}
//
// Determine if we've already cached the result of this access check
//
if ( AzpCheckOperationCache( &AcContext ) ) {
// Entire access check was satisfied from cache
WinStatus = NO_ERROR;
goto Cleanup;
}
//
// Compute the roles that apply to the scope
//
// There are two such lists: the roles that are children of the scope, and
// the roles that are children of the application the scope is in.
//
// If there was no scope specified, the just the roles for the application
// are set
//
RoleLists[0] = &AcContext.Application->Roles;
AcContext.RoleCount = RoleLists[0]->ObjectCount;
RoleListCount = 1;
if ( ScopeCount > 0 ) {
RoleLists[RoleListCount] = &AcContext.Scope->Roles;
AcContext.RoleCount += RoleLists[RoleListCount]->ObjectCount;
RoleListCount++;
}
//
// If there are no applicable roles,
// access is denied to all operations
//
if ( AcContext.RoleCount == 0 ) {
WinStatus = NO_ERROR;
AzPrint(( AZD_ACCESS_MORE, "AzAccessCheck: %ws: %ws: There are no roles for this scope\n", AcContext.ObjectNameString.String,
AcContext.ScopeNameString.String ? AcContext.ScopeNameString.String : L""));
goto Cleanup;
}
//
// Capture the roles.
// Create references so we can drop AzGlResource when we hit the wire
//
SafeAllocaAllocate( AcContext.RoleInfo, AcContext.RoleCount*sizeof(*AcContext.RoleInfo) );
if ( AcContext.RoleInfo == NULL ) {
WinStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
RoleIndex = 0;
MemoryRequired = 0;
TaskInfoArraySize = 0;
//
// boolean to indicate if the requested role is already found
//
BOOL bFoundRole=FALSE;
for ( RoleListIndex=0; RoleListIndex<RoleListCount; RoleListIndex++ ) {
PLIST_ENTRY ListEntry;
for ( ListEntry = RoleLists[RoleListIndex]->Head.Flink ;
ListEntry != &RoleLists[RoleListIndex]->Head ;
ListEntry = ListEntry->Flink ,
RoleIndex++ ) {
PGENERIC_OBJECT GenericObject;
//
// Grab a pointer to the next role to process
//
RoleInfo = &AcContext.RoleInfo[RoleIndex];
GenericObject = CONTAINING_RECORD( ListEntry,
GENERIC_OBJECT,
Next );
ASSERT( GenericObject->ObjectType == OBJECT_TYPE_ROLE );
//
// Grab a reference
//
InterlockedIncrement( &GenericObject->ReferenceCount );
AzpDumpGoRef( "Role reference", GenericObject );
Role = (PAZP_ROLE) GenericObject;
//
// Initialize the RoleInfo entry
//
RoleInfo = &AcContext.RoleInfo[RoleIndex];
RoleInfo->Role = Role;
RoleInfo->RoleProcessed = FALSE;
RoleInfo->SidsProcessed = FALSE;
RoleInfo->ResultStatus = NOT_YET_DONE;
//
// if a role is specified in the client context
// only perform access check on this role
//
if ( AcContext.ClientContext->RoleName.StringSize != 0 &&
( bFoundRole ||
!AzpEqualStrings( &(Role->GenericObject.ObjectName->ObjectName),
&AcContext.ClientContext->RoleName ) ) ) {
//
// this role is not the one, skip it
//
AzPrint(( AZD_ACCESS_MORE, "AzAccessCheck: This role %ws is not the requested role %ws \n", Role->GenericObject.ObjectName->ObjectName.String, AcContext.ClientContext->RoleName.String ));
RoleInfo->RoleProcessed = TRUE;
ProcessedRoleCount++;
} else {
//
// either this is the requested role, or
// we need to check for all roles
//
//
// Walk the list of operations and tasks so see which are applicable
// This is pass 1 which only determines the amount of memory needed
//
// Determine if this role has any operations that apply to this access check
//
WinStatus = AzpWalkTaskTree( &AcContext,
&Role->Operations,
&Role->Tasks,
0, // RecursionLevel
NULL, // No allocated memory, yet
&MemoryRequired,
&TaskInfoArraySize,
NULL, // No OpsAndTasks to fill in
&CallAgain );
if ( WinStatus != NO_ERROR ) {
goto Cleanup;
}
//
// If no applicable operations were found anywhere,
// ditch this task.
//
if ( !CallAgain ) {
AzPrint(( AZD_ACCESS_MORE, "AzAccessCheck: %ws: %ws: No operations for this role apply\n", AcContext.ObjectNameString.String, Role->GenericObject.ObjectName->ObjectName.String ));
RoleInfo->RoleProcessed = TRUE;
ProcessedRoleCount++;
}
//
// remember that the requested role is already found
//
bFoundRole = TRUE;
}
}
}
ASSERT( AcContext.RoleCount == RoleIndex );
//
// If we've processed all of the roles,
// we're done.
//
if ( ProcessedRoleCount == AcContext.RoleCount ) {
AzPrint(( AZD_ACCESS_MORE, "AzAccessCheck: %ws: %ws: No roles have applicable operations %ld.\n", AcContext.ObjectNameString.String,
AcContext.ScopeNameString.String ? AcContext.ScopeNameString.String : L"", AcContext.RoleCount ));
WinStatus = NO_ERROR;
goto Cleanup;
}
//
// Allocate a buffer for the task info and index arrays
//
ASSERT( (MemoryRequired+TaskInfoArraySize) != 0 );
SafeAllocaAllocate( AllocatedBuffer, MemoryRequired+TaskInfoArraySize );
if ( AllocatedBuffer == NULL ) {
WinStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
// AzPrint(( AZD_ACCESS_MORE, "Allocated: 0x%lx (0x%lx)\n", AllocatedBuffer, MemoryRequired+TaskInfoArraySize ));
AcContext.TaskInfo = (PAZ_TASK_INFO) AllocatedBuffer;
TempAllocatedMemory = (PULONG)(AllocatedBuffer + TaskInfoArraySize);
//
// Walk the list of operations and tasks so see which are applicable
// This is pass 2 which actually initializes the data structures
//
MemoryRequired = 0;
TaskInfoArraySize = 0;
for ( RoleIndex=0; RoleIndex<AcContext.RoleCount; RoleIndex++ ) {
//
// Grab a pointer to the next role to process
//
RoleInfo = &AcContext.RoleInfo[RoleIndex];
Role = RoleInfo->Role;
if ( RoleInfo->RoleProcessed ) {
continue;
}
//
// Determine if this role has any operations that apply to this access check
//
WinStatus = AzpWalkTaskTree( &AcContext,
&Role->Operations,
&Role->Tasks,
0, // RecursionLevel
&TempAllocatedMemory,
&MemoryRequired,
&TaskInfoArraySize,
&RoleInfo->OpsAndTasks,
&CallAgain );
if ( WinStatus != NO_ERROR ) {
goto Cleanup;
}
ASSERT( CallAgain );
if ( !CallAgain ) {
RoleInfo->RoleProcessed = TRUE;
ProcessedRoleCount++;
}
}
AzPrint(( AZD_ACCESS_MORE,
"AzContextAccessCheck: Starting Access Check loops\n"
));
//
// Loop through the roles several times. Each time be more willing to go off-machine.
//
// There are 3 iterations of this loop
// 0: Local only, no bizrules.
// 1: Local only, bizrules. If script engine timeout is set to 0, then skip this iteration.
// 2: remote ok, bizrules. If script engine timeout is set to 0, bizrules are not evaluated.
//
for ( LocalIndex=0 ; LocalIndex<3; LocalIndex++ ) {
LocalOnly = (LocalIndex < 2);
//
// If script engine timeout is set to 0, skip the 2nd iteration
//
if ( (LocalIndex == 1) &&
((AcContext.ClientContext)->GenericObject).AzStoreObject->ScriptEngineTimeout == 0 ) {
continue;
}
BizrulesOk = (LocalIndex > 0) &&
(((AcContext.ClientContext)->GenericObject).AzStoreObject->ScriptEngineTimeout != 0);
//
// If we've processed all of the roles,
// we're done.
//
if ( ProcessedRoleCount == AcContext.RoleCount ) {
AzPrint(( AZD_ACCESS_MORE, "AzAccessCheck: %ws: %ws: All roles have been processed %ld.\n", AcContext.ObjectNameString.String,
AcContext.ScopeNameString.String ? AcContext.ScopeNameString.String : L"", AcContext.RoleCount ));
break;
}
//
// Loop through the list of roles
//
for ( RoleIndex=0; RoleIndex<AcContext.RoleCount; RoleIndex++ ) {
// BOOLEAN FoundOperation;
ULONG AppMemberStatus;
BOOLEAN IsMember;
//
// Grab a pointer to the next role to process
//
RoleInfo = &AcContext.RoleInfo[RoleIndex];
Role = RoleInfo->Role;
//
// If we've processed all of the operations,
// we're done.
//
if ( AcContext.ProcessedOperationCount == AcContext.OperationCount ) {
AzPrint(( AZD_ACCESS_MORE, "AzAccessCheck: %ws: %ws: All operations have been processed %ld.\n", AcContext.ObjectNameString.String,
AcContext.ScopeNameString.String ? AcContext.ScopeNameString.String : L"", AcContext.OperationCount ));
break;
}
//
// If we've already processed this role,
// don't do it again.
//
if ( RoleInfo->RoleProcessed ) {
continue;
}
//
// Be verbose
//
AzPrint(( AZD_ACCESS_MORE, "AzAccessCheck: %ws: %ws: Process role\n", AcContext.ObjectNameString.String, Role->GenericObject.ObjectName->ObjectName.String ));
//
// Ensure the role still references at least one operation that hasn't previously
// been granted access.
//
// This check is cheaper than processing group membership or bizrules.
//
if ( AcContext.ProcessedOperationCount != 0 ) {
WinStatus = AzpWalkOpsAndTasks( &AcContext,
&Role->GenericObject.ObjectName->ObjectName,
&RoleInfo->OpsAndTasks,
WeedOutFinishedOps,
ERROR_INTERNAL_ERROR, // Not used for this opcode
BizrulesOk,
&CallAgain );
if ( WinStatus != NO_ERROR ) {
AzPrint(( AZD_ACCESS_MORE, "AzAccessCheck: %ws: %ws: AzpWalkOpsAndTasks failed %ld\n", AcContext.ObjectNameString.String, Role->GenericObject.ObjectName->ObjectName.String, WinStatus ));
goto Cleanup;
}
//
// If no operations from this role apply,
// ignore the entire role.
//
if ( !CallAgain ) {
// Mark this role as having been processed
RoleInfo->RoleProcessed = TRUE;
ProcessedRoleCount ++;
AzPrint(( AZD_ACCESS_MORE, "AzAccessCheck: %ws: %ws: No operations for this role apply\n", AcContext.ObjectNameString.String, Role->GenericObject.ObjectName->ObjectName.String ));
continue;
}
}
//
// Check the group memberhsip of the role if we haven't done so already
//
if ( RoleInfo->ResultStatus == NOT_YET_DONE ) {
//
// Check the NT Group membership in the role
// NT Group membership is always evaluated locally
//
if ( !RoleInfo->SidsProcessed ) {
AzPrint(( AZD_ACCESS_MORE, "AzAccessCheck: %ws: %ws: CheckSidMembership of role\n", AcContext.ObjectNameString.String, Role->GenericObject.ObjectName->ObjectName.String ));
WinStatus = AzpCheckSidMembership(
AcContext.ClientContext,
&Role->SidMembers,
&IsMember );
if ( WinStatus != NO_ERROR ) {
AzPrint(( AZD_ACCESS_MORE, "AzAccessCheck: %ws: %ws: CheckSidMembership failed %ld\n", AcContext.ObjectNameString.String, Role->GenericObject.ObjectName->ObjectName.String, WinStatus ));
goto Cleanup;
}
AzPrint(( AZD_ACCESS_MORE, "AzAccessCheck: %ws: %ws: CheckSidMembership is %ld\n", AcContext.ObjectNameString.String, Role->GenericObject.ObjectName->ObjectName.String, IsMember ));
// Convert the membership to a status code
RoleInfo->ResultStatus = IsMember ? NO_ERROR : NOT_YET_DONE;
RoleInfo->SidsProcessed = TRUE;
}
//
// If we couldn't determine membership based on SIDs,
// try via app groups
//
if ( RoleInfo->ResultStatus == NOT_YET_DONE ) {
//
// Check the app group membership
//
// *** Note: this routine will temporarily drop AzGlResource in cases where it hits the wire
//
AzPrint(( AZD_ACCESS_MORE, "AzAccessCheck: %ws: %ws: CheckGroupMembership of role\n", AcContext.ObjectNameString.String, Role->GenericObject.ObjectName->ObjectName.String ));
WinStatus = AzpCheckGroupMembership(
AcContext.ClientContext,
&Role->AppMembers,
LocalOnly,
0, // No recursion yet
&IsMember,
&AppMemberStatus );
if ( WinStatus != NO_ERROR ) {
AzPrint(( AZD_ACCESS_MORE, "AzAccessCheck: %ws: %ws: CheckGroupMembership failed %ld\n", AcContext.ObjectNameString.String, Role->GenericObject.ObjectName->ObjectName.String, WinStatus ));
goto Cleanup;
}
//
// If we couldn't determine membership,
// remember the status to return to our caller.
//
if ( AppMemberStatus != NO_ERROR ) {
RoleInfo->ResultStatus = AppMemberStatus;
AzPrint(( AZD_ACCESS_MORE, "AzAccessCheck: %ws: %ws: CheckGroupMembership extended status %ld\n", AcContext.ObjectNameString.String, Role->GenericObject.ObjectName->ObjectName.String, AppMemberStatus ));
} else {
// Convert the membership to a status code
RoleInfo->ResultStatus = IsMember ? NO_ERROR : ERROR_ACCESS_DENIED;
AzPrint(( AZD_ACCESS_MORE, "AzAccessCheck: %ws: %ws: CheckGroupMembership is %ld\n", AcContext.ObjectNameString.String, Role->GenericObject.ObjectName->ObjectName.String, IsMember ));
}
}
}
//
// If we've found an answer,
// update the results array.
//
if ( RoleInfo->ResultStatus != NOT_YET_DONE ) {
//
// ASSERT: We have a ResultStatus for this Role
//
// Set the ResultStatus in the Results array for every operation granted
// by this role.
//
// *** Note: this routine will temporarily drop AzGlResource in cases where it hits the wire
//
WinStatus = AzpWalkOpsAndTasks( &AcContext,
&Role->GenericObject.ObjectName->ObjectName,
&RoleInfo->OpsAndTasks,
SetResultStatus,
RoleInfo->ResultStatus,
BizrulesOk,
&CallAgain );
if ( WinStatus != NO_ERROR ) {
AzPrint(( AZD_ACCESS_MORE, "AzAccessCheck: %ws: %ws: AzpWalkOpsAndTasks failed %ld\n", AcContext.ObjectNameString.String, Role->GenericObject.ObjectName->ObjectName.String, WinStatus ));
goto Cleanup;
}
//
// If no operations from this role apply,
// ignore the entire role.
//
if ( !CallAgain ) {
// Mark this role as having been processed
RoleInfo->RoleProcessed = TRUE;
ProcessedRoleCount ++;
AzPrint(( AZD_ACCESS_MORE, "AzAccessCheck: %ws: %ws: Role finished being processed\n", AcContext.ObjectNameString.String, Role->GenericObject.ObjectName->ObjectName.String ));
continue;
}
//
// AzpWalkOpsAndTasks says CallAgain for ERROR_ACCESS_DENIED hoping for a NO_ERROR.
// That won't happen for a Role since the ResultStatus won't change.
//
if ( RoleInfo->ResultStatus == ERROR_ACCESS_DENIED &&
BizrulesOk ) {
// Mark this role as having been processed
RoleInfo->RoleProcessed = TRUE;
ProcessedRoleCount ++;
AzPrint(( AZD_ACCESS_MORE, "AzAccessCheck: %ws: %ws: Role finished being processed due to ACCESS_DENIED\n", AcContext.ObjectNameString.String, Role->GenericObject.ObjectName->ObjectName.String ));
continue;
}
}
}
}
WinStatus = NO_ERROR;
//
// Free any local resources
//
Cleanup:
//
// If access wasn't granted,
// process the results array to ensure the best status possible is returned
//
if ( WinStatus == NO_ERROR &&
AcContext.OperationCount != AcContext.ProcessedOperationCount ) {
for ( i=0; i<AcContext.OperationCount; i++ ) {
//
// Convert unknown status to a definitive access denied status
//
switch ( Results[i] ) {
case NOT_YET_DONE:
Results[i] = ERROR_ACCESS_DENIED;
break;
//
// A definitive status should be left alone
//
case NO_ERROR:
case ERROR_ACCESS_DENIED:
break;
//
// All other status codes should fail the access check API and not just
// deny access.
//
// Other status codes are temporarily put in the results array in hopes
// that a better status will be found via later processing.
//
// The only known status code is ERROR_NO_SUCH_DOMAIN.
//
default:
ASSERT( FALSE );
case ERROR_NO_SUCH_DOMAIN:
WinStatus = Results[i];
break;
}
}
}
//
// Generate the access check audits if the audit boolean is set to TRUE.
//
if ( WinStatus == NO_ERROR ) {
if ( AcContext.Application->GenericObject.IsGeneratingAudits &&
!AzpOpenToManageStore(AcContext.ClientContext->GenericObject.AzStoreObject) ) {
WinStatus = AzpAccessCheckGenerateAudit( &AcContext );
if ( WinStatus != NO_ERROR ) {
AzPrint(( AZD_ACCESS_MORE, "AzAccessCheck: %ws: AzpAccessCheckGenerateAudit failed with %ld\n", AcContext.ObjectNameString.String, WinStatus ));
}
}
}
//
// Update the cache of results
//
if ( WinStatus == NO_ERROR ) {
AzpUpdateOperationCache( &AcContext );
}
//
// Free the operation objects
//
if ( AcContext.OperationObjects != NULL ) {
for ( i=0; i<AcContext.OperationCount; i++ ) {
if ( AcContext.OperationObjects[i] != NULL ) {
ObDereferenceObject( (PGENERIC_OBJECT)AcContext.OperationObjects[i] );
}
}
SafeAllocaFree( AcContext.OperationObjects );
}
//
// Free the role info
//
if ( AcContext.RoleInfo != NULL ) {
for ( RoleIndex=0; RoleIndex<AcContext.RoleCount; RoleIndex++ ) {
ObDereferenceObject( (PGENERIC_OBJECT)AcContext.RoleInfo[RoleIndex].Role );
// SafeAllocaFree( AcContext.RoleInfo[RoleIndex].OpsAndTasks.OpIndexes ); // Part of AllocatedBuffer
}
SafeAllocaFree ( AcContext.RoleInfo );
}
//
// Free the task info
//
if ( AcContext.TaskInfo != NULL ) {
for ( i=0; i<AcContext.TaskCount; i++ ) {
ObDereferenceObject( (PGENERIC_OBJECT)AcContext.TaskInfo[i].Task );
// SafeAllocaFree( AcContext.TaskInfo[i].OpsAndTasks.OpIndexes ); // Part of AllocatedBuffer
}
// SafeAllocaFree ( AcContext.TaskInfo ); // Part of AllocatedBuffer
}
SafeAllocaFree( AllocatedBuffer );
//
// Free sundry other data
//
if ( AcContext.Scope != NULL ) {
ObDereferenceObject( (PGENERIC_OBJECT) AcContext.Scope );
}
AzpFreeString( &AcContext.ObjectNameString );
AzpFreeString( &AcContext.ScopeNameString );
if ( AcContext.ClientContext != NULL ) {
ObDereferenceObject( (PGENERIC_OBJECT)AcContext.ClientContext );
}
if ( AcContext.SaParameterNames != NULL ) {
SafeArrayUnaccessData( AcContext.SaParameterNames );
}
if ( AcContext.SaParameterValues != NULL ) {
SafeArrayUnaccessData( AcContext.SaParameterValues );
}
SafeAllocaFree( AcContext.UsedParameters );
//
// Drop the locks
//
AzpUnlockResource( &AzGlResource );
if ( CritSectLocked ) {
SafeLeaveCriticalSection( &AcContext.ClientContext->CritSect );
}
//
// Return the BusinessRuleString to the caller
//
if ( WinStatus == NO_ERROR && BusinessRuleString != NULL ) {
*BusinessRuleString = AcContext.BusinessRuleString.String;
} else {
AzpFreeString( &AcContext.BusinessRuleString );
}
return WinStatus;
}
DWORD
WINAPI
AzContextGetRoles(
IN AZ_HANDLE ClientContextHandle,
IN LPCWSTR ScopeName OPTIONAL,
OUT LPWSTR **RoleNames,
OUT DWORD *Count
)
/*++
Routine Description:
The application calls the GetRoles function whenever it wants to find the
roles the client belongs to in a specified scope.
If the ScopeName parameter is NULL, Roles applicable at the default Scope
are returned.
Otherwise, only those Roles applicable at the speficied scope (and not
including the roles at the default scope) are returned.
Biz rules re not evaluated since they do not come in the picture.
Arguments:
ClientContextHandle - Specifies a handle to the client context object indentifying
the client.
ScopeName - Specifies the scope name from which the roles are applicable.
NULL scope represents the Application scope.
RoleNames - Returns an array of role names applicable at the given scope.
RoleCount - Returns the number of eleements in the array.
Return Value:
Returns the status of the function.
NO_ERROR - The operation was successful
ERROR_NOT_ENOUGH_MEMORY - not enough memory
ERROR_NOT_FOUND - ScopeName is not defined in the AZ policy database.
Other status codes
--*/
{
DWORD WinStatus = NO_ERROR;
AZP_STRING ScopeNameString = {0};
PAZP_SCOPE Scope = NULL;
BOOLEAN CritSectLocked = FALSE;
PAZP_CLIENT_CONTEXT ClientContext = NULL;
PAZP_APPLICATION Application = NULL;
PGENERIC_OBJECT_HEAD RoleList = NULL;
PAZP_ROLE Role = NULL;
PAZ_ROLE_INFO RoleInfo = NULL;
ULONG RoleCount = 0;
ULONG RoleIndex = 0;
ULONG ProcessedRoleCount = 0;
ULONG ApplicableRoleCount = 0;
ULONG LocalIndex = 0;
BOOLEAN LocalOnly = FALSE;
ULONG Size;
ULONG i;
PLIST_ENTRY ListEntry = NULL;
//
// Initialization
//
*Count = 0;
*RoleNames = NULL;
//
// Grab the locks needed.
//
// Note, that even though the global lock is grabbed here, it is dropped during
// LDAP Queries. By ensuring that the global lock isn't held while
// going off-machine, the policy cache can be updated via the change notification
// or the AzUpdateCache mechanisms.
//
//
AzpLockResourceShared( &AzGlResource );
//
// Validate the passed in handle
//
WinStatus = ObReferenceObjectByHandle( (PGENERIC_OBJECT)ClientContextHandle,
FALSE, // Don't allow deleted objects
FALSE, // No cache to refresh
OBJECT_TYPE_CLIENT_CONTEXT );
if ( WinStatus != NO_ERROR ) {
goto Cleanup;
}
ClientContext = (PAZP_CLIENT_CONTEXT) ClientContextHandle;
Application = (PAZP_APPLICATION) ParentOfChild( &ClientContext->GenericObject );
if (!ARGUMENT_PRESENT(ScopeName)) {
//
// If ScopeName is not provided, we pick the Application Scope as the
// default scope.
//
RoleList = &Application->Roles;
} else {
//
// Capture the ScopeName provided and reference the Scope object.
//
WinStatus = AzpCaptureString( &ScopeNameString,
ScopeName,
AZ_MAX_SCOPE_NAME_LENGTH,
FALSE ); // NULL not ok
if ( WinStatus != NO_ERROR ) {
goto Cleanup;
}
WinStatus = ObReferenceObjectByName( &Application->Scopes,
&ScopeNameString,
AZP_FLAGS_REFRESH_CACHE,
(PGENERIC_OBJECT *)&Scope );
if ( WinStatus != NO_ERROR ) {
//
// if a scope cannot be found, ObReferenceObjectByName will
// return ERROR_NOT_FOUND. We need to mask it to more specific error
// code before returning to the caller
//
if ( WinStatus == ERROR_NOT_FOUND ) {
WinStatus = ERROR_SCOPE_NOT_FOUND;
}
goto Cleanup;
}
RoleList = &Scope->Roles;
}
//
// If there are no applicable roles, return now.
//
RoleCount = RoleList->ObjectCount;
if ( RoleCount == 0 ) {
WinStatus = NO_ERROR;
goto Cleanup;
}
//
// Grab the lock on the client context (observing locking order)
//
// This locking order allows the context crit sect to be locked for the life of the
// access check call and for the global lock to be periodically dropped when we
// go off-machine.
//
AzpUnlockResource( &AzGlResource );
SafeEnterCriticalSection( &ClientContext->CritSect );
CritSectLocked = TRUE;
AzpLockResourceShared( &AzGlResource );
//
// Recheck just in case someone deleted all the roles in our scope when
// we dropped the global lock.
//
RoleCount = RoleList->ObjectCount;
if ( RoleCount == 0 ) {
WinStatus = NO_ERROR;
goto Cleanup;
}
//
// Allocate memory for Role structures.
//
SafeAllocaAllocate( RoleInfo, RoleCount*sizeof(AZ_ROLE_INFO) );
if ( RoleInfo == NULL ) {
WinStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
//
// Grab all the roles, put them into a list and reference them. Now we will
// not lose them if we have to drop the global lock while going off the machine.
//
//
for ( ListEntry = RoleList->Head.Flink ;
ListEntry != &RoleList->Head ;
ListEntry = ListEntry->Flink, RoleIndex++ ) {
PGENERIC_OBJECT GenericObject;
//
// Grab a pointer to the next role to process
//
GenericObject = CONTAINING_RECORD( ListEntry,
GENERIC_OBJECT,
Next );
ASSERT( GenericObject->ObjectType == OBJECT_TYPE_ROLE );
//
// Grab a reference
//
InterlockedIncrement( &GenericObject->ReferenceCount );
AzpDumpGoRef( "Role reference", GenericObject );
Role = (PAZP_ROLE) GenericObject;
//
// Initialize the RoleInfo entry
//
RoleInfo[RoleIndex].Role = Role;
RoleInfo[RoleIndex].RoleProcessed = FALSE;
RoleInfo[RoleIndex].SidsProcessed = FALSE;
RoleInfo[RoleIndex].ResultStatus = NOT_YET_DONE;
}
//
// There are 2 iterations of this loop
// 0: Local only
// 1: remote ok
// Note that we do not evaluate BizRules like AccessCheck does.
//
for ( LocalIndex=0 ; LocalIndex<2; LocalIndex++ ) {
LocalOnly = (LocalIndex < 1);
//
// If we've processed all of the roles,
// we're done.
//
if ( ProcessedRoleCount == RoleCount ) {
break;
}
//
// Loop through the list of roles
//
for ( RoleIndex=0; RoleIndex<RoleCount; RoleIndex++ ) {
ULONG AppMemberStatus = NO_ERROR;
BOOLEAN IsMember = FALSE;
//
// If we've already processed this role,
// don't do it again.
//
if ( RoleInfo[RoleIndex].RoleProcessed ) {
continue;
}
//
// Grab a pointer to the next role to process
//
Role = RoleInfo[RoleIndex].Role;
//
// Check the group memberhsip of the role if we haven't done so already
//
if ( RoleInfo[RoleIndex].ResultStatus == NOT_YET_DONE ) {
//
// Check the NT Group membership in the role
// NT Group membership is always evaluated locally
//
if ( !RoleInfo[RoleIndex].SidsProcessed ) {
AzPrint(( AZD_ACCESS_MORE, "GetRoles: %ws: CheckSidMembership of role\n", Role->GenericObject.ObjectName->ObjectName.String ));
WinStatus = AzpCheckSidMembership(
ClientContext,
&Role->SidMembers,
&IsMember );
if ( WinStatus != NO_ERROR ) {
AzPrint(( AZD_ACCESS_MORE, "GetRoles: %ws: CheckSidMembership failed %ld\n", Role->GenericObject.ObjectName->ObjectName.String, WinStatus ));
goto Cleanup;
}
AzPrint(( AZD_ACCESS_MORE, "GetRoles: %ws: CheckSidMembership is %ld\n", Role->GenericObject.ObjectName->ObjectName.String, IsMember ));
// Convert the membership to a status code
if ( IsMember ) {
RoleInfo[RoleIndex].ResultStatus = NO_ERROR;
ApplicableRoleCount++;
} else {
RoleInfo[RoleIndex].ResultStatus = NOT_YET_DONE;
}
RoleInfo[RoleIndex].SidsProcessed = TRUE;
}
//
// If we couldn't determine membership based on SIDs,
// try via app groups
//
if ( RoleInfo[RoleIndex].ResultStatus == NOT_YET_DONE ) {
//
// Check the app group membership
//
// *** Note: this routine will temporarily drop AzGlResource in cases where it hits the wire
//
AzPrint(( AZD_ACCESS_MORE, "GetRoles: %ws: CheckGroupMembership of role\n", Role->GenericObject.ObjectName->ObjectName.String ));
WinStatus = AzpCheckGroupMembership(
ClientContext,
&Role->AppMembers,
LocalOnly,
0, // No recursion yet
&IsMember,
&AppMemberStatus );
if ( WinStatus != NO_ERROR ) {
AzPrint(( AZD_ACCESS_MORE, "GetRoles: %ws: CheckGroupMembership failed %ld\n", Role->GenericObject.ObjectName->ObjectName.String, WinStatus ));
goto Cleanup;
}
//
// Let check if we could determine membership,
//
if ( AppMemberStatus == NO_ERROR ) {
// Convert the membership to a status code
if ( IsMember ) {
RoleInfo[RoleIndex].ResultStatus = NO_ERROR;
ApplicableRoleCount++;
} else {
RoleInfo[RoleIndex].ResultStatus = ERROR_ACCESS_DENIED;
}
AzPrint(( AZD_ACCESS_MORE, "GetRoles: %ws: CheckGroupMembership is %ld\n", Role->GenericObject.ObjectName->ObjectName.String, IsMember ));
} else if (AppMemberStatus == ERROR_ACCESS_DENIED ||
AppMemberStatus == NOT_YET_DONE) {
//
// These error codes are ok. We did not encounter an
// error.
//
RoleInfo[RoleIndex].ResultStatus = AppMemberStatus;
} else {
//
// We failed to determine membership. The likely error
// code here would be ERROR_NO_SUCH_DOMAIN. We do not
// want to continue.
//
WinStatus = AppMemberStatus;
goto Cleanup;
}
AzPrint(( AZD_ACCESS_MORE, "GetRoles: %ws: CheckGroupMembership extended status %ld\n", Role->GenericObject.ObjectName->ObjectName.String, AppMemberStatus ));
}
}
//
// Mark the role as processed if
// we have determined that the client belongs/does not belong to the role
// OR
// if this is the last iteration
//
//
if ( RoleInfo[RoleIndex].ResultStatus != NOT_YET_DONE || !LocalOnly ) {
// Mark this role as having been processed
RoleInfo[RoleIndex].RoleProcessed = TRUE;
ProcessedRoleCount ++;
AzPrint(( AZD_ACCESS_MORE, "GetRoles: %ws: Role finished being processed \n", Role->GenericObject.ObjectName->ObjectName.String ));
}
}
}
ASSERT( ProcessedRoleCount == RoleCount );
//
// Check that we are returning non zero number of roles.
//
if ( ApplicableRoleCount > 0) {
PUCHAR Current = NULL;
//
// Get the size of the buffer required to return the strings for role names.
//
Size = (sizeof(LPWSTR)) * ApplicableRoleCount;
for ( RoleIndex=0; RoleIndex<RoleCount; RoleIndex++ ) {
// Pick only applicable roles.
if ( RoleInfo[RoleIndex].ResultStatus == NO_ERROR ) {
// Note that the StringSize also includes the NULL character.
Size += RoleInfo[RoleIndex].Role->GenericObject.ObjectName->ObjectName.StringSize;
} else if ( RoleInfo[RoleIndex].ResultStatus != ERROR_ACCESS_DENIED &&
RoleInfo[RoleIndex].ResultStatus != NOT_YET_DONE ) {
WinStatus = RoleInfo[RoleIndex].ResultStatus;
//
// This should not happen. We have taken care to break out early.
//
ASSERT(FALSE);
goto Cleanup;
}
}
//
// Allocate memory required to hold the strings.
//
*RoleNames = (LPWSTR *) AzpAllocateHeap( Size, "GETROLES" );
if ( *RoleNames == NULL ) {
WinStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
//
// Set the number of RoleNames we will return.
//
*Count = ApplicableRoleCount;
//
// Set the current buffer pointer.
//
Current = (PUCHAR) ( (*RoleNames)+ApplicableRoleCount );
//
// Copy all the applicable roles into our buffer.
//
for ( i=0, RoleIndex=0; RoleIndex<RoleCount; RoleIndex++ ) {
if ( RoleInfo[RoleIndex].ResultStatus == NO_ERROR ) {
(*RoleNames)[i] = (LPWSTR) Current;
RtlCopyMemory( Current,
RoleInfo[RoleIndex].Role->GenericObject.ObjectName->ObjectName.String,
RoleInfo[RoleIndex].Role->GenericObject.ObjectName->ObjectName.StringSize );
Current += RoleInfo[RoleIndex].Role->GenericObject.ObjectName->ObjectName.StringSize;
i++;
}
}
}
WinStatus = NO_ERROR;
Cleanup:
//
// Release references to all the object we touched.
//
//
if ( ClientContext != NULL ) {
ObDereferenceObject( (PGENERIC_OBJECT)ClientContext );
}
if ( Scope != NULL ) {
ObDereferenceObject( (PGENERIC_OBJECT)Scope );
}
if ( RoleInfo != NULL ) {
for ( RoleIndex=0; RoleIndex<RoleCount; RoleIndex++ ) {
ObDereferenceObject( (PGENERIC_OBJECT)RoleInfo[RoleIndex].Role );
}
SafeAllocaFree ( RoleInfo );
}
if ( ScopeName != NULL ) {
AzpFreeString( &ScopeNameString );
}
//
// Drop the locks
//
AzpUnlockResource( &AzGlResource );
if ( CritSectLocked ) {
SafeLeaveCriticalSection( &ClientContext->CritSect );
}
return WinStatus;
}
inline BOOL
AzpOpenToManageStore (
IN PAZP_AZSTORE pAzStore
)
/*++
Routine Description:
Detect if the store is opened for manage only
Arguments:
pAzStore - The authorization store object
Return Value:
TRUE if and only if the store's initialization flag has
set the manage only flag.
--*/
{
return (pAzStore->InitializeFlag & AZ_AZSTORE_FLAG_MANAGE_STORE_ONLY);
}