645 lines
18 KiB
C++
645 lines
18 KiB
C++
//+------------------------------------------------------------
|
||
//
|
||
// Copyright (C) 1999, Microsoft Corporation
|
||
//
|
||
// File: cnfgmgr.h
|
||
//
|
||
// Contents: Declaration of classes related to the handling on
|
||
// different LDAP host configurations.
|
||
// This includes LDAP failover and load balancing.
|
||
//
|
||
// Classes:
|
||
// CLdapCfgMgr
|
||
// CLdapCfg
|
||
// CLdapHost
|
||
// CCfgConnectionCache
|
||
// CCfgConnection
|
||
//
|
||
// Functions:
|
||
//
|
||
// History:
|
||
// jstamerj 1999/06/15 14:49:52: Created.
|
||
//
|
||
//-------------------------------------------------------------
|
||
#ifndef __CNFGMGR_H__
|
||
#define __CNFGMGR_H__
|
||
|
||
|
||
#include <windows.h>
|
||
#include "asyncctx.h"
|
||
#include <baseobj.h>
|
||
#include <ntdsapi.h>
|
||
#include <dsgetdc.h>
|
||
#include <lmcons.h>
|
||
#include <lmapibuf.h>
|
||
#include <baseobj.h>
|
||
#include "asyncctx.h"
|
||
|
||
class CLdapCfg;
|
||
class CLdapServerCfg;
|
||
class CCfgConectionCache;
|
||
class CCfgConnection;
|
||
|
||
#define CONN_RETRY_TIME (5*60) // 5 Minutes
|
||
typedef DWORD CONN_PRIORITY;
|
||
|
||
enum CONN_STATE {
|
||
CONN_STATE_INITIAL,
|
||
CONN_STATE_CONNECTED,
|
||
CONN_STATE_DOWN,
|
||
CONN_STATE_RETRY,
|
||
};
|
||
|
||
typedef struct _tagLdapServerConfig {
|
||
DWORD dwPort;
|
||
CONN_PRIORITY pri;
|
||
LDAP_BIND_TYPE bt;
|
||
CHAR szHost[CAT_MAX_DOMAIN];
|
||
CHAR szNamingContext[CAT_MAX_DOMAIN];
|
||
CHAR szAccount[CAT_MAX_LOGIN];
|
||
CHAR szPassword[CAT_MAX_PASSWORD];
|
||
} LDAPSERVERCONFIG, *PLDAPSERVERCONFIG;
|
||
|
||
typedef DWORD LDAPSERVERCOST, *PLDAPSERVERCOST;
|
||
|
||
//
|
||
// Connection costs:
|
||
//
|
||
// The smallest unit of cost is the number of pending searches.
|
||
// The next factor of cost is the connection state.
|
||
// States:
|
||
// Connected = + COST_CONNECTED
|
||
// Initially state (unconnected) = + COST_INITIAL
|
||
// Connection down = + COST_RETRY
|
||
// Connection recently went down = + COST_DOWN
|
||
//
|
||
// A configurable priority is always added to the cost.
|
||
//
|
||
#define COST_CONNECTED_LOCAL 0
|
||
#define COST_CONNECTED_REMOTE 2
|
||
#define COST_INITIAL_LOCAL 8
|
||
#define COST_INITIAL_REMOTE 16
|
||
#define COST_RETRY_LOCAL 12
|
||
#define COST_RETRY_REMOTE 32
|
||
#define COST_DOWN_LOCAL 0x80000000
|
||
#define COST_DOWN_REMOTE 0x80000000
|
||
#define COST_TOO_HIGH_TO_CONNECT 0x80000000
|
||
|
||
//
|
||
// The maximum number of threads that will try to connect to a
|
||
// connection in CONN_STATE_RETRY:
|
||
//
|
||
#define MAX_CONNECT_THREADS 1
|
||
|
||
//
|
||
// Requerying of available GC control:
|
||
// The code will rebuild the list of available GCs at a hard coded
|
||
// time interval. The code will also requery for available GCs after
|
||
// a hard coded number of connection failures and a minimum time interval.
|
||
//
|
||
#define REBUILD_GC_LIST_MAX_INTERVAL (60*60) // 1 hour
|
||
#define REBUILD_GC_LIST_MAX_FAILURES (100) // 100 connection failures
|
||
#define REBUILD_GC_LIST_MIN_INTERVAL (60*5) // 5 minutes
|
||
|
||
//
|
||
// An LDAP connection cache object that creates CCfgConnection objects
|
||
//
|
||
class CCfgConnectionCache :
|
||
public CBatchLdapConnectionCache
|
||
{
|
||
public:
|
||
HRESULT GetConnection(
|
||
CCfgConnection **ppConn,
|
||
PLDAPSERVERCONFIG pServerConfig,
|
||
CLdapServerCfg *pCLdapServerConfig);
|
||
|
||
CCachedLdapConnection *CreateCachedLdapConnection(
|
||
LPSTR szHost,
|
||
DWORD dwPort,
|
||
LPSTR szNamingContext,
|
||
LPSTR szAccount,
|
||
LPSTR szPassword,
|
||
LDAP_BIND_TYPE bt,
|
||
PVOID pCreateContext);
|
||
|
||
private:
|
||
#define SIGNATURE_CCFGCONNECTIONCACHE (DWORD)'CCCC'
|
||
#define SIGNATURE_CCFGCONNECTIONCACHE_INVALID (DWORD)'CCCX'
|
||
DWORD m_dwSignature;
|
||
};
|
||
|
||
//
|
||
// CLdapCfgMgr is a wrapper around CLdapCfg. It contains thread save
|
||
// code to build a new CLdapCfg object with a new list of available
|
||
// LDAP servers
|
||
//
|
||
CatDebugClass(CLdapCfgMgr),
|
||
public CBaseObject
|
||
{
|
||
public:
|
||
CLdapCfgMgr(
|
||
BOOL fAutomaticConfigUpdate,
|
||
ICategorizerParameters *pICatParams,
|
||
LDAP_BIND_TYPE bt = BIND_TYPE_NONE,
|
||
LPSTR pszAccount = NULL,
|
||
LPSTR pszPassword = NULL,
|
||
LPSTR pszNamingContext = NULL);
|
||
|
||
//
|
||
// Build a list of all available GCs and initialize
|
||
// This function may be called multiple times (necessary if the
|
||
// available GCs change)
|
||
//
|
||
HRESULT HrInit(
|
||
BOOL fRediscoverGCs = FALSE);
|
||
|
||
//
|
||
// Initialize using a specified list of avialable LDAP servers
|
||
// THis function may be called more than once
|
||
//
|
||
HRESULT HrInit(
|
||
DWORD dwcServers,
|
||
PLDAPSERVERCONFIG prgServerConfig);
|
||
|
||
//
|
||
// Get a connection
|
||
//
|
||
HRESULT HrGetConnection(
|
||
CCfgConnection **ppConn);
|
||
|
||
//
|
||
// Called very often to update the GC configuration if warranted.
|
||
//
|
||
HRESULT HrUpdateConfigurationIfNecessary();
|
||
|
||
//
|
||
// Wrapper to cancel all searches on all connections
|
||
//
|
||
VOID CancelAllConnectionSearches(
|
||
ISMTPServer *pIServer)
|
||
{
|
||
m_LdapConnectionCache.CancelAllConnectionSearches(
|
||
pIServer);
|
||
}
|
||
|
||
|
||
private:
|
||
~CLdapCfgMgr();
|
||
|
||
HRESULT HrGetGCServers(
|
||
IN LDAP_BIND_TYPE bt,
|
||
IN LPSTR pszAccount,
|
||
IN LPSTR pszPassword,
|
||
IN LPSTR pszNamingContext,
|
||
OUT DWORD *pdwcServerConfig,
|
||
OUT PLDAPSERVERCONFIG *pprgServerConfig);
|
||
|
||
HRESULT HrBuildGCServerArray(
|
||
IN LDAP_BIND_TYPE bt,
|
||
IN LPSTR pszAccount,
|
||
IN LPSTR pszPassword,
|
||
IN LPSTR pszNamingContext,
|
||
IN BOOL fRediscoverGCs,
|
||
OUT DWORD *pdwcServerConfig,
|
||
OUT PLDAPSERVERCONFIG *pprgServerConfig);
|
||
|
||
HRESULT HrBuildArrayFromDCInfo(
|
||
IN LDAP_BIND_TYPE bt,
|
||
IN LPSTR pszAccount,
|
||
IN LPSTR pszPassword,
|
||
IN LPSTR pszNamingContext,
|
||
IN DWORD dwcDSDCInfo,
|
||
IN PDS_DOMAIN_CONTROLLER_INFO_2 prgDSDCInfo,
|
||
OUT DWORD *pdwcServerConfig,
|
||
OUT PLDAPSERVERCONFIG *pprgServerConfig);
|
||
|
||
BOOL fReadyForUpdate();
|
||
|
||
LPSTR SzConnectNameFromDomainControllerInfo(
|
||
PDS_DOMAIN_CONTROLLER_INFO_2 pDCInfo)
|
||
{
|
||
if(pDCInfo->DnsHostName)
|
||
return pDCInfo->DnsHostName;
|
||
else if(pDCInfo->NetbiosName)
|
||
return pDCInfo->NetbiosName;
|
||
else
|
||
return NULL;
|
||
}
|
||
|
||
private:
|
||
#define SIGNATURE_CLDAPCFGMGR (DWORD)'MCLC'
|
||
#define SIGNATURE_CLDAPCFGMGR_INVALID (DWORD)'MCLX'
|
||
DWORD m_dwSignature;
|
||
BOOL m_fAutomaticConfigUpdate;
|
||
DWORD m_dwUpdateInProgress;
|
||
ULARGE_INTEGER m_ulLastUpdateTime;
|
||
CExShareLock m_sharelock;
|
||
CLdapCfg *m_pCLdapCfg;
|
||
|
||
//
|
||
// Default configuration to use with automatic host selection
|
||
//
|
||
LDAP_BIND_TYPE m_bt;
|
||
CHAR m_szNamingContext[CAT_MAX_DOMAIN];
|
||
CHAR m_szAccount[CAT_MAX_LOGIN];
|
||
CHAR m_szPassword[CAT_MAX_PASSWORD];
|
||
|
||
ICategorizerParameters *m_pICatParams;
|
||
|
||
CCfgConnectionCache m_LdapConnectionCache;
|
||
};
|
||
|
||
//
|
||
// CLdapCfg contains the configuration of a group of LDAP servers at
|
||
// one point in time. The group of LDAP servers may not be changed
|
||
// (without creating a new CLdapCfg object)
|
||
//
|
||
CatDebugClass(CLdapCfg),
|
||
public CBaseObject
|
||
{
|
||
public:
|
||
CLdapCfg();
|
||
|
||
void * operator new(size_t size, DWORD dwcServers);
|
||
//
|
||
// HrInit should only be called once per object
|
||
//
|
||
HRESULT HrInit(
|
||
DWORD dwcServers,
|
||
PLDAPSERVERCONFIG prgServerConfig,
|
||
CLdapCfg *pCLdapCfgOld);
|
||
|
||
//
|
||
// Get a connection
|
||
//
|
||
HRESULT HrGetConnection(
|
||
CCfgConnection **ppConn,
|
||
CCfgConnectionCache *pLdapConnectionCache);
|
||
|
||
DWORD DwNumConnectionFailures()
|
||
{
|
||
return m_dwcConnectionFailures;
|
||
}
|
||
DWORD DwNumServers()
|
||
{
|
||
return m_dwcServers;
|
||
}
|
||
|
||
private:
|
||
~CLdapCfg();
|
||
|
||
VOID ShuffleArray();
|
||
|
||
private:
|
||
#define SIGNATURE_CLDAPCFG (DWORD)'fCLC'
|
||
#define SIGNATURE_CLDAPCFG_INVALID (DWORD)'fCLX'
|
||
|
||
DWORD m_dwSignature;
|
||
DWORD m_dwInc;
|
||
CExShareLock m_sharelock; // Protects m_prgpCLdapServerCfg
|
||
DWORD m_dwcServers;
|
||
DWORD m_dwcConnectionFailures;
|
||
CLdapServerCfg **m_prgpCLdapServerCfg;
|
||
};
|
||
|
||
//
|
||
// CLdapServerCfg maintains information on the state of one LDAP
|
||
// server/port
|
||
//
|
||
CatDebugClass(CLdapServerCfg)
|
||
{
|
||
public:
|
||
static VOID GlobalInit()
|
||
{
|
||
InitializeListHead(&m_listhead);
|
||
}
|
||
static HRESULT GetServerCfg(
|
||
IN PLDAPSERVERCONFIG pServerConfig,
|
||
OUT CLdapServerCfg **ppCLdapServerCfg);
|
||
|
||
LONG AddRef()
|
||
{
|
||
return InterlockedIncrement(&m_lRefCount);
|
||
}
|
||
LONG Release()
|
||
{
|
||
LONG lRet;
|
||
lRet = InterlockedDecrement(&m_lRefCount);
|
||
if(lRet == 0) {
|
||
//
|
||
// Remove object from global list and destroy
|
||
//
|
||
m_listlock.ExclusiveLock();
|
||
|
||
if(m_lRefCount > 0) {
|
||
//
|
||
// Somebody grabbed this object out of the global list
|
||
// and AddRef'd it. Abort deletion.
|
||
//
|
||
} else {
|
||
|
||
RemoveEntryList(&m_le);
|
||
delete this;
|
||
}
|
||
m_listlock.ExclusiveUnlock();
|
||
}
|
||
return lRet;
|
||
}
|
||
|
||
//
|
||
// Get a connection
|
||
//
|
||
HRESULT HrGetConnection(
|
||
CCfgConnection **ppConn,
|
||
CCfgConnectionCache *pLdapConnectionCache);
|
||
|
||
VOID Cost(
|
||
OUT PLDAPSERVERCOST pCost);
|
||
|
||
VOID IncrementPendingSearches()
|
||
{
|
||
DWORD dwcSearches;
|
||
TraceFunctEnterEx((LPARAM)this, "CLdapServerCfg::IncrementPendingSearches");
|
||
dwcSearches = (LONG) InterlockedIncrement((PLONG)&m_dwcPendingSearches);
|
||
DebugTrace((LPARAM)this, "%ld pending searches on connection [%s:%d]",
|
||
dwcSearches, m_ServerConfig.szHost, m_ServerConfig.dwPort);
|
||
TraceFunctLeaveEx((LPARAM)this);
|
||
}
|
||
VOID DecrementPendingSearches()
|
||
{
|
||
DWORD dwcSearches;
|
||
TraceFunctEnterEx((LPARAM)this, "CLdapServerCfg::IncrementPendingSearches");
|
||
dwcSearches = (DWORD) InterlockedDecrement((PLONG)&m_dwcPendingSearches);
|
||
DebugTrace((LPARAM)this, "%ld pending searches on connection [%s:%d]",
|
||
dwcSearches, m_ServerConfig.szHost, m_ServerConfig.dwPort);
|
||
TraceFunctLeaveEx((LPARAM)this);
|
||
}
|
||
|
||
VOID UpdateConnectionState(
|
||
ULARGE_INTEGER *pft,
|
||
CONN_STATE connstate);
|
||
|
||
VOID IncrementFailedCount()
|
||
{
|
||
InterlockedIncrement((PLONG) &m_dwcFailedConnectAttempts);
|
||
}
|
||
VOID ResetFailedCount()
|
||
{
|
||
InterlockedExchange((PLONG) &m_dwcFailedConnectAttempts, 0);
|
||
}
|
||
CONN_STATE CurrentState()
|
||
{
|
||
return m_connstate;
|
||
}
|
||
ULARGE_INTEGER GetCurrentTime()
|
||
{
|
||
ULARGE_INTEGER FileTime;
|
||
|
||
_ASSERT(sizeof(ULARGE_INTEGER) == sizeof(FILETIME));
|
||
GetSystemTimeAsFileTime((LPFILETIME)&FileTime);
|
||
return FileTime;
|
||
}
|
||
private:
|
||
CLdapServerCfg();
|
||
~CLdapServerCfg();
|
||
|
||
HRESULT HrInit(
|
||
PLDAPSERVERCONFIG pServerConfig);
|
||
|
||
BOOL fReadyForRetry()
|
||
{
|
||
// 100 nanoseconds * 10^7 == 1 second
|
||
return ((GetCurrentTime().QuadPart - m_ftLastStateUpdate.QuadPart) >=
|
||
((LONGLONG)CONN_RETRY_TIME * 10000000));
|
||
}
|
||
|
||
BOOL fMatch(
|
||
PLDAPSERVERCONFIG pServerConfig);
|
||
|
||
static CLdapServerCfg *FindServerCfg(
|
||
PLDAPSERVERCONFIG pServerConfig);
|
||
|
||
static BOOL fIsLocalComputer(
|
||
PLDAPSERVERCONFIG pServerConfig);
|
||
|
||
private:
|
||
#define SIGNATURE_CLDAPSERVERCFG (DWORD)'CSLC'
|
||
#define SIGNATURE_CLDAPSERVERCFG_INVALID (DWORD)'CSLX'
|
||
DWORD m_dwSignature;
|
||
LONG m_lRefCount;
|
||
LDAPSERVERCONFIG m_ServerConfig;
|
||
CExShareLock m_sharelock;
|
||
CONN_STATE m_connstate;
|
||
ULARGE_INTEGER m_ftLastStateUpdate;
|
||
DWORD m_dwcPendingSearches;
|
||
DWORD m_dwcCurrentConnectAttempts;
|
||
DWORD m_dwcFailedConnectAttempts;
|
||
|
||
//
|
||
// Member variables to keep/protect a list of CLdapServer objects
|
||
//
|
||
LIST_ENTRY m_le;
|
||
BOOL m_fLocalServer;
|
||
static CExShareLock m_listlock;
|
||
static LIST_ENTRY m_listhead;
|
||
};
|
||
|
||
//
|
||
// An LDAP connection that notifies CLdapServerCfg about state changes
|
||
//
|
||
class CCfgConnection :
|
||
public CBatchLdapConnection
|
||
{
|
||
#define SIGNATURE_CCFGCONNECTION (DWORD)'oCCC'
|
||
#define SIGNATURE_CCFGCONNECTION_INVALID (DWORD)'oCCX'
|
||
public:
|
||
CCfgConnection(
|
||
LPSTR szHost,
|
||
DWORD dwPort,
|
||
LPSTR szNamingContext,
|
||
LPSTR szAccount,
|
||
LPSTR szPassword,
|
||
LDAP_BIND_TYPE bt,
|
||
CLdapConnectionCache *pCache,
|
||
CLdapServerCfg *pCLdapServerCfg) :
|
||
CBatchLdapConnection(
|
||
szHost,
|
||
dwPort,
|
||
szNamingContext,
|
||
szAccount,
|
||
szPassword,
|
||
bt,
|
||
pCache)
|
||
{
|
||
m_dwSignature = SIGNATURE_CCFGCONNECTION;
|
||
m_pCLdapServerCfg = pCLdapServerCfg;
|
||
pCLdapServerCfg->AddRef();
|
||
|
||
m_connstate = CONN_STATE_INITIAL;
|
||
}
|
||
|
||
~CCfgConnection()
|
||
{
|
||
_ASSERT(m_pCLdapServerCfg);
|
||
m_pCLdapServerCfg->Release();
|
||
|
||
_ASSERT(m_dwSignature == SIGNATURE_CCFGCONNECTION);
|
||
m_dwSignature = SIGNATURE_CCFGCONNECTION_INVALID;
|
||
}
|
||
|
||
virtual HRESULT Connect();
|
||
|
||
virtual HRESULT AsyncSearch( // Asynchronously look up
|
||
LPCWSTR szBaseDN, // objects matching specified
|
||
int nScope, // criteria in the DS. The
|
||
LPCWSTR szFilter, // results are passed to
|
||
LPCWSTR szAttributes[], // fnCompletion when they
|
||
DWORD dwPageSize, // Optinal page size
|
||
LPLDAPCOMPLETION fnCompletion, // become available.
|
||
LPVOID ctxCompletion);
|
||
|
||
private:
|
||
virtual VOID CallCompletion(
|
||
PPENDING_REQUEST preq,
|
||
PLDAPMessage pres,
|
||
HRESULT hrStatus,
|
||
BOOL fFinalCompletion);
|
||
|
||
VOID NotifyServerDown();
|
||
|
||
private:
|
||
DWORD m_dwSignature;
|
||
CLdapServerCfg *m_pCLdapServerCfg;
|
||
CExShareLock m_sharelock;
|
||
CONN_STATE m_connstate;
|
||
};
|
||
|
||
|
||
//+------------------------------------------------------------
|
||
//
|
||
// Function: CLdapCfgMgr::HrUpdateConfigurationIfNecessary
|
||
//
|
||
// Synopsis: Check to see if the CLdapCfg should be updated.
|
||
// If it should be, do the update.
|
||
//
|
||
// Arguments: NONE
|
||
//
|
||
// Returns:
|
||
// S_OK: Success
|
||
// error from HrInit
|
||
//
|
||
// History:
|
||
// jstamerj 1999/06/29 20:51:23: Created.
|
||
//
|
||
//-------------------------------------------------------------
|
||
inline HRESULT CLdapCfgMgr::HrUpdateConfigurationIfNecessary()
|
||
{
|
||
HRESULT hr = S_OK;
|
||
DWORD dw;
|
||
BOOL fUpdate;
|
||
|
||
if(m_fAutomaticConfigUpdate == FALSE)
|
||
//
|
||
// Update is disabled
|
||
return S_OK;
|
||
|
||
//
|
||
// See if some other thread is already updating the configuration
|
||
// (try to enter the lock)
|
||
//
|
||
dw = InterlockedExchange((PLONG)&m_dwUpdateInProgress, TRUE);
|
||
|
||
if(dw == FALSE) {
|
||
//
|
||
// No other thread is updating
|
||
//
|
||
fUpdate = fReadyForUpdate();
|
||
|
||
if(fUpdate) {
|
||
//
|
||
// Call HrInit to generate a new CLdapCfg
|
||
//
|
||
hr = HrInit(TRUE);
|
||
if(SUCCEEDED(hr)) {
|
||
//
|
||
// Set the last update time
|
||
//
|
||
GetSystemTimeAsFileTime((LPFILETIME)&m_ulLastUpdateTime);
|
||
}
|
||
}
|
||
//
|
||
// Release the lock
|
||
//
|
||
InterlockedExchange((PLONG)&m_dwUpdateInProgress, FALSE);
|
||
}
|
||
return hr;
|
||
} // CLdapCfgMgr::HrUpdateConfigurationIfNecessary
|
||
|
||
|
||
//+------------------------------------------------------------
|
||
//
|
||
// Function: CLdapCfgMgr::fReadyForUpdate
|
||
//
|
||
// Synopsis: Calculates wether or not this object is due for an
|
||
// automatic cfg update
|
||
//
|
||
// Arguments: NONE
|
||
//
|
||
// Returns:
|
||
// TRUE: Yes, it is time for an update
|
||
// FALSE: No, an update is not required at this time
|
||
//
|
||
// History:
|
||
// jstamerj 1999/06/30 12:08:35: Created.
|
||
//
|
||
//-------------------------------------------------------------
|
||
inline BOOL CLdapCfgMgr::fReadyForUpdate()
|
||
{
|
||
DWORD dwNumConnectionFailures;
|
||
ULARGE_INTEGER ulCurrentTime;
|
||
|
||
//
|
||
// We need an update when:
|
||
// 1) A periodic time interval has ellapsed
|
||
// 100 ns * 10^7 == 1 second
|
||
//
|
||
GetSystemTimeAsFileTime((LPFILETIME)&ulCurrentTime);
|
||
|
||
if((ulCurrentTime.QuadPart - m_ulLastUpdateTime.QuadPart) >=
|
||
(LONGLONG)REBUILD_GC_LIST_MAX_INTERVAL * 10000000)
|
||
|
||
return TRUE;
|
||
|
||
//
|
||
// We also need an update when:
|
||
// 2) We have received more than a set number of connection
|
||
// failures on the current configuration and at least a minimum
|
||
// time interval has passed
|
||
//
|
||
// Check for the mimimum time interval
|
||
//
|
||
if( (ulCurrentTime.QuadPart - m_ulLastUpdateTime.QuadPart) >=
|
||
((LONGLONG)REBUILD_GC_LIST_MIN_INTERVAL * 10000000)) {
|
||
//
|
||
// Get the number of connection failures
|
||
//
|
||
m_sharelock.ShareLock();
|
||
|
||
if(m_pCLdapCfg) {
|
||
dwNumConnectionFailures = m_pCLdapCfg->DwNumConnectionFailures();
|
||
} else {
|
||
dwNumConnectionFailures = 0;
|
||
_ASSERT(0 && "HrInit was not called or failed");
|
||
}
|
||
|
||
m_sharelock.ShareUnlock();
|
||
|
||
if(dwNumConnectionFailures >= REBUILD_GC_LIST_MAX_FAILURES)
|
||
return TRUE;
|
||
}
|
||
|
||
return FALSE;
|
||
} // CLdapCfgMgr::fReadyForUpdate
|
||
|
||
#endif //__CNFGMGR_H__
|