//+-----------------------------------------------------------------------
//
// 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 )


//+-------------------------------------------------------------------------
//
//  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 );
    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:   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;
    }
    
    //
    // 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
    )
{
    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;                            
}