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