635 lines
17 KiB
C++
635 lines
17 KiB
C++
//+---------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
// Copyright (C) Microsoft Corporation, 1997.
|
|
//
|
|
// File: R A S A F . C P P
|
|
//
|
|
// Contents: RAS Answer File objects.
|
|
//
|
|
// Notes:
|
|
//
|
|
// Author: shaunco 19 Apr 1997
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
#include "pch.h"
|
|
#pragma hdrstop
|
|
#include "ncipaddr.h"
|
|
#include "ncmisc.h"
|
|
#include "ncreg.h"
|
|
#include "ncsetup.h"
|
|
#include "rasaf.h"
|
|
#include "rasobj.h"
|
|
|
|
extern const WCHAR c_szAfAppleTalk[];
|
|
extern const WCHAR c_szAfAutoNetworkNumbers[];
|
|
extern const WCHAR c_szAfClientCanReqIpaddr[];
|
|
extern const WCHAR c_szAfClientReqNodeNumber[];
|
|
extern const WCHAR c_szAfDialinProtocols[];
|
|
extern const WCHAR c_szAfForceEncryptedData[];
|
|
extern const WCHAR c_szAfForceStrongEncryption[];
|
|
extern const WCHAR c_szAfForceEncryptedPassword[];
|
|
extern const WCHAR c_szAfWanNetPoolSize[];
|
|
extern const WCHAR c_szAfIpAddressStart[];
|
|
extern const WCHAR c_szAfIpAddressEnd[];
|
|
extern const WCHAR c_szAfIpxClientAccess[];
|
|
extern const WCHAR c_szAfIpx[];
|
|
extern const WCHAR c_szAfL2tpMaxVcs[];
|
|
extern const WCHAR c_szAfL2tpEndpoints[];
|
|
extern const WCHAR c_szAfMultilink[];
|
|
extern const WCHAR c_szAfNetNumberFrom[];
|
|
extern const WCHAR c_szAfNetbeuiClientAccess[];
|
|
extern const WCHAR c_szAfNetbeui[];
|
|
extern const WCHAR c_szAfNetwork[];
|
|
extern const WCHAR c_szAfParamsSection[];
|
|
extern const WCHAR c_szAfPptpEndpoints[];
|
|
extern const WCHAR c_szAfRouterType[];
|
|
extern const WCHAR c_szAfSecureVPN[];
|
|
extern const WCHAR c_szAfSetDialinUsage[];
|
|
extern const WCHAR c_szAfSameNetworkNumber[];
|
|
extern const WCHAR c_szAfTcpipClientAccess[];
|
|
extern const WCHAR c_szAfTcpip[];
|
|
extern const WCHAR c_szAfThisComputer[];
|
|
extern const WCHAR c_szAfUseDhcp[];
|
|
|
|
extern const WCHAR c_szInfId_MS_L2tpMiniport[];
|
|
extern const WCHAR c_szInfId_MS_PptpMiniport[];
|
|
extern const WCHAR c_szInfId_MS_PppoeMiniport[];
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
// CRasSrvAnswerFileData
|
|
//
|
|
HRESULT
|
|
CRasSrvAnswerFileData::HrOpenAndRead (
|
|
PCWSTR pszAnswerFile,
|
|
PCWSTR pszAnswerSection)
|
|
{
|
|
// Open the answer file. It will close itself in it's destructor.
|
|
CSetupInfFile inf;
|
|
UINT unErrorLine;
|
|
|
|
HRESULT hr = inf.HrOpen (
|
|
pszAnswerFile, NULL,
|
|
INF_STYLE_OLDNT | INF_STYLE_WIN4,
|
|
&unErrorLine);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_fRouterTypeSpecified = FALSE;
|
|
m_fSetUsageToDialin = FALSE;
|
|
|
|
// Prepare the default values for use when we fail to read.
|
|
// Initializing with defaults also helps to gaurantee that bogus
|
|
// values aren't used when we add items to the structure but fail
|
|
// to provide answer file support for them.
|
|
//
|
|
m_dataSrvCfg.GetDefault ();
|
|
m_dataSrvIp .GetDefault ();
|
|
m_dataSrvIpx.GetDefault ();
|
|
m_dataSrvNbf.GetDefault ();
|
|
|
|
DATA_SRV_CFG defSrvCfg = m_dataSrvCfg;
|
|
DATA_SRV_IP defSrvIp = m_dataSrvIp;
|
|
DATA_SRV_IPX defSrvIpx = m_dataSrvIpx;
|
|
DATA_SRV_NBF defSrvNbf = m_dataSrvNbf;
|
|
|
|
// Get the real parameter section.
|
|
//
|
|
tstring strSection;
|
|
hr = inf.HrGetString (pszAnswerSection,
|
|
c_szAfParamsSection, &strSection);
|
|
if (FAILED(hr))
|
|
{
|
|
// If we failed to find the parameter section, just try
|
|
// this one.
|
|
//
|
|
strSection = pszAnswerSection;
|
|
}
|
|
|
|
static const MAP_SZ_DWORD c_mapProtocols [] =
|
|
{
|
|
{ c_szAfTcpip, RPI_IP },
|
|
{ c_szAfIpx, RPI_IPX },
|
|
{ c_szAfNetbeui, RPI_NBF },
|
|
{ c_szAfAppleTalk, RPI_ATALK },
|
|
};
|
|
// Read the list of dial-in protocols.
|
|
//
|
|
hr = inf.HrGetMultiSzMapToDword (strSection.c_str(),
|
|
c_szAfDialinProtocols,
|
|
c_mapProtocols,
|
|
celems (c_mapProtocols),
|
|
&m_dwDialInProtocolIds);
|
|
if (FAILED(hr) || !m_dwDialInProtocolIds)
|
|
{
|
|
// If its not there, use all possible.
|
|
m_dwDialInProtocolIds = RPI_ALL;
|
|
}
|
|
|
|
hr = inf.HrGetStringAsBool (strSection.c_str(),
|
|
c_szAfMultilink,
|
|
&m_dataSrvCfg.fMultilink);
|
|
if (FAILED(hr))
|
|
{
|
|
m_dataSrvCfg.fMultilink = defSrvCfg.fMultilink;
|
|
}
|
|
|
|
hr = inf.HrGetDword(strSection.c_str(),
|
|
c_szAfRouterType,
|
|
(DWORD*)&m_dataSrvCfg.dwRouterType);
|
|
if (FAILED(hr))
|
|
{
|
|
m_dataSrvCfg.dwRouterType = defSrvCfg.dwRouterType;
|
|
}
|
|
else
|
|
{
|
|
m_fRouterTypeSpecified = TRUE;
|
|
}
|
|
|
|
hr = inf.HrGetDword (strSection.c_str(),
|
|
c_szAfForceEncryptedPassword,
|
|
&m_dataSrvCfg.dwAuthLevel);
|
|
if (FAILED(hr))
|
|
{
|
|
m_dataSrvCfg.dwAuthLevel = defSrvCfg.dwAuthLevel;
|
|
}
|
|
|
|
hr = inf.HrGetStringAsBool (strSection.c_str(),
|
|
c_szAfForceEncryptedData,
|
|
&m_dataSrvCfg.fDataEnc);
|
|
if (FAILED(hr))
|
|
{
|
|
m_dataSrvCfg.fDataEnc = defSrvCfg.fDataEnc;
|
|
}
|
|
|
|
hr = inf.HrGetStringAsBool (strSection.c_str(),
|
|
c_szAfForceStrongEncryption,
|
|
&m_dataSrvCfg.fStrongDataEnc);
|
|
if (FAILED(hr))
|
|
{
|
|
m_dataSrvCfg.fStrongDataEnc = defSrvCfg.fStrongDataEnc;
|
|
}
|
|
|
|
hr = inf.HrGetDword (strSection.c_str(),
|
|
c_szAfSecureVPN,
|
|
&m_dataSrvCfg.dwSecureVPN);
|
|
if (FAILED(hr))
|
|
{
|
|
m_dataSrvCfg.dwSecureVPN = defSrvCfg.dwSecureVPN;
|
|
}
|
|
|
|
// pmay: 251736
|
|
// Discover whether we are to set all port usage to 'dialin'
|
|
//
|
|
DWORD dwSetUsageToDialin;
|
|
|
|
hr = inf.HrGetDword(strSection.c_str(),
|
|
c_szAfSetDialinUsage,
|
|
&dwSetUsageToDialin);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_fSetUsageToDialin = !!dwSetUsageToDialin;
|
|
}
|
|
|
|
static const MAP_SZ_DWORD c_mapNetworkAccess [] =
|
|
{
|
|
{ c_szAfNetwork, TRUE },
|
|
{ c_szAfThisComputer, FALSE },
|
|
};
|
|
|
|
// Read the IP values.
|
|
//
|
|
m_dataSrvIp.fEnableIn =
|
|
(m_dwDialInProtocolIds & RPI_IP) ? TRUE : FALSE;
|
|
|
|
hr = inf.HrGetStringMapToDword (strSection.c_str(),
|
|
c_szAfTcpipClientAccess,
|
|
c_mapNetworkAccess,
|
|
celems (c_mapNetworkAccess),
|
|
(DWORD*)&m_dataSrvIp.fAllowNetworkAccess);
|
|
if (FAILED(hr))
|
|
{
|
|
m_dataSrvIp.fAllowNetworkAccess = defSrvIp.fAllowNetworkAccess;
|
|
}
|
|
|
|
hr = inf.HrGetStringAsBool (strSection.c_str(),
|
|
c_szAfUseDhcp,
|
|
&m_dataSrvIp.fUseDhcp);
|
|
if (FAILED(hr))
|
|
{
|
|
m_dataSrvIp.fUseDhcp = defSrvIp.fUseDhcp;
|
|
}
|
|
|
|
hr = inf.HrGetStringAsBool (strSection.c_str(),
|
|
c_szAfClientCanReqIpaddr,
|
|
&m_dataSrvIp.fAllowClientAddr);
|
|
if (FAILED(hr))
|
|
{
|
|
m_dataSrvIp.fAllowClientAddr = defSrvIp.fAllowClientAddr;
|
|
}
|
|
|
|
tstring strIpAddress;
|
|
hr = inf.HrGetString (strSection.c_str(),
|
|
c_szAfIpAddressStart,
|
|
&strIpAddress);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_dataSrvIp.dwIpStart = IpPszToHostAddr(strIpAddress.c_str());
|
|
}
|
|
else
|
|
{
|
|
m_dataSrvIp.dwIpStart = defSrvIp.dwIpStart;
|
|
}
|
|
|
|
hr = inf.HrGetString (strSection.c_str(),
|
|
c_szAfIpAddressEnd,
|
|
&strIpAddress);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_dataSrvIp.dwIpEnd = IpPszToHostAddr(strIpAddress.c_str());
|
|
}
|
|
else
|
|
{
|
|
m_dataSrvIp.dwIpEnd = defSrvIp.dwIpEnd;
|
|
}
|
|
|
|
|
|
// Read the IPX values.
|
|
//
|
|
m_dataSrvIpx.fEnableIn =
|
|
(m_dwDialInProtocolIds & RPI_IPX) ? TRUE : FALSE;
|
|
|
|
hr = inf.HrGetStringMapToDword (strSection.c_str(),
|
|
c_szAfIpxClientAccess,
|
|
c_mapNetworkAccess,
|
|
celems (c_mapNetworkAccess),
|
|
(DWORD*)&m_dataSrvIpx.fAllowNetworkAccess);
|
|
if (FAILED(hr))
|
|
{
|
|
m_dataSrvIpx.fAllowNetworkAccess = defSrvIpx.fAllowNetworkAccess;
|
|
}
|
|
|
|
hr = inf.HrGetStringAsBool (strSection.c_str(),
|
|
c_szAfAutoNetworkNumbers,
|
|
&m_dataSrvIpx.fUseAutoAddr);
|
|
if (FAILED(hr))
|
|
{
|
|
m_dataSrvIpx.fUseAutoAddr = defSrvIpx.fUseAutoAddr;
|
|
}
|
|
|
|
hr = inf.HrGetStringAsBool (strSection.c_str(),
|
|
c_szAfSameNetworkNumber,
|
|
&m_dataSrvIpx.fUseSameNetNum);
|
|
if (FAILED(hr))
|
|
{
|
|
m_dataSrvIpx.fUseSameNetNum = defSrvIpx.fUseSameNetNum;
|
|
}
|
|
|
|
hr = inf.HrGetStringAsBool (strSection.c_str(),
|
|
c_szAfClientReqNodeNumber,
|
|
&m_dataSrvIpx.fAllowClientNetNum);
|
|
if (FAILED(hr))
|
|
{
|
|
m_dataSrvIpx.fAllowClientNetNum = defSrvIpx.fAllowClientNetNum;
|
|
}
|
|
|
|
hr = inf.HrGetDword (strSection.c_str(),
|
|
c_szAfNetNumberFrom,
|
|
&m_dataSrvIpx.dwIpxNetFirst);
|
|
if (FAILED(hr))
|
|
{
|
|
m_dataSrvIpx.dwIpxNetFirst = defSrvIpx.dwIpxNetFirst;
|
|
}
|
|
|
|
hr = inf.HrGetDword (strSection.c_str(),
|
|
c_szAfWanNetPoolSize,
|
|
&m_dataSrvIpx.dwIpxWanPoolSize);
|
|
if (FAILED(hr))
|
|
{
|
|
m_dataSrvIpx.dwIpxWanPoolSize = defSrvIpx.dwIpxWanPoolSize;
|
|
}
|
|
|
|
// Read the NBF values.
|
|
//
|
|
m_dataSrvNbf.fEnableIn =
|
|
(m_dwDialInProtocolIds & RPI_NBF) ? TRUE : FALSE;
|
|
|
|
hr = inf.HrGetStringMapToDword (strSection.c_str(),
|
|
c_szAfNetbeuiClientAccess,
|
|
c_mapNetworkAccess,
|
|
celems (c_mapNetworkAccess),
|
|
(DWORD*)&m_dataSrvNbf.fAllowNetworkAccess);
|
|
if (FAILED(hr))
|
|
{
|
|
m_dataSrvNbf.fAllowNetworkAccess = defSrvNbf.fAllowNetworkAccess;
|
|
}
|
|
|
|
// Default anything bogus.
|
|
//
|
|
m_dataSrvCfg.CheckAndDefault ();
|
|
m_dataSrvIp .CheckAndDefault ();
|
|
m_dataSrvIpx.CheckAndDefault ();
|
|
m_dataSrvNbf.CheckAndDefault ();
|
|
|
|
hr = S_OK;
|
|
}
|
|
TraceError ("CRasSrvAnswerFileData::HrOpenAndRead", hr);
|
|
return hr;
|
|
}
|
|
|
|
VOID
|
|
CRasSrvAnswerFileData::SaveToRegistry (
|
|
VOID) const
|
|
{
|
|
m_dataSrvCfg.SaveToReg();
|
|
m_dataSrvIp .SaveToReg();
|
|
m_dataSrvIpx.SaveToReg();
|
|
m_dataSrvNbf.SaveToReg();
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
// CL2tpAnswerFileData
|
|
//
|
|
const DWORD c_MaxMaxVcs = 1000;
|
|
const DWORD c_DefMaxVcs = 1000;
|
|
const DWORD c_MaxEndpoints = 1000;
|
|
const DWORD c_DefEndpoints = 5;
|
|
|
|
VOID
|
|
CL2tpAnswerFileData::CheckAndDefault ()
|
|
{
|
|
if (m_cMaxVcs > c_MaxMaxVcs)
|
|
{
|
|
m_cMaxVcs = c_DefMaxVcs;
|
|
}
|
|
|
|
if (m_cEndpoints > c_MaxEndpoints)
|
|
{
|
|
m_cEndpoints = c_DefEndpoints;
|
|
}
|
|
}
|
|
|
|
HRESULT
|
|
CL2tpAnswerFileData::HrOpenAndRead (
|
|
PCWSTR pszAnswerFile,
|
|
PCWSTR pszAnswerSection)
|
|
{
|
|
ZeroMemory (this, sizeof(*this));
|
|
|
|
// Open the answer file. It will close itself in it's destructor.
|
|
CSetupInfFile inf;
|
|
UINT unErrorLine;
|
|
|
|
HRESULT hr = inf.HrOpen (
|
|
pszAnswerFile, NULL,
|
|
INF_STYLE_OLDNT | INF_STYLE_WIN4,
|
|
&unErrorLine);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Read the number of VPNs.
|
|
//
|
|
hr = inf.HrGetDword (pszAnswerSection,
|
|
c_szAfL2tpMaxVcs,
|
|
&m_cMaxVcs);
|
|
if (FAILED(hr))
|
|
{
|
|
m_cMaxVcs = c_DefMaxVcs;
|
|
}
|
|
|
|
hr = inf.HrGetDword (pszAnswerSection,
|
|
c_szAfL2tpEndpoints,
|
|
&m_cEndpoints);
|
|
if (FAILED(hr))
|
|
{
|
|
m_cEndpoints = c_DefEndpoints;
|
|
m_fWriteEndpoints = FALSE;
|
|
}
|
|
else
|
|
{
|
|
m_fWriteEndpoints = TRUE;
|
|
}
|
|
|
|
// Default anything bogus.
|
|
//
|
|
CheckAndDefault ();
|
|
|
|
hr = S_OK;
|
|
}
|
|
TraceError ("CL2tpAnswerFileData::HrOpenAndRead", hr);
|
|
return hr;
|
|
}
|
|
|
|
VOID
|
|
CL2tpAnswerFileData::SaveToRegistry (
|
|
INetCfg* pnc) const
|
|
{
|
|
Assert (pnc);
|
|
|
|
HKEY hkey;
|
|
HRESULT hr;
|
|
|
|
// Update the L2TP miniport's parameter key.
|
|
//
|
|
hr = HrOpenComponentParamKey (
|
|
pnc,
|
|
GUID_DEVCLASS_NET,
|
|
c_szInfId_MS_L2tpMiniport,
|
|
&hkey);
|
|
|
|
if (S_OK == hr)
|
|
{
|
|
(VOID) HrRegSetDword (hkey, L"MaxVcs", m_cMaxVcs);
|
|
|
|
if (m_fWriteEndpoints)
|
|
{
|
|
(VOID) HrRegSetDword (hkey, L"WanEndpoints", m_cEndpoints);
|
|
}
|
|
|
|
RegCloseKey (hkey);
|
|
}
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
// CPptpAnswerFileData
|
|
//
|
|
|
|
// Minimum and maximum number of Virtual Private Networks
|
|
// allowed by PPTP.
|
|
const DWORD c_cPptpVpnsMin = 0;
|
|
const DWORD c_cPptpVpnsMax = 1000;
|
|
|
|
static const DWORD c_cDefPptpVpnsWorkstation = 2;
|
|
static const DWORD c_cDefPptpVpnsServer = 5;
|
|
|
|
|
|
DWORD
|
|
CPptpAnswerFileData::GetDefaultNumberOfVpns ()
|
|
{
|
|
PRODUCT_FLAVOR pf;
|
|
GetProductFlavor(NULL, &pf);
|
|
|
|
DWORD cVpns;
|
|
|
|
// On the server product, default to 5 VPNs, otherwise, default to 2 VPNs.
|
|
//
|
|
if (PF_SERVER == pf)
|
|
{
|
|
cVpns = c_cDefPptpVpnsServer;
|
|
}
|
|
else
|
|
{
|
|
cVpns = c_cDefPptpVpnsWorkstation;
|
|
}
|
|
|
|
return cVpns;
|
|
}
|
|
|
|
#pragma warning(push)
|
|
#pragma warning(disable:4296)
|
|
VOID
|
|
CPptpAnswerFileData::CheckAndDefault ()
|
|
{
|
|
if ((m_cVpns < c_cPptpVpnsMin) || (m_cVpns > c_cPptpVpnsMax))
|
|
{
|
|
m_cVpns = GetDefaultNumberOfVpns ();
|
|
}
|
|
}
|
|
#pragma warning(pop)
|
|
|
|
HRESULT
|
|
CPptpAnswerFileData::HrOpenAndRead (
|
|
PCWSTR pszAnswerFile,
|
|
PCWSTR pszAnswerSection)
|
|
{
|
|
ZeroMemory (this, sizeof(*this));
|
|
|
|
// Open the answer file. It will close itself in it's destructor.
|
|
CSetupInfFile inf;
|
|
UINT unErrorLine;
|
|
|
|
HRESULT hr = inf.HrOpen (
|
|
pszAnswerFile, NULL,
|
|
INF_STYLE_OLDNT | INF_STYLE_WIN4,
|
|
&unErrorLine);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Read the number of VPNs.
|
|
//
|
|
hr = inf.HrGetDword (pszAnswerSection,
|
|
c_szAfPptpEndpoints,
|
|
&m_cVpns);
|
|
if (FAILED(hr))
|
|
{
|
|
m_cVpns = GetDefaultNumberOfVpns ();
|
|
}
|
|
|
|
// Default anything bogus.
|
|
//
|
|
CheckAndDefault ();
|
|
|
|
hr = S_OK;
|
|
}
|
|
TraceError ("CPptpAnswerFileData::HrOpenAndRead", hr);
|
|
return hr;
|
|
}
|
|
|
|
VOID
|
|
CPptpAnswerFileData::SaveToRegistry (
|
|
INetCfg* pnc) const
|
|
{
|
|
Assert (pnc);
|
|
|
|
HKEY hkey;
|
|
HRESULT hr;
|
|
|
|
// Update the PPTP miniport's parameter key.
|
|
//
|
|
hr = HrOpenComponentParamKey (
|
|
pnc,
|
|
GUID_DEVCLASS_NET,
|
|
c_szInfId_MS_PptpMiniport,
|
|
&hkey);
|
|
|
|
if (S_OK == hr)
|
|
{
|
|
(VOID) HrRegSetDword (hkey, L"WanEndpoints", m_cVpns);
|
|
|
|
RegCloseKey (hkey);
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
CPppoeAnswerFileData::CheckAndDefault ()
|
|
{
|
|
m_cVpns = 1;
|
|
}
|
|
|
|
HRESULT
|
|
CPppoeAnswerFileData::HrOpenAndRead (
|
|
PCWSTR pszAnswerFile,
|
|
PCWSTR pszAnswerSection)
|
|
{
|
|
ZeroMemory (this, sizeof(*this));
|
|
|
|
// Open the answer file. It will close itself in it's destructor.
|
|
CSetupInfFile inf;
|
|
UINT unErrorLine;
|
|
|
|
HRESULT hr = inf.HrOpen (
|
|
pszAnswerFile, NULL,
|
|
INF_STYLE_OLDNT | INF_STYLE_WIN4,
|
|
&unErrorLine);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Read the number of VPNs.
|
|
//
|
|
hr = inf.HrGetDword (pszAnswerSection,
|
|
c_szAfL2tpEndpoints,
|
|
&m_cVpns);
|
|
if (FAILED(hr))
|
|
{
|
|
m_cVpns = 1;
|
|
}
|
|
|
|
// Default anything bogus.
|
|
//
|
|
CheckAndDefault ();
|
|
|
|
hr = S_OK;
|
|
}
|
|
TraceError ("CPppoeAnswerFileData::HrOpenAndRead", hr);
|
|
return hr;
|
|
}
|
|
|
|
VOID
|
|
CPppoeAnswerFileData::SaveToRegistry (
|
|
INetCfg* pnc) const
|
|
{
|
|
Assert (pnc);
|
|
|
|
HKEY hkey;
|
|
HRESULT hr;
|
|
|
|
// Update the PPTP miniport's parameter key.
|
|
//
|
|
hr = HrOpenComponentParamKey (
|
|
pnc,
|
|
GUID_DEVCLASS_NET,
|
|
c_szInfId_MS_PppoeMiniport,
|
|
&hkey);
|
|
|
|
if (S_OK == hr)
|
|
{
|
|
(VOID) HrRegSetDword (hkey, L"WanEndpoints", m_cVpns);
|
|
|
|
RegCloseKey (hkey);
|
|
}
|
|
}
|