//
//  Author: DebiM
//  Date:   September 1996
//
//  File:   csacc.cxx
//
//      Class Store Manager implementation for a client desktop.
//
//      This source file contains implementations for IClassAccess
//      interface for CClassAccess object.
//      It also contains the IEnumPackage implementation for the 
//      aggregate of all class containers seen by the caller.
//
//
//---------------------------------------------------------------------

#include "cstore.hxx"

IClassAccess *GetNextValidClassStore(CLASSCONTAINER **pStoreList, 
                                     DWORD     cStores, 
                                     PSID      pUserSid,
                                     uCLSSPEC* pClassSpec,
                                     BOOL      fCache,
                                     DWORD*    pcount);


extern HRESULT GetUserClassStores(
                           PCLASSCONTAINER     **ppStoreList,
                           DWORD                *pcStores,
                           BOOL                 *pfCache,
                           PSID                 *ppUserSid);



//
// Link list pointer for Class Containers Seen
//
extern CLASSCONTAINER *gpContainerHead;

//
// Link list pointer for User Profiles Seen
//
extern USERPROFILE *gpUserHead;

//
// Global Class Factory for Class Container
//
extern CAppContainerCF *pCF;

//
// Critical Section used during operations on list of class stores
//
extern CRITICAL_SECTION    ClassStoreBindList;

//
// CClassAccess implementation
//

CClassAccess::CClassAccess()

{
     m_uRefs = 1;
     m_cCalls = 0;
     pStoreList = NULL;
     cStores = 0;
}

CClassAccess::~CClassAccess()

{
    DWORD i;
    for (i = 0; i < cStores; i++) 
    {
        if (pStoreList[i]->gpClassStore)
        {
            (pStoreList[i]->gpClassStore)->Release();
            pStoreList[i]->gpClassStore = NULL;
            CSDBGPrint((L"Found open container and closed."));
        }

        if (pStoreList[i]->pszClassStorePath)
        {
            CoTaskMemFree (pStoreList[i]->pszClassStorePath);
            pStoreList[i]->pszClassStorePath = NULL;
        }
        CoTaskMemFree(pStoreList[i]);
        pStoreList[i] = NULL;

    }
    CoTaskMemFree(pStoreList);
    cStores = NULL;
}

//----------------------------------------------------------------------
//
// 

void PrintClassSpec(
      uCLSSPEC       *   pclsspec         // Class Spec (GUID/Ext/MIME)
     )
{
    STRINGGUID szClsid;

    if (pclsspec->tyspec == TYSPEC_CLSID)
    {
        StringFromGUID (pclsspec->tagged_union.clsid, szClsid);
        CSDBGPrint((L" ... GetAppInfo by CLSID = %s", szClsid));
    }

    if (pclsspec->tyspec == TYSPEC_PROGID)
    {
        CSDBGPrint((L" ... GetAppInfo by ProgID = %s",
            pclsspec->tagged_union.pProgId));
    }

    if (pclsspec->tyspec == TYSPEC_MIMETYPE)
    {
        CSDBGPrint((L" ... GetAppInfo by MimeType = %s",
            pclsspec->tagged_union.pMimeType));
    }

    if (pclsspec->tyspec == TYSPEC_FILEEXT)
    {
        CSDBGPrint((L" ... GetAppInfo by FileExt = %s",
            pclsspec->tagged_union.pFileExt));
    }

    if (pclsspec->tyspec == TYSPEC_IID)
    {
        StringFromGUID (pclsspec->tagged_union.iid, szClsid);
        CSDBGPrint((L" ... GetAppInfo by IID = %s", szClsid));
    }
}

//----------------------------------------------------------------------


