/*++

Copyright (c) 1992  Microsoft Corporation

Module Name:

    Regcnreg.c

Abstract:

    This module contains the Win32 Registry APIs to connect to a remote
    Registry.  That is:

       - RegConnectRegistryA
       - RegConnectRegistryW
Author:

    David J. Gilman (davegi) 25-Mar-1992

Notes:

    The semantics of this API make it local only. That is there is no MIDL
    definition for RegConnectRegistry although it does call other client
    stubs, specifically OpenLocalMachine and OpenUsers.

Revision History:

    John Vert (jvert) 16-Jun-1995
       Added connect support for protocols other than named pipes by
       stealing code from Win95. This enabled NT machines to connect
       to registries on Win95 machines

--*/

#include <rpc.h>
#include "regrpc.h"
#include "client.h"
#include "shutinit.h"
#include "..\regconn\regconn.h"

LONG
BaseBindToMachine(
    IN LPCWSTR lpMachineName,
    IN PBIND_CALLBACK BindCallback,
    IN PVOID Context1,
    IN PVOID Context2
    );


typedef int (* RegConnFunction)(LPCWSTR, handle_t *);

RegConnFunction conn_functions[] = {
        RegConn_np,
        RegConn_spx,
        RegConn_ip_tcp,
        RegConn_nb_nb,
        RegConn_nb_tcp,
        RegConn_nb_ipx,
        NULL
};

LONG
Rpc_OpenPredefHandle(
    IN RPC_BINDING_HANDLE * pbinding OPTIONAL,
    IN HKEY hKey,
    OUT PHKEY phkResult
    )

/*++

Routine Description:

    Win32 Unicode API for establishing a connection to a predefined
    handle on another machine.

Parameters:

    pbinding - This is a pointer to the binding handle in order
                to allow access to multiple protocols (NT remote registry is only over
                named pipes).

    hKey - Supplies the predefined handle to connect to on the remote
        machine. Currently this parameter must be one of:

        - HKEY_LOCAL_MACHINE
        - HKEY_PERFORMANCE_DATA
        - HKEY_USERS

    phkResult - Returns a handle which represents the supplied predefined
        handle on the supplied machine.

Return Value:

    Returns ERROR_SUCCESS (0) for success; error-code for failure.
        On failure, the binding handle is freed.

Notes:

    For administration purposes this API allows programs to access the
    Registry on a remote machine.  In the current system the calling
    application must know the name of the remote machine that it wishes to
    connect to.  However, it is expected that in the future a directory
    service API will return the parameters necessary for this API.

--*/

{
    LONG    Error;
    HKEY    PreviousResult;

    ASSERT( (phkResult != NULL));
    PreviousResult = *phkResult;

    switch ((int)(ULONG_PTR)hKey)
    {
        case (int)(ULONG_PTR)HKEY_LOCAL_MACHINE:

            Error = (LONG)OpenLocalMachine((PREGISTRY_SERVER_NAME) pbinding,
                                           MAXIMUM_ALLOWED,
                                           phkResult );
            break;

        case (int)(ULONG_PTR)HKEY_PERFORMANCE_DATA:

            Error = (LONG)OpenPerformanceData((PREGISTRY_SERVER_NAME) pbinding,
                                              MAXIMUM_ALLOWED,
                                              phkResult );
            break;

        case (int)(ULONG_PTR)HKEY_USERS:

            Error = (LONG)OpenUsers((PREGISTRY_SERVER_NAME) pbinding,
                                    MAXIMUM_ALLOWED,
                                    phkResult );
            break;

        case (int)(ULONG_PTR)HKEY_CLASSES_ROOT:

            Error = (LONG)OpenClassesRoot((PREGISTRY_SERVER_NAME) pbinding,
                                          MAXIMUM_ALLOWED,
                                          phkResult );
            break;

        case (int)(ULONG_PTR)HKEY_CURRENT_USER:

            Error = (LONG)OpenCurrentUser((PREGISTRY_SERVER_NAME) pbinding,
                                          MAXIMUM_ALLOWED,
                                          phkResult );
            break;

        case (int)(ULONG_PTR)HKEY_PERFORMANCE_TEXT:

            Error = (LONG)OpenPerformanceText((PREGISTRY_SERVER_NAME) pbinding,
                                              MAXIMUM_ALLOWED,
                                              phkResult );

            break;

        case (int)(ULONG_PTR)HKEY_PERFORMANCE_NLSTEXT:

            Error = (LONG)OpenPerformanceNlsText((PREGISTRY_SERVER_NAME) pbinding,
                                              MAXIMUM_ALLOWED,
                                              phkResult );

            break;

        default:
            Error = ERROR_INVALID_HANDLE;
    }

    if( Error != ERROR_SUCCESS) {
        ASSERTMSG("WINREG: RPC failed, but modifed phkResult", *phkResult == PreviousResult);
        if (*pbinding != NULL)
            RpcBindingFree(pbinding);
    }

    return Error;
}

