|
|
//+---------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1992 - 1995.
//
// File: cache.c
//
// Contents:
//
// Classes:
//
// Functions:
//
// History: 09-23-97 jbanes LSA integration stuff.
// 07-31-98 jbanes Made thread-safe.
//
//----------------------------------------------------------------------------
#include "spbase.h"
#include <limits.h>
#include <mapper.h>
#include <sslcache.h>
#include <crypt.h>
SCHANNEL_CACHE SchannelCache = { NULL, // SessionCache
SP_CACHE_CLIENT_LIFESPAN, // dwClientLifespan
SP_CACHE_SERVER_LIFESPAN, // dwServerLifespan
SP_CACHE_CLEANUP_INTERVAL, // dwCleanupInterval
SP_MAXIMUM_CACHE_ELEMENTS, // dwCacheSize
SP_MAXIMUM_CACHE_ELEMENTS, // dwMaximumEntries
0 // dwUsedEntries
};
RTL_CRITICAL_SECTION g_CacheCleanupLock; BOOL g_CacheCleanupCritSectInitialized = FALSE; LIST_ENTRY g_CacheCleanupList; HANDLE g_CacheCleanupEvent = NULL; HANDLE g_CacheCleanupWaitObject = NULL; BOOL g_fMultipleProcessClientCache = FALSE; BOOL g_fCacheInitialized = FALSE;
// Perf counter values.
LONG g_cClientHandshakes = 0; LONG g_cServerHandshakes = 0; LONG g_cClientReconnects = 0; LONG g_cServerReconnects = 0;
BOOL SPCacheDelete( PSessCacheItem pItem);
BOOL CacheExpireElements( BOOL fBackground);
VOID CacheCleanupHandler( PVOID pVoid, BOOLEAN fTimeout);
SP_STATUS SPInitSessionCache(VOID) { DWORD i; NTSTATUS Status = STATUS_SUCCESS;
//
// Allocate memory for cache, and initialize synchronization resource.
//
InitializeListHead(&SchannelCache.EntryList);
__try { RtlInitializeResource(&SchannelCache.Lock); } __except(EXCEPTION_EXECUTE_HANDLER) { Status = STATUS_INSUFFICIENT_RESOURCES; goto cleanup; } SchannelCache.LockInitialized = TRUE;
SchannelCache.SessionCache = (PLIST_ENTRY)SPExternalAlloc(SchannelCache.dwCacheSize * sizeof(LIST_ENTRY)); if(SchannelCache.SessionCache == NULL) { Status = SP_LOG_RESULT(STATUS_NO_MEMORY); goto cleanup; }
for(i = 0; i < SchannelCache.dwCacheSize; i++) { InitializeListHead(&SchannelCache.SessionCache[i]); }
DebugLog((DEB_TRACE, "Space reserved at 0x%x for %d cache entries.\n", SchannelCache.SessionCache, SchannelCache.dwCacheSize));
//
// Initialize issuer cache.
//
SPInitIssuerCache();
//
// Initialize cache cleanup objects.
//
InitializeListHead(&g_CacheCleanupList); Status = RtlInitializeCriticalSection(&g_CacheCleanupLock); if(!NT_SUCCESS(Status)) { goto cleanup; } g_CacheCleanupCritSectInitialized = TRUE;
g_CacheCleanupEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if(NULL == g_CacheCleanupEvent) { Status = GetLastError(); goto cleanup; }
if(!RegisterWaitForSingleObject(&g_CacheCleanupWaitObject, g_CacheCleanupEvent, CacheCleanupHandler, NULL, SchannelCache.dwCleanupInterval, WT_EXECUTEDEFAULT)) { Status = GetLastError(); goto cleanup; }
g_fCacheInitialized = TRUE;
Status = STATUS_SUCCESS;
cleanup:
if(!NT_SUCCESS(Status)) { SPShutdownSessionCache(); }
return Status; }
SP_STATUS SPShutdownSessionCache(VOID) { PSessCacheItem pItem; PLIST_ENTRY pList;
SP_BEGIN("SPShutdownSessionCache");
if(SchannelCache.LockInitialized) { RtlAcquireResourceExclusive(&SchannelCache.Lock, TRUE); }
g_fCacheInitialized = FALSE;
if(SchannelCache.SessionCache != NULL) { // Blindly kill all cache items.
// No contexts should be running at
// this time.
pList = SchannelCache.EntryList.Flink;
while(pList != &SchannelCache.EntryList) { pItem = CONTAINING_RECORD(pList, SessCacheItem, EntryList.Flink); pList = pList->Flink;
SPCacheDelete(pItem); }
SPExternalFree(SchannelCache.SessionCache); }
if(g_CacheCleanupCritSectInitialized) { RtlDeleteCriticalSection(&g_CacheCleanupLock); g_CacheCleanupCritSectInitialized = FALSE; }
if(g_CacheCleanupWaitObject) { UnregisterWaitEx(g_CacheCleanupWaitObject, INVALID_HANDLE_VALUE); g_CacheCleanupWaitObject = NULL; }
if(g_CacheCleanupEvent) { CloseHandle(g_CacheCleanupEvent); g_CacheCleanupEvent = NULL; }
if(SchannelCache.LockInitialized) { RtlDeleteResource(&SchannelCache.Lock); SchannelCache.LockInitialized = FALSE; }
SPShutdownIssuerCache();
SP_RETURN(PCT_ERR_OK); }
LONG SPCacheReference( PSessCacheItem pItem) { LONG cRet;
if(pItem == NULL) { return -1; }
ASSERT(pItem->Magic == SP_CACHE_MAGIC);
cRet = InterlockedIncrement(&pItem->cRef);
return cRet; }
LONG SPCacheDereference(PSessCacheItem pItem) { long cRet;
if(pItem == NULL) { return -1; }
ASSERT(pItem->Magic == SP_CACHE_MAGIC);
cRet = InterlockedDecrement(&pItem->cRef);
ASSERT(cRet > 0);
return cRet; }
BOOL SPCacheDelete( PSessCacheItem pItem) { DebugLog((DEB_TRACE, "Delete cache item:0x%x\n", pItem));
if(pItem == NULL) { return FALSE; }
ASSERT(pItem->Magic == SP_CACHE_MAGIC);
if(pItem->hMasterKey) { if(!CryptDestroyKey(pItem->hMasterKey)) { SP_LOG_RESULT(GetLastError()); } pItem->hMasterKey = 0; }
if(pItem->pRemoteCert) { CertFreeCertificateContext(pItem->pRemoteCert); pItem->pRemoteCert = NULL; }
if(pItem->pRemotePublic) { SPExternalFree(pItem->pRemotePublic); pItem->pRemotePublic = NULL; }
if(pItem->phMapper) { if(pItem->hLocator) { SslCloseLocator(pItem->phMapper, pItem->hLocator); pItem->hLocator = 0; } SslDereferenceMapper(pItem->phMapper); } pItem->phMapper = NULL;
if(pItem->pbServerCertificate) { SPExternalFree(pItem->pbServerCertificate); pItem->pbServerCertificate = NULL; pItem->cbServerCertificate = 0; }
pItem->pActiveServerCred = NULL;
if(pItem->pServerCred) { SPDereferenceCredential(pItem->pServerCred, FALSE); pItem->pServerCred = NULL; }
if(pItem->szCacheID) { SPExternalFree(pItem->szCacheID); pItem->szCacheID = NULL; }
if(pItem->pClientCred) { SPDeleteCred(pItem->pClientCred, TRUE); pItem->pClientCred = NULL; }
if(pItem->pClientCert) { CertFreeCertificateContext(pItem->pClientCert); pItem->pClientCert = NULL; }
if(pItem->pClonedItem) { SPCacheDereference(pItem->pClonedItem); pItem->pClonedItem = NULL; }
if(pItem->pbAppData) { SPExternalFree(pItem->pbAppData); pItem->pbAppData = NULL; }
SPExternalFree(pItem);
return TRUE; }
void SPCachePurgeCredential( PSPCredentialGroup pCred) { PSessCacheItem pItem; PLIST_ENTRY pList;
//
// If this is a client credential and the cross-process cache is enabled, then
// don't purge its cache entries.
//
if(g_fMultipleProcessClientCache && (pCred->grbitProtocol & SP_PROT_CLIENTS)) { return; }
//
// Search through the cache entries looking for entries that are
// bound to the specified server credential.
//
if(SchannelCache.dwUsedEntries == 0) { return; }
RtlAcquireResourceShared(&SchannelCache.Lock, TRUE);
pList = SchannelCache.EntryList.Flink;
while(pList != &SchannelCache.EntryList) { pItem = CONTAINING_RECORD(pList, SessCacheItem, EntryList.Flink); pList = pList->Flink;
ASSERT(pItem->Magic == SP_CACHE_MAGIC);
// Does this item match the current credentials?
if(!IsSameThumbprint(&pCred->CredThumbprint, &pItem->CredThumbprint)) { continue; }
// Mark this entry as non-resumable. This will cause the entry to
// be deleted automatically by the cleanup routines.
pItem->ZombieJuju = FALSE; pItem->DeferredJuju = FALSE; }
RtlReleaseResource(&SchannelCache.Lock);
//
// Delete all unused non-resumable cache entries. This will release all of
// the cached master key objects belonging to this credential. This must be
// done now because the owning provider context will often be destroyed
// along with the credential.
//
CacheExpireElements(FALSE); }
void SPCachePurgeProcessId( ULONG ProcessId) { PSessCacheItem pItem; PLIST_ENTRY pList; //
// Search through the cache entries looking for entries that are
// bound to the specified process.
//
RtlAcquireResourceShared(&SchannelCache.Lock, TRUE);
pList = SchannelCache.EntryList.Flink;
while(pList != &SchannelCache.EntryList) { pItem = CONTAINING_RECORD(pList, SessCacheItem, EntryList.Flink); pList = pList->Flink;
ASSERT(pItem->Magic == SP_CACHE_MAGIC);
// Does this item match the specified process?
if(pItem->ProcessID != ProcessId) { continue; }
// Mark the entry as ownerless.
pItem->ProcessID = 0;
// Mark this entry as non-resumable. This will cause the entry to
// be deleted automatically by the cleanup routines.
pItem->ZombieJuju = FALSE; pItem->DeferredJuju = FALSE; }
RtlReleaseResource(&SchannelCache.Lock); }
BOOL IsSameTargetName( LPWSTR Name1, LPWSTR Name2) { if(Name1 == Name2) { return TRUE; }
if(Name1 == NULL || Name2 == NULL || wcscmp(Name1, Name2) != 0) { return FALSE; }
return TRUE; }
BOOL DoesAppAllowCipher( PSPCredentialGroup pCredGroup, PSessCacheItem pItem) { PKeyExchangeInfo pExchInfo;
if(pCredGroup == NULL) { return FALSE; }
//
// Is protocol supported?
//
if((pItem->fProtocol & pCredGroup->grbitEnabledProtocols) == 0) { return FALSE; }
//
// Is cipher supported?
//
if(pItem->dwStrength < pCredGroup->dwMinStrength) { return FALSE; }
if(pItem->dwStrength > pCredGroup->dwMaxStrength) { return FALSE; }
if(!IsAlgAllowed(pCredGroup, pItem->aiCipher)) { return FALSE; }
//
// Is hash supported?
//
if(!IsAlgAllowed(pCredGroup, pItem->aiHash)) { return FALSE; }
//
// Is exchange alg supported?
//
if(pItem->SessExchSpec != SP_EXCH_UNKNOWN) { pExchInfo = GetKeyExchangeInfo(pItem->SessExchSpec); if(pExchInfo == NULL) { return FALSE; }
if((pExchInfo->fProtocol & pItem->fProtocol) == 0) { return FALSE; }
if(!IsAlgAllowed(pCredGroup, pExchInfo->aiExch)) { return FALSE; } }
return TRUE; }
BOOL SPCacheRetrieveBySession( struct _SPContext * pContext, PBYTE pbSessionID, DWORD cbSessionID, PSessCacheItem *ppRetItem) { DWORD index; DWORD timeNow; ULONG ProcessID; PSessCacheItem pItem = NULL; PLIST_ENTRY pList; BOOL fFound = FALSE;
DebugLog((DEB_TRACE, "SPCacheRetrieveBySession (0x%x) called\n", pContext));
if(ppRetItem == NULL) { return FALSE; }
//
// Compute the cache index.
//
if(cbSessionID < sizeof(DWORD)) { DebugLog((DEB_TRACE, " FAILED\n")); return FALSE; } CopyMemory((PBYTE)&index, pbSessionID, sizeof(DWORD));
if(index >= SchannelCache.dwCacheSize) { DebugLog((DEB_TRACE, " FAILED\n")); return FALSE; }
//
// Retrieve the current time and application process id.
//
timeNow = GetTickCount();
SslGetClientProcess(&ProcessID);
//
// Lock the cache for read.
//
RtlAcquireResourceShared(&SchannelCache.Lock, TRUE);
//
// Search through the cache entries at the computed index.
//
pList = SchannelCache.SessionCache[index].Flink;
while(pList != &SchannelCache.SessionCache[index]) { pItem = CONTAINING_RECORD(pList, SessCacheItem, IndexEntryList.Flink); pList = pList->Flink ;
ASSERT(pItem->Magic == SP_CACHE_MAGIC);
// Is this entry resumable?
if(!pItem->ZombieJuju) { continue; }
// Has this item expired?
if(HasTimeElapsed(pItem->CreationTime, timeNow, pItem->Lifespan)) { continue; }
// Does the session id match?
if(cbSessionID != pItem->cbSessionID) { continue; } if(memcmp(pbSessionID, pItem->SessionID, cbSessionID) != 0) { continue; }
// Is this item for the protocol we're using.
if(0 == (pContext->dwProtocol & pItem->fProtocol)) { continue; }
// Does this item belong to our client process?
if(pItem->ProcessID != ProcessID) { continue; }
// Does this item match the current server credentials?
//
// We don't allow different server credentials to share cache
// entries, because if the credential that was used during
// the original full handshake is deleted, then the cache
// entry is unusable. Some server applications (I won't name names)
// create a new credential for each connection, and we have to
// guard against this.
//
// Note that this restriction may result in an extra full
// handshake when IE accesses an IIS site enabled for certificate
// mapping, mostly because IE's behavior is broken.
if(!IsSameThumbprint(&pContext->pCredGroup->CredThumbprint, &pItem->CredThumbprint)) { continue; }
#if 0
// Make sure that the application supports the cipher suite
// used by this cache entry. This becomes important now that
// we're allowing different server credentials to share
// cache entries.
if(!DoesAppAllowCipher(pContext->pCredGroup, pItem)) { continue; } #endif
//
// Found item in cache!!
//
fFound = TRUE; SPCacheReference(pItem);
// Are we replacing something?
// Then dereference the thing we are replacing.
if(*ppRetItem) { SPCacheDereference(*ppRetItem); }
// Return item referenced.
*ppRetItem = pItem; break; }
RtlReleaseResource(&SchannelCache.Lock);
if(fFound) { DebugLog((DEB_TRACE, " FOUND IT(0x%x)\n", pItem)); InterlockedIncrement(&g_cServerReconnects); } else { DebugLog((DEB_TRACE, " FAILED\n")); }
return fFound; }
DWORD ComputeClientCacheIndex( LPWSTR pszTargetName) { DWORD index; MD5_CTX Md5Hash; DWORD cbTargetName;
if(pszTargetName == NULL) { index = 0; } else { cbTargetName = lstrlenW(pszTargetName) * sizeof(WCHAR);
MD5Init(&Md5Hash); MD5Update(&Md5Hash, (PBYTE)pszTargetName, cbTargetName); MD5Final(&Md5Hash); CopyMemory((PBYTE)&index, Md5Hash.digest, sizeof(DWORD));
index %= SchannelCache.dwCacheSize; }
return index; }
BOOL SPCacheRetrieveByName( LPWSTR pszTargetName, PSPCredentialGroup pCredGroup, PSessCacheItem *ppRetItem) { DWORD index; PSessCacheItem pItem; PSessCacheItem pFoundEntry = NULL; DWORD timeNow; LUID LogonId; PLIST_ENTRY pList;
DebugLog((DEB_TRACE, "SPCacheRetrieveByName (%ls) called\n", pszTargetName));
if(ppRetItem == NULL) { return FALSE; }
//
// Retrieve the current time and user logon id.
//
timeNow = GetTickCount();
SslGetClientLogonId(&LogonId);
//
// Compute the cache index.
//
index = ComputeClientCacheIndex(pszTargetName);
//
// Lock the cache for read.
//
RtlAcquireResourceShared(&SchannelCache.Lock, TRUE);
//
// Search through the cache entries at the computed index.
//
pList = SchannelCache.SessionCache[index].Flink;
while(pList != &SchannelCache.SessionCache[index]) { pItem = CONTAINING_RECORD(pList, SessCacheItem, IndexEntryList.Flink); pList = pList->Flink ;
ASSERT(pItem->Magic == SP_CACHE_MAGIC);
// Is this entry resumable?
if(!pItem->ZombieJuju) { continue; }
// Is this item for the protocol we're using?
if(0 == (pCredGroup->grbitEnabledProtocols & pItem->fProtocol)) { continue; }
// Has this item expired?
if(HasTimeElapsed(pItem->CreationTime, timeNow, pItem->Lifespan)) { continue; }
// Does this item belong to our client?
if(!RtlEqualLuid(&pItem->LogonId, &LogonId)) { continue; }
// Does this item match our current credentials?
if(g_fMultipleProcessClientCache) { // If this cache entry has a client certificate associated with it
// and the passed in client credentials contain one or more certificates,
// then we need to make sure that they overlap.
if(IsValidThumbprint(&pItem->CertThumbprint) && pCredGroup->CredCount != 0) { if(!DoesCredThumbprintMatch(pCredGroup, &pItem->CertThumbprint)) { continue; } } } else { // Make sure the thumbprint of the credential group matches the
// thumbprint of the cache entry.
if(!IsSameThumbprint(&pCredGroup->CredThumbprint, &pItem->CredThumbprint)) { continue; } }
if(!IsSameTargetName(pItem->szCacheID, pszTargetName)) { continue; }
// Make sure that the application supports the cipher suite
// used by this cache entry. This becomes important in the
// multi-process client cache scenario, since different client
// applications may be running with different settings.
if(!DoesAppAllowCipher(pCredGroup, pItem)) { continue; }
//
// Found item in cache!!
//
if(pFoundEntry == NULL) { // This is the first matching entry found.
SPCacheReference(pItem);
// Remember the current entry.
pFoundEntry = pItem; } else { if(pItem->CreationTime > pFoundEntry->CreationTime) { // We found a newer entry.
SPCacheReference(pItem);
// Disable searching on the previous item.
pFoundEntry->ZombieJuju = FALSE;
// Release the previous item.
SPCacheDereference(pFoundEntry);
// Remember the current entry.
pFoundEntry = pItem; } else { // This item is older than the previously found entry.
// Disable searching on the current entry.
pItem->ZombieJuju = FALSE; } } }
RtlReleaseResource(&SchannelCache.Lock);
if(pFoundEntry) { // Found item in cache!!
// Are we replacing something?
// Then dereference the thing we are replacing.
if(*ppRetItem) { SPCacheDereference(*ppRetItem); }
// Return item referenced.
*ppRetItem = pFoundEntry;
DebugLog((DEB_TRACE, " FOUND IT(0x%x)\n", pFoundEntry)); InterlockedIncrement(&g_cClientReconnects); } else { DebugLog((DEB_TRACE, " FAILED\n")); }
return (pFoundEntry != NULL); }
//+---------------------------------------------------------------------------
//
// Function: CacheExpireElements
//
// Synopsis: Traverse the session cache and remove all expired entries.
// If the cache is oversized, then expire some entries
// early.
//
// Arguments: [fCleanupOnly] -- If this is set, then attempt to delete
// cache entries previously expired. Don't
// traverse the cache.
//
// History: 01-02-2000 jbanes Created.
//
// Notes: This routine should be called only once every five or ten
// minutes.
//
//----------------------------------------------------------------------------
BOOL CacheExpireElements( BOOL fBackground) { static LONG RefCount = 0; LONG LocalRefCount; DWORD timeNow; PSessCacheItem pItem; PLIST_ENTRY pList;
//
// If another thread is currently expiring elements, then try again
// later.
//
LocalRefCount = InterlockedIncrement(&RefCount);
if(fBackground && LocalRefCount > 1) { InterlockedDecrement(&RefCount); return FALSE; }
RtlEnterCriticalSection(&g_CacheCleanupLock);
//
// Retrieve the current time.
//
timeNow = GetTickCount();
//
// Search through the cache entries looking for expired entries.
//
RtlAcquireResourceExclusive(&SchannelCache.Lock, TRUE);
pList = SchannelCache.EntryList.Flink;
while(pList != &SchannelCache.EntryList) { pItem = CONTAINING_RECORD(pList, SessCacheItem, EntryList.Flink); pList = pList->Flink;
ASSERT(pItem->Magic == SP_CACHE_MAGIC);
// Is the cache entry currently being used?
if(pItem->cRef > 1) { continue; }
// Mark all expired cache entries as non-resumable.
if(HasTimeElapsed(pItem->CreationTime, timeNow, pItem->Lifespan)) { pItem->ZombieJuju = FALSE; pItem->DeferredJuju = FALSE; }
// If the cache has gotten too large, then expire elements early. The
// cache elements are sorted by creation time, so the oldest
// entries will be expired first.
if(SchannelCache.dwUsedEntries > SchannelCache.dwMaximumEntries) { pItem->ZombieJuju = FALSE; pItem->DeferredJuju = FALSE; } // Don't remove entries that are still valid.
if((pItem->ZombieJuju == TRUE) || (pItem->DeferredJuju == TRUE)) { continue; }
//
// Remove this entry from the cache, and add it to the list of
// entries to be destroyed.
//
RemoveEntryList(&pItem->IndexEntryList); RemoveEntryList(&pItem->EntryList); SchannelCache.dwUsedEntries--;
InsertTailList(&g_CacheCleanupList, &pItem->EntryList); }
RtlReleaseResource(&SchannelCache.Lock);
//
// Kill the expired zombies.
//
pList = g_CacheCleanupList.Flink;
while(pList != &g_CacheCleanupList) { pItem = CONTAINING_RECORD(pList, SessCacheItem, EntryList.Flink); pList = pList->Flink;
ASSERT(pItem->Magic == SP_CACHE_MAGIC);
// Remove entry from cleanup list.
RemoveEntryList(&pItem->EntryList);
// Destroy cache entry.
SPCacheDelete(pItem); }
RtlLeaveCriticalSection(&g_CacheCleanupLock);
InterlockedDecrement(&RefCount);
return TRUE; }
VOID CacheCleanupHandler( PVOID pVoid, BOOLEAN fTimeout) { UNREFERENCED_PARAMETER(pVoid);
if(SchannelCache.dwUsedEntries > 0) { if(fTimeout) { DebugLog((DEB_WARN, "Initiate periodic cache cleanup.\n")); }
CacheExpireElements(TRUE);
SPExpireIssuerCacheElements();
ResetEvent(g_CacheCleanupEvent); }
if(fTimeout) { GlobalCheckForCertificateRenewal(); } }
/* allocate a new cache item to be used
* by a context. Initialize it with the * pszTarget if the target exists. * Auto-Generate a SessionID */ BOOL SPCacheRetrieveNew( BOOL fServer, LPWSTR pszTargetName, PSessCacheItem * ppRetItem) { DWORD index; DWORD timeNow; ULONG ProcessID; LUID LogonId = {0}; PSessCacheItem pItem; BYTE rgbSessionId[SP_MAX_SESSION_ID];
DebugLog((DEB_TRACE, "SPCacheRetrieveNew called\n"));
//
// Trigger cache cleanup if too many cache entries already exist.
//
if(SchannelCache.dwUsedEntries > (SchannelCache.dwMaximumEntries * 21) / 20) { DebugLog((DEB_WARN, "Cache size (%d) exceeded threshold (%d), trigger cache cleanup.\n", SchannelCache.dwUsedEntries, SchannelCache.dwMaximumEntries)); SetEvent(g_CacheCleanupEvent); }
//
// Retrieve the current time and user logon id.
//
timeNow = GetTickCount();
SslGetClientProcess(&ProcessID);
if(!fServer) { SslGetClientLogonId(&LogonId); }
//
// Compute the session id and the cache index.
//
if(fServer) { if(!RtlGenRandom(rgbSessionId, sizeof(rgbSessionId))) { return FALSE; } index = *(DWORD *)rgbSessionId % SchannelCache.dwCacheSize; *(DWORD *)rgbSessionId = index; } else { ZeroMemory(rgbSessionId, sizeof(rgbSessionId)); index = ComputeClientCacheIndex(pszTargetName); }
//
// Allocate a new cache entry.
//
pItem = SPExternalAlloc(sizeof(SessCacheItem)); if(pItem == NULL) { SP_LOG_RESULT(SEC_E_INSUFFICIENT_MEMORY); return FALSE; }
//
// Fill in the cache internal fields.
//
pItem->Magic = SP_CACHE_MAGIC; pItem->cRef = 1;
pItem->CreationTime = timeNow; if(fServer) { pItem->Lifespan = SchannelCache.dwServerLifespan; } else { pItem->Lifespan = SchannelCache.dwClientLifespan; }
pItem->ProcessID = ProcessID; pItem->LogonId = LogonId;
if(pszTargetName) { pItem->szCacheID = SPExternalAlloc((lstrlenW(pszTargetName) + 1) * sizeof(WCHAR)); if(pItem->szCacheID == NULL) { SP_LOG_RESULT(SEC_E_INSUFFICIENT_MEMORY); SPExternalFree(pItem); return FALSE; } wcscpy(pItem->szCacheID, pszTargetName); } else { pItem->szCacheID = NULL; }
memcpy(pItem->SessionID, rgbSessionId, sizeof(rgbSessionId));
//
// Give the caller a reference.
//
SPCacheReference(pItem); *ppRetItem = pItem;
//
// Add the new entry to the cache.
//
RtlAcquireResourceExclusive(&SchannelCache.Lock, TRUE);
InsertTailList(&SchannelCache.SessionCache[index], &pItem->IndexEntryList); InsertTailList(&SchannelCache.EntryList, &pItem->EntryList); SchannelCache.dwUsedEntries++;
RtlReleaseResource(&SchannelCache.Lock);
DebugLog((DEB_TRACE, "Create new cache entry at: 0x%x\n", pItem)); return TRUE; }
BOOL SPCacheAdd( PSPContext pContext) { PSessCacheItem pItem; PSPCredentialGroup pCred; DWORD dwLifespan; DWORD timeNow;
timeNow = GetTickCount();
pItem = pContext->RipeZombie; if(!pItem) return FALSE;
ASSERT(pItem->Magic == SP_CACHE_MAGIC);
pCred = pContext->pCredGroup; if(!pCred) return FALSE;
if(pItem->fProtocol & SP_PROT_CLIENTS) { dwLifespan = min(pCred->dwSessionLifespan, SchannelCache.dwClientLifespan); } else { dwLifespan = min(pCred->dwSessionLifespan, SchannelCache.dwServerLifespan); }
// Remember which client certificate we used.
if(pItem->fProtocol & SP_PROT_CLIENTS) { pItem->CredThumbprint = pContext->pCredGroup->CredThumbprint;
if(pContext->pActiveClientCred) { DebugLog((DEB_TRACE, "Assign client certificate to cache entry: 0x%x\n", pItem)); pItem->CertThumbprint = pContext->pActiveClientCred->CertThumbprint; pItem->pClientCert = CertDuplicateCertificateContext(pContext->pActiveClientCred->pCert); if(pItem->pClientCert == NULL) { SP_LOG_RESULT(GetLastError()); } } else { DebugLog((DEB_TRACE, "Client cache entry 0x%x is anonymous.\n", pItem)); } }
// Are we supposed to defer reconnects for this connection?
if(pItem->pServerCred != NULL) { if(pItem->pServerCred->dwFlags & CRED_FLAG_DISABLE_RECONNECTS) { pItem->DeferredJuju = TRUE; } }
// Allow cache ownership of this item
pItem->dwFlags |= SP_CACHE_FLAG_READONLY; if(!pItem->DeferredJuju) { pItem->ZombieJuju = TRUE; }
// if we are a cloned item, abort the old
// item, and then dereference it.
if(pItem->pClonedItem) { pItem->pClonedItem->ZombieJuju = FALSE; SPCacheDereference(pItem->pClonedItem); pItem->pClonedItem = NULL; }
pItem->Lifespan = dwLifespan;
return TRUE; }
void SPCacheAssignNewServerCredential( PSessCacheItem pItem, PSPCredentialGroup pCred) { SPReferenceCredential(pCred);
if(pItem->pServerCred) { SPDereferenceCredential(pItem->pServerCred, FALSE); }
pItem->pServerCred = pCred; }
/* Allocate a new cache item, and copy
* over relevant information from old item, * and dereference old item. This is a helper * for REDO */ BOOL SPCacheClone(PSessCacheItem *ppItem) { PSessCacheItem pNewItem; PSessCacheItem pOldItem;
if(ppItem == NULL || *ppItem == NULL) { return FALSE; } pOldItem = *ppItem;
ASSERT(pOldItem->Magic == SP_CACHE_MAGIC); ASSERT(!(pOldItem->fProtocol & SP_PROT_CLIENTS) || !(pOldItem->fProtocol & SP_PROT_SERVERS));
// Get a fresh cache item.
pNewItem = NULL; if(!SPCacheRetrieveNew((pOldItem->fProtocol & SP_PROT_CLIENTS) == 0, pOldItem->szCacheID, &pNewItem)) { return FALSE; } // Copy the master CSP prov handle.
pNewItem->hMasterProv = pOldItem->hMasterProv;
// Copy over old relevant data
pNewItem->fProtocol = pOldItem->fProtocol; pNewItem->dwCF = pOldItem->dwCF; pNewItem->phMapper = pOldItem->phMapper;
if(pOldItem->pServerCred) { SPReferenceCredential(pOldItem->pServerCred); pNewItem->pServerCred = pOldItem->pServerCred; } pNewItem->pActiveServerCred = pOldItem->pActiveServerCred;
if(pOldItem->dwFlags & SP_CACHE_FLAG_MASTER_EPHEM) { pNewItem->dwFlags |= SP_CACHE_FLAG_MASTER_EPHEM; }
pNewItem->CredThumbprint = pOldItem->CredThumbprint,
// This item will be dereferenced, and
// Aborted when the new item is completed.
pNewItem->pClonedItem = pOldItem;
*ppItem = pNewItem;
return TRUE; }
NTSTATUS SetCacheAppData( PSessCacheItem pItem, PBYTE pbAppData, DWORD cbAppData) { RtlAcquireResourceExclusive(&SchannelCache.Lock, TRUE);
if(pItem->pbAppData) { SPExternalFree(pItem->pbAppData); }
pItem->pbAppData = pbAppData; pItem->cbAppData = cbAppData;
RtlReleaseResource(&SchannelCache.Lock);
return STATUS_SUCCESS; }
NTSTATUS GetCacheAppData( PSessCacheItem pItem, PBYTE *ppbAppData, DWORD *pcbAppData) { if(pItem->pbAppData == NULL) { *ppbAppData = NULL; *pcbAppData = 0; return STATUS_SUCCESS; }
RtlAcquireResourceShared(&SchannelCache.Lock, TRUE);
*pcbAppData = pItem->cbAppData; *ppbAppData = SPExternalAlloc(pItem->cbAppData); if(*ppbAppData == NULL) { RtlReleaseResource(&SchannelCache.Lock); return SP_LOG_RESULT(SEC_E_INSUFFICIENT_MEMORY); }
memcpy(*ppbAppData, pItem->pbAppData, pItem->cbAppData);
RtlReleaseResource(&SchannelCache.Lock);
return STATUS_SUCCESS; }
BOOL IsEntryToBeProcessed( PSessCacheItem pItem, PLUID LogonID, ULONG ProcessID, LPWSTR pszTargetName, DWORD dwFlags) { //
// Validate client entries.
//
if(pItem->fProtocol & SP_PROT_CLIENTS) { if((dwFlags & SSL_PURGE_CLIENT_ENTRIES) == 0 && (dwFlags & SSL_PURGE_CLIENT_ALL_ENTRIES) == 0) { return FALSE; }
if((dwFlags & SSL_PURGE_CLIENT_ALL_ENTRIES) == 0) { if(!RtlEqualLuid(&pItem->LogonId, LogonID)) { return FALSE; } }
if(pszTargetName != NULL) { if(pItem->szCacheID == NULL || wcscmp(pItem->szCacheID, pszTargetName) != 0) { return FALSE; } }
return TRUE; }
//
// Validate server entries.
//
if(pItem->fProtocol & SP_PROT_SERVERS) { if((dwFlags & SSL_PURGE_SERVER_ENTRIES) == 0 && (dwFlags & SSL_PURGE_SERVER_ALL_ENTRIES) == 0) { return FALSE; }
if(ProcessID != pItem->ProcessID) { if((dwFlags & SSL_PURGE_SERVER_ALL_ENTRIES) == 0) { return FALSE; } } }
return TRUE; }
NTSTATUS SPCachePurgeEntries( LUID *LogonID, ULONG ProcessID, LPWSTR pszTargetName, DWORD dwFlags) { PSessCacheItem pItem; PLIST_ENTRY pList; LIST_ENTRY DeleteList;
//
// Initialize the list of deleted entries.
//
InitializeListHead(&DeleteList);
//
// Enumerate through the cache entries.
//
RtlAcquireResourceExclusive(&SchannelCache.Lock, TRUE);
pList = SchannelCache.EntryList.Flink;
while(pList != &SchannelCache.EntryList) { pItem = CONTAINING_RECORD(pList, SessCacheItem, EntryList.Flink); pList = pList->Flink;
ASSERT(pItem->Magic == SP_CACHE_MAGIC);
if(!IsEntryToBeProcessed(pItem, LogonID, ProcessID, pszTargetName, dwFlags)) { continue; }
if(pItem->cRef > 1) { // This entry is currently being used, so don't delete.
// Mark it as non-resumable, though.
pItem->ZombieJuju = FALSE; pItem->DeferredJuju = FALSE; continue; }
//
// Remove this entry from the cache, and add it to the list of
// entries to be destroyed.
//
RemoveEntryList(&pItem->IndexEntryList); RemoveEntryList(&pItem->EntryList); SchannelCache.dwUsedEntries--;
InsertTailList(&DeleteList, &pItem->EntryList); }
RtlReleaseResource(&SchannelCache.Lock);
//
// Kill the purged zombies.
//
pList = DeleteList.Flink;
while(pList != &DeleteList) { pItem = CONTAINING_RECORD(pList, SessCacheItem, EntryList.Flink); pList = pList->Flink;
ASSERT(pItem->Magic == SP_CACHE_MAGIC);
SPCacheDelete(pItem); }
//
// Purge the issuer cache.
//
if(dwFlags & SSL_PURGE_SERVER_ENTRIES) { SPPurgeIssuerCache(); }
return STATUS_SUCCESS; }
NTSTATUS SPCacheGetInfo( LUID * LogonID, LPWSTR pszTargetName, DWORD dwFlags, PSSL_SESSION_CACHE_INFO_RESPONSE pCacheInfo) { PSessCacheItem pItem; PLIST_ENTRY pList; DWORD timeNow; ULONG ProcessID;
UNREFERENCED_PARAMETER(LogonID); UNREFERENCED_PARAMETER(pszTargetName);
pCacheInfo->CacheSize = SchannelCache.dwMaximumEntries; pCacheInfo->Entries = 0; pCacheInfo->ActiveEntries = 0; pCacheInfo->Zombies = 0; pCacheInfo->ExpiredZombies = 0; pCacheInfo->AbortedZombies = 0; pCacheInfo->DeletedZombies = 0;
timeNow = GetTickCount();
SslGetClientProcess(&ProcessID);
RtlAcquireResourceExclusive(&SchannelCache.Lock, TRUE);
pList = SchannelCache.EntryList.Flink;
while(pList != &SchannelCache.EntryList) { pItem = CONTAINING_RECORD(pList, SessCacheItem, EntryList.Flink); pList = pList->Flink;
ASSERT(pItem->Magic == SP_CACHE_MAGIC);
if(pItem->fProtocol & SP_PROT_CLIENTS) { if((dwFlags & SSL_RETRIEVE_CLIENT_ENTRIES) == 0) { continue; } } else { if((dwFlags & SSL_RETRIEVE_SERVER_ENTRIES) == 0) { continue; } }
pCacheInfo->Entries++;
if(pItem->cRef == 1) { pCacheInfo->Zombies++;
if(HasTimeElapsed(pItem->CreationTime, timeNow, pItem->Lifespan)) { pCacheInfo->ExpiredZombies++; } if(pItem->ZombieJuju == FALSE) { pCacheInfo->AbortedZombies++; } } else { pCacheInfo->ActiveEntries++; } }
RtlReleaseResource(&SchannelCache.Lock);
return STATUS_SUCCESS; }
NTSTATUS SPCacheGetPerfmonInfo( DWORD dwFlags, PSSL_PERFMON_INFO_RESPONSE pPerfmonInfo) { PSessCacheItem pItem; PLIST_ENTRY pList;
UNREFERENCED_PARAMETER(dwFlags);
//
// Compute performance numbers.
//
pPerfmonInfo->ClientHandshakesPerSecond = g_cClientHandshakes; pPerfmonInfo->ServerHandshakesPerSecond = g_cServerHandshakes; pPerfmonInfo->ClientReconnectsPerSecond = g_cClientReconnects; pPerfmonInfo->ServerReconnectsPerSecond = g_cServerReconnects;
//
// Compute cache info.
//
pPerfmonInfo->ClientCacheEntries = 0; pPerfmonInfo->ServerCacheEntries = 0; pPerfmonInfo->ClientActiveEntries = 0; pPerfmonInfo->ServerActiveEntries = 0;
RtlAcquireResourceShared(&SchannelCache.Lock, TRUE);
pList = SchannelCache.EntryList.Flink;
while(pList != &SchannelCache.EntryList) { pItem = CONTAINING_RECORD(pList, SessCacheItem, EntryList.Flink); pList = pList->Flink;
ASSERT(pItem->Magic == SP_CACHE_MAGIC);
if(pItem->fProtocol & SP_PROT_CLIENTS) { pPerfmonInfo->ClientCacheEntries++;
if(pItem->cRef > 1) { pPerfmonInfo->ClientActiveEntries++; } } else { pPerfmonInfo->ServerCacheEntries++;
if(pItem->cRef > 1) { pPerfmonInfo->ServerActiveEntries++; } } }
RtlReleaseResource(&SchannelCache.Lock);
return STATUS_SUCCESS; }
|