HRESULT STDMETHODCALLTYPE
CClassAccess::GetAppInfo(
         uCLSSPEC           *   pclsspec,            // Class Spec (GUID/Ext/MIME)
         QUERYCONTEXT       *   pQryContext,         // Query Attributes
         PACKAGEDISPINFO    *   pPackageInfo
        )

        //
        // This is the most common method to access the Class Store.
        // It queries the class store for implementations for a specific
        // Class Id, or File Ext, or ProgID or MIME type.
        //
        // If a matching implementation is available for the object type,
        // client architecture, locale and class context pointer to the
        // binary is returned.
{

    //
    // Assume that this method is called in the security context
    // of the user process. Hence there is no need to impersonate.
    //
    //
    // Get the list of Class Stores for this user
    //
    HRESULT             hr = S_OK;
    ULONG               i = 0, j = 0, k= 0;
    IClassAccess    *   pICA = NULL;
    BOOL                fCache = FALSE;
    PSID                pUserSid = NULL;
    BOOL                fFound = FALSE;
    QUERYCONTEXT        QueryContext;

        
    // added later.
    if (gDebug)
    {
        WCHAR   Name[32];
        DWORD   NameSize = 32;
   
        if ( ! GetUserName( Name, &NameSize ) )
            CSDBGPrint((L"GetAppInfo GetUserName failed 0x%x", GetLastError()));
        else
            CSDBGPrint((L"GetAppInfo as %s", Name));
    }

    if ((!pPackageInfo) ||
        (!IsValidReadPtrIn(pPackageInfo, sizeof(PACKAGEDISPINFO))))
            return E_INVALIDARG;

    memset(pPackageInfo, 0, sizeof(PACKAGEDISPINFO));

    if ( pQryContext )
    {
        QueryContext = *pQryContext;
    }
    else
    {
        // gets the default information.
        QueryContext.dwContext = CLSCTX_ALL;
        GetDefaultPlatform( &QueryContext.Platform );
        QueryContext.Locale = MAKELCID(GetUserDefaultLangID(), SUBLANG_NEUTRAL);
        QueryContext.dwVersionHi = (DWORD) -1;
        QueryContext.dwVersionLo = (DWORD) -1;
    }    

    if (gDebug)
        PrintClassSpec(pclsspec);

    if (!pStoreList)
        hr = GetUserClassStores(
                    &pStoreList,
                    &cStores,
                    &fCache,
                    &pUserSid);


    ERROR_ON_FAILURE(hr);

    for (i=0; i < cStores; i++)
    {

        if (!(pICA = GetNextValidClassStore(pStoreList, cStores, pUserSid, pclsspec, fCache, &i)))
            continue;

        //
        // Call method on this store
        //

        pICA->AddRef();

        hr = pICA->GetAppInfo(
            pclsspec,
            &QueryContext,
            pPackageInfo);

        // Release it after use.

        pICA->Release();

        //
        // Special case error return E_INVALIDARG
        // Do not continue to look, return this.
        //
        if (hr == E_INVALIDARG)
        {
            ERROR_ON_FAILURE(hr);
        }

        //
        // maintain access counters
        //
        (pStoreList[i])->cAccess++;

        //
        // We are iterating through the class stores from highest precedence to lowest --
        // thus, the first container to return success will be our choice.
        //
        if (SUCCEEDED(hr))
        {
            fFound = TRUE;
            break;
        }
        else
        {
            (pStoreList[i])->cNotFound++;
            CSDBGPrint((L"CClassAccess::GetAppInfo() returned 0x%x", hr));
        }
        hr = S_OK;
    }

 Error_Cleanup:
 
     if (pUserSid)
        CoTaskMemFree (pUserSid);
    if (fFound)
    {
        CSDBGPrint((L"Returning Package %s", pPackageInfo->pszPackageName));
        return S_OK;
    }
    if (!SUCCEEDED(hr))
        return hr;
    return CS_E_PACKAGE_NOTFOUND;
}

#define MAX_GUID_CCH 38
//
// IsClassStoreForPolicy
//

BOOL IsClassStoreForPolicy(CLASSCONTAINER* pClassStore,
                           LPWSTR          wszPolicyId)
{
    LPWSTR pszPolicyGuid;

    // Path looks like:
    // LDAP://CN=<Class Store Name>,CN=<user-or-machine>,CN=<{policy-guid}>,...

    //
    // Look for ',' first
    //
    pszPolicyGuid = wcschr(pClassStore->pszClassStorePath, L',');

    if (!pszPolicyGuid)
    {
        return FALSE;
    }

    //
    // Look for the second ','
    //
    pszPolicyGuid = wcschr(pszPolicyGuid + 1, L',');

    if (!pszPolicyGuid)
    {
        return FALSE;
    }

    //
    // Now get to '{' at start of guid -- it is 4 chars
    // past the ',' which we are currently at.  Use wcschr
    // to make sure we don't go past the end of the string
    // and that our assumptions about the structure of the
    // path are correct
    //
    if (wcschr(pszPolicyGuid, L'{') == (pszPolicyGuid + 4))
    {

        pszPolicyGuid += 4;

        //
        // Now that we have the '{', we are at the start of the guid
        // and can compare with the requested policy id
        //
        if (_wcsnicmp(pszPolicyGuid, wszPolicyId, MAX_GUID_CCH) == 0)
        {
            return TRUE;
        }
    }

    return FALSE;
}