LONG
LocalOpenPredefHandle(
    IN HKEY hKey,
    OUT PHKEY phkResult
    )

/*++

Routine Description:

    Opens a predefined handle locally. The purpose of this is to bypass RPC in the 
    case of connecting to the local machine.

Parameters:

    hKey - Supplies the predefined handle to connect to on the remote
        machine. Currently this parameter must be one of:

        - HKEY_LOCAL_MACHINE
        - HKEY_PERFORMANCE_DATA
        - HKEY_USERS

    phkResult - Returns a handle which represents the supplied predefined
        handle on the supplied machine.

Return Value:

    Returns ERROR_SUCCESS (0) for success; error-code for failure.

--*/

{
    LONG    Error;

    ASSERT( (phkResult != NULL));

    switch ((int)(ULONG_PTR)hKey)
    {
        case (int)(ULONG_PTR)HKEY_LOCAL_MACHINE:

            Error = (LONG)LocalOpenLocalMachine(NULL,
                                           MAXIMUM_ALLOWED,
                                           phkResult );
            break;

        case (int)(ULONG_PTR)HKEY_PERFORMANCE_DATA:

            Error = (LONG)LocalOpenPerformanceData(NULL,
                                              MAXIMUM_ALLOWED,
                                              phkResult );
            break;

        case (int)(ULONG_PTR)HKEY_USERS:

            Error = (LONG)LocalOpenUsers(NULL,
                                    MAXIMUM_ALLOWED,
                                    phkResult );
            break;

        case (int)(ULONG_PTR)HKEY_CLASSES_ROOT:

            Error = (LONG)LocalOpenClassesRoot(NULL,
                                          MAXIMUM_ALLOWED,
                                          phkResult );
            break;

        case (int)(ULONG_PTR)HKEY_CURRENT_USER:

            Error = (LONG)LocalOpenCurrentUser(NULL,
                                          MAXIMUM_ALLOWED,
                                          phkResult );
            break;

        case (int)(ULONG_PTR)HKEY_PERFORMANCE_TEXT:
        case (int)(ULONG_PTR)HKEY_PERFORMANCE_NLSTEXT:
        case (int)(ULONG_PTR)HKEY_CURRENT_CONFIG:
        case (int)(ULONG_PTR)HKEY_DYN_DATA:
            //
            // try not to break whoever used this
            //
            *phkResult = hKey;
            Error = ERROR_SUCCESS;
            break;

        default:
            Error = ERROR_INVALID_HANDLE;
    }

    return Error;
}

LONG
RegConnectRegistryW (
    IN LPCWSTR lpMachineName OPTIONAL,
    IN HKEY hKey,
    OUT PHKEY phkResult
    )

/*++

Routine Description:

    Win32 Unicode API for establishing a connection to a predefined
    handle on another machine.

Parameters:

    lpMachineName - Supplies a pointer to a null-terminated string that
    names the machine of interest.  If this parameter is NULL, the local
    machine name is used.

    hKey - Supplies the predefined handle to connect to on the remote
    machine. Currently this parameter must be one of:

    - HKEY_LOCAL_MACHINE
    - HKEY_PERFORMANCE_DATA
    - HKEY_USERS

    phkResult - Returns a handle which represents the supplied predefined
    handle on the supplied machine.

Return Value:

    Returns ERROR_SUCCESS (0) for success; error-code for failure.

Notes:

    For administration purposes this API allows programs to access the
    Registry on a remote machine.  In the current system the calling
    application must know the name of the remote machine that it wishes to
    connect to.  However, it is expected that in the future a directory
    service API will return the parameters necessary for this API.

    Even though HKEY_CLASSES and HKEY_CURRENT_USER are predefined handles,
    they are not supported by this API as they do not make sense in the
    context of a remote Registry.

--*/

{
    LONG    Error;
    WCHAR   ComputerName[MAX_COMPUTERNAME_LENGTH + 1];
    DWORD   bufLen = MAX_COMPUTERNAME_LENGTH + 1;

    ASSERT( ARGUMENT_PRESENT( phkResult ));

#if DBG
    if ( BreakPointOnEntry ) {
        DbgBreakPoint();
    }
#endif
    //
    // Check for local connect
    //

    if (lpMachineName == NULL) {
        //
        // always return a valid handle
        //
        Error = LocalOpenPredefHandle(hKey,phkResult);
        return Error;
    } else if (lpMachineName[0] == L'\0') {
        //
        // always return a valid handle
        //
        Error = LocalOpenPredefHandle(hKey,phkResult);
        return Error;
    }

    if (GetComputerNameW(ComputerName,&bufLen)) {

        if ((_wcsicmp(ComputerName,lpMachineName) == 0) ||
                ((lpMachineName[0] == '\\') &&
                 (lpMachineName[1] == '\\') &&
                 (_wcsicmp(ComputerName,&(lpMachineName[2]))==0))) {
            //
            // local connect
            //
            //
            // always return a valid handle
            //
            Error = LocalOpenPredefHandle(hKey,phkResult);
            return Error;
        } 
    } 

    Error = BaseBindToMachine(lpMachineName,
                              Rpc_OpenPredefHandle,
                              (PVOID)hKey,
                              (PVOID)phkResult);

    if( Error == ERROR_SUCCESS) {
        TagRemoteHandle( phkResult );
    }
    return Error;
}

