/*
 * Copyright 2004-2007 Juan Lang
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */
#include <stdarg.h>
#include "windef.h"
#include "winbase.h"
#include "wincrypt.h"
#include "wine/debug.h"
#include "wine/list.h"
#include "crypt32_private.h"

WINE_DEFAULT_DEBUG_CHANNEL(crypt);

typedef struct _WINE_STORE_LIST_ENTRY
{
    PWINECRYPT_CERTSTORE store;
    DWORD                dwUpdateFlags;
    DWORD                dwPriority;
    struct list          entry;
} WINE_STORE_LIST_ENTRY, *PWINE_STORE_LIST_ENTRY;

typedef struct _WINE_COLLECTIONSTORE
{
    WINECRYPT_CERTSTORE hdr;
    CRITICAL_SECTION    cs;
    struct list         stores;
} WINE_COLLECTIONSTORE, *PWINE_COLLECTIONSTORE;

static void WINAPI CRYPT_CollectionCloseStore(HCERTSTORE store, DWORD dwFlags)
{
    PWINE_COLLECTIONSTORE cs = store;
    PWINE_STORE_LIST_ENTRY entry, next;

    TRACE("(%p, %08x)\n", store, dwFlags);

    LIST_FOR_EACH_ENTRY_SAFE(entry, next, &cs->stores, WINE_STORE_LIST_ENTRY,
     entry)
    {
        TRACE("closing %p\n", entry);
        CertCloseStore(entry->store, dwFlags);
        CryptMemFree(entry);
    }
    cs->cs.DebugInfo->Spare[0] = 0;
    DeleteCriticalSection(&cs->cs);
    CRYPT_FreeStore(store);
}

static void *CRYPT_CollectionCreateContextFromChild(PWINE_COLLECTIONSTORE store,
 PWINE_STORE_LIST_ENTRY storeEntry, void *child, size_t contextSize,
 BOOL addRef)
{
    void *ret = Context_CreateLinkContext(contextSize, child,
     sizeof(PWINE_STORE_LIST_ENTRY), addRef);

    if (ret)
        *(PWINE_STORE_LIST_ENTRY *)Context_GetExtra(ret, contextSize)
         = storeEntry;

    return ret;
}

static BOOL CRYPT_CollectionAddContext(PWINE_COLLECTIONSTORE store,
 unsigned int contextFuncsOffset, void *context, void *toReplace, unsigned int contextSize,
 void **pChildContext)
{
    BOOL ret;
    void *childContext = NULL;
    PWINE_STORE_LIST_ENTRY storeEntry = NULL;

    TRACE("(%p, %d, %p, %p, %d)\n", store, contextFuncsOffset, context,
     toReplace, contextSize);

    ret = FALSE;
    if (toReplace)
    {
        void *existingLinked = Context_GetLinkedContext(toReplace, contextSize);
        PCONTEXT_FUNCS contextFuncs;

        storeEntry = *(PWINE_STORE_LIST_ENTRY *)Context_GetExtra(toReplace,
         contextSize);
        contextFuncs = (PCONTEXT_FUNCS)((LPBYTE)storeEntry->store +
         contextFuncsOffset);
        ret = contextFuncs->addContext(storeEntry->store, context,
         existingLinked, (const void **)&childContext);
    }
    else
    {
        PWINE_STORE_LIST_ENTRY entry, next;

        EnterCriticalSection(&store->cs);
        LIST_FOR_EACH_ENTRY_SAFE(entry, next, &store->stores,
         WINE_STORE_LIST_ENTRY, entry)
        {
            if (entry->dwUpdateFlags & CERT_PHYSICAL_STORE_ADD_ENABLE_FLAG)
            {
                PCONTEXT_FUNCS contextFuncs = (PCONTEXT_FUNCS)(
                 (LPBYTE)entry->store + contextFuncsOffset);

                storeEntry = entry;
                ret = contextFuncs->addContext(entry->store, context, NULL,
                 (const void **)&childContext);
                break;
            }
        }
        LeaveCriticalSection(&store->cs);
        if (!storeEntry)
            SetLastError(E_ACCESSDENIED);
    }
    *pChildContext = childContext;
    return ret;
}

