You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
775 lines
18 KiB
775 lines
18 KiB
/*
|
|
File: radius.c
|
|
|
|
Upgrades radius configuration from nt4 steelhead to win2k rras.
|
|
|
|
Paul Mayfield, 2/8/99
|
|
*/
|
|
|
|
#include "upgrade.h"
|
|
|
|
//
|
|
// Information describing a radius server
|
|
//
|
|
typedef struct _RAD_SERVER_NODE
|
|
{
|
|
PWCHAR pszName;
|
|
|
|
DWORD dwTimeout;
|
|
DWORD dwAuthPort;
|
|
DWORD dwAcctPort;
|
|
DWORD dwScore;
|
|
BOOL bEnableAuth;
|
|
BOOL bEnableAcct;
|
|
BOOL bAccountingOnOff;
|
|
|
|
struct _RAD_SERVER_NODE * pNext;
|
|
|
|
} RAD_SERVER_NODE;
|
|
|
|
//
|
|
// A radius server list
|
|
//
|
|
typedef struct _RAD_SERVER_LIST
|
|
{
|
|
RAD_SERVER_NODE* pHead;
|
|
DWORD dwCount;
|
|
|
|
} RAD_SERVER_LIST;
|
|
|
|
//
|
|
// Info used by routines that manipulate the nt5 radius registry hive
|
|
//
|
|
typedef struct _RAD_CONFIG_INFO
|
|
{
|
|
HKEY hkAuthServers;
|
|
HKEY hkAuthProviders;
|
|
|
|
HKEY hkAcctServers;
|
|
HKEY hkAcctProviders;
|
|
|
|
} RAD_CONFIG_INFO;
|
|
|
|
//
|
|
// Registry value names
|
|
//
|
|
// The all caps ones were taken from nt40 src.
|
|
//
|
|
static const WCHAR PSZTIMEOUT[] = L"Timeout";
|
|
static const WCHAR PSZAUTHPORT[] = L"AuthPort";
|
|
static const WCHAR PSZACCTPORT[] = L"AcctPort";
|
|
static const WCHAR PSZENABLEACCT[] = L"EnableAccounting";
|
|
static const WCHAR PSZENABLEACCTONOFF[] = L"EnableAccountingOnOff";
|
|
static const WCHAR PSZENABLEAUTH[] = L"EnableAuthentication";
|
|
static const WCHAR PSZSCORE[] = L"Score";
|
|
|
|
static const WCHAR pszTempRegKey[] = L"Temp";
|
|
static const WCHAR pszAccounting[] = L"Accounting\\Providers";
|
|
static const WCHAR pszAuthentication[] = L"Authentication\\Providers";
|
|
static const WCHAR pszActiveProvider[] = L"ActiveProvider";
|
|
static const WCHAR pszRadServersFmt[] = L"%s\\Servers";
|
|
static const WCHAR pszServers[] = L"Servers";
|
|
|
|
static const WCHAR pszGuidRadAuth[] =
|
|
L"{1AA7F83F-C7F5-11D0-A376-00C04FC9DA04}";
|
|
|
|
static const WCHAR pszGuidRadAcct[] =
|
|
L"{1AA7F840-C7F5-11D0-A376-00C04FC9DA04}";
|
|
|
|
|
|
// Defaults
|
|
//
|
|
#define DEFTIMEOUT 5
|
|
#define DEFAUTHPORT 1645
|
|
#define DEFACCTPORT 1646
|
|
#define MAXSCORE 30
|
|
|
|
RAD_SERVER_NODE g_DefaultRadNode =
|
|
{
|
|
NULL,
|
|
|
|
DEFTIMEOUT,
|
|
DEFAUTHPORT,
|
|
DEFACCTPORT,
|
|
MAXSCORE,
|
|
TRUE,
|
|
TRUE,
|
|
TRUE,
|
|
|
|
NULL
|
|
};
|
|
|
|
//
|
|
// Loads a radius server node's configuration from the registry
|
|
// (assumed nt4 format and that defaults are assigned to pNode)
|
|
//
|
|
DWORD
|
|
RadNodeLoad(
|
|
IN HKEY hKey,
|
|
OUT RAD_SERVER_NODE* pNode)
|
|
{
|
|
RTL_QUERY_REGISTRY_TABLE paramTable[8];
|
|
BOOL bTrue = TRUE;
|
|
DWORD i;
|
|
|
|
// Initialize the table of parameters
|
|
RtlZeroMemory(¶mTable[0], sizeof(paramTable));
|
|
|
|
paramTable[0].Name = (PWCHAR)PSZTIMEOUT;
|
|
paramTable[0].EntryContext = &(pNode->dwTimeout);
|
|
|
|
paramTable[1].Name = (PWCHAR)PSZAUTHPORT;
|
|
paramTable[1].EntryContext = &(pNode->dwAuthPort);
|
|
|
|
paramTable[2].Name = (PWCHAR)PSZACCTPORT;
|
|
paramTable[2].EntryContext = &(pNode->dwAcctPort);
|
|
|
|
paramTable[3].Name = (PWCHAR)PSZENABLEAUTH;
|
|
paramTable[3].EntryContext = &(pNode->bEnableAuth);
|
|
|
|
paramTable[4].Name = (PWCHAR)PSZENABLEACCT;
|
|
paramTable[4].EntryContext = &(pNode->bEnableAcct);
|
|
|
|
paramTable[5].Name = (PWCHAR)PSZENABLEACCTONOFF;
|
|
paramTable[5].EntryContext = &(pNode->bAccountingOnOff);
|
|
|
|
paramTable[6].Name = (PWCHAR)PSZSCORE;
|
|
paramTable[6].EntryContext = &(pNode->dwScore);
|
|
|
|
// We're reading all dwords, set the types
|
|
// accordingly
|
|
//
|
|
for (i = 0; i < (sizeof(paramTable) / sizeof(*paramTable)) - 1; i++)
|
|
{
|
|
paramTable[i].Flags = RTL_QUERY_REGISTRY_DIRECT;
|
|
paramTable[i].DefaultType = REG_DWORD;
|
|
paramTable[i].DefaultLength = sizeof(DWORD);
|
|
paramTable[i].DefaultData = paramTable[i].EntryContext;
|
|
}
|
|
|
|
// Read in the values
|
|
//
|
|
RtlQueryRegistryValues(
|
|
RTL_REGISTRY_HANDLE,
|
|
(PWSTR)hKey,
|
|
paramTable,
|
|
NULL,
|
|
NULL);
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
// Add the authentication server node
|
|
//
|
|
DWORD
|
|
RadNodeSave(
|
|
IN HKEY hKey,
|
|
IN RAD_SERVER_NODE* pNode,
|
|
IN BOOL bAuth)
|
|
{
|
|
DWORD dwErr = NO_ERROR;
|
|
HKEY hkServer = NULL;
|
|
|
|
do
|
|
{
|
|
// Create the server key in which to store the info
|
|
//
|
|
dwErr = RegCreateKeyExW(
|
|
hKey,
|
|
pNode->pszName,
|
|
0,
|
|
NULL,
|
|
REG_OPTION_NON_VOLATILE,
|
|
KEY_ALL_ACCESS,
|
|
NULL,
|
|
&hkServer,
|
|
NULL);
|
|
if (dwErr != ERROR_SUCCESS)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (bAuth)
|
|
{
|
|
RegSetValueExW(
|
|
hkServer,
|
|
(PWCHAR)PSZAUTHPORT,
|
|
0,
|
|
REG_DWORD,
|
|
(BYTE*)&pNode->dwAuthPort,
|
|
sizeof(DWORD));
|
|
|
|
RegSetValueExW(
|
|
hkServer,
|
|
(PWCHAR)PSZSCORE,
|
|
0,
|
|
REG_DWORD,
|
|
(BYTE*)&pNode->dwScore,
|
|
sizeof(DWORD));
|
|
|
|
RegSetValueExW(
|
|
hkServer,
|
|
(PWCHAR)PSZTIMEOUT,
|
|
0,
|
|
REG_DWORD,
|
|
(BYTE*)&pNode->dwTimeout,
|
|
sizeof(DWORD));
|
|
}
|
|
else
|
|
{
|
|
RegSetValueExW(
|
|
hkServer,
|
|
(PWCHAR)PSZACCTPORT,
|
|
0,
|
|
REG_DWORD,
|
|
(BYTE*)&pNode->dwAcctPort,
|
|
sizeof(DWORD));
|
|
|
|
RegSetValueExW(
|
|
hkServer,
|
|
(PWCHAR)PSZSCORE,
|
|
0,
|
|
REG_DWORD,
|
|
(BYTE*)&pNode->dwScore,
|
|
sizeof(DWORD));
|
|
|
|
RegSetValueExW(
|
|
hkServer,
|
|
(PWCHAR)PSZTIMEOUT,
|
|
0,
|
|
REG_DWORD,
|
|
(BYTE*)&pNode->dwTimeout,
|
|
sizeof(DWORD));
|
|
|
|
RegSetValueExW(
|
|
hkServer,
|
|
(PWCHAR)PSZENABLEACCTONOFF,
|
|
0,
|
|
REG_DWORD,
|
|
(BYTE*)&pNode->bAccountingOnOff,
|
|
sizeof(DWORD));
|
|
}
|
|
|
|
} while (FALSE);
|
|
|
|
// Cleanup
|
|
{
|
|
if (hkServer)
|
|
{
|
|
RegCloseKey(hkServer);
|
|
}
|
|
}
|
|
|
|
return dwErr;
|
|
}
|
|
|
|
//
|
|
// Callback from registry key enumerator that adds the server at the given key
|
|
// to the list of radius servers.
|
|
//
|
|
DWORD
|
|
RadSrvListAddNodeFromKey(
|
|
IN PWCHAR pszName, // sub key name
|
|
IN HKEY hKey, // sub key
|
|
IN HANDLE hData)
|
|
{
|
|
DWORD dwErr = NO_ERROR;
|
|
RAD_SERVER_LIST * pList = (RAD_SERVER_LIST*)hData;
|
|
RAD_SERVER_NODE * pNode = NULL;
|
|
|
|
do
|
|
{
|
|
// Initialize the new node
|
|
//
|
|
pNode = (RAD_SERVER_NODE*) UtlAlloc(sizeof(RAD_SERVER_NODE));
|
|
if (pNode == NULL)
|
|
{
|
|
dwErr = ERROR_NOT_ENOUGH_MEMORY;
|
|
break;
|
|
}
|
|
CopyMemory(pNode, &g_DefaultRadNode, sizeof(RAD_SERVER_NODE));
|
|
|
|
// Initialize the name
|
|
//
|
|
pNode->pszName = UtlDupString(pszName);
|
|
if (pNode->pszName == NULL)
|
|
{
|
|
dwErr = ERROR_NOT_ENOUGH_MEMORY;
|
|
break;
|
|
}
|
|
|
|
// Load in the registry settings
|
|
//
|
|
dwErr = RadNodeLoad(hKey, pNode);
|
|
if (dwErr != NO_ERROR)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Add the node to the list
|
|
//
|
|
pNode->pNext = pList->pHead;
|
|
pList->pHead = pNode;
|
|
pList->dwCount += 1;
|
|
|
|
} while (FALSE);
|
|
|
|
// Cleanup
|
|
{
|
|
}
|
|
|
|
return dwErr;
|
|
}
|
|
|
|
//
|
|
// Generates a RAD_SERVER_LIST based on the configuration (assumed
|
|
// nt4 format) in the given registry key
|
|
//
|
|
DWORD
|
|
RadSrvListGenerate(
|
|
IN HKEY hkSettings,
|
|
OUT RAD_SERVER_LIST** ppList)
|
|
{
|
|
RAD_SERVER_LIST* pList = NULL;
|
|
DWORD dwErr = NO_ERROR;
|
|
|
|
do
|
|
{
|
|
// Alloc/Init the list
|
|
pList = (RAD_SERVER_LIST*) UtlAlloc(sizeof(RAD_SERVER_LIST));
|
|
if (pList == NULL)
|
|
{
|
|
dwErr = ERROR_NOT_ENOUGH_MEMORY;
|
|
break;
|
|
}
|
|
ZeroMemory(pList, sizeof(RAD_SERVER_LIST));
|
|
|
|
// Build the list
|
|
//
|
|
dwErr = UtlEnumRegistrySubKeys(
|
|
hkSettings,
|
|
NULL,
|
|
RadSrvListAddNodeFromKey,
|
|
(HANDLE)pList);
|
|
if (dwErr != NO_ERROR)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Assign the return value
|
|
//
|
|
*ppList = pList;
|
|
|
|
} while (FALSE);
|
|
|
|
// Cleanup
|
|
{
|
|
}
|
|
|
|
return dwErr;
|
|
}
|
|
|
|
//
|
|
// Cleans up a radius server list
|
|
//
|
|
DWORD
|
|
RadSrvListCleanup(
|
|
IN RAD_SERVER_LIST* pList)
|
|
{
|
|
RAD_SERVER_NODE* pNode = NULL;
|
|
|
|
if (pList)
|
|
{
|
|
for (pNode = pList->pHead; pNode; pNode = pList->pHead)
|
|
{
|
|
if (pNode->pszName)
|
|
{
|
|
UtlFree(pNode->pszName);
|
|
}
|
|
pList->pHead = pNode->pNext;
|
|
UtlFree(pNode);
|
|
}
|
|
UtlFree(pList);
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
//
|
|
// Opens the registry keys required by pNode
|
|
//
|
|
DWORD
|
|
RadOpenRegKeys(
|
|
IN HKEY hkRouter,
|
|
IN RAD_SERVER_NODE* pNode,
|
|
IN OUT RAD_CONFIG_INFO* pInfo)
|
|
{
|
|
DWORD dwErr = NO_ERROR;
|
|
WCHAR pszPath[MAX_PATH];
|
|
|
|
do
|
|
{
|
|
// Open the authentication keys as needed
|
|
//
|
|
if (pNode->bEnableAuth)
|
|
{
|
|
if (pInfo->hkAuthProviders == NULL)
|
|
{
|
|
// Open the auth providers key
|
|
//
|
|
dwErr = RegOpenKeyExW(
|
|
hkRouter,
|
|
pszAuthentication,
|
|
0,
|
|
KEY_ALL_ACCESS,
|
|
&pInfo->hkAuthProviders);
|
|
if (dwErr != NO_ERROR)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Generate the servers key name
|
|
//
|
|
wsprintfW(pszPath, pszRadServersFmt, pszGuidRadAuth);
|
|
|
|
// Open the auth servers key
|
|
//
|
|
dwErr = RegCreateKeyExW(
|
|
pInfo->hkAuthProviders,
|
|
pszPath,
|
|
0,
|
|
NULL,
|
|
REG_OPTION_NON_VOLATILE,
|
|
KEY_ALL_ACCESS,
|
|
NULL,
|
|
&pInfo->hkAuthServers,
|
|
NULL);
|
|
if (dwErr != NO_ERROR)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Open the accounting keys
|
|
//
|
|
if (pNode->bEnableAcct)
|
|
{
|
|
if (pInfo->hkAcctProviders == NULL)
|
|
{
|
|
// Open the auth providers key
|
|
//
|
|
dwErr = RegOpenKeyExW(
|
|
hkRouter,
|
|
pszAccounting,
|
|
0,
|
|
KEY_ALL_ACCESS,
|
|
&pInfo->hkAcctProviders);
|
|
if (dwErr != NO_ERROR)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Generate the servers key name
|
|
//
|
|
wsprintfW(pszPath, pszRadServersFmt, pszGuidRadAcct);
|
|
|
|
// Open the auth servers key
|
|
//
|
|
dwErr = RegCreateKeyExW(
|
|
pInfo->hkAcctProviders,
|
|
pszPath,
|
|
0,
|
|
NULL,
|
|
REG_OPTION_NON_VOLATILE,
|
|
KEY_ALL_ACCESS,
|
|
NULL,
|
|
&pInfo->hkAcctServers,
|
|
NULL);
|
|
if (dwErr != NO_ERROR)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
} while (FALSE);
|
|
|
|
// Cleanup
|
|
{
|
|
}
|
|
|
|
return dwErr;
|
|
}
|
|
|
|
//
|
|
// Cleans up info from radius installation
|
|
//
|
|
DWORD
|
|
RadCloseRegKeys(
|
|
IN RAD_CONFIG_INFO* pInfo)
|
|
{
|
|
if (pInfo)
|
|
{
|
|
if (pInfo->hkAuthServers)
|
|
{
|
|
RegCloseKey(pInfo->hkAuthServers);
|
|
pInfo->hkAuthServers = NULL;
|
|
}
|
|
|
|
if (pInfo->hkAuthProviders)
|
|
{
|
|
RegCloseKey(pInfo->hkAuthProviders);
|
|
pInfo->hkAuthProviders = NULL;
|
|
}
|
|
|
|
if (pInfo->hkAcctServers)
|
|
{
|
|
RegCloseKey(pInfo->hkAcctServers);
|
|
pInfo->hkAcctServers = NULL;
|
|
}
|
|
|
|
if (pInfo->hkAcctProviders)
|
|
{
|
|
RegCloseKey(pInfo->hkAcctProviders);
|
|
pInfo->hkAcctProviders = NULL;
|
|
}
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
//
|
|
// Adds the given server to the win2k section of the registry
|
|
//
|
|
DWORD
|
|
RadInstallServer(
|
|
IN HKEY hkRouter,
|
|
IN RAD_SERVER_NODE* pNode,
|
|
IN OUT RAD_CONFIG_INFO* pInfo)
|
|
{
|
|
DWORD dwErr = NO_ERROR;
|
|
|
|
do
|
|
{
|
|
// Based on the node, open or create any neccessary
|
|
// registry keys.
|
|
//
|
|
dwErr = RadOpenRegKeys(hkRouter, pNode, pInfo);
|
|
if (dwErr != NO_ERROR)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (pNode->bEnableAuth)
|
|
{
|
|
// Add the authentication server node
|
|
//
|
|
dwErr = RadNodeSave(
|
|
pInfo->hkAuthServers,
|
|
pNode,
|
|
TRUE);
|
|
if (dwErr != NO_ERROR)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Set the active authentication provider
|
|
//
|
|
dwErr = RegSetValueExW(
|
|
pInfo->hkAuthProviders,
|
|
(PWCHAR)pszActiveProvider,
|
|
0,
|
|
REG_SZ,
|
|
(BYTE*)pszGuidRadAuth,
|
|
(wcslen(pszGuidRadAuth) + 1) * sizeof(WCHAR));
|
|
if (dwErr != NO_ERROR)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pNode->bEnableAcct)
|
|
{
|
|
// Add the accounting server node
|
|
//
|
|
dwErr = RadNodeSave(
|
|
pInfo->hkAcctServers,
|
|
pNode,
|
|
FALSE);
|
|
if (dwErr != NO_ERROR)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Set the active accounting provider
|
|
//
|
|
dwErr = RegSetValueExW(
|
|
pInfo->hkAcctProviders,
|
|
(PWCHAR)pszActiveProvider,
|
|
0,
|
|
REG_SZ,
|
|
(BYTE*)pszGuidRadAcct,
|
|
(wcslen(pszGuidRadAcct) + 1) * sizeof(WCHAR));
|
|
if (dwErr != NO_ERROR)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
} while (FALSE);
|
|
|
|
// Cleanup
|
|
{
|
|
}
|
|
|
|
return dwErr;
|
|
}
|
|
|
|
//
|
|
// Migrates radius settings from the settings key into the
|
|
// router key.
|
|
//
|
|
DWORD
|
|
RadMigrateSettings(
|
|
IN HKEY hkRouter,
|
|
IN HKEY hkSettings)
|
|
{
|
|
DWORD dwErr = NO_ERROR;
|
|
RAD_SERVER_LIST* pList = NULL;
|
|
RAD_CONFIG_INFO* pInfo = NULL;
|
|
RAD_SERVER_NODE* pNode = NULL;
|
|
|
|
do
|
|
{
|
|
// Generate the list of servers based on
|
|
// the loaded settings
|
|
dwErr = RadSrvListGenerate(hkSettings, &pList);
|
|
if (dwErr != NO_ERROR)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// If there were no servers, then there's nothing
|
|
// to do
|
|
//
|
|
if (pList->pHead == NULL)
|
|
{
|
|
dwErr = NO_ERROR;
|
|
break;
|
|
}
|
|
|
|
// Allocate and init the info blob that will be
|
|
// used by the install funcs.
|
|
//
|
|
pInfo = (RAD_CONFIG_INFO*) UtlAlloc(sizeof(RAD_CONFIG_INFO));
|
|
if (pInfo == NULL)
|
|
{
|
|
dwErr = ERROR_NOT_ENOUGH_MEMORY;
|
|
break;
|
|
}
|
|
ZeroMemory(pInfo, sizeof(RAD_CONFIG_INFO));
|
|
|
|
// Install all of the servers
|
|
//
|
|
for (pNode = pList->pHead; pNode; pNode = pNode->pNext)
|
|
{
|
|
RadInstallServer(hkRouter, pNode, pInfo);
|
|
}
|
|
|
|
} while (FALSE);
|
|
|
|
// Cleanup
|
|
{
|
|
if (pList)
|
|
{
|
|
RadSrvListCleanup(pList);
|
|
}
|
|
if (pInfo)
|
|
{
|
|
RadCloseRegKeys(pInfo);
|
|
UtlFree(pInfo);
|
|
}
|
|
}
|
|
|
|
return dwErr;
|
|
}
|
|
|
|
//
|
|
// Performs the upgrade work
|
|
//
|
|
DWORD
|
|
RadiusToRouterUpgrade(
|
|
IN PWCHAR pszFile)
|
|
{
|
|
DWORD dwErr = NO_ERROR;
|
|
HKEY hkRouter = NULL, hkTemp = NULL, hkSettings = NULL;
|
|
|
|
do
|
|
{
|
|
// Get the Router subkey
|
|
//
|
|
dwErr = UtlAccessRouterKey(&hkRouter);
|
|
if (dwErr != NO_ERROR)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Load registry data that has been saved off
|
|
//
|
|
dwErr = UtlLoadSavedSettings(
|
|
hkRouter,
|
|
(PWCHAR)pszTempRegKey,
|
|
pszFile,
|
|
&hkTemp);
|
|
if (dwErr != NO_ERROR)
|
|
{
|
|
PrintMessage(L"Unable to load radius settings.\n");
|
|
break;
|
|
}
|
|
|
|
// Load the settings key
|
|
//
|
|
dwErr = RegOpenKeyExW(
|
|
hkTemp,
|
|
pszServers,
|
|
0,
|
|
KEY_ALL_ACCESS,
|
|
&hkSettings);
|
|
if (dwErr != NO_ERROR)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Migrate radius information
|
|
//
|
|
dwErr = RadMigrateSettings(hkRouter, hkSettings);
|
|
if (dwErr != NO_ERROR)
|
|
{
|
|
PrintMessage(L"Unable to migrate radius settings.\n");
|
|
break;
|
|
}
|
|
|
|
} while (FALSE);
|
|
|
|
// Cleanup
|
|
{
|
|
if (hkSettings)
|
|
{
|
|
RegCloseKey(hkSettings);
|
|
}
|
|
if (hkTemp)
|
|
{
|
|
UtlDeleteRegistryTree(hkTemp);
|
|
RegCloseKey(hkTemp);
|
|
RegDeleteKey(hkRouter, pszTempRegKey);
|
|
}
|
|
if (hkRouter)
|
|
{
|
|
RegCloseKey(hkRouter);
|
|
}
|
|
}
|
|
|
|
return dwErr;
|
|
}
|
|
|