//
// GetNextValidClassStore
//
//

IClassAccess *GetNextValidClassStore(CLASSCONTAINER** pStoreList, 
                                     DWORD            cStores, 
                                     PSID             pUserSid,
                                     uCLSSPEC*        pClassSpec,
                                     BOOL             fCache,
                                     DWORD *          pcount)
{
    IClassAccess *pretICA = NULL;
    BOOL          bSpecificPolicy;
    LPWSTR        wszPolicyGuid;
    HRESULT       hr;

    wszPolicyGuid = NULL;

    hr = S_OK;

    bSpecificPolicy = pClassSpec ? TYSPEC_PACKAGENAME == pClassSpec->tyspec : FALSE;

    if (bSpecificPolicy)
    {
        hr = StringFromCLSID(pClassSpec->tagged_union.ByName.PolicyId, &wszPolicyGuid);
    }

    if (SUCCEEDED(hr))
    {
        for (pStoreList += (*pcount); (*pcount) < cStores; (*pcount)++, pStoreList++)
        {
            if ((*pStoreList)->gpClassStore != NULL)
            {
                break;
            }

            if (bSpecificPolicy &&
                !IsClassStoreForPolicy(*pStoreList, wszPolicyGuid))
            {
                continue;
            }

            if (FALSE) // ((*pStoreList)->cBindFailures >= MAX_BIND_ATTEMPTS)
            {
                // Number of continuous failures have reached MAX_BIND_ATTEMPTS
                // for this container.
                // Will temporarily disable lookups in this container.
                // Report it in EventLog once
                //

                if ((*pStoreList)->cBindFailures == MAX_BIND_ATTEMPTS)
                {
                    //LogCsPathError((*pStoreList)->pszClassStorePath, hr);
                    (*pStoreList)->cBindFailures++;
                }
            continue;
            }
            else
            { 
                CSDBGPrint((L"CS: .. Connecting to Store %d ... %s..",
                            (*pcount),
                            (*pStoreList)->pszClassStorePath));
                //
                // Bind to this Class Store
                //

                if ((wcsncmp ((*pStoreList)->pszClassStorePath, L"ADCS:", 5) == 0) ||
                    (wcsncmp ((*pStoreList)->pszClassStorePath, L"LDAP:", 5) == 0))
                {
                    //
                    // If the Storename starts with ADCS or LDAP
                    // it is NTDS based implementation. Call directly.
                    //
    
                    IClassAccess *pCA = NULL;
                    LPOLESTR szClassStore = (*pStoreList)->pszClassStorePath;

                    // skipping ADCS:
                    if (wcsncmp ((*pStoreList)->pszClassStorePath, L"ADCS:", 5) == 0)
                        szClassStore += 5;

                    hr = pCF->CreateConnectedInstance(
                        szClassStore,
                        pUserSid,
                        fCache,
                        (void **)&pCA);

                    if (SUCCEEDED(hr))
                    {
                        EnterCriticalSection (&ClassStoreBindList);
                        
                        if ((*pStoreList)->gpClassStore != NULL)
                        {
                            pCA->Release();
                            pCA = NULL;
                        }
                        else
                        {
                            (*pStoreList)->gpClassStore = pCA;
                            pCA = NULL;
                        }
                        
                        LeaveCriticalSection (&ClassStoreBindList);
                    }
                }
                else
                {
                    //
                    // Support for Third Party Pluggable
                    // Class Stores is not in Beta2.
                    //
                    
                    ReportEventCS(hr = CS_E_INVALID_PATH, CS_E_INVALID_PATH, (*pStoreList)->pszClassStorePath);
                }

                if (SUCCEEDED(hr))
                {
                    (*pStoreList)->cBindFailures = 0;
                    hr = S_OK;
                    break;
                }

                if (!SUCCEEDED(hr))
                {
                    CSDBGPrint((L"Failed to connect to this store"));
                    
                    if ((*pStoreList)->cBindFailures == 0)
                    {
                        // First failue or First failure after successful
                        // binding.
                        // Report it in EventLog
                        //

                        //LogCsPathError((*pStoreList)->pszClassStorePath, hr);
                    }
                    
                    ((*pStoreList)->cBindFailures) ++;
                    continue;
                }
            }
        }
    }

    if (wszPolicyGuid)
    {
        CoTaskMemFree(wszPolicyGuid);
    }

    if ((*pcount) != cStores)
        pretICA = (*pStoreList)->gpClassStore;


    return pretICA;
}