/* Advances a collection enumeration by one context, if possible, where
 * advancing means:
 * - calling the current store's enumeration function once, and returning
 *   the enumerated context if one is returned
 * - moving to the next store if the current store has no more items, and
 *   recursively calling itself to get the next item.
 * Returns NULL if the collection contains no more items or on error.
 * Assumes the collection store's lock is held.
 */
static void *CRYPT_CollectionAdvanceEnum(PWINE_COLLECTIONSTORE store,
 PWINE_STORE_LIST_ENTRY storeEntry, PCONTEXT_FUNCS contextFuncs,
 PCWINE_CONTEXT_INTERFACE contextInterface, void *pPrev, size_t contextSize)
{
    void *ret, *child;
    struct list *storeNext = list_next(&store->stores, &storeEntry->entry);

    TRACE("(%p, %p, %p)\n", store, storeEntry, pPrev);

    if (pPrev)
    {
        /* Ref-counting funny business: "duplicate" (addref) the child, because
         * the free(pPrev) below can cause the ref count to become negative.
         */
        child = Context_GetLinkedContext(pPrev, contextSize);
        contextInterface->duplicate(child);
        child = contextFuncs->enumContext(storeEntry->store, child);
        contextInterface->free(pPrev);
        pPrev = NULL;
    }
    else
        child = contextFuncs->enumContext(storeEntry->store, NULL);
    if (child)
        ret = CRYPT_CollectionCreateContextFromChild(store, storeEntry, child,
         contextSize, FALSE);
    else
    {
        if (storeNext)
        {
            /* We always want the same function pointers (from certs, crls)
             * in the next store, so use the same offset into the next store.
             */
            size_t offset = (LPBYTE)contextFuncs - (LPBYTE)storeEntry->store;
            PWINE_STORE_LIST_ENTRY storeNextEntry =
             LIST_ENTRY(storeNext, WINE_STORE_LIST_ENTRY, entry);
            PCONTEXT_FUNCS storeNextContexts =
             (PCONTEXT_FUNCS)((LPBYTE)storeNextEntry->store + offset);

            ret = CRYPT_CollectionAdvanceEnum(store, storeNextEntry,
             storeNextContexts, contextInterface, NULL, contextSize);
        }
        else
        {
            SetLastError(CRYPT_E_NOT_FOUND);
            ret = NULL;
        }
    }
    TRACE("returning %p\n", ret);
    return ret;
}

static BOOL CRYPT_CollectionAddCert(PWINECRYPT_CERTSTORE store, void *cert,
 void *toReplace, const void **ppStoreContext)
{
    BOOL ret;
    void *childContext = NULL;
    PWINE_COLLECTIONSTORE cs = (PWINE_COLLECTIONSTORE)store;

    ret = CRYPT_CollectionAddContext(cs, offsetof(WINECRYPT_CERTSTORE, certs),
     cert, toReplace, sizeof(CERT_CONTEXT), &childContext);
    if (ppStoreContext && childContext)
    {
        PWINE_STORE_LIST_ENTRY storeEntry = *(PWINE_STORE_LIST_ENTRY *)
         Context_GetExtra(childContext, sizeof(CERT_CONTEXT));
        PCERT_CONTEXT context =
         CRYPT_CollectionCreateContextFromChild(cs, storeEntry, childContext,
         sizeof(CERT_CONTEXT), TRUE);

        if (context)
            context->hCertStore = store;
        *ppStoreContext = context;
    }
    CertFreeCertificateContext(childContext);
    return ret;
}

