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.
1420 lines
39 KiB
1420 lines
39 KiB
//+-----------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
//
|
|
// Copyright (c) Microsoft Corporation 1992 - 1996
|
|
//
|
|
// File: credmgr.cxx
|
|
//
|
|
// Contents: Code for managing credentials list for the Kerberos package
|
|
//
|
|
//
|
|
// History: 17-April-1996 Created MikeSw
|
|
// 26-Sep-1998 ChandanS
|
|
// Added more debugging support etc.
|
|
//
|
|
//------------------------------------------------------------------------
|
|
|
|
#include <kerb.hxx>
|
|
#define CREDMGR_ALLOCATE
|
|
#include <kerbp.h>
|
|
#ifdef RETAIL_LOG_SUPPORT
|
|
static TCHAR THIS_FILE[]=TEXT(__FILE__);
|
|
#endif
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KerbInitCredentialList
|
|
//
|
|
// Synopsis: Initializes the Credentials list
|
|
//
|
|
// Effects: allocates a resources
|
|
//
|
|
// Arguments: none
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns: STATUS_SUCCESS on success, other error codes
|
|
// on failure
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
NTSTATUS
|
|
KerbInitCredentialList(
|
|
VOID
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
|
|
Status = KerbInitializeList( &KerbCredentialList, CRED_MGR_LOCK_ENUM );
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
KerberosCredentialsInitialized = TRUE;
|
|
|
|
Cleanup:
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
KerbFreeList( &KerbCredentialList);
|
|
|
|
}
|
|
return(Status);
|
|
}
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KerbFreeCredentialList
|
|
//
|
|
// Synopsis: Frees the credentials list
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments: none
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns: none
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
VOID
|
|
KerbFreeCredentialList(
|
|
VOID
|
|
)
|
|
{
|
|
PKERB_CREDENTIAL Credential;
|
|
|
|
|
|
if (KerberosCredentialsInitialized)
|
|
{
|
|
KerbLockList(&KerbCredentialList);
|
|
|
|
//
|
|
// Go through the list of logon sessions and dereferences them all
|
|
//
|
|
|
|
while (!IsListEmpty(&KerbCredentialList.List))
|
|
{
|
|
Credential = CONTAINING_RECORD(
|
|
KerbCredentialList.List.Flink,
|
|
KERB_CREDENTIAL,
|
|
ListEntry.Next
|
|
);
|
|
|
|
KerbReferenceListEntry(
|
|
&KerbCredentialList,
|
|
&Credential->ListEntry,
|
|
TRUE
|
|
);
|
|
|
|
KerbDereferenceCredential(Credential);
|
|
|
|
}
|
|
|
|
KerbFreeList(&KerbCredentialList);
|
|
}
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KerbAllocateCredential
|
|
//
|
|
// Synopsis: Allocates a credential structure
|
|
//
|
|
// Effects: Allocates a credential, but does not add it to the
|
|
// list of credentials
|
|
//
|
|
// Arguments: NewCredential - receives a new credential allocated
|
|
// with KerbAllocate
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns: STATUS_SUCCESS on success
|
|
// STATUS_INSUFFICIENT_RESOURCES if the allocation fails
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
NTSTATUS
|
|
KerbAllocateCredential(
|
|
PKERB_CREDENTIAL * NewCredential
|
|
)
|
|
{
|
|
PKERB_CREDENTIAL Credential;
|
|
SECPKG_CALL_INFO CallInfo;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
*NewCredential = NULL;
|
|
|
|
if (!LsaFunctions->GetCallInfo(&CallInfo))
|
|
{
|
|
D_DebugLog((DEB_ERROR,"Failed to get call info. %ws, line %d\n",
|
|
THIS_FILE, __LINE__));
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Cleanup;
|
|
}
|
|
|
|
Credential = (PKERB_CREDENTIAL) KerbAllocate(
|
|
sizeof(KERB_CREDENTIAL) );
|
|
|
|
if (Credential == NULL)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Cleanup;
|
|
}
|
|
|
|
Credential->ClientProcess = CallInfo.ProcessId;
|
|
|
|
KerbInitializeListEntry(
|
|
&Credential->ListEntry
|
|
);
|
|
//
|
|
// Set the references to 1 since we are returning a pointer to the
|
|
// logon session
|
|
//
|
|
|
|
Credential->HandleCount = 1;
|
|
|
|
*NewCredential = Credential;
|
|
|
|
Cleanup:
|
|
return(Status);
|
|
}
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KerbInsertCredential
|
|
//
|
|
// Synopsis: Inserts a logon session into the list of logon sessions
|
|
//
|
|
// Effects: bumps reference count on logon session
|
|
//
|
|
// Arguments: Credential - Credential to insert
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns: STATUS_SUCCESS always
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
NTSTATUS
|
|
KerbInsertCredential(
|
|
IN PKERB_CREDENTIAL Credential
|
|
)
|
|
{
|
|
|
|
//
|
|
// insert entry at tail of list.
|
|
// reason: entries at the head are more likely to have _TGT flag set
|
|
// and, those are the ones we want to satisfy from cache for repeat
|
|
// high stress offenders...
|
|
//
|
|
|
|
Credential->CredentialTag = KERB_CREDENTIAL_TAG_ACTIVE;
|
|
|
|
KerbInsertListEntryTail(
|
|
&Credential->ListEntry,
|
|
&KerbCredentialList
|
|
);
|
|
|
|
return(STATUS_SUCCESS);
|
|
}
|
|
|
|
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KerbFreePrimaryCredentials
|
|
//
|
|
// Synopsis: frees a primary credentials structure
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
VOID
|
|
KerbFreePrimaryCredentials(
|
|
IN PKERB_PRIMARY_CREDENTIAL Credentials,
|
|
IN BOOLEAN FreeBaseStructure
|
|
)
|
|
{
|
|
if (Credentials != NULL)
|
|
{
|
|
KerbFreeString(&Credentials->DomainName);
|
|
KerbFreeString(&Credentials->OldDomainName);
|
|
KerbFreeString(&Credentials->UserName);
|
|
KerbFreeString(&Credentials->OldUserName);
|
|
|
|
RtlZeroMemory( &Credentials->OldHashPassword, sizeof(Credentials->OldHashPassword) );
|
|
if (Credentials->ClearPassword.Buffer != NULL)
|
|
{
|
|
RtlZeroMemory(
|
|
Credentials->ClearPassword.Buffer,
|
|
Credentials->ClearPassword.Length
|
|
);
|
|
KerbFreeString(&Credentials->ClearPassword);
|
|
RtlZeroMemory(&Credentials->ClearPassword, sizeof(Credentials->ClearPassword));
|
|
}
|
|
if (Credentials->Passwords != NULL)
|
|
{
|
|
KerbFreeStoredCred(Credentials->Passwords);
|
|
}
|
|
if (Credentials->OldPasswords != NULL)
|
|
{
|
|
KerbFreeStoredCred(Credentials->OldPasswords);
|
|
}
|
|
KerbPurgeTicketCache(&Credentials->ServerTicketCache);
|
|
KerbPurgeTicketCache(&Credentials->AuthenticationTicketCache);
|
|
|
|
KerbFreePKCreds(Credentials->PublicKeyCreds, FALSE);
|
|
Credentials->PublicKeyCreds = NULL;
|
|
|
|
if (FreeBaseStructure)
|
|
{
|
|
KerbFree(Credentials);
|
|
}
|
|
}
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KerbFreeCredential
|
|
//
|
|
// Synopsis: Frees a credential structure and all contained data
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments: Credential - The credential to free.
|
|
//
|
|
// Requires: This credential must be unlinked
|
|
//
|
|
// Returns:
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
VOID
|
|
KerbFreeCredential(
|
|
IN PKERB_CREDENTIAL Credential
|
|
)
|
|
{
|
|
|
|
Credential->CredentialTag = KERB_CREDENTIAL_TAG_DELETE;
|
|
|
|
if (Credential->SuppliedCredentials != NULL)
|
|
{
|
|
KerbFreePrimaryCredentials(Credential->SuppliedCredentials, TRUE);
|
|
}
|
|
DsysAssert(Credential->ListEntry.Next.Flink == NULL);
|
|
KerbFreeString(&Credential->CredentialName);
|
|
|
|
KerbFree(Credential);
|
|
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KerbGetTicketForCredential
|
|
//
|
|
// Synopsis: Obtains a TGT for a credential if it doesn't already
|
|
// have one.
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
NTSTATUS
|
|
KerbGetTicketForCredential(
|
|
IN OPTIONAL PKERB_LOGON_SESSION LogonSession,
|
|
IN PKERB_CREDENTIAL Credential,
|
|
IN OPTIONAL PKERB_CREDMAN_CRED CredManCredentials,
|
|
IN OPTIONAL PUNICODE_STRING SuppRealm
|
|
)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PKERB_LOGON_SESSION LocalLogonSession = NULL;
|
|
BOOLEAN GetRestrictedTgt = FALSE;
|
|
PKERB_TICKET_CACHE_ENTRY Tgt = NULL;
|
|
BOOLEAN PrimaryLogonSessionCredential = FALSE;
|
|
|
|
//
|
|
// We've got to make a determination w.r.t. whether this
|
|
// is an attempt to renew our primary credential.
|
|
// This will affect our logon session flags.
|
|
//
|
|
|
|
|
|
if (!ARGUMENT_PRESENT(LogonSession))
|
|
{
|
|
LocalLogonSession = KerbReferenceLogonSession(
|
|
&Credential->LogonId,
|
|
FALSE // don't unlink
|
|
);
|
|
if (LocalLogonSession == NULL)
|
|
{
|
|
Status = STATUS_NO_SUCH_LOGON_SESSION;
|
|
goto Cleanup;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
LocalLogonSession = LogonSession;
|
|
}
|
|
|
|
|
|
//
|
|
// Here we make the assumption that if we didn't get a credential
|
|
// and we also got a logon session, then we're dealing w/ the
|
|
// logon session's primary credential
|
|
// f
|
|
if ((ARGUMENT_PRESENT(Credential)) &&
|
|
(Credential->SuppliedCredentials == NULL) &&
|
|
(!ARGUMENT_PRESENT(CredManCredentials)))
|
|
|
|
{
|
|
PrimaryLogonSessionCredential = TRUE;
|
|
D_DebugLog((DEB_TRACE_CRED, "Getting Credentials for primary logon session - %x\n", LogonSession));
|
|
}
|
|
else
|
|
{
|
|
D_DebugLog((DEB_TRACE_CRED, "Got a credential && a logon session\n"));
|
|
}
|
|
|
|
Status = KerbGetTicketGrantingTicket(
|
|
LocalLogonSession,
|
|
Credential,
|
|
CredManCredentials,
|
|
SuppRealm,
|
|
&Tgt,
|
|
NULL // don't return credential key
|
|
);
|
|
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto Cleanup;
|
|
|
|
}
|
|
|
|
KerbWriteLockLogonSessions(LocalLogonSession);
|
|
|
|
//
|
|
// Clear the logon deferred bit for the logon session, if set
|
|
// Note: This will only be cleared as a result of refreshing
|
|
// logon session's primary cred tgt
|
|
//
|
|
if (PrimaryLogonSessionCredential &&
|
|
((LocalLogonSession->LogonSessionFlags & KERB_LOGON_DEFERRED) != 0))
|
|
{
|
|
LocalLogonSession->LogonSessionFlags &= ~KERB_LOGON_DEFERRED;
|
|
}
|
|
|
|
//
|
|
// If we have a credential, be sure to set the TGT_avail bit for
|
|
// those credentials.
|
|
//
|
|
if (ARGUMENT_PRESENT(Credential))
|
|
{
|
|
Credential->CredentialFlags |= KERB_CRED_TGT_AVAIL;
|
|
if ((Credential->CredentialFlags & KERB_CRED_RESTRICTED) != 0)
|
|
{
|
|
GetRestrictedTgt = TRUE;
|
|
}
|
|
}
|
|
|
|
if (ARGUMENT_PRESENT(CredManCredentials))
|
|
{
|
|
CredManCredentials->CredentialFlags |= KERB_CRED_TGT_AVAIL;
|
|
}
|
|
|
|
KerbUnlockLogonSessions(LocalLogonSession);
|
|
|
|
#ifdef RESTRICTED_TOKEN
|
|
if (GetRestrictedTgt)
|
|
{
|
|
Status = KerbGetRestrictedTgtForCredential(
|
|
LocalLogonSession,
|
|
Credential
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DebugLog((DEB_ERROR,"Failed to get restricted TGT for credential: 0x%x\n",Status));
|
|
|
|
KerbRemoveTicketCacheEntry(Tgt);
|
|
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
Cleanup:
|
|
|
|
//
|
|
// Reset the bits if we failed
|
|
//
|
|
|
|
if (LocalLogonSession && !NT_SUCCESS(Status))
|
|
{
|
|
KerbWriteLockLogonSessions(LocalLogonSession);
|
|
|
|
//
|
|
// Don't touch logon session flag, unless we're
|
|
// dealing w/ our own logon session. This means
|
|
// we don't have a TGT for our initial logon session,
|
|
// See RefreshTgt() -- only place we supply logon session
|
|
//
|
|
if (PrimaryLogonSessionCredential)
|
|
{
|
|
LocalLogonSession->LogonSessionFlags |= KERB_LOGON_DEFERRED;
|
|
D_DebugLog((DEB_TRACE_CRED, "Toggling ON logon deferred bit for logon session %x\n", LogonSession));
|
|
}
|
|
|
|
//
|
|
// Or, we expected it to be there with our (supplied) credential
|
|
//
|
|
if (ARGUMENT_PRESENT(Credential))
|
|
{
|
|
Credential->CredentialFlags &= ~KERB_CRED_TGT_AVAIL;
|
|
}
|
|
|
|
KerbUnlockLogonSessions(LocalLogonSession);
|
|
}
|
|
|
|
if (!ARGUMENT_PRESENT(LogonSession) && (LocalLogonSession != NULL))
|
|
{
|
|
KerbDereferenceLogonSession(LocalLogonSession);
|
|
}
|
|
if (Tgt != NULL)
|
|
{
|
|
KerbDereferenceTicketCacheEntry(
|
|
Tgt
|
|
);
|
|
|
|
}
|
|
return(Status);
|
|
}
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KerbReferenceCredential
|
|
//
|
|
// Synopsis: Locates a logon session from the logon ID and references it
|
|
//
|
|
// Effects: Increments reference count and possible unlinks it from list
|
|
//
|
|
// Arguments: LogonId - LogonId of logon session to locate
|
|
// RequiredFlags - Flags required
|
|
// RemoveFromList - If TRUE, logon session will be delinked
|
|
// Credential - Receives the referenced credential
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns: NT status codes
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
NTSTATUS
|
|
KerbReferenceCredential(
|
|
IN LSA_SEC_HANDLE CredentialHandle,
|
|
IN ULONG RequiredFlags,
|
|
IN BOOLEAN RemoveFromList,
|
|
OUT PKERB_CREDENTIAL * Credential
|
|
)
|
|
{
|
|
PKERB_CREDENTIAL LocalCredential = NULL;
|
|
BOOLEAN Found = FALSE;
|
|
SECPKG_CALL_INFO CallInfo;
|
|
BOOLEAN LocalRemoveFromList = FALSE;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
ULONG DereferenceCount;
|
|
|
|
*Credential = NULL;
|
|
|
|
if(LsaFunctions->GetCallInfo(&CallInfo))
|
|
{
|
|
DereferenceCount = CallInfo.CallCount;
|
|
} else {
|
|
ASSERT((STATUS_INTERNAL_ERROR == STATUS_SUCCESS));
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
if( CallInfo.Attributes & SECPKG_CALL_CLEANUP )
|
|
{
|
|
CallInfo.Attributes |= SECPKG_CALL_IS_TCB;
|
|
DebugLog((DEB_TRACE, "CredHandle %p leaked by ProcessId %x Deref count: %x\n",
|
|
CredentialHandle, CallInfo.ProcessId, DereferenceCount));
|
|
}
|
|
|
|
KerbLockList(&KerbCredentialList);
|
|
|
|
//
|
|
// Go through the list of logon sessions looking for the correct
|
|
// LUID
|
|
//
|
|
|
|
__try {
|
|
LocalCredential = (PKERB_CREDENTIAL)CredentialHandle;
|
|
|
|
while( LocalCredential->CredentialTag == KERB_CREDENTIAL_TAG_ACTIVE )
|
|
{
|
|
if (((CallInfo.Attributes & SECPKG_CALL_IS_TCB) == 0) &&
|
|
(LocalCredential->ClientProcess != CallInfo.ProcessId) )
|
|
{
|
|
D_DebugLog((DEB_ERROR,"Trying to reference a credential from another process! %ws, line %d\n", THIS_FILE, __LINE__));
|
|
// FESTER
|
|
D_DebugLog((DEB_ERROR, "Cred - %x \nClient process - %d Call info Pid - %d\n", LocalCredential, LocalCredential->ClientProcess, CallInfo.ProcessId));
|
|
Found = FALSE;
|
|
Status = STATUS_PRIVILEGE_NOT_HELD;
|
|
break;
|
|
}
|
|
|
|
KerbReferenceListEntry(
|
|
&KerbCredentialList,
|
|
&LocalCredential->ListEntry,
|
|
FALSE // don't remove
|
|
);
|
|
|
|
Found = TRUE;
|
|
break;
|
|
}
|
|
|
|
} __except (EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
D_DebugLog((DEB_ERROR, "Trying to reference invalid credential %ws, line %d\n", THIS_FILE, __LINE__));
|
|
Found = FALSE;
|
|
}
|
|
|
|
if (!Found)
|
|
{
|
|
LocalCredential = NULL;
|
|
Status = STATUS_INVALID_HANDLE;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// get TGTs in order to normalize supplied UPN credentials for non TCB callers
|
|
//
|
|
|
|
if ((RequiredFlags & KERB_CRED_INBOUND)
|
|
&& LocalCredential->SuppliedCredentials
|
|
&& (LocalCredential->SuppliedCredentials->DomainName.Length == 0)
|
|
&& ((CallInfo.Attributes & SECPKG_CALL_IS_TCB) == 0))
|
|
{
|
|
DebugLog((DEB_TRACE_CRED, "KerbReferenceCredential trying to normalize UPN %wZ, RequiredFlags %#x\n", &LocalCredential->SuppliedCredentials->UserName, RequiredFlags));
|
|
RequiredFlags |= KERB_CRED_TGT_AVAIL;
|
|
}
|
|
|
|
//
|
|
// In some cases, we need to get a TGT here - but not for the S4U cases.
|
|
//
|
|
|
|
ULONG MissingFlags = RequiredFlags - (LocalCredential->CredentialFlags & RequiredFlags);
|
|
|
|
if (MissingFlags != 0)
|
|
{
|
|
D_DebugLog((DEB_TRACE, "Credential %p is missing flags: needs %x\n",
|
|
Credential,
|
|
MissingFlags));
|
|
|
|
if ((MissingFlags &= KERB_CRED_TGT_AVAIL) != 0)
|
|
{
|
|
if (( LocalCredential->CredentialFlags & (KERB_CRED_S4U_REQUIRED | KERB_CRED_LOCAL_ACCOUNT )) == 0)
|
|
{
|
|
DsysAssert(!RemoveFromList);
|
|
|
|
KerbUnlockList(&KerbCredentialList);
|
|
|
|
D_DebugLog((DEB_TRACE_CRED,"Getting missing TGT for credential %x\n", LocalCredential));
|
|
|
|
Status = KerbGetTicketForCredential(
|
|
NULL,
|
|
LocalCredential,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
KerbLockList(&KerbCredentialList);
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
Status = SEC_E_NO_CREDENTIALS;
|
|
}
|
|
}
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// Since there may be multiple outstanding handles using this
|
|
// structure we don't want to really remove it from the list unless
|
|
// the last one releases it.
|
|
//
|
|
|
|
if (RemoveFromList)
|
|
{
|
|
ASSERT( DereferenceCount != 0 );
|
|
ASSERT ( (DereferenceCount <= LocalCredential->HandleCount) );
|
|
|
|
if( DereferenceCount > LocalCredential->HandleCount ) {
|
|
LocalCredential->HandleCount = 0;
|
|
} else {
|
|
LocalCredential->HandleCount -= DereferenceCount;
|
|
}
|
|
|
|
if (LocalCredential->HandleCount == 0)
|
|
{
|
|
LocalRemoveFromList = TRUE;
|
|
}
|
|
}
|
|
|
|
KerbReferenceListEntry(
|
|
&KerbCredentialList,
|
|
&LocalCredential->ListEntry,
|
|
LocalRemoveFromList
|
|
);
|
|
|
|
KerbDereferenceCredential(LocalCredential);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Remove the earlier reference
|
|
//
|
|
|
|
KerbDereferenceCredential(LocalCredential);
|
|
LocalCredential = NULL;
|
|
}
|
|
|
|
|
|
*Credential = LocalCredential;
|
|
}
|
|
|
|
KerbUnlockList(&KerbCredentialList);
|
|
return(Status);
|
|
}
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KerbDereferenceCredential
|
|
//
|
|
// Synopsis: Dereferences a logon session - if reference count goes
|
|
// to zero it frees the logon session
|
|
//
|
|
// Effects: decrements reference count
|
|
//
|
|
// Arguments: Credential - Logon session to dereference
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns: none
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
VOID
|
|
KerbDereferenceCredential(
|
|
IN PKERB_CREDENTIAL Credential
|
|
)
|
|
{
|
|
if (KerbDereferenceListEntry(
|
|
&Credential->ListEntry,
|
|
&KerbCredentialList
|
|
) )
|
|
{
|
|
KerbFreeCredential(Credential);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KerbPurgeCredentials
|
|
//
|
|
// Synopsis: Purges the list of credentials associated with a logon session
|
|
// by dereferencing and unlinking them.
|
|
//
|
|
// Effects: Unlinks all credential on the list
|
|
//
|
|
// Arguments: CredentialList - List of credentials to purge
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns: none
|
|
//
|
|
// Notes: No longer used, as some system processes hold cred handles long
|
|
// after logons go away. Leads to refcounting disasters.
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
/*
|
|
|
|
VOID
|
|
KerbPurgeCredentials(
|
|
IN PLIST_ENTRY CredentialList
|
|
)
|
|
{
|
|
PKERB_CREDENTIAL Credential;
|
|
|
|
KerbLockList(&KerbCredentialList);
|
|
while (!IsListEmpty(CredentialList))
|
|
{
|
|
Credential = CONTAINING_RECORD(
|
|
CredentialList->Flink,
|
|
KERB_CREDENTIAL,
|
|
NextForThisLogonSession
|
|
);
|
|
|
|
|
|
//
|
|
// Remove it from the credential list
|
|
//
|
|
|
|
//RemoveEntryList(&Credential->NextForThisLogonSession);
|
|
Credential->HandleCount = 0;
|
|
|
|
//
|
|
// Reference it to unlink it and then dereference it
|
|
//
|
|
|
|
KerbReferenceListEntry(
|
|
&KerbCredentialList,
|
|
&Credential->ListEntry,
|
|
TRUE
|
|
);
|
|
|
|
KerbDereferenceCredential(Credential);
|
|
|
|
}
|
|
KerbUnlockList(&KerbCredentialList);
|
|
|
|
} */
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KerbLocateCredential
|
|
//
|
|
// Synopsis:
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
PKERB_CREDENTIAL
|
|
KerbLocateCredential(
|
|
IN PLUID LogonId,
|
|
IN ULONG CredentialUseFlags,
|
|
IN PKERB_PRIMARY_CREDENTIAL SuppliedCredentials,
|
|
IN ULONG CredentialFlags,
|
|
IN PUNICODE_STRING CredentialName
|
|
)
|
|
{
|
|
PLIST_ENTRY ListEntry;
|
|
PKERB_CREDENTIAL Credential = NULL;
|
|
BOOLEAN Found = FALSE;
|
|
SECPKG_CALL_INFO CallInfo;
|
|
NT_OWF_PASSWORD HashPassword;
|
|
|
|
if( SuppliedCredentials != NULL )
|
|
{
|
|
if ( SuppliedCredentials->ClearPassword.Buffer != NULL )
|
|
{
|
|
RtlCalculateNtOwfPassword(
|
|
&SuppliedCredentials->ClearPassword,
|
|
&HashPassword
|
|
);
|
|
}
|
|
else
|
|
{
|
|
ZeroMemory( &HashPassword, sizeof(HashPassword) );
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Match both flags
|
|
//
|
|
|
|
CredentialUseFlags |= CredentialFlags;
|
|
|
|
if(!LsaFunctions->GetCallInfo(&CallInfo))
|
|
{
|
|
|
|
D_DebugLog((DEB_ERROR,"Failed to get client info:. %ws, line %d\n",
|
|
THIS_FILE, __LINE__));
|
|
return(NULL);
|
|
}
|
|
|
|
|
|
KerbLockList(&KerbCredentialList);
|
|
|
|
//
|
|
// Go through the list of logon sessions looking for the correct
|
|
// LUID
|
|
//
|
|
|
|
for (ListEntry = KerbCredentialList.List.Flink ;
|
|
ListEntry != &KerbCredentialList.List ;
|
|
ListEntry = ListEntry->Flink )
|
|
{
|
|
Credential = CONTAINING_RECORD(ListEntry, KERB_CREDENTIAL, ListEntry.Next);
|
|
|
|
if( (Credential->ClientProcess != CallInfo.ProcessId) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( (Credential->CredentialFlags & KERB_CRED_MATCH_FLAGS) != CredentialUseFlags)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(!RtlEqualLuid(
|
|
&Credential->LogonId,
|
|
LogonId
|
|
))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(!RtlEqualUnicodeString(
|
|
CredentialName,
|
|
&Credential->CredentialName,
|
|
FALSE
|
|
))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( SuppliedCredentials != NULL )
|
|
{
|
|
//
|
|
// credentials supplied, but candidate didn't have creds. continue search.
|
|
//
|
|
|
|
if( Credential->SuppliedCredentials == NULL )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
|
|
if(!RtlEqualUnicodeString(
|
|
&SuppliedCredentials->UserName,
|
|
&Credential->SuppliedCredentials->UserName,
|
|
FALSE
|
|
))
|
|
{
|
|
if(!RtlEqualUnicodeString(
|
|
&SuppliedCredentials->UserName,
|
|
&Credential->SuppliedCredentials->OldUserName,
|
|
FALSE
|
|
))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
//
|
|
// note: both candidate and input SuppliedCredentials ClearPassword
|
|
// is actually encrypted via KerbHidePassword().
|
|
//
|
|
|
|
if(!RtlEqualMemory(
|
|
&HashPassword,
|
|
&Credential->SuppliedCredentials->OldHashPassword,
|
|
sizeof(HashPassword)
|
|
))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
|
|
//
|
|
// optimize for UPN case:
|
|
// check as typed versus as stored/updated first,
|
|
// then check as typed versus as typed in original cred build.
|
|
//
|
|
|
|
if(!RtlEqualUnicodeString(
|
|
&SuppliedCredentials->DomainName,
|
|
&Credential->SuppliedCredentials->DomainName,
|
|
FALSE
|
|
))
|
|
{
|
|
if(!RtlEqualUnicodeString(
|
|
&SuppliedCredentials->DomainName,
|
|
&Credential->SuppliedCredentials->OldDomainName,
|
|
FALSE
|
|
))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
|
|
if ((SuppliedCredentials->PublicKeyCreds != NULL) &&
|
|
(Credential->SuppliedCredentials->PublicKeyCreds != NULL))
|
|
{
|
|
|
|
//
|
|
// note: both candidate and input SuppliedCredentials Pin
|
|
// is actually encrypted via KerbHidePassword().
|
|
//
|
|
|
|
if(!RtlEqualUnicodeString(
|
|
&SuppliedCredentials->PublicKeyCreds->Pin,
|
|
&Credential->SuppliedCredentials->PublicKeyCreds->Pin,
|
|
FALSE
|
|
))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!KerbComparePublicKeyCreds(
|
|
SuppliedCredentials->PublicKeyCreds,
|
|
Credential->SuppliedCredentials->PublicKeyCreds
|
|
))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
//
|
|
// credentials not supplied, but candidate has creds. continue search
|
|
//
|
|
|
|
if( Credential->SuppliedCredentials != NULL )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
}
|
|
|
|
KerbReferenceListEntry(
|
|
&KerbCredentialList,
|
|
&Credential->ListEntry,
|
|
FALSE
|
|
);
|
|
|
|
Credential->HandleCount++;
|
|
Found = TRUE;
|
|
break;
|
|
|
|
}
|
|
|
|
KerbUnlockList(&KerbCredentialList);
|
|
|
|
if (!Found)
|
|
{
|
|
Credential = NULL;
|
|
}
|
|
return(Credential);
|
|
}
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KerbCreateCredential
|
|
//
|
|
// Synopsis: Creates a new credential and links it to the credential list
|
|
// and the list for this logon session
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments: LogonId - LogonId for this logon session
|
|
// LogonSession - LogonSession for the client
|
|
// CredentialUseFlags - Flags indicating if the credential is
|
|
// inbound or outbound
|
|
// SuppliedCredentials - (Optionally) supplied credentials to store
|
|
// in the credentials. If these are present, there need
|
|
// not be a password on the logon session. The field is
|
|
// zeroed when the primary creds are stuck in the
|
|
// credential structure.
|
|
// CredentialFlags - Flags about how credentials are to be
|
|
// used, such as to not use a PAC or to use a null
|
|
// session.
|
|
// NewCredential - Receives new credential, referenced and linked
|
|
// ExpirationTime - Receives credential expiration time
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns: STATUS_SUCCESS on success,
|
|
// STATUS_INSUFFICIENT_RESOURCES on allocation failure
|
|
//
|
|
// Notes: Readers and writers of this credential must hold the
|
|
// credential lock
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
NTSTATUS
|
|
KerbCreateCredential(
|
|
IN PLUID LogonId,
|
|
IN PKERB_LOGON_SESSION LogonSession,
|
|
IN ULONG CredentialUseFlags,
|
|
IN PKERB_PRIMARY_CREDENTIAL * SuppliedCredentials,
|
|
IN ULONG CredentialFlags,
|
|
IN PUNICODE_STRING CredentialName,
|
|
OUT PKERB_CREDENTIAL * NewCredential,
|
|
OUT PTimeStamp ExpirationTime
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PKERB_CREDENTIAL Credential = NULL;
|
|
ULONG LogonSessionFlags = 0;
|
|
UNICODE_STRING ServiceRealm = NULL_UNICODE_STRING;
|
|
BOOLEAN FoundKdc = FALSE;
|
|
|
|
//
|
|
// Make sure the flags are valid
|
|
//
|
|
|
|
if (( CredentialUseFlags == 0) ||
|
|
((CredentialUseFlags & ~SECPKG_CRED_BOTH) != 0))
|
|
{
|
|
D_DebugLog((DEB_ERROR,"Invalid credential use flags: 0x%x. %ws, line %d\n",CredentialUseFlags, THIS_FILE, __LINE__));
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Check to see if we already have a credential for this situation
|
|
//
|
|
|
|
Credential = KerbLocateCredential(
|
|
LogonId,
|
|
CredentialUseFlags,
|
|
*SuppliedCredentials,
|
|
CredentialFlags,
|
|
CredentialName
|
|
);
|
|
|
|
if (Credential != NULL)
|
|
{
|
|
KerbReadLockLogonSessions(LogonSession);
|
|
|
|
*ExpirationTime = LogonSession->Lifetime;
|
|
|
|
KerbUnlockLogonSessions(LogonSession);
|
|
|
|
*NewCredential = Credential;
|
|
return(STATUS_SUCCESS);
|
|
}
|
|
|
|
Status = KerbAllocateCredential(&Credential);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
Credential->LogonId = *LogonId;
|
|
|
|
//
|
|
// Make sure the logon session is valid for acquiring credentials
|
|
//
|
|
|
|
KerbReadLockLogonSessions(LogonSession);
|
|
|
|
LogonSessionFlags = LogonSession->LogonSessionFlags;
|
|
*ExpirationTime = LogonSession->Lifetime;
|
|
|
|
KerbUnlockLogonSessions(LogonSession);
|
|
|
|
Credential->SuppliedCredentials = *SuppliedCredentials;
|
|
*SuppliedCredentials = NULL;
|
|
|
|
Credential->CredentialName = *CredentialName;
|
|
CredentialName->Buffer = NULL;
|
|
|
|
Credential->CredentialFlags = CredentialUseFlags | CredentialFlags;
|
|
|
|
if (( CredentialUseFlags & KERB_CRED_BOTH ) == 0)
|
|
{
|
|
D_DebugLog((DEB_ERROR,"Invalid credential use flags: 0x%x. %ws, line %d\n",CredentialUseFlags, THIS_FILE, __LINE__));
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Evaluate the outbound credential cases. We may need to do S4U.
|
|
//
|
|
if (( Credential->SuppliedCredentials == NULL ) &&
|
|
(( CredentialFlags & KERB_CRED_NULL_SESSION) == 0))
|
|
{
|
|
if (( CredentialUseFlags & KERB_CRED_OUTBOUND) != 0)
|
|
{
|
|
if (( LogonSessionFlags & KERB_LOGON_S4U_REQUIRED ) != 0)
|
|
{
|
|
//
|
|
// This logon session was created w/o the capabilities of
|
|
// going "off box". In this case, we need to fail this transaction
|
|
//
|
|
if (( LogonSessionFlags & KERB_LOGON_DELEGATE_OK ) == 0)
|
|
{
|
|
DebugLog((DEB_TRACE_CRED, "Cant go off box w/ non-fwdble logon session & no supp creds\n"));
|
|
Status = SEC_E_NO_CREDENTIALS;
|
|
goto Cleanup;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// All others are OK for leaving the machine. Mark this cred
|
|
// as an S4U cred, then get the heck out of here.
|
|
//
|
|
DebugLog((DEB_TRACE_CRED, "Acquiring cred, S4U required\n"));
|
|
Credential->CredentialFlags |= KERB_CRED_S4U_REQUIRED;
|
|
}
|
|
}
|
|
else if (( LogonSessionFlags & KERB_LOGON_LOCAL_ONLY ) != 0)
|
|
{
|
|
//
|
|
// Local logon session - we can only use these w/o supplied creds
|
|
// if we use credman.
|
|
//
|
|
Credential->CredentialFlags |= KERB_CRED_LOCAL_ACCOUNT;
|
|
|
|
}
|
|
else if (( LogonSessionFlags & KERB_LOGON_DEFERRED ) != 0)
|
|
{
|
|
//
|
|
// I don't believe we'll hit this case anymore, as all "non pwd" logon sessions
|
|
// should be using S4U, unless they're local.
|
|
//
|
|
if (( LogonSessionFlags & KERB_LOGON_NO_PASSWORD ) != 0 )
|
|
{
|
|
DebugLog((DEB_TRACE_CRED,"Trying to acquire cred handle w/ no supplied creds for ls (%p) no pass or TGT\n", LogonSession));
|
|
Status = SEC_E_NO_CREDENTIALS;
|
|
goto Cleanup;
|
|
}
|
|
|
|
}
|
|
else if (( LogonSessionFlags & KERB_LOGON_DEFERRED ) == 0)
|
|
{
|
|
//
|
|
// Normal case, where we have a TGT
|
|
//
|
|
Credential->CredentialFlags |= KERB_CRED_TGT_AVAIL;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Generic case, where we don't have a TGT for a domain logon,
|
|
// but we have info for a TGT. Assert until we verify we're not missing
|
|
// anything important - Contact Todds
|
|
//
|
|
DebugLog((DEB_ERROR, "Missing case for session FLAGs %x\n", LogonSessionFlags));
|
|
}
|
|
}
|
|
|
|
if (( CredentialUseFlags & KERB_CRED_INBOUND) != 0)
|
|
{
|
|
if (( LogonSessionFlags & KERB_LOGON_DEFERRED ) == 0)
|
|
{
|
|
Credential->CredentialFlags |= KERB_CRED_TGT_AVAIL;
|
|
}
|
|
else
|
|
{
|
|
if ((LogonSessionFlags & (KERB_LOGON_NO_PASSWORD | KERB_LOGON_LOCAL_ONLY)) != 0)
|
|
{
|
|
D_DebugLog((DEB_WARN, "Trying to get inbound cred with no supplied creds, no pwd or tgt, or local only\n"));
|
|
Status = SEC_E_NO_CREDENTIALS;
|
|
goto Cleanup;
|
|
}
|
|
|
|
KerbReadLockLogonSessions(LogonSession);
|
|
Status = KerbDuplicateString(
|
|
&ServiceRealm,
|
|
&LogonSession->PrimaryCredentials.DomainName
|
|
);
|
|
|
|
KerbUnlockLogonSessions(LogonSession);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// If there was no domain name, then assume there is a KDC.
|
|
//
|
|
if (ServiceRealm.Length == 0)
|
|
{
|
|
FoundKdc = TRUE;
|
|
}
|
|
|
|
if (!FoundKdc)
|
|
{
|
|
//
|
|
// If we are a DC, or f this domain is our worksatation
|
|
// domain, check to see if
|
|
// we have a DNS name. If we do, then at one point we were
|
|
// part of an NT5 domain. Otherwise call DsGetDCName to see
|
|
// if there is a KDC around
|
|
//
|
|
|
|
if ((KerbGlobalRole == KerbRoleDomainController) ||
|
|
KerbIsThisOurDomain(
|
|
&ServiceRealm
|
|
))
|
|
{
|
|
FoundKdc = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we haven't found one yet, try looking for a KDC in
|
|
// this domain
|
|
//
|
|
|
|
if (!FoundKdc)
|
|
{
|
|
PKERB_BINDING_CACHE_ENTRY BindingHandle = NULL;
|
|
|
|
DsysAssert(ServiceRealm.MaximumLength >= ServiceRealm.Length + sizeof(WCHAR));
|
|
DsysAssert(ServiceRealm.Buffer[ServiceRealm.Length/sizeof(WCHAR)] == L'\0');
|
|
|
|
Status = KerbGetKdcBinding(
|
|
&ServiceRealm,
|
|
NULL, // no account name
|
|
0, // no desired flags,
|
|
FALSE, // don't call kadmin
|
|
FALSE,
|
|
&BindingHandle
|
|
);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
FoundKdc = TRUE;
|
|
KerbDereferenceBindingCacheEntry(BindingHandle);
|
|
}
|
|
|
|
}
|
|
if (!FoundKdc)
|
|
{
|
|
D_DebugLog((DEB_ERROR,"Didn't find KDC for domain %wZ. %ws, line %d\n",
|
|
&ServiceRealm, THIS_FILE, __LINE__ ));
|
|
Status = SEC_E_NO_AUTHENTICATING_AUTHORITY;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// In this case, we have supplied credentials.
|
|
//
|
|
D_DebugLog((DEB_TRACE, "Got supplied credentials\n"));
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
// Insert the credential into the list of credentials
|
|
//
|
|
|
|
KerbInsertCredential(Credential);
|
|
|
|
//
|
|
// Notice: the order of acquiring these locks is important.
|
|
//
|
|
|
|
*NewCredential = Credential;
|
|
|
|
Cleanup:
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
if (Credential != NULL)
|
|
{
|
|
//
|
|
// Make sure we haven't linked this one yet.
|
|
//
|
|
|
|
DsysAssert(Credential->ListEntry.ReferenceCount == 1);
|
|
KerbFreeCredential(Credential);
|
|
}
|
|
|
|
//
|
|
// Map the error if necessary. Normally STATUS_OBJECT_NAME_NOT_FOUND
|
|
// gets mapped to SEC_E_UNKNOWN_TARGET, but this is an invalid
|
|
// status to return from AcquireCredentialsHandle, so instead
|
|
// return SEC_E_NO_CREDENTIALS.
|
|
//
|
|
|
|
if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
|
|
{
|
|
Status = SEC_E_NO_CREDENTIALS;
|
|
}
|
|
}
|
|
KerbFreeString(&ServiceRealm);
|
|
return(Status);
|
|
}
|