//+--------------------------------------------------------------------------- // // 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 #include #include 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; }