/*++

Copyright (c) 1992  Microsoft Corporation

Module Name:

    provider.c

Abstract:

    Implements support routines for trust providers.

Author:

    Robert Reichel (Robertre) April 9, 1996

Revision History:

--*/

#ifdef _DEBUG

#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>

#endif

#include <windows.h>
#include <wintrust.h>
#include "provider.h"
#include "trust.h"

///////////////////////////////////////////////////////////////////////////////
//                                                                            /
// Routines to maintain list of loaded trust providers for the client side    /
// of WinVerifyTrust.                                                         /
//                                                                            /
///////////////////////////////////////////////////////////////////////////////

/////////////////////////////////
//                              /
// Private data structures      /
//                              /
/////////////////////////////////

//
//  Location of trust provider information in the registry.
//

#define REGISTRY_TRUSTPROVIDERS TEXT("System\\CurrentControlSet\\Services\\WinTrust\\TrustProviders")
#define REGISTRY_ROOT           HKEY_LOCAL_MACHINE

#define ACTION_IDS              TEXT("$ActionIDs")
#define DLL_NAME                TEXT("$DLL")

#define IsEqualActionID( id1, id2)    (!memcmp(id1, id2, sizeof(GUID))) 

//
// List of loaded providers.  Each loaded provider is represented
// by a LOADED_PROVIDER structure on this list.
//

PLOADED_PROVIDER ProviderList = NULL;

//
// Local Prototypes
//

PLOADED_PROVIDER
ProviderTestProviderForAction(
    IN HKEY hKey,
    IN LPTSTR KeyName,
    IN GUID * ActionID
    );

PLOADED_PROVIDER
ProviderLoadProvider(
    IN HKEY hKey,
    IN LPTSTR KeyName
    );

PLOADED_PROVIDER
ProviderIsProviderLoaded(
    IN LPTSTR KeyName
    );

PLOADED_PROVIDER
ProviderCheckLoadedProviders(
    IN GUID * ActionID
    );

PLOADED_PROVIDER
ProviderScanKnownProviders(
    IN GUID * ActionID
    );

PLOADED_PROVIDER
ProviderSearchAllProviders(
    IN GUID * ActionID
    );


///////////////////////////////////////////////////////////////////////////////
//                                                                            /
//                  Routines exported from this module                        /
//                                                                            /
///////////////////////////////////////////////////////////////////////////////

BOOL
WinSubmitCertificate(
    LPWIN_CERTIFICATE lpCertificate
    )

/*++

Routine Description:

    This routine will call into the set of loaded trust providers
    giving each the opportunity the add the passed certificate to
    their certificate cache.


Arguments:

    lpCertificate - Supplies the Certificate to be cached.

Return Value:

    Returns TRUE for success, FALSE for failure.  Extended error status
    is available using GetLastError.


--*/


{
    PLOADED_PROVIDER Provider;

    AcquireReadLock();

    Provider = ProviderList;

    while (Provider != NULL && 

           //
           // We only want to call fully initialized providers here,
           //

           Provider->ProviderInitialized == PROVIDER_INITIALIZATION_SUCCESS 
           ) {

        (*Provider->ClientInfo->lpServices->SubmitCertificate)( lpCertificate );

        Provider = Provider->Next;
    }

    ReleaseReadLock();
    return( TRUE );
}


PLOADED_PROVIDER
WinTrustFindActionID(
    IN GUID * dwActionID
    )

/*++

Routine Description:

    This routine will perform the following actions in sequence:

    1) All loaded providers will be searched for the desired ActionID.  The
       first match that implements the requested ActionID will be returned.

    2) If no loaded provider is suitable, then the list of providers in the
       registry will be searched for hint information.  Every potential
       provider will be loaded and queried to see if it exports the desired
       ActionID.  Also, hint information will be updated on every loaded
       provider.

    3) If no hint information leads us to a suitable provider, an exhaustive
       search of every provider mentioned in the registry will take place.
       If none are found, failure is returned.  Processing will stop as soon
       as a suitable provider is discovered.

Arguments:

    dwActionID - The ActionID to be found.

Return Value:

    On success, returns a pointer to a LOADED_PROVIDER structure
    representing the provider.

    On failure, returns NULL.

--*/

