/*++

Copyright (c) 1998, Microsoft Corporation

Module Name:

    sharing.c

Abstract:

    This module contains code for routines which support connection sharing
    configuration.

    Connection sharing involves a public (internet) interface, ordinarily
    a dialup interface identified by phonebook/entry-name, as well as
    a private (home) interface, required to be a lan interface.

    On setting up connection sharing, the service is enabled if necessary,
    and the private lan interface is configured with static address 169.254.0.1
    via the TCP/IP 'SetAdapterIpAddress' API routine.

    The name of the shared connection is stored in the registry along with
    the GUID of the shared private LAN connection, under the registry key
    HKLM\Software\Microsoft\SharedAccess\Parameters.

    N.B. NT registry routines are used, to avoid the hit incurred by
    going through the Win32 server.

Author:

    Abolade Gbadegesin  (aboladeg)  22-Apr-1998

Revision History:

--*/

#include "precomp.h"
#pragma hdrstop
#define _PNP_POWER_
#include <ndispnp.h>
#include <ntddip.h>
#include <winsock2.h>
#include <dhcpcapi.h>
#include <netconp.h>

#if 0

//
// Structure:   CS_ADDRESS_INFORMATION
//

typedef struct _CS_ADDRESS_INFORMATION {
    PKEY_VALUE_PARTIAL_INFORMATION IPAddress;
    PKEY_VALUE_PARTIAL_INFORMATION SubnetMask;
    PKEY_VALUE_PARTIAL_INFORMATION DefaultGateway;
    PKEY_VALUE_PARTIAL_INFORMATION EnableDHCP;
} CS_ADDRESS_INFORMATION, *PCS_ADDRESS_INFORMATION;

//
// DHCPCSVC.DLL import prototype
//

typedef DWORD
(APIENTRY* PDHCPNOTIFYCONFIGCHANGE)(
    LPWSTR,
    LPWSTR,
    BOOL,
    DWORD,
    DWORD,
    DWORD,
    SERVICE_ENABLE
    );

#endif

//
// OLE entrypoints loaded dynamically
//

PCOINITIALIZEEX g_pCoInitializeEx;
PCOUNINITIALIZE g_pCoUninitialize;
PCOCREATEINSTANCE g_pCoCreateInstance;
PCOSETPROXYBLANKET g_pCoSetProxyBlanket;
PCOTASKMEMFREE g_pCoTaskMemFree;

//
// CONSTANT DEFINITIONS
//

#if 0
const CHAR c_szAllocateAndGetIpAddrTableFromStack[] =
    "AllocateAndGetIpAddrTableFromStack";
#endif
const CHAR c_szCoInitializeEx[] = "CoInitializeEx";
const CHAR c_szCoUninitialize[] = "CoUninitialize";
const CHAR c_szCoCreateInstance[] = "CoCreateInstance";
const CHAR c_szCoSetProxyBlanket[] = "CoSetProxyBlanket";
const CHAR c_szCoTaskMemFree[] = "CoTaskMemFree";
#if 0
const CHAR c_szDhcpNotifyConfigChange[] = "DhcpNotifyConfigChange";
const CHAR c_szGetInterfaceInfo[] = "GetInterfaceInfo";
#endif
const CHAR c_szMprConfigBufferFree[] = "MprConfigBufferFree";
const CHAR c_szMprConfigServerConnect[] = "MprConfigServerConnect";
const CHAR c_szMprConfigServerDisconnect[] = "MprConfigServerDisconnect";
const CHAR c_szMprConfigTransportGetHandle[] = "MprConfigTransportGetHandle";
const CHAR c_szMprConfigTransportGetInfo[] = "MprConfigTransportGetInfo";
const CHAR c_szMprInfoBlockFind[] = "MprInfoBlockFind";
#if 0
const CHAR c_szSetAdapterIpAddress[] = "SetAdapterIpAddress";
#endif
const TCHAR c_szSharedAccess[] = TEXT("SharedAccess");
#if 0
const WCHAR c_szBackupDefaultGateway[] = L"BackupDefaultGateway";
const WCHAR c_szBackupEnableDHCP[] = L"BackupEnableDHCP";
const WCHAR c_szBackupIPAddress[] = L"BackupIPAddress";
const WCHAR c_szBackupSubnetMask[] = L"BackupSubnetMask";
const WCHAR c_szDefaultGateway[] = L"DefaultGateway";
const WCHAR c_szDevice[] = L"\\Device\\";
const WCHAR c_szDhcpcsvcDll[] = L"DHCPCSVC.DLL";
const WCHAR c_szEmpty[] = L"";
const WCHAR c_szEnableDHCP[] = L"EnableDHCP";
const WCHAR c_szInterfaces[] = L"Interfaces";
const WCHAR c_szIPAddress[] = L"IPAddress";
const WCHAR c_szIphlpapiDll[] = L"IPHLPAPI.DLL";
#endif
const WCHAR c_szMprapiDll[] = L"MPRAPI.DLL";
const WCHAR c_szMsTcpip[] = L"MS_TCPIP";
const WCHAR c_szOle32Dll[] = L"OLE32.DLL";
#if 0
const WCHAR c_szScopeAddress[] = L"ScopeAddress";
const WCHAR c_szScopeMask[] = L"ScopeMask";
#endif
const WCHAR c_szSharedAccessParametersKey[] =
    L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\SharedAccess"
    L"\\Parameters";
#if 0
const WCHAR c_szSharedConnection[] = L"SharedConnection";
const WCHAR c_szSharedPrivateLan[] = L"SharedPrivateLan";
const WCHAR c_szSubnetMask[] = L"SubnetMask";
const WCHAR c_szTcpip[] = L"Tcpip";
const WCHAR c_szTcpipParametersKey[] =
    L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\Tcpip"
    L"\\Parameters";
const WCHAR c_szFirewallConnection[] = L"FirewallConnection";
const WCHAR c_szFirewallConnectionCount[] = L"FirewallConnectionCount";
#endif

//
// LOCAL VARIABLE DEFINITIONS
//

static BOOLEAN CsInitialized = FALSE;
static CRITICAL_SECTION CsCriticalSection;
static BOOLEAN CsDllMainCalled = FALSE;
static HINSTANCE CsOle32Dll = NULL;

//
// FUNCTION PROTOTYPES
//

#if 0

VOID
CspBackupAddressInformation(
    HANDLE Key,
    PCS_ADDRESS_INFORMATION AddressInformation
    );

NTSTATUS
CspCaptureAddressInformation(
    PWCHAR AdapterGuid,
    PCS_ADDRESS_INFORMATION Information
    );

VOID
CspCleanupAddressInformation(
    PCS_ADDRESS_INFORMATION AddressInformation
    );

NTSTATUS
CspRestoreAddressInformation(
    HANDLE Key,
    PWCHAR AdapterGuid
    );

BOOLEAN
CspIsConnectionFwWorker(
    LPRASSHARECONN ConnectionArray,
    ULONG Count,
    LPRASSHARECONN Connection,
    ULONG *ConnNumber OUT OPTIONAL
    );

ULONG
CspAddFirewallConnection(
    LPRASSHARECONN Connection,
    ULONG Number
    );

ULONG
CspRemoveFirewallConnection(
    LPRASSHARECONN Connection,
    ULONG Position,
    LPRASSHARECONN ConnectionArray,
    ULONG Count
    );
    
#endif


BOOL
CsDllMain(
    ULONG Reason
    )

/*++

Routine Description:

    This pseudo-entrypoint is invoked by RASAPI32.DLL's DllMain,
    to initialize and shutdown the connection-sharing module.
    Initialization is minimal to keep down the performance hit incurred
    on systems which make no use of the shared-access functionality.

Arguments:

    Reason - indicates whether to initialize or shutdown.

Return Value:

    BOOL - indicates success (TRUE) or failure (FALSE).

--*/

{
    if (Reason == DLL_PROCESS_ATTACH) {
        __try {
            InitializeCriticalSection(&CsCriticalSection);
        } __except(EXCEPTION_EXECUTE_HANDLER) {
            return FALSE;
        }
        CsDllMainCalled = TRUE;
    } else if (Reason == DLL_PROCESS_DETACH) {
        if (!CsDllMainCalled) { return TRUE; }
        __try {
            EnterCriticalSection(&CsCriticalSection);
        } __except(EXCEPTION_EXECUTE_HANDLER) {
            return TRUE;
        }
        CsShutdownModule();
        LeaveCriticalSection(&CsCriticalSection);
        DeleteCriticalSection(&CsCriticalSection);
    }
    return TRUE;
} // DllMain


VOID
CsControlService(
    ULONG ControlCode
    )

/*++

Routine Description:

    This routine is called to send a control-code to the Shared Access service
    if it is active. Control-codes are used to indicate changes to the settings
    for the service; see IPNATHLP.H for a list of private control-codes used
    to indicate configuration changes.

Arguments:

    ControlCode - the control to be sent.

Return Value:

    none.

--*/

{
    SC_HANDLE ScmHandle;
    SC_HANDLE ServiceHandle;
    SERVICE_STATUS ServiceStatus;

    ScmHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    if (ScmHandle) {
        ServiceHandle =
            OpenService(ScmHandle, c_szSharedAccess, SERVICE_ALL_ACCESS);
        if (ServiceHandle) {
            ControlService(ServiceHandle, ControlCode, &ServiceStatus);
            CloseServiceHandle(ServiceHandle);
        }
        CloseServiceHandle(ScmHandle);
    }

} // CsControlService

#if 0


ULONG
CsFirewallConnection(
    LPRASSHARECONN Connection,
    BOOLEAN Enable
    )

/*++

Routine Description:

    This routine is invoked to enable or disable the firewall on a connection.

Arguments:

    Connection - the connection to [un]firewall

    Enable - true if the firewall is to be enabled for this connection,
             false if the firewall is to be disabled
             
Return Value:

    Win32 Error code

--*/