static void *CRYPT_CollectionEnumCert(PWINECRYPT_CERTSTORE store, void *pPrev)
{
    PWINE_COLLECTIONSTORE cs = (PWINE_COLLECTIONSTORE)store;
    void *ret;

    TRACE("(%p, %p)\n", store, pPrev);

    EnterCriticalSection(&cs->cs);
    if (pPrev)
    {
        PWINE_STORE_LIST_ENTRY storeEntry =
         *(PWINE_STORE_LIST_ENTRY *)Context_GetExtra(pPrev,
         sizeof(CERT_CONTEXT));

        ret = CRYPT_CollectionAdvanceEnum(cs, storeEntry,
         &storeEntry->store->certs, pCertInterface, pPrev,
         sizeof(CERT_CONTEXT));
    }
    else
    {
        if (!list_empty(&cs->stores))
        {
            PWINE_STORE_LIST_ENTRY storeEntry = LIST_ENTRY(cs->stores.next,
             WINE_STORE_LIST_ENTRY, entry);

            ret = CRYPT_CollectionAdvanceEnum(cs, storeEntry,
             &storeEntry->store->certs, pCertInterface, NULL,
             sizeof(CERT_CONTEXT));
        }
        else
        {
            SetLastError(CRYPT_E_NOT_FOUND);
            ret = NULL;
        }
    }
    LeaveCriticalSection(&cs->cs);
    if (ret)
        ((PCERT_CONTEXT)ret)->hCertStore = store;
    TRACE("returning %p\n", ret);
    return ret;
}

static BOOL CRYPT_CollectionDeleteCert(PWINECRYPT_CERTSTORE store,
 void *pCertContext)
{
    BOOL ret;
    PCCERT_CONTEXT linked;

    TRACE("(%p, %p)\n", store, pCertContext);

    /* Deleting the linked context results in its ref count getting
     * decreased, but the caller of this (CertDeleteCertificateFromStore) also
     * decreases pCertContext's ref count, by calling
     * CertFreeCertificateContext.  Increase ref count of linked context to
     * compensate.
     */
    linked = Context_GetLinkedContext(pCertContext, sizeof(CERT_CONTEXT));
    CertDuplicateCertificateContext(linked);
    ret = CertDeleteCertificateFromStore(linked);
    return ret;
}

static BOOL CRYPT_CollectionAddCRL(PWINECRYPT_CERTSTORE store, void *crl,
 void *toReplace, const void **ppStoreContext)
{
    BOOL ret;
    void *childContext = NULL;
    PWINE_COLLECTIONSTORE cs = (PWINE_COLLECTIONSTORE)store;

    ret = CRYPT_CollectionAddContext(cs, offsetof(WINECRYPT_CERTSTORE, crls),
     crl, toReplace, sizeof(CRL_CONTEXT), &childContext);
    if (ppStoreContext && childContext)
    {
        PWINE_STORE_LIST_ENTRY storeEntry = *(PWINE_STORE_LIST_ENTRY *)
         Context_GetExtra(childContext, sizeof(CRL_CONTEXT));
        PCRL_CONTEXT context =
         CRYPT_CollectionCreateContextFromChild(cs, storeEntry, childContext,
         sizeof(CRL_CONTEXT), TRUE);

        if (context)
            context->hCertStore = store;
        *ppStoreContext = context;
    }
    CertFreeCRLContext(childContext);
    return ret;
}