{
    PLOADED_PROVIDER Provider = NULL;

    Provider = ProviderCheckLoadedProviders( dwActionID );

    if (NULL == Provider) {

        Provider = ProviderScanKnownProviders( dwActionID );
    }

    if (NULL == Provider) {

        Provider = ProviderSearchAllProviders( dwActionID );
    }

#ifdef _DEBUG

    if (NULL == Provider) {
        DbgPrint("Provider not found for ActionId ");
        DbgPrint("%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X",
                dwActionID->Data1, dwActionID->Data2, dwActionID->Data3, dwActionID->Data4[0],
                dwActionID->Data4[1], dwActionID->Data4[2], dwActionID->Data4[3], dwActionID->Data4[4],
                dwActionID->Data4[5], dwActionID->Data4[6], dwActionID->Data4[7]);
        DbgPrint("\n");
    }
#endif

    return( Provider );
}


BOOL
WinLoadTrustProvider(
    GUID * ActionID
    )
/*++

Routine Description:

    Determines if a trust provider exporting the passed Action ID
    exists.

Arguments:

    ActionID - provides the desired Action ID.

Return Value:

    TRUE - A trust provider with the desired action has been found.

    FALSE - No trust provider with the desired action could be found.

--*/
{
    if (WinTrustFindActionID( ActionID ) != NULL) {

        return( TRUE );

    } else {

        return( FALSE );
    }
}


///////////////////////////////////////////////////////////////////////////////
//                                                                            /
//                     Routines local to this module                          /
//                                                                            /
///////////////////////////////////////////////////////////////////////////////

PLOADED_PROVIDER
ProviderCheckLoadedProviders(
    IN GUID * ActionID
    )

/*++

Routine Description:

    Walks the list of loaded providers, attempting to find one
    that implements the specified action ID.

Arguments:

    ActionID - Specifies the desired ActionID.

Return Value:

    On success, returns a pointer to a LOADED_PROVIDER structure.  On failure,
    returns NULL.

--*/

{
    PLOADED_PROVIDER Provider;
    ULONG i;

    AcquireReadLock();

    Provider = ProviderList;

    while (Provider != NULL && 

           //
           // We only want to look at fully initialized providers here,
           // we'll pick up any that are partially initialized in a later
           // pass.
           //

           Provider->ProviderInitialized == PROVIDER_INITIALIZATION_SUCCESS 
           ) {

        for (i=0; i < Provider->ClientInfo->dwActionIdCount; i++) {

            if (IsEqualActionID(&Provider->ClientInfo->lpActionIdArray[i], ActionID )) {

                ReleaseReadLock();
                return( Provider );
            }
        }

        Provider = Provider->Next;
    }

    ReleaseReadLock();
    return( NULL );
}


PLOADED_PROVIDER
ProviderScanKnownProviders(
    IN GUID * ActionID
    )

/*++

Routine Description:

    This routine will examine the contents of the registry to determine
    if we have ever loaded a provider with the specified ActionID.  If
    so, we will re-load the provider and verify that it still exports the
    desired ActionID.

Arguments:

    ActionID - Supplies the desired ActionID.

Return Value:

    On success, returns a pointer to a LOADED_PROVIDER block.  Returns NULL
    on failure.

--*/

