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.
 
 
 
 
 
 

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);
}