Leaked source code of windows server 2003
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.
 
 
 
 
 
 

3714 lines
108 KiB

//
// ldapconn.cpp -- This file contains the class implementation for:
// CLdapConnection
// CLdapConnectionCache
//
// Created:
// Dec 31, 1996 -- Milan Shah (milans)
//
// Changes:
//
#include "precomp.h"
#include "ldapconn.h"
#include "icatitemattr.h"
#define SECURITY_WIN32
#include "security.h"
LDAP_TIMEVAL CLdapConnection::m_ldaptimeout = { LDAPCONN_DEFAULT_RESULT_TIMEOUT, 0 };
DWORD CLdapConnection::m_dwLdapRequestTimeLimit = DEFAULT_LDAP_REQUEST_TIME_LIMIT;
//
// LDAP counter block
//
CATLDAPPERFBLOCK g_LDAPPerfBlock;
//+----------------------------------------------------------------------------
//
// Function: CLdapConnection::CLdapConnection
//
// Synopsis: Constructor for a CLdapConnection object.
//
// Arguments: [szHost] -- The actual name of the LDAP host to connect to.
// If it is NULL, and we are running on an NT5 machine, we'll
// use the default DC
//
// [dwPort] -- The remote tcp port to connect to. If
// zero, LDAP_PORT is assumed
//
// [szNamingContext] -- The naming context to use within the
// LDAP DS. If NULL, the naming context will be determined
// by using the default naming context of the LDAP DS.
//
// By allowing a naming context to be associated with an
// ldap connection, we can have multiple "logical" ldap
// connections served by the same LDAP DS. This is useful
// if folks want to setup mutliple virtual SMTP/POP3 servers
// all served by the same LDAP DS. The naming context in
// that case would be the name of the OU to restrict the
// DS operations to.
//
// [szAccount] -- The DN of the account to log in as.
//
// [szPassword] -- The password to use to log in.
//
// [bt] -- The bind method to use. (none, simple, or generic)
//
// Returns: Nothing
//
//-----------------------------------------------------------------------------
CLdapConnection::CLdapConnection(
IN LPSTR szHost,
IN DWORD dwPort,
IN LPSTR szNamingContext,
IN LPSTR szAccount,
IN LPSTR szPassword,
IN LDAP_BIND_TYPE bt)
{
int i;
CatFunctEnter( "CLdapConnection::CLdapConnection" );
m_dwSignature = SIGNATURE_LDAPCONN;
m_pCPLDAPWrap = NULL;
m_fValid = TRUE;
m_fTerminating = FALSE;
if (szNamingContext != NULL && szNamingContext[0] != 0) {
_ASSERT(strlen(szNamingContext) < sizeof(m_szNamingContext) );
strcpy(m_szNamingContext, szNamingContext);
m_fDefaultNamingContext = FALSE;
i = MultiByteToWideChar(
CP_UTF8,
0,
m_szNamingContext,
-1,
m_wszNamingContext,
sizeof(m_wszNamingContext) / sizeof(m_wszNamingContext[0]));
_ASSERT(i > 0);
} else {
m_szNamingContext[0] = 0;
m_wszNamingContext[0] = 0;
m_fDefaultNamingContext = TRUE;
}
_ASSERT( (szHost != NULL) &&
(strlen(szHost) < sizeof(m_szHost)) );
_ASSERT( (bt == BIND_TYPE_NONE) ||
(bt == BIND_TYPE_CURRENTUSER) ||
((szAccount != NULL) &&
(szAccount[0] != 0) &&
(strlen(szAccount) < sizeof(m_szAccount)))
);
_ASSERT( (bt == BIND_TYPE_NONE) ||
(bt == BIND_TYPE_CURRENTUSER) ||
((szPassword != NULL) &&
(strlen(szPassword) < sizeof(m_szPassword))) );
strcpy(m_szHost, szHost);
SetPort(dwPort);
if ((bt != BIND_TYPE_NONE) &&
(bt != BIND_TYPE_CURRENTUSER)) {
strcpy(m_szAccount, szAccount);
strcpy(m_szPassword, szPassword);
} else {
m_szAccount[0] = 0;
m_szPassword[0] = 0;
}
m_bt = bt;
//
// Initialize the async search completion structures
//
InitializeSpinLock( &m_spinlockCompletion );
// InitializeCriticalSection( &m_cs );
m_hCompletionThread = INVALID_HANDLE_VALUE;
m_hOutstandingRequests = INVALID_HANDLE_VALUE;
m_pfTerminateCompletionThreadIndicator = NULL;
InitializeListHead( &m_listPendingRequests );
m_fCancel = FALSE;
m_dwRefCount = 1;
m_dwDestructionWaiters = 0;
m_hShutdownEvent = INVALID_HANDLE_VALUE;
CatFunctLeave();
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnection::~CLdapConnection
//
// Synopsis: Destructor for a CLdapConnection object
//
// Arguments: None
//
// Returns: Nothing.
//
//-----------------------------------------------------------------------------
CLdapConnection::~CLdapConnection()
{
CatFunctEnter( "CLdapConnection::~CLdapConnection" );
_ASSERT(m_dwSignature == SIGNATURE_LDAPCONN);
//
// Disconnect
//
if (m_pCPLDAPWrap != NULL) {
Disconnect();
}
if (m_hOutstandingRequests != INVALID_HANDLE_VALUE)
CloseHandle( m_hOutstandingRequests );
if (m_hShutdownEvent != INVALID_HANDLE_VALUE)
CloseHandle( m_hShutdownEvent );
// DeleteCriticalSection( &m_cs );
m_dwSignature = SIGNATURE_LDAPCONN_INVALID;
CatFunctLeave();
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnection::~CLdapConnection
//
// Synopsis: Called on the last release
//
// Arguments: None
//
// Returns: Nothing.
//
//-----------------------------------------------------------------------------
VOID CLdapConnection::FinalRelease()
{
CancelAllSearches();
//
// If there was an async completion thread, we need to indicate to it that
// it should exit. That thread could be stuck at one of two points -
// either it is waiting on the m_hOutstandingRequests semaphore to be
// fired, or it is blocked on ldap_result(). So, we set the event and
// close out m_pldap, then we wait for the async completion thread to
// quit.
//
SetTerminateIndicatorTrue();
if (m_hOutstandingRequests != INVALID_HANDLE_VALUE) {
LONG nUnused;
ReleaseSemaphore(m_hOutstandingRequests, 1, &nUnused);
}
//
// We do not wait for the LdapCompletionThread to die if it is the
// LdapCompletionThread itself that is deleting us. If we did, it would
// cause a deadlock.
//
if (m_hCompletionThread != INVALID_HANDLE_VALUE) {
if (m_idCompletionThread != GetCurrentThreadId()) {
WaitForSingleObject( m_hCompletionThread, INFINITE );
}
CloseHandle( m_hCompletionThread );
}
delete this;
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnection::InitializeFromRegistry
//
// Synopsis: Reads registry-configurable parameters to initialize
// LDAP connection parameters.
//
// LDAPCONN_RESULT_TIMEOUT_VALUE is used as the timeout value
// passed into ldap_result.
//
// LDAP_REQUEST_TIME_LIMIT_VALUE is used to set the search time
// limit option on the connection and also for the expiration time
// on pending search requests.
//
// Arguments: None
//
// Returns: TRUE if successfully connected, FALSE otherwise.
//
//-----------------------------------------------------------------------------
VOID CLdapConnection::InitializeFromRegistry()
{
HKEY hkey;
DWORD dwErr, dwType, dwValue, cbValue;
dwErr = RegOpenKey(HKEY_LOCAL_MACHINE, LDAPCONN_RESULT_TIMEOUT_KEY, &hkey);
if (dwErr == ERROR_SUCCESS) {
cbValue = sizeof(dwValue);
dwErr = RegQueryValueEx(
hkey,
LDAPCONN_RESULT_TIMEOUT_VALUE,
NULL,
&dwType,
(LPBYTE) &dwValue,
&cbValue);
if ((dwErr == ERROR_SUCCESS) && (dwType == REG_DWORD) && (dwValue > 0))
m_ldaptimeout.tv_sec = dwValue;
cbValue = sizeof(dwValue);
dwErr = RegQueryValueEx(
hkey,
LDAP_REQUEST_TIME_LIMIT_VALUE,
NULL,
&dwType,
(LPBYTE) &dwValue,
&cbValue);
if ((dwErr == ERROR_SUCCESS) && (dwType == REG_DWORD) && (dwValue > 0))
m_dwLdapRequestTimeLimit = dwValue;
RegCloseKey( hkey );
}
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnection::Connect
//
// Synopsis: Establishes a connection to the LDAP host and, if a naming
// context has not been established, asks the host for the
// default naming context.
//
// Arguments: None
//
// Returns: TRUE if successfully connected, FALSE otherwise.
//
//-----------------------------------------------------------------------------
HRESULT CLdapConnection::Connect()
{
CatFunctEnter( "CLdapConnection::Connect" );
DWORD ldapErr = LDAP_SUCCESS; // innocent until proven...
LPSTR pszHost = (m_szHost[0] == '\0' ? NULL : m_szHost);
if (m_pCPLDAPWrap == NULL) {
DebugTrace(LDAP_CONN_DBG, "Connecting to [%s:%d]",
pszHost ? pszHost : "NULL",
m_dwPort);
m_pCPLDAPWrap = new CPLDAPWrap( GetISMTPServerEx(), pszHost, m_dwPort);
if(m_pCPLDAPWrap == NULL) {
HRESULT hr = E_OUTOFMEMORY;
ERROR_LOG("new CPLDAPWrap");
}
if((m_pCPLDAPWrap != NULL) &&
(m_pCPLDAPWrap->GetPLDAP() == NULL)) {
//
// Failure to connect; release
//
m_pCPLDAPWrap->Release();
m_pCPLDAPWrap = NULL;
}
DebugTrace(LDAP_CONN_DBG, "ldap_open returned 0x%x", m_pCPLDAPWrap);
if (m_pCPLDAPWrap != NULL) {
INCREMENT_LDAP_COUNTER(Connections);
INCREMENT_LDAP_COUNTER(OpenConnections);
//
// First, set some options - no autoreconnect, and no chasing of
// referrals
//
ULONG ulLdapOff = (ULONG)((ULONG_PTR)LDAP_OPT_OFF);
ULONG ulLdapRequestTimeLimit = m_dwLdapRequestTimeLimit;
ULONG ulLdapVersion = LDAP_VERSION3;
ldap_set_option(
GetPLDAP(), LDAP_OPT_REFERRALS, (LPVOID) &ulLdapOff);
ldap_set_option(
GetPLDAP(), LDAP_OPT_AUTO_RECONNECT, (LPVOID) &ulLdapOff);
ldap_set_option(
GetPLDAP(), LDAP_OPT_TIMELIMIT, (LPVOID) &ulLdapRequestTimeLimit);
ldap_set_option(
GetPLDAP(), LDAP_OPT_PROTOCOL_VERSION, (LPVOID) &ulLdapVersion);
ldapErr = BindToHost( GetPLDAP(), m_szAccount, m_szPassword);
DebugTrace(LDAP_CONN_DBG, "BindToHost returned 0x%x", ldapErr);
} else {
INCREMENT_LDAP_COUNTER(ConnectFailures);
ldapErr = LDAP_SERVER_DOWN;
}
//
// Figure out the naming context for this connection if none was
// initially specified and we are using the default LDAP_PORT
// (a baseDN of "" is acceptable on other LDAP ports such as
// a GC)
//
if ((m_dwPort == LDAP_PORT) &&
(ldapErr == LDAP_SUCCESS) &&
(m_szNamingContext[0] == 0)) {
ldapErr = GetDefaultNamingContext();
if (ldapErr != LDAP_SUCCESS)
Disconnect();
} // end if port 389, successful bind and no naming context
} else { // end if we didn't have a connection already
DebugTrace(
LDAP_CONN_DBG,
"Already connected to %s:%d, pldap = 0x%x",
m_szHost, m_dwPort, GetPLDAP());
}
DebugTrace(LDAP_CONN_DBG, "Connect status = 0x%x", ldapErr);
if (ldapErr != LDAP_SUCCESS) {
m_fValid = FALSE;
CatFunctLeave();
return( LdapErrorToHr( ldapErr) );
} else {
CatFunctLeave();
return( S_OK );
}
}
//+------------------------------------------------------------
//
// Function: CLdapConnection::GetDefaultNamingContext
//
// Synopsis: Gets the default naming context from the LDAP server that
// we are connected to. Note: this should only be called from
// Connect. It is not gaurenteed to be multi-thread safe, and it may
// not work when there is an LdapCompletionThread for this connection.
//
// Arguments: None
//
// Returns:
// LDAP_SUCCESS: fetched m_szNamingContext successfully
// else, an LDAP error describing why we couldn't get a naming context
//
// History:
// jstamerj 2002/04/16 14:36:28: Created.
//
//-------------------------------------------------------------
ULONG CLdapConnection::GetDefaultNamingContext()
{
ULONG ldapErr = LDAP_SUCCESS;
PLDAPMessage pmsg = NULL;
PLDAPMessage pentry = NULL;
LPWSTR rgszAttributes[2] = { L"defaultNamingContext", NULL };
LPWSTR *rgszValues = NULL;
int i = 0;
CatFunctEnterEx((LPARAM)this, "CLdapConnection::GetDefaultNamingContext");
ldapErr = ldap_search_sW(
GetPLDAP(), // ldap binding
L"", // base DN
LDAP_SCOPE_BASE, // scope of search
L"(objectClass=*)", // filter,
rgszAttributes, // attributes required
FALSE, // attributes-only is false
&pmsg);
DebugTrace(
LDAP_CONN_DBG,
"Search for namingContexts returned 0x%x",
ldapErr);
// If the search succeeded
if ((ldapErr == LDAP_SUCCESS) &&
// and there is at least one entry
((pentry = ldap_first_entry(GetPLDAP(), pmsg)) != NULL) &&
// and there are values
((rgszValues = ldap_get_valuesW(GetPLDAP(), pentry,
rgszAttributes[0])) != NULL) &&
// and there is at least one value
(ldap_count_valuesW(rgszValues) != 0) &&
// and the length of that value is within limits
(wcslen(rgszValues[0]) <
sizeof(m_wszNamingContext)/sizeof(WCHAR)) &&
// and the UTF8 conversion succeeds
(WideCharToMultiByte(
CP_UTF8,
0,
rgszValues[0],
-1,
m_szNamingContext,
sizeof(m_szNamingContext),
NULL,
NULL) > 0))
{
//
// Use the first value for our naming context.
//
wcscpy(m_wszNamingContext, rgszValues[0]);
DebugTrace(
LDAP_CONN_DBG,
"NamingContext is [%s]",
m_szNamingContext);
} else {
HRESULT hr = ldapErr; // Used by ERROR_LOG
ERROR_LOG("ldap_search_sW");
ldapErr = LDAP_OPERATIONS_ERROR;
}
if (rgszValues != NULL)
ldap_value_freeW( rgszValues );
if (pmsg != NULL)
ldap_msgfree( pmsg );
CatFunctLeaveEx((LPARAM)this);
return ldapErr;
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnection::Disconnect
//
// Synopsis: Disconnects from the ldap host
//
// Arguments: None
//
// Returns: Nothing
//
//-----------------------------------------------------------------------------
VOID CLdapConnection::Disconnect()
{
BOOL fValid;
CatFunctEnter("CLdapConnection::Disconnect");
if (m_pCPLDAPWrap != NULL) {
SetTerminateIndicatorTrue();
fValid = InterlockedExchange((PLONG) &m_fValid, FALSE);
m_pCPLDAPWrap->Release();
m_pCPLDAPWrap = NULL;
if( fValid ) {
DECREMENT_LDAP_COUNTER(OpenConnections);
}
}
CatFunctLeave();
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnection::Invalidate
//
// Synopsis: Marks this connection invalid. Once this is done, it will
// return FALSE from all calls to IsEqual, thus effectively
// removing itself from all searches for cached connections.
//
// Arguments: None
//
// Returns: Nothing
//
//-----------------------------------------------------------------------------
VOID CLdapConnection::Invalidate()
{
BOOL fValid;
fValid = InterlockedExchange((PLONG) &m_fValid, FALSE);
if( fValid ) {
DECREMENT_LDAP_COUNTER(OpenConnections);
}
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnection::IsValid
//
// Synopsis: Returns whether the connection is valid or not.
//
// Arguments: None
//
// Returns: TRUE if valid, FALSE if a call to Invalidate has been made.
//
//-----------------------------------------------------------------------------
BOOL CLdapConnection::IsValid()
{
return( m_fValid );
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnection::BindToHost
//
// Synopsis: Creates a binding to the LDAP host using the given account
// and password.
//
// Arguments: [pldap] -- The ldap connection to bind.
// [szAccount] -- The account to use. Of the form "account-name"
// or "domain\account-name".
// [szPassword] -- The password to use.
//
// Returns: LDAP result of bind.
//
//-----------------------------------------------------------------------------
DWORD CLdapConnection::BindToHost(
PLDAP pldap,
LPSTR szAccount,
LPSTR szPassword)
{
CatFunctEnter( "CLdapConnection::BindToHost" );
DWORD ldapErr;
char szDomain[ DNLEN + 1];
LPSTR pszDomain, pszUser;
HANDLE hToken; // LogonUser modifies hToken
BOOL fLogon = FALSE; // even if it fails! So, we
// have to look at the result
// of LogonUser!
//
// If this connection was created with anonymous access rights, there is
// no bind action to do.
//
if (m_bt == BIND_TYPE_NONE) {
ldapErr = ERROR_SUCCESS;
goto Cleanup;
}
//
// If we are supposed to use simple bind, do it now
//
if (m_bt == BIND_TYPE_SIMPLE) {
ldapErr = ldap_simple_bind_s(pldap,szAccount, szPassword);
DebugTrace(0, "ldap_simple_bind returned 0x%x", ldapErr);
if(ldapErr != LDAP_SUCCESS)
LogLdapError(ldapErr, "ldap_simple_bind_s(pldap,\"%s\",szPassword), PLDAP = 0x%08lx", szAccount, pldap);
goto Cleanup;
}
//
// If we are supposed to logon with current credetials, do it now.
//
if (m_bt == BIND_TYPE_CURRENTUSER) {
//-------------------------------------------------------------------
// X5: TBD
// This is the normal case for Exchange services. We are connecting
// as LocalSystem, so we must use Kerberos (this is true for the LDAP
// server as of Win2000 SP1).
// If we cannot bind as Kerberos, LDAP_AUTH_NEGOTIATE may negotiate
// down to NTLM, at which point we become anonymous, and the bind
// succeeds. Anonymous binding is useless to Exchange, so we would
// rather force Kerberos and fail if Kerberos has a problem. Use a
// SEC_WINNT_AUTH_IDENTITY_EX to specify that only Kerberos auth
// should be tried.
//-------------------------------------------------------------------
SEC_WINNT_AUTH_IDENTITY_EX authstructex;
ZeroMemory (&authstructex, sizeof(authstructex));
authstructex.Version = SEC_WINNT_AUTH_IDENTITY_VERSION;
authstructex.Length = sizeof (authstructex);
authstructex.PackageList = (PUCHAR) MICROSOFT_KERBEROS_NAME_A;
authstructex.PackageListLength = strlen ((PCHAR) authstructex.PackageList);
authstructex.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
ldapErr = ldap_bind_s(pldap,
NULL,
(PCHAR) &authstructex,
LDAP_AUTH_NEGOTIATE);
DebugTrace(0, "ldap_bind returned 0x%x", ldapErr);
if(ldapErr != LDAP_SUCCESS)
LogLdapError(ldapErr, "ldap_bind_s(pldap, NULL, &authstructex, LDAP_AUTH_NEGOTIATE), PLDAP = 0x%08lx", pldap);
goto Cleanup;
}
//
// Parse out the domain and user names from the szAccount parameter.
//
if ((pszUser = strchr(szAccount, '\\')) == NULL) {
pszUser = szAccount;
pszDomain = NULL;
} else {
ULONG cbDomain = (ULONG)(((ULONG_PTR) pszUser) - ((ULONG_PTR) szAccount));
if(cbDomain < sizeof(szDomain)) {
strncpy( szDomain, szAccount, cbDomain);
szDomain[cbDomain] = '\0';
} else {
ldapErr = LDAP_INVALID_CREDENTIALS;
goto Cleanup;
}
pszDomain = cbDomain > 0 ? szDomain : NULL;
pszUser++; // Go past the backslash
}
//
// Logon as the given user, impersonate, and attempt the LDAP bind.
//
fLogon = LogonUser(pszUser, pszDomain, szPassword, LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, &hToken);
if(!fLogon) {
HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
ERROR_LOG("LogonUser");
} else {
fLogon = ImpersonateLoggedOnUser(hToken);
if(!fLogon) {
HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
ERROR_LOG("ImpersonateLoggedOnUser");
}
}
if (fLogon) {
ldapErr = ldap_bind_s(pldap, NULL, NULL, LDAP_AUTH_SSPI);
DebugTrace(0, "ldap_bind returned 0x%x", ldapErr);
if(ldapErr != LDAP_SUCCESS)
LogLdapError(ldapErr, "ldap_bind_s(pldap, NULL, NULL, LDAP_AUTH_SSPI), PLDAP = 0x%08lx", pldap);
RevertToSelf();
} else {
if (GetLastError() == ERROR_PRIVILEGE_NOT_HELD)
ldapErr = LDAP_INSUFFICIENT_RIGHTS;
else
ldapErr = LDAP_INVALID_CREDENTIALS;
}
Cleanup:
if (fLogon)
CloseHandle( hToken );
//
// Increment counters
//
if(m_bt != BIND_TYPE_NONE) {
if(ldapErr == ERROR_SUCCESS) {
INCREMENT_LDAP_COUNTER(Binds);
} else {
INCREMENT_LDAP_COUNTER(BindFailures);
}
}
CatFunctLeave();
return( ldapErr);
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnection::IsEqual
//
// Synopsis: Figures out if this connection represents a connection to the
// given Host,NamingContext,Account, and Password parameters.
//
// Arguments: [szHost] -- The name of the LDAP host
// [dwPort] -- The remote tcp port # of the LDAP connection
// [szNamingContext] -- The naming context within the DS
// [szAccount] -- The account used to bind to the LDAP DS.
// [szPassword] -- The password used with szAccount.
// [BindType] -- The bind type used to connect to host.
//
// Returns: TRUE if this connection represents the connection to the
// given LDAP context, FALSE otherwise.
//
//-----------------------------------------------------------------------------
BOOL CLdapConnection::IsEqual(
LPSTR szHost,
DWORD dwPort,
LPSTR szNamingContext,
LPSTR szAccount,
LPSTR szPassword,
LDAP_BIND_TYPE BindType)
{
CatFunctEnter("CLdapConnection::IsEqual");
BOOL fResult = FALSE;
_ASSERT( szHost != NULL );
_ASSERT( szAccount != NULL );
_ASSERT( szPassword != NULL );
if (!m_fValid)
return( FALSE );
DebugTrace(
LDAP_CONN_DBG,
"Comparing %s:%d;%s;%s",
szHost, dwPort, szNamingContext, szAccount);
DebugTrace(
LDAP_CONN_DBG,
"With %s:%d;%s;%s; Def NC = %s",
m_szHost, m_dwPort, m_szNamingContext, m_szAccount,
m_fDefaultNamingContext ? "TRUE" : "FALSE");
//
// See if the host/port match.
//
fResult = (BOOL) ((lstrcmpi( szHost, m_szHost) == 0) &&
fIsPortEqual(dwPort));
//
// If the host matches, see if the bind info matches.
//
if (fResult) {
switch (BindType) {
case BIND_TYPE_NONE:
case BIND_TYPE_CURRENTUSER:
fResult = (BindType == m_bt);
break;
case BIND_TYPE_SIMPLE:
case BIND_TYPE_GENERIC:
fResult = (BindType == m_bt) &&
(lstrcmpi(szAccount, m_szAccount) == 0) &&
(lstrcmpi(szPassword, m_szPassword) == 0);
break;
default:
_ASSERT( FALSE && "Invalid Bind Type in CLdapConnection::IsEqual");
break;
}
}
if (fResult) {
//
// If caller specified a naming context, see if it matches. Otherwise,
// see if we are using the Default Naming Context.
//
if (szNamingContext && szNamingContext[0] != 0)
fResult = (lstrcmpi(szNamingContext, m_szNamingContext) == 0);
else
fResult = m_fDefaultNamingContext;
}
CatFunctLeave();
return( fResult );
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnection::Search
//
// Synopsis: Issues a synchronous search request. Returns the result as an
// opaque pointer that can be passed to GetFirstEntry /
// GetNextEntry
//
// Arguments: [szBaseDN] -- The DN of the container object within which to
// search.
// [nScope] -- One of LDAP_SCOPE_BASE, LDAP_SCOPE_ONELEVEL, or
// LDAP_SCOPE_SUBTREE.
// [szFilter] -- The search filter to use. If NULL, a default
// filter is used.
// [rgszAttributes] -- The list of attributes to retrieve.
// [ppResult] -- The result is passed back in here.
//
// Returns: TRUE if success, FALSE otherwise
//
//-----------------------------------------------------------------------------
HRESULT CLdapConnection::Search(
LPCSTR szBaseDN,
int nScope,
LPCSTR szFilter,
LPCSTR *rgszAttributes,
PLDAPRESULT *ppResult)
{
//$$BUGBUG: Obsolete code
CatFunctEnter("CLdapConnection::Search");
DWORD ldapErr = LDAP_SUCCESS;
LPCSTR szFilterToUse = szFilter != NULL ? szFilter : "(objectClass=*)";
if (m_pCPLDAPWrap != NULL) {
ldapErr = ldap_search_s(
GetPLDAP(), // ldap binding
(LPSTR) szBaseDN, // container DN to search
nScope, // Base, 1 or multi level
(LPSTR) szFilterToUse, // search filter
(LPSTR *)rgszAttributes, // attributes to retrieve
FALSE, // attributes-only is false
(PLDAPMessage *) ppResult); // return result here
} else {
ldapErr = LDAP_UNAVAILABLE;
}
if (ldapErr != LDAP_SUCCESS) {
CatFunctLeave();
return( LdapErrorToHr( ldapErr) );
} else {
CatFunctLeave();
return( S_OK );
}
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnection::AsyncSearch
//
// Synopsis: Issues an asynchronous search request. Inserts a pending
// request item into the m_pPendingHead queue, so that the
// given completion routine may be called when the results are
// available.
//
// As a side effect, if this is the first time an async request
// is being issued on this connection, a thread to handle search
// completions is created.
//
// Arguments: [szBaseDN] -- The DN of the container object within which to
// search.
// [nScope] -- One of LDAP_SCOPE_BASE, LDAP_SCOPE_ONELEVEL, or
// LDAP_SCOPE_SUBTREE.
// [szFilter] -- The search filter to use. If NULL, a default
// filter is used.
// [rgszAttributes] -- The list of attributes to retrieve.
// [dwPageSize] -- The desired page size for results. If
// zero, a non-paged ldap search is performed.
// [fnCompletion] -- The LPLDAPCOMPLETION routine to call when
// results are available.
// [ctxCompletion] -- The context to pass to fnCompletion.
//
// Returns: [ERROR_SUCCESS] -- Successfully issued the search request.
//
// [ERROR_OUTOFMEMORY] -- Unable to allocate working data strucs
//
// Win32 Error from ldap_search() call if something went wrong.
//
//-----------------------------------------------------------------------------
HRESULT CLdapConnection::AsyncSearch(
LPCWSTR szBaseDN,
int nScope,
LPCWSTR szFilter,
LPCWSTR *rgszAttributes,
DWORD dwPageSize,
LPLDAPCOMPLETION fnCompletion,
LPVOID ctxCompletion)
{
CatFunctEnter("CLdapConnectio::AsyncSearch");
HRESULT hr;
DWORD dwLdapErr;
PPENDING_REQUEST preq;
ULONG msgid;
//
// First, see if we need to create the completion thread.
//
hr = CreateCompletionThreadIfNeeded();
if(FAILED(hr)) {
ERROR_LOG("CreateCompletionThreadIfNeeded");
return hr;
}
//
// Next, allocate a new PENDING_REQUEST record to represent this async
// request.
//
preq = new PENDING_REQUEST;
if (preq == NULL)
{
hr = E_OUTOFMEMORY;
ERROR_LOG("new PENIDNG_REQUEST");
return( HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY) );
}
preq->fnCompletion = fnCompletion;
preq->ctxCompletion = ctxCompletion;
preq->dwPageSize = dwPageSize;
//
// Initialize msgid to -1 so it can't possibly match any valid msgid that
// the completion thread might be looking for in the pending request list.
//
preq->msgid = -1;
//
//
if(dwPageSize) {
//
// Init the paged search if that is what we will be doing
//
preq->pldap_search = ldap_search_init_pageW(
GetPLDAP(), // LDAP connection to use
(LPWSTR) szBaseDN, // Starting container DN
nScope, // depth of search
(LPWSTR) szFilter, // Search filter
(LPWSTR *) rgszAttributes, // Attributes array
FALSE, // Attributes only?
NULL, // Server controls
NULL, // Client controls
0, // PageTimeLimit
0, // TotalSizeLimit
NULL); // Sorting keys
if(preq->pldap_search == NULL) {
ULONG ulLdapErr = LdapGetLastError();
LogLdapError(ulLdapErr, "ldap_search_init_pageW(GetPLDAP(), \"%S\", %d, \"%S\", rgszAttributes, ...), PLDAP = 0x%08lx",
szBaseDN, nScope, szFilter, GetPLDAP());
dwLdapErr = LdapErrorToHr(ulLdapErr);
ErrorTrace((LPARAM)this, "ldap_search_init_page failed with err %d (0x%x)", dwLdapErr, dwLdapErr);
delete preq;
//$$BUGBUG: We call LdapErrorToHr twice?
return ( LdapErrorToHr(dwLdapErr));
}
} else {
preq->pldap_search = NULL; // Not doing a paged search
}
//
// We might want to abandon all of our outstanding requests at
// some point. Because of this, we use this sharelock to prevent
// abandoning requests with msgid still set to -1
//
m_ShareLock.ShareLock();
//
// Link the request into the queue of pending requests so that the
// completion thread can pick it up when a result is available.
//
InsertPendingRequest( preq );
if(dwPageSize) {
//
// Issue an async request for the next page of matches
//
dwLdapErr = ldap_get_next_page(
GetPLDAP(), // LDAP connection to use
preq->pldap_search, // LDAP page search context
dwPageSize, // page size desired
&msgid);
if(dwLdapErr != LDAP_SUCCESS) {
LogLdapError(dwLdapErr, "ldap_get_next_page(GetPLDAP(),preq->pldap_search,%d,&msgid), PDLAP = 0x%08lx",
dwPageSize, GetPLDAP());
}
} else {
//
// Now, attempt to issue the async search request.
//
dwLdapErr = ldap_search_extW(
GetPLDAP(), // LDAP connection to use
(LPWSTR) szBaseDN, // Starting container DN
nScope, // depth of search
(LPWSTR) szFilter, // Search filter
(LPWSTR *)rgszAttributes, // List of attributes to get
FALSE, // Attributes only?
NULL, // Server controls
NULL, // Client controls
0, // Time limit
0, // Size limit
&msgid);
if(dwLdapErr != LDAP_SUCCESS) {
LogLdapError(dwLdapErr, "ldap_search_extW(GetPLDAP(), \"%S\", %d, \"%S\", rgszAttributes, ...), PLDAP = 0x%08lx",
szBaseDN, nScope, szFilter, GetPLDAP());
}
}
//
// One last thing - ldap_search could fail, in which case we need to
// remove the PENDING_REQUEST item we just inserted.
//
if (dwLdapErr != LDAP_SUCCESS) { // ldap_search failed!
DebugTrace((LPARAM)this, "DispError %d 0x%08lx conn %08lx", dwLdapErr, dwLdapErr, (PLDAP)(GetPLDAP()));
RemovePendingRequest( preq );
m_ShareLock.ShareUnlock();
INCREMENT_LDAP_COUNTER(SearchFailures);
if(preq->pldap_search) {
INCREMENT_LDAP_COUNTER(PagedSearchFailures);
//
// Free the ldap page search context
//
ldap_search_abandon_page(
GetPLDAP(),
preq->pldap_search);
}
delete preq;
return( LdapErrorToHr(dwLdapErr) );
} else {
preq->msgid = (int) msgid;
INCREMENT_LDAP_COUNTER(Searches);
INCREMENT_LDAP_COUNTER(PendingSearches);
if(dwPageSize)
INCREMENT_LDAP_COUNTER(PagedSearches);
//
// WARNING: preq could have been processed and free'd in the
// completion routine at this point so it is not advisable to view
// it!
//
DebugTrace((LPARAM)msgid, "Dispatched ldap search request %ld 0x%08lx conn %08lx", msgid, msgid, (PLDAP)(GetPLDAP()));
m_ShareLock.ShareUnlock();
ReleaseSemaphore( m_hOutstandingRequests, 1, NULL );
}
CatFunctLeave();
return( S_OK );
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnection::CancelAllSearches
//
// Synopsis: Cancels all pending requests to the LDAP server.
//
// Arguments: [hr] -- The error code to complete pending requests with.
// Defaults to HRESULT_FROM_WIN32(ERROR_CANCELLED)
// [pISMTPServer] -- Interface on which to call StopHint after
// every cancelled search. Defaults to NULL, in which case no
// StopHint is called.
//
// Returns: Nothing
//
//-----------------------------------------------------------------------------
VOID CLdapConnection::CancelAllSearches(
HRESULT hr,
ISMTPServer *pISMTPServer)
{
CatFunctEnter("CLdapConnection::CancelAllSearches");
PLIST_ENTRY pli;
PPENDING_REQUEST preq = NULL;
LIST_ENTRY listCancel;
//
// We need to visit every node of m_listPendingRequests and call the
// completion routine with the error. But, we want to call the
// completion routine outside the critical section, so that calls to
// AsyncSearch (from other threads or this thread!) won't block. So,
// we simply transfer m_listPendingRequests to a temporary list under
// the critical section, and then complete the temporary list outside
// the critical section.
//
//
// Transfer m_listPendingRequests to listCancel under the critical
// section
//
InitializeListHead( &listCancel );
//
// We need exclusive access to the list (no half completed
// searches are welcome), so get the exclusive lock
//
m_ShareLock.ExclusiveLock();
AcquireSpinLock( &m_spinlockCompletion );
//
// swipe the contents of the pending request list
//
InsertTailList( &m_listPendingRequests, &listCancel);
RemoveEntryList( &m_listPendingRequests );
InitializeListHead( &m_listPendingRequests );
//
// Inform ProcessAyncResult that we've cancelled everything
//
NotifyCancel();
ReleaseSpinLock( &m_spinlockCompletion );
m_ShareLock.ExclusiveUnlock();
//
// Cancel all pending requests outside the critical section
//
for (pli = listCancel.Flink;
pli != & listCancel;
pli = listCancel.Flink) {
preq = CONTAINING_RECORD(pli, PENDING_REQUEST, li);
RemoveEntryList( &preq->li );
ErrorTrace(0, "Calling ldap_abandon for msgid %ld",
preq->msgid);
AbandonRequest(preq);
CallCompletion(
preq,
NULL,
hr,
TRUE);
if (pISMTPServer) {
pISMTPServer->ServerStopHintFunction();
}
delete preq;
}
CatFunctLeave();
return;
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnection::ProcessAsyncResult
//
// Synopsis: Routine that LdapCompletionThread calls to process any
// results for async searches it receives.
//
// Arguments: [pres] -- The PLDAPMessage to process. This routine will free
// this result when its done with it.
//
// [dwLdapError] -- The status of the received message.
//
// [pfTerminateIndicator] -- Ptr to boolean that is set
// to true when we want to
// shutdown
//
// Returns: Nothing.
//
//-----------------------------------------------------------------------------
VOID CLdapConnection::ProcessAsyncResult(
PLDAPMessage presIN,
DWORD dwLdapError,
BOOL *pfTerminateIndicator)
{
CatFunctEnterEx((LPARAM)this, "CLdapConnection::ProcessAsyncResult");
//
// balanced by a release at the end of ProcessAsyncResult
//
int msgid;
PLIST_ENTRY pli;
PPENDING_REQUEST preq = NULL;
LONG lOops = 0; // It's possible we've recieved a result for a
// query that's was sent by ldap_search_ext
// currently in another thread and the msgid
// hasn't been stamped yet. If this happens,
// we've consumed someone other request's
// semaphore count..keep track of these here
// and release them when we're done.
BOOL fNoMsgID = FALSE; // Set this to true if we see one or more
// messages with ID = -1
BOOL fFinalCompletion = TRUE; // TRUE unless this is a partial
// completion of a paged search
BOOL fPagedSearch = FALSE;
PLDAPMessage pres = presIN;
CPLDAPWrap *pCLDAPWrap = NULL;
ISMTPServerEx *pISMTPServerEx = GetISMTPServerEx();
_ASSERT(m_pCPLDAPWrap);
_ASSERT(pfTerminateIndicator);
if( pISMTPServerEx )
pISMTPServerEx->AddRef();
pCLDAPWrap = m_pCPLDAPWrap;
pCLDAPWrap->AddRef();
//
// If dwLdapError is LDAP_SERVER_DOWN, pres will be NULL and we simply
// have to complete all outstanding requests with that error
//
if ((pres == NULL) || (dwLdapError == LDAP_SERVER_DOWN)) {
_ASSERT(dwLdapError != 0);
INCREMENT_LDAP_COUNTER(GeneralCompletionFailures);
ErrorTrace(0, "Generic LDAP error %d 0x%08lx", dwLdapError, dwLdapError);
CancelAllSearches( LdapErrorToHr( dwLdapError ) );
goto CLEANUP;
}
//
// We have a search specific result, find the search request and complete
// it.
//
_ASSERT( pres != NULL );
msgid = pres->lm_msgid;
DebugTrace(msgid, "Processing message %d 0x%08lx conn %08lx", pres->lm_msgid, pres->lm_msgid, (PLDAP)(pCLDAPWrap->GetPLDAP()));
while(preq == NULL) {
//
// Lookup the msgid in the list of pending requests.
//
AcquireSpinLock( &m_spinlockCompletion );
// EnterCriticalSection( &m_cs );
for (pli = m_listPendingRequests.Flink;
pli != &m_listPendingRequests && preq == NULL;
pli = pli->Flink) {
PPENDING_REQUEST preqCandidate;
preqCandidate = CONTAINING_RECORD(pli, PENDING_REQUEST, li);
if (preqCandidate->msgid == msgid) {
preq = preqCandidate;
RemoveEntryList( &preq->li );
//
// Clear the cancel bit here so we'll know if Cancel
// was recently requested later in this function
//
ClearCancel();
} else if (preqCandidate->msgid == -1) {
fNoMsgID = TRUE;
}
}
ReleaseSpinLock( &m_spinlockCompletion );
// LeaveCriticalSection( &m_cs );
if (preq == NULL) {
LPCSTR rgSubStrings[2];
CHAR szMsgId[11];
_snprintf(szMsgId, sizeof(szMsgId), "0x%08lx", msgid);
rgSubStrings[0] = szMsgId;
rgSubStrings[1] = m_szHost;
if(!fNoMsgID) {
ErrorTrace((LPARAM)this, "Couldn't find message ID %d in list of pending requests. Ignoring it", msgid);
//
// If we don't find the message in our list of pending requests,
// and we see no messages with ID == -1, it means
// some other thread came in and cancelled the search before we could
// process it. This is ok - just return.
//
CatLogEvent(
GetISMTPServerEx(),
CAT_EVENT_LDAP_UNEXPECTED_MSG,
2,
rgSubStrings,
S_OK,
szMsgId,
LOGEVENT_FLAG_ALWAYS,
LOGEVENT_LEVEL_FIELD_ENGINEERING);
//
// It is also possible wldap32 is giving us a msgid we
// never dispatched. We need to re-release the
// semaphore count we consumed if this is the case
//
lOops++; // For the msgid we did not find
goto CLEANUP;
} else {
//
// So this(these) messages with id==-1 could possibly be
// the one we're looking for. If this is so, we just
// consumed a semaphore count of a different request.
// Block for our semaphore and keep track of the extra
// semaphore counts we are consuming (lOops)
//
CatLogEvent(
GetISMTPServerEx(),
CAT_EVENT_LDAP_PREMATURE_MSG,
2,
rgSubStrings,
S_OK,
szMsgId,
LOGEVENT_FLAG_ALWAYS,
LOGEVENT_LEVEL_FIELD_ENGINEERING);
lOops++;
DebugTrace((LPARAM)this, "Couldn't find message ID %d in list of pending requests. Waiting retry #%d", msgid, lOops);
// Oops, we consumed a semaphore count not meant for us
_VERIFY(WaitForSingleObject(m_hOutstandingRequests, INFINITE) ==
WAIT_OBJECT_0);
if(*pfTerminateIndicator)
goto CLEANUP;
// Try again to find our request
fNoMsgID = FALSE;
}
}
}
_ASSERT(preq);
INCREMENT_LDAP_COUNTER(SearchesCompleted);
DECREMENT_LDAP_COUNTER(PendingSearches);
//
// Determine wether or not this is the final completion call (by
// default fFinalCompletion is TRUE)
//
fPagedSearch = (preq->pldap_search != NULL);
if(fPagedSearch) {
INCREMENT_LDAP_COUNTER(PagedSearchesCompleted);
if (dwLdapError == ERROR_SUCCESS) {
ULONG ulTotalCount;
//
// The result is one page of the search. Dispatch a request
// for the next page
//
// First, call ldap_get_paged_count (required so wldap32 can
// "save off the cookie that the server sent to resumt the
// search")
//
dwLdapError = ldap_get_paged_count(
pCLDAPWrap->GetPLDAP(),
preq->pldap_search,
&ulTotalCount,
pres);
if(dwLdapError == ERROR_SUCCESS) {
//
// Dispatch a search for the next page
//
dwLdapError = ldap_get_next_page(
pCLDAPWrap->GetPLDAP(),
preq->pldap_search,
preq->dwPageSize,
(PULONG) &(preq->msgid));
if(dwLdapError == ERROR_SUCCESS) {
//
// Another request has been dispatched, so this was
// not the final search
//
INCREMENT_LDAP_COUNTER(Searches);
INCREMENT_LDAP_COUNTER(PagedSearches);
INCREMENT_LDAP_COUNTER(PendingSearches);
fFinalCompletion = FALSE;
ReleaseSemaphore( m_hOutstandingRequests, 1, NULL );
} else if(dwLdapError == LDAP_NO_RESULTS_RETURNED) {
//
// We have the last page now. The paged search will be
// freed in cleanup code below.
//
DebugTrace(
(LPARAM)this,
"ldap_get_next_page returned LDAP_NO_RESULTS_RETURNED. Paged search completed.");
} else {
LogLdapError(dwLdapError, "ldap_get_next_page(GetPLDAP(),preq->pldap_search,%d,&(preq->msgid), PLDAP = 0x%08lx",
preq->dwPageSize,
pCLDAPWrap->GetPLDAP());
INCREMENT_LDAP_COUNTER(SearchFailures);
INCREMENT_LDAP_COUNTER(PagedSearchFailures);
}
} else {
LogLdapError(dwLdapError, "ldap_get_paged_count, PLDAP = 0x%08lx",
pCLDAPWrap->GetPLDAP());
}
}
}
//
// Call the completion routine of the Request.
//
if ( (dwLdapError == ERROR_SUCCESS)
|| ((dwLdapError == LDAP_NO_RESULTS_RETURNED) && fPagedSearch) ) {
CallCompletion(
preq,
pres,
S_OK,
fFinalCompletion);
//
// CallCompletion will handle the freeing of pres
//
pres = NULL;
} else {
DebugTrace(0, "Search request %d completed with LDAP error 0x%x",
msgid, dwLdapError);
ErrorTrace(msgid, "ProcError %d 0x%08lx msgid %d 0x%08lx conn %08lx", dwLdapError, dwLdapError, pres->lm_msgid, pres->lm_msgid, (PLDAP)(pCLDAPWrap->GetPLDAP()));
INCREMENT_LDAP_COUNTER(SearchCompletionFailures);
if(preq->pldap_search != NULL)
INCREMENT_LDAP_COUNTER(PagedSearchCompletionFailures);
CallCompletion(
preq,
NULL,
LdapErrorToHr( dwLdapError ),
fFinalCompletion);
//
// CallCompletion will handle the freeing of pres
// BUGBUG ??? No it won't; we passed in NULL, not pres. It has nothing to clean up,
// so leave it at its present value so that cleanup code below can take a crack at it.
//
// pres = NULL;
//
// It is unsafe to touch CLdapConnection past here -- it may
// be deleted (or waiting in the destructor)
//
}
if (!fFinalCompletion) {
//
// If we were asked to cancel all searches between the time we
// got the preq pointer out of the list and now, abandon the
// pending search, and notify our caller we're cancelled
//
AcquireSpinLock(&m_spinlockCompletion);
if(CancelOccured()) {
ReleaseSpinLock(&m_spinlockCompletion);
AbandonRequest(preq);
CallCompletion(
preq,
NULL,
HRESULT_FROM_WIN32(ERROR_CANCELLED),
TRUE);
delete preq;
} else {
//
// we're doing another async wldap32 operation for the
// next page. Put preq back in the pending request list,
// updating the tick count first
//
preq->dwTickCount = GetTickCount();
InsertTailList(&m_listPendingRequests, &(preq->li));
ReleaseSpinLock(&m_spinlockCompletion);
}
}
CLEANUP:
//
// Release the extra semaphore counts we might have consumed
//
if((*pfTerminateIndicator == FALSE) && (lOops > 0)) {
ReleaseSemaphore(m_hOutstandingRequests, lOops, NULL);
}
if(fFinalCompletion)
{
if (fPagedSearch) {
//
// Free the paged search
//
dwLdapError = ldap_search_abandon_page(
pCLDAPWrap->GetPLDAP(),
preq->pldap_search);
if(dwLdapError != LDAP_SUCCESS)
{
ErrorTrace((LPARAM)this, "ldap_search_abandon_page failed %08lx", dwLdapError);
//
// Nothing we can do if we can't free the search
//
LogLdapError(
pISMTPServerEx,
dwLdapError,
"ldap_search_abandon_page, PLDAP = 0x%08lx",
pCLDAPWrap->GetPLDAP());
}
}
delete preq;
}
if(pres) {
FreeResult(pres);
}
if(pCLDAPWrap)
pCLDAPWrap->Release();
if(pISMTPServerEx)
pISMTPServerEx->Release();
CatFunctLeaveEx((LPARAM)this);
}
//+----------------------------------------------------------------------------
//
// Function: LdapCompletionThread
//
// Synopsis: Friend function of CLdapConnection that handles results
// received for requests sent via CLdapConnection::AsyncSearch.
//
// Arguments: [ctx] -- Opaque pointer to the CLdapConnection instance which
// we will service.
//
// Returns: Always ERROR_SUCCESS.
//
//-----------------------------------------------------------------------------
DWORD WINAPI LdapCompletionThread(
LPVOID ctx)
{
CatFunctEnterEx((LPARAM)ctx, "LdapCompletionThread");
CLdapConnection *pConn = (CLdapConnection *) ctx;
int nResultCode = LDAP_RES_SEARCH_RESULT;
DWORD dwError;
PLDAPMessage pres;
BOOL fTerminate = FALSE;
//
// Make sure we have a friend CLdapConnection object!
//
_ASSERT( pConn != NULL );
//
// Tell our friend to set fTerminate to true when it wants us to return.
//
pConn->SetTerminateCompletionThreadIndicator( &fTerminate );
//
// Sit in a loop waiting on results for AsyncSearch requests issued by
// our pConn friend. Do so until our pConn friend terminates the
// LDAP connection we are servicing.
//
do {
pConn->CancelExpiredSearches(
pConn->LdapErrorToHr( LDAP_TIMELIMIT_EXCEEDED ));
dwError = WaitForSingleObject(
pConn->m_hOutstandingRequests, INFINITE );
if (dwError != WAIT_OBJECT_0 || fTerminate)
break;
DebugTrace((LPARAM)pConn, "Calling ldap_result now");
nResultCode = ldap_result(
pConn->GetPLDAP(), // LDAP connection to use
(ULONG) LDAP_RES_ANY, // Search msgid
LDAP_MSG_ALL, // Get all results
&(CLdapConnection::m_ldaptimeout), // Timeout
&pres);
if (fTerminate)
break;
if (nResultCode != 0) {
//
// We are supposed to call ldap_result2error to find out what the
// result specific error code is.
//
dwError = ldap_result2error( pConn->GetPLDAP(), pres, FALSE );
if ((dwError == LDAP_SUCCESS) ||
(dwError == LDAP_RES_SEARCH_RESULT) ||
(dwError == LDAP_REFERRAL_V2)) {
//
// Good, we have a search result. Tell our friend pConn to handle
// it.
//
pConn->ProcessAsyncResult( pres, ERROR_SUCCESS, &fTerminate);
} else {
if (pres != NULL) {
pConn->LogLdapError(dwError, "ldap_result2error, PLDAP = 0x%08lx, msgid = %d",
pConn->GetPLDAP(), pres->lm_msgid);
ErrorTrace(
(LPARAM)pConn,
"LdapCompletionThread - error from ldap_result() for non NULL pres - 0x%x (%d)",
dwError, dwError);
pConn->ProcessAsyncResult( pres, dwError, &fTerminate);
} else {
pConn->LogLdapError(dwError, "ldap_result2error, PLDAP = 0x%08lx, pres = NULL",
pConn->GetPLDAP());
ErrorTrace(
(LPARAM)pConn,
"LdapCompletionThread - generic error from ldap_result() 0x%x (%d)",
dwError, dwError);
ErrorTrace(
(LPARAM)pConn,
"nResultCode = %d", nResultCode);
dwError = LDAP_SERVER_DOWN;
pConn->ProcessAsyncResult( NULL, dwError, &fTerminate);
}
}
} else {
pConn->LogLdapError(nResultCode, "ldap_result (timeout), PLDAP = 0x%08lx",
pConn->GetPLDAP());
pConn->ProcessAsyncResult( NULL, LDAP_SERVER_DOWN, &fTerminate);
}
} while ( !fTerminate );
CatFunctLeaveEx((LPARAM)pConn);
return( 0 );
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnection::CancelExpiredSearches
//
// Synopsis: Cancels searches in the pending request queue that have msgids
// other than -1 that have been there for more than
// m_dwLdapRequestTimeLimit seconds. Completions are called
// on each of these pending requests with hr as the failure code.
//
// Arguments: [hr] -- completion status code.
//
// Returns: Nothing.
//
//-----------------------------------------------------------------------------
VOID CLdapConnection::CancelExpiredSearches(HRESULT hr)
{
PLIST_ENTRY ple;
PPENDING_REQUEST preq;
BOOL fDone = FALSE;
DWORD dwTickCount;
LPCSTR rgSubStrings[2];
CHAR szMsgId[11];
//
// check for expired pending requests. We will start at the head of the
// pending request queue because the ones at the front are the oldest.
// We will ignore all pending requests that have a msgid of -1, because
// they may be removed from the list in AsyncSearch if the search issue
// failed, in which case we don't want to remove the pending request here.
//
dwTickCount = GetTickCount();
while (!fDone) {
AcquireSpinLock(&m_spinlockCompletion);
ple = m_listPendingRequests.Flink;
if (ple == &m_listPendingRequests) {
//
// no pending requests
//
preq = NULL;
} else {
preq = CONTAINING_RECORD(ple, PENDING_REQUEST, li);
if ((preq->msgid != -1) &&
(dwTickCount - preq->dwTickCount > m_dwLdapRequestTimeLimit * 1000)) {
//
// this request has expired
//
RemoveEntryList( &preq->li );
} else {
//
// request has not expired or has msgid == -1
//
preq = NULL;
}
}
ReleaseSpinLock(&m_spinlockCompletion);
if (preq) {
_snprintf(szMsgId, sizeof(szMsgId), "0x%08lx", preq->msgid);
rgSubStrings[0] = szMsgId;
rgSubStrings[1] = m_szHost;
CatLogEvent(
GetISMTPServerEx(),
CAT_EVENT_LDAP_CAT_TIME_LIMIT,
2,
rgSubStrings,
S_OK,
szMsgId,
LOGEVENT_FLAG_ALWAYS,
LOGEVENT_LEVEL_FIELD_ENGINEERING);
//
// we have an expired request that has been removed from the queue
//
AbandonRequest(preq);
CallCompletion(
preq,
NULL,
hr,
TRUE);
delete preq;
//
// We need to down the semaphore count since it was upped in AsyncSearch.
// It is possible that the semaphore hasn't been upped yet due to timing,
// but the thread that queued the request is about to up the semaphore,
// so we have to wait for it. This should be *extremely* rare, as
// the thread issuing the request would have to go unscheduled for the
// entire duration of the request time limit (m_dwLdapRequestTimeLimit).
//
_VERIFY(WaitForSingleObject(m_hOutstandingRequests, INFINITE) ==
WAIT_OBJECT_0);
} else {
fDone = TRUE;
}
}
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnection::GetFirstEntry
//
// Synopsis: Retrieves the first entry from a search result. The result is
// returned as a pointer to an opaque type; all one can do is
// query the attribute-values of the entry using
// GetAttributeValues
//
// Arguments: [pResult] -- The result set returned by Search.
// [ppEntry] -- On successful return, pointer to first entry in
// result is returned here.
//
// Returns: TRUE if successful, FALSE otherwise.
//
//-----------------------------------------------------------------------------
HRESULT CLdapConnection::GetFirstEntry(
PLDAPRESULT pResult,
PLDAPENTRY *ppEntry)
{
CatFunctEnter("CLdapConnection::GetFirstEntry");
PLDAPMessage pres = (PLDAPMessage) pResult;
_ASSERT( m_pCPLDAPWrap != NULL );
_ASSERT( pResult != NULL );
_ASSERT( ppEntry != NULL );
*ppEntry = (PLDAPENTRY) ldap_first_entry(GetPLDAP(), pres);
if (*ppEntry == NULL) {
DebugTrace(0, "GetFirstEntry failed!");
CatFunctLeave();
return( HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS) );
} else {
CatFunctLeave();
return( S_OK );
}
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnection::GetNextEntry
//
// Synopsis: Retrieves the next entry from a result set.
//
// Arguments: [pLastEntry] -- The last entry returned.
// [ppEntry] -- The next entry in the result set.
//
// Returns: TRUE if successful, FALSE otherwise.
//
//-----------------------------------------------------------------------------
HRESULT CLdapConnection::GetNextEntry(
PLDAPENTRY pLastEntry,
PLDAPENTRY *ppEntry)
{
CatFunctEnter("CLdapConnection::GetNextEntry");
PLDAPMessage plastentry = (PLDAPMessage) pLastEntry;
_ASSERT( m_pCPLDAPWrap != NULL );
_ASSERT( pLastEntry != NULL );
_ASSERT( ppEntry != NULL );
*ppEntry = (PLDAPENTRY) ldap_next_entry( GetPLDAP(), plastentry );
if (*ppEntry == NULL) {
CatFunctLeave();
return( HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS) );
} else {
CatFunctLeave();
return( S_OK );
}
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnection::GetAttributeValues
//
// Synopsis: Retrieves the values of a specified attribute of the given
// entry.
//
// Arguments: [pEntry] -- The entry whose attribute value is desired.
// [szAttribute] -- The attribute whose value is desired.
// [prgszValues] -- On return, contains pointer to array of
// string values
//
// Returns: TRUE if successful, FALSE otherwise
//
//-----------------------------------------------------------------------------
HRESULT CLdapConnection::GetAttributeValues(
PLDAPENTRY pEntry,
LPCSTR szAttribute,
LPSTR *prgszValues[])
{
CatFunctEnter("CLdapConnection::GetAttributeValues");
_ASSERT(m_pCPLDAPWrap != NULL);
_ASSERT(pEntry != NULL);
_ASSERT(szAttribute != NULL);
_ASSERT(prgszValues != NULL);
*prgszValues = ldap_get_values(
GetPLDAP(),
(PLDAPMessage) pEntry,
(LPSTR) szAttribute);
if ((*prgszValues) == NULL) {
CatFunctLeave();
return( HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS) );
} else {
CatFunctLeave();
return( S_OK );
}
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnection::FreeResult
//
// Synopsis: Frees a search result and all its entries.
//
// Arguments: [pResult] -- Result retrieved via Search.
//
// Returns: Nothing
//
//-----------------------------------------------------------------------------
VOID CLdapConnection::FreeResult(
PLDAPRESULT pResult)
{
CatFunctEnter("CLdapConnection::FreeResult");
_ASSERT( pResult != NULL );
ldap_msgfree( (PLDAPMessage) pResult );
CatFunctLeave();
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnection::FreeValues
//
// Synopsis: Frees the attribute values retrieved from GetAttributeValues
//
// Arguments: [rgszValues] -- The array of values to free.
//
// Returns: Nothing
//
//-----------------------------------------------------------------------------
VOID CLdapConnection::FreeValues(
LPSTR rgszValues[])
{
CatFunctEnter("CLdapConnection::FreeValues");
_ASSERT( rgszValues != NULL );
ldap_value_free( rgszValues );
CatFunctLeave();
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnection::ModifyAttributes
//
// Synopsis: Adds, deletes, or modifies attributes on a DS object.
//
// Arguments: [nOperation] -- One of LDAP_MOD_ADD, LDAP_MOD_DELETE, or
// LDAP_MOD_REPLACE.
// [szDN] -- DN of the DS object.
// [rgszAttributes] -- The list of attributes
// [rgrgszValues] -- The list of values associated with each
// attribute. rgrgszValues[0] points to an array of values
// associated with rgszAttribute[0]; rgrgszValues[1] points
// to an array of values associated with rgszAttribute[1];
// and so on.
//
// Returns: TRUE if success, FALSE otherwise.
//
//-----------------------------------------------------------------------------
HRESULT CLdapConnection::ModifyAttributes(
int nOperation,
LPCSTR szDN,
LPCSTR *rgszAttributes,
LPCSTR *rgrgszValues[])
{
//$$BUGBUG: Legacy code
CatFunctEnter("CLdapConnection::ModifyAttributes");
int i, cAttr;
PLDAPMod *prgMods = NULL, rgMods;
DWORD ldapErr;
_ASSERT( m_pCPLDAPWrap != NULL );
_ASSERT( nOperation == LDAP_MOD_ADD ||
nOperation == LDAP_MOD_DELETE ||
nOperation == LDAP_MOD_REPLACE );
_ASSERT( szDN != NULL );
_ASSERT( rgszAttributes != NULL );
_ASSERT( rgrgszValues != NULL || nOperation == LDAP_MOD_DELETE );
for (cAttr = 0; rgszAttributes[ cAttr ] != NULL; cAttr++) {
// NOTHING TO DO.
}
//
// Below, we allocate a single chunk of memory that contains an array
// of pointers to LDAPMod structures. Immediately following that array is
// the space for the LDAPMod structures themselves.
//
prgMods = (PLDAPMod *) new BYTE[ (cAttr+1) *
(sizeof(PLDAPMod) + sizeof(LDAPMod)) ];
if (prgMods != NULL) {
rgMods = (PLDAPMod) &prgMods[cAttr+1];
for (i = 0; i < cAttr; i++) {
rgMods[i].mod_op = nOperation;
rgMods[i].mod_type = (LPSTR) rgszAttributes[i];
if (rgrgszValues != NULL) {
rgMods[i].mod_vals.modv_strvals = (LPSTR *)rgrgszValues[i];
} else {
rgMods[i].mod_vals.modv_strvals = NULL;
}
prgMods[i] = &rgMods[i];
}
prgMods[i] = NULL; // Null terminate the array
ldapErr = ldap_modify_s( GetPLDAP(), (LPSTR) szDN, prgMods );
delete [] prgMods;
} else {
ldapErr = LDAP_NO_MEMORY;
}
if (ldapErr != LDAP_SUCCESS) {
DebugTrace(LDAP_CONN_DBG, "Status = 0x%x", ldapErr);
CatFunctLeave();
return( LdapErrorToHr( ldapErr) );
} else {
CatFunctLeave();
return( S_OK );
}
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnection::LdapErrorToWin32
//
// Synopsis: Converts LDAP errors to Win32
//
// Arguments: [dwLdapError] -- The LDAP error to convert
//
// Returns: Equivalent Win32 error
//
//-----------------------------------------------------------------------------
HRESULT CLdapConnection::LdapErrorToHr(
DWORD dwLdapError)
{
DWORD dwErr;
CatFunctEnter("LdapErrorToWin32");
switch (dwLdapError) {
case LDAP_SUCCESS:
dwErr = NO_ERROR;
break;
case LDAP_OPERATIONS_ERROR:
case LDAP_PROTOCOL_ERROR:
dwErr = CAT_E_DBFAIL;
break;
case LDAP_TIMELIMIT_EXCEEDED:
dwErr = ERROR_TIMEOUT;
break;
case LDAP_SIZELIMIT_EXCEEDED:
dwErr = ERROR_DISK_FULL;
break;
case LDAP_AUTH_METHOD_NOT_SUPPORTED:
dwErr = ERROR_NOT_SUPPORTED;
break;
case LDAP_STRONG_AUTH_REQUIRED:
dwErr = ERROR_ACCESS_DENIED;
break;
case LDAP_ADMIN_LIMIT_EXCEEDED:
dwErr = CAT_E_DBFAIL;
break;
case LDAP_ATTRIBUTE_OR_VALUE_EXISTS:
dwErr = ERROR_FILE_EXISTS;
break;
case LDAP_NO_SUCH_OBJECT:
dwErr = ERROR_FILE_NOT_FOUND;
break;
case LDAP_INAPPROPRIATE_AUTH:
dwErr = ERROR_ACCESS_DENIED;
break;
case LDAP_INVALID_CREDENTIALS:
dwErr = ERROR_LOGON_FAILURE;
break;
case LDAP_INSUFFICIENT_RIGHTS:
dwErr = ERROR_ACCESS_DENIED;
break;
case LDAP_BUSY:
dwErr = ERROR_BUSY;
break;
case LDAP_UNAVAILABLE:
dwErr = CAT_E_DBCONNECTION;
break;
case LDAP_UNWILLING_TO_PERFORM:
dwErr = CAT_E_TRANX_FAILED;
break;
case LDAP_ALREADY_EXISTS:
dwErr = ERROR_FILE_EXISTS;
break;
case LDAP_OTHER:
dwErr = CAT_E_TRANX_FAILED;
break;
case LDAP_SERVER_DOWN:
dwErr = CAT_E_DBCONNECTION;
break;
case LDAP_LOCAL_ERROR:
dwErr = CAT_E_TRANX_FAILED;
break;
case LDAP_NO_MEMORY:
dwErr = ERROR_OUTOFMEMORY;
break;
case LDAP_TIMEOUT:
dwErr = ERROR_TIMEOUT;
break;
case LDAP_CONNECT_ERROR:
dwErr = CAT_E_DBCONNECTION;
break;
case LDAP_NOT_SUPPORTED:
dwErr = ERROR_NOT_SUPPORTED;
break;
default:
DebugTrace(
0,
"LdapErrorToWin32: No equivalent for ldap error 0x%x",
dwLdapError);
dwErr = dwLdapError;
break;
}
DebugTrace(
LDAP_CONN_DBG,
"LdapErrorToWin32: Ldap Error 0x%x == Win32 error %d (0x%x) == HResult %d (0x%x)",
dwLdapError, dwErr, dwErr, HRESULT_FROM_WIN32(dwErr), HRESULT_FROM_WIN32(dwErr));
CatFunctLeave();
return( HRESULT_FROM_WIN32(dwErr) );
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnection::CreateCompletionThreadIfNeeded
//
// Synopsis: Helper function to create a completion thread that will
// watch for results of async ldap searches.
//
// Arguments: None
//
// Returns: TRUE if success, FALSE otherwise
//
//-----------------------------------------------------------------------------
HRESULT CLdapConnection::CreateCompletionThreadIfNeeded()
{
HRESULT hr = S_OK;
BOOL fLocked = FALSE;
CatFunctEnterEx((LPARAM)this, "CLdapConnection::CreateCompletionThreadIfNeeded");
//
// Test to see if we already have a completion thread...
//
if (m_hCompletionThread != INVALID_HANDLE_VALUE) {
hr = S_OK;
goto CLEANUP;
}
//
// Looks like we'll have to create a completion thread. Lets acquire
// m_spinlockCompletion so only one of us tries to do this...
//
AcquireSpinLock( &m_spinlockCompletion );
// EnterCriticalSection( &m_cs );
fLocked = TRUE;
//
// Check one more time inside the lock - someone might have beaten us to
// it.
//
if (m_hOutstandingRequests == INVALID_HANDLE_VALUE) {
m_hOutstandingRequests = CreateSemaphore(NULL, 0, LONG_MAX, NULL);
if (m_hOutstandingRequests == NULL) {
m_hOutstandingRequests = INVALID_HANDLE_VALUE;
hr = HRESULT_FROM_WIN32(GetLastError());
ERROR_LOG("CreateSemaphore");
goto CLEANUP;
}
}
if (m_hCompletionThread == INVALID_HANDLE_VALUE) {
//
// Create the completion thread
//
m_hCompletionThread =
CreateThread(
NULL, // Security Attributes
0, // Initial stack - default
LdapCompletionThread,// Starting address
(LPVOID) this, // Param to LdapCompletionRtn
0, // Create Flags
&m_idCompletionThread);// Receives thread id
if (m_hCompletionThread == NULL) {
m_hCompletionThread = INVALID_HANDLE_VALUE;
hr = HRESULT_FROM_WIN32(GetLastError());
ERROR_LOG("CreateThread");
goto CLEANUP;
}
}
CLEANUP:
if(fLocked) {
ReleaseSpinLock( &m_spinlockCompletion );
// LeaveCriticalSection( &m_cs );
}
DebugTrace((LPARAM)this, "returning %08lx", hr);
CatFunctLeaveEx((LPARAM)this);
return hr;
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnection::SetTerminateCompletionThreadIndicator
//
// Synopsis: Callback for our LdapCompletionThread to set a pointer to a
// boolean that will be set to TRUE when the LdapCompletionThread
// needs to terminate.
//
// Arguments: [pfTerminateCompletionThreadIndicator] -- Pointer to boolean
// which will be set to true when the completion thread should
// terminate.
//
// Returns: Nothing
//
//-----------------------------------------------------------------------------
VOID CLdapConnection::SetTerminateCompletionThreadIndicator(
BOOL *pfTerminateCompletionThreadIndicator)
{
_ASSERT(pfTerminateCompletionThreadIndicator);
InterlockedExchangePointer(
(PVOID *) &m_pfTerminateCompletionThreadIndicator,
(PVOID) pfTerminateCompletionThreadIndicator);
if(m_fTerminating) {
//
// We may have decided to terminate before the
// LdapCompletionThread had the chance to call this function.
// If this is the case, we still need to set the thread's
// terminate indicator to true. We call
// SetTerminateIndicatorTrue() to accomplish this. It uses
// interlocked functions to ensure that the terminate
// indicator pointer is not set to true more than once.
//
SetTerminateIndicatorTrue();
}
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnection::InsertPendingRequest
//
// Synopsis: Inserts a new PENDING_REQUEST record in the m_pPendingHead
// list so that the completion thread will find it when the
// search result is available.
//
// Arguments: [preq] -- The PENDING_REQUEST record to insert.
//
// Returns: Nothing, this always succeeds.
//
//-----------------------------------------------------------------------------
VOID CLdapConnection::InsertPendingRequest(
PPENDING_REQUEST preq)
{
AcquireSpinLock( &m_spinlockCompletion );
preq->dwTickCount = GetTickCount();
InsertTailList( &m_listPendingRequests, &preq->li );
ReleaseSpinLock( &m_spinlockCompletion );
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnection::RemovePendingRequest
//
// Synopsis: Removes a PENDING_REQUEST record from the
// m_listPendingRequests list.
//
// Arguments: [preq] -- The PENDING_REQUEST record to remove
//
// Returns: Nothing
//
//-----------------------------------------------------------------------------
VOID CLdapConnection::RemovePendingRequest(
PPENDING_REQUEST preq)
{
AcquireSpinLock( &m_spinlockCompletion );
RemoveEntryList( &preq->li );
ReleaseSpinLock( &m_spinlockCompletion );
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnectionCache::CLdapConnectionCache
//
// Synopsis: Constructor
//
// Arguments: None
//
// Returns: Nothing
//
//-----------------------------------------------------------------------------
#define MAX_HOST_CONNECTIONS 100
#define DEFAULT_HOST_CONNECTIONS 8
CLdapConnectionCache::CLdapConnectionCache(
ISMTPServerEx *pISMTPServerEx)
{
CatFunctEnter("CLdapConnectionCache::CLdapConnectionCache");
m_cRef = 0;
for (DWORD i = 0; i < LDAP_CONNECTION_CACHE_TABLE_SIZE; i++) {
InitializeListHead( &m_rgCache[i] );
}
m_nNextConnectionSkipCount = 0;
m_cMaxHostConnections = DEFAULT_HOST_CONNECTIONS;
m_cCachedConnections = 0;
ZeroMemory(&m_rgcCachedConnections, sizeof(m_rgcCachedConnections));
m_pISMTPServerEx = pISMTPServerEx;
if(m_pISMTPServerEx)
m_pISMTPServerEx->AddRef();
InitializeFromRegistry();
CatFunctLeave();
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnectionCache::InitializeFromRegistry
//
// Synopsis: Helper function that looks up parameters from the registry.
// The only configurable parameter is
// MAX_LDAP_CONNECTIONS_PER_HOST_KEY, which is read into
// m_cMaxHostConnections.
//
// Arguments: None
//
// Returns: Nothing.
//
//-----------------------------------------------------------------------------
VOID CLdapConnectionCache::InitializeFromRegistry()
{
HKEY hkey;
DWORD dwErr, dwType, dwValue, cbValue;
cbValue = sizeof(dwValue);
dwErr = RegOpenKey(HKEY_LOCAL_MACHINE, MAX_LDAP_CONNECTIONS_PER_HOST_KEY, &hkey);
if (dwErr == ERROR_SUCCESS) {
dwErr = RegQueryValueEx(
hkey,
MAX_LDAP_CONNECTIONS_PER_HOST_VALUE,
NULL,
&dwType,
(LPBYTE) &dwValue,
&cbValue);
RegCloseKey( hkey );
}
if (dwErr == ERROR_SUCCESS && dwType == REG_DWORD &&
dwValue > 0 && dwValue < MAX_HOST_CONNECTIONS) {
InterlockedExchange((PLONG) &m_cMaxHostConnections, (LONG)dwValue);
} else {
InterlockedExchange(
(PLONG) &m_cMaxHostConnections, (LONG) DEFAULT_HOST_CONNECTIONS);
}
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnectionCache::~CLdapConnectionCache
//
// Synopsis: Destructor
//
// Arguments: None
//
// Returns: Nothing
//
//-----------------------------------------------------------------------------
CLdapConnectionCache::~CLdapConnectionCache()
{
CatFunctEnter("CLdapConnectionCache::~CLdapConnectionCache");
unsigned short i;
for (i = 0; i < LDAP_CONNECTION_CACHE_TABLE_SIZE; i++) {
_ASSERT( IsListEmpty( &m_rgCache[i] ) );
}
if(m_pISMTPServerEx)
m_pISMTPServerEx->Release();
CatFunctLeave();
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnectionCache::AddRef
//
// Synopsis: Increment the refcount on this Connection Cache object.
// Indicates that there is one more CEmailIDLdapStore object that
// wants to avail of our services.
//
// Arguments: None
//
// Returns: Nothing
//
//-----------------------------------------------------------------------------
VOID CLdapConnectionCache::AddRef()
{
InterlockedIncrement( &m_cRef );
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnectionCache::Release
//
// Synopsis: Decrements the refcount on this connection cache object.
// Indicates that there is one less CEmailIDLdapStore object that
// wants to use our services.
//
// If the refcount drops to 0, all outstanding LDAP connections
// are destroyed!
//
// Arguments: None
//
// Returns: Nothing
//
//-----------------------------------------------------------------------------
VOID CLdapConnectionCache::Release()
{
unsigned short i;
CCachedLdapConnection *pcc;
LIST_ENTRY *pli;
_ASSERT( m_cRef > 0 );
if (InterlockedDecrement( &m_cRef ) == 0) {
for (i = 0; i < LDAP_CONNECTION_CACHE_TABLE_SIZE; i++) {
m_rgListLocks[i].ExclusiveLock();
for (pli = m_rgCache[i].Flink;
pli != &m_rgCache[i];
pli = m_rgCache[i].Flink) {
pcc = CONTAINING_RECORD(pli, CCachedLdapConnection, li);
RemoveEntryList( &pcc->li );
//
// Initialize li just in case someone attempts another
// removal
//
InitializeListHead( &pcc->li );
pcc->Disconnect();
pcc->ReleaseAndWaitForDestruction();
}
m_rgListLocks[i].ExclusiveUnlock();
}
}
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnectionCache::GetConnection
//
// Synopsis: Gets a connection to a given LDAP host
//
// Arguments: [szNamingContext] -- The container within the DS. Could be
// null to indicate root of the DS.
// [szHost] -- the LDAP Host
// [dwPort] -- the remote LDAP tcp port (if zero, LDAP_PORT is assumed)
// [szAccount] -- The account to be used to log in
// [szPassword] -- The password to be used to log in
// [bt] -- The bind method to use to log in
// [pCreateContext] -- a pointer to pass to
// CreateCachedLdapConnection when
// we need to create a new connection.
//
// Returns: Pointer to Connected LDAP connection or NULL
//
//-----------------------------------------------------------------------------
HRESULT CLdapConnectionCache::GetConnection(
CLdapConnection **ppConn,
LPSTR szHost,
DWORD dwPort,
LPSTR szNamingContext,
LPSTR szAccount,
LPSTR szPassword,
LDAP_BIND_TYPE bt,
PVOID pCreateContext)
{
CatFunctEnter("CLdapConnectionCache::GetConnection");
LPSTR szConnectionName = szHost;
unsigned short n;
LIST_ENTRY *pli;
CCachedLdapConnection *pcc;
LONG nSkipCount, nTargetSkipCount;
HRESULT hr = S_OK;
_ASSERT( szHost != NULL );
_ASSERT( szAccount != NULL );
_ASSERT( szPassword != NULL );
//
// See if we have a cached connection already.
//
n = Hash( szConnectionName );
m_rgListLocks[n].ShareLock();
nTargetSkipCount = m_nNextConnectionSkipCount % m_cMaxHostConnections;
for (nSkipCount = 0, pcc= NULL, pli = m_rgCache[n].Flink;
pli != &m_rgCache[n];
pli = pli->Flink) {
pcc = CONTAINING_RECORD(pli, CCachedLdapConnection, li);
if (pcc->IsEqual(szHost, dwPort, szNamingContext, szAccount, szPassword, bt)
&& ((nSkipCount++ == nTargetSkipCount)
|| (pcc->GetRefCount() == 1)))
break;
else
pcc = NULL;
}
if (pcc)
pcc->AddRef(); // Add the caller's reference
m_rgListLocks[n].ShareUnlock();
DebugTrace( LDAP_CCACHE_DBG, "Cached connection is 0x%x", pcc);
DebugTrace( LDAP_CCACHE_DBG,
"nTargetSkipCount = %d, nSkipCount = %d",
nTargetSkipCount, nSkipCount);
//
// If we don't have a cached connection, we need to create a new one.
//
if (pcc == NULL) {
m_rgListLocks[n].ExclusiveLock();
for (nSkipCount = 0, pcc = NULL, pli = m_rgCache[n].Flink;
pli != &m_rgCache[n];
pli = pli->Flink) {
pcc = CONTAINING_RECORD(pli, CCachedLdapConnection, li);
if (pcc->IsEqual(szHost, dwPort, szNamingContext, szAccount, szPassword, bt)
&& (++nSkipCount == m_cMaxHostConnections ||
pcc->GetRefCount() == 1))
break;
else
pcc = NULL;
}
if (pcc) {
pcc->AddRef(); // Add the caller's reference
} else {
pcc = CreateCachedLdapConnection(
szHost, dwPort, szNamingContext,
szAccount, szPassword, bt, pCreateContext);
if (pcc != NULL) {
hr = pcc->Connect();
if (FAILED(hr)) {
ERROR_LOG("pcc->Connect");
ErrorTrace(LDAP_CCACHE_DBG, "Failed to connect 0x%x, hr = 0x%x", pcc, hr);
pcc->Release();
pcc = NULL;
} else {
pcc->AddRef(); // Reference for the connection in
// the cache
InsertTailList( &m_rgCache[n], &pcc->li );
m_cCachedConnections++;
m_rgcCachedConnections[n]++;
}
} else {
hr = E_OUTOFMEMORY;
ERROR_LOG("CreateCachedLdapConnection");
}
}
m_rgListLocks[n].ExclusiveUnlock();
DebugTrace(LDAP_CCACHE_DBG, "New connection is 0x%x", pcc);
}
//
// If we are returning a connection, then bump up the skip count so we
// round-robin through valid connections
//
if (pcc != NULL) {
InterlockedIncrement( &m_nNextConnectionSkipCount );
}
//
// Done.
//
*ppConn = pcc;
CatFunctLeave();
return( hr );
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnectionCache::CancelAllConnectionSearches
//
// Synopsis: Walks through all connections and cancels any pending searches
// on them.
//
// Arguments: [None]
//
// Returns: Nothing
//
//-----------------------------------------------------------------------------
VOID CLdapConnectionCache::CancelAllConnectionSearches(
ISMTPServer *pISMTPServer)
{
CatFunctEnterEx((LPARAM)this, "CLdapConnectionCache::CancelAllConnectionSearches");
PLIST_ENTRY pli;
DWORD i;
DWORD dwcArraySize = 0;
DWORD dwcArrayElements = 0;
CCachedLdapConnection **rgpcc = NULL;
CCachedLdapConnection *pcc = NULL;
for (i = 0; i < LDAP_CONNECTION_CACHE_TABLE_SIZE; i++) {
m_rgListLocks[i].ExclusiveLock();
//
// Do we have enough space? Realloc if necessary
//
if( ((DWORD) m_rgcCachedConnections[i]) > dwcArraySize) {
dwcArraySize = m_rgcCachedConnections[i];
//
// Alloc array
//
rgpcc = (CCachedLdapConnection **)
alloca( dwcArraySize * sizeof(CCachedLdapConnection *));
}
for (pli = m_rgCache[i].Flink, dwcArrayElements = 0;
pli != &m_rgCache[i];
pli = pli->Flink, dwcArrayElements++) {
//
// If this assert fires, it means m_rgcCachedConnections[n] is
// somehow less than the number of connections in the list.
//
_ASSERT(dwcArrayElements < dwcArraySize);
pcc = CONTAINING_RECORD(pli, CCachedLdapConnection, li);
//
// Grab the connection (copy and addref the conn ptr)
//
rgpcc[dwcArrayElements] = pcc;
pcc->AddRef();
}
m_rgListLocks[i].ExclusiveUnlock();
//
// Cancel all searches outside the lock
//
for(DWORD dwCount = 0;
dwCount < dwcArrayElements;
dwCount++) {
rgpcc[dwCount]->CancelAllSearches(
HRESULT_FROM_WIN32(ERROR_CANCELLED),
pISMTPServer);
rgpcc[dwCount]->Release();
}
}
CatFunctLeaveEx((LPARAM)this);
}
//+----------------------------------------------------------------------------
//
// Function: CLdapConnectionCache::Hash
//
// Synopsis: Computes a hash given a connection name. Here, we use a simple
// xor of all the chars in the name.
//
// Arguments: [szConnectionName] -- Name to compute the hash of
//
// Returns: A value between 0 and LDAP_CONNECTION_CACHE_TABLE_SIZE-1,
// inclusive.
//
//-----------------------------------------------------------------------------
unsigned short CLdapConnectionCache::Hash(
LPSTR szConnectionName)
{
CatFunctEnter("CLdapConnectionCache::Hash");
int i;
unsigned short n = 0;
_ASSERT( szConnectionName != NULL );
for (i = 0, n = szConnectionName[i];
szConnectionName[i] != 0;
n ^= szConnectionName[i], i++) {
// NOTHING TO DO
}
CatFunctLeave();
return( n & (LDAP_CONNECTION_CACHE_TABLE_SIZE-1));
}
//+------------------------------------------------------------
//
// Function: CLdapConnection::CallCompletion
//
// Synopsis: Create all the ICategorizerItemAttributes and call the
// completion routine
//
// Arguments:
// preq: PENDING_REQUEST
// pres: LdapMessage
// hrStatus: Status of lookup
// fFinalCompletion:
// FALSE: This is a completion for
// pending results; there will be another completion
// called with more results
// TRUE: This is the final completion call
//
// Returns: NOTHING; calls completion routine with any error
//
// History:
// jstamerj 1998/07/02 13:57:20: Created.
//
//-------------------------------------------------------------
VOID CLdapConnection::CallCompletion(
PPENDING_REQUEST preq,
PLDAPMessage pres,
HRESULT hrStatus,
BOOL fFinalCompletion)
{
HRESULT hr = S_OK;
ICategorizerItemAttributes **rgpIAttributes = NULL;
BOOL fAllocatedArray = FALSE;
int nEntries;
PLDAPMessage pMessage;
CLdapResultWrap *pResultWrap = NULL;
CatFunctEnterEx((LPARAM)this, "CLdapConnection::CallCompletion");
if(pres) {
//
// Wrap the result so that pres can be refcounted
//
nEntries = ldap_count_entries(GetPLDAP(), pres);
pResultWrap = new CLdapResultWrap(GetISMTPServerEx(), m_pCPLDAPWrap, pres);
if(pResultWrap == NULL) {
hr = E_OUTOFMEMORY;
ErrorTrace((LPARAM)this, "Out of memory Allocing CLdapResultWrap");
ERROR_LOG("new CLdapResultWrap");
goto CALLCOMPLETION;
}
//
// AddRef here, release at the end of this function
//
pResultWrap->AddRef();
} else {
nEntries = 0;
}
if(nEntries > 0) {
//
// Allocate array for all these ICategorizerItemAttributes
//
rgpIAttributes = new ICategorizerItemAttributes * [nEntries];
if(rgpIAttributes == NULL) {
hr = E_OUTOFMEMORY;
ErrorTrace((LPARAM)this, "Out of memory Allocing ICategorizerItemAttribute array failed");
ERROR_LOG("new ICategorizerItemAttributes *[]");
goto CALLCOMPLETION;
}
ZeroMemory(rgpIAttributes, nEntries * sizeof(ICategorizerItemAttributes *));
//
// Iterage through all the DS Objectes returned and create an
// ICategorizerItemAttributes implementation for each of them
//
pMessage = ldap_first_entry(GetPLDAP(), pres);
for(int nCount = 0; nCount < nEntries; nCount++) {
_ASSERT(pMessage);
rgpIAttributes[nCount] = new CICategorizerItemAttributesIMP(
GetPLDAP(),
pMessage,
pResultWrap);
if(rgpIAttributes[nCount] == NULL) {
hr = E_OUTOFMEMORY;
ErrorTrace((LPARAM)this, "Out of memory Allocing ICategorizerItemAttributesIMP class");
ERROR_LOG("new CICategorizerItemAttributesIMP");
goto CALLCOMPLETION;
}
rgpIAttributes[nCount]->AddRef();
pMessage = ldap_next_entry(GetPLDAP(), pMessage);
}
// That should have been the last entry
_ASSERT(pMessage == NULL);
} else {
//
// nEntries is zero
//
rgpIAttributes = NULL;
}
CALLCOMPLETION:
if(FAILED(hr)) {
//
// Something failed creating the above array
// Call completion routine with error
//
preq->fnCompletion(
preq->ctxCompletion,
0,
NULL,
hr,
fFinalCompletion);
} else {
//
// Nothing failed in this function; call completion with
// passed in hrStatus
//
preq->fnCompletion(
preq->ctxCompletion,
nEntries,
rgpIAttributes,
hrStatus,
fFinalCompletion);
}
//
// Clean up
//
if(rgpIAttributes) {
for(int nCount = 0; nCount < nEntries; nCount++) {
if(rgpIAttributes[nCount])
rgpIAttributes[nCount]->Release();
}
delete rgpIAttributes;
}
if(pResultWrap != NULL) {
pResultWrap->Release();
} else if(pres) {
//
// We were unable to create pResultWrap, so we have to free
// the LDAP result ourself (normally CLdapResultWrap free's
// the ldap result when all references have been released)
//
FreeResult(pres);
}
}
//+------------------------------------------------------------
//
// Function: CLdapConnection::Release
//
// Synopsis: Release a refcount to this object. Delete this object
// when the refcout hits zero
//
// Arguments: None
//
// Returns:
// S_OK: Success
//
// History:
// jstamerj 1999/04/01 00:09:36: Created.
//
//-------------------------------------------------------------
DWORD CLdapConnection::Release()
{
DWORD dwNewRefCount;
dwNewRefCount = InterlockedDecrement((PLONG) &m_dwRefCount);
if(dwNewRefCount == 0) {
if(m_dwDestructionWaiters) {
//
// Threads are waiting on the destruction event, so let
// the last thread to wakeup delete this object
//
_ASSERT(m_hShutdownEvent != INVALID_HANDLE_VALUE);
_VERIFY(SetEvent(m_hShutdownEvent));
} else {
//
// Nobody is waiting, so delete this object
//
FinalRelease();
}
}
return dwNewRefCount;
} // CLdapConnection::Release
//+------------------------------------------------------------
//
// Function: CLdapConnection::ReleaseAndWaitForDestruction
//
// Synopsis: Release a refcount and block this thread until the object
// is destroyed
//
// Arguments: NONE
//
// Returns: NOTHING
//
// History:
// jstamerj 1999/04/01 00:12:13: Created.
//
//-------------------------------------------------------------
VOID CLdapConnection::ReleaseAndWaitForDestruction()
{
DWORD dw;
CatFunctEnterEx((LPARAM)this, "CLdapConnection::ReleaseAndWaitForDestruction");
_ASSERT(m_hShutdownEvent != INVALID_HANDLE_VALUE);
//
// Increment the count of threads waiting for destruction
//
InterlockedIncrement((PLONG)&m_dwDestructionWaiters);
//
// Release our refcount; if the new refcount is zero, this object
// will NOT be deleted; instead m_hShutdownEvent will be set
//
Release();
//
// Wait for all refcounts to be released
//
dw = WaitForSingleObject(
m_hShutdownEvent,
INFINITE);
_ASSERT(WAIT_OBJECT_0 == dw);
//
// Decrement the number of threads waiting for termination; if we
// are the last thread to leave here, we need to delete this
// object
//
if( InterlockedDecrement((PLONG)&m_dwDestructionWaiters) == 0)
FinalRelease();
CatFunctLeaveEx((LPARAM)this);
} // CLdapConnection::ReleaseAndWaitForDestruction
//+------------------------------------------------------------
//
// Function: CLdapConnection::HrInitialize
//
// Synopsis: Initialize error prone members
//
// Arguments: NONE
//
// Returns:
// S_OK: Success
//
// History:
// jstamerj 1999/04/01 00:17:56: Created.
//
//-------------------------------------------------------------
HRESULT CLdapConnection::HrInitialize()
{
HRESULT hr = S_OK;
CatFunctEnterEx((LPARAM)this, "CLdapConnection::HrInitialize");
m_hShutdownEvent = CreateEvent(
NULL, // Security attributes
TRUE, // fManualReset
FALSE, // Initial state is NOT signaled
NULL); // No name
if(NULL == m_hShutdownEvent) {
hr = HRESULT_FROM_WIN32(GetLastError());
ERROR_LOG("CreateEvent");
//
// Remember that m_hShutdownEvent is invalid
//
m_hShutdownEvent = INVALID_HANDLE_VALUE;
FatalTrace((LPARAM)this, "Error creating event hr %08lx", hr);
goto CLEANUP;
}
CLEANUP:
DebugTrace((LPARAM)this, "returning %08lx", hr);
CatFunctLeaveEx((LPARAM)this);
return hr;
} // CLdapConnection::HrInitialize
//+------------------------------------------------------------
//
// Function: CLdapConnectionCache::CCachedLdapConnection::Release
//
// Synopsis: Override Release for the cached LDAP connection
//
// Arguments: NONE
//
// Returns: New refcount
//
// History:
// jstamerj 1999/04/01 00:30:55: Created.
//
//-------------------------------------------------------------
DWORD CLdapConnectionCache::CCachedLdapConnection::Release()
{
DWORD dw;
CatFunctEnterEx((LPARAM)this, "CLdapConnectionCache::CCachedLdapConnection::Release");
dw = CLdapConnection::Release();
if((dw == 1) && (!IsValid())) {
//
// The ldap connection cache is the only entity that has a
// reference to this and this is invalid -- it should be
// removed from the cache
//
m_pCache->RemoveFromCache(this);
}
CatFunctLeaveEx((LPARAM)this);
return dw;
} // CLdapConnectionCache::CCachedLdapConnection::Release
//+------------------------------------------------------------
//
// Function: CLdapConnectionCache::RemoveFromCache
//
// Synopsis: Removes an LDAP connection object from the cache
//
// Arguments:
// pConn: the connection to remove
//
// Returns: NOTHING
//
// History:
// jstamerj 1999/04/01 00:38:43: Created.
//
//-------------------------------------------------------------
VOID CLdapConnectionCache::RemoveFromCache(
CCachedLdapConnection *pConn)
{
BOOL fRemoved = FALSE;
CatFunctEnterEx((LPARAM)this, "CLdapConnectionCache::RemoveFromCache");
DWORD dwHash = 0;
DebugTrace((LPARAM)this, "pConn = %08lx", pConn);
dwHash = Hash(pConn->SzHost());
//
// Before locking, check to see if the connection has already been removed
//
if(!IsListEmpty( &(pConn->li))) {
m_rgListLocks[dwHash].ExclusiveLock();
//
// Check again in case the connection was removed from the
// cache before we got the lock
//
if(!IsListEmpty( &(pConn->li))) {
RemoveEntryList( &(pConn->li) );
//
// Initialize li just in case someone attempts another removal
//
InitializeListHead( &(pConn->li) );
fRemoved = TRUE;
m_cCachedConnections--;
m_rgcCachedConnections[dwHash]--;
}
m_rgListLocks[dwHash].ExclusiveUnlock();
if(fRemoved)
pConn->Release();
}
CatFunctLeaveEx((LPARAM)this);
} // CLdapConnectionCache::RemoveFromCache
//+------------------------------------------------------------
//
// Function: CLdapConnection::AsyncSearch (UTF8)
//
// Synopsis: Same as AsyncSearch, accept this accepts a UTF8 search
// filter.
//
// Arguments: See AsyncSearch
//
// Returns:
// S_OK: Success
//
// History:
// jstamerj 1999/12/09 18:22:41: Created.
//
//-------------------------------------------------------------
HRESULT CLdapConnection::AsyncSearch(
LPCWSTR szBaseDN, // objects matching specified
int nScope, // criteria in the DS. The
LPCSTR szFilterUTF8, // results are passed to
LPCWSTR szAttributes[], // fnCompletion when they
DWORD dwPageSize, // Optinal page size
LPLDAPCOMPLETION fnCompletion, // become available.
LPVOID ctxCompletion)
{
#define FILTER_STRING_CHOOSE_HEAP (10 * 1024) // filter strings 10K or larger will go on the heap
HRESULT hr = S_OK;
LPWSTR wszFilter = NULL;
int cchFilter = 0;
BOOL fUseHeapBuffer = FALSE;
int i = 0;
CatFunctEnterEx((LPARAM)this, "CLdapConnection::AsyncSearch");
//
// Convert BaseDN and Filter to unicode (from UTF8)
//
// calculate lengths
//
cchFilter = MultiByteToWideChar(
CP_UTF8,
0,
szFilterUTF8,
-1,
NULL,
0);
if(cchFilter == 0) {
hr = HRESULT_FROM_WIN32(GetLastError());
ERROR_LOG("MultiByteToWideChar - 0");
goto CLEANUP;
}
//
// allocate space
//
if(cchFilter * sizeof(WCHAR) < FILTER_STRING_CHOOSE_HEAP) {
wszFilter = (LPWSTR) alloca(cchFilter * sizeof(WCHAR));
} else {
fUseHeapBuffer = TRUE;
wszFilter = new WCHAR[cchFilter];
}
if(wszFilter == NULL) {
//$$BUGBUG: alloca does not return NULL. It throws exceptions on error
// This will catch heap alloc failures though.
hr = E_OUTOFMEMORY;
ERROR_LOG("alloca");
goto CLEANUP;
}
i = MultiByteToWideChar(
CP_UTF8,
0,
szFilterUTF8,
-1,
wszFilter,
cchFilter);
if(i == 0) {
hr = HRESULT_FROM_WIN32(GetLastError());
ERROR_LOG("MultiByteToWideChar - 1");
goto CLEANUP;
}
//
// Call unicode based AsyncSearch
//
hr = AsyncSearch(
szBaseDN,
nScope,
wszFilter,
szAttributes,
dwPageSize,
fnCompletion,
ctxCompletion);
if(FAILED(hr)) {
ERROR_LOG("AsyncSearch");
}
CLEANUP:
if(wszFilter && fUseHeapBuffer) {
delete [] wszFilter;
}
DebugTrace((LPARAM)this, "returning %08lx", hr);
CatFunctLeaveEx((LPARAM)this);
return hr;
} // CLdapConnection::AsyncSearch
//+------------------------------------------------------------
//
// Function: CLdapConnection::AsyncSearch
//
// Synopsis: same as above with UTF8 search filter and base DN
//
// Arguments: see above
//
// Returns:
// S_OK: Success
//
// History:
// jstamerj 1999/12/09 20:50:53: Created.
//
//-------------------------------------------------------------
HRESULT CLdapConnection::AsyncSearch(
LPCSTR szBaseDN, // objects matching specified
int nScope, // criteria in the DS. The
LPCSTR szFilterUTF8, // results are passed to
LPCWSTR szAttributes[], // fnCompletion when they
DWORD dwPageSize, // Optinal page size
LPLDAPCOMPLETION fnCompletion, // become available.
LPVOID ctxCompletion)
{
HRESULT hr = S_OK;
LPWSTR wszBaseDN = NULL;
int cchBaseDN = 0;
int i = 0;
CatFunctEnterEx((LPARAM)this, "CLdapConnection::AsyncSearch");
//
// Convert BaseDN and Filter to unicode (from UTF8)
//
// calculate lengths
//
cchBaseDN = MultiByteToWideChar(
CP_UTF8,
0,
szBaseDN,
-1,
NULL,
0);
if(cchBaseDN == 0) {
hr = HRESULT_FROM_WIN32(GetLastError());
ERROR_LOG("MultiByteToWideChar - 0");
goto CLEANUP;
}
//
// allocate space
//
wszBaseDN = (LPWSTR) alloca(cchBaseDN * sizeof(WCHAR));
if(wszBaseDN == NULL) {
//$$BUGBUG: alloca does not return NULL. It throws exceptions on error.
hr = E_OUTOFMEMORY;
ERROR_LOG("alloca");
goto CLEANUP;
}
i = MultiByteToWideChar(
CP_UTF8,
0,
szBaseDN,
-1,
wszBaseDN,
cchBaseDN);
if(i == 0) {
hr = HRESULT_FROM_WIN32(GetLastError());
ERROR_LOG("MultiByteToWideChar - 1");
goto CLEANUP;
}
//
// Call unicode based AsyncSearch
//
hr = AsyncSearch(
wszBaseDN,
nScope,
szFilterUTF8,
szAttributes,
dwPageSize,
fnCompletion,
ctxCompletion);
if(FAILED(hr)) {
ERROR_LOG("AsyncSearch");
}
CLEANUP:
DebugTrace((LPARAM)this, "returning %08lx", hr);
CatFunctLeaveEx((LPARAM)this);
return hr;
} // CLdapConnection::AsyncSearch
//+------------------------------------------------------------
//
// Function: CLdapConnection::LogLdapError
//
// Synopsis: Logs an eventlog for a wldap32 error
//
// Arguments:
// ulLdapErr: LDAP error to log
// pszFormatString: _snprintf Format string for function name
// ...: variable list of args for format string
//
// Returns: Nothing
//
// History:
// jstamerj 2001/12/12 20:45:09: Created.
//
//-------------------------------------------------------------
VOID CLdapConnection::LogLdapError(
IN ULONG ulLdapErr,
IN LPSTR pszFormatString,
...)
{
int nRet = 0;
CHAR szArgBuffer[1024 + 1]; // MSDN says wvsprintf never uses more than 1024 bytes
// ...but wvsprintf really needs an extra byte to store
// a null terminator in some cases (see X5:198202).
va_list ap;
va_start(ap, pszFormatString);
nRet = wvsprintf(szArgBuffer, pszFormatString, ap);
_ASSERT(nRet < 1024 + 1);
::LogLdapError(
GetISMTPServerEx(),
ulLdapErr,
m_szHost,
szArgBuffer);
}
//+------------------------------------------------------------
//
// Function: CLdapConnection::LogLdapError
//
// Synopsis: Logs an eventlog for a wldap32 error
//
// Arguments:
// pISMTPServerEx: SMTP server instance
// ulLdapErr: LDAP error to log
// pszFormatString: _snprintf Format string for function name
// ...: variable list of args for format string
//
// Returns: Nothing
//
// History:
// dlongley 2002/1/31: Created.
//
//-------------------------------------------------------------
VOID CLdapConnection::LogLdapError(
IN ISMTPServerEx *pISMTPServerEx,
IN ULONG ulLdapErr,
IN LPSTR pszFormatString,
...)
{
int nRet = 0;
CHAR szArgBuffer[1024 + 1]; // MSDN says wvsprintf never uses more than 1024 bytes
// ...but wvsprintf really needs an extra byte to store
// a null terminator in some cases (see X5:198202).
va_list ap;
if( !pISMTPServerEx )
return;
va_start(ap, pszFormatString);
nRet = wvsprintf(szArgBuffer, pszFormatString, ap);
_ASSERT(nRet < 1024 + 1);
::LogLdapError(
pISMTPServerEx,
ulLdapErr,
"",
szArgBuffer);
}
VOID LogLdapError(
IN ISMTPServerEx *pISMTPServerEx,
IN ULONG ulLdapErr,
IN LPSTR pszHost,
IN LPSTR pszCall)
{
int nRet = 0;
LPCSTR rgSubStrings[3];
CHAR szErrNo[11];
nRet = _snprintf(szErrNo, sizeof(szErrNo), "0x%08lx", ulLdapErr);
_ASSERT(nRet == 10);
rgSubStrings[0] = szErrNo;
rgSubStrings[1] = pszCall;
rgSubStrings[2] = pszHost;
CatLogEvent(
pISMTPServerEx,
CAT_EVENT_LDAP_ERROR,
3,
rgSubStrings,
ulLdapErr,
szErrNo,
LOGEVENT_FLAG_ALWAYS,
LOGEVENT_LEVEL_FIELD_ENGINEERING);
}