static void *CRYPT_CollectionEnumCRL(PWINECRYPT_CERTSTORE store, void *pPrev)
{
    PWINE_COLLECTIONSTORE cs = (PWINE_COLLECTIONSTORE)store;
    void *ret;

    TRACE("(%p, %p)\n", store, pPrev);

    EnterCriticalSection(&cs->cs);
    if (pPrev)
    {
        PWINE_STORE_LIST_ENTRY storeEntry =
         *(PWINE_STORE_LIST_ENTRY *)Context_GetExtra(pPrev,
         sizeof(CRL_CONTEXT));

        ret = CRYPT_CollectionAdvanceEnum(cs, storeEntry,
         &storeEntry->store->crls, pCRLInterface, pPrev, sizeof(CRL_CONTEXT));
    }
    else
    {
        if (!list_empty(&cs->stores))
        {
            PWINE_STORE_LIST_ENTRY storeEntry = LIST_ENTRY(cs->stores.next,
             WINE_STORE_LIST_ENTRY, entry);

            ret = CRYPT_CollectionAdvanceEnum(cs, storeEntry,
             &storeEntry->store->crls, pCRLInterface, NULL,
             sizeof(CRL_CONTEXT));
        }
        else
        {
            SetLastError(CRYPT_E_NOT_FOUND);
            ret = NULL;
        }
    }
    LeaveCriticalSection(&cs->cs);
    if (ret)
        ((PCRL_CONTEXT)ret)->hCertStore = store;
    TRACE("returning %p\n", ret);
    return ret;
}

static BOOL CRYPT_CollectionDeleteCRL(PWINECRYPT_CERTSTORE store,
 void *pCrlContext)
{
    BOOL ret;
    PCCRL_CONTEXT linked;

    TRACE("(%p, %p)\n", store, pCrlContext);

    /* Deleting the linked context results in its ref count getting
     * decreased, but the caller of this (CertDeleteCRLFromStore) also
     * decreases pCrlContext's ref count, by calling CertFreeCRLContext.
     * Increase ref count of linked context to compensate.
     */
    linked = Context_GetLinkedContext(pCrlContext, sizeof(CRL_CONTEXT));
    CertDuplicateCRLContext(linked);
    ret = CertDeleteCRLFromStore(linked);
    return ret;
}

static BOOL CRYPT_CollectionAddCTL(PWINECRYPT_CERTSTORE store, void *ctl,
 void *toReplace, const void **ppStoreContext)
{
    BOOL ret;
    void *childContext = NULL;
    PWINE_COLLECTIONSTORE cs = (PWINE_COLLECTIONSTORE)store;

    ret = CRYPT_CollectionAddContext(cs, offsetof(WINECRYPT_CERTSTORE, ctls),
     ctl, toReplace, sizeof(CTL_CONTEXT), &childContext);
    if (ppStoreContext && childContext)
    {
        PWINE_STORE_LIST_ENTRY storeEntry = *(PWINE_STORE_LIST_ENTRY *)
         Context_GetExtra(childContext, sizeof(CTL_CONTEXT));
        PCTL_CONTEXT context =
         CRYPT_CollectionCreateContextFromChild(cs, storeEntry, childContext,
         sizeof(CTL_CONTEXT), TRUE);

        if (context)
            context->hCertStore = store;
        *ppStoreContext = context;
    }
    CertFreeCTLContext(childContext);
    return ret;
}

static void *CRYPT_CollectionEnumCTL(PWINECRYPT_CERTSTORE store, void *pPrev)
{
    PWINE_COLLECTIONSTORE cs = (PWINE_COLLECTIONSTORE)store;
    void *ret;

    TRACE("(%p, %p)\n", store, pPrev);

    EnterCriticalSection(&cs->cs);
    if (pPrev)
    {
        PWINE_STORE_LIST_ENTRY storeEntry =
         *(PWINE_STORE_LIST_ENTRY *)Context_GetExtra(pPrev,
         sizeof(CTL_CONTEXT));

        ret = CRYPT_CollectionAdvanceEnum(cs, storeEntry,
         &storeEntry->store->ctls, pCTLInterface, pPrev, sizeof(CTL_CONTEXT));
    }
    else
    {
        if (!list_empty(&cs->stores))
        {
            PWINE_STORE_LIST_ENTRY storeEntry = LIST_ENTRY(cs->stores.next,
             WINE_STORE_LIST_ENTRY, entry);

            ret = CRYPT_CollectionAdvanceEnum(cs, storeEntry,
             &storeEntry->store->ctls, pCTLInterface, NULL,
             sizeof(CTL_CONTEXT));
        }
        else
        {
            SetLastError(CRYPT_E_NOT_FOUND);
            ret = NULL;
        }
    }
    LeaveCriticalSection(&cs->cs);
    if (ret)
        ((PCTL_CONTEXT)ret)->hCertStore = store;
    TRACE("returning %p\n", ret);
    return ret;
}

