|
|
//+-----------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (c) Microsoft Corporation 1992 - 2001
//
// File: kerbs4u.cxx
//
// Contents: Code for doing S4UToSelf() logon.
//
//
// History: 14-March-2001 Created Todds
//
//------------------------------------------------------------------------
#include <kerb.hxx>
#include <kerbp.h>
#include <kerbs4u.h>
extern "C" { #include <md5.h>
}
#ifdef DEBUG_SUPPORT
static TCHAR THIS_FILE[]=TEXT(__FILE__); #endif
#define FILENO FILENO_S4U
//
// Used for ticket leak detection.
//
#if DBG
EXTERN LIST_ENTRY GlobalTicketList; #endif
EXTERN LONG GlobalTicketListSize;
KERBEROS_LIST KerbS4UCache;
#define KerbLockS4UCache( ) SafeEnterCriticalSection( &KerbS4UCache.Lock )
#define KerbUnlockS4UCache( ) SafeLeaveCriticalSection( &KerbS4UCache.Lock )
//
// S4U Cache management functions.
//
NTSTATUS KerbInitS4UCache() { NTSTATUS Status;
Status = KerbInitializeList(&KerbS4UCache, S4U_CACHE_LOCK_ENUM); if (NT_SUCCESS( Status )) { Status = KerbScheduleS4UCleanup(); }
return Status; }
VOID KerbReferenceS4UCacheEntry( IN PKERB_S4UCACHE_DATA CacheEntry ) { DsysAssert(CacheEntry->ListEntry.ReferenceCount != 0); InterlockedIncrement( (LONG *)&CacheEntry->ListEntry.ReferenceCount ); }
VOID KerbFreeS4UCacheEntry( IN PKERB_S4UCACHE_DATA CacheEntry ) { if (ARGUMENT_PRESENT(CacheEntry)) { KerbFree(CacheEntry); } }
VOID KerbDereferenceS4UCacheEntry( IN PKERB_S4UCACHE_DATA CacheEntry ) { DsysAssert(CacheEntry->ListEntry.ReferenceCount != 0);
if ( 0 == InterlockedDecrement( (LONG *)&CacheEntry->ListEntry.ReferenceCount )) { KerbFreeS4UCacheEntry(CacheEntry); }
return; }
VOID KerbInsertS4UCacheEntry( IN PKERB_S4UCACHE_DATA CacheEntry ) {
if ( !InterlockedCompareExchange( &CacheEntry->Linked, (LONG)TRUE, (LONG)FALSE )) { InterlockedIncrement( (LONG *)&CacheEntry->ListEntry.ReferenceCount ); KerbLockS4UCache();
InsertHeadList( &KerbS4UCache.List, &CacheEntry->ListEntry.Next );
KerbUnlockS4UCache(); }
}
VOID KerbRemoveS4UCacheEntry( IN PKERB_S4UCACHE_DATA CacheEntry ) { //
// An entry can only be unlinked from the cache once
//
if ( InterlockedCompareExchange( &CacheEntry->Linked, (LONG)FALSE, (LONG)TRUE )) { DsysAssert(CacheEntry->ListEntry.ReferenceCount != 0);
KerbLockS4UCache();
RemoveEntryList(&CacheEntry->ListEntry.Next);
KerbUnlockS4UCache();
KerbDereferenceS4UCacheEntry(CacheEntry);
} }
PKERB_S4UCACHE_DATA KerbLocateS4UCacheEntry( IN PLUID LogonId, IN OUT PULONG CacheState ) { PKERB_S4UCACHE_DATA Entry = NULL; PLIST_ENTRY ListEntry = NULL; TimeStamp CurrentTime; BOOLEAN Found = FALSE;
*CacheState = 0;
KerbLockS4UCache(); for (ListEntry = KerbS4UCache.List.Flink ; ListEntry != &KerbS4UCache.List ; ListEntry = ListEntry->Flink ) {
Entry = CONTAINING_RECORD(ListEntry, KERB_S4UCACHE_DATA, ListEntry.Next);
if (RtlEqualLuid(LogonId, &Entry->LogonId)) { KerbReferenceS4UCacheEntry(Entry); *CacheState = Entry->CacheState;
GetSystemTimeAsFileTime( (PFILETIME) &CurrentTime ); if ( KerbGetTime( CurrentTime ) > KerbGetTime( Entry->CacheEndtime )) { *CacheState = S4UCACHE_TIMEOUT; }
Found = TRUE; break; }
} KerbUnlockS4UCache();
if ( !Found ) { Entry = NULL; }
return Entry;
}
//+-------------------------------------------------------------------------
//
// Function: CacheMatch
//
// Synopsis: Checks for a S4U Ticket cache match based on flags. Inline function, mainly
// provided for readability.
//
// Effects:
//
//
// Requires:
//
// Returns: The referenced cache entry or NULL if it was not found.
//
// Notes: If an invalid entry is found it may be dereferenced
//
//
//--------------------------------------------------------------------------
BOOLEAN CacheMatch( IN PKERB_TICKET_CACHE_ENTRY Entry, IN PLUID LogonId, IN PKERB_INTERNAL_NAME ClientName, IN PUNICODE_STRING ClientRealm, IN PKERB_INTERNAL_NAME AltClientName, IN ULONG LookupFlags, IN OUT BOOLEAN *Remove ) {
BOOLEAN fRet = FALSE; *Remove = FALSE;
//
// We must use a ticket which is relative to the calling
// security context.
//
if (!RtlEqualLuid(LogonId, &Entry->EvidenceLogonId)) { DebugLog((DEB_ERROR, "Cache miss due to luid\n")); return FALSE; } if (KerbTicketIsExpiring(Entry, TRUE )) { *Remove = TRUE; return FALSE; }
switch (LookupFlags) { case S4UTICKETCACHE_FOR_EVIDENCE: //
// All tickets in the S4U cache are "for evidence",
// unless they're not forwardable..
//
return ((Entry->TicketFlags & KERB_TICKET_FLAGS_forwardable) != 0);
case S4UTICKETCACHE_MATCH_ALL: //
// This is used when caching a new S4U ticket. In that case, all of the
// fields must match.
//
DsysAssert(ARGUMENT_PRESENT(ClientName)); DsysAssert(ARGUMENT_PRESENT(ClientRealm));
//
// Tickets obtained due ASC created network logons are only used for
// evidence, not for S4U.
//
if (( Entry->CacheFlags & KERB_TICKET_CACHE_ASC_TICKET ) != 0) { return FALSE; }
fRet = RtlEqualUnicodeString(ClientRealm, &Entry->ClientDomainName, TRUE); if (fRet) { fRet = KerbEqualKdcNames(ClientName, Entry->ClientName); }
if (fRet & (ARGUMENT_PRESENT(AltClientName))) { fRet = KerbEqualKdcNames(ClientName, Entry->AltClientName); }
break;
case S4UTICKETCACHE_USEALTNAME:
//
// Here, we check for a UPN match on a ticket that's
// been "normalized" through a previous S4USelf logon.
//
DsysAssert(ARGUMENT_PRESENT(AltClientName)); //
// Tickets obtained due ASC created network logons are only used for
// evidence, not for S4U.
//
if (( Entry->CacheFlags & KERB_TICKET_CACHE_ASC_TICKET ) != 0) { return FALSE; } fRet = KerbEqualKdcNames(AltClientName, Entry->AltClientName); break;
default:
//
// The default, literally. This is a simple cName / Crealm match used
// on S4USelf tickets.
//
//
// Tickets obtained due ASC created network logons are only used for
// evidence, not for S4U.
//
if (( Entry->CacheFlags & KERB_TICKET_CACHE_ASC_TICKET ) != 0) { return FALSE; } fRet = (KerbEqualKdcNames(ClientName, Entry->ClientName) & RtlEqualUnicodeString(ClientRealm, &Entry->ClientDomainName, TRUE));
break; }
return fRet; }
//+-------------------------------------------------------------------------
//
// Function: KerbLocateS4UTicketCacheEntry
//
// Synopsis: References a ticket cache entry by name
//
// Effects: Increments the reference count on the ticket cache entry
//
// Arguments: TicketCache - the ticket cache to search
// FullServiceName - Optionally contains full service name
// of target, including domain name. If it is NULL,
// then the first element in the list is returned.
// RealmName - Realm of service name. If length is zero, is not
// used for comparison.
//
// Requires:
//
// Returns: The referenced cache entry or NULL if it was not found.
//
// Notes: If an invalid entry is found it may be dereferenced
//
//
//--------------------------------------------------------------------------
PKERB_TICKET_CACHE_ENTRY KerbLocateS4UTicketCacheEntry( IN PKERB_TICKET_CACHE TicketCache, IN PLUID LogonIdToMatch, IN OPTIONAL PKERB_INTERNAL_NAME ClientName, IN OPTIONAL PUNICODE_STRING ClientRealm, IN OPTIONAL PKERB_INTERNAL_NAME AltClientName, IN ULONG LookupFlags ) { PLIST_ENTRY ListEntry; PKERB_TICKET_CACHE_ENTRY CacheEntry = NULL; BOOLEAN Found = FALSE; BOOLEAN Remove = FALSE; BOOLEAN CleanupNeeded = FALSE; BOOLEAN Timeout = FALSE; TimeStamp CurrentTime; TimeStamp CutoffTime;
GetSystemTimeAsFileTime((PFILETIME) &CurrentTime); KerbReadLockTicketCache(); KerbSetTime(&CutoffTime, KerbGetTime(TicketCache->LastCleanup) + KerbGetTime(KerbGlobalS4UTicketLifetime)); Timeout = (KerbGetTime(CurrentTime) < KerbGetTime(CutoffTime)); //
// Go through the ticket cache looking for the correct entry
//
for (ListEntry = TicketCache->CacheEntries.Flink ; ListEntry != &TicketCache->CacheEntries ; ListEntry = ListEntry->Flink ) { CacheEntry = CONTAINING_RECORD(ListEntry, KERB_TICKET_CACHE_ENTRY, ListEntry.Next);
if ( CacheMatch( CacheEntry, LogonIdToMatch, ClientName, ClientRealm, AltClientName, LookupFlags, &Remove ) ) { KerbReferenceTicketCacheEntry(CacheEntry); Found = TRUE; break; } //
// If we detect expired tickets, cleanup the cache. Only do this
// every 15 minutes, though, so we don't end up with a bunch of
// contention on the ticketcache resource in stress conditions.
//
if ( Remove ) { CleanupNeeded = Timeout; } }
KerbUnlockTicketCache();
if (!Found) { CacheEntry = NULL; }
//
// We cleanup here to avoid having to always take the write lock.
//
if ( CleanupNeeded ) { KerbAgeTickets( TicketCache ); DebugLog((DEB_TRACE_S4U, "Cleanup needed\n")); }
return(CacheEntry); }
NTSTATUS KerbGetCallingLuid( IN OUT PLUID CallingLuid, IN OPTIONAL HANDLE hProcess ) {
NTSTATUS Status, TokenStatus; SECPKG_CLIENT_INFO ClientInfo; CLIENT_ID ClientId; OBJECT_ATTRIBUTES Obja; HANDLE hClientProcess = NULL; HANDLE hToken = NULL; HANDLE ImpersonationToken = NULL; TOKEN_STATISTICS TokenStats; DWORD TokenStatsSize = sizeof(TokenStats);
if (!ARGUMENT_PRESENT(hProcess)) { Status = LsaFunctions->GetClientInfo( &ClientInfo );
if (!NT_SUCCESS( Status )) { return ( Status ); } else if ((ClientInfo.ClientFlags & SECPKG_CLIENT_THREAD_TERMINATED) != 0) { return STATUS_ACCESS_DENIED; }
ClientId.UniqueProcess = (HANDLE)LongToHandle(ClientInfo.ProcessID); ClientId.UniqueThread = (HANDLE)NULL; } else { ClientId.UniqueProcess = hProcess; ClientId.UniqueThread = (HANDLE)NULL; }
//
// If we're called by an in-proc lsass caller, we could be impersonating.
// Revert, to make sure we're ok to do the below operation.
//
TokenStatus = NtOpenThreadToken( NtCurrentThread(), TOKEN_QUERY | TOKEN_IMPERSONATE, TRUE, &ImpersonationToken );
if( NT_SUCCESS(TokenStatus) ) { //
// stop impersonating.
//
RevertToSelf(); }
InitializeObjectAttributes( &Obja, NULL, 0, NULL, NULL );
Status = NtOpenProcess( &hClientProcess, (ACCESS_MASK)PROCESS_QUERY_INFORMATION, &Obja, &ClientId );
if (NT_SUCCESS( Status )) {
Status = NtOpenProcessToken( hClientProcess, TOKEN_QUERY, &hToken );
if (NT_SUCCESS( Status ) ) {
Status = NtQueryInformationToken( hToken, TokenStatistics, &TokenStats, TokenStatsSize, &TokenStatsSize );
if (NT_SUCCESS( Status )) { RtlCopyLuid( CallingLuid, &(TokenStats.AuthenticationId) ); }
NtClose( hToken ); }
NtClose( hClientProcess ); }
if (!NT_SUCCESS( Status )) { D_DebugLog((DEB_ERROR, "Failed to get LUID\n")); }
if( ImpersonationToken != NULL ) { //
// put the thread token back if we were impersonating.
//
SetThreadToken( NULL, ImpersonationToken ); NtClose( ImpersonationToken ); }
return ( Status );
}
//+-------------------------------------------------------------------------
//
// Function: KerbInitializeS4UCacheData
//
// Synopsis: Check the S4UCACHE_DATA to see if we can do S4U for this target.
//
// Effects:
//
// Arguments: LogonSession - Logon session of the service doing the
// S4U request
//
// Requires: You must hold the logon session lock.
//
// Returns:
//
// Notes:
//
//
//+-------------------------------------------------------------------------
NTSTATUS KerbInitializeS4UCacheData( IN OUT PKERB_S4UCACHE_DATA *CacheData, IN PLUID LogonId, IN ULONG CacheFlags ) { PKERB_S4UCACHE_DATA LocalData = NULL; TimeStamp CurrentTime;
*CacheData = NULL; LocalData = (PKERB_S4UCACHE_DATA) KerbAllocate(sizeof(KERB_S4UCACHE_DATA)); if ( NULL == LocalData ) { return STATUS_NO_MEMORY; }
RtlCopyLuid( &LocalData->LogonId, LogonId );
LocalData->CacheState = CacheFlags; GetSystemTimeAsFileTime( (PFILETIME) &CurrentTime ); KerbSetTime(&LocalData->CacheEndtime, KerbGetTime(CurrentTime) + KerbGetTime( KerbGlobalS4UCacheTimeout )); KerbInsertS4UCacheEntry(LocalData);
*CacheData = LocalData; LocalData = NULL; return STATUS_SUCCESS; }
//+-------------------------------------------------------------------------
//
// Function: KerbUpdateS4UCacheData
//
// Synopsis: Update the S4UCACHE_DATA
//
// Effects:
//
// Arguments: LogonSession - Logon session of the service doing the
// S4U request
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//+-------------------------------------------------------------------------
NTSTATUS KerbUpdateS4UCacheData( IN PKERB_LOGON_SESSION LogonSession, IN ULONG Flags ) { NTSTATUS Status = STATUS_SUCCESS; TimeStamp CurrentTime;
PKERB_S4UCACHE_DATA CacheData = NULL; ULONG CacheFlags = 0;
KerbLockS4UCache(); CacheData = KerbLocateS4UCacheEntry( &LogonSession->LogonId, &CacheFlags );
if ( CacheData != NULL ) { D_DebugLog((DEB_TRACE_S4U, "Updating existing entry %p - %x\n", CacheData, Flags)); CacheData->CacheState = Flags; GetSystemTimeAsFileTime( (PFILETIME) &CurrentTime ); KerbSetTime(&CacheData->CacheEndtime, KerbGetTime(CurrentTime) + KerbGetTime( KerbGlobalS4UCacheTimeout )); KerbDereferenceS4UCacheEntry(CacheData); } else { //
// We don't have one for this logon ID. Create one.
//
Status = KerbInitializeS4UCacheData( &CacheData, &LogonSession->LogonId, Flags ); }
KerbUnlockS4UCache();
return Status; }
//+-------------------------------------------------------------------------
//
// Function: KerbCacheS4UTicket
//
// Synopsis: Ensures that the ticket we got back from the KDC actually has
// the information we requested, then caches it.
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes: This function performs a quick correctness test only before
// caching the S4U Ticket.
// We don't want to take the extra hit to decrypt this
// ticket to get "real" name, as We'll do that later, when
// we build the token (krbtoken.cxx) Later, we should see if
// there's a better way to do this.
//
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbCacheS4UTicket( IN PKERB_LOGON_SESSION CallerLogonSession, IN PKERB_KDC_REPLY KdcReply, IN PKERB_ENCRYPTED_KDC_REPLY KdcReplyBody, IN PKERB_INTERNAL_NAME OurTargetName, IN PKERB_INTERNAL_NAME S4UClientName, IN OPTIONAL PKERB_INTERNAL_NAME AltS4UClientName, IN PUNICODE_STRING S4UClientRealm, IN ULONG CacheFlags, IN OPTIONAL PKERB_TICKET_CACHE TicketCache, OUT PKERB_TICKET_CACHE_ENTRY * NewCacheEntry ) { NTSTATUS Status = STATUS_LOGON_FAILURE; KERBERR KerbErr; BOOLEAN Result = FALSE; PKERB_TICKET_CACHE_ENTRY CacheEntry = NULL; PKERB_TICKET_CACHE_ENTRY OldCacheEntry = NULL;
*NewCacheEntry = NULL;
CacheEntry = (PKERB_TICKET_CACHE_ENTRY) KerbAllocate(sizeof(KERB_TICKET_CACHE_ENTRY)); if (CacheEntry == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
CacheEntry->ListEntry.ReferenceCount = 1;
#if DBG
if ( CacheEntry ) {
KerbWriteLockTicketCache(); InsertHeadList( &GlobalTicketList, &CacheEntry->GlobalListEntry ); KerbUnlockTicketCache(); }
#endif
InterlockedIncrement( &GlobalTicketListSize );
//
// Check Client identity - if any of these *don't* match, it means our KDC
// doesn't understand the protocol, and is just giving us a ticket to ourselves,
// from ourselves - e.g. sname = us, cname = us.
//
// If the KDC understands S4U, then sname = us, but cname = client name. Same
// goes for the crealm and the target name.
//
KerbErr = KerbCompareKdcNameToPrincipalName( &KdcReply->client_name, S4UClientName, &Result );
if (!KERB_SUCCESS(KerbErr)) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } else if (!Result) { KerbReadLockLogonSessions(CallerLogonSession); //
// Hmm.. Is this the caller logon cname?
//
if (KerbCompareStringToPrincipalName( &KdcReply->client_name, &CallerLogonSession->PrimaryCredentials.UserName )) { Status = STATUS_NO_S4U_PROT_SUPPORT; } else { Status = STATUS_LOGON_FAILURE; } KerbUnlockLogonSessions(CallerLogonSession); DebugLog((DEB_ERROR, "S4UClient name != Reply Client name %x\n", Status)); goto Cleanup; }
KerbErr = KerbCompareUnicodeRealmToKerbRealm( &KdcReply->client_realm, S4UClientRealm, &Result );
if (!KERB_SUCCESS(KerbErr)) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } else if (!Result) { KerbReadLockLogonSessions(CallerLogonSession);
KerbErr = KerbCompareUnicodeRealmToKerbRealm( &KdcReply->client_realm, &CallerLogonSession->PrimaryCredentials.DomainName, &Result );
KerbUnlockLogonSessions(CallerLogonSession);
if (!KERB_SUCCESS(KerbErr)) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
Status = (Result ? STATUS_NO_S4U_PROT_SUPPORT : STATUS_LOGON_FAILURE); DebugLog((DEB_ERROR, "S4UClient REALM != Reply Client REALM %x\n", Status)); goto Cleanup; }
//
// Check target name.
//
KerbErr = KerbCompareKdcNameToPrincipalName( &KdcReply->ticket.server_name, OurTargetName, &Result );
if (!KERB_SUCCESS(KerbErr)) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } else if (!Result) { DebugLog((DEB_ERROR, "ServerName != TargetName\n")); Status = STATUS_NO_S4U_PROT_SUPPORT; goto Cleanup; }
CacheEntry->TicketFlags = KerbConvertFlagsToUlong(&KdcReplyBody->flags);
if (KdcReplyBody->bit_mask & KERB_ENCRYPTED_KDC_REPLY_starttime_present) { KerbConvertGeneralizedTimeToLargeInt( &CacheEntry->StartTime, &KdcReplyBody->KERB_ENCRYPTED_KDC_REPLY_starttime, NULL ); } else { KerbConvertGeneralizedTimeToLargeInt( &CacheEntry->StartTime, &KdcReplyBody->authtime, NULL ); }
KerbConvertGeneralizedTimeToLargeInt( &CacheEntry->EndTime, &KdcReplyBody->endtime, NULL );
if (KdcReplyBody->bit_mask & KERB_ENCRYPTED_KDC_REPLY_renew_until_present) { KerbConvertGeneralizedTimeToLargeInt( &CacheEntry->RenewUntil, &KdcReplyBody->KERB_ENCRYPTED_KDC_REPLY_renew_until, NULL ); }
//
// Time skew check ?
//
Status = KerbDuplicateKdcName( &CacheEntry->TargetName, S4UClientName );
if (!NT_SUCCESS(Status)) { goto Cleanup; }
Status = KerbDuplicateStringEx( &CacheEntry->ClientDomainName, S4UClientRealm, FALSE );
if (!NT_SUCCESS(Status)) { goto Cleanup; } Status = KerbDuplicateKdcName( &CacheEntry->ClientName, S4UClientName );
if (!NT_SUCCESS(Status)) { goto Cleanup; }
if (ARGUMENT_PRESENT( AltS4UClientName )) { Status = KerbDuplicateKdcName( &CacheEntry->AltClientName, S4UClientName );
if (!NT_SUCCESS(Status)) { goto Cleanup; } }
//
// ServiceName is unused for S4U but other spots in Kerberos expect it
// to be non-NULL. Dupe the ClientName into it as the best match.
//
Status = KerbDuplicateKdcName( &CacheEntry->ServiceName, OurTargetName );
if (!NT_SUCCESS(Status)) { goto Cleanup; }
if (!KERB_SUCCESS(KerbDuplicateTicket( &CacheEntry->Ticket, &KdcReply->ticket ))) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
CacheEntry->EvidenceLogonId = CallerLogonSession->LogonId; CacheEntry->CacheFlags = CacheFlags | KERB_TICKET_CACHE_S4U_TICKET; if (ARGUMENT_PRESENT(TicketCache)) { //
// Before we insert this ticket we want to remove any
// previous instances of tickets to the same service.
//
OldCacheEntry = KerbLocateS4UTicketCacheEntry( TicketCache, &CallerLogonSession->LogonId, S4UClientName, S4UClientRealm, AltS4UClientName, S4UTICKETCACHE_MATCH_ALL );
if (OldCacheEntry != NULL) { D_DebugLog((DEB_TRACE_S4U, "Removing S4U Cache Entry - %p\n", OldCacheEntry)); KerbRemoveTicketCacheEntry( OldCacheEntry ); KerbDereferenceTicketCacheEntry( OldCacheEntry ); }
KerbInsertTicketCacheEntry( TicketCache, CacheEntry ); }
*NewCacheEntry = CacheEntry; CacheEntry = NULL;
Cleanup:
if (!NT_SUCCESS(Status)) { if (NULL != CacheEntry) { KerbDereferenceTicketCacheEntry(CacheEntry); } }
return(Status); }
//+-------------------------------------------------------------------------
//
// Function: KerbGetS4UClientRealm
//
// Synopsis: Gets the client realm for a given UPN
//
//
// Effects:
//
// Arguments: LogonSession - Logon session of the service doing the
// S4U request
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbGetS4UClientRealm( IN PKERB_LOGON_SESSION LogonSession, IN PKERB_INTERNAL_NAME * S4UClientName, IN OUT PUNICODE_STRING S4UTargetRealm // TBD: Place for credential handle?
) { NTSTATUS Status = STATUS_SUCCESS; KERBERR KerbErr; PKERB_INTERNAL_NAME KdcServiceKdcName = NULL; PKERB_INTERNAL_NAME ClientName = NULL; UNICODE_STRING ClientRealm = {0}; UNICODE_STRING CorrectRealm = {0}; ULONG RetryCount = KERB_CLIENT_REFERRAL_MAX; ULONG RequestFlags = 0; BOOLEAN MitRealmLogon = FALSE; PKERB_TICKET_CACHE_ENTRY TicketCacheEntry = NULL;
RtlInitUnicodeString( S4UTargetRealm, NULL );
//
// Use our server credentials to start off the AS_REQ process.
// We may get a referral elsewhere, however.
//
Status = KerbGetClientNameAndRealm( NULL, &LogonSession->PrimaryCredentials, FALSE, NULL, &MitRealmLogon, TRUE, &ClientName, &ClientRealm );
if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR,"Failed to get client name & realm: 0x%x, %ws line %d\n", Status, THIS_FILE, __LINE__ )); goto Cleanup; }
GetTicketRestart:
D_DebugLog((DEB_TRACE_S4U, "KerbGetS4UClientRealm GetTicketRestart ClientRealm %wZ\n", &ClientRealm));
KerbErr = KerbBuildFullServiceKdcName( &ClientRealm, &KerbGlobalKdcServiceName, KRB_NT_SRV_INST, &KdcServiceKdcName );
if (!KERB_SUCCESS(KerbErr)) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
Status = KerbGetAuthenticationTicket( LogonSession, NULL, NULL, FALSE, // no preauth..
KdcServiceKdcName, &ClientRealm, (*S4UClientName), RequestFlags, KERB_TICKET_CACHE_PRIMARY_TGT, &TicketCacheEntry, NULL, &CorrectRealm ); //
// If it failed but gave us another realm to try, go there
//
if (!NT_SUCCESS(Status) && (CorrectRealm.Length != 0)) { if (--RetryCount != 0) { KerbFreeKdcName(&KdcServiceKdcName); KerbFreeString(&ClientRealm); ClientRealm = CorrectRealm; CorrectRealm.Buffer = NULL; goto GetTicketRestart; } else { // Tbd: Log error here? Max referrals reached..
goto Cleanup; }
}
//
// If we get STATUS_WRONG_PASSWORD, we succeeded in finding the
// client realm. Otherwise, we're hosed. As the password we used
// is bogus, we should never succeed, btw...
//
if (Status == STATUS_WRONG_PASSWORD) { *S4UTargetRealm = ClientRealm; Status = STATUS_SUCCESS; }
Cleanup:
// if we succeeded, we got the correct realm,
// and we need to pass that back to caller
if (!NT_SUCCESS(Status)) { KerbFreeString(&ClientRealm); }
KerbFreeKdcName(&KdcServiceKdcName); KerbFreeKdcName(&ClientName);
if (NULL != TicketCacheEntry) { KerbRemoveTicketCacheEntry(TicketCacheEntry); KerbDereferenceTicketCacheEntry(TicketCacheEntry); }
return(Status); }
//+-------------------------------------------------------------------------
//
// Function: KerbPackAndSignS4UPreauthData
//
// Synopsis: Attempt to gets TGT for an S4U client for name
// location purposes.
//
//
// Effects:
//
// Arguments: LogonSession - Logon session of the service doing the
// S4U request
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbSignAndPackS4UPreauthData( IN OUT PKERB_PA_DATA_LIST * PreAuthData, IN PKERB_TICKET_CACHE_ENTRY TicketGrantingTicket, IN PKERB_PA_FOR_USER S4UPreauth ) { NTSTATUS Status = STATUS_SUCCESS; KERBERR KerbErr;
PKERB_PA_DATA_LIST ListElement = NULL; unsigned char Hash[MD5DIGESTLEN];
*PreAuthData = NULL; S4UPreauth->cksum.checksum.value = Hash;
Status = KerbHashS4UPreauth( S4UPreauth, &TicketGrantingTicket->SessionKey, KERB_CHECKSUM_HMAC_MD5, &S4UPreauth->cksum );
if ( !NT_SUCCESS(Status) ) { goto Cleanup; }
ListElement = (PKERB_PA_DATA_LIST) KerbAllocate(sizeof(KERB_PA_DATA_LIST)); if (ListElement == NULL) { Status = STATUS_NO_MEMORY; goto Cleanup; }
KerbErr = KerbPackData( S4UPreauth, KERB_PA_FOR_USER_PDU, (PULONG) &ListElement->value.preauth_data.length, (PUCHAR *) &ListElement->value.preauth_data.value );
if (!KERB_SUCCESS(KerbErr)) { Status = KerbMapKerbError(KerbErr); goto Cleanup; }
ListElement->value.preauth_data_type = KRB5_PADATA_FOR_USER; ListElement->next = NULL;
*PreAuthData = ListElement; ListElement = NULL;
Cleanup:
if (ListElement) { KerbFreePreAuthData(ListElement); }
return Status; }
//+-------------------------------------------------------------------------
//
// Function: KerbBuildS4UPreauth
//
// Synopsis: Attempt to gets TGT for an S4U client for name
// location purposes.
//
//
// Effects:
//
// Arguments: LogonSession - Logon session of the service doing the
// S4U request
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
KERBERR KerbBuildS4UPreauth( IN OUT PKERB_PA_FOR_USER S4UPreauth, IN PKERB_INTERNAL_NAME S4UClientName, IN PUNICODE_STRING S4UClientRealm, IN OPTIONAL PKERB_MESSAGE_BUFFER AuthorizationData ) { KERBERR KerbErr; LPSTR PackageName = MICROSOFT_KERBEROS_NAME_A;
KerbErr = KerbConvertKdcNameToPrincipalName( &S4UPreauth->userName, S4UClientName );
if (!KERB_SUCCESS(KerbErr)) { goto Cleanup; }
KerbErr = KerbConvertUnicodeStringToRealm( &S4UPreauth->userRealm, S4UClientRealm );
if (!KERB_SUCCESS(KerbErr)) { goto Cleanup; }
if (ARGUMENT_PRESENT(AuthorizationData)) { S4UPreauth->authorization_data.value = AuthorizationData->Buffer; S4UPreauth->authorization_data.length = AuthorizationData->BufferSize; S4UPreauth->bit_mask |= KERB_PA_FOR_USER_authorization_data_present; }
S4UPreauth->authentication_package = PackageName;
Cleanup:
return KerbErr; }
//+-------------------------------------------------------------------------
//
// Function: KerbGetTgtToS4URealm
//
// Synopsis: We need a TGT to the client realm under the caller's cred's
// so we can make a S4U TGS_REQ.
//
//
// Effects:
//
// Arguments: LogonSession - Logon session of the service doing the
// S4U request
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbGetTgtToS4URealm( IN PKERB_LOGON_SESSION CallerLogonSession, IN PKERB_CREDENTIAL Credential, IN PUNICODE_STRING S4UClientRealm, IN OUT PKERB_TICKET_CACHE_ENTRY * S4UTgt, IN ULONG Flags, IN ULONG TicketOptions, IN ULONG EncryptionType ) { NTSTATUS Status; ULONG RetryFlags = 0; BOOLEAN CrossRealm = FALSE; BOOLEAN TicketCacheLocked = FALSE, LogonSessionsLocked = FALSE;
PKERB_TICKET_CACHE_ENTRY TicketGrantingTicket = NULL; PKERB_TICKET_CACHE_ENTRY LastTgt = NULL; PKERB_TICKET_CACHE_ENTRY TicketCacheEntry = NULL; PKERB_KDC_REPLY KdcReply = NULL; PKERB_ENCRYPTED_KDC_REPLY KdcReplyBody = NULL; PKERB_INTERNAL_NAME TargetTgtKdcName = NULL; UNICODE_STRING ClientRealm = NULL_UNICODE_STRING; PKERB_PRIMARY_CREDENTIAL PrimaryCredentials = NULL;
*S4UTgt = NULL;
KerbReadLockLogonSessions(CallerLogonSession); LogonSessionsLocked = TRUE;
if ((Credential != NULL) && (Credential->SuppliedCredentials != NULL)) { PrimaryCredentials = Credential->SuppliedCredentials; } else { PrimaryCredentials = &CallerLogonSession->PrimaryCredentials; }
Status = KerbGetTgtForService( CallerLogonSession, Credential, NULL, NULL, S4UClientRealm, 0, &TicketGrantingTicket, &CrossRealm );
if (!NT_SUCCESS(Status)) { goto Cleanup; }
//
// If this isn't cross realm, then we've got a TGT to the realm.
// return it and bail.
//
if (!CrossRealm) { *S4UTgt = TicketGrantingTicket; TicketGrantingTicket = NULL; goto Cleanup; }
if (!KERB_SUCCESS(KerbBuildFullServiceKdcName( S4UClientRealm, &KerbGlobalKdcServiceName, KRB_NT_SRV_INST, &TargetTgtKdcName ))) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
//
// Copy out the client realm name which is used when obtaining the ticket
//
Status = KerbDuplicateString( &ClientRealm, &PrimaryCredentials->DomainName ); if (!NT_SUCCESS(Status)) { goto Cleanup; }
if (LogonSessionsLocked) { KerbUnlockLogonSessions(CallerLogonSession); LogonSessionsLocked = FALSE; }
//
// Do some referral chasing to get our ticket grantin ticket.
//
while (!RtlEqualUnicodeString( S4UClientRealm, &TicketGrantingTicket->TargetDomainName, TRUE )) {
//
// If we just got two TGTs for the same domain, then we must have
// gotten as far as we can. Chances our our RealTargetRealm is a
// variation of what the KDC hands out.
//
// Typically, this call handles the Netbios / DNS case, where the
// TGT we're looking for (netbios form) is one we just got back.
//
if ((LastTgt != NULL) && RtlEqualUnicodeString( &LastTgt->TargetDomainName, &TicketGrantingTicket->TargetDomainName, TRUE )) { if (TicketCacheLocked) { KerbUnlockTicketCache(); TicketCacheLocked = FALSE; }
KerbSetTicketCacheEntryTarget( S4UClientRealm, LastTgt );
D_DebugLog((DEB_TRACE_CTXT, "Got two TGTs for same realm (%wZ), bailing out of referral loop\n", &LastTgt->TargetDomainName)); break; }
D_DebugLog((DEB_TRACE_CTXT, "Getting referral TGT for \n")); D_KerbPrintKdcName((DEB_TRACE_CTXT, TargetTgtKdcName)); D_KerbPrintKdcName((DEB_TRACE_CTXT, TicketGrantingTicket->ServiceName));
if (TicketCacheLocked) { KerbUnlockTicketCache(); TicketCacheLocked = FALSE; }
//
// Cleanup old state
//
KerbFreeTgsReply(KdcReply); KerbFreeKdcReplyBody(KdcReplyBody); KdcReply = NULL; KdcReplyBody = NULL;
Status = KerbGetTgsTicket( &ClientRealm, TicketGrantingTicket, TargetTgtKdcName, FALSE, TicketOptions, EncryptionType, NULL, NULL, // no PA data here.
NULL, // no tgt reply since target is krbtgt
NULL, NULL, // let kdc determine end time
&KdcReply, &KdcReplyBody, &RetryFlags );
if (!NT_SUCCESS(Status)) { DebugLog((DEB_WARN,"Failed to get TGS ticket for service 0x%x :", Status )); KerbPrintKdcName(DEB_WARN, TargetTgtKdcName); DebugLog((DEB_WARN, "%ws, line %d\n", THIS_FILE, __LINE__));
//
// We want to map cross-domain failures to failures indicating
// that a KDC could not be found. This means that for Kerberos
// logons, the negotiate code will retry with a different package
//
// if (Status == STATUS_NO_TRUST_SAM_ACCOUNT)
// {
// Status = STATUS_NO_LOGON_SERVERS;
// }
goto Cleanup; }
//
// Now we have a ticket - Cache it
//
DsysAssert( !LogonSessionsLocked ); KerbWriteLockLogonSessions(CallerLogonSession); LogonSessionsLocked = TRUE;
Status = KerbCreateTicketCacheEntry( KdcReply, KdcReplyBody, NULL, // no target name
NULL, // no targe realm
0, // no flags
&CallerLogonSession->PrimaryCredentials.AuthenticationTicketCache, NULL, // no credential key
&TicketCacheEntry );
KerbUnlockLogonSessions(CallerLogonSession); LogonSessionsLocked = FALSE;
if (!NT_SUCCESS(Status)) { goto Cleanup; }
if (LastTgt != NULL) { KerbDereferenceTicketCacheEntry(LastTgt); LastTgt = NULL; } LastTgt = TicketGrantingTicket; TicketGrantingTicket = TicketCacheEntry; TicketCacheEntry = NULL;
DsysAssert( !TicketCacheLocked ); KerbReadLockTicketCache(); TicketCacheLocked = TRUE;
} // ** WHILE **
*S4UTgt = TicketGrantingTicket; TicketGrantingTicket = NULL;
Cleanup:
if (TicketCacheLocked) { KerbUnlockTicketCache(); }
if (LogonSessionsLocked) { KerbUnlockLogonSessions(CallerLogonSession); }
KerbFreeTgsReply( KdcReply ); KerbFreeKdcReplyBody( KdcReplyBody ); KerbFreeKdcName( &TargetTgtKdcName );
if (TicketGrantingTicket != NULL) { KerbDereferenceTicketCacheEntry(TicketGrantingTicket); }
if (LastTgt != NULL) { KerbDereferenceTicketCacheEntry(LastTgt); }
KerbFreeString( &ClientRealm ); return Status; }
//+-------------------------------------------------------------------------
//
// Function: KerbGetS4UTargetName
//
// Synopsis: Attempt to gets target for TGS_REQ
//
//
// Effects:
//
// Arguments: LogonSession - Logon session of the service doing the
// S4U request
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//----------------------------------
NTSTATUS KerbGetS4UTargetName( IN OUT PKERB_INTERNAL_NAME * NewTarget, IN PKERB_PRIMARY_CREDENTIAL Cred, IN PLUID CallerLuid, IN OUT PBOOLEAN Upn ) { NTSTATUS Status = STATUS_SUCCESS; USHORT i; BOOLEAN Found = FALSE;
PKERB_INTERNAL_NAME FinalTarget = NULL; LUID System = SYSTEM_LUID; LUID NetworkService = NETWORKSERVICE_LUID;
*NewTarget = NULL; *Upn = FALSE;
if (RtlEqualLuid(&System, CallerLuid) || RtlEqualLuid(&NetworkService, CallerLuid)) { KerbGlobalReadLock();
Status = KerbDuplicateKdcName( NewTarget, KerbGlobalMitMachineServiceName );
KerbGlobalReleaseLock(); goto cleanup; }
//
// This is a non-system caller. We'll want to use a UPN for the
// target name - do we have a UPN in the primary credential? If not,
// fake one up based on the "implicit" UPN.
//
if (Cred->DomainName.Length == 0) { //
// Better have an @ sign in the user name, or we're through.
//
for (i=0;i < Cred->UserName.Length ;i++) { if (Cred->UserName.Buffer[i] == L'@') { Found = TRUE; break; } }
if (!Found) { Status = SEC_E_NO_CREDENTIALS; D_DebugLog((DEB_ERROR, "Invalid user name for S4u\n")); goto cleanup; }
FinalTarget = (PKERB_INTERNAL_NAME) MIDL_user_allocate( KERB_INTERNAL_NAME_SIZE(1) + Cred->UserName.MaximumLength );
if (FinalTarget == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto cleanup; }
FinalTarget->NameCount = 1; FinalTarget->NameType = KRB_NT_ENTERPRISE_PRINCIPAL;
Status = KerbDuplicateString( &FinalTarget->Names[0], &Cred->UserName );
if (!NT_SUCCESS(Status)) { goto cleanup; }
} else { //
// Craft a UPN out of thin air :)
//
PWSTR Offset;
FinalTarget = (PKERB_INTERNAL_NAME) MIDL_user_allocate( KERB_INTERNAL_NAME_SIZE(1) + Cred->UserName.Length + Cred->DomainName.Length + (2*sizeof(WCHAR)) );
if (FinalTarget == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto cleanup; }
FinalTarget->NameCount = 1; FinalTarget->NameType = KRB_NT_ENTERPRISE_PRINCIPAL; FinalTarget->Names[0].Length = Cred->UserName.Length + Cred->DomainName.Length + sizeof(WCHAR);
FinalTarget->Names[0].MaximumLength = Cred->UserName.Length + Cred->DomainName.Length + (2*sizeof(WCHAR));
FinalTarget->Names[0].Buffer = (PWSTR) RtlOffsetToPointer(FinalTarget, sizeof(KERB_INTERNAL_NAME));
RtlCopyMemory( FinalTarget->Names[0].Buffer, Cred->UserName.Buffer, Cred->UserName.Length );
Offset = (PWSTR) FinalTarget->Names[0].Buffer + (Cred->UserName.Length/sizeof(WCHAR));
(*Offset) = L'@'; Offset++;
RtlCopyMemory( Offset, Cred->DomainName.Buffer, Cred->DomainName.Length ); }
*Upn = TRUE; *NewTarget = FinalTarget;
cleanup:
return Status; }
//+-------------------------------------------------------------------------
//
// Function: KerbGetS4UServiceTicket
//
// Synopsis: Gets an s4uself service ticket.
//
//
// Effects:
//
// Arguments: LogonSession - Logon session of the service doing the
// S4U request
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbGetS4USelfServiceTicket( IN PKERB_LOGON_SESSION CallerLogonSession, IN PKERB_LOGON_SESSION NewLogonSession, IN PKERB_CREDENTIAL Credential, IN PKERB_INTERNAL_NAME S4UClientName, IN PKERB_INTERNAL_NAME AltS4UClientName, IN PUNICODE_STRING S4UClientRealm, IN OUT PKERB_TICKET_CACHE_ENTRY * S4UTicket, IN ULONG Flags, IN ULONG TicketOptions, IN ULONG EncryptionType, IN OPTIONAL PKERB_MESSAGE_BUFFER AdditionalAuthData ) { NTSTATUS Status; PKERB_TICKET_CACHE_ENTRY TicketCacheEntry = NULL; PKERB_TICKET_CACHE_ENTRY TicketGrantingTicket = NULL; PKERB_TICKET_CACHE_ENTRY LastTgt = NULL; PKERB_KDC_REPLY KdcReply = NULL; PKERB_ENCRYPTED_KDC_REPLY KdcReplyBody = NULL; PKERB_PA_DATA_LIST S4UPaDataList = NULL; BOOLEAN LogonSessionsLocked = FALSE; ULONG S4UEvidenceTicketCacheFlags = 0; KERB_ENCRYPTION_KEY U2UServerSKey = {0}; KERB_TGT_REPLY FakeTgtReply = {0}; PKERB_TICKET_CACHE_ENTRY pRealTicketGrantingTicket = NULL; UNICODE_STRING NoTargetName = {0};
PKERB_INTERNAL_NAME RealTargetName = NULL; PKERB_INTERNAL_NAME TargetName = NULL;
UNICODE_STRING RealTargetRealm = NULL_UNICODE_STRING; PKERB_INTERNAL_NAME TargetTgtKdcName = NULL; PKERB_PRIMARY_CREDENTIAL PrimaryCredentials = NULL; BOOLEAN UsedCredentials = FALSE, Upn = FALSE; UNICODE_STRING ClientRealm = NULL_UNICODE_STRING; BOOLEAN CacheTicket = KerbGlobalCacheS4UTicket; ULONG ReferralCount = 0; ULONG RetryFlags = 0; PLUID CallingLogonId; KERB_PA_FOR_USER S4UPreAuth = {0}; BOOLEAN fMITRetryAlreadyMade = FALSE; BOOLEAN TgtRetryMade = FALSE; TimeStamp RequestBodyEndTime;
GetSystemTimeAsFileTime((PFILETIME) &RequestBodyEndTime); RequestBodyEndTime.QuadPart += KerbGetTime(KerbGlobalS4UTicketLifetime);
//
// Check to see if the credential has any primary credentials
//
TGTRetry:
DsysAssert( !LogonSessionsLocked ); KerbReadLockLogonSessions(CallerLogonSession); LogonSessionsLocked = TRUE;
if ((Credential != NULL) && (Credential->SuppliedCredentials != NULL)) { PrimaryCredentials = Credential->SuppliedCredentials; UsedCredentials = TRUE; CallingLogonId = &Credential->LogonId; } else { PrimaryCredentials = &CallerLogonSession->PrimaryCredentials; CallingLogonId = &CallerLogonSession->LogonId; }
Status = KerbGetS4UTargetName( &TargetName, PrimaryCredentials, CallingLogonId, &Upn );
if (!NT_SUCCESS(Status)) { goto Cleanup; }
if ((Flags & (KERB_GET_TICKET_NO_CACHE | KERB_TARGET_DID_ALTNAME_LOOKUP)) == 0) { TicketCacheEntry = KerbLocateS4UTicketCacheEntry( &PrimaryCredentials->S4UTicketCache, CallingLogonId, S4UClientName, S4UClientRealm, NULL, 0 ); } else if (( Flags & KERB_GET_TICKET_NO_CACHE ) != 0 ) { CacheTicket = FALSE; }
if (TicketCacheEntry != NULL) { ULONG TicketFlags; ULONG CacheTicketFlags; ULONG CacheEncryptionType;
//
// Check if the flags are present
//
KerbReadLockTicketCache(); CacheTicketFlags = TicketCacheEntry->TicketFlags; CacheEncryptionType = TicketCacheEntry->Ticket.encrypted_part.encryption_type; KerbUnlockTicketCache();
TicketFlags = KerbConvertKdcOptionsToTicketFlags(TicketOptions);
if (((CacheTicketFlags & TicketFlags) != TicketFlags) || ((EncryptionType != 0) && (CacheEncryptionType != EncryptionType)))
{ KerbDereferenceTicketCacheEntry(TicketCacheEntry); TicketCacheEntry = NULL; } else { //
// Check the ticket time
//
if (KerbTicketIsExpiring(TicketCacheEntry, TRUE)) { KerbDereferenceTicketCacheEntry(TicketCacheEntry); TicketCacheEntry = NULL; } else { goto SuccessOut; } } }
if ( LogonSessionsLocked ) { KerbUnlockLogonSessions(CallerLogonSession); LogonSessionsLocked = FALSE; }
//
// Get a krbtgt/S4URealm ticket
//
D_DebugLog((DEB_TRACE_S4U, "KerbGetS4UServiceTicket calling KerbGetTgtToS4URealm ClientRealm %wZ\n", S4UClientRealm));
Status = KerbGetTgtToS4URealm( CallerLogonSession, Credential, S4UClientRealm, &TicketGrantingTicket, 0, // tbd: support for these options?
0, EncryptionType );
if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "KerbGetS4UServiceTicket cannot get S4U Tgt - %x\n", Status)); goto Cleanup; }
//
// Build the preauth for our TGS req
//
Status = KerbBuildS4UPreauth( &S4UPreAuth, S4UClientName, S4UClientRealm, AdditionalAuthData );
if (!NT_SUCCESS(Status)) { D_DebugLog((DEB_ERROR, "KerbBuildS4UPreauth failed - %x\n",Status)); goto Cleanup; }
//
// Pack and sign the preauth based on the session key in the ticketgranting
// ticket - we'll need to do this now, and for final TGS_REQ
//
Status = KerbSignAndPackS4UPreauthData( &S4UPaDataList, TicketGrantingTicket, &S4UPreAuth );
if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "FAILED to sign S4u preauth (%p) %x\n", TicketGrantingTicket, Status)); DsysAssert(FALSE); goto Cleanup; }
//
// Copy out the client realm name which is used when obtaining the ticket
//
KerbReadLockLogonSessions(CallerLogonSession); LogonSessionsLocked = TRUE;
Status = KerbDuplicateString( &ClientRealm, &PrimaryCredentials->DomainName ); if (!NT_SUCCESS(Status)) { goto Cleanup; }
DsysAssert( LogonSessionsLocked ); KerbUnlockLogonSessions(CallerLogonSession); LogonSessionsLocked = FALSE;
ReferralRestart:
//
// This is our first S4U TGS_REQ. We'll eventually transit back to our
// realm. Note: If we get back a referral, the KDC reply is a TGT to
// another realm, so keep trying, but be sure to use that TGT.
//
if (RtlEqualUnicodeString( &ClientRealm, &TicketGrantingTicket->TargetDomainName, TRUE) && (NULL == PrimaryCredentials->Passwords)) // user2user required
{ BOOLEAN CrossRealm = FALSE;
//
// below call requires ls lock be held...
//
KerbReadLockLogonSessions( CallerLogonSession ); Status = KerbGetTgtForService( CallerLogonSession, Credential, NULL, // no credman cred
& PrimaryCredentials->DomainName, // supplied realm
&NoTargetName, KERB_TICKET_CACHE_PRIMARY_TGT, &pRealTicketGrantingTicket, &CrossRealm );
KerbUnlockLogonSessions( CallerLogonSession );
if (NT_SUCCESS(Status)) { ASSERT(pRealTicketGrantingTicket);
if (CrossRealm) { Status = STATUS_INTERNAL_ERROR; DebugLog((DEB_ERROR, "KerbGetS4UServiceTicket KLIN(%#x) got unexpected cross realm TGT\n", KLIN(FILENO, __LINE__))); } else { FakeTgtReply.version = KERBEROS_VERSION; FakeTgtReply.message_type = KRB_TGT_REP;
FakeTgtReply.ticket = pRealTicketGrantingTicket->Ticket; } }
if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "KerbGetS4UServiceTicket KLIN(%#x) failed to get tgt\n", KLIN(FILENO, __LINE__))); goto Cleanup; } }
D_DebugLog((DEB_TRACE, "KerbGetS4UServiceTicket KLIN(%#x) calling KerbGetTgsTicket (ReferralRestart)\n", KLIN(FILENO, __LINE__)));
Status = KerbGetTgsTicket( &ClientRealm, TicketGrantingTicket, TargetName, Flags, TicketOptions, EncryptionType, NULL, S4UPaDataList, pRealTicketGrantingTicket ? &FakeTgtReply : NULL, NULL, &RequestBodyEndTime, &KdcReply, &KdcReplyBody, &RetryFlags );
if (STATUS_USER2USER_REQUIRED == Status) { Status = STATUS_SUCCESS;
if (!pRealTicketGrantingTicket) { BOOLEAN CrossRealm = FALSE;
KerbReadLockLogonSessions( CallerLogonSession ); Status = KerbGetTgtForService( CallerLogonSession, Credential, NULL, // no credman cred
& PrimaryCredentials->DomainName, // supplied realm
&NoTargetName, KERB_TICKET_CACHE_PRIMARY_TGT, &pRealTicketGrantingTicket, &CrossRealm );
KerbUnlockLogonSessions( CallerLogonSession );
if (NT_SUCCESS(Status) ) { ASSERT(pRealTicketGrantingTicket);
if (!CrossRealm) { FakeTgtReply.version = KERBEROS_VERSION; FakeTgtReply.message_type = KRB_TGT_REP;
FakeTgtReply.ticket = pRealTicketGrantingTicket->Ticket; } else { Status = STATUS_INTERNAL_ERROR; DebugLog((DEB_ERROR, "KerbGetS4UServiceTicket KLIN(%#x) got unexpected cross realm TGT\n", KLIN(FILENO, __LINE__))); } }
if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "KerbGetS4UServiceTicket KLIN(%#x) failed to get tgt\n", KLIN(FILENO, __LINE__))); goto Cleanup; } }
if (NT_SUCCESS(Status)) { D_DebugLog((DEB_TRACE_U2U, "KerbGetS4UServiceTicket KLIN(%#x) calling KerbGetTgsTicket\n", KLIN(FILENO, __LINE__)));
Status = KerbGetTgsTicket( &ClientRealm, TicketGrantingTicket, TargetName, FALSE, TicketOptions, EncryptionType, NULL, S4UPaDataList, &FakeTgtReply, NULL, &RequestBodyEndTime, &KdcReply, &KdcReplyBody, &RetryFlags ); }
if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "KerbGetS4UServiceTicket failed to get TGS ticket for service vis u2u 0x%x\n", Status)); goto Cleanup; } }
if (NT_SUCCESS(Status) && pRealTicketGrantingTicket) { KerbReadLockTicketCache(); KerbDuplicateKey(&U2UServerSKey, &(pRealTicketGrantingTicket->SessionKey)); S4UEvidenceTicketCacheFlags |= KERB_TICKET_CACHE_TKT_ENC_IN_SKEY; KerbUnlockTicketCache(); }
//
// We're done w/ S4UTgt. Deref, and check
// for errors
//
if (TicketGrantingTicket != NULL) { //
// Check for bad option TGT purging
//
if (((RetryFlags & KERB_RETRY_WITH_NEW_TGT) != 0) && !TgtRetryMade) { DebugLog((DEB_WARN, "Doing TGT retry - %p\n", TicketGrantingTicket));
//
// Unlink && purge bad tgt
//
KerbRemoveTicketCacheEntry(TicketGrantingTicket); KerbDereferenceTicketCacheEntry(TicketGrantingTicket); // free from list
TgtRetryMade = TRUE; TicketGrantingTicket = NULL; goto TGTRetry; }
KerbDereferenceTicketCacheEntry(TicketGrantingTicket); TicketGrantingTicket = NULL; }
if (!NT_SUCCESS(Status)) { //
// Check for the MIT retry case
// TBD: Clean this "code" up...
//
if (((RetryFlags & KERB_MIT_NO_CANONICALIZE_RETRY) != 0) && (!fMITRetryAlreadyMade)) { Status = KerbMITGetMachineDomain( TargetName, S4UClientRealm, &TicketGrantingTicket );
if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR,"Failed Query policy information %ws, line %d\n", THIS_FILE, __LINE__)); goto Cleanup; }
fMITRetryAlreadyMade = TRUE;
goto TGTRetry; }
DebugLog((DEB_WARN, "Failed to get TGS ticket for service 0x%x : \n", Status)); KerbPrintKdcName(DEB_WARN, TargetName); DebugLog((DEB_WARN, "%ws, line %d\n", THIS_FILE, __LINE__)); goto Cleanup; }
//
// Check for referral info in the name
// Should be there if S4Urealm != Our Realm
Status = KerbGetReferralNames( KdcReplyBody, TargetName, &RealTargetRealm );
if (!NT_SUCCESS(Status)) { goto Cleanup; }
//
// If this is not a referral ticket, just cache it and return
// the cache entry.
//
if (RealTargetRealm.Length == 0) { //
// Now we have a ticket - lets cache it
//
KerbWriteLockLogonSessions(CallerLogonSession);
Status = KerbCacheS4UTicket( CallerLogonSession, KdcReply, KdcReplyBody, TargetName, S4UClientName, AltS4UClientName, S4UClientRealm, S4UEvidenceTicketCacheFlags, CacheTicket ? &PrimaryCredentials->S4UTicketCache : NULL, &TicketCacheEntry );
KerbUnlockLogonSessions(CallerLogonSession);
if (!NT_SUCCESS(Status)) { goto Cleanup; }
//
// We're done, so get out of here.
//
goto SuccessOut; }
//
// The server referred us to another domain. Get the service's full
// name from the ticket and try to find a TGT in that domain.
//
Status = KerbDuplicateKdcName( &RealTargetName, TargetName );
if (!NT_SUCCESS(Status)) { goto Cleanup; }
D_DebugLog((DEB_TRACE_CTXT, "KerbGetS4UServiceTicket got referral ticket for service ")); D_KerbPrintKdcName((DEB_TRACE_CTXT, TargetName)); D_DebugLog((DEB_TRACE_CTXT, "in realm %wZ ", &RealTargetRealm));
//
// Turn the KDC reply (xrealm tgt w/ s4u pac) into something we can use,
// but *don't* cache it.
//
Status = KerbCreateTicketCacheEntry( KdcReply, KdcReplyBody, NULL, // no target name
NULL, 0, // no flags
NULL, // don't cache
NULL, // no credential key
&TicketCacheEntry );
if (!NT_SUCCESS(Status)) { goto Cleanup; }
TicketGrantingTicket = TicketCacheEntry; TicketCacheEntry = NULL;
//
// cleanup
//
KerbFreeTgsReply(KdcReply); KerbFreeKdcReplyBody(KdcReplyBody); KdcReply = NULL; KdcReplyBody = NULL;
//
// Now we are in a case where we have a realm to aim for and a TGT. While
// we don't have a TGT for the target realm, get one.
//
if (!KERB_SUCCESS(KerbBuildFullServiceKdcName( &RealTargetRealm, &KerbGlobalKdcServiceName, KRB_NT_SRV_INST, &TargetTgtKdcName ))) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
//
// Referral chasing code block - very important to get right
// If we know the "real" target realm, eg. from GC, then
// we'll walk trusts until we hit "real" target realm.
//
while (!RtlEqualUnicodeString( &RealTargetRealm, &TicketGrantingTicket->TargetDomainName, TRUE)) { //
// If we just got two TGTs for the same domain, then we must have
// gotten as far as we can. Chances our our RealTargetRealm is a
// variation of what the KDC hands out.
//
D_DebugLog((DEB_TRACE_CTXT, "RealTargetRealm %wZ\n", &RealTargetRealm)); D_DebugLog((DEB_TRACE_CTXT, "TGT %wZ\n", &TicketGrantingTicket->TargetDomainName));
if ((LastTgt != NULL) && RtlEqualUnicodeString( &LastTgt->TargetDomainName, &TicketGrantingTicket->TargetDomainName, TRUE )) { KerbSetTicketCacheEntryTarget( &RealTargetRealm, LastTgt );
D_DebugLog((DEB_TRACE_CTXT, "Got two TGTs for same realm (%wZ), S4U\n", &LastTgt->TargetDomainName)); break; }
D_DebugLog((DEB_TRACE_CTXT, "KerbGetS4UServiceTicket getting referral TGT for TargetTgtKdcName ")); D_KerbPrintKdcName((DEB_TRACE_CTXT, TargetTgtKdcName));
//
// Cleanup old state
//
KerbFreeTgsReply(KdcReply); KerbFreeKdcReplyBody(KdcReplyBody); KdcReply = NULL; KdcReplyBody = NULL;
Status = KerbGetTgsTicket( &ClientRealm, TicketGrantingTicket, TargetTgtKdcName, FALSE, TicketOptions, EncryptionType, NULL, NULL, NULL, // no tgt reply since target is krbtgt
NULL, &RequestBodyEndTime, &KdcReply, &KdcReplyBody, &RetryFlags );
if (!NT_SUCCESS(Status)) { DebugLog((DEB_WARN, "KerbGetTgsTicket failed to get TGS ticket for service 0x%x :", Status)); KerbPrintKdcName(DEB_WARN, TargetTgtKdcName); DebugLog((DEB_WARN, "%ws, line %d\n", THIS_FILE, __LINE__));
//
// We want to map cross-domain failures to failures indicating
// that a KDC could not be found. This means that for Kerberos
// logons, the negotiate code will retry with a different package
//
// if (Status == STATUS_NO_TRUST_SAM_ACCOUNT)
// {
// Status = STATUS_NO_LOGON_SERVERS;
// }
goto Cleanup; }
//
// Now we have a ticket - don't cache it, however, as it has the
// user's PAC, and shouldn't be re-used later.
//
Status = KerbCreateTicketCacheEntry( KdcReply, KdcReplyBody, NULL, NULL, 0, // no flags
NULL, NULL, // no credential key
&TicketCacheEntry );
if (!NT_SUCCESS(Status)) { goto Cleanup; }
if (LastTgt != NULL) { KerbDereferenceTicketCacheEntry(LastTgt); LastTgt = NULL; } LastTgt = TicketGrantingTicket; TicketGrantingTicket = TicketCacheEntry; TicketCacheEntry = NULL; } // ** WHILE **
//
// Now we must have a TGT to our service's domain. Get a ticket
// to the service.
//
//
// Cleanup old state
//
KerbFreeTgsReply(KdcReply); KerbFreeKdcReplyBody(KdcReplyBody); KerbFreePreAuthData(S4UPaDataList); KdcReply = NULL; KdcReplyBody = NULL; S4UPaDataList = NULL;
//
// Pack and sign the preauth based on the session key in the ticketgranting
// ticket
//
Status = KerbSignAndPackS4UPreauthData( &S4UPaDataList, TicketGrantingTicket, &S4UPreAuth );
if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "Error signing S4u preauth (%p) %x\n", TicketGrantingTicket, Status)); DsysAssert(FALSE); goto Cleanup; }
if (NULL != PrimaryCredentials->Passwords) // no user2user
{ D_DebugLog((DEB_TRACE, "KerbGetS4UServiceTicket xForest final calling KerbGetTgsTicket\n"));
Status = KerbGetTgsTicket( &ClientRealm, TicketGrantingTicket, TargetName, FALSE, TicketOptions, EncryptionType, NULL, S4UPaDataList, NULL, NULL, &RequestBodyEndTime, &KdcReply, &KdcReplyBody, &RetryFlags ); }
if ((NULL == PrimaryCredentials->Passwords) || (STATUS_USER2USER_REQUIRED == Status)) { BOOLEAN CrossRealm = FALSE;
if (!pRealTicketGrantingTicket) {
KerbReadLockLogonSessions( CallerLogonSession ); Status = KerbGetTgtForService( CallerLogonSession, Credential, NULL, // no credman cred
& PrimaryCredentials->DomainName, // supplied realm
&NoTargetName, KERB_TICKET_CACHE_PRIMARY_TGT, &pRealTicketGrantingTicket, &CrossRealm );
KerbUnlockLogonSessions( CallerLogonSession );
if (NT_SUCCESS(Status)) { ASSERT(pRealTicketGrantingTicket);
if (CrossRealm) { Status = STATUS_INTERNAL_ERROR; DebugLog((DEB_ERROR, "KerbGetS4UServiceTicket got unexpected cross realm TGT\n")); } else { FakeTgtReply.version = KERBEROS_VERSION; FakeTgtReply.message_type = KRB_TGT_REP;
FakeTgtReply.ticket = pRealTicketGrantingTicket->Ticket; } } }
if (NT_SUCCESS(Status)) { D_DebugLog((DEB_TRACE_U2U, "KerbGetS4UServiceTicket xForest final u2u calling KerbGetTgsTicket\n"));
Status = KerbGetTgsTicket( &ClientRealm, TicketGrantingTicket, TargetName, FALSE, TicketOptions, EncryptionType, NULL, S4UPaDataList, &FakeTgtReply, NULL, &RequestBodyEndTime, &KdcReply, &KdcReplyBody, &RetryFlags ); }
if (NT_SUCCESS(Status)) { ASSERT((S4UEvidenceTicketCacheFlags & KERB_TICKET_CACHE_TKT_ENC_IN_SKEY) == 0);
KerbReadLockTicketCache(); KerbDuplicateKey(&U2UServerSKey, &(pRealTicketGrantingTicket->SessionKey)); S4UEvidenceTicketCacheFlags |= KERB_TICKET_CACHE_TKT_ENC_IN_SKEY; KerbUnlockTicketCache(); }
if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "KerbGetS4UServiceTicket failed to get TGS ticket for service vis u2u 0x%x\n", Status)); goto Cleanup; } } else if (!NT_SUCCESS(Status)) { DebugLog((DEB_WARN,"Failed to get TGS ticket for service 0x%x ", Status )); KerbPrintKdcName(DEB_WARN, RealTargetName); DebugLog((DEB_WARN, "%ws, line %d\n", THIS_FILE, __LINE__)); goto Cleanup; }
//
// Now that we are in the domain to which we were referred, check for referral
// info in the name
//
KerbFreeString(&RealTargetRealm); Status = KerbGetReferralNames( KdcReplyBody, RealTargetName, &RealTargetRealm ); if (!NT_SUCCESS(Status)) { goto Cleanup; }
//
// If this is not a referral ticket, just cache it and return
// the cache entry.
//
if (RealTargetRealm.Length != 0) { //
// To prevent loops, we limit the number of referral we'll take
//
if (ReferralCount > KerbGlobalMaxReferralCount) { DebugLog((DEB_ERROR, "KerbGetS4UServiceTicket Maximum referral count exceeded for name: ")); KerbPrintKdcName(DEB_ERROR, RealTargetName); Status = STATUS_MAX_REFERRALS_EXCEEDED; goto Cleanup; }
ReferralCount++;
//
// Don't cache the interdomain TGT, as it has PAC info in it.
//
Status = KerbCreateTicketCacheEntry( KdcReply, KdcReplyBody, NULL, // no target name
NULL, // no target realm
0, // no flags
NULL, // don't cache
NULL, // no credential key
&TicketCacheEntry ); //
// Cleanup old state
//
KerbFreeTgsReply(KdcReply); KerbFreeKdcReplyBody(KdcReplyBody); KerbFreePreAuthData(S4UPaDataList); KdcReply = NULL; KdcReplyBody = NULL; S4UPaDataList = NULL;
if (!NT_SUCCESS(Status)) { goto Cleanup; }
if (LastTgt != NULL) { KerbDereferenceTicketCacheEntry(LastTgt); LastTgt = NULL; }
LastTgt = TicketGrantingTicket; TicketGrantingTicket = TicketCacheEntry; TicketCacheEntry = NULL;
D_DebugLog((DEB_TRACE_CTXT, "KerbGetS4UServiceTicket restart referral: %wZ", &RealTargetRealm));
Status = KerbSignAndPackS4UPreauthData( &S4UPaDataList, TicketGrantingTicket, &S4UPreAuth );
if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "Error signing S4u preauth (%p) %x\n", TicketGrantingTicket, Status)); DsysAssert(FALSE); goto Cleanup; }
goto ReferralRestart; }
//
// Now we have a ticket - lets cache it
//
//
// Before doing anything, verify that the client name on the ticket
// is equal to the client name requested during the S4u
//
KerbWriteLockLogonSessions(CallerLogonSession);
Status = KerbCacheS4UTicket( CallerLogonSession, KdcReply, KdcReplyBody, TargetName, S4UClientName, AltS4UClientName, S4UClientRealm, S4UEvidenceTicketCacheFlags, CacheTicket ? &PrimaryCredentials->S4UTicketCache : NULL, &TicketCacheEntry );
KerbUnlockLogonSessions(CallerLogonSession); if (!NT_SUCCESS(Status)) { goto Cleanup; }
SuccessOut:
if (TicketCacheEntry && (S4UEvidenceTicketCacheFlags & KERB_TICKET_CACHE_TKT_ENC_IN_SKEY)) { KerbWriteLockTicketCache();
ASSERT(TicketCacheEntry->CacheFlags & KERB_TICKET_CACHE_TKT_ENC_IN_SKEY);
KerbFreeKey(&(TicketCacheEntry->SessionKey)); DebugLog((DEB_TRACE_U2U, "KerbGetS4UServiceTicket returning u2u SKEY\n"));
KerbDuplicateKey(&TicketCacheEntry->SessionKey, &U2UServerSKey);
KerbUnlockTicketCache(); }
*S4UTicket = TicketCacheEntry; TicketCacheEntry = NULL;
Cleanup:
KerbFreeTgsReply( KdcReply ); KerbFreeKdcReplyBody( KdcReplyBody ); KerbFreeKdcName( &TargetTgtKdcName ); KerbFreeString( &RealTargetRealm ); KerbFreeKdcName(&RealTargetName); KerbFreeKdcName(&TargetName); KerbFreePrincipalName(&S4UPreAuth.userName); KerbFreeRealm(&S4UPreAuth.userRealm); KerbFreePreAuthData(S4UPaDataList);
if (pRealTicketGrantingTicket) { KerbDereferenceTicketCacheEntry(pRealTicketGrantingTicket); }
if (LogonSessionsLocked) { KerbUnlockLogonSessions(CallerLogonSession); }
KerbFreeString(&RealTargetRealm);
//
// We never cache TGTs in this routine
// so it's just a blob of memory
//
if (TicketGrantingTicket != NULL) { KerbDereferenceTicketCacheEntry(TicketGrantingTicket); } if (LastTgt != NULL) { KerbDereferenceTicketCacheEntry(LastTgt); }
//
// If we still have a pointer to the ticket cache entry, free it now.
//
if (TicketCacheEntry != NULL) { KerbRemoveTicketCacheEntry( TicketCacheEntry ); KerbDereferenceTicketCacheEntry(TicketCacheEntry); }
KerbFreeString(&ClientRealm); KerbFreeKey(&U2UServerSKey);
return (Status); }
//+-------------------------------------------------------------------------
//
// Function: KerbPrepareEvidenceTicket
//
// Synopsis: Decrypts a ticket w/ key1, and encrypts it w/ key2
//
// Effects:
//
// Arguments: DecryptKey - key used to decrypt ticket
// EncryptionKey - key used to re-encrypt ticket
// Ticket - the ticket to decrypt / re-encrypt
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbPrepareEvidenceTicket( IN PKERB_ENCRYPTION_KEY DecryptKey, IN PKERB_ENCRYPTION_KEY EncryptionKey, IN OUT PKERB_TICKET Ticket ) { KERBERR KerbErr; NTSTATUS Status = STATUS_LOGON_FAILURE; PUCHAR EncryptedPart = NULL; ULONG EncryptSize;
SafeAllocaAllocate( EncryptedPart, Ticket->encrypted_part.cipher_text.length ); if (EncryptedPart == NULL) { return STATUS_NO_MEMORY; }
EncryptSize = Ticket->encrypted_part.cipher_text.length; KerbErr = KerbDecryptDataEx( &Ticket->encrypted_part, DecryptKey, KERB_TICKET_SALT, &EncryptSize, EncryptedPart );
if (!KERB_SUCCESS( KerbErr )) { goto Cleanup; }
//
// Free up the old encrypted_part.
//
if ( Ticket->encrypted_part.cipher_text.value != NULL ) { MIDL_user_free( Ticket->encrypted_part.cipher_text.value ); Ticket->encrypted_part.cipher_text.value = NULL; Ticket->encrypted_part.cipher_text.length = 0; }
//
// allocate sufficient space for the encrypted data.
//
KerbErr = KerbAllocateEncryptionBufferWrapper( EncryptionKey->keytype, EncryptSize, &Ticket->encrypted_part.cipher_text.length, &Ticket->encrypted_part.cipher_text.value );
if (!KERB_SUCCESS(KerbErr)) { goto Cleanup; }
KerbErr = KerbEncryptDataEx( &Ticket->encrypted_part, EncryptSize, EncryptedPart, KERB_NO_KEY_VERSION, KERB_TICKET_SALT, EncryptionKey );
if (!KERB_SUCCESS( KerbErr )) { goto Cleanup; }
Status = STATUS_SUCCESS;
Cleanup:
if ( !KERB_SUCCESS(KerbErr) && ( Ticket->encrypted_part.cipher_text.value != NULL) ) { MIDL_user_free( Ticket->encrypted_part.cipher_text.value ); Ticket->encrypted_part.cipher_text.value = NULL; Ticket->encrypted_part.cipher_text.length = 0; }
SafeAllocaFree( EncryptedPart ); return Status;
}
//+-------------------------------------------------------------------------
//
// Function: KerbGetTicketByS4UProxy
//
// Synopsis: Gets a ticket to a service and handles cross-domain referrals
//
// Effects:
//
// Arguments: LogonSession - the logon session to use for ticket caching
// and the identity of the caller.
// TargetName - Name of the target for which to obtain a ticket.
// TargetDomainName - Domain name of target
// Flags - Flags about the request
// TicketOptions - KDC options flags
// EncryptionType - optional Requested encryption type
// ErrorMessage - Error message from an AP request containing hints
// for next ticket.
// AuthorizationData - Optional authorization data to stick
// in the ticket.
// TgtReply - TGT to use for getting a ticket with enc_tkt_in_skey
// TicketCacheEntry - Receives a referenced ticket cache entry.
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbGetServiceTicketByS4UProxy( IN PKERB_LOGON_SESSION LogonSession, IN PKERB_LOGON_SESSION CallerLogonSession, IN PKERB_CREDENTIAL Credential, IN PKERB_TICKET_CACHE_ENTRY EvidenceTicketCacheEntry, IN PKERB_INTERNAL_NAME TargetName, IN PUNICODE_STRING TargetDomainName, IN OPTIONAL PKERB_SPN_CACHE_ENTRY SpnCacheEntry, IN ULONG Flags, IN OPTIONAL ULONG TicketOptions, IN OPTIONAL ULONG EncryptionType, IN OPTIONAL PKERB_ERROR ErrorMessage, IN OPTIONAL PKERB_AUTHORIZATION_DATA AuthorizationData, IN OPTIONAL PKERB_TGT_REPLY TgtReply, OUT PKERB_TICKET_CACHE_ENTRY * NewCacheEntry, OUT LPGUID pLogonGuid OPTIONAL ) { NTSTATUS Status = STATUS_SUCCESS; PKERB_TICKET_CACHE_ENTRY TicketCacheEntry = NULL; PKERB_TICKET_CACHE_ENTRY TicketGrantingTicket = NULL; PKERB_TICKET_CACHE_ENTRY PrimaryTgt = NULL; PKERB_TICKET_CACHE_ENTRY LastTgt = NULL; PKERB_KDC_REPLY KdcReply = NULL; PKERB_ENCRYPTED_KDC_REPLY KdcReplyBody = NULL; KERB_TICKET EvidenceTicket = {0}; PKERB_ENCRYPTION_KEY DecryptKey = NULL; BOOLEAN LogonSessionsLocked = FALSE; BOOLEAN TicketCacheLocked = FALSE; PKERB_INTERNAL_NAME RealTargetName = NULL; UNICODE_STRING RealTargetRealm = NULL_UNICODE_STRING; UNICODE_STRING SpnTargetRealm = NULL_UNICODE_STRING; PKERB_INTERNAL_NAME TargetTgtKdcName = NULL; PKERB_PRIMARY_CREDENTIAL ProxyServerCreds = NULL; PKERB_PRIMARY_CREDENTIAL PrimaryCreds = NULL; UNICODE_STRING ClientRealm = NULL_UNICODE_STRING; BOOLEAN CacheTicket = TRUE; ULONG ReferralCount = 0; ULONG RetryFlags = 0; ULONG S4UFlags = S4UCACHE_S4U_AVAILABLE; BOOLEAN CacheBasedFailure = FALSE, Update = FALSE; TimeStamp RequestBodyEndTime;
GetSystemTimeAsFileTime((PFILETIME) &RequestBodyEndTime); RequestBodyEndTime.QuadPart += KerbGetTime(KerbGlobalS4UTicketLifetime);
//
// Get our credentials - TBD: Fester: Supplied creds irrelevant?
//
DsysAssert( !LogonSessionsLocked ); KerbReadLockLogonSessions( LogonSession ); PrimaryCreds = &LogonSession->PrimaryCredentials; LogonSessionsLocked = TRUE;
ProxyServerCreds = &CallerLogonSession->PrimaryCredentials;
//
// Make sure the name is not zero length
//
if ((TargetName->NameCount == 0) || (TargetName->Names[0].Length == 0)) { D_DebugLog((DEB_ERROR,"Kdc GetServiceTicket: trying to crack zero length name.\n")); Status = SEC_E_TARGET_UNKNOWN; goto Cleanup; }
//
// First check the ticket cache for this logon session. We don't look
// for the target principal name because we can't be assured that this
// is a valid principal name for the target. If we are doing user-to-
// user, don't use the cache because the tgt key may have changed
//
if ((TgtReply == NULL) && ((Flags & KERB_GET_TICKET_NO_CACHE) == 0)) { TicketCacheEntry = KerbLocateTicketCacheEntry( &PrimaryCreds->ServerTicketCache, TargetName, TargetDomainName ); } else { //
// We don't want to cache user-to-user tickets
//
CacheTicket = FALSE; }
if (TicketCacheEntry != NULL) { //
// If we were given an error message that indicated a bad password
// throw away the cached ticket
//
if (ARGUMENT_PRESENT(ErrorMessage) && ((KERBERR) ErrorMessage->error_code == KRB_AP_ERR_MODIFIED)) { KerbRemoveTicketCacheEntry(TicketCacheEntry); KerbDereferenceTicketCacheEntry(TicketCacheEntry); TicketCacheEntry = NULL; } else { ULONG TicketFlags; ULONG CacheTicketFlags; ULONG CacheEncryptionType;
//
// Check if the flags are present
//
KerbReadLockTicketCache(); CacheTicketFlags = TicketCacheEntry->TicketFlags; CacheEncryptionType = TicketCacheEntry->Ticket.encrypted_part.encryption_type; KerbUnlockTicketCache();
TicketFlags = KerbConvertKdcOptionsToTicketFlags(TicketOptions);
if (((CacheTicketFlags & TicketFlags) != TicketFlags) || ((EncryptionType != 0) && (CacheEncryptionType != EncryptionType)))
{ KerbDereferenceTicketCacheEntry(TicketCacheEntry); TicketCacheEntry = NULL; } else { //
// Check the ticket time
//
if (KerbTicketIsExpiring(TicketCacheEntry, TRUE)) { KerbDereferenceTicketCacheEntry(TicketCacheEntry); TicketCacheEntry = NULL; } else { *NewCacheEntry = TicketCacheEntry; D_DebugLog((DEB_TRACE_S4U,"Found S4U ticket cache entry %x\n", TicketCacheEntry)); D_KerbPrintKdcName((DEB_TRACE_S4U, TicketCacheEntry->TargetName)); D_DebugLog((DEB_TRACE_S4U,"Realm - %wZ\n", &TicketCacheEntry->DomainName)); TicketCacheEntry = NULL; goto Cleanup; } } } }
//
// Determine the state of the SPNCache using information in the credential.
// Only do this if we've not been handed
//
if ( ARGUMENT_PRESENT(SpnCacheEntry) && TargetDomainName->Buffer == NULL ) { Status = KerbGetSpnCacheStatus( SpnCacheEntry, ProxyServerCreds, &SpnTargetRealm );
if (NT_SUCCESS( Status )) { KerbFreeString(&RealTargetRealm); RealTargetRealm = SpnTargetRealm; RtlZeroMemory(&SpnTargetRealm, sizeof(UNICODE_STRING)); D_DebugLog((DEB_TRACE_SPN_CACHE, "Found SPN cache entry - %wZ\n", &RealTargetRealm)); D_KerbPrintKdcName((DEB_TRACE_SPN_CACHE, TargetName)); } else if ( Status != STATUS_NO_MATCH ) { D_DebugLog((DEB_TRACE_SPN_CACHE, "KerbGetSpnCacheStatus failed %x\n", Status)); D_DebugLog((DEB_TRACE_SPN_CACHE, "TargetName: \n")); D_KerbPrintKdcName((DEB_TRACE_SPN_CACHE, TargetName));
CacheBasedFailure = TRUE; goto Cleanup; }
Status = STATUS_SUCCESS; }
//
// If the caller wanted any special options, don't cache this ticket.
//
if ((TicketOptions != 0) || (EncryptionType != 0) || ((Flags & KERB_GET_TICKET_NO_CACHE) != 0)) { CacheTicket = FALSE; }
DsysAssert( LogonSessionsLocked ); KerbUnlockLogonSessions(LogonSession); LogonSessionsLocked = FALSE;
//
// No cached entry was found so go ahead and call the KDC to
// get the ticket.
//
//
// First call always starts w/ TGT to our domain. This allows us to
// determine if we've met the requirements of the S4U logon, e.g. is the
// target name appropriate?
//
KerbReadLockLogonSessions( CallerLogonSession );
PrimaryTgt = KerbLocateTicketCacheEntryByRealm( &ProxyServerCreds->AuthenticationTicketCache, NULL, KERB_TICKET_CACHE_PRIMARY_TGT );
KerbUnlockLogonSessions( CallerLogonSession );
if ( NULL == PrimaryTgt ) { Status = KerbGetTicketGrantingTicket( CallerLogonSession, NULL, NULL, NULL, &PrimaryTgt, NULL // don't return credential key
);
if (!NT_SUCCESS( Status )) { D_DebugLog((DEB_ERROR, "Failed to get primary TGT from logon session\n")); goto Cleanup; } }
//
// Copy out the client realm name which is used when obtaining the ticket
//
Status = KerbDuplicateString( &ClientRealm, &ProxyServerCreds->DomainName ); if (!NT_SUCCESS(Status)) { goto Cleanup; }
//
// Prepare the evidence ticket.
//
if (!KERB_SUCCESS( KerbDuplicateTicket( &EvidenceTicket, &EvidenceTicketCacheEntry->Ticket ))) { Status = STATUS_NO_MEMORY; goto Cleanup; }
//
// Now its's time to make the request.
//
D_DebugLog((DEB_TRACE_S4U, "KerbGetServiceTicketByS4UProxy EvidenceTicketCacheEntry %p\n", EvidenceTicketCacheEntry));
Status = KerbGetTgsTicket( &ClientRealm, PrimaryTgt, TargetName, Flags, TicketOptions, EncryptionType, NULL, NULL, // no pa data
TgtReply, // This is for the service directly, so use TGT
&EvidenceTicket, &RequestBodyEndTime, &KdcReply, &KdcReplyBody, &RetryFlags );
if (!NT_SUCCESS( Status )) { DebugLog((DEB_WARN, "Failed S4Uproxy request %x(%x) \n", Status, RetryFlags)); goto Cleanup; }
//
// Check for referral info in the name
//
Status = KerbGetReferralNames( KdcReplyBody, TargetName, &RealTargetRealm );
if (!NT_SUCCESS(Status)) { goto Cleanup; }
//
// If this is not a referral ticket, just cache it and return
// the cache entry.
//
if ( RealTargetRealm.Length == 0 ) { //
// Now we have a ticket - lets cache it
//
KerbReadLockLogonSessions(LogonSession);
Status = KerbCreateTicketCacheEntry( KdcReply, KdcReplyBody, TargetName, TargetDomainName, 0, CacheTicket ? &PrimaryCreds->ServerTicketCache : NULL, NULL, // no credential key
&TicketCacheEntry );
KerbUnlockLogonSessions( LogonSession );
if (!NT_SUCCESS( Status )) { goto Cleanup; }
*NewCacheEntry = TicketCacheEntry; TicketCacheEntry = NULL; Update = TRUE;
//
// We're done, so get out of here.
//
goto Cleanup; }
//
// REFERRAL - The service we're talking to does not live in the caller's
// account domain.
//
//
// Get the real target name
//
Status = KerbDuplicateKdcName( &RealTargetName, TargetName );
if (!NT_SUCCESS(Status)) { goto Cleanup; }
D_DebugLog((DEB_TRACE_S4U, "Got referral ticket for service \n")); D_KerbPrintKdcName((DEB_TRACE_S4U,TargetName)); D_DebugLog((DEB_TRACE_S4U, "in realm \n")); D_KerbPrintKdcName((DEB_TRACE_S4U,RealTargetName));
//
// Generate a ticket cache entry for the interdomain
// TGT accompanying this KdcReply.
//
Status = KerbCreateTicketCacheEntry( KdcReply, KdcReplyBody, NULL, // no target name
NULL, // no target realm
0, // no flags
NULL, // don't cache this one.
NULL, // don't copy cred key
&TicketCacheEntry );
if (!NT_SUCCESS( Status )) { goto Cleanup; }
//
// We've got a new TGT from the referral.
// Create a plaintext version of our evidence ticket, and
// use the session key from the TGT to create an encrypted ticket.
//
KerbDereferenceTicketCacheEntry(PrimaryTgt); TicketGrantingTicket = TicketCacheEntry; TicketCacheEntry = NULL;
KerbReadLockLogonSessions( CallerLogonSession );
DecryptKey = KerbGetKeyFromList( CallerLogonSession->PrimaryCredentials.Passwords, TicketGrantingTicket->SessionKey.keytype );
KerbUnlockLogonSessions( CallerLogonSession );
if ( DecryptKey == NULL ) { DebugLog((DEB_ERROR, "Could not find a key to decrypt evidence ticket\n")); goto Cleanup; }
Status = KerbPrepareEvidenceTicket( DecryptKey, &TicketGrantingTicket->SessionKey, &EvidenceTicket );
if ( Status == STATUS_LOGON_FAILURE ) { //
// Old Key?
//
KerbReadLockLogonSessions( CallerLogonSession );
if (CallerLogonSession->PrimaryCredentials.OldPasswords != NULL) { DecryptKey = KerbGetKeyFromList( CallerLogonSession->PrimaryCredentials.OldPasswords, TicketGrantingTicket->SessionKey.keytype );
if (DecryptKey != NULL ) { Status = KerbPrepareEvidenceTicket( DecryptKey, &TicketGrantingTicket->SessionKey, &EvidenceTicket ); }
}
KerbUnlockLogonSessions( CallerLogonSession ); }
if (!NT_SUCCESS(Status)) { D_DebugLog((DEB_ERROR, "Failed to prepare evidence ticket\n")); goto Cleanup; }
DecryptKey = &TicketGrantingTicket->SessionKey;
//
// Now we are in a case where we have a realm to aim for and a TGT. While
// we don't have a TGT for the target realm, get one.
//
ReferralRestart:
if (!KERB_SUCCESS(KerbBuildFullServiceKdcName( &RealTargetRealm, &KerbGlobalKdcServiceName, KRB_NT_SRV_INST, &TargetTgtKdcName ))) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
DsysAssert(!TicketCacheLocked); KerbReadLockTicketCache(); TicketCacheLocked = TRUE;
//
// Referral chasing code block - very important to get right
// If we know the "real" target realm, eg. from GC, then
// we'll walk trusts until we hit "real" target realm.
//
while (!RtlEqualUnicodeString( &RealTargetRealm, &TicketGrantingTicket->TargetDomainName, TRUE )) {
//
// If we just got two TGTs for the same domain, then we must have
// gotten as far as we can. Chances our our RealTargetRealm is a
// variation of what the KDC hands out.
//
if ((LastTgt != NULL) && RtlEqualUnicodeString( &LastTgt->TargetDomainName, &TicketGrantingTicket->TargetDomainName, TRUE )) {
KerbUnlockTicketCache();
KerbSetTicketCacheEntryTarget( &RealTargetRealm, LastTgt );
KerbReadLockTicketCache(); TicketCacheLocked = TRUE; D_DebugLog((DEB_ERROR, "Got two TGTs for same realm (%wZ), bailing out of referral loop\n", &LastTgt->TargetDomainName)); break; }
D_DebugLog((DEB_TRACE_S4U, "Getting referral TGT for \n")); D_KerbPrintKdcName((DEB_TRACE_S4U, TargetTgtKdcName)); D_KerbPrintKdcName((DEB_TRACE_S4U, TicketGrantingTicket->ServiceName));
KerbUnlockTicketCache(); TicketCacheLocked = FALSE;
//
// Cleanup old state
//
KerbFreeTgsReply(KdcReply); KerbFreeKdcReplyBody(KdcReplyBody); KdcReply = NULL; KdcReplyBody = NULL;
Status = KerbGetTgsTicket( &ClientRealm, TicketGrantingTicket, TargetTgtKdcName, FALSE, TicketOptions, EncryptionType, AuthorizationData, NULL, // no pa data
NULL, // no tgt reply since target is krbtgt
&EvidenceTicket, NULL, &KdcReply, &KdcReplyBody, &RetryFlags );
if (!NT_SUCCESS(Status)) { DebugLog((DEB_WARN,"Failed to get TGS ticket for service 0x%x :", Status )); KerbPrintKdcName(DEB_WARN, TargetTgtKdcName ); DebugLog((DEB_WARN, "%ws, line %d\n", THIS_FILE, __LINE__));
goto Cleanup; }
KerbReadLockLogonSessions(LogonSession); LogonSessionsLocked = TRUE;
Status = KerbCreateTicketCacheEntry( KdcReply, KdcReplyBody, NULL, // no target name
NULL, // no targe realm
0, // no flags
NULL, // no cache
NULL, // no cred key
&TicketCacheEntry );
KerbUnlockLogonSessions(LogonSession); LogonSessionsLocked = FALSE;
if (!NT_SUCCESS(Status)) { goto Cleanup; }
if (LastTgt != NULL) { KerbDereferenceTicketCacheEntry(LastTgt); LastTgt = NULL; } LastTgt = TicketGrantingTicket; TicketGrantingTicket = TicketCacheEntry; TicketCacheEntry = NULL;
Status = KerbPrepareEvidenceTicket( DecryptKey, &TicketGrantingTicket->SessionKey, &EvidenceTicket );
if (!NT_SUCCESS( Status )) { D_DebugLog((DEB_ERROR, "Failed to prepare evidence ticket\n")); goto Cleanup; }
DecryptKey = &TicketGrantingTicket->SessionKey;
KerbReadLockTicketCache(); TicketCacheLocked = TRUE; } // ** WHILE **
DsysAssert(TicketCacheLocked); KerbUnlockTicketCache(); TicketCacheLocked = FALSE;
//
// Now we must have a TGT to the destination domain. Get a ticket
// to the service.
//
//
// Cleanup old state
//
KerbFreeTgsReply(KdcReply); KerbFreeKdcReplyBody(KdcReplyBody); KdcReply = NULL; KdcReplyBody = NULL; RetryFlags = 0;
Status = KerbGetTgsTicket( &ClientRealm, TicketGrantingTicket, RealTargetName, FALSE, TicketOptions, EncryptionType, AuthorizationData, NULL, // no pa data
TgtReply, &EvidenceTicket, &RequestBodyEndTime, &KdcReply, &KdcReplyBody, &RetryFlags );
if (!NT_SUCCESS(Status)) { DebugLog((DEB_WARN,"Failed to get TGS S4U ticket for service 0x%x ", Status )); KerbPrintKdcName(DEB_WARN, RealTargetName); DebugLog((DEB_WARN, "%ws, line %d\n", THIS_FILE, __LINE__)); goto Cleanup; }
//
// Now that we are in the domain to which we were referred, check for referral
// info in the name
//
KerbFreeString(&RealTargetRealm); Status = KerbGetReferralNames( KdcReplyBody, RealTargetName, &RealTargetRealm ); if (!NT_SUCCESS(Status)) { goto Cleanup; }
//
// If this is not a referral ticket, just cache it and return
// the cache entry.
//
if (RealTargetRealm.Length != 0) { //
// To prevent loops, we limit the number of referral we'll take
//
if (ReferralCount > KerbGlobalMaxReferralCount) { D_DebugLog((DEB_ERROR,"Maximum referral count exceeded for name: ")); D_KerbPrintKdcName((DEB_ERROR,RealTargetName)); Status = STATUS_MAX_REFERRALS_EXCEEDED; goto Cleanup; }
ReferralCount++;
Status = KerbCreateTicketCacheEntry( KdcReply, KdcReplyBody, NULL, // no target name
NULL, // no target realm
0, // no flags
NULL, // no cache
NULL, // no cred key
&TicketCacheEntry );
//
// Cleanup old state
//
KerbFreeTgsReply(KdcReply); KerbFreeKdcReplyBody(KdcReplyBody); KdcReply = NULL; KdcReplyBody = NULL;
if (!NT_SUCCESS(Status)) { goto Cleanup; }
if (LastTgt != NULL) { KerbDereferenceTicketCacheEntry(LastTgt); LastTgt = NULL; }
LastTgt = TicketGrantingTicket; TicketGrantingTicket = TicketCacheEntry; TicketCacheEntry = NULL;
Status = KerbPrepareEvidenceTicket( DecryptKey, &TicketGrantingTicket->SessionKey, &EvidenceTicket );
if (!NT_SUCCESS( Status )) { DebugLog((DEB_ERROR, "Failed to prepare evidence ticket %x\n", Status)); goto Cleanup; }
DecryptKey = &TicketGrantingTicket->SessionKey;
D_DebugLog((DEB_TRACE_S4U, "Restart referral:%wZ", &RealTargetRealm)); goto ReferralRestart; }
KerbReadLockLogonSessions(LogonSession); LogonSessionsLocked = TRUE;
//
// We've got our S4U service ticket. Cache it.
//
Status = KerbCreateTicketCacheEntry( KdcReply, KdcReplyBody, TargetName, TargetDomainName, 0, // no flags
CacheTicket ? &PrimaryCreds->ServerTicketCache : NULL, NULL, // no cred key
&TicketCacheEntry );
KerbUnlockLogonSessions(LogonSession); LogonSessionsLocked = FALSE;
if (!NT_SUCCESS(Status)) { goto Cleanup; }
D_DebugLog((DEB_TRACE_S4U, "Got the S4U ticket (%p)\n", TicketCacheEntry));
*NewCacheEntry = TicketCacheEntry; TicketCacheEntry = NULL; Update = TRUE;
Cleanup:
//
// Bad or unlocatable SPN -- Don't update if we got the value from the cache, though.
//
if (( TargetName->NameType == KRB_NT_SRV_INST ) && ( NT_SUCCESS(Status) || Status == STATUS_NO_TRUST_SAM_ACCOUNT ) && ( !CacheBasedFailure )) { NTSTATUS Tmp; ULONG UpdateValue = KERB_SPN_UNKNOWN; PUNICODE_STRING Realm = NULL;
if ( NT_SUCCESS( Status )) { Realm = &(*NewCacheEntry)->TargetDomainName; UpdateValue = KERB_SPN_KNOWN; } Tmp = KerbUpdateSpnCacheEntry( SpnCacheEntry, TargetName, ProxyServerCreds, UpdateValue, Realm ); }
//
// Update the S4U cache
//
if (( RetryFlags & ( KERB_RETRY_DISABLE_S4U | KERB_RETRY_NO_S4UMATCH)) != 0) { S4UFlags = ((RetryFlags & KERB_RETRY_DISABLE_S4U) ? S4UCACHE_S4U_UNAVAILABLE : S4UCACHE_S4U_AVAILABLE );
//
// Allow downgrade to NTLM.
//
Status = SEC_E_NO_CREDENTIALS; Update = TRUE; }
if ( Update ) { KerbUpdateS4UCacheData( CallerLogonSession, S4UFlags ); }
KerbFreeTgsReply( KdcReply ); KerbFreeKdcReplyBody( KdcReplyBody ); KerbFreeKdcName( &TargetTgtKdcName ); KerbFreeString( &RealTargetRealm ); KerbFreeKdcName(&RealTargetName); KerbFreeString( &SpnTargetRealm);
KerbFreeDuplicatedTicket( &EvidenceTicket );
if (TicketCacheLocked) { KerbUnlockTicketCache(); }
if (LogonSessionsLocked) { KerbUnlockLogonSessions(LogonSession); }
KerbFreeString(&RealTargetRealm);
if (TicketGrantingTicket != NULL) { if (Status == STATUS_WRONG_PASSWORD) { KerbRemoveTicketCacheEntry( TicketGrantingTicket ); } KerbDereferenceTicketCacheEntry(TicketGrantingTicket); } if (LastTgt != NULL) { KerbDereferenceTicketCacheEntry(LastTgt); LastTgt = NULL; }
KerbFreeString(&ClientRealm);
//
// If we still have a pointer to the ticket cache entry, free it now.
//
if (TicketCacheEntry != NULL) { KerbRemoveTicketCacheEntry( TicketCacheEntry ); KerbDereferenceTicketCacheEntry(TicketCacheEntry); } return(Status); }
//+-------------------------------------------------------------------------
//
// Function: KerbCreateS4ULogonSession
//
// Synopsis: Creates a logon session to accompany the S4ULogon.
//
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbCreateS4ULogonSession( IN PKERB_INTERNAL_NAME S4UClientName, IN PUNICODE_STRING S4UClientRealm, IN PLUID pLuid, IN OUT PKERB_LOGON_SESSION * LogonSession ) { NTSTATUS Status = STATUS_SUCCESS; UNICODE_STRING S4UClient = {0};
*LogonSession = NULL;
if (!KERB_SUCCESS( KerbConvertKdcNameToString( &S4UClient, S4UClientName, NULL )) ) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
Status = KerbCreateLogonSession( pLuid, &S4UClient, S4UClientRealm, NULL, NULL, 0, KERB_LOGON_S4U_SESSION, FALSE, LogonSession );
if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "KerbCreateLogonSession failed %x %ws, line %d\n", Status, THIS_FILE, __LINE__)); goto Cleanup; }
Cleanup:
KerbFreeString(&S4UClient); return Status;
}
//+-------------------------------------------------------------------------
//
// Function: KerbS4UToSelfLogon
//
// Synopsis: Attempt to gets TGT for an S4U client for name
// location purposes.
//
//
// Effects:
//
// Arguments: LogonSession - Logon session of the service doing the
// S4U request
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbS4UToSelfLogon( IN PVOID ProtocolSubmitBuffer, IN PVOID ClientBufferBase, IN ULONG SubmitBufferSize, OUT PKERB_LOGON_SESSION * NewLogonSession, OUT PLUID LogonId, OUT PKERB_TICKET_CACHE_ENTRY * WorkstationTicket, OUT PKERB_INTERNAL_NAME * S4UClientName, OUT PUNICODE_STRING S4UClientRealm, OUT PLUID AlternateLuid ) { NTSTATUS Status;
PKERB_S4U_LOGON LogonInfo = NULL; PKERB_LOGON_SESSION CallerLogonSession = NULL; PKERB_TICKET_CACHE_ENTRY Duplicate = NULL; SECPKG_CLIENT_INFO ClientInfo; PKERB_INTERNAL_NAME AltClientName = NULL;
ULONG Flags = KERB_CRACK_NAME_USE_WKSTA_REALM, ProcessFlags = 0; LUID LocalService = LOCALSERVICE_LUID; LUID CallerLuid = SYSTEM_LUID; LUID NetworkService = NETWORKSERVICE_LUID;
PKERB_TICKET_CACHE_ENTRY LocalTicket = NULL;
UNICODE_STRING DummyRealm = {0};
*NewLogonSession = NULL; *WorkstationTicket = NULL; *S4UClientName = NULL;
RtlInitUnicodeString( S4UClientRealm, NULL );
Status = LsaFunctions->GetClientInfo(&ClientInfo); if (!NT_SUCCESS(Status)) { D_DebugLog((DEB_ERROR,"Failed to get client information: 0x%x. %ws, line %d\n",Status, THIS_FILE, __LINE__)); goto Cleanup; } else if ((ClientInfo.ClientFlags & SECPKG_CLIENT_THREAD_TERMINATED) != 0) { Status = STATUS_ACCESS_DENIED; goto Cleanup; }
//
// TBD: Is this correct? Local service LUIDs have no network creds,
// so we've got to fail S4U to Self.
// (OR - Do we use local system?)
// NETWORK uses local system luid, so we're ok w/ that
//
if (RtlEqualLuid(&LocalService, &ClientInfo.LogonId)) { D_DebugLog((DEB_ERROR, "Failing S4U due to LocalService\n")); Status = SEC_E_NO_CREDENTIALS; goto Cleanup; } else if (!RtlEqualLuid(&NetworkService, &ClientInfo.LogonId)) { RtlCopyLuid(&CallerLuid, &ClientInfo.LogonId); }
//
// Use this LUID for decrypting S4U Service ticket
//
RtlCopyLuid(AlternateLuid, &CallerLuid);
LogonInfo = (PKERB_S4U_LOGON) ProtocolSubmitBuffer; RELOCATE_ONE(&LogonInfo->ClientUpn); NULL_RELOCATE_ONE(&LogonInfo->ClientRealm);
//
// Make sure we have enough room to add a NULL to the end of the UPN
//
if (LogonInfo->ClientUpn.Length > KERB_MAX_UNICODE_STRING) { Status = STATUS_NAME_TOO_LONG; goto Cleanup; }
D_DebugLog((DEB_TRACE_S4U, "KerbS4UToSelfLogon ClientUpn %wZ, ClientRealm %wZ, Flags %#x\n", &LogonInfo->ClientUpn, &LogonInfo->ClientRealm, LogonInfo->Flags)); Status = KerbProcessTargetNames( &LogonInfo->ClientUpn, NULL, Flags, &ProcessFlags, S4UClientName, &DummyRealm, NULL );
if (!NT_SUCCESS(Status)) { goto Cleanup; }
CallerLogonSession = KerbReferenceLogonSession( &ClientInfo.LogonId, FALSE );
if (NULL == CallerLogonSession) { D_DebugLog((DEB_ERROR, "Failed to locate caller's logon session - %x\n", ClientInfo.LogonId)); Status = STATUS_NO_SUCH_LOGON_SESSION; goto Cleanup; }
//
// First, we need to get the client's realm from the UPN
//
if (LogonInfo->ClientRealm.Length == 0) { //
// Make sure that our processed name is correct type - don't accept
// NT4 style names as user names.
//
AltClientName = (*S4UClientName);
if ( AltClientName->NameType != KRB_NT_ENTERPRISE_PRINCIPAL ) { DebugLog((DEB_ERROR, "Wrong name type passed to S4U (%x)\n", AltClientName->NameType)); Status = STATUS_INVALID_ACCOUNT_NAME; goto Cleanup; }
Flags |= KERB_TARGET_DID_ALTNAME_LOOKUP;
//
// pre-empt burning a TGT request to find caller realm,
// if we find it in our cache.
//
KerbReadLockLogonSessions( CallerLogonSession ); LocalTicket = KerbLocateS4UTicketCacheEntry( &CallerLogonSession->PrimaryCredentials.S4UTicketCache, &ClientInfo.LogonId, NULL, NULL, AltClientName, S4UTICKETCACHE_USEALTNAME ); KerbUnlockLogonSessions( CallerLogonSession );
if ( LocalTicket == NULL ) { Status = KerbGetS4UClientRealm( CallerLogonSession, S4UClientName, S4UClientRealm ); if (!NT_SUCCESS(Status)) { goto Cleanup; } } else { //
// Found the s4u ticket in the callers s4u cache - get the info
// we need from it.
//
Status = KerbDuplicateString( S4UClientRealm, &LocalTicket->ClientDomainName ); if (!NT_SUCCESS(Status)) { goto Cleanup; } } } else { Status = KerbDuplicateString( S4UClientRealm, &LogonInfo->ClientRealm ); if (!NT_SUCCESS(Status)) { goto Cleanup; } }
//
// Allocate a locally unique ID for this logon session. We will
// create it in the LSA just before returning.
//
Status = NtAllocateLocallyUniqueId( LogonId ); if (!NT_SUCCESS(Status)) { goto Cleanup; }
Status = KerbCreateS4ULogonSession( (*S4UClientName), S4UClientRealm, LogonId, NewLogonSession );
if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR,"Failed to create logon session 0x%x. %ws, line %d\n",Status, THIS_FILE, __LINE__)); goto Cleanup; }
if ( LocalTicket == NULL ) { Status = KerbGetS4USelfServiceTicket( CallerLogonSession, (*NewLogonSession), NULL, // tbd: need to put credential here?
(*S4UClientName), AltClientName, S4UClientRealm, WorkstationTicket, 0, // no flags
0, // no ticketoptions
0, // no enctype
NULL // auth data
);
if (!NT_SUCCESS(Status)) { goto Cleanup; } } else { *WorkstationTicket = LocalTicket; LocalTicket = NULL; }
KerbWriteLockLogonSessions( (*NewLogonSession) ); //
// Is this going to be a "delegatable" logon session?
//
if (((*WorkstationTicket)->TicketFlags & KERB_TICKET_FLAGS_forwardable) != 0) { (*NewLogonSession)->LogonSessionFlags |= KERB_LOGON_DELEGATE_OK; }
#if DBG
else { D_DebugLog((DEB_TRACE_S4U, "Created non-delegatable logon session via s4u logon\n")); }
#endif
Status = KerbDuplicateTicketCacheEntry( (*WorkstationTicket), &Duplicate );
if (!NT_SUCCESS(Status)) { KerbUnlockLogonSessions((*NewLogonSession)); goto Cleanup; }
KerbInsertTicketCacheEntry( &((*NewLogonSession)->PrimaryCredentials.S4UTicketCache), Duplicate );
KerbDereferenceTicketCacheEntry( Duplicate ); KerbUnlockLogonSessions((*NewLogonSession));
Cleanup:
if (!NT_SUCCESS(Status)) { //
// TBD: Negative cache here, based on client name
//
KerbFreeString(S4UClientRealm); KerbFreeKdcName(S4UClientName); *S4UClientName = NULL;
}
KerbFreeString( &DummyRealm );
if ( LocalTicket != NULL ) { KerbDereferenceTicketCacheEntry( LocalTicket ); }
return Status; }
//+-------------------------------------------------------------------------
//
// Function: KerbGetS4UProxyEvidence
//
// Synopsis: Get our evidence ticket from the logonsession. Its either a
// service ticket (for ASC logon sessions), or a S4UToSelf request
// if we don't have a service ticket.
//
// Effects:
//
// Arguments: LogonSession - Logon session of the service doing the
// S4U request
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbGetS4UProxyEvidence( IN PKERB_LOGON_SESSION LogonSession, IN PKERB_INTERNAL_NAME TargetName, IN ULONG ClientProcess, IN OUT PKERB_LOGON_SESSION * CallingLogonSession, IN OUT PKERB_TICKET_CACHE_ENTRY * TicketCacheEntry ) { NTSTATUS Status; ULONG Flags = KERB_CRACK_NAME_USE_WKSTA_REALM, ProcessFlags = 0; ULONG LogonSessionFlags; BOOLEAN LogonSessionLocked = FALSE; UNICODE_STRING TmpRealm = {0}; LUID LogonId; PKERB_TICKET_CACHE_ENTRY NewCacheEntry = NULL; PKERB_INTERNAL_NAME ClientName = NULL; PKERB_LOGON_SESSION CallerLogonSession = NULL;
*TicketCacheEntry = NULL; *CallingLogonSession = NULL;
Status = KerbGetCallingLuid( &LogonId, ((HANDLE) LongToHandle(ClientProcess)) );
if (!NT_SUCCESS( Status )) { goto Cleanup; }
if (!KerbAllowedForS4UProxy( &LogonId )) { Status = STATUS_NOT_SUPPORTED; goto Cleanup; }
//
// Get caller's logon session.
//
CallerLogonSession = KerbReferenceLogonSession( &LogonId, FALSE );
if (NULL == CallerLogonSession) { DsysAssert( FALSE ); Status = STATUS_NO_SUCH_LOGON_SESSION; goto Cleanup; }
KerbReadLockLogonSessions( LogonSession ); LogonSessionFlags = LogonSession->LogonSessionFlags;
NewCacheEntry = KerbLocateS4UTicketCacheEntry( &LogonSession->PrimaryCredentials.S4UTicketCache, &LogonId, NULL, // just get the ticket used for S4U.
NULL, NULL, S4UTICKETCACHE_FOR_EVIDENCE );
KerbUnlockLogonSessions( LogonSession );
if ( NewCacheEntry == NULL ) { //
// What's this? An ASC logon sessoin w/o a ticket?
//
if (( LogonSessionFlags & KERB_LOGON_DELEGATE_OK ) == 0 ) { DebugLog((DEB_ERROR, "Non Fwdable logon session used in S4u\n")); Status = STATUS_NO_SUCH_LOGON_SESSION; goto Cleanup; // right thing? Or fall through?
}
DsysAssert( !LogonSessionLocked ); KerbReadLockLogonSessions( LogonSession ); LogonSessionLocked = TRUE;
Status = KerbProcessTargetNames( &LogonSession->PrimaryCredentials.UserName, NULL, Flags, &ProcessFlags, &ClientName, &TmpRealm, NULL );
if (!NT_SUCCESS( Status )) { goto Cleanup; }
KerbFreeString( &TmpRealm );
Status = KerbDuplicateString( &TmpRealm, &LogonSession->PrimaryCredentials.DomainName );
if (!NT_SUCCESS( Status )) { goto Cleanup; }
DsysAssert( LogonSessionLocked ); KerbUnlockLogonSessions( LogonSession ); LogonSessionLocked = FALSE;
Status = KerbGetS4USelfServiceTicket( CallerLogonSession, LogonSession, NULL, ClientName, NULL, // no alt client name
&TmpRealm, &NewCacheEntry, 0, 0, 0, NULL );
if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "KerbGetS4UProxyEvidence failed to get S4U ticket - %x\n", Status)); goto Cleanup; }
ASSERT(NewCacheEntry);
if (NewCacheEntry->CacheFlags & KERB_TICKET_CACHE_TKT_ENC_IN_SKEY) { DebugLog((DEB_ERROR, "KerbGetS4UProxyEvidence does not allow u2u evidence key\n")); Status = SEC_E_NO_CREDENTIALS; goto Cleanup; } if ((NewCacheEntry->TicketFlags & KERB_TICKET_FLAGS_forwardable) == 0) { KerbWriteLockLogonSessions( LogonSession ); LogonSession->LogonSessionFlags &= ~KERB_LOGON_DELEGATE_OK; KerbUnlockLogonSessions( LogonSession );
DebugLog((DEB_TRACE_S4U, "KerbGetS4UProxyEvidence created non-delegatable logon session via s4u logon\n")); Status = SEC_E_NO_CREDENTIALS; goto Cleanup; }
PKERB_TICKET_CACHE_ENTRY Duplicate; Status = KerbDuplicateTicketCacheEntry( NewCacheEntry, &Duplicate );
if (!NT_SUCCESS( Status )) { goto Cleanup; }
KerbWriteLockLogonSessions ( LogonSession ); LogonSession->LogonSessionFlags |= KERB_LOGON_DELEGATE_OK;
KerbInsertTicketCacheEntry( &LogonSession->PrimaryCredentials.S4UTicketCache, Duplicate );
KerbUnlockLogonSessions( LogonSession );
KerbDereferenceTicketCacheEntry( Duplicate ); Duplicate = NULL; } //
// don't allow TKT encrypted in SKEY as evidence ticket for S4UProxy
//
else if (NewCacheEntry->CacheFlags & KERB_TICKET_CACHE_TKT_ENC_IN_SKEY) { DebugLog((DEB_ERROR, "KerbGetS4UProxyEvidence does not allow evidence ticket encrypted in SKEY\n")); Status = SEC_E_NO_CREDENTIALS; goto Cleanup; }
*CallingLogonSession = CallerLogonSession; CallerLogonSession = NULL;
*TicketCacheEntry = NewCacheEntry; NewCacheEntry = NULL;
Cleanup:
if (NewCacheEntry) { KerbDereferenceTicketCacheEntry(NewCacheEntry); }
if ( CallerLogonSession != NULL ) { KerbDereferenceLogonSession( CallerLogonSession ); }
if ( LogonSessionLocked ) { KerbUnlockLogonSessions( LogonSession ); }
KerbFreeKdcName( &ClientName ); KerbFreeString( &TmpRealm );
return Status; }
//+-------------------------------------------------------------------------
//
// Function: KerbS4UQueryWorker
//
// Synopsis: Handles a S4U Query
//
// Effects:
//
// Arguments: TaskHandle - handle to the task (for rescheduling, etc.)
// TaskItem - task context
//
// Requires:
//
// Returns: Nothing
//
// Notes:
//
//
//--------------------------------------------------------------------------
void KerbS4UCleanupWorker( void * TaskHandle, void * TaskItem ) { PKERB_S4UCACHE_DATA Entry = NULL; PLIST_ENTRY ListEntry = NULL; TimeStamp CurrentTime;
GetSystemTimeAsFileTime( (PFILETIME) &CurrentTime ); KerbLockS4UCache(); for (ListEntry = KerbS4UCache.List.Flink ; ListEntry != &KerbS4UCache.List ; ListEntry = ListEntry->Flink ) { Entry = CONTAINING_RECORD(ListEntry, KERB_S4UCACHE_DATA, ListEntry.Next); if ( KerbGetTime( CurrentTime ) > KerbGetTime( Entry->CacheEndtime )) { ListEntry = ListEntry->Blink; D_DebugLog(( DEB_TRACE_S4U, "Aging out %p\n", Entry )); KerbRemoveS4UCacheEntry(Entry);
}
#if DBG
else { D_DebugLog(( DEB_TRACE_S4U, "NOT aging %p\n", Entry)); } #endif
}
KerbUnlockS4UCache();
}
//+-------------------------------------------------------------------------
//
// Function: KerbS4UQueryWorker
//
// Synopsis: Handles a S4U Query
//
// Effects:
//
// Arguments: TaskHandle - handle to the task (for rescheduling, etc.)
// TaskItem - task context
//
// Requires:
//
// Returns: Nothing
//
// Notes:
//
//
//--------------------------------------------------------------------------
void KerbS4UQueryWorker( void * TaskHandle, void * TaskItem ) {
NTSTATUS Status = STATUS_SUCCESS; PKERB_LOGON_SESSION LogonSession = (PKERB_LOGON_SESSION) TaskItem; PKERB_INTERNAL_NAME TargetName = NULL; PKERB_TICKET_CACHE_ENTRY ServiceTicket = NULL; PKERB_TICKET_CACHE_ENTRY S4UTicket = NULL; PKERB_TICKET_CACHE_ENTRY Tgt = NULL; UNICODE_STRING TargetRealm = NULL_UNICODE_STRING; UNICODE_STRING TempName = NULL_UNICODE_STRING; BOOLEAN LogonSessionLocked = FALSE, Upn = FALSE, CrossRealm = FALSE; KERB_TGT_REPLY TgtReply ={0}; PKERB_ENCRYPTION_KEY EncryptKey = NULL;
//
// Get a service ticket to ourselves. This will be used in the proxy
// request.
//
#if DBG
SYSTEMTIME st; GetLocalTime(&st); DebugLog((DEB_TRACE_S4U, "Firing off S4UQuery worker LS %p\n", LogonSession)); DebugLog((DEB_TRACE_S4U, "Current time: %02d:%02d:%02d\n", (ULONG)st.wHour, (ULONG)st.wMinute, (ULONG)st.wSecond)); #endif
KerbReferenceLogonSessionByPointer(LogonSession, FALSE); KerbWriteLockLogonSessions( LogonSession ); LogonSessionLocked = TRUE;
Status = KerbGetS4UTargetName( &TargetName, &LogonSession->PrimaryCredentials, &LogonSession->LogonId, &Upn );
if (!NT_SUCCESS( Status )) { goto Cleanup; }
Status = KerbDuplicateStringEx( &TargetRealm, &LogonSession->PrimaryCredentials.DomainName, FALSE );
KerbUnlockLogonSessions( LogonSession ); LogonSessionLocked = FALSE;
if (!NT_SUCCESS( Status )) { goto Cleanup; }
//
// Targetting a UPN usually requires U2U. Get our TGT.
//
if ( Upn ) { KerbReadLockLogonSessions( LogonSession );
Status = KerbGetTgtForService( LogonSession, NULL, NULL, NULL, &TempName, // no target realm
KERB_TICKET_CACHE_PRIMARY_TGT, &Tgt, &CrossRealm );
KerbUnlockLogonSessions( LogonSession ); if (!NT_SUCCESS(Status) || CrossRealm ) { DebugLog((DEB_ERROR, "KerbBuildTgtReply failed to get TGT, CrossRealm ? %s\n", CrossRealm ? "true" : "false")); Status = STATUS_USER2USER_REQUIRED; goto Cleanup; }
TgtReply.version = KERBEROS_VERSION; TgtReply.message_type = KRB_TGT_REP; TgtReply.ticket = Tgt->Ticket;
}
Status = KerbGetServiceTicket( LogonSession, NULL, NULL, TargetName, &TargetRealm, NULL, 0, 0, 0, NULL, NULL, (Upn ? &TgtReply : NULL), &ServiceTicket, NULL );
if ( !NT_SUCCESS(Status) ) { DebugLog((DEB_TRACE_S4U, "Couldn't get service ticket in S4UQueryWorker %x\n", Status)); goto Cleanup; }
//
// U2U + S4UProxy won't work. Target teh local machine.
//
if ( Upn ) { KerbFreeKdcName( &TargetName ); KerbFreeString( &TargetRealm ); KerbGlobalReadLock();
Status = KerbDuplicateKdcName( &TargetName, KerbGlobalMitMachineServiceName ); if (NT_SUCCESS( Status )) { Status = KerbDuplicateString( &TargetRealm, &KerbGlobalDnsDomainName );
}
KerbGlobalReleaseLock(); if (!NT_SUCCESS( Status )) { goto Cleanup; }
//
// We can't use U2U + S4UProxy... Decrypt the ticket, and re-encrypt w/ our password.
//
EncryptKey = KerbGetKeyFromList( LogonSession->PrimaryCredentials.Passwords, ServiceTicket->Ticket.encrypted_part.encryption_type );
if ( EncryptKey == NULL) { goto Cleanup; }
Status = KerbPrepareEvidenceTicket( &Tgt->SessionKey, EncryptKey, &ServiceTicket->Ticket );
if (!NT_SUCCESS( Status )) { DebugLog((DEB_ERROR, "Couldn't encrypt evidence ticket %x\n", Status)); goto Cleanup; }
}
Status = KerbGetServiceTicketByS4UProxy( LogonSession, LogonSession, NULL, ServiceTicket, TargetName, &TargetRealm, NULL, KERB_GET_TICKET_NO_CACHE, 0, 0, NULL, NULL, NULL, &S4UTicket, NULL );
if (!NT_SUCCESS( Status )) { goto Cleanup; }
Cleanup:
//
// Make sure to turn off one shot bit, so further requests can process
//
if ( !LogonSessionLocked ) { KerbWriteLockLogonSessions( LogonSession ); }
LogonSession->LogonSessionFlags &= ~KERB_LOGON_ONE_SHOT; KerbUnlockLogonSessions( LogonSession );
KerbDereferenceLogonSession(LogonSession);
#if DBG
D_DebugLog((DEB_TRACE_S4U, "Result %x :: ", Status));
if (KerbAllowedForS4UProxy(&LogonSession->LogonId)) { DebugLog((DEB_TRACE_S4U, "Allowed\n")); } else { DebugLog((DEB_TRACE_S4U, "NOT Allowed\n")); }
#endif
KerbFreeKdcName( &TargetName );
if ( ServiceTicket ) { KerbDereferenceTicketCacheEntry( ServiceTicket ); }
if ( S4UTicket ) { KerbDereferenceTicketCacheEntry( S4UTicket ); }
if ( Tgt ) { KerbDereferenceTicketCacheEntry( Tgt ); }
return; }
//+-------------------------------------------------------------------------
//
// Function: KerbS4UTaskCleanup
//
// Synopsis: Destroys a S4U query task
//
// Effects:
//
// Arguments: TaskItem - cache entry to destroy
//
// Requires:
//
// Returns: Nothing
//
// Notes:
//
//
//--------------------------------------------------------------------------
void KerbS4UTaskCleanup( void * TaskItem ) { PKERB_LOGON_SESSION LogonSession = (PKERB_LOGON_SESSION) TaskItem;
if ( LogonSession ) { //
// Release the refcount held by the task worker.
//
KerbDereferenceLogonSession(LogonSession); } }
//+-------------------------------------------------------------------------
//
// Function: KerbScheduleS4UQuery
//
// Synopsis: Sets up a task in the priority queue so that we can make
// periodic attempts at S4UProxy, and determine if there are
// any targets we're qualified (as indicated by the e_data).
// See gettgs.cxx / KerbUnpackAdditionalTickets for more info
// on this functionality.
//
// Effects:
//
// Arguments: LogonSession - Logon session of the service doing the
// S4U request
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbScheduleS4UQuery( IN PKERB_LOGON_SESSION LogonSession, IN LONG Interval, IN BOOLEAN Periodic ) { NTSTATUS Status = STATUS_SUCCESS; ULONG LogonSessionFlags; KerbReadLockLogonSessions( LogonSession ); LogonSessionFlags = LogonSession->LogonSessionFlags; KerbUnlockLogonSessions( LogonSession ); //
// If we've already scheduled a query, bail now, as we
// don't need to create a new task entry.
//
if ((LogonSessionFlags & KERB_LOGON_ONE_SHOT) != 0) { return Status; } D_DebugLog((DEB_TRACE_S4U, "Adding %s task to %p\n", (Periodic ? "periodic" : "oneshot"), LogonSession)); Status = KerbAddScavengerTask( Periodic, Interval, 0, // no special processing flags
KerbS4UQueryWorker, KerbS4UTaskCleanup, LogonSession, NULL );
if (!NT_SUCCESS(Status)) { D_DebugLog((DEB_ERROR, "Failed to schedule scavenger task (%x) for LS %p\n", Status, LogonSession)); } else { //
// Add a reference for the task...
//
if ( !Periodic ) { KerbWriteLockLogonSessions( LogonSession ); LogonSession->LogonSessionFlags |= KERB_LOGON_ONE_SHOT; KerbUnlockLogonSessions( LogonSession ); }
KerbReferenceLogonSessionByPointer(LogonSession, FALSE); } return Status; }
//+-------------------------------------------------------------------------
//
// Function: KerbScheduleS4UCleanup
//
// Synopsis: Walks the list to cleanup any old, unreferenced entries.
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbScheduleS4UCleanup()
{ NTSTATUS Status = STATUS_SUCCESS; LONG Interval = 60 * 1000 * 60; // once per hour
Status = KerbAddScavengerTask( TRUE, Interval, 0, // no special processing flags
KerbS4UCleanupWorker, NULL, NULL, NULL );
if (!NT_SUCCESS(Status)) { D_DebugLog((DEB_ERROR, "Failed to schedule scavenger task (%x) \n", Status)); } return Status; }
//+-------------------------------------------------------------------------
//
// Function: KerbAllowedForS4UProxy
//
// Synopsis: Check the S4UCACHE_DATA to see if we can do S4U for this target.
// This will only get called in the AcceptSecurityContext() code path
// where we'll need to see if we need to cache tickets. Cache them,
// for now, but kick off a worker thread to see if there's any need
// to cache tickets in the future.
//
// Effects:
//
// Arguments: LogonSession - Logon session of the service doing the
// S4U request
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//+-------------------------------------------------------------------------
BOOLEAN KerbAllowedForS4UProxy( IN PLUID LogonId ) { ULONG CacheState = 0; PKERB_S4UCACHE_DATA S4UCacheEntry = NULL; BOOLEAN fRet = TRUE, Update = FALSE; PKERB_LOGON_SESSION LogonSession = NULL; LUID LocalService = LOCALSERVICE_LUID; LUID Anonymous = ANONYMOUS_LOGON_LUID; KERBEROS_MACHINE_ROLE Role;
Role = KerbGetGlobalRole(); //
// S4UToProxy is not allowed for anything but
// server products.
//
if (!KerbGlobalRunningServer || RtlEqualLuid(LogonId, &LocalService) || RtlEqualLuid(LogonId, &Anonymous) || Role < KerbRoleWorkstation ) { return FALSE; }
S4UCacheEntry = KerbLocateS4UCacheEntry( LogonId, &CacheState );
if ( NULL != S4UCacheEntry ) { if (( CacheState & S4UCACHE_S4U_UNAVAILABLE ) != 0) { D_DebugLog((DEB_TRACE_S4U, "Luid %x:%x can't do S4u\n", LogonId->HighPart, LogonId->LowPart)); fRet = FALSE; } if (( CacheState & S4UCACHE_TIMEOUT ) != 0) { Update = TRUE; } KerbDereferenceS4UCacheEntry(S4UCacheEntry); } else { Update = TRUE; }
if ( Update ) { LONG Period = 1;
LogonSession = KerbReferenceLogonSession(LogonId, FALSE); if ( LogonSession ) { KerbReadLockLogonSessions( LogonSession ); if (( LogonSession->LogonSessionFlags & KERB_LOGON_LOCAL_ONLY ) != 0) { KerbUnlockLogonSessions( LogonSession ); DebugLog((DEB_TRACE_S4U, "Local account %p - s4u not allowed\n", LogonSession )); fRet = FALSE; } else { KerbUnlockLogonSessions( LogonSession ); //
// Do this async in 1sec to keep from blocking
// ASC.
//
KerbScheduleS4UQuery(LogonSession, Period, FALSE); KerbDereferenceLogonSession( LogonSession ); } } else { fRet = FALSE; } }
return fRet; }
|