633 lines
13 KiB
C
633 lines
13 KiB
C
|
/*++
|
||
|
|
||
|
Copyright (c) 1995 Microsoft Corporation
|
||
|
|
||
|
Module Name:
|
||
|
|
||
|
wannet.c
|
||
|
|
||
|
Abstract:
|
||
|
|
||
|
Wan net allocation module
|
||
|
|
||
|
Author:
|
||
|
|
||
|
Stefan Solomon 11/03/1995
|
||
|
|
||
|
Revision History:
|
||
|
|
||
|
|
||
|
--*/
|
||
|
|
||
|
#include "precomp.h"
|
||
|
#pragma hdrstop
|
||
|
|
||
|
|
||
|
extern DWORD (WINAPI * RmCreateGlobalRoute)(PUCHAR Network);
|
||
|
extern HANDLE g_hRouterLog;
|
||
|
|
||
|
ULONG LastUsedRandSeed;
|
||
|
|
||
|
//
|
||
|
// Variable keeping the initialization state of the IPXCP Configuration Database
|
||
|
//
|
||
|
|
||
|
BOOL WanConfigDbaseInitialized = FALSE;
|
||
|
|
||
|
// WAN net pool entry structure
|
||
|
typedef struct _NET_ENTRY {
|
||
|
|
||
|
BOOL InUse;
|
||
|
UCHAR Network[4];
|
||
|
|
||
|
} NET_ENTRY, *PNET_ENTRY;
|
||
|
|
||
|
// Structure represents a pool of
|
||
|
// wan net numbers. This pool dynamically
|
||
|
// grows and shrinks as clients dial
|
||
|
// in and hang up
|
||
|
typedef struct _WAN_NET_POOL {
|
||
|
DWORD dwMaxSize;
|
||
|
DWORD dwCurSize;
|
||
|
DWORD dwInUseCount;
|
||
|
NET_ENTRY * pEntries;
|
||
|
} WAN_NET_POOL, *PWAN_NET_POOL;
|
||
|
|
||
|
#define WANNET_DEFAULT_SIZE 10
|
||
|
#define WANNET_DEFAULT_GROW 100
|
||
|
#define WANNET_MAXSIZE 64000
|
||
|
|
||
|
//
|
||
|
// WAN net numbers pool
|
||
|
//
|
||
|
WAN_NET_POOL WanNetPool = {WANNET_MAXSIZE, 0, 0, NULL};
|
||
|
|
||
|
//
|
||
|
// Global WAN net
|
||
|
//
|
||
|
|
||
|
//UCHAR GlobalConfig.RParams.GlobalWanNet[4] = {0,0,0,0};
|
||
|
|
||
|
ULONG GlobalWanNetIndex; // when global wan net comes from pool - the index
|
||
|
|
||
|
DWORD
|
||
|
InitWanNetConfigDbase(VOID);
|
||
|
|
||
|
DWORD
|
||
|
WanNetAlloc(PUCHAR Network,
|
||
|
PULONG AllocatedNetworkIndexp);
|
||
|
|
||
|
DWORD
|
||
|
CreateWanNetPool(VOID);
|
||
|
|
||
|
DWORD
|
||
|
GrowWanNetPool (WAN_NET_POOL * pPool);
|
||
|
|
||
|
DWORD
|
||
|
CreateGlobalWanNet(VOID);
|
||
|
|
||
|
VOID
|
||
|
DestroyWanNetPool(VOID);
|
||
|
|
||
|
VOID
|
||
|
DestroyGlobalWanNet(VOID);
|
||
|
|
||
|
DWORD
|
||
|
AllocWanNetFromPool(PUCHAR Network,
|
||
|
PULONG AllocatedNetworkIndexp);
|
||
|
|
||
|
VOID
|
||
|
FreeWanNetToPool(ULONG AllocatedNetworkIndex);
|
||
|
|
||
|
DWORD
|
||
|
GetRandomNetNumber(PUCHAR Network);
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Function: GetWanNetNumber
|
||
|
|
||
|
Descr: This function is called by IPXCP or IPXWAN to get a network
|
||
|
number from the pool.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
Network
|
||
|
AllocatedNetworkIndex - if the value returned by this param is
|
||
|
not INVALID_NETWORK_INDEX then the caller must call ReleaseWanNetNumber
|
||
|
to free the net to the pool.
|
||
|
InterfaceType
|
||
|
|
||
|
Returns NO_ERROR - a network has been successfully allocated
|
||
|
|
||
|
--*/
|
||
|
|
||
|
|
||
|
DWORD
|
||
|
GetWanNetNumber(IN OUT PUCHAR Network,
|
||
|
IN OUT PULONG AllocatedNetworkIndexp,
|
||
|
IN ULONG InterfaceType)
|
||
|
{
|
||
|
DWORD rc;
|
||
|
|
||
|
memcpy(Network, nullnet, 4);
|
||
|
*AllocatedNetworkIndexp = INVALID_NETWORK_INDEX;
|
||
|
|
||
|
// if this a router <-> router connection and we are configured for
|
||
|
// Unnumbered RIP -> return 0.
|
||
|
if((InterfaceType == IF_TYPE_WAN_ROUTER) ||
|
||
|
(InterfaceType == IF_TYPE_PERSONAL_WAN_ROUTER)) {
|
||
|
|
||
|
if(GlobalConfig.EnableUnnumberedWanLinks) {
|
||
|
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ACQUIRE_DATABASE_LOCK;
|
||
|
|
||
|
if(!WanConfigDbaseInitialized) {
|
||
|
|
||
|
if((rc = InitWanNetConfigDbase()) != NO_ERROR) {
|
||
|
|
||
|
RELEASE_DATABASE_LOCK;
|
||
|
return rc;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// check the interface type
|
||
|
if((InterfaceType == IF_TYPE_WAN_WORKSTATION) &&
|
||
|
GlobalConfig.RParams.EnableGlobalWanNet) {
|
||
|
|
||
|
memcpy(Network, GlobalConfig.RParams.GlobalWanNet, 4);
|
||
|
*AllocatedNetworkIndexp = INVALID_NETWORK_INDEX;
|
||
|
|
||
|
rc = NO_ERROR;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
rc = WanNetAlloc(Network,
|
||
|
AllocatedNetworkIndexp);
|
||
|
}
|
||
|
|
||
|
RELEASE_DATABASE_LOCK;
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Function: ReleaseWanNetNumber
|
||
|
|
||
|
Descr: This function will be called by ipxcp to release the net number
|
||
|
used for configuring the WAN link. The call is issued when the
|
||
|
WAN link gets disconnected.
|
||
|
|
||
|
--*/
|
||
|
|
||
|
VOID
|
||
|
ReleaseWanNetNumber(ULONG AllocatedNetworkIndex)
|
||
|
{
|
||
|
ACQUIRE_DATABASE_LOCK;
|
||
|
|
||
|
SS_ASSERT(WanConfigDbaseInitialized);
|
||
|
SS_ASSERT(AllocatedNetworkIndex != INVALID_NETWORK_INDEX);
|
||
|
|
||
|
FreeWanNetToPool(AllocatedNetworkIndex);
|
||
|
|
||
|
RELEASE_DATABASE_LOCK;
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Function: InitWanNetConfigDatabase
|
||
|
|
||
|
Descr: Configures the database of network numbers used for incoming
|
||
|
WAN links.
|
||
|
|
||
|
Remark: This function is called from the GetWanNetNumber (see below) when
|
||
|
IPXCP has an incoming call and the router has been started and the
|
||
|
database hasn't been configured yet.
|
||
|
|
||
|
Remark2: >> called with database lock held <<
|
||
|
|
||
|
--*/
|
||
|
|
||
|
|
||
|
DWORD
|
||
|
InitWanNetConfigDbase(VOID)
|
||
|
{
|
||
|
DWORD rc;
|
||
|
|
||
|
// create wan net pool
|
||
|
if(!GlobalConfig.EnableAutoWanNetAllocation) {
|
||
|
|
||
|
if((rc = CreateWanNetPool()) != NO_ERROR) {
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// create global wan net
|
||
|
if(GlobalConfig.RParams.EnableGlobalWanNet) {
|
||
|
|
||
|
if((rc = CreateGlobalWanNet()) != NO_ERROR) {
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
WanConfigDbaseInitialized = TRUE;
|
||
|
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Function: DestroyWanNetConfigDatabase
|
||
|
|
||
|
Descr: Frees resources allocated for the wan net config database
|
||
|
|
||
|
Remark: called when the router stops
|
||
|
|
||
|
--*/
|
||
|
|
||
|
VOID
|
||
|
DestroyWanNetConfigDatabase(VOID)
|
||
|
{
|
||
|
if(!WanConfigDbaseInitialized) {
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// destroy wan net pool
|
||
|
if(!GlobalConfig.EnableAutoWanNetAllocation) {
|
||
|
|
||
|
DestroyWanNetPool();
|
||
|
}
|
||
|
|
||
|
WanConfigDbaseInitialized = FALSE;
|
||
|
}
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Function: WanNetAlloc
|
||
|
|
||
|
Descr:
|
||
|
|
||
|
Remark: >> called with database lock held <<
|
||
|
|
||
|
--*/
|
||
|
|
||
|
DWORD
|
||
|
WanNetAlloc(IN OUT PUCHAR Network,
|
||
|
IN OUT PULONG AllocatedNetworkIndexp)
|
||
|
{
|
||
|
DWORD rc;
|
||
|
|
||
|
if(GlobalConfig.EnableAutoWanNetAllocation) {
|
||
|
|
||
|
// try a number of times to generate a unique net number
|
||
|
rc = GetRandomNetNumber(Network);
|
||
|
*AllocatedNetworkIndexp = INVALID_NETWORK_INDEX;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
rc = AllocWanNetFromPool(Network,
|
||
|
AllocatedNetworkIndexp);
|
||
|
}
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Function: CreateWanNetPool
|
||
|
|
||
|
Descr:
|
||
|
|
||
|
Remark: >> called with database lock held <<
|
||
|
|
||
|
--*/
|
||
|
|
||
|
DWORD
|
||
|
CreateWanNetPool(VOID)
|
||
|
{
|
||
|
ULONG i;
|
||
|
PNET_ENTRY nep;
|
||
|
PWSTR nesp;
|
||
|
ULONG wannet;
|
||
|
UCHAR asc[9];
|
||
|
PUCHAR ascp;
|
||
|
|
||
|
// Create the pool of WAN network numbers to be used in configuring
|
||
|
// incoming WAN links.
|
||
|
|
||
|
if ((GlobalConfig.WanNetPoolStr.Buffer!=NULL) && (GlobalConfig.WanNetPoolStr.Length >0)) {
|
||
|
DWORD strsz = 0;
|
||
|
nesp = GlobalConfig.WanNetPoolStr.Buffer;
|
||
|
strsz = 0;
|
||
|
while (*nesp!=0) {
|
||
|
strsz += 1;
|
||
|
wannet = wcstoul (nesp, NULL, 16);
|
||
|
if ((wannet==0) || (wannet==0xFFFFFFFF))
|
||
|
break;
|
||
|
nesp += wcslen (nesp) + 1;
|
||
|
}
|
||
|
if ((*nesp!=0) ||
|
||
|
(strsz!=(GlobalConfig.WanNetPoolSize)) ||
|
||
|
((GlobalConfig.WanNetPoolSize) > WANNET_MAXSIZE)) {
|
||
|
|
||
|
TraceIpx(WANNET_TRACE, "Invalid wan net pool config WanNetPoolSize =%d\n,"
|
||
|
" entries: ",
|
||
|
(GlobalConfig.WanNetPoolSize));
|
||
|
nesp = GlobalConfig.WanNetPoolStr.Buffer;
|
||
|
while (*nesp!=0) {
|
||
|
TraceIpx(WANNET_TRACE|TRACE_NO_STDINFO, "%ls ", nesp);
|
||
|
nesp += wcslen (nesp) + 1;
|
||
|
}
|
||
|
|
||
|
return ERROR_CAN_NOT_COMPLETE;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
if (((GlobalConfig.FirstWanNet) == 0) ||
|
||
|
((GlobalConfig.FirstWanNet) == 0xFFFFFFFF) ||
|
||
|
((GlobalConfig.WanNetPoolSize) > WANNET_MAXSIZE)) {
|
||
|
|
||
|
TraceIpx(WANNET_TRACE, "Invalid wan net pool config, FirstWanNet=%x, WanNetPoolSize =%d\n",
|
||
|
GlobalConfig.FirstWanNet, GlobalConfig.WanNetPoolSize);
|
||
|
|
||
|
return ERROR_CAN_NOT_COMPLETE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If the WanNetPoolSize is 0, then assume any size
|
||
|
if ((GlobalConfig.WanNetPoolSize == 0) ||
|
||
|
(GlobalConfig.WanNetPoolSize > WANNET_MAXSIZE))
|
||
|
{
|
||
|
GlobalConfig.WanNetPoolSize = WANNET_MAXSIZE;
|
||
|
}
|
||
|
|
||
|
// Initialize the wan net pool
|
||
|
WanNetPool.dwMaxSize = GlobalConfig.WanNetPoolSize;
|
||
|
WanNetPool.dwCurSize = 0;
|
||
|
WanNetPool.dwInUseCount = 0;
|
||
|
WanNetPool.pEntries = NULL;
|
||
|
|
||
|
return GrowWanNetPool (&WanNetPool);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// This function resizes the wan net pool to accomodate additional
|
||
|
// callers.
|
||
|
//
|
||
|
DWORD GrowWanNetPool (WAN_NET_POOL * pPool) {
|
||
|
PWCHAR pszNetList = GlobalConfig.WanNetPoolStr.Buffer;
|
||
|
PNET_ENTRY pNewEntries, pCur;
|
||
|
DWORD i, dwNewSize, dwNewNet;
|
||
|
UCHAR uNetwork[9], *puNetwork;
|
||
|
uNetwork[8] = 0;
|
||
|
puNetwork = uNetwork;
|
||
|
|
||
|
// Enforce that we aren't going to grow beyond our bounds
|
||
|
if (pPool->dwCurSize >= pPool->dwMaxSize)
|
||
|
return ERROR_CAN_NOT_COMPLETE;
|
||
|
|
||
|
// Initialize the new size
|
||
|
if (! pPool->dwCurSize)
|
||
|
dwNewSize = WANNET_DEFAULT_SIZE;
|
||
|
else
|
||
|
dwNewSize = pPool->dwCurSize + WANNET_DEFAULT_GROW;
|
||
|
|
||
|
// Truncate the new size to the maximum size
|
||
|
if (dwNewSize > pPool->dwMaxSize)
|
||
|
dwNewSize = pPool->dwMaxSize;
|
||
|
|
||
|
// Initailize a new array of entries
|
||
|
pNewEntries = GlobalAlloc(GPTR, sizeof(NET_ENTRY) * dwNewSize);
|
||
|
if (pNewEntries == NULL) {
|
||
|
SS_ASSERT(FALSE);
|
||
|
return ERROR_NOT_ENOUGH_MEMORY;
|
||
|
}
|
||
|
|
||
|
// Copy over the old entries
|
||
|
if (pPool->dwCurSize)
|
||
|
CopyMemory (pNewEntries, pPool->pEntries, pPool->dwCurSize * sizeof(NET_ENTRY));
|
||
|
|
||
|
// Go through the new entries verifying them on the network
|
||
|
for(i = pPool->dwCurSize, pCur = &(pNewEntries[pPool->dwCurSize]); i < dwNewSize; i++, pCur++) {
|
||
|
// If the user hadn't given a specific list of addresses,
|
||
|
// then add test the next numeric network number
|
||
|
if ((pszNetList == NULL) || (*pszNetList == L'\0'))
|
||
|
dwNewNet = GlobalConfig.FirstWanNet + i;
|
||
|
|
||
|
// Otherwise, get the next number from the list
|
||
|
else {
|
||
|
dwNewNet = wcstoul (pszNetList, NULL, 16);
|
||
|
pszNetList += wcslen (pszNetList) + 1;
|
||
|
}
|
||
|
|
||
|
// check if this network number is unique. Generate an warning log
|
||
|
// if it isn't
|
||
|
PUTULONG2LONG(pCur->Network, dwNewNet);
|
||
|
if(IsRoute(pCur->Network) || (dwNewNet == 0xFFFFFFFF) || (dwNewNet == 0)) {
|
||
|
NetToAscii(puNetwork, pCur->Network);
|
||
|
RouterLogWarningW(
|
||
|
g_hRouterLog,
|
||
|
ROUTERLOG_IPXCP_WAN_NET_POOL_NETWORK_NUMBER_CONFLICT,
|
||
|
1,
|
||
|
(PWCHAR*)&puNetwork,
|
||
|
NO_ERROR);
|
||
|
|
||
|
TraceIpx(WANNET_TRACE, "InitWanNetConfigDbase: Configured WAN pool network %.2x%.2x%.2x%.2x is in use!\n",
|
||
|
pCur->Network[0], pCur->Network[1], pCur->Network[2], pCur->Network[3]);
|
||
|
pCur->InUse = TRUE;
|
||
|
pPool->dwInUseCount++;
|
||
|
}
|
||
|
else
|
||
|
pCur->InUse = FALSE;
|
||
|
}
|
||
|
|
||
|
// Free the old pool and assign the new one
|
||
|
GlobalFree (pPool->pEntries);
|
||
|
pPool->pEntries = pNewEntries;
|
||
|
pPool->dwCurSize = dwNewSize;
|
||
|
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Function: CreateGlobalWanNet
|
||
|
|
||
|
Descr:
|
||
|
|
||
|
Remark: >> called with database lock held <<
|
||
|
|
||
|
--*/
|
||
|
|
||
|
DWORD
|
||
|
CreateGlobalWanNet(VOID)
|
||
|
{
|
||
|
DWORD rc;
|
||
|
ULONG i;
|
||
|
|
||
|
if(GlobalConfig.EnableAutoWanNetAllocation) {
|
||
|
|
||
|
// create the global wan net "automatically".
|
||
|
// We do that by trying to use the system timer value
|
||
|
rc = GetRandomNetNumber(GlobalConfig.RParams.GlobalWanNet);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
rc = AllocWanNetFromPool(GlobalConfig.RParams.GlobalWanNet,
|
||
|
&GlobalWanNetIndex);
|
||
|
}
|
||
|
|
||
|
if(rc == NO_ERROR) {
|
||
|
|
||
|
// add the global wan net to the routing table if router is started
|
||
|
// if the router is not started, it will get the global wan net when
|
||
|
// it will issue the IpxcpBind call
|
||
|
if(RouterStarted) {
|
||
|
|
||
|
rc =(*RmCreateGlobalRoute)(GlobalConfig.RParams.GlobalWanNet);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
VOID
|
||
|
DestroyWanNetPool(VOID)
|
||
|
{
|
||
|
WAN_NET_POOL DefaultPool = {WANNET_MAXSIZE, 0, 0, NULL};
|
||
|
|
||
|
if (WanNetPool.dwCurSize) {
|
||
|
if (WanNetPool.pEntries)
|
||
|
GlobalFree (WanNetPool.pEntries);
|
||
|
WanNetPool = DefaultPool;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
DWORD
|
||
|
AllocWanNetFromPool(PUCHAR Network,
|
||
|
PULONG NetworkIndexp)
|
||
|
{
|
||
|
ULONG i;
|
||
|
PNET_ENTRY nep;
|
||
|
DWORD rc;
|
||
|
|
||
|
// First, see if we have to grow the pool
|
||
|
if (WanNetPool.dwInUseCount >= WanNetPool.dwCurSize)
|
||
|
GrowWanNetPool (&WanNetPool);
|
||
|
|
||
|
// get a net from the pool
|
||
|
for(i=0, nep=WanNetPool.pEntries; i<WanNetPool.dwCurSize; i++, nep++) {
|
||
|
|
||
|
if(!nep->InUse) {
|
||
|
|
||
|
nep->InUse = TRUE;
|
||
|
WanNetPool.dwInUseCount++;
|
||
|
memcpy(Network, nep->Network, 4);
|
||
|
*NetworkIndexp = i;
|
||
|
|
||
|
rc = NO_ERROR;
|
||
|
goto Exit;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// can't find a free net pool entry
|
||
|
*NetworkIndexp = INVALID_NETWORK_INDEX;
|
||
|
rc = ERROR_CAN_NOT_COMPLETE;
|
||
|
|
||
|
Exit:
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
VOID
|
||
|
FreeWanNetToPool(ULONG AllocatedNetworkIndex)
|
||
|
{
|
||
|
PNET_ENTRY nep;
|
||
|
|
||
|
if(AllocatedNetworkIndex >= WanNetPool.dwCurSize) {
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
nep = &(WanNetPool.pEntries[AllocatedNetworkIndex]);
|
||
|
|
||
|
SS_ASSERT(nep->InUse);
|
||
|
|
||
|
nep->InUse = FALSE;
|
||
|
WanNetPool.dwInUseCount--;
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
DWORD randn(DWORD seed)
|
||
|
{
|
||
|
seed = seed * 1103515245 + 12345;
|
||
|
return seed;
|
||
|
}
|
||
|
|
||
|
|
||
|
DWORD
|
||
|
GetRandomNetNumber(PUCHAR Network)
|
||
|
{
|
||
|
DWORD rc = ERROR_CAN_NOT_COMPLETE;
|
||
|
ULONG i, seed, high, low, netnumber;
|
||
|
|
||
|
for(i=0; i<100000; i++) {
|
||
|
|
||
|
seed = GetTickCount();
|
||
|
|
||
|
// check if this isn't the same seed as last used
|
||
|
if(seed == LastUsedRandSeed) {
|
||
|
|
||
|
seed++;
|
||
|
}
|
||
|
|
||
|
LastUsedRandSeed = seed;
|
||
|
|
||
|
// generate a sequence of two random numbers using the time tick count
|
||
|
// as seed
|
||
|
low = randn(seed) >> 16;
|
||
|
high = randn(randn(seed)) & 0xffff0000;
|
||
|
|
||
|
netnumber = high + low;
|
||
|
|
||
|
PUTULONG2LONG(Network, netnumber);
|
||
|
|
||
|
if(!IsRoute(Network)) {
|
||
|
|
||
|
rc = NO_ERROR;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Reconfigures the wannet database
|
||
|
//
|
||
|
DWORD WanNetReconfigure() {
|
||
|
|
||
|
ACQUIRE_DATABASE_LOCK;
|
||
|
|
||
|
// Destroy the current pool
|
||
|
DestroyWanNetPool();
|
||
|
|
||
|
// Mark everything as unintialized
|
||
|
WanConfigDbaseInitialized = FALSE;
|
||
|
|
||
|
RELEASE_DATABASE_LOCK;
|
||
|
|
||
|
return NO_ERROR;
|
||
|
}
|