static BOOL CRYPT_CollectionDeleteCTL(PWINECRYPT_CERTSTORE store,
 void *pCtlContext)
{
    BOOL ret;
    PCCTL_CONTEXT linked;

    TRACE("(%p, %p)\n", store, pCtlContext);

    /* Deleting the linked context results in its ref count getting
     * decreased, but the caller of this (CertDeleteCTLFromStore) also
     * decreases pCtlContext's ref count, by calling CertFreeCTLContext.
     * Increase ref count of linked context to compensate.
     */
    linked = Context_GetLinkedContext(pCtlContext, sizeof(CTL_CONTEXT));
    CertDuplicateCTLContext(linked);
    ret = CertDeleteCTLFromStore(linked);
    return ret;
}

static BOOL WINAPI CRYPT_CollectionControl(HCERTSTORE hCertStore, DWORD dwFlags,
 DWORD dwCtrlType, void const *pvCtrlPara)
{
    BOOL ret;
    PWINE_COLLECTIONSTORE store = hCertStore;
    PWINE_STORE_LIST_ENTRY entry;

    TRACE("(%p, %08x, %d, %p)\n", hCertStore, dwFlags, dwCtrlType,
     pvCtrlPara);

    if (!store)
        return TRUE;
    if (store->hdr.dwMagic != WINE_CRYPTCERTSTORE_MAGIC)
    {
        SetLastError(E_INVALIDARG);
        return FALSE;
    }
    if (store->hdr.type != StoreTypeCollection)
    {
        SetLastError(E_INVALIDARG);
        return FALSE;
    }

    ret = TRUE;
    EnterCriticalSection(&store->cs);
    LIST_FOR_EACH_ENTRY(entry, &store->stores, WINE_STORE_LIST_ENTRY, entry)
    {
        if (entry->store->control)
        {
            ret = entry->store->control(entry->store, dwFlags, dwCtrlType,
             pvCtrlPara);
            if (!ret)
                break;
        }
    }
    LeaveCriticalSection(&store->cs);
    return ret;
}

PWINECRYPT_CERTSTORE CRYPT_CollectionOpenStore(HCRYPTPROV hCryptProv,
 DWORD dwFlags, const void *pvPara)
{
    PWINE_COLLECTIONSTORE store;

    if (dwFlags & CERT_STORE_DELETE_FLAG)
    {
        SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
        store = NULL;
    }
    else
    {
        store = CryptMemAlloc(sizeof(WINE_COLLECTIONSTORE));
        if (store)
        {
            memset(store, 0, sizeof(WINE_COLLECTIONSTORE));
            CRYPT_InitStore(&store->hdr, dwFlags, StoreTypeCollection);
            store->hdr.closeStore          = CRYPT_CollectionCloseStore;
            store->hdr.certs.addContext    = CRYPT_CollectionAddCert;
            store->hdr.certs.enumContext   = CRYPT_CollectionEnumCert;
            store->hdr.certs.deleteContext = CRYPT_CollectionDeleteCert;
            store->hdr.crls.addContext     = CRYPT_CollectionAddCRL;
            store->hdr.crls.enumContext    = CRYPT_CollectionEnumCRL;
            store->hdr.crls.deleteContext  = CRYPT_CollectionDeleteCRL;
            store->hdr.ctls.addContext     = CRYPT_CollectionAddCTL;
            store->hdr.ctls.enumContext    = CRYPT_CollectionEnumCTL;
            store->hdr.ctls.deleteContext  = CRYPT_CollectionDeleteCTL;
            store->hdr.control             = CRYPT_CollectionControl;
            InitializeCriticalSection(&store->cs);
            store->cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": PWINE_COLLECTIONSTORE->cs");
            list_init(&store->stores);
        }
    }
    return (PWINECRYPT_CERTSTORE)store;
}

