1808 lines
41 KiB
C
1808 lines
41 KiB
C
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// 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>
|
||
|
|
||
|
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;
|
||
|
DWORD g_CacheCleanupCount = 0;
|
||
|
HANDLE g_CacheCleanupEvent = NULL;
|
||
|
HANDLE g_CacheCleanupWaitObject = NULL;
|
||
|
BOOL g_fMultipleProcessClientCache = FALSE;
|
||
|
BOOL g_fCacheInitialized = FALSE;
|
||
|
|
||
|
// Perf counter values.
|
||
|
DWORD g_cClientHandshakes = 0;
|
||
|
DWORD g_cServerHandshakes = 0;
|
||
|
DWORD g_cClientReconnects = 0;
|
||
|
DWORD g_cServerReconnects = 0;
|
||
|
|
||
|
|
||
|
BOOL
|
||
|
SPCacheDelete(
|
||
|
PSessCacheItem pItem);
|
||
|
|
||
|
BOOL
|
||
|
CacheExpireElements(
|
||
|
BOOL fCleanupOnly,
|
||
|
BOOL fBackground);
|
||
|
|
||
|
VOID
|
||
|
CacheCleanupHandler(
|
||
|
PVOID pVoid,
|
||
|
BOOLEAN fTimeout);
|
||
|
|
||
|
|
||
|
SP_STATUS
|
||
|
SPInitSessionCache(VOID)
|
||
|
{
|
||
|
DWORD i;
|
||
|
NTSTATUS Status = STATUS_SUCCESS;
|
||
|
|
||
|
SP_BEGIN("SPInitSessionCache");
|
||
|
|
||
|
//
|
||
|
// Allocate memory for cache, and initialize synchronization resource.
|
||
|
//
|
||
|
|
||
|
InitializeListHead(&SchannelCache.EntryList);
|
||
|
RtlInitializeResource(&SchannelCache.Lock);
|
||
|
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 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();
|
||
|
}
|
||
|
|
||
|
SP_RETURN(Status);
|
||
|
}
|
||
|
|
||
|
|
||
|
SP_STATUS
|
||
|
SPShutdownSessionCache(VOID)
|
||
|
{
|
||
|
PSessCacheItem pItem;
|
||
|
PLIST_ENTRY pList;
|
||
|
DWORD i;
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
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)
|
||
|
{
|
||
|
long cRet;
|
||
|
|
||
|
DebugLog((DEB_TRACE, "Delete cache item:0x%x\n", pItem));
|
||
|
|
||
|
if(pItem == NULL)
|
||
|
{
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
ASSERT(pItem->Magic == SP_CACHE_MAGIC);
|
||
|
|
||
|
pItem->pActiveServerCred = NULL;
|
||
|
pItem->pServerCred = NULL;
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
if(pItem->szCacheID)
|
||
|
{
|
||
|
SPExternalFree(pItem->szCacheID);
|
||
|
pItem->szCacheID = NULL;
|
||
|
}
|
||
|
|
||
|
if(pItem->pClientCred)
|
||
|
{
|
||
|
SPDeleteCred(pItem->pClientCred);
|
||
|
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;
|
||
|
DWORD i;
|
||
|
|
||
|
|
||
|
//
|
||
|
// Only server credentials are bound to the cache, so return if this is
|
||
|
// a client credential.
|
||
|
//
|
||
|
|
||
|
if(pCred->grbitProtocol & SP_PROT_CLIENTS)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Search through the cache entries looking for entries that are
|
||
|
// bound to the specified server credential.
|
||
|
//
|
||
|
|
||
|
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);
|
||
|
|
||
|
// Is this a server cache entry?
|
||
|
if((pItem->fProtocol & SP_PROT_SERVERS) == 0)
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// 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.
|
||
|
//
|
||
|
|
||
|
CacheExpireElements(FALSE, FALSE);
|
||
|
}
|
||
|
|
||
|
|
||
|
void
|
||
|
SPCachePurgeProcessId(
|
||
|
ULONG ProcessId)
|
||
|
{
|
||
|
PSessCacheItem pItem;
|
||
|
PLIST_ENTRY pList;
|
||
|
DWORD i;
|
||
|
|
||
|
|
||
|
//
|
||
|
// 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);
|
||
|
|
||
|
|
||
|
//
|
||
|
// Delete all unused non-resumable cache entries.
|
||
|
//
|
||
|
|
||
|
CacheExpireElements(FALSE, FALSE);
|
||
|
}
|
||
|
|
||
|
|
||
|
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;
|
||
|
PLIST_ENTRY pList;
|
||
|
BOOL fFound = FALSE;
|
||
|
|
||
|
DebugLog((DEB_TRACE, "SPCacheRetrieveBySession (%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;
|
||
|
}
|
||
|
|
||
|
|
||
|
// 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;
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// 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(%u)\n", index));
|
||
|
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 = wcslen(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;
|
||
|
PSPCredential pCurrentCred = NULL;
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
// Don't allow reconnects when Skipjack is used.
|
||
|
if(pItem->aiCipher == CALG_SKIPJACK)
|
||
|
{
|
||
|
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->pCredList != NULL)
|
||
|
{
|
||
|
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(%u)\n", index));
|
||
|
InterlockedIncrement(&g_cClientReconnects);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
DebugLog((DEB_TRACE, " FAILED\n"));
|
||
|
}
|
||
|
|
||
|
return (pFoundEntry != NULL);
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL
|
||
|
IsApplicationCertificateMapper(
|
||
|
PHMAPPER phMapper)
|
||
|
{
|
||
|
if(phMapper == NULL)
|
||
|
{
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
if(phMapper->m_dwFlags & SCH_FLAG_SYSTEM_MAPPER)
|
||
|
{
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// 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.
|
||
|
//
|
||
|
// The tricky bit is how to handle the case where the cache
|
||
|
// entry belongs to IIS, and has an IIS certificate mapper
|
||
|
// "locator" attached to it. In this case, we cannot destroy
|
||
|
// the cache element unless the client process is IIS, because
|
||
|
// we need to callback to IIS in order to destroy the locator.
|
||
|
// In this case, we remove the element from the cache, and
|
||
|
// leave it laying around in a global "cache cleanup" list.
|
||
|
// If this list gets too large, then this routine should be
|
||
|
// called frequently, with the "fCleanupOnly" parameter set
|
||
|
// to TRUE.
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
BOOL
|
||
|
CacheExpireElements(
|
||
|
BOOL fCleanupOnly,
|
||
|
BOOL fBackground)
|
||
|
{
|
||
|
static ULONG RefCount = 0;
|
||
|
ULONG LocalRefCount;
|
||
|
DWORD timeNow;
|
||
|
ULONG ProcessID;
|
||
|
PSessCacheItem pItem;
|
||
|
PLIST_ENTRY pList;
|
||
|
DWORD CleanupCount;
|
||
|
ULONG Count;
|
||
|
|
||
|
//
|
||
|
// 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 and application process id.
|
||
|
//
|
||
|
|
||
|
timeNow = GetTickCount();
|
||
|
|
||
|
SslGetClientProcess(&ProcessID);
|
||
|
|
||
|
|
||
|
//
|
||
|
// Search through the cache entries looking for expired entries.
|
||
|
//
|
||
|
|
||
|
if(!fCleanupOnly)
|
||
|
{
|
||
|
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)
|
||
|
{
|
||
|
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.
|
||
|
//
|
||
|
|
||
|
CleanupCount = 0;
|
||
|
pList = g_CacheCleanupList.Flink;
|
||
|
|
||
|
while(pList != &g_CacheCleanupList)
|
||
|
{
|
||
|
pItem = CONTAINING_RECORD(pList, SessCacheItem, EntryList.Flink);
|
||
|
pList = pList->Flink;
|
||
|
|
||
|
ASSERT(pItem->Magic == SP_CACHE_MAGIC);
|
||
|
|
||
|
// Make sure that we only destroy server entries that belong to the
|
||
|
// current application process. This is necessary because of the
|
||
|
// IIS certificate mapper.
|
||
|
if(pItem->ProcessID != 0 &&
|
||
|
pItem->ProcessID != ProcessID)
|
||
|
{
|
||
|
if(IsApplicationCertificateMapper(pItem->phMapper))
|
||
|
{
|
||
|
CleanupCount++;
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Remove entry from cleanup list.
|
||
|
RemoveEntryList(&pItem->EntryList);
|
||
|
|
||
|
// Destroy cache entry.
|
||
|
SPCacheDelete(pItem);
|
||
|
}
|
||
|
|
||
|
g_CacheCleanupCount = CleanupCount;
|
||
|
|
||
|
RtlLeaveCriticalSection(&g_CacheCleanupLock);
|
||
|
|
||
|
InterlockedDecrement(&RefCount);
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
VOID
|
||
|
CacheCleanupHandler(
|
||
|
PVOID pVoid,
|
||
|
BOOLEAN fTimeout)
|
||
|
{
|
||
|
if(SchannelCache.dwUsedEntries > 0)
|
||
|
{
|
||
|
if(fTimeout)
|
||
|
{
|
||
|
DebugLog((DEB_WARN, "Initiate periodic cache cleanup.\n"));
|
||
|
}
|
||
|
|
||
|
CacheExpireElements(FALSE, TRUE);
|
||
|
|
||
|
ResetEvent(g_CacheCleanupEvent);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* 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;
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Perform cache garbage collection when the list of entries to be
|
||
|
// deleted grows too large.
|
||
|
//
|
||
|
|
||
|
if(fServer && g_CacheCleanupCount > 50)
|
||
|
{
|
||
|
DebugLog((DEB_WARN, "Attempt background cleanup of deleted zombies.\n"));
|
||
|
|
||
|
CacheExpireElements(TRUE, TRUE);
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Retrieve the current time and user logon id.
|
||
|
//
|
||
|
|
||
|
timeNow = GetTickCount();
|
||
|
|
||
|
SslGetClientProcess(&ProcessID);
|
||
|
SslGetClientLogonId(&LogonId);
|
||
|
|
||
|
|
||
|
//
|
||
|
// Compute the session id and the cache index.
|
||
|
//
|
||
|
|
||
|
if(fServer)
|
||
|
{
|
||
|
GenerateRandomBits(rgbSessionId, sizeof(rgbSessionId));
|
||
|
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;
|
||
|
|
||
|
#ifdef LOCK_MASTER_KEYS
|
||
|
pItem->csMasterKey = g_rgcsMasterKey + (index % SP_MASTER_KEY_CS_COUNT);
|
||
|
#endif
|
||
|
|
||
|
if(pszTargetName)
|
||
|
{
|
||
|
pItem->szCacheID = SPExternalAlloc((wcslen(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);
|
||
|
|
||
|
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)
|
||
|
{
|
||
|
pItem->CertThumbprint = pContext->pActiveClientCred->CertThumbprint;
|
||
|
pItem->pClientCert = CertDuplicateCertificateContext(pContext->pActiveClientCred->pCert);
|
||
|
if(pItem->pClientCert == NULL)
|
||
|
{
|
||
|
SP_LOG_RESULT(GetLastError());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 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;
|
||
|
}
|
||
|
|
||
|
/* 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;
|
||
|
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;
|
||
|
|
||
|
DebugLog((DEB_TRACE, "Purge cache entries\n"));
|
||
|
|
||
|
//
|
||
|
// 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;
|
||
|
}
|
||
|
|
||
|
if(pItem->ProcessID != 0 &&
|
||
|
pItem->ProcessID != ProcessID)
|
||
|
{
|
||
|
if(IsApplicationCertificateMapper(pItem->phMapper))
|
||
|
{
|
||
|
// This entry has a mapper structure that doesn't belong
|
||
|
// to the calling process, 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);
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
|
||
|
pCacheInfo->CacheSize = SchannelCache.dwMaximumEntries;
|
||
|
pCacheInfo->Entries = 0;
|
||
|
pCacheInfo->ActiveEntries = 0;
|
||
|
pCacheInfo->Zombies = 0;
|
||
|
pCacheInfo->ExpiredZombies = 0;
|
||
|
pCacheInfo->AbortedZombies = 0;
|
||
|
pCacheInfo->DeletedZombies = g_CacheCleanupCount;
|
||
|
|
||
|
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;
|
||
|
|
||
|
//
|
||
|
// 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;
|
||
|
}
|
||
|
|