{
    HKEY    hKey;             // Handle to the base of the provider information.
    HKEY    hSubKey;          // Handle to the provider currently being examined.
    LONG    Result;           // Returned by registry API.
    DWORD   cSubKeys;         // Number of providers under the root key.
    DWORD   cbMaxSubKeyLen;   // Maximum provider name length.
    ULONG   i,j;              // Indicies for iterating through providers and action IDs.
    LPTSTR  SubKeyName;       // Points to the name of the current provider.
    GUID    Buffer[10];       // Assume no more than 10 action ids in a provider
    LPBYTE  Data;
    DWORD   cbData;
    GUID *  ActionIds;
    PLOADED_PROVIDER FoundProvider = NULL;

    //
    // Open the registry and get a list of installed trust providers
    //

    Result = RegOpenKeyEx(
                 REGISTRY_ROOT,
                 REGISTRY_TRUSTPROVIDERS,
                 0L,
                 GENERIC_READ,
                 &hKey
                 );

    if (Result != ERROR_SUCCESS) {
        return( NULL );
    }

    //
    // Find out how many subkeys there are.
    //

    Result = RegQueryInfoKey (  hKey,               // handle of key to query
                                NULL,               // address of buffer for class string
                                NULL,               // address of size of class string buffer
                                NULL,               // reserved
                                &cSubKeys,          // address of buffer for number of subkeys
                                &cbMaxSubKeyLen,    // address of buffer for longest subkey name length
                                NULL,               // address of buffer for longest class string length
                                NULL,               // address of buffer for number of value entries
                                NULL,               // address of buffer for longest value name length
                                NULL,               // address of buffer for longest value data length
                                NULL,               // address of buffer for security descriptor length
                                NULL                // address of buffer for last write time
                                );

    if (ERROR_SUCCESS != Result) {
        RegCloseKey( hKey );
        return( NULL );
    }

    //
    // Iterate through the subkeys, looking for ones with hint information.
    //

    cbMaxSubKeyLen += sizeof( WCHAR );

    SubKeyName = LocalAlloc( 0, cbMaxSubKeyLen );

    if (NULL == SubKeyName) {
        RegCloseKey( hKey );
        return(NULL);
    }

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

        DWORD ValueType;
        DWORD KeyNameLength;

        KeyNameLength = cbMaxSubKeyLen;

        Result = RegEnumKeyEx( hKey,               // handle of key to enumerate
                               i,                  // index of subkey to enumerate
                               SubKeyName,         // address of buffer for subkey name
                               &KeyNameLength,     // address for size of subkey buffer
                               NULL,               // reserved
                               NULL,               // address of buffer for class string
                               NULL,               // address for size of class buffer
                               NULL                // address for time key last written to
                               );

        //
        // Not much to do if this fails, try enumerating the rest of them and see
        // what happens.
        //

        if (Result != ERROR_SUCCESS) {
            continue;
        }

//        //
//        // If this provider is already loaded, we don't need to check it,
//        // since we already have up to date information on its Action IDs.
//        //
//
//        AcquireReadLock();
//
//        if (NULL != ProviderIsProviderLoaded( SubKeyName )) {
//
//            ReleaseReadLock();
//            continue;
//        }
//
//        ReleaseReadLock();

        Result = RegOpenKeyEx(
                     hKey,
                     SubKeyName,
                     0L,
                     GENERIC_READ | MAXIMUM_ALLOWED,
                     &hSubKey
                     );

        //
        // If this failed for some reason, just keep looking.
        //

        if (Result != ERROR_SUCCESS) {
            continue;
        }

        Data = (LPBYTE)Buffer;
        cbData = sizeof( Buffer );

        Result = RegQueryValueEx(
                    hSubKey,    // handle of key to query
                    ACTION_IDS, // address of name of value to query
                    NULL,       // reserved
                    &ValueType, // address of buffer for value type
                    Data,       // address of data buffer
                    &cbData     // address of data buffer size
                   );

        if (ERROR_MORE_DATA == Result) {

            //
            // More than 10 action ids in this provider
            //

            Data = LocalAlloc( 0, cbData );

            if (NULL == Data) {
                RegCloseKey( hSubKey );
                continue;
            }

            Result = RegQueryValueEx(
                        hSubKey,    // handle of key to query
                        ACTION_IDS, // address of name of value to query
                        NULL,       // reserved
                        &ValueType, // address of buffer for value type
                        Data,       // address of data buffer
                        &cbData     // address of data buffer size
                       );
        }

        if (Result != ERROR_SUCCESS) {

            //
            // We couldn't query the value for some reason.  It may not
            // exist on this key.  For whatever reason, keep moving down
            // the list of subkeys.
            //

            if (Data != (LPBYTE)Buffer) {
                LocalFree( Data );
            }

            RegCloseKey( hSubKey );
            continue;
        }

        ActionIds = (GUID *)Data;

        for (j = 0; j < cbData / sizeof( GUID ); j++) {

            if (IsEqualActionID(&ActionIds[j], ActionID)) {

                //
                // Got a potential match.  Load the dll
                // and see if it's bona-fide.
                //

                FoundProvider = ProviderTestProviderForAction( hSubKey, SubKeyName, ActionID );

                break;
            }
        }

        if (Data != (LPBYTE)Buffer) {
            LocalFree( Data );
        }

        RegCloseKey( hSubKey );

        if (FoundProvider != NULL) {

            //
            // Got one.  Clean up and return it.
            //

            LocalFree( SubKeyName );
            RegCloseKey( hKey );
            return( FoundProvider );
        }
    }

    LocalFree( SubKeyName );

    RegCloseKey( hKey );

    return( NULL );
}