LONG
BaseBindToMachine(
    IN LPCWSTR lpMachineName,
    IN PBIND_CALLBACK BindCallback,
    IN PVOID Context1,
    IN PVOID Context2
    )

/*++

Routine Description:

    This is a helper routine used to create an RPC binding from
    a given machine name.

Arguments:

    lpMachineName - Supplies a pointer to a machine name. Must not
                    be NULL.

    BindCallback - Supplies the function that should be called once
                   a binding has been created to initiate the connection.

    Context1 - Supplies the first parameter to pass to the callback routine.

    Context2 - Supplies the second parameter to pass to the callback routine.

Return Value:

    Returns ERROR_SUCCESS (0) for success; error-code for failure.

--*/

{
    LONG    Error;
    int         i;
    RegConnFunction conn_fn;
    RPC_BINDING_HANDLE binding;

    conn_fn = conn_functions[0];
    i = 1;

    //
    // Iterate through the protocols until we find one that
    // can connect.
    //
    do {
        Error = conn_fn(lpMachineName,&binding);

        if (Error == ERROR_SUCCESS) {

            //
            // For the named pipes protocol, we use a static endpoint, so the
            // call to RpcEpResolveBinding is not needed.
            // Also, the server checks the user's credentials on opening
            // the named pipe, so RpcBindingSetAuthInfo is not needed.
            //
            if (conn_fn != RegConn_np) {
                Error = (LONG)RpcEpResolveBinding(binding,winreg_ClientIfHandle);

                if (Error == ERROR_SUCCESS) {
                    Error = (LONG)RpcBindingSetAuthInfo(binding,
                                            "",     // ServerPrincName
                                            RPC_C_AUTHN_LEVEL_CONNECT,
                                            RPC_C_AUTHN_WINNT,
                                            NULL,   // AuthIdentity
                                            RPC_C_AUTHZ_NONE);
                }
            }
            if (Error == ERROR_SUCCESS) {
                Error = (BindCallback)(&binding,
                                       Context1,
                                       Context2);
                RpcBindingFree(&binding);
                if (Error != RPC_S_SERVER_UNAVAILABLE) {
                    return Error;
                }
            } else {
                RpcBindingFree(&binding);
            }
        }

        //
        // Try the next protocol's connection function.
        //
        if (Error) {
            conn_fn = conn_functions[i];
            i++;
        }

    } while (!((Error == ERROR_SUCCESS) || (conn_fn == NULL)));

    if (Error != ERROR_SUCCESS) {
        if ((Error == RPC_S_INVALID_ENDPOINT_FORMAT) ||
            (Error == RPC_S_INVALID_NET_ADDR) ) {
            Error = ERROR_INVALID_COMPUTERNAME;
        } else {
            Error = ERROR_BAD_NETPATH;
        }
    }

    return(Error);
}

    
LONG
APIENTRY
RegConnectRegistryA (
    LPCSTR lpMachineName,
    HKEY hKey,
    PHKEY phkResult
    )

/*++

Routine Description:

    Win32 ANSI API for establishes a connection to a predefined handle on
    another machine.

    RegConnectRegistryA converts the lpMachineName argument to a Unicode
    string and then calls RegConnectRegistryW.

--*/

{
    UNICODE_STRING     MachineName;
    ANSI_STRING     AnsiString;
    NTSTATUS        Status;
    LONG        Error;

#if DBG
    if ( BreakPointOnEntry ) {
        DbgBreakPoint();
    }
#endif


    //
    // Convert the subkey to a counted Unicode string
    //

    RtlInitAnsiString( &AnsiString, lpMachineName );
    Status = RtlAnsiStringToUnicodeString(&MachineName,
                                          &AnsiString,
                                          TRUE);

    if( ! NT_SUCCESS( Status )) {
        return RtlNtStatusToDosError( Status );
    }

    Error = (LONG)RegConnectRegistryW(MachineName.Buffer,
                                      hKey,
                                      phkResult);
    RtlFreeUnicodeString(&MachineName);
    return Error;

}
