#include "ldapc.hxx" #pragma hdrstop ADS_LDP BindCache ; DWORD BindCacheCount = 0 ; CRITICAL_SECTION BindCacheCritSect ; LUID ReservedLuid = { 0, 0 } ; // // Wait for 1000ms * 60 * minutes // #define RETIRE_HANDLE (1000 * 60 * 5) // // Return the LUID for current credentials. Note we only use LUID. In theory // it may be better to use SourceName as well as identifier but the former // doesnt seem to always return the same string. // DWORD BindCacheGetLuid(LUID *Luid, LUID *ModifiedId) { HANDLE TokenHandle = NULL ; TOKEN_STATISTICS TokenInformation ; DWORD ReturnLength ; // // Try thread first. If fail, try process. // if (!OpenThreadToken( GetCurrentThread(), TOKEN_QUERY, TRUE, &TokenHandle)) { if (!OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY, &TokenHandle)) { return(GetLastError()) ; } } // // Get the TokenSource info. // if (!GetTokenInformation( TokenHandle, TokenStatistics, &TokenInformation, sizeof(TokenInformation), &ReturnLength)) { CloseHandle(TokenHandle) ; return(GetLastError()) ; } *Luid = TokenInformation.AuthenticationId ; *ModifiedId = TokenInformation.ModifiedId; CloseHandle(TokenHandle) ; return(NO_ERROR) ; } // // Initializes a cache entry // DWORD BindCacheAllocEntry(ADS_LDP **ppCacheEntry) { ADS_LDP *pCacheEntry ; *ppCacheEntry = NULL ; if (!(pCacheEntry = (PADS_LDP)AllocADsMem(sizeof(ADS_LDP)))) { return(GetLastError()) ; } pCacheEntry->RefCount = 0; pCacheEntry->Flags = 0; pCacheEntry->List.Flink = NULL ; pCacheEntry->List.Blink = NULL ; pCacheEntry->ReferralEntries = NULL ; pCacheEntry->nReferralEntries = 0 ; pCacheEntry->fKeepAround = FALSE; *ppCacheEntry = pCacheEntry ; return NO_ERROR ; } // // Invalidates a cache entry so it will not be used. // VOID BindCacheInvalidateEntry(ADS_LDP *pCacheEntry) { pCacheEntry->Flags |= LDP_CACHE_INVALID ; } // // !!!WARNING!!! Make sure you hold the bindcache critsect // when calling this function. // VOID CommonRemoveEntry(ADS_LDP *pCacheEntry, LIST_ENTRY *DeleteReferralList) { for (DWORD i=0; i < pCacheEntry->nReferralEntries; i++) { if (BindCacheDerefHelper( pCacheEntry->ReferralEntries[i], DeleteReferralList) == 0 ) { InsertTailList( DeleteReferralList, &(pCacheEntry->ReferralEntries[i])->ReferralList ); } } // // Cleanup the entry // // --BindCacheCount ; (void) FreeADsMem(pCacheEntry->Server) ; pCacheEntry->Server = NULL ; delete pCacheEntry->pCredentials; pCacheEntry->pCredentials = NULL; if (pCacheEntry->ReferralEntries) { FreeADsMem(pCacheEntry->ReferralEntries); } RemoveEntryList(&pCacheEntry->List) ; return; } #if 0 VOID BindCacheCleanTimedOutEntries() { DWORD dwCurrTick = 0; DWORD dwLastTick = 0; BOOL fRemoved = FALSE; ADS_LDP *ldRemove = NULL; ENTER_BIND_CRITSECT(); PADS_LDP pEntry = (PADS_LDP) BindCache.List.Flink ; // // Loop thru looking for match. A match is defined as: // servername & LUID matches, and it is NOT invalid. // while (pEntry != &BindCache) { // // See if this one is keepAround entry that is old // if (pEntry->RefCount == 0) { ADsAssert(pEntry->fKeepAround); // // GetCurrent tick and see if we need to del the entry // dwCurrTick = GetTickCount(); dwLastTick = pEntry->dwLastUsed; if ((dwLastTick == 0) || ((dwLastTick <= dwCurrTick) && ((dwCurrTick - dwLastTick) > RETIRE_HANDLE)) || ((dwLastTick > dwCurrTick) && ((dwCurrTick + (((DWORD)(-1)) - dwLastTick)) > RETIRE_HANDLE))) { // // Entry needs to be removed. // CommonRemoveEntry(pEntry); fRemoved = TRUE; } } // refCount == 0 if (fRemoved) { LdapUnbind(pEntry); ldRemove = pEntry; } pEntry = (PADS_LDP)pEntry->List.Flink ; if (ldRemove) { FreeADsMem(ldRemove); ldRemove = NULL; } } LEAVE_BIND_CRITSECT(); return; } #endif // // Lookup an entry in the cache. Does not take into account timeouts. // Increments ref count if found. // PADS_LDP BindCacheLookup( LPWSTR Address, LUID Luid, LUID ModifiedId, CCredentials& Credentials, DWORD dwPort ) { DWORD i ; ENTER_BIND_CRITSECT() ; PADS_LDP pEntry = (PADS_LDP) BindCache.List.Flink ; // // Loop thru looking for match. A match is defined as: // servername & LUID matches, and it is NOT invalid. // while (pEntry != &BindCache) { if ((pEntry->Server != NULL) && !(pEntry->Flags & LDP_CACHE_INVALID) && (wcscmp(pEntry->Server, Address) == 0) && pEntry->PortNumber == dwPort && (memcmp(&Luid,&(pEntry->Luid),sizeof(Luid))==0) && ((memcmp(&ModifiedId, &(pEntry->ModifiedId), sizeof(Luid)) == 0) || (memcmp(&ModifiedId, &ReservedLuid, sizeof(Luid)) == 0))) { if ((*(pEntry->pCredentials) == Credentials) || CanCredentialsBeReused(pEntry->pCredentials, &Credentials)) { ++pEntry->RefCount ; LEAVE_BIND_CRITSECT() ; return(pEntry) ; } } pEntry = (PADS_LDP)pEntry->List.Flink ; } LEAVE_BIND_CRITSECT() ; // // Before we leave clean out stale entries // //BindCacheCleanTimedOutEntries(); return NULL ; } // // Check if credentials can be reused. They can be reused if username and // non-ignored auth flags match and incoming credentials thesame password. // Ignored auth flags are those defined by // BIND_CACHE_IGNORED_FLAGS as not to be used in considering whether to // reuse a connection. // BOOL CanCredentialsBeReused( CCredentials *pCachedCreds, CCredentials *pIncomingCreds ) { PWSTR pszIncomingUser = NULL; PWSTR pszIncomingPwd = NULL; PWSTR pszCachedUser = NULL; PWSTR pszCachedPwd = NULL; HRESULT hr; BOOL rc = FALSE; // // Test that the non-ignored auth flags match. // We need to be smart and reuse the credentials based on the flags. // if ( (~BIND_CACHE_IGNORED_FLAGS & pCachedCreds->GetAuthFlags()) != (~BIND_CACHE_IGNORED_FLAGS & pIncomingCreds->GetAuthFlags())) { return FALSE; } // // Get the user names // hr = pCachedCreds->GetUserName(&pszCachedUser); BAIL_ON_FAILURE(hr); hr = pIncomingCreds->GetUserName(&pszIncomingUser); BAIL_ON_FAILURE(hr); // // Get the password // hr = pIncomingCreds->GetPassword(&pszIncomingPwd); BAIL_ON_FAILURE(hr); hr = pCachedCreds->GetPassword(&pszCachedPwd); BAIL_ON_FAILURE(hr); // // Only when both username and password match, will we reuse the connection handle // if (((pszCachedUser && pszIncomingUser && wcscmp(pszCachedUser, pszIncomingUser) == 0) || (!pszCachedUser && !pszIncomingUser)) && ((pszCachedPwd && pszIncomingPwd && wcscmp(pszCachedPwd, pszIncomingPwd) == 0) || (!pszCachedPwd && !pszIncomingPwd))) { rc = TRUE; } error: if (pszCachedUser) { FreeADsStr(pszCachedUser); } if (pszIncomingUser) { FreeADsStr(pszIncomingUser); } if (pszCachedPwd) { FreeADsStr(pszCachedPwd); } if (pszIncomingPwd) { FreeADsStr(pszIncomingPwd); } return rc; } // // Lookup an entry in the cache based on the Ldap Handle // DOES NOT Increment ref count // PADS_LDP GetCacheEntry(PLDAP pLdap) { ENTER_BIND_CRITSECT() ; PADS_LDP pEntry = (PADS_LDP) BindCache.List.Flink ; // // Loop thru looking for match. A match is defined as: // servername & LUID matches, and it is NOT invalid. // while (pEntry != &BindCache) { if (pEntry->LdapHandle == pLdap) { LEAVE_BIND_CRITSECT() ; return(pEntry) ; } pEntry = (PADS_LDP)pEntry->List.Flink ; } LEAVE_BIND_CRITSECT() ; return NULL ; } // // Add entry to cache // DWORD BindCacheAdd( LPWSTR Address, LUID Luid, LUID ModifiedId, CCredentials& Credentials, DWORD dwPort, PADS_LDP pCacheEntry) { ENTER_BIND_CRITSECT() ; if (BindCacheCount > MAX_BIND_CACHE_SIZE) { // // If exceed limit, just dont put in cache. Since we leave the // RefCount & the Links unset, the deref will simply note that // this entry is not in cache and allow it to be freed. // // We limit cache so that if someone leaks handles we dont over // time end up traversing this huge linked list. // LEAVE_BIND_CRITSECT() ; return(NO_ERROR) ; } LPWSTR pServer = (LPWSTR) AllocADsMem( (wcslen(Address)+1)*sizeof(WCHAR)) ; if (!pServer) { LEAVE_BIND_CRITSECT() ; return(GetLastError()) ; } CCredentials * pCredentials = new CCredentials(Credentials); if (!pCredentials) { FreeADsMem(pServer); LEAVE_BIND_CRITSECT(); return(GetLastError()); } // // setup the data // wcscpy(pServer,Address) ; pCacheEntry->pCredentials = pCredentials; pCacheEntry->PortNumber = dwPort; pCacheEntry->Server = pServer ; pCacheEntry->RefCount = 1 ; pCacheEntry->Luid = Luid ; pCacheEntry->ModifiedId = ModifiedId; // // insert into list // InsertHeadList(&BindCache.List, &pCacheEntry->List) ; ++BindCacheCount ; LEAVE_BIND_CRITSECT() ; return NO_ERROR ; } // // Bump up the reference count of the particular cache entry // Returns the final ref count or zero if not there. // BOOL BindCacheAddRef(ADS_LDP *pCacheEntry) { DWORD dwCount = 0; ENTER_BIND_CRITSECT() ; if ((pCacheEntry->List.Flink == NULL) && (pCacheEntry->List.Blink == NULL) && (pCacheEntry->RefCount == NULL)) { // // this is one of them entries that has no LUID. // ie. it never got into the cache. // LEAVE_BIND_CRITSECT() ; return(0) ; } ADsAssert(pCacheEntry->List.Flink) ; ADsAssert(pCacheEntry->RefCount > 0) ; pCacheEntry->RefCount++ ; // // Save info onto stack variable as we are going // to leave the critsect before we exit. // dwCount = pCacheEntry->RefCount; LEAVE_BIND_CRITSECT() ; return(dwCount) ; } // // Adds a referral entry of pNewEntry to pPrimaryEntry. Increments the reference // count of pPrimaryEntry if succesful. // BOOL AddReferralLink( PADS_LDP pPrimaryEntry, PADS_LDP pNewEntry ) { ENTER_BIND_CRITSECT() ; if (!pPrimaryEntry) { goto error; } if (!pPrimaryEntry->ReferralEntries) { pPrimaryEntry->ReferralEntries = (PADS_LDP *) AllocADsMem( sizeof(PADS_LDP) * MAX_REFERRAL_ENTRIES); if (!pPrimaryEntry->ReferralEntries) { goto error; } pPrimaryEntry->nReferralEntries = 0; } if (pPrimaryEntry->nReferralEntries >= MAX_REFERRAL_ENTRIES) { // // We won't remember more than this // goto error; } pPrimaryEntry->ReferralEntries[pPrimaryEntry->nReferralEntries] = pNewEntry; if (!BindCacheAddRef(pNewEntry)) { goto error; } pPrimaryEntry->nReferralEntries++; LEAVE_BIND_CRITSECT() ; return TRUE; error: LEAVE_BIND_CRITSECT(); return FALSE; } DWORD BindCacheDeref(ADS_LDP *pCacheEntry) { DWORD dwCount = 0; LIST_ENTRY DeleteReferralList; PLIST_ENTRY pEntry; PADS_LDP EntryInfo; InitializeListHead(&DeleteReferralList); dwCount = BindCacheDerefHelper(pCacheEntry, &DeleteReferralList); // Delete the cached entries // while (!IsListEmpty (&DeleteReferralList)) { pEntry = RemoveHeadList (&DeleteReferralList); EntryInfo = CONTAINING_RECORD (pEntry, ADS_LDP, ReferralList); LdapUnbind(EntryInfo); FreeADsMem(EntryInfo); } return dwCount; } // // Dereference an entry in the cache. Removes if ref count is zero. // Returns the final ref count or zero if not there. If zero, caller // should close the handle. // DWORD BindCacheDerefHelper(ADS_LDP *pCacheEntry, LIST_ENTRY * DeleteReferralList) { DWORD dwCount=0; ENTER_BIND_CRITSECT() ; if ((pCacheEntry->List.Flink == NULL) && (pCacheEntry->List.Blink == NULL) && (pCacheEntry->RefCount == NULL)) { // // this is one of them entries that has no LUID. // ie. it never got into the cache. // LEAVE_BIND_CRITSECT() ; return(0) ; } ADsAssert(pCacheEntry->List.Flink) ; ADsAssert(pCacheEntry->RefCount > 0) ; // // Dereference by one. If result is non zero, just return. // --pCacheEntry->RefCount ; if (pCacheEntry->RefCount) { // // Use a stack variable for this value as // we call return outside the critsect. // dwCount = pCacheEntry->RefCount; LEAVE_BIND_CRITSECT() ; return(dwCount); } // // Before clearing the entry away verify that // we do not need to KeepAround this one. // if (pCacheEntry->fKeepAround) { // // Set the timer on this entry and leave. // pCacheEntry->dwLastUsed = GetTickCount(); LEAVE_BIND_CRITSECT() ; } else { // // Now that this entry is going away, deref all the referred entries. // CommonRemoveEntry(pCacheEntry, DeleteReferralList); LEAVE_BIND_CRITSECT() ; } // // Look for any other entries that need to be cleaned out // //BindCacheCleanTimedOutEntries(); return 0 ; } VOID BindCacheInit( VOID ) { InitializeCriticalSection(&BindCacheCritSect) ; InitializeListHead(&BindCache.List) ; } VOID BindCacheCleanup( VOID ) { PADS_LDP pEntry = (PADS_LDP) BindCache.List.Flink ; while (pEntry != &BindCache) { PADS_LDP pNext = (PADS_LDP) pEntry->List.Flink; (void) FreeADsMem(pEntry->Server) ; pEntry->Server = NULL ; if (pEntry->ReferralEntries) { FreeADsMem(pEntry->ReferralEntries); } RemoveEntryList(&pEntry->List) ; pEntry = pNext; } // // Delte the critical section initialized in BindCacheInit // DeleteCriticalSection(&BindCacheCritSect) ; } // // Mark handle so that we keep it even after the object count // has hit zero and remove only after a x mins of zero count. // HRESULT LdapcKeepHandleAround(ADS_LDP *ld) { ADsAssert(ld); ld->fKeepAround = TRUE; return S_OK; }