PLOADED_PROVIDER
ProviderSearchAllProviders(
    GUID * ActionID
    )
/*++

Routine Description:

    This routine will find all trust providers installed in the registry,
    and query each one for the desired action id until either one is
    discovered or none are left.

    Note that this is our last chance to find a provider.  That being the case,
    we're not going to see if the provider is already loaded, because we
    can miss a provider due to a race condition if we do that.

Arguments:

    ActionID - Provides the desired action id.

Return Value:

    Returns a pointer to a loaded provider, otherwise NULL.

--*/

{
    HKEY    hKey;             // Handle to the base of the provider information.
    HKEY    hSubKey;          // Handle to the provider currently being examined.
    LONG    Result;           // Returned by registry API.
    DWORD   cSubKeys;         // Number of providers under the root key.
    DWORD   cbMaxSubKeyLen;   // Maximum provider name length.
    ULONG   i;              // Indicies for iterating through providers and action IDs.
    LPTSTR  SubKeyName;       // Points to the name of the current provider.
    GUID *  ActionIds;
    PLOADED_PROVIDER FoundProvider = NULL;

    //
    // Open the registry and get a list of installed trust providers
    //

    Result = RegOpenKeyEx(
                 REGISTRY_ROOT,
                 REGISTRY_TRUSTPROVIDERS,
                 0L,
                 GENERIC_READ,
                 &hKey
                 );

    if (Result != ERROR_SUCCESS) {
        return( NULL );
    }

    //
    // Find out how many subkeys there are.
    //

    Result = RegQueryInfoKey (  hKey,               // handle of key to query
                                NULL,               // address of buffer for class string
                                NULL,               // address of size of class string buffer
                                NULL,               // reserved
                                &cSubKeys,          // address of buffer for number of subkeys
                                &cbMaxSubKeyLen,    // address of buffer for longest subkey name length
                                NULL,               // address of buffer for longest class string length
                                NULL,               // address of buffer for number of value entries
                                NULL,               // address of buffer for longest value name length
                                NULL,               // address of buffer for longest value data length
                                NULL,               // address of buffer for security descriptor length
                                NULL                // address of buffer for last write time
                                );

    if (ERROR_SUCCESS != Result) {
        RegCloseKey( hKey );
        return( NULL );
    }

    //
    // Iterate through the subkeys, looking for ones with hint information.
    //

    cbMaxSubKeyLen += sizeof( WCHAR );

    SubKeyName = LocalAlloc( 0, cbMaxSubKeyLen );

    if (NULL == SubKeyName) {
        RegCloseKey( hKey );
        return(NULL);
    }

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

        DWORD KeyNameLength;

        KeyNameLength = cbMaxSubKeyLen;

        Result = RegEnumKeyEx( hKey,               // handle of key to enumerate
                               i,                  // index of subkey to enumerate
                               SubKeyName,         // address of buffer for subkey name
                               &KeyNameLength,     // address for size of subkey buffer
                               NULL,               // reserved
                               NULL,               // address of buffer for class string
                               NULL,               // address for size of class buffer
                               NULL                // address for time key last written to
                               );

        //
        // Not much to do if this fails, try enumerating the rest of them and see
        // what happens.
        //

        if (Result != ERROR_SUCCESS) {
            continue;
        }

//        //
//        // If this provider is already loaded, we don't need to check it,
//        // since we already have up to date information on its Action IDs.
//        //
//
//        AcquireReadLock();
//
//        if (NULL != ProviderIsProviderLoaded( SubKeyName )) {
//            ReleaseReadLock();
//            continue;
//        }
//
//        ReleaseReadLock();

        Result = RegOpenKeyEx(
                     hKey,
                     SubKeyName,
                     0L,
                     GENERIC_READ | MAXIMUM_ALLOWED,
                     &hSubKey
                     );

        if (ERROR_SUCCESS != Result) {

            //
            // Failed for some reason.  Go back and try the next one.
            //

            continue;
        }

        FoundProvider = ProviderTestProviderForAction( hSubKey, SubKeyName, ActionID );

        RegCloseKey( hSubKey );

        if (NULL != FoundProvider) {
            
            //
            // Got one.  Clean up and return.
            //

            LocalFree( SubKeyName );
            RegCloseKey( hKey );
            return( FoundProvider );
        }

        continue;
    }

    LocalFree( SubKeyName );
    RegCloseKey( hKey );
    return( NULL );
}


