You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5171 lines
141 KiB
5171 lines
141 KiB
//+-----------------------------------------------------------------------
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
|