{
    ULONG Count = 0;
    ULONG Position;
    LPRASSHARECONN ConnectionArray;
    DWORD Error;
    BOOLEAN IsFirewalled = FALSE;

    //
    // Query the number of currently firewalled connections, and
    // retrieve the connection array if any exists.
    //

    Error = CsQueryFirewallConnections(NULL, &Count);
    if (Error && Error != ERROR_INSUFFICIENT_BUFFER) {
        return Error;
    }

    if (Count) {
        ConnectionArray =
            (LPRASSHARECONN) Malloc(Count * sizeof(RASSHARECONN));

        if (!ConnectionArray) {
            return ERROR_NOT_ENOUGH_MEMORY;
        }

        Error = CsQueryFirewallConnections(ConnectionArray, &Count);
        if (Error) {
            Free(ConnectionArray);
            return Error;
        }
    } else {
        ConnectionArray = NULL;
    }

    //
    // If there are firewalled connections, check to see if the connection
    // passed in is one of them.
    //

    if (Count) {
        IsFirewalled = CspIsConnectionFwWorker(
                            ConnectionArray,
                            Count,
                            Connection,
                            &Position
                            );
    }

    if (Enable) {
    
        if (!IsFirewalled) {
            Error = CspAddFirewallConnection(
                        Connection,
                        Count
                        );

            if(ERROR_SUCCESS == Error) {
            
                //
                // Start (if needed) and update service. If the service
                // is already running, CsStartService returns ERROR_SUCCESS.
                //

                if (0 == Count) {
                    Error = CsStartService();
                }
                CsControlService(IPNATHLP_CONTROL_UPDATE_CONNECTION);
            }
            
        } else {

            //
            // Define ALREADY_ENABLED error?
            //
            
            Error = ERROR_CAN_NOT_COMPLETE;
        }

    } else {
    
        if (IsFirewalled) {
            Error = CspRemoveFirewallConnection(
                        Connection,
                        Position,
                        ConnectionArray,
                        Count
                        );

            if (ERROR_SUCCESS == Error) {
            
                //
                // Stop or update service. We only stop the service if
                // there is no shared connection, and this was the last
                // firewalled connection (i.e., count was 10
                //
                
                RASSHARECONN SharedConn;
                Error = CsQuerySharedConnection(&SharedConn);

                if (ERROR_SUCCESS != Error && 1 == Count) { 
                    CsStopService();
                } else {
                    CsControlService(IPNATHLP_CONTROL_UPDATE_CONNECTION);
                }
                Error = ERROR_SUCCESS;
            }

        } else {

            //
            // Define NOT_FIREWALLED error?
            //
            
            Error = ERROR_CAN_NOT_COMPLETE;
        }
    }

    if (ConnectionArray) {
        Free(ConnectionArray);
    }

    return Error;
} // CsFirewallConnection

#endif


ULONG
CsInitializeModule(
    VOID
    )

/*++

Routine Description:

    This routine is called to initialize the connection-sharing configuration
    module. Initialization consists of loading the entrypoints which we have
    deferred loading up till now, in both MPRAPI.DLL and OLE32.DLL.

Arguments:

    Instance - handle to the module-instance

Return Value:

    ULONG - Win32 status code.

--*/

{
    ULONG Error;
    HINSTANCE Hinstance;
    EnterCriticalSection(&CsCriticalSection);
    if (CsInitialized) {
        Error = NO_ERROR;
    } else {
        if (!(CsOle32Dll = LoadLibraryW(c_szOle32Dll)) ||
            !(g_pCoInitializeEx =
                (PCOINITIALIZEEX)GetProcAddress(
                    CsOle32Dll, c_szCoInitializeEx
                    )) ||
            !(g_pCoUninitialize =
                (PCOUNINITIALIZE)GetProcAddress(
                    CsOle32Dll, c_szCoUninitialize
                    )) ||
            !(g_pCoCreateInstance =
                (PCOCREATEINSTANCE)GetProcAddress(
                    CsOle32Dll, c_szCoCreateInstance
                    )) ||
            !(g_pCoSetProxyBlanket =
                (PCOSETPROXYBLANKET)GetProcAddress(
                    CsOle32Dll, c_szCoSetProxyBlanket
                    )) ||
            !(g_pCoTaskMemFree =
                (PCOTASKMEMFREE)GetProcAddress(
                    CsOle32Dll, c_szCoTaskMemFree
                    ))) {
            if (CsOle32Dll) { FreeLibrary(CsOle32Dll); CsOle32Dll = NULL; }
            TRACE1("CsInitializeModule: %d", GetLastError());
            Error = ERROR_PROC_NOT_FOUND;
        } else {
            CsInitialized = TRUE;
            Error = NO_ERROR;
        }
    }
    LeaveCriticalSection(&CsCriticalSection);
    return Error;

} // CsInitializeModule

#if 0


ULONG
CsIsFirewalledConnection(
    LPRASSHARECONN Connection,
    PBOOLEAN Firewalled
    )

/*++

Routine Description:

    This routine is invoked to determine if a connection is firewalled

Arguments:

    Connection - the connection to check

    Firewalled - receives the return value

Return Value:

    ULONG - win32 error
    
--*/

