676 lines
18 KiB
C++
676 lines
18 KiB
C++
//+-----------------------------------------------------------------------
|
||
//
|
||
// Microsoft Windows
|
||
//
|
||
// Copyright (c) Microsoft Corporation 2000
|
||
//
|
||
// File: user.cxx
|
||
//
|
||
// Contents: Context manipulation functions
|
||
//
|
||
//
|
||
// History: KDamour 15Mar00 Derrived from NTLM context.cxx
|
||
//
|
||
//------------------------------------------------------------------------
|
||
#include "global.h"
|
||
|
||
// This list contains all of the User Contexts
|
||
LIST_ENTRY l_UserCtxtList;
|
||
|
||
// Lock for access to UserCtxtList
|
||
RTL_CRITICAL_SECTION l_UserCtxtCritSect;
|
||
|
||
|
||
// Indicate if completed Initialization of Credential Handler
|
||
BOOL g_bUserContextInitialized = FALSE;
|
||
|
||
|
||
|
||
//+--------------------------------------------------------------------
|
||
//
|
||
// Function: UserCtxtHandlerInit
|
||
//
|
||
// Synopsis: Initializes the context manager package
|
||
//
|
||
// Arguments: none
|
||
//
|
||
// Returns: NTSTATUS
|
||
//
|
||
// Notes: Called by SpInstanceInit
|
||
//
|
||
//---------------------------------------------------------------------
|
||
NTSTATUS
|
||
UserCtxtHandlerInit(VOID)
|
||
{
|
||
NTSTATUS Status = STATUS_SUCCESS;
|
||
|
||
//
|
||
// Initialize the Context list to be empty.
|
||
//
|
||
|
||
Status = RtlInitializeCriticalSection(&l_UserCtxtCritSect);
|
||
if (!NT_SUCCESS(Status))
|
||
{
|
||
DebugLog((DEB_ERROR, "UserCtxtHandlerInit: Failed to initialize critsec 0x%x\n", Status));
|
||
goto CleanUp;
|
||
}
|
||
|
||
InitializeListHead( &l_UserCtxtList );
|
||
|
||
|
||
// Simple variable test to make sure all initialized;
|
||
g_bUserContextInitialized = TRUE;
|
||
|
||
CleanUp:
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
// Add a Context into the UserMode Context List
|
||
NTSTATUS
|
||
UserCtxtHandlerInsertCred(
|
||
IN PDIGEST_USERCONTEXT pUserContext
|
||
)
|
||
{
|
||
RtlEnterCriticalSection( &l_UserCtxtCritSect );
|
||
DebugLog((DEB_TRACE, "UserCtxtHandlerRelease: (RefCount) UserContextInit linked 0x%x\n", pUserContext->LsaContext));
|
||
InsertHeadList( &l_UserCtxtList, &pUserContext->Next );
|
||
RtlLeaveCriticalSection( &l_UserCtxtCritSect );
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
// Initialize a UserMode Context
|
||
NTSTATUS NTAPI
|
||
UserCtxtInit(
|
||
PDIGEST_USERCONTEXT pContext
|
||
)
|
||
{
|
||
|
||
NTSTATUS Status = STATUS_SUCCESS;
|
||
|
||
DebugLog((DEB_TRACE_FUNC, "UserCtxtInit: Entering\n"));
|
||
ASSERT(pContext);
|
||
|
||
if (!pContext)
|
||
{
|
||
return STATUS_INVALID_PARAMETER;
|
||
}
|
||
|
||
ZeroMemory(pContext, sizeof(DIGEST_USERCONTEXT));
|
||
|
||
DebugLog((DEB_TRACE_FUNC, "UserCtxtInit: Leaving \n"));
|
||
return Status;
|
||
}
|
||
|
||
|
||
// Once done with a context - release the resouces
|
||
NTSTATUS NTAPI
|
||
UserCtxtFree(
|
||
IN PDIGEST_USERCONTEXT pContext
|
||
)
|
||
{
|
||
NTSTATUS Status = STATUS_SUCCESS;
|
||
int i = 0;
|
||
|
||
DebugLog((DEB_TRACE_FUNC, "UserCtxtFree: Entering with LSA context 0x%x\n", pContext->LsaContext));
|
||
ASSERT(pContext);
|
||
ASSERT(pContext->lReferences == 0);
|
||
|
||
if (!pContext)
|
||
{
|
||
return STATUS_INVALID_PARAMETER;
|
||
}
|
||
|
||
if (pContext->ClientTokenHandle)
|
||
{
|
||
NTSTATUS IgnoreStatus;
|
||
IgnoreStatus = NtClose(pContext->ClientTokenHandle);
|
||
// ASSERT (NT_SUCCESS (IgnoreStatus));
|
||
if (!NT_SUCCESS(IgnoreStatus))
|
||
{
|
||
DebugLog((DEB_ERROR, "UserCtxtFree: Could not Close the TokenHandle!!!!\n"));
|
||
}
|
||
pContext->ClientTokenHandle = NULL;
|
||
}
|
||
|
||
if (pContext->hSealCryptKey)
|
||
{
|
||
CryptDestroyKey( pContext->hSealCryptKey );
|
||
pContext->hSealCryptKey = NULL;
|
||
}
|
||
|
||
if (pContext->hUnsealCryptKey)
|
||
{
|
||
CryptDestroyKey( pContext->hUnsealCryptKey );
|
||
pContext->hUnsealCryptKey = NULL;
|
||
}
|
||
|
||
StringFree(&(pContext->strSessionKey));
|
||
UnicodeStringFree(&(pContext->ustrAccountName));
|
||
|
||
//
|
||
// Values utilized in the Initial Digest Auth ChallResponse
|
||
// Can be utilized for defaults in future MakeSignature/VerifySignature
|
||
//
|
||
for (i=0; i < MD5_AUTH_LAST; i++)
|
||
{
|
||
StringFree(&(pContext->strParam[i]));
|
||
}
|
||
|
||
DigestFreeMemory(pContext);
|
||
|
||
DebugLog((DEB_TRACE_FUNC, "UserCtxtFree: Leaving\n"));
|
||
return Status;
|
||
}
|
||
|
||
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine checks to see if the Context is for the specified
|
||
Client Connection, and references the Context if it is valid.
|
||
|
||
The caller may optionally request that the Context be
|
||
removed from the list of valid Contexts - preventing future
|
||
requests from finding this Context.
|
||
|
||
Arguments:
|
||
|
||
ContextHandle - Points to the ContextHandle of the Context
|
||
to be referenced.
|
||
|
||
RemoveContext - This boolean value indicates whether the caller
|
||
wants the Context to be removed from the list
|
||
of Contexts. TRUE indicates the Context is to be removed.
|
||
FALSE indicates the Context is not to be removed.
|
||
|
||
|
||
Return Value:
|
||
|
||
NULL - the Context was not found.
|
||
|
||
Otherwise - returns a pointer to the referenced Context.
|
||
|
||
--*/
|
||
NTSTATUS NTAPI
|
||
UserCtxtHandlerHandleToContext(
|
||
IN ULONG_PTR ContextHandle,
|
||
IN BOOLEAN RemoveContext,
|
||
OUT PDIGEST_USERCONTEXT *ppContext
|
||
)
|
||
{
|
||
NTSTATUS Status = STATUS_SUCCESS;
|
||
PLIST_ENTRY ListEntry = NULL;
|
||
PDIGEST_USERCONTEXT pContext = NULL;
|
||
LONG lReferences = 0;
|
||
|
||
DebugLog((DEB_TRACE_FUNC, "UserCtxtHandlerHandleToContext: Entering\n" ));
|
||
|
||
|
||
//
|
||
// Acquire exclusive access to the Context list
|
||
//
|
||
|
||
RtlEnterCriticalSection( &l_UserCtxtCritSect );
|
||
|
||
//
|
||
// Now walk the list of Contexts looking for a match.
|
||
//
|
||
|
||
for ( ListEntry = l_UserCtxtList.Flink;
|
||
ListEntry != &l_UserCtxtList;
|
||
ListEntry = ListEntry->Flink )
|
||
{
|
||
|
||
pContext = CONTAINING_RECORD( ListEntry, DIGEST_USERCONTEXT, Next );
|
||
|
||
//
|
||
// Found a match ... reference this Context
|
||
// (if the Context is being removed, we would increment
|
||
// and then decrement the reference, so don't bother doing
|
||
// either - since they cancel each other out).
|
||
//
|
||
|
||
DebugLog((DEB_TRACE, "UserCtxtHandlerHandleToContext: Checking context %lx for userctxt %lx\n",
|
||
pContext->LsaContext, ContextHandle ));
|
||
|
||
if (pContext->LsaContext != ContextHandle)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
|
||
if (!RemoveContext)
|
||
{
|
||
lReferences = InterlockedIncrement(&pContext->lReferences);
|
||
}
|
||
else
|
||
{
|
||
DebugLog((DEB_TRACE, "UserCtxtHandlerRelease: (RefCount) UserContextInit delinked 0x%x\n", ContextHandle));
|
||
RemoveEntryList( &pContext->Next );
|
||
}
|
||
|
||
DebugLog((DEB_TRACE, "CtxtHandlerHandleToContext FOUND Context = 0x%x, RemoveContext = %d, ReferenceCount = %ld\n",
|
||
pContext, RemoveContext, pContext->lReferences));
|
||
*ppContext = pContext;
|
||
goto CleanUp;
|
||
|
||
}
|
||
|
||
//
|
||
// No match found
|
||
//
|
||
|
||
DebugLog((DEB_WARN, "UserCtxtHandlerHandleToContext: Tried to reference unknown Context 0x%lx\n", ContextHandle ));
|
||
Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
||
*ppContext = NULL;
|
||
|
||
CleanUp:
|
||
|
||
RtlLeaveCriticalSection( &l_UserCtxtCritSect );
|
||
DebugLog((DEB_TRACE_FUNC, "UserCtxtHandlerHandleToContext: Leaving Status 0x%x\n", Status ));
|
||
|
||
return(Status);
|
||
}
|
||
|
||
|
||
|
||
// Check the Creation time with the Current time.
|
||
// If the difference is greater than the MAX allowed, Context is no longer valid
|
||
BOOL
|
||
UserCtxtHandlerTimeHasElapsed(
|
||
PDIGEST_USERCONTEXT pContext)
|
||
{
|
||
BOOL bStatus = FALSE;
|
||
DWORD dwTimeElapsed = 0;
|
||
time_t timeCurrent = time(NULL);
|
||
|
||
return FALSE;
|
||
|
||
// dwTimeElapsed = (DWORD)(timeCurrent - pContext->TimeCreated);
|
||
|
||
if (dwTimeElapsed > g_dwParameter_Lifetime)
|
||
{
|
||
bStatus = TRUE;
|
||
}
|
||
|
||
return(bStatus);
|
||
}
|
||
|
||
|
||
|
||
//+--------------------------------------------------------------------
|
||
//
|
||
// Function: CtxtHandlerRelease
|
||
//
|
||
// Synopsis: Releases the Context by decreasing reference counter
|
||
//
|
||
// Arguments: pContext - pointer to credential to de-reference
|
||
//
|
||
// Returns: NTSTATUS
|
||
//
|
||
// Notes: Called by ASC. Since multiple threads must wait for ownership
|
||
// of a context, reference count must be either 0 (unused) or 1 (in process)
|
||
//
|
||
//---------------------------------------------------------------------
|
||
NTSTATUS
|
||
UserCtxtHandlerRelease(
|
||
PDIGEST_USERCONTEXT pUserContext)
|
||
{
|
||
NTSTATUS Status = STATUS_SUCCESS;
|
||
LONG lReferences = 0;
|
||
|
||
lReferences = InterlockedDecrement(&pUserContext->lReferences);
|
||
|
||
DebugLog((DEB_TRACE, "UserCtxtHandlerRelease: (RefCount) UserContextInit deref 0x%x references %ld\n",
|
||
pUserContext, lReferences));
|
||
|
||
ASSERT( lReferences >= 0 );
|
||
|
||
//
|
||
// If the count has dropped to zero, then free all alloced stuff
|
||
//
|
||
|
||
if (lReferences == 0)
|
||
{
|
||
DebugLog((DEB_TRACE, "UserCtxtHandlerRelease: (RefCount) UserContextInit freed 0x%x\n", pUserContext));
|
||
Status = UserCtxtFree(pUserContext);
|
||
}
|
||
|
||
return(Status);
|
||
}
|
||
|
||
|
||
// Following functions make use of the lock for insuring single threaded operation
|
||
|
||
|
||
/*++
|
||
|
||
RoutineDescription:
|
||
|
||
Creates a new DACL for the token granting the server and client
|
||
all access to the token.
|
||
|
||
Arguments:
|
||
|
||
Token - Handle to an impersonation token open for TOKEN_QUERY and
|
||
WRITE_DAC
|
||
|
||
Return Value:
|
||
|
||
STATUS_INSUFFICIENT_RESOURCES - insufficient memory to complete
|
||
the function.
|
||
|
||
Errors from NtSetSecurityObject
|
||
|
||
--*/
|
||
NTSTATUS
|
||
SspCreateTokenDacl(
|
||
HANDLE Token
|
||
)
|
||
{
|
||
NTSTATUS Status = STATUS_SUCCESS;
|
||
PTOKEN_USER ProcessTokenUser = NULL;
|
||
PTOKEN_USER ThreadTokenUser = NULL;
|
||
PTOKEN_USER ImpersonationTokenUser = NULL;
|
||
HANDLE ProcessToken = NULL;
|
||
HANDLE ImpersonationToken = NULL;
|
||
BOOL fInsertImpersonatingUser = FALSE;
|
||
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
|
||
ULONG AclLength = 0;
|
||
PACL NewDacl = NULL;
|
||
SECURITY_DESCRIPTOR SecurityDescriptor;
|
||
|
||
BOOL fReleaseContextLock = FALSE;
|
||
|
||
|
||
DebugLog((DEB_TRACE_FUNC, "SspCreateTokenDacl: Entering Token is 0x%x\n", Token));
|
||
|
||
//
|
||
// Build the two well known sids we need.
|
||
//
|
||
|
||
if (g_NtDigestGlobalLocalSystemSid == NULL || g_NtDigestGlobalAliasAdminsSid == NULL ) {
|
||
|
||
RtlEnterCriticalSection(&l_UserCtxtCritSect);
|
||
fReleaseContextLock = TRUE;
|
||
|
||
if (g_NtDigestGlobalLocalSystemSid == NULL)
|
||
{
|
||
PSID pLocalSidSystem = NULL;
|
||
DebugLog((DEB_TRACE, "SspCreateTokenDacl: Allocate and Init LocalSystem SID\n"));
|
||
Status = RtlAllocateAndInitializeSid(
|
||
&NtAuthority,
|
||
1,
|
||
SECURITY_LOCAL_SYSTEM_RID,
|
||
0,0,0,0,0,0,0,
|
||
&pLocalSidSystem
|
||
);
|
||
if (!NT_SUCCESS(Status))
|
||
{
|
||
DebugLog((DEB_ERROR, "SspCreateTokenDacl: RtlAllocateAndInitializeSid returns 0x%lx\n", Status ));
|
||
goto Cleanup;
|
||
}
|
||
DebugLog((DEB_TRACE, "SspCreateTokenDacl: Allocate and Init LocalSystem SID DONE\n"));
|
||
g_NtDigestGlobalLocalSystemSid = pLocalSidSystem;
|
||
}
|
||
|
||
if (g_NtDigestGlobalAliasAdminsSid == NULL)
|
||
{
|
||
PSID pLocalSidAdmins = NULL;
|
||
|
||
DebugLog((DEB_TRACE, "SspCreateTokenDacl: Allocate and Init AliasAdmin SID\n"));
|
||
Status = RtlAllocateAndInitializeSid(
|
||
&NtAuthority,
|
||
2,
|
||
SECURITY_BUILTIN_DOMAIN_RID,
|
||
DOMAIN_ALIAS_RID_ADMINS,
|
||
0,0,0,0,0,0,
|
||
&pLocalSidAdmins
|
||
);
|
||
if (!NT_SUCCESS(Status))
|
||
{
|
||
DebugLog((DEB_ERROR, "SspCreateTokenDacl, RtlAllocateAndInitializeSid returns 0x%lx\n", Status ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
DebugLog((DEB_TRACE, "SspCreateTokenDacl: Allocate and Init AliasAdmin SID DONE\n"));
|
||
g_NtDigestGlobalAliasAdminsSid = pLocalSidAdmins;
|
||
}
|
||
|
||
RtlLeaveCriticalSection(&l_UserCtxtCritSect);
|
||
fReleaseContextLock = FALSE;
|
||
}
|
||
|
||
//
|
||
// it's possible that the current thread is impersonating a user.
|
||
// if that's the case, get it's token user, and revert to insure we
|
||
// can open the process token.
|
||
//
|
||
|
||
Status = NtOpenThreadToken(
|
||
NtCurrentThread(),
|
||
TOKEN_QUERY | TOKEN_IMPERSONATE,
|
||
TRUE,
|
||
&ImpersonationToken
|
||
);
|
||
|
||
if( NT_SUCCESS(Status) )
|
||
{
|
||
//
|
||
// stop impersonating.
|
||
//
|
||
|
||
RevertToSelf();
|
||
|
||
//
|
||
// get the token user for the impersonating user.
|
||
//
|
||
Status = SspGetTokenUser(
|
||
ImpersonationToken,
|
||
&ImpersonationTokenUser
|
||
);
|
||
|
||
if (!NT_SUCCESS(Status))
|
||
{
|
||
DebugLog((DEB_ERROR, "SspCreateTokenDacl: SspGetTokenUser (1) returns 0x%lx\n", Status ));
|
||
goto Cleanup;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Open the process token to find out the user sid
|
||
//
|
||
|
||
Status = NtOpenProcessToken(
|
||
NtCurrentProcess(),
|
||
TOKEN_QUERY,
|
||
&ProcessToken
|
||
);
|
||
|
||
if(!NT_SUCCESS(Status))
|
||
{
|
||
DebugLog((DEB_ERROR, "SspCreateTokenDacl: NtOpenProcessToken returns 0x%lx\n", Status ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// get the token user for the process token.
|
||
//
|
||
Status = SspGetTokenUser(
|
||
ProcessToken,
|
||
&ProcessTokenUser
|
||
);
|
||
|
||
if (!NT_SUCCESS(Status))
|
||
{
|
||
DebugLog((DEB_ERROR, "SspCreateTokenDacl: SspGetTokenUser (2) returns 0x%lx\n", Status ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
//
|
||
// Now get the token user for the thread.
|
||
//
|
||
Status = SspGetTokenUser(
|
||
Token,
|
||
&ThreadTokenUser
|
||
);
|
||
|
||
if (!NT_SUCCESS(Status))
|
||
{
|
||
DebugLog((DEB_ERROR, "SspCreateTokenDacl: SspGetTokenUser (3) returns 0x%lx\n", Status ));
|
||
goto Cleanup;
|
||
}
|
||
|
||
|
||
AclLength = 4 * sizeof( ACCESS_ALLOWED_ACE ) - 4 * sizeof( ULONG ) +
|
||
RtlLengthSid( ProcessTokenUser->User.Sid ) +
|
||
RtlLengthSid( ThreadTokenUser->User.Sid ) +
|
||
RtlLengthSid( g_NtDigestGlobalLocalSystemSid ) +
|
||
RtlLengthSid( g_NtDigestGlobalAliasAdminsSid ) +
|
||
sizeof( ACL );
|
||
|
||
//
|
||
// determine if we need to add impersonation token sid onto the token Dacl.
|
||
//
|
||
|
||
if( ImpersonationTokenUser &&
|
||
!RtlEqualSid( ImpersonationTokenUser->User.Sid, ProcessTokenUser->User.Sid ) &&
|
||
!RtlEqualSid( ImpersonationTokenUser->User.Sid, ThreadTokenUser->User.Sid )
|
||
)
|
||
{
|
||
AclLength += (sizeof(ACCESS_ALLOWED_ACE) - sizeof( ULONG )) +
|
||
RtlLengthSid( ImpersonationTokenUser->User.Sid );
|
||
|
||
fInsertImpersonatingUser = TRUE;
|
||
}
|
||
|
||
|
||
NewDacl = (PACL)DigestAllocateMemory(AclLength );
|
||
|
||
if (NewDacl == NULL) {
|
||
|
||
Status = STATUS_INSUFFICIENT_RESOURCES;
|
||
DebugLog((DEB_ERROR, "SspCreateTokenDacl: NtLmallocate returns 0x%lx\n", NewDacl));
|
||
goto Cleanup;
|
||
}
|
||
|
||
Status = RtlCreateAcl( NewDacl, AclLength, ACL_REVISION2 );
|
||
ASSERT(NT_SUCCESS( Status ));
|
||
|
||
Status = RtlAddAccessAllowedAce (
|
||
NewDacl,
|
||
ACL_REVISION2,
|
||
TOKEN_ALL_ACCESS,
|
||
ProcessTokenUser->User.Sid
|
||
);
|
||
ASSERT( NT_SUCCESS( Status ));
|
||
|
||
Status = RtlAddAccessAllowedAce (
|
||
NewDacl,
|
||
ACL_REVISION2,
|
||
TOKEN_ALL_ACCESS,
|
||
ThreadTokenUser->User.Sid
|
||
);
|
||
ASSERT( NT_SUCCESS( Status ));
|
||
|
||
if( fInsertImpersonatingUser )
|
||
{
|
||
Status = RtlAddAccessAllowedAce (
|
||
NewDacl,
|
||
ACL_REVISION2,
|
||
TOKEN_ALL_ACCESS,
|
||
ImpersonationTokenUser->User.Sid
|
||
);
|
||
ASSERT( NT_SUCCESS( Status ));
|
||
}
|
||
|
||
Status = RtlAddAccessAllowedAce (
|
||
NewDacl,
|
||
ACL_REVISION2,
|
||
TOKEN_ALL_ACCESS,
|
||
g_NtDigestGlobalAliasAdminsSid
|
||
);
|
||
ASSERT( NT_SUCCESS( Status ));
|
||
|
||
Status = RtlAddAccessAllowedAce (
|
||
NewDacl,
|
||
ACL_REVISION2,
|
||
TOKEN_ALL_ACCESS,
|
||
g_NtDigestGlobalLocalSystemSid
|
||
);
|
||
ASSERT( NT_SUCCESS( Status ));
|
||
|
||
Status = RtlCreateSecurityDescriptor (
|
||
&SecurityDescriptor,
|
||
SECURITY_DESCRIPTOR_REVISION
|
||
);
|
||
ASSERT( NT_SUCCESS( Status ));
|
||
|
||
Status = RtlSetDaclSecurityDescriptor(
|
||
&SecurityDescriptor,
|
||
TRUE,
|
||
NewDacl,
|
||
FALSE
|
||
);
|
||
|
||
ASSERT( NT_SUCCESS( Status ));
|
||
|
||
Status = NtSetSecurityObject(
|
||
Token,
|
||
DACL_SECURITY_INFORMATION,
|
||
&SecurityDescriptor
|
||
);
|
||
|
||
ASSERT( NT_SUCCESS( Status ));
|
||
|
||
|
||
Cleanup:
|
||
|
||
if( fReleaseContextLock )
|
||
RtlLeaveCriticalSection(&l_UserCtxtCritSect);
|
||
|
||
if (ImpersonationToken != NULL)
|
||
{
|
||
//
|
||
// put the thread token back if we were impersonating.
|
||
//
|
||
|
||
SetThreadToken( NULL, ImpersonationToken );
|
||
NtClose(ImpersonationToken);
|
||
}
|
||
|
||
if (ThreadTokenUser != NULL) {
|
||
DigestFreeMemory( ThreadTokenUser );
|
||
}
|
||
|
||
if (ProcessTokenUser != NULL) {
|
||
DigestFreeMemory( ProcessTokenUser );
|
||
}
|
||
|
||
if (ImpersonationTokenUser != NULL) {
|
||
|
||
DigestFreeMemory( ImpersonationTokenUser );
|
||
}
|
||
|
||
if (NewDacl != NULL) {
|
||
DigestFreeMemory( NewDacl );
|
||
}
|
||
|
||
if (ProcessToken != NULL)
|
||
{
|
||
NtClose(ProcessToken);
|
||
}
|
||
|
||
DebugLog((DEB_TRACE_FUNC, "SspCreateTokenDacl: Leaving Token is 0x%x\n", Token));
|
||
|
||
return( Status );
|
||
}
|
||
|