PLOADED_PROVIDER
ProviderTestProviderForAction(
    IN HKEY hKey,
    IN LPTSTR KeyName,
    IN GUID * ActionID
    )

/*++

Routine Description:

    This routine will find the provider dll referenced by the passed key handle
    and examine it to see if it implements the passed ActionID.

    The candidate provider dll will be added to the loaded providers list,
    regardless of whether or not it implements the desired Action ID.

Arguments:

    hKey - Handle to an open registry key describing a provider dll.

    KeyName - The name of the registry key passed in hKey.

    ActionID - The desired Action ID.

Return Value:

    Returns a pointer to a loaded provider structure describing the candidate
    provider dll if and only if the dll implements the passed Action ID.

    If the candidate provider does not implement the passed Action ID, or
    some other error occurs, the routine returns NULL.

--*/

{
    PLOADED_PROVIDER Provider;
    LPWINTRUST_PROVIDER_CLIENT_INFO ClientInfo;
    GUID * ActionIds;
    DWORD i;

    Provider = ProviderLoadProvider( hKey, KeyName );

    if (NULL == Provider) {
        return( NULL );
    }

    ClientInfo = Provider->ClientInfo;

    ActionIds = ClientInfo->lpActionIdArray;

    for (i=0; i<ClientInfo->dwActionIdCount; i++) {

        if (IsEqualActionID(ActionID, &ActionIds[i])) {
            return( Provider );
        }
    }

    return(NULL);
}


PLOADED_PROVIDER
ProviderIsProviderLoaded(
    IN LPTSTR KeyName
    )

/*++

Routine Description:

    This routine will examine the list of loaded providers and determine (by
    examining the key name information) whether or not the passed provider
    is already on the list.

    Note: this routine does not acquire or release any locks.  It is up to the
    caller to decide what kind of lock is appropriate and make the necessary
    calls.

Arguments:

    KeyName - The key name of this provider.

Return Value:

    If the passed provider is found, returns a pointer to the loaded provider.
    If not found, it returns NULL.

--*/
{
    PLOADED_PROVIDER Provider;

    Provider = ProviderList;

    while (Provider != NULL ) {

        if (lstrcmp( KeyName, Provider->SubKeyName) == 0) {
            return(Provider);
        }

        Provider = Provider->Next;
    }

    return(NULL);
}


PLOADED_PROVIDER
ProviderLoadProvider(
    IN HKEY hKey,
    IN LPTSTR KeyName
    )
/*++

Routine Description:

    This routine will load the provider described in the passed registry
    key and add it to the loaded providers list.

    It will also update any hint information that is maintained under the
    registry key.

    Following is an example of a system configured to load "Software Publisher"
    and "Windows Compatible" Trust Providers:

    HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\WinTrust\TrustProviders
        \Software Publisher
            \$DLL [REG_EXPAND_SZ]: SoftPub.dll
            \State [REG_DWORD]:   0x0006

        \Windows Compatible
            \$DLL [REG_EXPAND_SZ]:  WinComp.dll
            \EXCEPTIONS
                \Terror Scanner
                \GaudAwful Video

    In this example, hKey refers to either the "Software Publisher" or "Windows
    Compatible" keys.

Arguments:

    hKey - Supplies a handle to the registry key describing the current provider.

    KeyName - Supplies the name of the key passed in the first parameter.

Return Value:

    return-value - Description of conditions needed to return value. - or -
    None.

--*/