HRESULT STDMETHODCALLTYPE CClassAccess::EnumPackages(
        LPOLESTR        pszPackageName, 
        GUID            *pCategory,
        ULONGLONG       *pLastUsn,
        DWORD           dwAppFlags,      // AppType options
        IEnumPackage    **ppIEnumPackage)
{
    //
    // Get the list of Class Stores for this user
    //
    HRESULT             hr = S_OK;
    ULONG               i;
    IEnumPackage       *Enum[MAXCLASSSTORES];
    ULONG               cEnum = 0;
    CMergedEnumPackage *EnumMerged = NULL;
    IClassAccess       *pICA = NULL;
    ULONGLONG          LastUsn, CopyLastUsn, *pCopyLastUsn;
    BOOL               fCache = FALSE;
    PSID               pUserSid = NULL;

    // added later.
    if (gDebug)
    {
        WCHAR   Name[32];
        DWORD   NameSize = 32;
   
        if ( ! GetUserName( Name, &NameSize ) )
            CSDBGPrint((L"EnumPackage GetUserName failed 0x%x", GetLastError()));
        else
            CSDBGPrint((L"EnumPackage as %s", Name));
    }

    LastUsn = 0;

    if (pLastUsn)
    {
        //
        // Check pLastUsn
        //
        if (!IsValidReadPtrIn(pLastUsn, sizeof(ULONGLONG)))
            return E_INVALIDARG;

        pCopyLastUsn = &CopyLastUsn;
        *pCopyLastUsn = *pLastUsn;
    }
    else
        pCopyLastUsn = NULL;
    
    //
    // Get the list of Class Stores for this user
    //
    if (!pStoreList)
        hr = GetUserClassStores(
                        &pStoreList,
                        &cStores,
                        &fCache,
                        &pUserSid);
    

    *ppIEnumPackage = NULL;


    if ((hr == S_OK) && (cStores == 0))
    {
        hr = CS_E_NO_CLASSSTORE;
    }

    if (!SUCCEEDED(hr))
    {
        //
        // Free the Sid
        //
        if (pUserSid)
            CoTaskMemFree (pUserSid);
        return hr;
    }

    for (i=0; i < cStores; i++)
    {
        if (!(pICA = GetNextValidClassStore(pStoreList, cStores, pUserSid, NULL, fCache, &i)))
            continue;
        //
        // Call method on this store
        //

        hr = pICA->EnumPackages (pszPackageName, 
            pCategory,
            pCopyLastUsn,
            dwAppFlags,
            &(Enum[cEnum]));

        if (hr == E_INVALIDARG)
        {
            break;
        }

        if (pCopyLastUsn)
        {
            if (LastUsn < *pCopyLastUsn)
                LastUsn = *pCopyLastUsn;
            *pCopyLastUsn = *pLastUsn;
        }
        if (SUCCEEDED(hr))
            cEnum++;
    }
    
    if (SUCCEEDED(hr))
    {
        
        EnumMerged = new CMergedEnumPackage;
        hr = EnumMerged->Initialize(Enum, cEnum);
        
        if (FAILED(hr))
        {
            for (i = 0; i < cEnum; i++)
                Enum[i]->Release();
            delete EnumMerged;
        }
        else
        {
            hr = EnumMerged->QueryInterface(IID_IEnumPackage, (void **)ppIEnumPackage);
            if (FAILED(hr))
                delete EnumMerged;
        }
        
        if (pLastUsn)
        {
            if (LastUsn > *pLastUsn)
                *pLastUsn = LastUsn;
        }
    }

    if (pUserSid)
        CoTaskMemFree (pUserSid);
    return hr;
}


//--------------------------------------------------------------

CMergedEnumPackage::CMergedEnumPackage()
{
    m_pcsEnum = NULL;
    m_cEnum = 0;
    m_csnum = 0;
    m_dwRefCount = 0;
}

