Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

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