{
    LPTSTR ModuleName                           = NULL;
    HINSTANCE LibraryHandle                     = NULL;
    LPWINTRUST_PROVIDER_CLIENT_INFO ClientInfo  = NULL;
    PLOADED_PROVIDER Provider                   = NULL;
    PLOADED_PROVIDER FoundProvider              = NULL;
    LPWSTR ProviderName                         = NULL;
    LPTSTR SubKeyName                           = NULL;

    DWORD Type;
    DWORD cbData = 0;
    LONG Result;
    LPWINTRUST_PROVIDER_CLIENT_INITIALIZE ProcAddr;
    BOOL Referenced = FALSE;
    DWORD size;
    BOOL Inited;

    //
    // Take a write lock on the provider list so we can see if this
    // module is already on the list or not.
    //

    AcquireWriteLock();

    if (NULL != (FoundProvider = ProviderIsProviderLoaded( KeyName ))) {

        //
        // We've found a provider with the same name on the list.
        // It is in one of three states:
        //
        // 1) Fully initialized: in this case, we have no more work to do,
        //    simply return a pointer to the provider we found.
        //
        // 2) In the process of being initialized: in this case, we need
        //    to increment the reference count on this provider and wait
        //    on the list event for a wakeup telling us that init has
        //    completed.
        //
        // 3) Initialization was attempted, and failed.  In this case,
        //    one of the waiters will clean up.  Just leave and return
        //    NULL.
        //

        while (TRUE) {

            switch (FoundProvider->ProviderInitialized) {
                case PROVIDER_INITIALIZATION_SUCCESS:
                    {
                        //
                        // BUGBUG: should the refcount be zero'd out here?
                        // We have a writelock, we can if we need to.
                        //

                        ReleaseWriteLock();

                        return( FoundProvider );
                    }
                case PROVIDER_INITIALIZATION_IN_PROGRESS:
                    {
                        FoundProvider->RefCount++;   // protected by write lock, no need for interlocked.
                        Referenced = TRUE;

                        //
                        // Loop forever until something happens.
                        //

                        do {

                            //
                            // Event <- not signaled, forcing wait
                            //

                            ResetListEvent();
    
                            ReleaseWriteLock();
    
                            //
                            // Note that someone may set the event here.  In
                            // this case, we'll wake up immediately, but that's
                            // ok.
                            //
    
                            WaitForListEvent();
    
                            AcquireWriteLock();

                            if (PROVIDER_INITIALIZATION_IN_PROGRESS == FoundProvider->ProviderInitialized) {

                                //
                                // Spurious wakeup, reset the list event and wait again.
                                //

                            } else {

                                //
                                // We've changed state, break out of this loop and go back to the   
                                // top of the switch and see what happened.  Note that we have a    
                                // write lock at this point, which is expected at the top of the     
                                // switch.
                                //

                                break;
                            }

                        } while ( TRUE );

                        //
                        // Go back to top of outer loop.
                        //

                        break;
                    }

                case PROVIDER_INITIALIZATION_FAILED:
                    {
                        //
                        // If we referenced this provider to wait on it,
                        // we may need to be the one to clean him up.
                        //
                        // If we didn't reference it (it was dead when we
                        // got here), then just leave and let someone
                        // who did reference it clean it up.
                        //

#ifdef _DEBUG

        DbgPrint("Top of ProviderLoadProvider, init failed, refcount = %d\n",FoundProvider->RefCount);

#endif

                        if (Referenced) {

                            if (--FoundProvider->RefCount == 0) {

#ifdef _DEBUG

        DbgPrint("Top of ProviderLoadProvider, thread %x removing provider at %X from list\n",GetCurrentThreadId(),FoundProvider);

#endif
    
                                //
                                // Remove this module from doubly linked list.
                                //

                                if (FoundProvider->Prev != NULL) {

                                    FoundProvider->Prev->Next = FoundProvider->Next;

                                } else {

                                    //
                                    // We're at the head of the list
                                    //

                                    ProviderList = FoundProvider->Next;
                                }

                                if (FoundProvider->Next != NULL) {

                                    FoundProvider->Next->Prev = FoundProvider->Prev;
                                } 

                                LocalFree( FoundProvider->SubKeyName );
                                LocalFree( FoundProvider );
                            }
                        } 

                        ReleaseWriteLock();

                        return( NULL );

                        break;
                    }
            }
        }
    }
    
    //
    // Extract the dll name from the $DLL value
    //

    Result = RegQueryValueEx( hKey,           // handle of key to query
                              TEXT("$DLL"),   // address of name of value to query
                              NULL,           // reserved
                              &Type,          // address of buffer for value type
                              NULL,           // address of data buffer
                              &cbData         // address of data buffer size
                              );

//    if (ERROR_MORE_DATA != Result) {
//        goto error_cleanup;
//    }

    if (ERROR_SUCCESS != Result) {
        goto error_cleanup;
    }

    cbData += sizeof( TCHAR );

    ModuleName = LocalAlloc( 0, cbData );

    if (NULL == ModuleName) {
        goto error_cleanup;
    }

    ModuleName[cbData - 1] = TEXT('\0');

    Result = RegQueryValueEx( hKey,           // handle of key to query
                              TEXT("$DLL"),   // address of name of value to query
                              NULL,           // reserved
                              &Type,          // address of buffer for value type
                              (LPBYTE)ModuleName,   // address of data buffer
                              &cbData         // address of data buffer size
                              );

    if (ERROR_SUCCESS != Result) {
        goto error_cleanup;
    }

    //
    // Expand environment strings if necessary
    //

    if (Type == REG_EXPAND_SZ) {

        DWORD ExpandedLength = 0;
        LPTSTR ExpandedModuleName = NULL;

        ExpandedLength = ExpandEnvironmentStrings( ModuleName, NULL, 0 );

        if (0 == ExpandedLength) {
            goto error_cleanup;
        }

        ExpandedModuleName = LocalAlloc( 0, ExpandedLength );

        if (NULL == ExpandedModuleName) {
            goto error_cleanup;
        }

        ExpandedLength = ExpandEnvironmentStrings( ModuleName, ExpandedModuleName, ExpandedLength );

        if (0 == ExpandedLength) {
            LocalFree( ExpandedModuleName );
            goto error_cleanup;
        }

        //
        // Free the old module name, use the new one
        //

        LocalFree( ModuleName );

        ModuleName = ExpandedModuleName;
    }

    size = (lstrlen( KeyName ) + 1) * sizeof( WCHAR );

    ProviderName = LocalAlloc( 0, size );

    if (NULL == ProviderName) {
        goto error_cleanup;
    }


#ifdef UNICODE

    //
    // If we've been compiled as unicode, the KeyName we got from
    // the registry consists of WCHARs, so we can just copy it into
    // the Name buffer.
    //

    lstrcpy( ProviderName, KeyName );

#else

    //
    // If we've been compiled as ANSI, then KeyName is an ANSI string,
    // and we need to convert it to WCHARs.
    //

    MultiByteToWideChar ( CP_ACP, 0, KeyName, -1, ProviderName, size );

#endif // !UNICODE

    //
    // ModuleName now contains the module name, attempt to load it
    // and ask it to initialize itself.
    //

    LibraryHandle = LoadLibrary( (LPTSTR)ModuleName );

    if (NULL == LibraryHandle) {
        DWORD Error;

        Error = GetLastError();

        goto error_cleanup;
    }

    ProcAddr = (LPWINTRUST_PROVIDER_CLIENT_INITIALIZE) GetProcAddress( LibraryHandle, (LPCSTR)"WinTrustProviderClientInitialize");

    if (NULL == ProcAddr) {
        goto error_cleanup;
    }

    SubKeyName = LocalAlloc( 0, (lstrlen( KeyName ) + 1) * sizeof( TCHAR ));

    if (NULL == SubKeyName) {
        goto error_cleanup;
    }

    lstrcpy( SubKeyName, KeyName );

    Provider = LocalAlloc( 0, sizeof( LOADED_PROVIDER ));

    if (NULL == Provider) {
        LocalFree( SubKeyName );
        goto error_cleanup;
    }

    //
    // Ready to call init routine.
    //

    Provider->RefCount = 1;
    Provider->ProviderInitialized = PROVIDER_INITIALIZATION_IN_PROGRESS;

    //
    // Set the subkey name so anyone else looking for this provider will
    // find this one and wait.
    //
    // Note that we don't want to use the ProviderName as will be passed into
    // the init routine here, because we've forced that to WCHARs regardless
    // of whether we're ANSI or Unicode, and we want this string to reflect
    // the base system for efficiency.
    //

    Provider->SubKeyName = SubKeyName;

    Provider->Next = ProviderList;
    Provider->Prev = NULL;

    if (Provider->Next != NULL) {
        Provider->Next->Prev = Provider;
    }

    ProviderList = Provider;

    ReleaseWriteLock();

    //
    // bugbug try-except probably appropriate here...
    //

#ifdef _DEBUG

    DbgPrint("Calling WinTrustProviderClientInitialize in %s\n",ModuleName);

#endif

    Inited = (*ProcAddr)( WIN_TRUST_REVISION_1_0, &WinTrustClientTPInfo, ProviderName, &ClientInfo );

    AcquireWriteLock();

    if (TRUE != Inited) {

#ifdef _DEBUG

        DbgPrint("WinTrustProviderClientInitialize in %s failed\n",ModuleName);

#endif

        Provider->ProviderInitialized = PROVIDER_INITIALIZATION_FAILED;

        //
        // Signal event, waking up waiters.
        //

        SetListEvent();

        if (--Provider->RefCount == 0) {

            //
            // Remove this module from doubly linked list.
            //

#ifdef _DEBUG

        DbgPrint("Provider init failed, thread %x removing provider at %X from list\n",GetCurrentThreadId(),Provider);

#endif

            if (Provider->Prev != NULL) {

                Provider->Prev->Next = Provider->Next;

            } else {

                //
                // We're at the head of the list
                //

                ProviderList = Provider->Next;
            }

            if (Provider->Next != NULL) {

                Provider->Next->Prev = Provider->Prev;
            } 

            LocalFree( Provider->SubKeyName );
            LocalFree( Provider );
        }

        //
        // We could release the lock now, because we're either going to
        // do nothing to this provider, or we've removed it from
        // the list and no one else can get to it.
        //

        goto error_cleanup;
    }

    //
    // Since we have a write lock, it doesn't matter what order we
    // do this in, since there are no readers.  Just be sure to signal
    // the event under the write lock.
    //

    Provider->ProviderInitialized = PROVIDER_INITIALIZATION_SUCCESS;
    Provider->ModuleHandle = LibraryHandle;
    Provider->ModuleName = ModuleName;
    Provider->ClientInfo = ClientInfo;

    //
    // Init is done and successful.  Wake anyone who might be waiting.
    //

    SetListEvent();

    ReleaseWriteLock();

    //
    // Attempt to update (or create) hint information about
    // the provider in the key.
    //
    // Failure here is non-critical.
    //

    ( VOID ) RegSetValueEx( hKey,                                          // handle of key to set value for
                            ACTION_IDS,                                    // address of value to set
                            0,                                             // reserved
                            REG_BINARY,                                    // flag for value type
                            (CONST BYTE *)ClientInfo->lpActionIdArray,     // address of value data
                            ClientInfo->dwActionIdCount * sizeof( GUID )  // size of value data
                            );

    return( Provider );

error_cleanup:

    ReleaseWriteLock();

    if (NULL != LibraryHandle) {
        FreeLibrary( LibraryHandle );
    }

    if (NULL != ModuleName) {
        LocalFree( ModuleName );
    }

    if (NULL != ProviderName) {
        LocalFree( ProviderName );
    }

    return( NULL );
}