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
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);
|
|
|
|
}
|
|
|