{
    ULONG Count = 0;
    LPRASSHARECONN ConnectionArray;
    ULONG Error;

    if (!Firewalled) {
        return ERROR_INVALID_PARAMETER;
    }
    *Firewalled = FALSE;

    Error = CsQueryFirewallConnections(NULL, &Count);
    if (Error && Error != ERROR_INSUFFICIENT_BUFFER) {
        return Error;
    }

    ConnectionArray =
        (LPRASSHARECONN) Malloc(Count * sizeof(RASSHARECONN));

    if (!ConnectionArray) {
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    Error = CsQueryFirewallConnections(ConnectionArray, &Count);
    if (Error) {
        Free(ConnectionArray);
        return Error;
    }

    *Firewalled = CspIsConnectionFwWorker(ConnectionArray, Count, Connection, NULL);
    Free(ConnectionArray);
    return NO_ERROR;
} // CsIsConnectionFirewalled


BOOLEAN
CsIsRoutingProtocolInstalled(
    ULONG ProtocolId
    )

/*++

Routine Description:

    This routine is invoked to determine whether the routing protocol
    with the given protocol-ID is installed for Routing and Remote Access.
    This is determined by examining the configuration for the service.

Arguments:

    ProtocolId - identifies the protocol to be found

Return Value:

    TRUE if the protocol is installed, FALSE otherwise.

--*/

{
    PUCHAR Buffer;
    ULONG BufferLength;
    HINSTANCE Hinstance;
    PMPRCONFIGBUFFERFREE MprConfigBufferFree;
    PMPRCONFIGSERVERCONNECT MprConfigServerConnect;
    PMPRCONFIGSERVERDISCONNECT MprConfigServerDisconnect;
    PMPRCONFIGTRANSPORTGETHANDLE MprConfigTransportGetHandle;
    PMPRCONFIGTRANSPORTGETINFO MprConfigTransportGetInfo;
    PMPRINFOBLOCKFIND MprInfoBlockFind;
    HANDLE ServerHandle;
    HANDLE TransportHandle;

    //
    // Load the MPRAPI.DLL module and retrieve the entrypoints
    // to be used for examining the RRAS configuration.
    //

    if (!(Hinstance = LoadLibraryW(c_szMprapiDll)) ||
        !(MprConfigBufferFree = 
            (PMPRCONFIGBUFFERFREE)
                GetProcAddress(Hinstance, c_szMprConfigBufferFree)) ||
        !(MprConfigServerConnect = 
            (PMPRCONFIGSERVERCONNECT)
                GetProcAddress(Hinstance, c_szMprConfigServerConnect)) ||
        !(MprConfigServerDisconnect = 
            (PMPRCONFIGSERVERDISCONNECT)
                GetProcAddress(Hinstance, c_szMprConfigServerDisconnect)) ||
        !(MprConfigTransportGetHandle = 
            (PMPRCONFIGTRANSPORTGETHANDLE)
                GetProcAddress(Hinstance, c_szMprConfigTransportGetHandle)) ||
        !(MprConfigTransportGetInfo = 
            (PMPRCONFIGTRANSPORTGETINFO)
                GetProcAddress(Hinstance, c_szMprConfigTransportGetInfo)) ||
        !(MprInfoBlockFind = 
            (PMPRINFOBLOCKFIND)
                GetProcAddress(Hinstance, c_szMprInfoBlockFind))) {
        if (Hinstance) { FreeLibrary(Hinstance); }
        return FALSE;
    }

    //
    // Connect to the RRAS configuration, and retrieve the configuration
    // for the IP transport-layer routing protocols. This should include
    // the configuration for the routing-protocol in 'ProtocolId',
    // if installed.
    //

    ServerHandle = NULL;
    if (MprConfigServerConnect(NULL, &ServerHandle) != NO_ERROR ||
        MprConfigTransportGetHandle(ServerHandle, PID_IP, &TransportHandle)
            != NO_ERROR ||
        MprConfigTransportGetInfo(
            ServerHandle,
            TransportHandle,
            &Buffer,
            &BufferLength, 
            NULL,
            NULL,
            NULL
            ) != NO_ERROR) {
        if (ServerHandle) { MprConfigServerDisconnect(ServerHandle); }
        FreeLibrary(Hinstance);
        return FALSE;
    }

    MprConfigServerDisconnect(ServerHandle);

    //
    // Look for the requested protocol's configuration,
    // and return TRUE if it is found; otherwise, return FALSE.
    //

    if (MprInfoBlockFind(Buffer, ProtocolId, NULL, NULL, NULL) == NO_ERROR) {
        MprConfigBufferFree(Buffer);
        FreeLibrary(Hinstance);
        return TRUE;
    }
    MprConfigBufferFree(Buffer);
    FreeLibrary(Hinstance);
    return FALSE;
} // CsIsRoutingProtocolInstalled

#endif


ULONG
CsIsSharedConnection(
    LPRASSHARECONN Connection,
    PBOOLEAN Shared
    )

/*++

Routine Description:

    This routine is invoked to determine whether the given connection
    is the currently-shared connection.

    For added performance, this may be changed to cache the shared-connection
    and use registry change-notification to detect updates.

Arguments:

    Connection - the connection in question

    Shared - receives 'TRUE' if the 'Name' is the shared connection,
        and 'FALSE' otherwise

Return Value:

    ULONG - Win32 status code.

Environment:

    This routine is called *without* initializing the module
    (i.e. loading mprapi.dll and ole32.dll), for performance reasons.
    Hence, it may not invoke any mprapi.dll routines.

--*/

{
    ULONG Error;
    RASSHARECONN SharedConnection;
    if (Shared) {
        Error = CsQuerySharedConnection(&SharedConnection);
        if (Error) {
            *Shared = FALSE;
        } else {
            *Shared = RasIsEqualSharedConnection(Connection, &SharedConnection);
        }
    }
    return NO_ERROR;
} // CsIsSharedConnection

#if 0


ULONG
CsMapGuidToAdapterIndex(
    PWCHAR Guid,
    PGETINTERFACEINFO GetInterfaceInfo
    )

/*++

Routine Description:

    This routine is called to match the GUID in the given string to
    an adapter in the list returned by calling the given entrypoint.

Arguments:

    Guid - identifies the GUID of the adapter to be found

    GetInterfaceInfo - supplies GUID information for each adapter

Return Value:

    ULONG - the index of the adapter, if found; otherwise, -1.

--*/

{
    ULONG AdapterIndex = (ULONG)-1;
    ULONG i;
    ULONG GuidLength;
    PIP_INTERFACE_INFO Info;
    PWCHAR Name;
    ULONG NameLength;
    ULONG Size;
    Size = 0;
    GuidLength = lstrlenW(Guid);
    if (GetInterfaceInfo(NULL, &Size) == ERROR_INSUFFICIENT_BUFFER) {
        Info = Malloc(Size);
        if (Info) {
            if (GetInterfaceInfo(Info, &Size) == NO_ERROR) {
                for (i = 0; i < (ULONG)Info->NumAdapters; i++) {
                    NameLength = lstrlenW(Info->Adapter[i].Name);
                    if (NameLength < GuidLength) { continue; }
                    Name = Info->Adapter[i].Name + (NameLength - GuidLength);
                    if (lstrcmpiW(Guid, Name) == 0) {
                        AdapterIndex = Info->Adapter[i].Index;
                        break;
                    }
                }
            }
            Free(Info);
        }
    }
    return AdapterIndex;
} // CsMapGuidToAdapter

#endif


NTSTATUS
CsOpenKey(
    PHANDLE Key,
    ACCESS_MASK DesiredAccess,
    PCWSTR Name
    )

/*++

Routine Description:

    This routine is invoked to open a given registry key.

Arguments:

    Key - receives the opened key

    DesiredAccess - specifies the requested access

    Name - specifies the key to be opened

Return Value:

    NTSTATUS - NT status code.

--*/

{
    OBJECT_ATTRIBUTES ObjectAttributes;
    UNICODE_STRING UnicodeString;
    RtlInitUnicodeString(&UnicodeString, Name);
    InitializeObjectAttributes(
        &ObjectAttributes,
        &UnicodeString,
        OBJ_CASE_INSENSITIVE,
        NULL,
        NULL
        );
    return NtOpenKey(Key, DesiredAccess, &ObjectAttributes);
} // CsOpenKey

#if 0


ULONG
CspAddFirewallConnection(
    LPRASSHARECONN Connection,
    ULONG Number
    )

/*++

Routine Description:

    This routine is invoked to add a connection to the registry set.

Arguments:

    Connection - the connection to add

    Number - the number of this connection
    
Return Value:

    Win32 error code

--*/

{
    HANDLE Key;
    UNICODE_STRING ValueName;
    ULONG Count;
    NTSTATUS Status;

    //
    // +11 is enough room to hold the digits of a number >4,000,000,000, so
    // buffer overflow won't be an issue below.
    //
    
    WCHAR wsz[sizeof(c_szFirewallConnection)/sizeof(WCHAR) + 11];

    //
    // Open the key to SharedAccess/Parameters
    //

    Status = CsOpenKey(&Key, KEY_ALL_ACCESS, c_szSharedAccessParametersKey);
    if (!NT_SUCCESS(Status)) {
        return RtlNtStatusToDosError(Status);
    }

    //
    // Generate the string for the connection value
    //

    swprintf(wsz, L"%s%u", c_szFirewallConnection, Number);
    RtlInitUnicodeString(&ValueName, wsz);

    //
    // Write the connection to the registry
    //

    Status = NtSetValueKey(
                Key,
                &ValueName,
                0,
                REG_BINARY,
                Connection,
                Connection->dwSize
                );

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

    //
    // Write the updated count to the registry
    //

    RtlInitUnicodeString(&ValueName, c_szFirewallConnectionCount);
    Count = Number + 1; // number is 0 indexed

    Status = NtSetValueKey(
                Key,
                &ValueName,
                0,
                REG_DWORD,
                &Count,
                sizeof(DWORD)
                );

    NtClose(Key);
    return RtlNtStatusToDosError(Status);
} // CspAddFirewallConnection


VOID
CspBackupAddressInformation(
    HANDLE Key,
    PCS_ADDRESS_INFORMATION Information
    )
{
    NTSTATUS status;
    UNICODE_STRING UnicodeString;
    do {
        RtlInitUnicodeString(&UnicodeString, c_szBackupIPAddress);
        status =
            NtSetValueKey(
                Key,
                &UnicodeString,
                0,
                Information->IPAddress->Type,
                Information->IPAddress->Data,
                Information->IPAddress->DataLength
                );
        if (!NT_SUCCESS(status)) { break; }
        RtlInitUnicodeString(&UnicodeString, c_szBackupSubnetMask);
        status =
            NtSetValueKey(
                Key,
                &UnicodeString,
                0,
                Information->SubnetMask->Type,
                Information->SubnetMask->Data,
                Information->SubnetMask->DataLength
                );
        if (!NT_SUCCESS(status)) { break; }
        RtlInitUnicodeString(&UnicodeString, c_szBackupDefaultGateway);
        status =
            NtSetValueKey(
                Key,
                &UnicodeString,
                0,
                Information->DefaultGateway->Type,
                Information->DefaultGateway->Data,
                Information->DefaultGateway->DataLength
                );
        if (!NT_SUCCESS(status)) { break; }
        RtlInitUnicodeString(&UnicodeString, c_szBackupEnableDHCP);
        status =
            NtSetValueKey(
                Key,
                &UnicodeString,
                0,
                Information->EnableDHCP->Type,
                Information->EnableDHCP->Data,
                Information->EnableDHCP->DataLength
                );
        if (!NT_SUCCESS(status)) { break; }
        return;
    } while(FALSE);
    RtlInitUnicodeString(&UnicodeString, c_szBackupIPAddress);
    NtDeleteValueKey(Key, &UnicodeString);
    RtlInitUnicodeString(&UnicodeString, c_szBackupSubnetMask);
    NtDeleteValueKey(Key, &UnicodeString);
    RtlInitUnicodeString(&UnicodeString, c_szBackupDefaultGateway);
    NtDeleteValueKey(Key, &UnicodeString);
    RtlInitUnicodeString(&UnicodeString, c_szBackupEnableDHCP);
    NtDeleteValueKey(Key, &UnicodeString);
} // CspBackupAddressInformation


NTSTATUS
CspCaptureAddressInformation(
    PWCHAR AdapterGuid,
    PCS_ADDRESS_INFORMATION Information
    )
{
    HANDLE Key;
    PWCHAR KeyName;
    ULONG KeyNameLength;
    NTSTATUS status;

    KeyNameLength =
        sizeof(WCHAR) *
        (lstrlenW(c_szTcpipParametersKey) + 1 +
         lstrlenW(c_szInterfaces) + 1 +
         lstrlenW(AdapterGuid) + 2);
    if (!(KeyName = Malloc(KeyNameLength))) { return STATUS_NO_MEMORY; }

    wsprintfW(
        KeyName, L"%ls\\%ls\\%ls", c_szTcpipParametersKey, c_szInterfaces,
        AdapterGuid
        );
    status = CsOpenKey(&Key, KEY_READ, KeyName);
    Free(KeyName);
    if (!NT_SUCCESS(status)) { return status; }

    do {
        status =
            CsQueryValueKey(
                Key, c_szIPAddress, &Information->IPAddress
                );
        if (!NT_SUCCESS(status)) { break; }
        status =
            CsQueryValueKey(
                Key, c_szSubnetMask, &Information->SubnetMask
                );
        if (!NT_SUCCESS(status)) { break; }
        status =
            CsQueryValueKey(
                Key, c_szDefaultGateway, &Information->DefaultGateway
                );
        if (!NT_SUCCESS(status)) { break; }
        status =
            CsQueryValueKey(
                Key, c_szEnableDHCP, &Information->EnableDHCP
                );
        if (!NT_SUCCESS(status)) { break; }
    } while(FALSE);

    NtClose(Key);
    return status;
} // CspCaptureAddressInformation


VOID
CspCleanupAddressInformation(
    PCS_ADDRESS_INFORMATION Information
    )
{
    Free0(Information->IPAddress);
    Free0(Information->SubnetMask);
    Free0(Information->DefaultGateway);
    Free0(Information->EnableDHCP);
} // CspCleanupAddressInformation


ULONG
CspRemoveFirewallConnection(
    LPRASSHARECONN Connection,
    ULONG Position,
    LPRASSHARECONN ConnectionArray,
    ULONG Count
    )

/*++

Routine Description:

    This routine is invoked to remove a connection to the registry set.

Arguments:

    Connection - the connection to remove

    Number - its index in ConnectionArray

    ConnectionArray - currently firewalled connections

    Count - the number of entries in ConnectionArray
    
Return Value:

    Win32 error code

--*/

{
    HANDLE Key;
    UNICODE_STRING ValueName;
    ULONG i;
    NTSTATUS Status;

    //
    // +11 is enough room to hold the digits of a number >4,000,000,000, so
    // buffer overflow won't be an issue below.
    //
    
    WCHAR wsz[sizeof(c_szFirewallConnection)/sizeof(WCHAR) + 11];

    //
    // Open the key to IPFirewall/Parameters
    //

    Status = CsOpenKey(&Key, KEY_ALL_ACCESS, c_szSharedAccessParametersKey);
    if (!NT_SUCCESS(Status)) {
        return RtlNtStatusToDosError(Status);
    }

    //
    // Shift entries above the connection we're removing down one
    // (overwriting the entry we want to remove)
    //

    for (i = Position + 1; i < Count; i++) {
    
        //
        // Generate key name for previous entry
        //

        swprintf(wsz, L"%s%u", c_szFirewallConnection, i - 1);
        RtlInitUnicodeString(&ValueName, wsz);

        //
        // Write current entry into previous slot
        //

        Status = NtSetValueKey(
                Key,
                &ValueName,
                0,
                REG_BINARY,
                &ConnectionArray[i],
                ConnectionArray[i].dwSize
                );

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

    //
    // Delete the last entry. This is either the entry we want to
    // remove (if it was the last entry to begin with), or an entry
    // that has already been duplicated into the previous position.
    //

    swprintf(wsz, L"%s%u", c_szFirewallConnection, Count - 1);
    RtlInitUnicodeString(&ValueName, wsz);

    Status = NtDeleteValueKey(Key, &ValueName);

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


    //
    // Store the decremented count in the registry
    //

    RtlInitUnicodeString(&ValueName, c_szFirewallConnectionCount);
    i = Count - 1;

    Status = NtSetValueKey(
                Key,
                &ValueName,
                0,
                REG_DWORD,
                &i,
                sizeof(DWORD)
                );


    NtClose(Key);
    return RtlNtStatusToDosError(Status);
} // CspRemoveFirewallConnection



ULONG
CsQueryFirewallConnections(
    LPRASSHARECONN ConnectionArray,
    ULONG *ConnectionCount
    )

/*++

Routine Description:

    This routine is invoked to retrieve the firewalled connections, if any.

Arguments:

    ConnectionArray - receives the retrieved connections.

    ConnectionCount - in: how many entries the array can hold
                       out: number of entries returned, or the needed
                            size of the array (for ERROR_INSUFFICIENT_BUFFER)

Return Value:

    ULONG - Win32 status code.

--*/

{
    HANDLE Key;
    PKEY_VALUE_PARTIAL_INFORMATION Information;
    NTSTATUS Status;
    ULONG Count;
    ULONG i;

    if (!ConnectionCount) { return ERROR_INVALID_PARAMETER; }
    if (*ConnectionCount && !ConnectionArray) {
        //
        // It's OK to pass in NULL for the array if just trying
        // to determine what size buffer to use
        //
        
        return ERROR_INVALID_PARAMETER;
    }

    //
    // Open the 'SharedAccess\Parameters' key,
    // and read the 'FirewallConnectionCount' value
    //

    Status = CsOpenKey(&Key, KEY_READ, c_szSharedAccessParametersKey);
    if (!NT_SUCCESS(Status)) {
        TRACE1(
            "CsQueryFirewallConnections: CsOpenKey=%x", Status
            );
        return RtlNtStatusToDosError(Status);
    }

    Status = CsQueryValueKey(Key, c_szFirewallConnectionCount, &Information);
    if (NT_SUCCESS(Status)) {

        //
        // Validate the information, and check to see if the passed in array
        // is sufficient in size.
        //

        if (Information->DataLength != sizeof(DWORD) ||
            Information->Type != REG_DWORD) {
            
            TRACE(
                "CsQueryFirewallConnections: invalid data in registry for count"
                );
            NtClose(Key);
            Free(Information);
            return ERROR_INVALID_DATA;

        }

        Count = (ULONG) *Information->Data;
        Free(Information);

    } else {
        Count = 0;
    }

    if (*ConnectionCount < Count) {
        //
        // Too many entries for the passed in buffer
        //

        NtClose(Key);
        *ConnectionCount = Count;
        return ERROR_INSUFFICIENT_BUFFER;
    }

    *ConnectionCount = Count;

    //
    // Read all of the connection entries from the registry.
    //

    for(i = 0; i < Count; i++) {
        WCHAR wsz[sizeof(c_szFirewallConnection)/sizeof(WCHAR) + 11];

        swprintf(wsz, L"%s%u", c_szFirewallConnection, i);
        Status = CsQueryValueKey(Key, wsz, &Information);
        if (!NT_SUCCESS(Status)) {
            NtClose(Key);
            return RtlNtStatusToDosError(Status);
        }

        //
        // Validate the retrieved information,
        // and copy it to the given buffer
        //

        if (Information->DataLength != sizeof(RASSHARECONN) ||
            ((LPRASSHARECONN)Information->Data)->dwSize != sizeof(RASSHARECONN)) {

            TRACE2(
                "CsQueryFirewallConnections: invalid length %d (size=%d) in registry",
                Information->DataLength,
                ((LPRASSHARECONN)Information->Data)->dwSize
                );
                
            Free(Information);
            NtClose(Key);
            return ERROR_INVALID_DATA;
        }

        CopyMemory(&ConnectionArray[i], Information->Data, sizeof(RASSHARECONN));
        Free(Information);
    }

    return NO_ERROR;

} // CsQueryFirewallConnections


BOOLEAN
CspIsConnectionFwWorker(
    LPRASSHARECONN ConnectionArray,
    ULONG Count,
    LPRASSHARECONN Connection,
    ULONG *Position OUT OPTIONAL
    )

/*++

Routine Description:

    This routine is invoked to determine if a connection is firewalled

Arguments:

    ConnectionArray - Buffer containing currently FWd connections

    Count - number of connections in the array

    Connection - the connection to check

    Position - receives the number of the connection, if found (undefined otherwise)

Return Value:

    BOOLEAN -- true if the passed in connection is currently firewalled

--*/

{
    ULONG i;

    for (i = 0; i < Count; i++) {
        if (RasIsEqualSharedConnection(Connection, &ConnectionArray[i])) {
            if (Position) *Position = i;
            return TRUE;
        }
    }

    return FALSE;
} // FwpIsConnectionFwWorker


NTSTATUS
CspRestoreAddressInformation(
    HANDLE Key,
    PWCHAR AdapterGuid
    )
{
    HANDLE AdapterKey = NULL;
    PWCHAR AdapterKeyName = NULL;
    PDHCPNOTIFYCONFIGCHANGE DhcpNotifyConfigChange;
    ULONG Error;
    HINSTANCE Hinstance;
    CS_ADDRESS_INFORMATION Information;
    ULONG Length;
    NTSTATUS status;
    UNICODE_STRING UnicodeString;

    if (!(Hinstance = LoadLibraryW(c_szDhcpcsvcDll)) ||
        !(DhcpNotifyConfigChange =
            (PDHCPNOTIFYCONFIGCHANGE)
                GetProcAddress(
                    Hinstance, c_szDhcpNotifyConfigChange
                    ))) {
        if (Hinstance) { FreeLibrary(Hinstance); }
        return ERROR_PROC_NOT_FOUND;
    }

    do {

        ZeroMemory(&Information, sizeof(Information));
        status =
            CsQueryValueKey(
                Key, c_szBackupIPAddress, &Information.IPAddress
                );
        if (!NT_SUCCESS(status)) { break; }
        status =
            CsQueryValueKey(
                Key, c_szBackupSubnetMask, &Information.SubnetMask
                );
        if (!NT_SUCCESS(status)) { break; }
        status =
            CsQueryValueKey(
                Key, c_szBackupDefaultGateway, &Information.DefaultGateway
                );
        if (!NT_SUCCESS(status)) { break; }
        status =
            CsQueryValueKey(
                Key, c_szBackupEnableDHCP, &Information.EnableDHCP
                );
        if (!NT_SUCCESS(status)) { break; }

        Length =
            sizeof(WCHAR) *
            (lstrlenW(c_szTcpipParametersKey) + 1 +
             lstrlenW(c_szInterfaces) + 1 +
             lstrlenW(AdapterGuid) + 2);
        if (!(AdapterKeyName = Malloc(Length))) {
            status = STATUS_NO_MEMORY;
            break;
        }

        wsprintfW(
            AdapterKeyName, L"%ls\\%ls\\%ls", c_szTcpipParametersKey,
            c_szInterfaces, AdapterGuid
            );
        status = CsOpenKey(&AdapterKey, KEY_ALL_ACCESS, AdapterKeyName);
        if (!NT_SUCCESS(status)) { break; }

        RtlInitUnicodeString(&UnicodeString, c_szIPAddress);
        status =
            NtSetValueKey(
                AdapterKey,
                &UnicodeString,
                0,
                Information.IPAddress->Type,
                Information.IPAddress->Data,
                Information.IPAddress->DataLength
                );
        RtlInitUnicodeString(&UnicodeString, c_szSubnetMask);
        status =
            NtSetValueKey(
                AdapterKey,
                &UnicodeString,
                0,
                Information.SubnetMask->Type,
                Information.SubnetMask->Data,
                Information.SubnetMask->DataLength
                );
        RtlInitUnicodeString(&UnicodeString, c_szDefaultGateway);
        status =
            NtSetValueKey(
                AdapterKey,
                &UnicodeString,
                0,
                Information.DefaultGateway->Type,
                Information.DefaultGateway->Data,
                Information.DefaultGateway->DataLength
                );
        RtlInitUnicodeString(&UnicodeString, c_szEnableDHCP);
        status =
            NtSetValueKey(
                AdapterKey,
                &UnicodeString,
                0,
                Information.EnableDHCP->Type,
                Information.EnableDHCP->Data,
                Information.EnableDHCP->DataLength
                );
        if (!NT_SUCCESS(status)) { break; }

        RtlInitUnicodeString(&UnicodeString, c_szBackupIPAddress);
        NtDeleteValueKey(Key, &UnicodeString);
        RtlInitUnicodeString(&UnicodeString, c_szBackupSubnetMask);
        NtDeleteValueKey(Key, &UnicodeString);
        RtlInitUnicodeString(&UnicodeString, c_szBackupDefaultGateway);
        NtDeleteValueKey(Key, &UnicodeString);
        RtlInitUnicodeString(&UnicodeString, c_szBackupEnableDHCP);
        NtDeleteValueKey(Key, &UnicodeString);

        if (*(PULONG)Information.EnableDHCP->Data) {
            Error =
                DhcpNotifyConfigChange(
                    NULL,
                    AdapterGuid,
                    FALSE,
                    0,
                    0,
                    0,
                    DhcpEnable
                    );
        } else {

            ULONG Address;
            UNICODE_STRING BindList;
            UNICODE_STRING LowerComponent;
            ULONG Mask;
            IP_PNP_RECONFIG_REQUEST Request;
            UNICODE_STRING UpperComponent;

            Address = IpPszToHostAddr((PWCHAR)Information.IPAddress->Data);
            if (Address) {
                Address = RtlUlongByteSwap(Address);
                Mask = IpPszToHostAddr((PWCHAR)Information.SubnetMask->Data);
                if (Mask) {
                    Mask = RtlUlongByteSwap(Mask);
                    Error =
                        DhcpNotifyConfigChange(
                            NULL,
                            AdapterGuid,
                            TRUE,
                            0,
                            Address,
                            Mask,
                            DhcpDisable
                            );
                }
            }

            RtlInitUnicodeString(&BindList, c_szEmpty);
            RtlInitUnicodeString(&LowerComponent, c_szEmpty);
            RtlInitUnicodeString(&UpperComponent, c_szTcpip);
            ZeroMemory(&Request, sizeof(Request));
            Request.version = IP_PNP_RECONFIG_VERSION;
            Request.gatewayListUpdate = TRUE;
            Request.Flags = IP_PNP_FLAG_GATEWAY_LIST_UPDATE;
            status =
                NdisHandlePnPEvent(
                    NDIS,
                    RECONFIGURE,
                    &LowerComponent,
                    &UpperComponent,
                    &BindList,
                    &Request,
                    sizeof(Request)
                    );
        }
    } while(FALSE);
    if (AdapterKey) { NtClose(AdapterKey); }
    Free0(AdapterKeyName);
    CspCleanupAddressInformation(&Information);
    FreeLibrary(Hinstance);
    return status;
} // CspRestoreAddressInformation


ULONG
CsQueryLanConnTable(
    LPRASSHARECONN ExcludedConnection,
    NETCON_PROPERTIES** LanConnTable,
    LPDWORD LanConnCount
    )

/*++

Routine Description:

    This routine is invoked to retrieve an array of LAN connections,
    discounting 'ExcludeConnection', which will typically be the name
    of the public interface.

Arguments:

    ExcludeConnection - a connection not allowed to be the private connection

    LanConnTable - optionally receives a table of possible private networks.

    LanConnCount - receives a count of the possible private networks.

Return Value:

    ULONG - Win32 status code.

--*/

{
    BOOLEAN CleanupOle = TRUE;
    INetConnection* ConArray[32];
    ULONG ConCount;
    INetConnectionManager* ConMan = NULL;
    IEnumNetConnection* EnumCon = NULL;
    ULONG Error;
    HRESULT hr;
    ULONG i;
    ULONG j;
    ULONG LanCount = 0;
    NETCON_PROPERTIES* LanProps = NULL;
    NETCON_PROPERTIES* LanTable = NULL;
    BSTR Name;
    NETCON_STATUS ncs;
    NTSTATUS status;
    ULONG Size;
    NETCON_MEDIATYPE MediaType;
    UNICODE_STRING UnicodeString;

    *LanConnCount = 0;
    if (LanConnTable) { *LanConnTable = NULL; }

    hr = g_pCoInitializeEx(NULL, COINIT_MULTITHREADED|COINIT_DISABLE_OLE1DDE);
    if (!SUCCEEDED(hr)) {
        if (hr == RPC_E_CHANGED_MODE) {
            CleanupOle = FALSE;
        } else {
            TRACE1("CsQueryLanConnTable: CoInitializeEx=%x", hr);
            return ERROR_CAN_NOT_COMPLETE;
        }
    }

    i = 0;
    Error = NO_ERROR;

    do {

        //
        // Instantiate the connection manager
        //

        hr =
            g_pCoCreateInstance(
                &CLSID_ConnectionManager,
                NULL,
                CLSCTX_SERVER,
                &IID_INetConnectionManager,
                (PVOID*)&ConMan
                );
        if (!SUCCEEDED(hr)) {
            TRACE1("CsQueryLanConnTable: CoCreateInstance=%x", hr);
            ConMan = NULL; break;
        }

        //
        // Instantiate a connection-enumerator
        //

        hr =
            INetConnectionManager_EnumConnections(
                ConMan,
                NCME_DEFAULT,
                &EnumCon
                );
        if (!SUCCEEDED(hr)) {
            TRACE1("CsQueryLanConnTable: EnumConnections=%x", hr);
            EnumCon = NULL; break;
        }

        hr =
            g_pCoSetProxyBlanket(
                (IUnknown*)EnumCon,
                RPC_C_AUTHN_WINNT,
                RPC_C_AUTHN_NONE,
                NULL,
                RPC_C_AUTHN_LEVEL_CALL,
                RPC_C_IMP_LEVEL_IMPERSONATE,
                NULL,
                EOAC_NONE
                );

        //
        // Enumerate the items
        //

        for ( ; ; ) {

            hr =
                IEnumNetConnection_Next(
                    EnumCon,
                    Dimension(ConArray),
                    ConArray,
                    &ConCount
                    );
            if (!SUCCEEDED(hr) || !ConCount) { hr = S_OK; break; }

            if (LanConnTable) {

                //
                // Allocate or reallocate the memory for storing
                // connections which we will return to the caller.
                //

                if (!LanTable) {
                    LanTable =
                        (NETCON_PROPERTIES*)
                            GlobalAlloc(
                                0,
                                ConCount * sizeof(NETCON_PROPERTIES)
                                );
                } else {
                    PVOID Temp =
                        GlobalAlloc(
                            0,
                            (LanCount + ConCount) * sizeof(NETCON_PROPERTIES)
                            );
                    if (Temp) {
                        CopyMemory(
                            Temp,
                            LanTable,
                            LanCount * sizeof(NETCON_PROPERTIES)
                            );
                    }
                    GlobalFree(LanTable);
                    LanTable = Temp;
                }

                if (!LanTable) { Error = ERROR_NOT_ENOUGH_MEMORY; break; }
            }

            LanCount += ConCount;

            //
            // Examine the properties for the connections retrieved
            //

            for (j = 0; j < ConCount; j++) {

                hr = INetConnection_GetProperties(ConArray[j], &LanProps);
                INetConnection_Release(ConArray[j]);

                if (SUCCEEDED(hr) &&
                    LanProps->MediaType == NCM_LAN &&
                    (!ExcludedConnection->fIsLanConnection ||
                     !IsEqualGUID(
                        &ExcludedConnection->guid, &LanProps->guidId))) {

                    //
                    // This connection qualifies to be private; copy it.
                    //

                    if (!LanConnTable) {
                        ++i;
                    } else {
                        LanTable[i] = *LanProps;
                        LanTable[i].pszwName = StrDupW(LanProps->pszwName);
                        LanTable[i].pszwDeviceName =
                            StrDupW(LanProps->pszwDeviceName);
                        if (LanTable[i].pszwName &&
                            LanTable[i].pszwDeviceName
                            ) {
                            ++i;
                        } else {
                            Free0(LanTable[i].pszwName);
                            Free0(LanTable[i].pszwDeviceName);
                        }
                    }
                }

                if (LanProps) {
                    g_pCoTaskMemFree(LanProps->pszwName);
                    g_pCoTaskMemFree(LanProps->pszwDeviceName);
                    g_pCoTaskMemFree(LanProps);
                    LanProps = NULL;
                }
            }
        }

    } while (FALSE);

    if (EnumCon) { IEnumNetConnection_Release(EnumCon); }
    if (ConMan) { INetConnectionManager_Release(ConMan); }
    if (CleanupOle) { g_pCoUninitialize(); }

    if (LanConnTable) { *LanConnTable = LanTable; }
    *LanConnCount = i;

    return Error;

} // CsQueryLanConnTable


VOID
CsQueryScopeInformation(
    IN OUT PHANDLE Key,
    PULONG Address,
    PULONG Mask
    )

/*++

Routine Description:

    This routine is called to retrieve the private network address and mask
    to be used for shared access. If no value is found, the default
    is supplied.

Arguments:

    Key - optionally supplies an open handle to the SharedAccess\Parameters
        registry key

    Address - receives the address for the private network,
        in network byte order

    Mask - receives the mask for the private network, in network byte order

Return Value:

    none.

--*/

{
    PKEY_VALUE_PARTIAL_INFORMATION Information;
    HANDLE LocalKey = NULL;
    NTSTATUS status;

    if (!Key) { Key = &LocalKey; }
    if (*Key) {
        status = STATUS_SUCCESS;
    } else {
        status = CsOpenKey(Key, KEY_ALL_ACCESS, c_szSharedAccessParametersKey);
    }

    if (NT_SUCCESS(status)) {
        status = CsQueryValueKey(*Key, c_szScopeAddress, &Information);
        if (NT_SUCCESS(status)) {
            if (!(*Address = IpPszToHostAddr((PWCHAR)Information->Data))) {
                Free(Information);
            } else {
                Free(Information);
                status = CsQueryValueKey(*Key, c_szScopeMask, &Information);
                if (NT_SUCCESS(status)) {
                    if (!(*Mask = IpPszToHostAddr((PWCHAR)Information->Data))) {
                        Free(Information);
                    } else {
                        Free(Information);
                        *Address = RtlUlongByteSwap(*Address);
                        *Mask = RtlUlongByteSwap(*Mask);
                        if (LocalKey) { NtClose(LocalKey); }
                        return;
                    }
                }
            }
        }
    }

    *Address = DEFAULT_SCOPE_ADDRESS;
    *Mask = DEFAULT_SCOPE_MASK;
    if (LocalKey) { NtClose(LocalKey); }

} // CsQueryScopeInformation

#endif


ULONG
CsQuerySharedConnection(
    LPRASSHARECONN Connection
    )

/*++

Routine Description:

    This routine is invoked to retrieve the shared connection, if any.

Arguments:

    Connection - receives the retrieved connection.

Return Value:

    ULONG - Win32 status code.

--*/

{
    BOOL fUninitializeCOM = TRUE;
    IHNetIcsSettings *pIcsSettings;
    IEnumHNetIcsPublicConnections *pEnumIcsPub;
    IHNetIcsPublicConnection *pIcsPub;
    IHNetConnection *pConn;
    ULONG ulCount;
    HRESULT hr;
    
    ASSERT(NULL != g_pCoInitializeEx);
    ASSERT(NULL != g_pCoCreateInstance);
    ASSERT(NULL != g_pCoUninitialize);

    if (!Connection) { return ERROR_INVALID_PARAMETER; }
    
    hr = g_pCoInitializeEx(
            NULL,
            COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE
            );

    if (FAILED(hr))
    {
        fUninitializeCOM = FALSE;
                
        if(RPC_E_CHANGED_MODE == hr)
        {
            hr = S_OK;    
        }
    }
    
    if (SUCCEEDED(hr)) 
    {
        hr = g_pCoCreateInstance(
                &CLSID_HNetCfgMgr,
                NULL,
                CLSCTX_ALL,
                &IID_IHNetIcsSettings,
                (VOID**)&pIcsSettings
                );
    }

    if (SUCCEEDED(hr))
    {
        hr = IHNetIcsSettings_EnumIcsPublicConnections(
                pIcsSettings,
                &pEnumIcsPub
                );

        IHNetIcsSettings_Release(pIcsSettings);
    }

    if (SUCCEEDED(hr))
    {
        hr = IEnumHNetIcsPublicConnections_Next(
                pEnumIcsPub,
                1,
                &pIcsPub,
                &ulCount
                );

        IEnumHNetIcsPublicConnections_Release(pEnumIcsPub);
    }

    if (SUCCEEDED(hr) && 1 == ulCount)
    {
        hr = IHNetIcsPublicConnection_QueryInterface(
                pIcsPub,
                &IID_IHNetConnection,
                (VOID**)&pConn
                );

        IHNetIcsPublicConnection_Release(pIcsPub);
    }
    else
    {
        hr = E_FAIL;
    }

    if (SUCCEEDED(hr))
    {
        HNET_CONN_PROPERTIES *pProps;
        
        //
        // Convert the IHNetConnection to a RASSHARECONN
        //

        hr = IHNetConnection_GetProperties(pConn, &pProps);

        if (SUCCEEDED(hr) && pProps->fLanConnection)
        {
            GUID *pGuid;
        
            g_pCoTaskMemFree(pProps);
            hr = IHNetConnection_GetGuid(pConn, &pGuid);

            if (SUCCEEDED(hr))
            {
                RasGuidToSharedConnection(pGuid, Connection);
                g_pCoTaskMemFree(pGuid);
            }
        }
        else if (SUCCEEDED(hr))
        {
            LPWSTR pszwName;
            LPWSTR pszwPath;
            
            g_pCoTaskMemFree(pProps);

            hr = IHNetConnection_GetName(pConn, &pszwName);

            if (SUCCEEDED(hr))
            {
                hr = IHNetConnection_GetRasPhonebookPath(pConn, &pszwPath);

                if (SUCCEEDED(hr))
                {
                    RasEntryToSharedConnection(pszwPath, pszwName, Connection);

                    g_pCoTaskMemFree(pszwPath);
                }

                g_pCoTaskMemFree(pszwName);
            }
        }

        IHNetConnection_Release(pConn);
    }

    if (fUninitializeCOM)
    {
        g_pCoUninitialize();
    }

    return SUCCEEDED(hr) ? NO_ERROR : ERROR_CAN_NOT_COMPLETE;

} // CsQuerySharedConnection

#if 0


ULONG
CsQuerySharedPrivateLan(
    GUID* LanGuid
    )

/*++

Routine Description:

    This routine is invoked to retrieve the private LAN connection, if any.

Arguments:

    LanGuid - receives the retrieved GUID.

Return Value:

    ULONG - Win32 status code.

--*/

{
    ULONG Error;
    HANDLE Key;
    PKEY_VALUE_PARTIAL_INFORMATION Information;
    NTSTATUS status;
    UNICODE_STRING UnicodeString;

    //
    // Open the 'SharedAccess\Parameters' key, read the 'SharedPrivateLan'
    // value, and convert it to a GUID
    //

    status = CsOpenKey(&Key, KEY_READ, c_szSharedAccessParametersKey);
    if (!NT_SUCCESS(status)) {
        TRACE1("CsQuerySharedPrivateLan: NtOpenKey=%x", status);
        return RtlNtStatusToDosError(status);
    }

    status = CsQueryValueKey(Key, c_szSharedPrivateLan, &Information);
    NtClose(Key);
    if (!NT_SUCCESS(status)) { return NO_ERROR; }

    RtlInitUnicodeString(&UnicodeString, (PWCHAR)Information->Data);
    status = RtlGUIDFromString(&UnicodeString, LanGuid);
    Free(Information);
    return NT_SUCCESS(status) ? NO_ERROR : RtlNtStatusToDosError(status);

} // CsQuerySharedPrivateLan


ULONG
CsQuerySharedPrivateLanAddress(
    PULONG Address
    )

/*++

Routine Description:

    This routine is invoked to retrieve the IP address assigned
    to the shared private LAN interface.

Arguments:

    Address - on output, receives the IP address of the shared private LAN
        interface.

Return Value:

    ULONG - Win32 status code.

--*/

{
    ULONG AdapterIndex;
    PALLOCATEANDGETIPADDRTABLEFROMSTACK AllocateAndGetIpAddrTableFromStack;
    ULONG Error;
    PGETINTERFACEINFO GetInterfaceInfo;
    HINSTANCE Hinstance;
    ULONG i;
    PKEY_VALUE_PARTIAL_INFORMATION Information;
    HANDLE Key = NULL;
    NTSTATUS status;
    PMIB_IPADDRTABLE Table;

    if (!Address) { return ERROR_INVALID_PARAMETER; }

    //
    // Open the service's Parameters key and attempt to retrieve
    // the GUID for the shared private LAN interface.
    //

    status = CsOpenKey(&Key, KEY_READ, c_szSharedAccessParametersKey);
    if (!NT_SUCCESS(status)) {
        TRACE1("CsQuerySharedPrivateLanAddress: NtOpenKey=%x", status);
        return RtlNtStatusToDosError(status);
    }
    status = CsQueryValueKey(Key, c_szSharedPrivateLan, &Information);
    NtClose(Key);
    if (!NT_SUCCESS(status)) { return ERROR_SHARING_NO_PRIVATE_LAN; }

    //
    // Load IPHLPAPI, which contains the 'GetInterfaceInfo' entrypoint
    // that we will use to map this GUID to an adapter-index,
    // as well as the 'AllocateAndGetIpAddrTableFromStack' entrypoint
    // that we will use to map the adapter-index to an IP address list.
    //

    if (!(Hinstance = LoadLibraryW(c_szIphlpapiDll)) ||
        !(AllocateAndGetIpAddrTableFromStack =
            (PALLOCATEANDGETIPADDRTABLEFROMSTACK)
                GetProcAddress(
                    Hinstance, c_szAllocateAndGetIpAddrTableFromStack
                    )) ||
        !(GetInterfaceInfo =
            (PGETINTERFACEINFO)
                GetProcAddress(
                    Hinstance, c_szGetInterfaceInfo
                    ))) {
        if (Hinstance) { FreeLibrary(Hinstance); }
        Free(Information);
        return ERROR_PROC_NOT_FOUND;
    }

    //
    // Map the GUID to an adapter index.
    //

    AdapterIndex =
        CsMapGuidToAdapterIndex((PWCHAR)Information->Data, GetInterfaceInfo);
    Free(Information);
    if (AdapterIndex == (ULONG)-1) {
        FreeLibrary(Hinstance);
        return ERROR_SHARING_NO_PRIVATE_LAN;
    }

    //
    // Map the adapter index to an IP address
    //

    Error =
        AllocateAndGetIpAddrTableFromStack(
            &Table,
            FALSE,
            GetProcessHeap(),
            0
            );
    FreeLibrary(Hinstance);
    if (Error) { return Error; }
    for (i = 0; i < Table->dwNumEntries; i++) {
        if (AdapterIndex == Table->table[i].dwIndex) {
            break;
        }
    }
    if (i >= Table->dwNumEntries) {
        Error = ERROR_SHARING_NO_PRIVATE_LAN;
    } else {
        *Address = Table->table[i].dwAddr;
    }
    HeapFree(GetProcessHeap(), 0, Table);
    return Error;

} // CsQuerySharedPrivateLanAddress

#endif


NTSTATUS
CsQueryValueKey(
    HANDLE Key,
    const WCHAR ValueName[],
    PKEY_VALUE_PARTIAL_INFORMATION* Information
    )

/*++

Routine Description:

    This routine is called to obtain the value of a registry key.

Arguments:

    Key - the key to be queried

    ValueName - the value to be queried

    Information - receives a pointer to the information read

Return Value:

    NTSTATUS - NT status code.

--*/

{
    UCHAR Buffer[sizeof(KEY_VALUE_PARTIAL_INFORMATION)];
    ULONG InformationLength;
    NTSTATUS status;
    UNICODE_STRING UnicodeString;

    RtlInitUnicodeString(&UnicodeString, ValueName);

    *Information = (PKEY_VALUE_PARTIAL_INFORMATION)Buffer;
    InformationLength = sizeof(KEY_VALUE_PARTIAL_INFORMATION);

    //
    // Read the value's size
    //

    status =
        NtQueryValueKey(
            Key,
            &UnicodeString,
            KeyValuePartialInformation,
            *Information,
            InformationLength,
            &InformationLength
            );

    if (!NT_SUCCESS(status) && status != STATUS_BUFFER_OVERFLOW &&
        status != STATUS_BUFFER_TOO_SMALL) {
        *Information = NULL;
        return status;
    }

    //
    // Allocate space for the value's size
    //

    *Information = (PKEY_VALUE_PARTIAL_INFORMATION)Malloc(InformationLength+2);
    if (!*Information) { return STATUS_NO_MEMORY; }

    //
    // Read the value's data
    //

    status =
        NtQueryValueKey(
            Key,
            &UnicodeString,
            KeyValuePartialInformation,
            *Information,
            InformationLength,
            &InformationLength
            );
    if (!NT_SUCCESS(status)) { Free(*Information); *Information = NULL; }
    return status;

} // CsQueryValueKey

#if 0


ULONG
CsRenameSharedConnection(
    LPRASSHARECONN NewConnection
    )

/*++

Routine Description:

    This routine is invoked to change the name of the currently-shared
    connection, if any. It is assumed that the private LAN will remain
    unchanged, and that the connection which is currently shared is a dialup
    connection.

Arguments:

    NewConnection - the new name for the shared connection

Return Value:

    ULONG - Win32 status code.

--*/

{
    HANDLE AdminHandle;
    PUCHAR Buffer;
    LPRASSHARECONN OldConnection;
    ULONG Error;
    PUCHAR Header;
    HANDLE Key;
    PKEY_VALUE_PARTIAL_INFORMATION Information;
    HANDLE InterfaceHandle;
    ULONG Length;
    OBJECT_ATTRIBUTES ObjectAttributes;
    HANDLE ServerHandle;
    NTSTATUS status;
    UNICODE_STRING UnicodeString;

    //
    // Open the 'SharedAccess\Parameters' key,
    // and read the 'SharedConnection' value
    //

    status = CsOpenKey(&Key, KEY_READ, c_szSharedAccessParametersKey);
    if (!NT_SUCCESS(status)) {
        TRACE1("CsRenameSharedConnection: NtOpenKey=%x", status);
        return RtlNtStatusToDosError(status);
    }

    status = CsQueryValueKey(Key, c_szSharedConnection, &Information);
    NtClose(Key);
    if (!NT_SUCCESS(status)) { return NO_ERROR; }

    //
    // Validate the data retrieved
    //

    if (Information->DataLength != sizeof(RASSHARECONN) ||
        ((LPRASSHARECONN)Information->Data)->dwSize != sizeof(RASSHARECONN)
        ) {
        TRACE2(
            "CsRenameSharedConnection: invalid length %d (size=%d) in registry",
            Information->DataLength, ((LPRASSHARECONN)Information->Data)->dwSize
            );
        Free(Information); return NO_ERROR;
    }

    //
    // Ensure that the connection which was shared is not a LAN connection,
    // and if so proceed to share the new connection instead.
    //

    OldConnection = (LPRASSHARECONN)Information->Data;
    if (OldConnection->fIsLanConnection) {
        TRACE("CsRenameSharedConnection: cannot rename shared LAN connection");
        Free(Information); return ERROR_INVALID_PARAMETER;
    }

    //
    // Clear any cached credentials for the old connection,
    // and share the new connection.
    //

    RasSetSharedConnectionCredentials(OldConnection, NULL);
    Free(Information);
    
    return CsShareConnection(NewConnection);

} // CsRenameSharedConnection


ULONG
CsSetupSharedPrivateLan(
    REFGUID LanGuid,
    BOOLEAN EnableSharing
    )

/*++

Routine Description:

    This routine is invoked to configure the designated private connection.

Arguments:

    LanGuid - identifies the LAN connection to be configured

    EnableSharing - if TRUE, sharing is enabled and the static address is set;
        otherwise, sharing is disabled.

Return Value:

    ULONG - Win32 status code.

--*/

{
    CS_ADDRESS_INFORMATION AddressInformation;
    PALLOCATEANDGETIPADDRTABLEFROMSTACK AllocateAndGetIpAddrTableFromStack;
    ANSI_STRING AnsiString;
    ULONG Error;
    PGETINTERFACEINFO GetInterfaceInfo;
    HINSTANCE Hinstance;
    ULONG i;
    HANDLE Key = NULL;
    UNICODE_STRING LanGuidString;
    ULONG ScopeAddress;
    ULONG ScopeMask;
    PSETADAPTERIPADDRESS SetAdapterIpAddress;
    ULONG Size;
    NTSTATUS status;
    PMIB_IPADDRTABLE Table;
    UNICODE_STRING UnicodeString;

    //
    // To install or remove the static private IP address,
    // we make use of several entrypoints in IPHLPAPI.DLL,
    // which we now load dynamically.
    //

    RtlStringFromGUID(LanGuid, &UnicodeString);
    RtlUnicodeStringToAnsiString(&AnsiString, &UnicodeString, TRUE);
    if (!(Hinstance = LoadLibraryW(c_szIphlpapiDll)) ||
        !(AllocateAndGetIpAddrTableFromStack =
            (PALLOCATEANDGETIPADDRTABLEFROMSTACK)
                GetProcAddress(
                    Hinstance, c_szAllocateAndGetIpAddrTableFromStack
                    )) ||
        !(GetInterfaceInfo =
            (PGETINTERFACEINFO)
                GetProcAddress(
                    Hinstance, c_szGetInterfaceInfo
                    )) ||
        !(SetAdapterIpAddress =
            (PSETADAPTERIPADDRESS)
                GetProcAddress(
                    Hinstance, c_szSetAdapterIpAddress
                    ))) {
        if (Hinstance) { FreeLibrary(Hinstance); }
        RtlFreeUnicodeString(&UnicodeString);
        RtlFreeAnsiString(&AnsiString);
        return ERROR_PROC_NOT_FOUND;
    }

    //
    // Determine whether some LAN adapter other than the private LAN
    // is already using a 169.254.0.0 address.
    // In the process, make sure that the private LAN has only one
    // IP address (otherwise, 'SetAdapterIpAddress' fails.)
    //

    CsQueryScopeInformation(&Key, &ScopeAddress, &ScopeMask);
    if (!Key) {
        FreeLibrary(Hinstance);
        RtlFreeUnicodeString(&UnicodeString);
        RtlFreeAnsiString(&AnsiString);
        return ERROR_CAN_NOT_COMPLETE;
    }
    Error =
        AllocateAndGetIpAddrTableFromStack(
            &Table,
            FALSE,
            GetProcessHeap(),
            0
            );
    if (!Error) {
        ULONG Index;
        ULONG Count;
        Index = CsMapGuidToAdapterIndex(UnicodeString.Buffer, GetInterfaceInfo);
        for (i = 0, Count = 0; i < Table->dwNumEntries; i++) {
            if (Index == Table->table[i].dwIndex) {
                ++Count;
            } else if ((Table->table[i].dwAddr & ScopeMask) ==
                       (ScopeAddress & ScopeMask)) {
                //
                // It appears that some other LAN adapter has an address in
                // the proposed scope.
                // This may happen when multiple netcards go into autonet mode
                // or when the RAS server is handing out autonet addresses.
                // Therefore, as long as we're using the autonet scope,
                // allow this behavior; otherwise prohibit it.
                //
                if ((ScopeAddress & ScopeMask) != 0x0000fea9) {
                    break;
                }
            }
        }
        if (i < Table->dwNumEntries) {
            Error = ERROR_SHARING_ADDRESS_EXISTS;
        } else if (Count > 1) {
            Error = ERROR_SHARING_MULTIPLE_ADDRESSES;
        }
        HeapFree(GetProcessHeap(), 0, Table);
    }

    if (Error) {
        FreeLibrary(Hinstance);
        RtlFreeUnicodeString(&UnicodeString);
        RtlFreeAnsiString(&AnsiString);
        NtClose(Key);
        return Error;
    }

    //
    // Set the predefined static IP address for the private LAN,
    // which we now read either from the registry or from the internal default.
    //
    // Before actually making the change, we capture the original IP address
    // so that it can be restored when the user turns off shared access.
    // Once the IP address is changed, we backup the original IP address
    // in the shared access parameters key.
    //

    status =
        CspCaptureAddressInformation(
            UnicodeString.Buffer, &AddressInformation
            );

    Error =
        SetAdapterIpAddress(
            AnsiString.Buffer,
            FALSE,
            ScopeAddress,
            ScopeMask,
            0
            );
    if (!Error) {
        if (NT_SUCCESS(status)) {
            CspBackupAddressInformation(Key, &AddressInformation);
        }
    } else {
        TRACE1("CsSetupSharedPrivateLan: SetAdapterIpAddress=%d", Error);
        if (Error == ERROR_TOO_MANY_NAMES) {
            Error = ERROR_SHARING_MULTIPLE_ADDRESSES;
        } else {
            //
            // Query the state of the connection.
            // If it is disconnected, convert the error code
            // to something more informative.
            //
            UNICODE_STRING DeviceString;
            NIC_STATISTICS NdisStatistics;
            RtlInitUnicodeString(&DeviceString, c_szDevice);
            RtlAppendUnicodeStringToString(&DeviceString, &UnicodeString);
            NdisStatistics.Size = sizeof(NdisStatistics);
            NdisQueryStatistics(&DeviceString, &NdisStatistics);
            RtlFreeUnicodeString(&DeviceString);
            if  (NdisStatistics.MediaState == MEDIA_STATE_UNKNOWN) {
                Error = ERROR_SHARING_HOST_ADDRESS_CONFLICT;
            } else if (NdisStatistics.DeviceState != DEVICE_STATE_CONNECTED ||
                NdisStatistics.MediaState != MEDIA_STATE_CONNECTED) {
                Error = ERROR_SHARING_NO_PRIVATE_LAN;
            }
        }
    }

    CspCleanupAddressInformation(&AddressInformation);
    FreeLibrary(Hinstance);
    RtlFreeUnicodeString(&UnicodeString);
    RtlFreeAnsiString(&AnsiString);
    if (Error) { NtClose(Key); return Error; }

    //
    // All went well above; now we save the name of the private LAN connection
    // under the 'SharedAccess\\Parameters' registry key.
    //

    RtlStringFromGUID(LanGuid, &LanGuidString);
    RtlInitUnicodeString(&UnicodeString, c_szSharedPrivateLan);
    status =
        NtSetValueKey(
            Key,
            &UnicodeString,
            0,
            REG_SZ,
            LanGuidString.Buffer,
            LanGuidString.Length + sizeof(WCHAR)
            );
    NtClose(Key);
    RtlFreeUnicodeString(&LanGuidString);
    if (!NT_SUCCESS(status)) { return RtlNtStatusToDosError(status); }
    return NO_ERROR;

} // CsSetupSharedPrivateLan


ULONG
CsSetSharedPrivateLan(
    REFGUID LanGuid
    )

/*++

Routine Description:

    This routine is invoked to (re)configure the designated private connection

Arguments:

    LanGuid - identifies the new LAN connection to be configured

Return Value:

    ULONG - Win32 status code.

--*/

{
    HANDLE Key;
    ULONG Error;
    NTSTATUS status;
    PKEY_VALUE_PARTIAL_INFORMATION Information;
    UNICODE_STRING UnicodeString;

    status = CsOpenKey(&Key, KEY_ALL_ACCESS, c_szSharedAccessParametersKey);
    if (!NT_SUCCESS(status)) {
        TRACE1("CsSetSharedPrivateLan: CsOpenKey=%x", status);
        return RtlNtStatusToDosError(status);
    }

    //
    // Remove old information (and reset old interface) in registry if present
    //
    
    status = CsQueryValueKey(Key, c_szSharedPrivateLan, &Information);
    RtlInitUnicodeString(&UnicodeString, c_szSharedPrivateLan);
    NtDeleteValueKey(Key, &UnicodeString);
    if (NT_SUCCESS(status)) {
        CspRestoreAddressInformation(Key, (PWCHAR)Information->Data);
        Free(Information);
    }

    //
    // Setup the private network with a private address
    //

    Error = CsSetupSharedPrivateLan(LanGuid, TRUE);
    if (Error) {
        TRACE1("CsSetSharedPrivateLan: CsSetupSharedPrivateLan=%d",Error);
        return Error;
    }

    return NO_ERROR;
    
} // CsSetSharedPrivateLan


ULONG
CsShareConnection(
    LPRASSHARECONN Connection
    )

/*++

Routine Description:

    This routine enables sharing on the connection with the given name.
    
Arguments:

    Connection - the connection to be shared

Return Value:

    ULONG - Win32 status code.

--*/

{
    UNICODE_STRING BindList;
    HANDLE Key;
    UNICODE_STRING LowerComponent;
    IP_PNP_RECONFIG_REQUEST Request;
    NTSTATUS status;
    UNICODE_STRING UpperComponent;
    ULONG Value;
    UNICODE_STRING ValueString;

    //
    // Set the 'SharedConnection' value in the registry,
    // under the 'SharedAccess\Parameters' key.
    //

    status = CsOpenKey(&Key, KEY_ALL_ACCESS, c_szSharedAccessParametersKey);
    if (!NT_SUCCESS(status)) {
        TRACE1("CsShareConnection: CsOpenKey=%x", status);
        return RtlNtStatusToDosError(status);
    }

    RtlInitUnicodeString(&ValueString, c_szSharedConnection);
    status =
        NtSetValueKey(
            Key,
            &ValueString,
            0,
            REG_BINARY,
            Connection,
            Connection->dwSize
            );
    
    NtClose(Key);
    if (!NT_SUCCESS(status)) {
        TRACE1("CsShareConnection: NtSetValueKey=%x", status);
        return RtlNtStatusToDosError(status);
    }

    return NO_ERROR;
    
} // CsShareConnection

#endif


VOID
CsShutdownModule(
    VOID
    )

/*++

Routine Description:

    This routine is invoked to clean up state for the module.

Arguments:

    none.

Return Value:

    none.

Environment:

    Invoked with 'CsCriticalSection' held by the caller.

--*/

{
    if (CsInitialized) {
        if (CsOle32Dll) { FreeLibrary(CsOle32Dll); CsOle32Dll = NULL; }
    }
} // CsShutdownModule

#if 0


ULONG
CsStartService(
    VOID
    )

/*++

Routine Description:

    This routine is invoked to start the routing and remote access service.

Arguments:

    none.

Return Value:

    ULONG - Win32 status code.

Revision History:

    Loosely based on CService::HrMoveOutOfState by KennT.

--*/

{
    ULONG Error;
    SC_HANDLE ScmHandle;
    SC_HANDLE ServiceHandle;
    SERVICE_STATUS ServiceStatus;
    ULONG Timeout;

    //
    // Connect to the service control manager
    //

    ScmHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    if (!ScmHandle) { return GetLastError(); }

    do {

        //
        // Open the shared access service
        //

        ServiceHandle =
            OpenService(ScmHandle, c_szSharedAccess, SERVICE_ALL_ACCESS);
        if (!ServiceHandle) { Error = GetLastError(); break; }

        //
        // Mark it as auto-start
        //

        ChangeServiceConfig(
            ServiceHandle,
            SERVICE_NO_CHANGE,
            SERVICE_AUTO_START,
            SERVICE_NO_CHANGE,
            NULL,
            NULL,
            NULL,
            NULL,
            NULL,
            NULL,
            NULL
            );

        //
        // Attempt to start the service
        //

        if (!StartService(ServiceHandle, 0, NULL)) {
            Error = GetLastError();
            if (Error == ERROR_SERVICE_ALREADY_RUNNING) { Error = NO_ERROR; }
            break;
        }

        //
        // Wait for the service to start
        //

        Timeout = 30;
        Error = ERROR_CAN_NOT_COMPLETE;

        do {

            //
            // Query the service's state
            //

            if (!QueryServiceStatus(ServiceHandle, &ServiceStatus)) {
                Error = GetLastError(); break;
            }

            //
            // See if the service has started
            //

            if (ServiceStatus.dwCurrentState == SERVICE_RUNNING) {
                Error = NO_ERROR; break;
            } else if (ServiceStatus.dwCurrentState == SERVICE_STOPPED ||
                       ServiceStatus.dwCurrentState == SERVICE_STOP_PENDING) {
                break;
            }

            //
            // Wait a little longer
            //

            Sleep(1000);

        } while(Timeout--);

    } while(FALSE);

    if (ServiceHandle) { CloseServiceHandle(ServiceHandle); }
    CloseServiceHandle(ScmHandle);

    return Error;

} // CsStartService


VOID
CsStopService(
    VOID
    )

/*++

Routine Description:

    This routine is invoked to uninstall the service.
    The routine, however, does not uninstall the service at all,
    which just goes to show you...
    Instead, it marks the service as demand-start.

Arguments:

    none.

Return Value:

    none.

--*/

{
    ULONG Error;
    SC_HANDLE ScmHandle;
    SC_HANDLE ServiceHandle;
    SERVICE_STATUS ServiceStatus;

    //
    // Connect to the service control manager
    //

    ScmHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    if (!ScmHandle) { return; }

    do {

        //
        // Open the shared access service
        //

        ServiceHandle =
            OpenService(ScmHandle, c_szSharedAccess, SERVICE_ALL_ACCESS);
        if (!ServiceHandle) { Error = GetLastError(); break; }

        //
        // Mark it as demand-start
        //

        ChangeServiceConfig(
            ServiceHandle,
            SERVICE_NO_CHANGE,
            SERVICE_DEMAND_START,
            SERVICE_NO_CHANGE,
            NULL,
            NULL,
            NULL,
            NULL,
            NULL,
            NULL,
            NULL
            );

        //
        // Attempt to stop the service
        //

        ControlService(ServiceHandle, SERVICE_CONTROL_STOP, &ServiceStatus);

    } while(FALSE);

    if (ServiceHandle) { CloseServiceHandle(ServiceHandle); }
    CloseServiceHandle(ScmHandle);

    return;


} // CsStopService


ULONG
CsUnshareConnection(
    BOOLEAN RemovePrivateLan,
    PBOOLEAN Shared
    )

/*++

Routine Description:

    This routine is invoked to unshare a shared connection.
    This is accomplished by removing the settings from the registry.

Arguments:

    RemovePrivateLan - if TRUE, the private LAN connection is reset
        to use DHCP rather than the NAT private address.

    Shared - receives 'TRUE' if a shared connection was found, FALSE otherwise.

Return Value:

    ULONG - Win32 status code.

--*/

{
    LPRASSHARECONN Connection;
    HANDLE Key;
    PKEY_VALUE_PARTIAL_INFORMATION Information;
    PIP_NAT_INTERFACE_INFO Info;
    GUID LanGuid;
    ULONG Length;
    ULONG Size;
    NTSTATUS status;
    UNICODE_STRING UnicodeString;

    if (Shared) { *Shared = FALSE; }

    //
    // Open the 'SharedAccess\Parameters' key, read the 'SharedConnection'
    // value, and validate the information retrieved.
    //

    status = CsOpenKey(&Key, KEY_ALL_ACCESS, c_szSharedAccessParametersKey);
    if (!NT_SUCCESS(status)) {
        TRACE1("CsUnshareConnection: NtOpenKey=%x", status);
        return RtlNtStatusToDosError(status);
    }

    //
    // Read the 'SharedConnection' value
    //

    status = CsQueryValueKey(Key, c_szSharedConnection, &Information);
    if (!NT_SUCCESS(status)) { return NO_ERROR; }

    if (Information->DataLength != sizeof(RASSHARECONN) ||
        ((LPRASSHARECONN)Information->Data)->dwSize != sizeof(RASSHARECONN)) {
        TRACE2(
            "CsUnshareConnection: invalid length %d (size=%d) in registry",
            Information->DataLength, ((LPRASSHARECONN)Information->Data)->dwSize
            );
        NtClose(Key); Free(Information); return NO_ERROR;
    }

    //
    // Inform the caller that a connection was indeed originally shared,
    // clear any credentials cached for that connection, free the buffer
    // containing the shared connection's information, and delete the
    // 'SharedConnection' value from the registry.
    //

    if (Shared) { *Shared = TRUE; }
    Connection = (LPRASSHARECONN)Information->Data;
    RasSetSharedConnectionCredentials(Connection, NULL);

    Free(Information);
    RtlInitUnicodeString(&UnicodeString, c_szSharedConnection);
    NtDeleteValueKey(Key, &UnicodeString);

    //
    // See if we're resetting the private LAN connection,
    // and if so, read (and delete) the 'SharedPrivateLan' value.
    // In the process, restore the original address-information
    // for the connection.
    //

    if (RemovePrivateLan) {
        status = CsQueryValueKey(Key, c_szSharedPrivateLan, &Information);
        RtlInitUnicodeString(&UnicodeString, c_szSharedPrivateLan);
        NtDeleteValueKey(Key, &UnicodeString);
        if (NT_SUCCESS(status)) {
            CspRestoreAddressInformation(Key, (PWCHAR)Information->Data);
            Free(Information);
        }
    }

    NtClose(Key);
    return NO_ERROR;

} // CsUnshareConnection


WCHAR*
StrDupW(
    LPCWSTR psz
    )
{
    WCHAR* psz2 = Malloc((lstrlenW(psz) + 1) * sizeof(WCHAR));
    if (psz2) { lstrcpyW(psz2, psz); }
    return psz2;
}


VOID
TestBackupAddress(
    PWCHAR Guid
    )
{
    HANDLE Key;
    CS_ADDRESS_INFORMATION Information;
    NTSTATUS status;
    status = CspCaptureAddressInformation(Guid, &Information);
    if (NT_SUCCESS(status)) {
        status = CsOpenKey(&Key, KEY_ALL_ACCESS, c_szSharedAccessParametersKey);
        if (NT_SUCCESS(status)) {
            CspBackupAddressInformation(Key, &Information);
            NtClose(Key);
        }
        CspCleanupAddressInformation(&Information);
    }
}

VOID
TestRestoreAddress(
    PWCHAR Guid
    )
{
    HANDLE Key;
    NTSTATUS status;
    status = CsOpenKey(&Key, KEY_ALL_ACCESS, c_szSharedAccessParametersKey);
    if (NT_SUCCESS(status)) {
        status = CspRestoreAddressInformation(Key, Guid);
    }
}

VOID CsRefreshNetConnections(
    VOID
    )
{
    BOOL bUninitializeCOM = TRUE;
    HRESULT hResult;
    
    ASSERT(NULL != g_pCoInitializeEx);
    ASSERT(NULL != g_pCoCreateInstance);
    ASSERT(NULL != g_pCoUninitialize);
    
    hResult = g_pCoInitializeEx(NULL, COINIT_MULTITHREADED|COINIT_DISABLE_OLE1DDE); // we don't know if the thread is COM or not
    if(RPC_E_CHANGED_MODE == hResult)
    {
        hResult = S_OK;
        bUninitializeCOM = FALSE;
    }
    
    if (SUCCEEDED(hResult)) 
    {
        INetConnectionRefresh * pRefresh = NULL;
        hResult = g_pCoCreateInstance(&CLSID_ConnectionManager, NULL, CLSCTX_LOCAL_SERVER | CLSCTX_NO_CODE_DOWNLOAD, &IID_INetConnectionRefresh, (void**) &pRefresh);
        if(SUCCEEDED(hResult))
        {

            g_pCoSetProxyBlanket((IUnknown*) pRefresh, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,  RPC_C_AUTHN_LEVEL_CALL,  RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);
            // ignore error as the interface is not invalidated by error
            
            hResult = INetConnectionRefresh_RefreshAll(pRefresh);
            INetConnectionRefresh_Release(pRefresh);
        }
        
        if(TRUE == bUninitializeCOM)
        {
            g_pCoUninitialize();
        }
    }
}

#endif