BOOL WINAPI CertAddStoreToCollection(HCERTSTORE hCollectionStore,
 HCERTSTORE hSiblingStore, DWORD dwUpdateFlags, DWORD dwPriority)
{
    PWINE_COLLECTIONSTORE collection = hCollectionStore;
    WINECRYPT_CERTSTORE *sibling = hSiblingStore;
    PWINE_STORE_LIST_ENTRY entry;
    BOOL ret;

    TRACE("(%p, %p, %08x, %d)\n", hCollectionStore, hSiblingStore,
     dwUpdateFlags, dwPriority);

    if (!collection || !sibling)
        return TRUE;
    if (collection->hdr.dwMagic != WINE_CRYPTCERTSTORE_MAGIC)
    {
        SetLastError(E_INVALIDARG);
        return FALSE;
    }
    if (collection->hdr.type != StoreTypeCollection)
    {
        SetLastError(E_INVALIDARG);
        return FALSE;
    }
    if (sibling->dwMagic != WINE_CRYPTCERTSTORE_MAGIC)
    {
        SetLastError(E_INVALIDARG);
        return FALSE;
    }

    entry = CryptMemAlloc(sizeof(WINE_STORE_LIST_ENTRY));
    if (entry)
    {
        InterlockedIncrement(&sibling->ref);
        TRACE("sibling %p's ref count is %d\n", sibling, sibling->ref);
        entry->store = sibling;
        entry->dwUpdateFlags = dwUpdateFlags;
        entry->dwPriority = dwPriority;
        list_init(&entry->entry);
        TRACE("%p: adding %p, priority %d\n", collection, entry, dwPriority);
        EnterCriticalSection(&collection->cs);
        if (dwPriority)
        {
            PWINE_STORE_LIST_ENTRY cursor;
            BOOL added = FALSE;

            LIST_FOR_EACH_ENTRY(cursor, &collection->stores,
             WINE_STORE_LIST_ENTRY, entry)
            {
                if (cursor->dwPriority < dwPriority)
                {
                    list_add_before(&cursor->entry, &entry->entry);
                    added = TRUE;
                    break;
                }
            }
            if (!added)
                list_add_tail(&collection->stores, &entry->entry);
        }
        else
            list_add_tail(&collection->stores, &entry->entry);
        LeaveCriticalSection(&collection->cs);
        ret = TRUE;
    }
    else
        ret = FALSE;
    return ret;
}

void WINAPI CertRemoveStoreFromCollection(HCERTSTORE hCollectionStore,
 HCERTSTORE hSiblingStore)
{
    PWINE_COLLECTIONSTORE collection = hCollectionStore;
    WINECRYPT_CERTSTORE *sibling = hSiblingStore;
    PWINE_STORE_LIST_ENTRY store, next;

    TRACE("(%p, %p)\n", hCollectionStore, hSiblingStore);

    if (!collection || !sibling)
        return;
    if (collection->hdr.dwMagic != WINE_CRYPTCERTSTORE_MAGIC)
    {
        SetLastError(E_INVALIDARG);
        return;
    }
    if (collection->hdr.type != StoreTypeCollection)
        return;
    if (sibling->dwMagic != WINE_CRYPTCERTSTORE_MAGIC)
    {
        SetLastError(E_INVALIDARG);
        return;
    }
    EnterCriticalSection(&collection->cs);
    LIST_FOR_EACH_ENTRY_SAFE(store, next, &collection->stores,
     WINE_STORE_LIST_ENTRY, entry)
    {
        if (store->store == sibling)
        {
            list_remove(&store->entry);
            CertCloseStore(store->store, 0);
            CryptMemFree(store);
            break;
        }
    }
    LeaveCriticalSection(&collection->cs);
}
