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.
 
 
 
 
 
 

2053 lines
49 KiB

//+-----------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (c) Microsoft Corporation 1992 - 1996
//
// File: bndcache.cxx
//
// Contents: spn cache for Kerberos Package
//
//
// History: 13-August-1996 Created MikeSw
//
//------------------------------------------------------------------------
#include <kerb.hxx>
#include <kerbp.h>
#include <spncache.h>
//
// TBD: Switch this to a table & resource, or entries for
// each SPN prefix.
//
BOOLEAN KerberosSpnCacheInitialized = FALSE;
KERBEROS_LIST KerbSpnCache;
LONG SpnCount;
#define KerbWriteLockSpnCache() KerbLockList(&KerbSpnCache);
#define KerbReadLockSpnCache() KerbLockList(&KerbSpnCache);
#define KerbUnlockSpnCache() KerbUnlockList(&KerbSpnCache);
#define KerbWriteLockSpnCacheEntry(_x_) RtlAcquireResourceExclusive( &_x_->ResultLock, TRUE)
#define KerbReadLockSpnCacheEntry(_x_) RtlAcquireResourceShared( &_x_->ResultLock, TRUE)
#define KerbUnlockSpnCacheEntry(_x_) RtlReleaseResource( &_x_->ResultLock)
#define KerbConvertSpnCacheEntryReadToWriteLock(_x_) RtlConvertSharedToExclusive( &_x_->ResultLock )
BOOLEAN HostToRealmUsed = FALSE;
BOOLEAN HostToRealmInitialized = FALSE;
RTL_AVL_TABLE HostToRealmTable;
SAFE_RESOURCE HostToRealmLock;
#define HostToRealmReadLock() SafeAcquireResourceShared(&HostToRealmLock, TRUE)
#define HostToRealmWriteLock() SafeAcquireResourceExclusive(&HostToRealmLock, TRUE)
#define HostToRealmUnlock() SafeReleaseResource(&HostToRealmLock)
//
// Allocation Routines for our table.
//
PVOID
NTAPI
KerbTableAllocateRoutine(
struct _RTL_AVL_TABLE * Table,
CLONG ByteSize
)
{
UNREFERENCED_PARAMETER( Table );
return KerbAllocate( ByteSize );
}
void
NTAPI
KerbTableFreeRoutine(
struct _RTL_AVL_TABLE * Table,
PVOID Buffer
)
{
UNREFERENCED_PARAMETER( Table );
KerbFree( Buffer );
}
//+-------------------------------------------------------------------------
//
// Function: KerbStringComparisonRoutine
//
// Synopsis: Used in tables to compare unicode strings
//
// Effects:
//
// Arguments:
//
//
// Requires:
//
// Returns: none
//
// Notes:
//
//
//--
RTL_GENERIC_COMPARE_RESULTS
NTAPI
KerbTableStringComparisonRoutine(
struct _RTL_AVL_TABLE * Table,
PVOID FirstStruct,
PVOID SecondStruct
)
{
INT Result;
UNICODE_STRING *String1, *String2;
UNREFERENCED_PARAMETER( Table );
ASSERT( FirstStruct );
ASSERT( SecondStruct );
String1 = ( UNICODE_STRING * )FirstStruct;
String2 = ( UNICODE_STRING * )SecondStruct;
Result = RtlCompareUnicodeString(
String1,
String2,
TRUE
);
if ( Result < 0 ) {
return GenericLessThan;
} else if ( Result > 0 ) {
return GenericGreaterThan;
} else {
return GenericEqual;
}
}
//+-------------------------------------------------------------------------
//
// Function: KerbPurgeHostToRealmTable
//
// Synopsis: Creates an entry and puts it in the HostToRealmTable
//
// Effects:
//
// Arguments: none
//
// Requires:
//
// Returns: STATUS_SUCCESS on success, other error codes on failure
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID
KerbPurgeHostToRealmTable()
{
PHOST_TO_REALM_KEY Key = NULL;
BOOLEAN fDeleted;
HostToRealmWriteLock();
HostToRealmUsed = FALSE;
for ( Key = ( PHOST_TO_REALM_KEY )RtlEnumerateGenericTableAvl( &HostToRealmTable, TRUE );
Key != NULL;
Key = ( PHOST_TO_REALM_KEY )RtlEnumerateGenericTableAvl( &HostToRealmTable, TRUE ))
{
fDeleted = RtlDeleteElementGenericTableAvl( &HostToRealmTable, (PVOID) &Key->SpnSuffix );
DsysAssert( fDeleted );
}
HostToRealmUnlock();
}
//+-------------------------------------------------------------------------
//
// Function: KerbAddHostToRealmMapping
//
// Synopsis: Creates an entry and puts it in the HostToRealmTable
//
// Effects:
//
// Arguments: none
//
// Requires:
//
// Returns: STATUS_SUCCESS on success, other error codes on failure
//
// Notes:
//
//
//--------------------------------------------------------------------------
BOOLEAN
KerbAddHostToRealmMapping(
HKEY hKey,
LPWSTR Realm
)
{
BOOLEAN fRet = FALSE;
ULONG Type;
ULONG WinError = ERROR_SUCCESS;
NTSTATUS Status;
PBYTE Data = NULL;
PWCHAR pCh;
ULONG DataSize = 0;
WCHAR Value[] = KERB_HOST_TO_REALM_VAL;
UNICODE_STRING RealmString = {0};
ULONG index, StringCount = 0;
RtlInitUnicodeString(
&RealmString,
Realm
);
Status = RtlUpcaseUnicodeString(
&RealmString,
&RealmString,
FALSE
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
//
// First query the SPN strings under this realm key.
//
WinError = RegQueryValueEx(
hKey,
Value,
NULL,
&Type,
NULL,
&DataSize
);
if ( Type != REG_MULTI_SZ )
{
D_DebugLog((DEB_ERROR, "Wrong registry type \n"));
goto Cleanup;
}
if ((WinError == ERROR_MORE_DATA) || (WinError == ERROR_SUCCESS))
{
SafeAllocaAllocate( Data, DataSize );
if (Data == NULL)
{
goto Cleanup;
}
WinError = RegQueryValueEx(
hKey,
Value,
NULL,
&Type,
Data,
&DataSize
);
}
if ( WinError != ERROR_SUCCESS )
{
goto Cleanup;
}
pCh = (PWCHAR) Data;
//
// Now parse out the reg_multi_sz to create the keys for the table.
//
for ( index = 0; index < DataSize; index += sizeof(WCHAR), pCh++ )
{
if ( (*pCh) == L'\0' )
{
StringCount++;
if (*(pCh+1) == L'\0')
{
break;
}
}
}
//
// Build the keys, one for each SPN substring entry.
// These keys are contigous blobs - to speed up table lookups, and
// confine them to a page of memory.
//
pCh = (PWCHAR) Data;
for ( index = 0; index < StringCount ; index++ )
{
ULONG uSize = (2*sizeof(UNICODE_STRING));
UNICODE_STRING SpnSuffix = {0};
PHOST_TO_REALM_KEY Key = NULL, NewKey = NULL;
PBYTE tmp;
RtlInitUnicodeString(
&SpnSuffix,
pCh
);
Status = RtlUpcaseUnicodeString(
&SpnSuffix,
&SpnSuffix,
FALSE
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
uSize += ROUND_UP_COUNT(SpnSuffix.MaximumLength, ALIGN_LPDWORD);
uSize += RealmString.MaximumLength;
Key = (PHOST_TO_REALM_KEY) KerbAllocate(uSize);
if (NULL == Key)
{
goto Cleanup;
}
tmp = (PBYTE) &Key->NameBuffer;
Key->SpnSuffix.Length = SpnSuffix.Length;
Key->SpnSuffix.MaximumLength = SpnSuffix.MaximumLength;
Key->SpnSuffix.Buffer = (PWCHAR) tmp;
RtlCopyMemory(
tmp,
SpnSuffix.Buffer,
SpnSuffix.MaximumLength
);
tmp += ROUND_UP_COUNT(SpnSuffix.MaximumLength, ALIGN_LPDWORD);
Key->TargetRealm.Length = RealmString.Length;
Key->TargetRealm.MaximumLength = RealmString.MaximumLength;
Key->TargetRealm.Buffer = (PWCHAR) tmp;
RtlCopyMemory(
tmp,
RealmString.Buffer,
RealmString.MaximumLength
);
NewKey = (PHOST_TO_REALM_KEY) RtlInsertElementGenericTableAvl(
&HostToRealmTable,
Key,
uSize,
NULL
);
KerbFree(Key);
if (NewKey == NULL)
{
D_DebugLog((DEB_ERROR, "Insert into table failed\n"));
goto Cleanup;
}
NewKey->SpnSuffix.Buffer = NewKey->NameBuffer;
NewKey->TargetRealm.Buffer = NewKey->NameBuffer +
(ROUND_UP_COUNT(SpnSuffix.MaximumLength, ALIGN_LPDWORD) / sizeof(WCHAR));
pCh += (SpnSuffix.MaximumLength / sizeof(WCHAR));
DebugLog((DEB_TRACE_SPN_CACHE, "NewKey %p\n", NewKey));
}
HostToRealmUsed = TRUE;
fRet = TRUE;
Cleanup:
SafeAllocaFree( Data );
return fRet;
}
//+-------------------------------------------------------------------------
//
// Function: KerbRefreshHostToRealmTable
//
// Synopsis: Used as a registry callback routine for cleaning, and reusing
// hosttorealm cache.
// Effects:
//
// Arguments: none
//
// Requires:
//
// Returns: void
//
// Notes:
//
//
//----
VOID
KerbRefreshHostToRealmTable()
{
if (!HostToRealmInitialized)
{
return;
}
KerbPurgeHostToRealmTable();
KerbCreateHostToRealmMappings();
}
//+-------------------------------------------------------------------------
//
// Function: KerbInitHostToRealmTable
//
// Synopsis: Initializes the host to realm table.
//
// Effects: allocates a resources
//
// Arguments: none
//
// Requires:
//
// Returns: STATUS_SUCCESS on success, other error codes on failure
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbInitHostToRealmTable(
VOID
)
{
NTSTATUS Status = STATUS_SUCCESS;;
//
// Initialize the host to realm mapping
// table
//
RtlInitializeGenericTableAvl(
&HostToRealmTable,
KerbTableStringComparisonRoutine,
KerbTableAllocateRoutine,
KerbTableFreeRoutine,
NULL
);
__try
{
SafeInitializeResource(&HostToRealmLock, HOST_2_REALM_LIST_LOCK);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
HostToRealmInitialized = TRUE;
KerbCreateHostToRealmMappings();
Cleanup:
return(Status);
}
#if DBG
#define MAX_NAMES 8
LPWSTR Prefix[MAX_NAMES] = { L"CIFS", L"HTTP", L"RPCSS", L"HOST", L"GC", L"LDAP", L"DNS", L"???"};
ULONG NameCount[MAX_NAMES];
ULONG Tracking[MAX_NAMES];
//
// Fester: Remove this when we figure out a good pattern of
// SPN usage.
//
VOID
KerbLogSpnStats(
PKERB_INTERNAL_NAME Spn
)
{
ULONG i;
UNICODE_STRING Namelist;
if (Spn->NameCount < MAX_NAMES)
{
Tracking[Spn->NameCount]++;
}
for (i = 0; i < (MAX_NAMES - 1); i++)
{
RtlInitUnicodeString(
&Namelist,
Prefix[i]
);
if (RtlEqualUnicodeString(
&Spn->Names[0],
&Namelist,
TRUE
))
{
NameCount[i]++;
return;
}
}
//
// Count this as a miss
//
NameCount[MAX_NAMES-1]++;
}
#endif
//+-------------------------------------------------------------------------
//
// Function: KerbInitSpnCache
//
// Synopsis: Initializes the SPN cache
//
// Effects: allocates a resources
//
// Arguments: none
//
// Requires:
//
// Returns: STATUS_SUCCESS on success, other error codes on failure
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbInitSpnCache(
VOID
)
{
NTSTATUS Status;
Status = KerbInitializeList( &KerbSpnCache, SPN_CACHE_LOCK_ENUM );
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
Status = KerbInitHostToRealmTable();
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
KerberosSpnCacheInitialized = TRUE;
Cleanup:
if (!NT_SUCCESS(Status))
{
KerbFreeList( &KerbSpnCache );
}
return(Status);
}
//+-------------------------------------------------------------------------
//
// Function: KerbCleanupSpnCache
//
// Synopsis: Frees the Spn cache
//
// Effects:
//
// Arguments: none
//
// Requires:
//
// Returns: none
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID
KerbCleanupSpnCache(
VOID
)
{
PKERB_SPN_CACHE_ENTRY CacheEntry;
DebugLog((DEB_TRACE_SPN_CACHE, "Cleaning up SPN cache\n"));
if (KerberosSpnCacheInitialized)
{
KerbWriteLockSpnCache();
while (!IsListEmpty(&KerbSpnCache.List))
{
CacheEntry = CONTAINING_RECORD(
KerbSpnCache.List.Flink,
KERB_SPN_CACHE_ENTRY,
ListEntry.Next
);
KerbReferenceListEntry(
&KerbSpnCache,
&CacheEntry->ListEntry,
TRUE
);
KerbDereferenceSpnCacheEntry(CacheEntry);
}
KerbUnlockSpnCache();
}
}
//+-------------------------------------------------------------------------
//
// Function: KerbParseDnsName
//
// Synopsis: Parse Dns name from left to right, at each "."
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes: The unicode string passed in will be modified - use a copy if
// you still need the string.
//
//
//--
BOOLEAN
KerbParseDnsName(
IN UNICODE_STRING * Name
)
{
USHORT Index;
BOOLEAN Found = FALSE;
ASSERT( Name );
ASSERT( Name->Length > 0 );
ASSERT( Name->Buffer != NULL );
for ( Index = 0 ; Index < Name->Length ; Index += sizeof( WCHAR )) {
if ( Name->Buffer[Index / sizeof( WCHAR )] == L'.' ) {
Found = TRUE;
Index += sizeof( WCHAR );
break;
}
}
ASSERT( !Found || Index < Name->Length );
Name->Buffer += Index / sizeof( WCHAR );
Name->Length = Name->Length - Index;
Name->MaximumLength = Name->MaximumLength - Index;
ASSERT( Found || Name->Length == 0 );
return ( Found );
}
//+-------------------------------------------------------------------------
//
// Function: KerbSpnSubstringMatch
//
// Synopsis: Attempts to match an SPN to a known realm mapping
//
// Effects: Returns a new target realm.
//
// Arguments: decrements reference count and delets cache entry if it goes
// to zero
//
// Requires: SpnCacheEntry - The spn cache entry to dereference.
//
// Returns: none
//
// Notes:
//
//
//--
NTSTATUS
KerbSpnSubstringMatch(
IN PKERB_INTERNAL_NAME Spn,
IN OUT PUNICODE_STRING TargetRealm
)
{
UNICODE_STRING RemainingParts ={0};
NTSTATUS Status = STATUS_SUCCESS;
PHOST_TO_REALM_KEY MatchedRealm = NULL;
PWCHAR FreeMe;
//
// The SPN must have at least 2 parts (host / machine . realm )
//
if ( !HostToRealmUsed || Spn->NameCount < 2 )
{
Status = STATUS_NO_MATCH;
return Status;
}
//
// Try first component.
//
SafeAllocaAllocate( RemainingParts.Buffer, Spn->Names[1].MaximumLength );
if ( RemainingParts.Buffer == NULL)
{
return STATUS_NO_MEMORY;
}
FreeMe = RemainingParts.Buffer; // note: KerbParseDnsName will move buffer ptr.
RtlCopyMemory(
RemainingParts.Buffer,
Spn->Names[1].Buffer,
Spn->Names[1].MaximumLength
);
RemainingParts.Length = Spn->Names[1].Length;
RemainingParts.MaximumLength = Spn->Names[1].MaximumLength;
//
// All realms are stored in UPPERCASE in the table.
//
if (!NT_SUCCESS(RtlUpcaseUnicodeString(&RemainingParts,&Spn->Names[1],FALSE)))
{
SafeAllocaFree( FreeMe );
return STATUS_INTERNAL_ERROR;
}
HostToRealmReadLock();
do
{
MatchedRealm = (PHOST_TO_REALM_KEY) RtlLookupElementGenericTableAvl(
&HostToRealmTable,
&RemainingParts
);
} while (( MatchedRealm == NULL ) && KerbParseDnsName(&RemainingParts));
SafeAllocaFree( FreeMe );
if ( MatchedRealm == NULL )
{
D_DebugLog((DEB_TRACE_SPN_CACHE, "Missed cache for %wZ\n", &Spn->Names[1]));
HostToRealmUnlock();
return STATUS_NO_MATCH;
}
D_DebugLog((DEB_TRACE_SPN_CACHE, "HIT cache for %wZ\n", &Spn->Names[1]));
D_DebugLog((DEB_TRACE_SPN_CACHE, "Realm %wZ\n", &MatchedRealm->TargetRealm));
Status = KerbDuplicateString(
TargetRealm,
&MatchedRealm->TargetRealm
);
HostToRealmUnlock();
return Status;
}
//+-------------------------------------------------------------------------
//
// Function: KerbCreateSpnMappings
//
// Synopsis: Uses registry to create
//
// Effects: Increments the reference count on the spn cache entry
//
// Arguments: SpnCacheEntry - spn cache entry to reference
//
// Requires: The spn cache must be locked
//
// Returns: none
//
// Notes:
//
//
//--------------------------------------------------------------------------
#define MAX_KEY_NAME 512
VOID
KerbCreateHostToRealmMappings()
{
DWORD WinError, KeyIndex = 0;
NTSTATUS Status = STATUS_SUCCESS;
HKEY Key = NULL, SubKey = NULL;
WCHAR KeyName[MAX_KEY_NAME];
DWORD KeyNameSize = MAX_KEY_NAME;
if (!HostToRealmInitialized)
{
DsysAssert(FALSE);
return;
}
HostToRealmWriteLock();
WinError = RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
KERB_HOST_TO_REALM_KEY,
0,
KEY_READ,
&Key
);
if (WinError != ERROR_SUCCESS)
{
goto Cleanup;
}
while (TRUE)
{
WinError = RegEnumKeyExW(
Key,
KeyIndex,
KeyName,
&KeyNameSize,
NULL,
NULL,
NULL,
NULL
);
if (WinError != ERROR_SUCCESS)
{
D_DebugLog((DEB_ERROR, "RegEnumKeyExW failed - %x\n", WinError));
goto Cleanup;
}
WinError = RegOpenKeyExW(
Key,
KeyName,
0,
KEY_READ,
&SubKey
);
if (WinError != ERROR_SUCCESS)
{
goto Cleanup;
}
Status = KerbAddHostToRealmMapping(
SubKey,
KeyName
);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR,"KerbAddHostToRealmMapping failed! - %x\n", Status));
goto LocalCleanup;
}
LocalCleanup:
if (SubKey != NULL)
{
RegCloseKey(SubKey);
SubKey = NULL;
}
KeyIndex++;
KeyNameSize = MAX_KEY_NAME;
} // ** WHILE **
Cleanup:
if (SubKey != NULL)
{
RegCloseKey(SubKey);
}
if (Key != NULL)
{
RegCloseKey(Key);
}
HostToRealmUnlock();
}
//+-------------------------------------------------------------------------
//
// Function: KerbCleanupResult
//
// Synopsis: Cleans up result entry
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns: none
//
// Notes:
//
//
//+-------------------------------------------------------------------------
VOID
KerbCleanupResult(
IN PSPN_CACHE_RESULT Result
)
{
KerbFreeString(&Result->AccountRealm);
KerbFreeString(&Result->TargetRealm);
}
//+-------------------------------------------------------------------------
//
// Function: KerbPurgeResultByIndex
//
// Synopsis: Removes
//
// Effects: Dereferences the spn cache entry to make it go away
// when it is no longer being used.
//
// Arguments: decrements reference count and delets cache entry if it goes
// to zero
//
// Requires: SpnCacheEntry - The spn cache entry to dereference.
//
// Returns: none
//
// Notes:
//
//
//+-------------------------------------------------------------------------
VOID
KerbPurgeResultByIndex(
IN PKERB_SPN_CACHE_ENTRY CacheEntry,
IN ULONG IndexToPurge
)
{
ULONG i;
DebugLog((DEB_ERROR, "Purging %p, %i\n", CacheEntry, IndexToPurge));
KerbCleanupResult(&CacheEntry->Results[IndexToPurge]);
CacheEntry->ResultCount--;
for (i = IndexToPurge; i < CacheEntry->ResultCount; i++)
{
CacheEntry->Results[i] = CacheEntry->Results[i+1];
}
//
// Zero out fields in last entry so we don't leak on an error path (or free
// bogus info) if we reuse the entry...
//
RtlZeroMemory(
&CacheEntry->Results[i],
sizeof(SPN_CACHE_RESULT)
);
}
//+-------------------------------------------------------------------------
//
// Function: KerbDereferenceSpnCacheEntry
//
// Synopsis: Dereferences a spn cache entry
//
// Effects: Dereferences the spn cache entry to make it go away
// when it is no longer being used.
//
// Arguments: decrements reference count and delets cache entry if it goes
// to zero
//
// Requires: SpnCacheEntry - The spn cache entry to dereference.
//
// Returns: none
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID
KerbDereferenceSpnCacheEntry(
IN PKERB_SPN_CACHE_ENTRY SpnCacheEntry
)
{
if (KerbDereferenceListEntry(
&SpnCacheEntry->ListEntry,
&KerbSpnCache
) )
{
KerbFreeSpnCacheEntry(SpnCacheEntry);
}
}
//+-------------------------------------------------------------------------
//
// Function: KerbReferenceSpnCacheEntry
//
// Synopsis: References a spn cache entry
//
// Effects: Increments the reference count on the spn cache entry
//
// Arguments: SpnCacheEntry - spn cache entry to reference
//
// Requires: The spn cache must be locked
//
// Returns: none
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID
KerbReferenceSpnCacheEntry(
IN PKERB_SPN_CACHE_ENTRY SpnCacheEntry,
IN BOOLEAN RemoveFromList
)
{
KerbWriteLockSpnCache();
KerbReferenceListEntry(
&KerbSpnCache,
&SpnCacheEntry->ListEntry,
RemoveFromList
);
KerbUnlockSpnCache();
}
//+-------------------------------------------------------------------------
//
// Function: KerbAgeResults
//
// Synopsis: Ages out a given cache entry's result list. Used
// to reduce the result list to a manageable size, and
// as a scavenger to cleanup orphaned / unused entries.
//
// Effects: Increments the reference count on the spn cache entry
//
// Arguments: SpnCacheEntry - spn cache entry to reference
//
// Requires: The spn cache must be locked
//
// Returns: none
//
// Notes:
//
//
//+-------------------------------------------------------------------------
VOID
KerbAgeResults(
IN PKERB_SPN_CACHE_ENTRY CacheEntry
)
{
TimeStamp CurrentTime, BackoffTime;
ULONG i;
LONG Interval;
GetSystemTimeAsFileTime((PFILETIME) &CurrentTime);
//
// Age out everything older than GlobalSpnCacheTimeout first.
//
for ( i = 0; i < CacheEntry->ResultCount; i++ )
{
if (KerbGetTime(CacheEntry->Results[i].CacheStartTime) + KerbGetTime(KerbGlobalSpnCacheTimeout) < KerbGetTime(CurrentTime))
{
D_DebugLog((DEB_TRACE_SPN_CACHE, "removing %x %p\n"));
KerbPurgeResultByIndex(CacheEntry, i);
}
}
if ( CacheEntry->ResultCount < MAX_RESULTS )
{
return;
}
for ( Interval = 13; Interval > 0; Interval -= 4)
{
KerbSetTimeInMinutes(&BackoffTime, Interval);
for ( i=0; i < CacheEntry->ResultCount ; i++ )
{
if (KerbGetTime(CacheEntry->Results[i].CacheStartTime) + KerbGetTime(BackoffTime) < KerbGetTime(CurrentTime))
{
D_DebugLog((DEB_TRACE_SPN_CACHE, "removing %x %p\n"));
KerbPurgeResultByIndex(CacheEntry, i);
}
}
if ( CacheEntry->ResultCount < MAX_RESULTS )
{
return;
}
}
//
// Still have MAX_RESULTS after all that geezzz..
//
DebugLog((DEB_ERROR, "Can't get below MAX_RESULTS (%p) \n", CacheEntry ));
DsysAssert(FALSE);
for ( i=0; i < CacheEntry->ResultCount ; i++ )
{
KerbPurgeResultByIndex(CacheEntry, i);
}
return;
}
//+-------------------------------------------------------------------------
//
// Function: KerbTaskSpnCacheScavenger
//
// Synopsis: Cleans up any old SPN cache entries. Triggered by 30 minute
// task.
//
// Effects:
//
// Arguments: SpnCacheEntry - spn cache entry to reference
//
// Requires: The spn cache entry must be locked
//
// Returns: none
//
// Notes:
//
//
//
VOID
KerbSpnCacheScavenger()
{
PKERB_SPN_CACHE_ENTRY CacheEntry = NULL;
PLIST_ENTRY ListEntry;
BOOLEAN FreeMe = FALSE;
KerbWriteLockSpnCache();
for (ListEntry = KerbSpnCache.List.Flink ;
ListEntry != &KerbSpnCache.List ;
ListEntry = ListEntry->Flink )
{
CacheEntry = CONTAINING_RECORD(ListEntry, KERB_SPN_CACHE_ENTRY, ListEntry.Next);
KerbWriteLockSpnCacheEntry( CacheEntry );
KerbAgeResults(CacheEntry);
//
// Time to pull this one from list.
//
if ( CacheEntry->ResultCount == 0 )
{
ListEntry = ListEntry->Blink;
KerbReferenceSpnCacheEntry(
CacheEntry,
TRUE
);
FreeMe = TRUE;
}
KerbUnlockSpnCacheEntry( CacheEntry );
//
// Pull the list reference.
//
if ( FreeMe )
{
KerbDereferenceSpnCacheEntry( CacheEntry );
FreeMe = FALSE;
}
}
KerbUnlockSpnCache();
}
//+-------------------------------------------------------------------------
//
// Function: KerbAddCacheResult
//
// Synopsis: Uses registry to create
//
// Effects: Increments the reference count on the spn cache entry
//
// Arguments: SpnCacheEntry - spn cache entry to reference
//
// Requires: The spn cache resource must be locked
//
// Returns: none
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbAddCacheResult(
IN PKERB_SPN_CACHE_ENTRY CacheEntry,
IN PKERB_PRIMARY_CREDENTIAL AccountCredential,
IN ULONG UpdateFlags,
IN OPTIONAL PUNICODE_STRING NewRealm
)
{
NTSTATUS Status = STATUS_SUCCESS;
PSPN_CACHE_RESULT Result = NULL;
D_DebugLog((DEB_TRACE_SPN_CACHE, "KerbAddCacheResult add domain %wZ to _KERB_SPN_CACHE_ENTRY %p (UpdateFlags %#x), NewRealm %wZ for ", &AccountCredential->DomainName, CacheEntry, UpdateFlags, NewRealm));
D_KerbPrintKdcName((DEB_TRACE_SPN_CACHE, CacheEntry->Spn));
//
// If we don't have an account realm w/ this credential (e.g someone
// supplied you a UPN to acquirecredentialshandle, don't use the
// spn cache.
//
if ( AccountCredential->DomainName.Length == 0 )
{
return STATUS_NOT_SUPPORTED;
}
//
// First off, see if we're hitting the limits for our array.
// We shouldn't ever get close to MAX_RESULTS, but if we do,
// age out the least current CacheResult.
//
if ( (CacheEntry->ResultCount + 1) == MAX_RESULTS )
{
KerbAgeResults(CacheEntry);
}
Result = &CacheEntry->Results[CacheEntry->ResultCount];
Status = KerbDuplicateStringEx(
&Result->AccountRealm,
&AccountCredential->DomainName,
FALSE
);
if (!NT_SUCCESS( Status ))
{
goto Cleanup;
}
if (ARGUMENT_PRESENT( NewRealm ))
{
D_DebugLog((DEB_TRACE_SPN_CACHE, "Known - realm %wZ\n", NewRealm));
DsysAssert(UpdateFlags != KERB_SPN_UNKNOWN);
Status = KerbDuplicateStringEx(
&Result->TargetRealm,
NewRealm,
FALSE
);
if (!NT_SUCCESS( Status ))
{
goto Cleanup;
}
}
#if DBG
else
{
DsysAssert(UpdateFlags != KERB_SPN_KNOWN);
}
#endif
Result->CacheFlags = UpdateFlags;
GetSystemTimeAsFileTime((PFILETIME) &Result->CacheStartTime);
CacheEntry->ResultCount++;
Cleanup:
if (!NT_SUCCESS( Status ) )
{
if ( Result != NULL )
{
KerbCleanupResult( Result );
}
}
return Status;
}
//+-------------------------------------------------------------------------
//
// Function: KerbBuildSpnCacheEntry
//
// Synopsis: Builds a spn cache entry
//
// Effects:
//
// Arguments:
//
// Requires: SpnCacheEntry - The spn cache entry to dereference.
//
// Returns: none
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbCreateSpnCacheEntry(
IN PKERB_INTERNAL_NAME Spn,
IN PKERB_PRIMARY_CREDENTIAL AccountCredential,
IN ULONG UpdateFlags,
IN OPTIONAL PUNICODE_STRING NewRealm,
IN OUT PKERB_SPN_CACHE_ENTRY* NewCacheEntry
)
{
NTSTATUS Status;
PKERB_SPN_CACHE_ENTRY CacheEntry = NULL;
BOOLEAN FreeResource = FALSE;
*NewCacheEntry = NULL;
CacheEntry = (PKERB_SPN_CACHE_ENTRY) KerbAllocate( sizeof(KERB_SPN_CACHE_ENTRY) );
if ( CacheEntry == NULL )
{
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
Status = KerbDuplicateKdcName(
&CacheEntry->Spn,
Spn
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
Status = KerbAddCacheResult(
CacheEntry,
AccountCredential,
UpdateFlags,
NewRealm
);
if (!NT_SUCCESS( Status ))
{
goto Cleanup;
}
KerbInitializeListEntry( &CacheEntry->ListEntry );
__try
{
RtlInitializeResource( &CacheEntry->ResultLock );
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
FreeResource = TRUE;
KerbInsertSpnCacheEntry(CacheEntry);
*NewCacheEntry = CacheEntry;
CacheEntry = NULL;
InterlockedIncrement( &SpnCount );
Cleanup:
if (!NT_SUCCESS(Status) && ( CacheEntry ))
{
KerbCleanupResult(&CacheEntry->Results[0]);
KerbFreeKdcName( &CacheEntry->Spn );
if ( FreeResource )
{
RtlDeleteResource( &CacheEntry->ResultLock );
}
KerbFree(CacheEntry);
}
return Status;
}
//+-------------------------------------------------------------------------
//
// Function: KerbScanResults
//
// Synopsis: Scans result list.
//
// Effects:
//
// Arguments:
//
// Requires: SpnCacheEntry - The spn cache entry to dereference.
//
// Returns: none
//
// Notes:
//
//
//---
BOOLEAN
KerbScanResults(
IN PKERB_SPN_CACHE_ENTRY CacheEntry,
IN PUNICODE_STRING Realm,
IN OUT PULONG Index
)
{
BOOLEAN Found = FALSE;
ULONG i;
for ( i=0; i < CacheEntry->ResultCount; i++)
{
if (RtlEqualUnicodeString(
&CacheEntry->Results[i].AccountRealm,
Realm,
TRUE
))
{
Found = TRUE;
*Index = i;
break;
}
}
return Found;
}
//+-------------------------------------------------------------------------
//
// Function: KerbUpdateSpnCacheEntry
//
// Synopsis: Updates a spn cache entry
//
// Effects:
//
// Arguments:
//
// Requires: SpnCacheEntry - The spn cache entry to dereference.
//
// Returns: none
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbUpdateSpnCacheEntry(
IN OPTIONAL PKERB_SPN_CACHE_ENTRY ExistingCacheEntry,
IN PKERB_INTERNAL_NAME Spn,
IN PKERB_PRIMARY_CREDENTIAL AccountCredential,
IN ULONG UpdateFlags,
IN OPTIONAL PUNICODE_STRING NewRealm
)
{
PKERB_SPN_CACHE_ENTRY CacheEntry = ExistingCacheEntry;
NTSTATUS Status = STATUS_SUCCESS;
BOOLEAN Found = FALSE, Update = FALSE;
ULONG Index = 0;
//
// We're not using SPN cache
//
if (KerbGlobalSpnCacheTimeout.QuadPart == 0 || !KerberosSpnCacheInitialized )
{
return STATUS_SUCCESS;
}
//
// If we didn't have a cache entry before, see if we do now, or create
// one if necessary.
//
if (!ARGUMENT_PRESENT( ExistingCacheEntry ))
{
KerbWriteLockSpnCache();
CacheEntry = KerbLocateSpnCacheEntry( Spn );
if ( CacheEntry == NULL)
{
Status = KerbCreateSpnCacheEntry(
Spn,
AccountCredential,
UpdateFlags,
NewRealm,
&CacheEntry
);
if (NT_SUCCESS(Status))
{
//
// All done, get outta here.
//
D_DebugLog((DEB_TRACE_SPN_CACHE, "Created new cache entry %p (%x) \n", CacheEntry, UpdateFlags));
D_KerbPrintKdcName((DEB_TRACE_SPN_CACHE, Spn));
D_DebugLog((DEB_TRACE_SPN_CACHE, "%wZ\n", &AccountCredential->DomainName));
KerbDereferenceSpnCacheEntry( CacheEntry );
}
KerbUnlockSpnCache();
return Status;
}
KerbUnlockSpnCache();
}
//
// Got an existing entry - update it.
//
KerbReadLockSpnCacheEntry( CacheEntry );
if (KerbScanResults(
CacheEntry,
&AccountCredential->DomainName,
&Index
))
{
Found = TRUE;
Update = (( CacheEntry->Results[Index].CacheFlags & UpdateFlags) != UpdateFlags);
}
KerbUnlockSpnCacheEntry( CacheEntry );
//
// To avoid always taking the write lock, we'll need to rescan the result
// list under a write lock.
//
if ( Update )
{
KerbWriteLockSpnCacheEntry( CacheEntry );
if (KerbScanResults(
CacheEntry,
&AccountCredential->DomainName,
&Index
))
{
//
// Hasn't been updated or removed in the small time slice above. Update.
//
if (( CacheEntry->Results[Index].CacheFlags & UpdateFlags) != UpdateFlags )
{
D_DebugLog((
DEB_TRACE_SPN_CACHE,
"KerbUpdateSpnCacheEntry changing _KERB_SPN_CACHE_ENTRY %p Result Index %#x: AccountRealm %wZ, TargetRealm %wZ, NewRealm %wZ, CacheFlags %#x to CacheFlags %#x for ",
CacheEntry,
Index,
&CacheEntry->Results[Index].AccountRealm,
&CacheEntry->Results[Index].TargetRealm,
NewRealm,
CacheEntry->Results[Index].CacheFlags,
UpdateFlags
));
D_KerbPrintKdcName((DEB_TRACE_SPN_CACHE, CacheEntry->Spn));
CacheEntry->Results[Index].CacheFlags = UpdateFlags;
GetSystemTimeAsFileTime( (LPFILETIME) &CacheEntry->Results[Index].CacheStartTime );
KerbFreeString(&CacheEntry->Results[Index].TargetRealm);
if (ARGUMENT_PRESENT( NewRealm ))
{
DsysAssert( UpdateFlags == KERB_SPN_KNOWN );
Status = KerbDuplicateStringEx(
&CacheEntry->Results[Index].TargetRealm,
NewRealm,
FALSE
);
if (!NT_SUCCESS( Status ))
{
KerbUnlockSpnCacheEntry( CacheEntry );
goto Cleanup;
}
}
}
}
else
{
Found = FALSE;
}
KerbUnlockSpnCacheEntry( CacheEntry );
}
if (!Found)
{
KerbWriteLockSpnCacheEntry ( CacheEntry );
//
// Still not found
//
if (!KerbScanResults( CacheEntry, &AccountCredential->DomainName, &Index ))
{
Status = KerbAddCacheResult(
CacheEntry,
AccountCredential,
UpdateFlags,
NewRealm
);
}
KerbUnlockSpnCacheEntry( CacheEntry );
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
}
Cleanup:
//
// Created a new cache entry, referenced w/i this function.
//
if (!ARGUMENT_PRESENT( ExistingCacheEntry ) && CacheEntry )
{
KerbDereferenceSpnCacheEntry( CacheEntry );
}
return Status;
}
//+-------------------------------------------------------------------------
//
// Function: KerbLocateSpnCacheEntry
//
// Synopsis: References a spn cache entry by name
//
// Effects: Increments the reference count on the spn cache entry
//
// Arguments: RealmName - Contains the name of the realm for which to
// obtain a binding handle.
// DesiredFlags - Flags desired for binding, such as PDC required
// RemoveFromList - Remove cache entry from cache when found.
//
// 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_SPN_CACHE_ENTRY
KerbLocateSpnCacheEntry(
IN PKERB_INTERNAL_NAME Spn
)
{
PLIST_ENTRY ListEntry;
PKERB_SPN_CACHE_ENTRY CacheEntry = NULL;
BOOLEAN Found = FALSE;
if (Spn->NameType != KRB_NT_SRV_INST)
{
return NULL;
}
#if DBG
KerbLogSpnStats(Spn);
#endif
//
// We're not using SPN cache
//
if (KerbGlobalSpnCacheTimeout.QuadPart == 0 || !KerberosSpnCacheInitialized )
{
return NULL;
}
//
// Scale the cache by aging out old entries.
//
if ( SpnCount > MAX_CACHE_ENTRIES )
{
KerbSpnCacheScavenger();
}
KerbReadLockSpnCache();
//
// Go through the spn cache looking for the correct entry
//
for (ListEntry = KerbSpnCache.List.Flink ;
ListEntry != &KerbSpnCache.List ;
ListEntry = ListEntry->Flink )
{
CacheEntry = CONTAINING_RECORD(ListEntry, KERB_SPN_CACHE_ENTRY, ListEntry.Next);
if (KerbEqualKdcNames(CacheEntry->Spn,Spn))
{
KerbReferenceSpnCacheEntry(
CacheEntry,
FALSE
);
D_DebugLog((DEB_TRACE_SPN_CACHE, "SpnCacheEntry %p\n", CacheEntry));
Found = TRUE;
break;
}
}
if (!Found)
{
CacheEntry = NULL;
}
KerbUnlockSpnCache();
return(CacheEntry);
}
//+-------------------------------------------------------------------------
//
// Function: KerbCleanupResultList
//
// Synopsis: Frees memory associated with a result list
//
// Effects:
//
// Arguments: SpnCacheEntry - The cache entry to free. It must be
// unlinked, and the Resultlock must be held.
//
// Requires:
//
// Returns: none
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID
KerbCleanupResultList(
IN PKERB_SPN_CACHE_ENTRY CacheEntry
)
{
for (ULONG i = 0; i < CacheEntry->ResultCount; i++)
{
KerbCleanupResult(&CacheEntry->Results[i]);
}
CacheEntry->ResultCount = 0;
}
//+-------------------------------------------------------------------------
//
// Function: KerbFreeSpnCacheEntry
//
// Synopsis: Frees memory associated with a spn cache entry
//
// Effects:
//
// Arguments: SpnCacheEntry - The cache entry to free. It must be
// unlinked.
//
// Requires:
//
// Returns: none
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID
KerbFreeSpnCacheEntry(
IN PKERB_SPN_CACHE_ENTRY SpnCacheEntry
)
{
//
// Must be unlinked..
//
DsysAssert(SpnCacheEntry->ListEntry.Next.Flink == NULL);
DsysAssert(SpnCacheEntry->ListEntry.Next.Blink == NULL);
KerbWriteLockSpnCacheEntry(SpnCacheEntry);
KerbCleanupResultList(SpnCacheEntry);
KerbUnlockSpnCacheEntry(SpnCacheEntry);
RtlDeleteResource(&SpnCacheEntry->ResultLock);
KerbFreeKdcName(&SpnCacheEntry->Spn);
KerbFree(SpnCacheEntry);
InterlockedDecrement( &SpnCount );
}
//+-------------------------------------------------------------------------
//
// Function: KerbInsertBinding
//
// Synopsis: Inserts a binding into the spn cache
//
// Effects: bumps reference count on binding
//
// Arguments: CacheEntry - Cache entry to insert
//
// Requires:
//
// Returns: STATUS_SUCCESS always
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbInsertSpnCacheEntry(
IN PKERB_SPN_CACHE_ENTRY CacheEntry
)
{
IF_DEBUG(DISABLE_SPN_CACHE)
{
DebugLog((DEB_TRACE_SPN_CACHE, "KerbInsertSpnCacheEntry spn cache disabled\n"));
return STATUS_SUCCESS;
}
KerbInsertListEntry(
&CacheEntry->ListEntry,
&KerbSpnCache
);
return(STATUS_SUCCESS);
}
//+-------------------------------------------------------------------------
//
// Function: KerbGetSpnCacheStatus
//
// Synopsis: Gets the status of a cache entry for a given realm.
//
// Effects: Returns STATUS_NO_SAM_TRUST_RELATIONSHIP for unknown SPNs,
// STATUS_NO_MATCH, if we're missing a realm result, or
// STATUS_SUCCESS ++ dupe the SPNREalm for the "real" realm
// of the SPN relative to the account realm.
//
// Arguments: CacheEntry - SPN cache entry from ProcessTargetName()
// Credential - Primary cred for account realm.
// SpnRealm - IN OUT Filled in w/ target realm of SPN
//
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbGetSpnCacheStatus(
IN PKERB_SPN_CACHE_ENTRY CacheEntry,
IN PKERB_PRIMARY_CREDENTIAL Credential,
IN OUT PUNICODE_STRING SpnRealm
)
{
NTSTATUS Status = STATUS_NO_MATCH;;
ULONG i;
TimeStamp CurrentTime;
BOOLEAN Purge = FALSE;
GetSystemTimeAsFileTime((PFILETIME) &CurrentTime);
//
// Read Lock the spn cache entry
//
KerbReadLockSpnCacheEntry( CacheEntry );
if (KerbScanResults(
CacheEntry,
&Credential->DomainName,
&i
))
{
if (CacheEntry->Results[i].CacheFlags & KERB_SPN_UNKNOWN)
{
//
// Check and see if this timestamp has expired.
//
if (KerbGetTime(CacheEntry->Results[i].CacheStartTime) + KerbGetTime(KerbGlobalSpnCacheTimeout) < KerbGetTime(CurrentTime))
{
Purge = TRUE;
Status = STATUS_SUCCESS;
}
else
{
Status = STATUS_NO_TRUST_SAM_ACCOUNT;
DebugLog((DEB_WARN, "SPN not found\n"));
KerbPrintKdcName(DEB_WARN, CacheEntry->Spn);
}
}
else if (CacheEntry->Results[i].CacheFlags & KERB_SPN_KNOWN)
{
Status = KerbDuplicateStringEx(
SpnRealm,
&CacheEntry->Results[i].TargetRealm,
FALSE
);
D_DebugLog((DEB_TRACE_SPN_CACHE, "Found %wZ\n", SpnRealm));
D_KerbPrintKdcName((DEB_TRACE_SPN_CACHE, CacheEntry->Spn));
}
}
KerbUnlockSpnCacheEntry( CacheEntry );
if (!NT_SUCCESS( Status ))
{
return Status;
}
//
// Take the write lock, and verify that we still need to purge the above
// result.
//
if ( Purge )
{
KerbWriteLockSpnCacheEntry( CacheEntry );
if (KerbScanResults(
CacheEntry,
&Credential->DomainName,
&i
))
{
if (KerbGetTime(CacheEntry->Results[i].CacheStartTime) +
KerbGetTime(KerbGlobalSpnCacheTimeout) < KerbGetTime(CurrentTime))
{
D_DebugLog((DEB_TRACE_SPN_CACHE, "Purging %p due to time\n", &CacheEntry->Results[i]));
KerbPurgeResultByIndex( CacheEntry, i );
}
}
KerbUnlockSpnCacheEntry( CacheEntry );
Status = STATUS_NO_MATCH;
}
return Status;
}