#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;
}