CMergedEnumPackage::~CMergedEnumPackage()
{
    ULONG    i;
    for (i = 0; i < m_cEnum; i++)
        m_pcsEnum[i]->Release();
    CoTaskMemFree(m_pcsEnum);
}

HRESULT  __stdcall  CMergedEnumPackage::QueryInterface(REFIID riid,
                                            void  * * ppObject)
{
    *ppObject = NULL; //gd
    if ((riid==IID_IUnknown) || (riid==IID_IEnumPackage))
    {
        *ppObject=(IEnumPackage *) this;
    }
    else
    {
        return E_NOINTERFACE;
    }
    AddRef();
    return S_OK;
}

ULONG  __stdcall  CMergedEnumPackage::AddRef()
{
    InterlockedIncrement((long*) &m_dwRefCount);
    return m_dwRefCount;
}



ULONG  __stdcall  CMergedEnumPackage::Release()
{
    ULONG dwRefCount;
    if ((dwRefCount = InterlockedDecrement((long*) &m_dwRefCount))==0)
    {
        delete this;
        return 0;
    }
    return dwRefCount;
}


HRESULT  __stdcall CMergedEnumPackage::Next(
            ULONG             celt,
            PACKAGEDISPINFO   *rgelt,
            ULONG             *pceltFetched)
{
    ULONG count=0, total = 0;
    HRESULT hr;

    for (; m_csnum < m_cEnum; m_csnum++)
    {
        count = 0;
        hr = m_pcsEnum[m_csnum]->Next(celt, rgelt+total, &count);

        if (hr == E_INVALIDARG)
        {
            return hr;
        }

        total += count;
        celt -= count;

        if (!celt)
            break;
    }
    if (pceltFetched)
        *pceltFetched = total;
    if (!celt)
        return S_OK;
    return S_FALSE;
}

HRESULT  __stdcall CMergedEnumPackage::Skip(
            ULONG             celt)
{
    PACKAGEDISPINFO *pPackageInfo = NULL;
    HRESULT          hr = S_OK;
    ULONG            cgot = 0, i;

    pPackageInfo = (PACKAGEDISPINFO *)CoTaskMemAlloc(sizeof(PACKAGEDISPINFO)*celt);
    if (!pPackageInfo)
        return E_OUTOFMEMORY;

    hr = Next(celt, pPackageInfo, &cgot);

    for (i = 0; i < cgot; i++)
        ReleasePackageInfo(pPackageInfo+i);
    CoTaskMemFree(pPackageInfo);
    
    return hr;
}

HRESULT  __stdcall CMergedEnumPackage::Reset()
{
    ULONG i;
    for (i = 0; ((i <= m_csnum) && (i < m_cEnum)); i++)
        m_pcsEnum[i]->Reset(); // ignoring all error values
    m_csnum = 0;
    return S_OK;
}

HRESULT  __stdcall CMergedEnumPackage::Clone(IEnumPackage   **ppIEnumPackage)
{
    ULONG i;
    CMergedEnumPackage *pClone;
    IEnumPackage **pcsEnumCloned=NULL;

    pClone = new CMergedEnumPackage;
    pcsEnumCloned = (IEnumPackage **)CoTaskMemAlloc(sizeof(IEnumPackage *)*m_cEnum);
    if (!pcsEnumCloned)
        return E_OUTOFMEMORY;

    for ( i = 0; i < m_cEnum; i++)
        m_pcsEnum[i]->Clone(&(pcsEnumCloned[i]));

    pClone->m_csnum = m_csnum;
    pClone->Initialize(pcsEnumCloned, m_cEnum);
    *ppIEnumPackage = (IEnumPackage *)pClone;
    pClone->AddRef();
    CoTaskMemFree(pcsEnumCloned);
    return S_OK;
}

HRESULT  CMergedEnumPackage::Initialize(IEnumPackage **pcsEnum, ULONG cEnum)
{
    ULONG i;
    m_csnum = 0;
    m_pcsEnum = (IEnumPackage **)CoTaskMemAlloc(sizeof(IEnumPackage *) * cEnum);
    if (!m_pcsEnum)
        return E_OUTOFMEMORY;
    for (i = 0; i < cEnum; i++)
        m_pcsEnum[i] = pcsEnum[i];
    m_cEnum = cEnum;
    return S_OK;
}