// // 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 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; TraceFunctEnter( "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)); _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); m_dwPort = (dwPort != 0) ? dwPort : LDAP_PORT; 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; TraceFunctLeave(); } //+---------------------------------------------------------------------------- // // Function: CLdapConnection::~CLdapConnection // // Synopsis: Destructor for a CLdapConnection object // // Arguments: None // // Returns: Nothing. // //----------------------------------------------------------------------------- CLdapConnection::~CLdapConnection() { TraceFunctEnter( "CLdapConnection::~CLdapConnection" ); _ASSERT(m_dwSignature == SIGNATURE_LDAPCONN); m_dwSignature = SIGNATURE_LDAPCONN_INVALID; // // 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); } if (m_pCPLDAPWrap != NULL) { Disconnect(); } // // 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 && m_idCompletionThread != GetCurrentThreadId()) { WaitForSingleObject( m_hCompletionThread, INFINITE ); CloseHandle( m_hCompletionThread ); } if (m_hOutstandingRequests != INVALID_HANDLE_VALUE) CloseHandle( m_hOutstandingRequests ); if (m_hShutdownEvent != INVALID_HANDLE_VALUE) CloseHandle( m_hShutdownEvent ); // DeleteCriticalSection( &m_cs ); TraceFunctLeave(); } //+---------------------------------------------------------------------------- // // 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() { TraceFunctEnter( "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( pszHost, m_dwPort); 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 = 5 * 60; // 5 minutes 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)) { PLDAPMessage pmsg = NULL, pentry; LPWSTR rgszAttributes[2]; LPWSTR *rgszValues = NULL; int cValues; rgszAttributes[0] = L"defaultNamingContext"; rgszAttributes[1] = NULL; 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 (ldapErr == LDAP_SUCCESS && (pentry = ldap_first_entry(GetPLDAP(), pmsg)) != NULL && (rgszValues = ldap_get_valuesW(GetPLDAP(), pentry, rgszAttributes[0])) != NULL && (cValues = ldap_count_valuesW(rgszValues)) != 0) { // // Search for the default DN. The best way to do this is // look for the namingContext that doesn't have the literal // "Configuration" in it. // for (int i = 0; i < cValues; i++) { if (wcsstr( rgszValues[i], L"configuration" ) == NULL && wcsstr( rgszValues[i], L"Configuration" ) == NULL && wcsstr( rgszValues[i], L"CONFIGURATION") == NULL) { int j; _ASSERT( wcslen(rgszValues[i]) < (sizeof(m_wszNamingContext)/sizeof(WCHAR))); wcscpy(m_wszNamingContext, rgszValues[i]); j = WideCharToMultiByte( CP_UTF8, 0, m_wszNamingContext, -1, m_szNamingContext, sizeof(m_szNamingContext), NULL, NULL); _ASSERT(j > 0); break; } } DebugTrace( LDAP_CONN_DBG, "NamingContext is [%s]", m_szNamingContext); } else { ldapErr = LDAP_OPERATIONS_ERROR; } if (rgszValues != NULL) ldap_value_freeW( rgszValues ); if (pmsg != NULL) ldap_msgfree( pmsg ); 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; TraceFunctLeave(); return( LdapErrorToHr( ldapErr) ); } else { TraceFunctLeave(); return( S_OK ); } } //+---------------------------------------------------------------------------- // // Function: CLdapConnection::Disconnect // // Synopsis: Disconnects from the ldap host // // Arguments: None // // Returns: Nothing // //----------------------------------------------------------------------------- VOID CLdapConnection::Disconnect() { TraceFunctEnter("CLdapConnection::Disconnect"); if (m_pCPLDAPWrap != NULL) { SetTerminateIndicatorTrue(); m_fValid = FALSE; m_pCPLDAPWrap->Release(); m_pCPLDAPWrap = NULL; DECREMENT_LDAP_COUNTER(OpenConnections); } TraceFunctLeave(); } //+---------------------------------------------------------------------------- // // 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() { m_fValid = FALSE; } //+---------------------------------------------------------------------------- // // 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) { TraceFunctEnter( "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); 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); 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)); strncpy( szDomain, szAccount, cbDomain ); szDomain[cbDomain] = 0; pszDomain = cbDomain > 0 ? szDomain : NULL; pszUser++; // Go past the backslash } // // Logon as the given user, impersonate, and attempt the LDAP bind. // if ((fLogon = LogonUser(pszUser, pszDomain, szPassword, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &hToken)) && ImpersonateLoggedOnUser(hToken)) { ldapErr = ldap_bind_s(pldap, NULL, NULL, LDAP_AUTH_SSPI); DebugTrace(0, "ldap_bind returned 0x%x", ldapErr); 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); } } TraceFunctLeave(); 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) { TraceFunctEnter("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) && (m_dwPort == 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; } TraceFunctLeave(); 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) { TraceFunctEnter("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) { TraceFunctLeave(); return( LdapErrorToHr( ldapErr) ); } else { TraceFunctLeave(); 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) { TraceFunctEnter("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)) return hr; // // Next, allocate a new PENDING_REQUEST record to represent this async // request. // preq = new PENDING_REQUEST; if (preq == NULL) 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) { dwLdapErr = LdapErrorToHr(LdapGetLastError()); ErrorTrace((LPARAM)this, "ldap_search_init_page failed with err %d (0x%x)", dwLdapErr, dwLdapErr); delete preq; 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); } 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); } // // 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 ); } TraceFunctLeave(); 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) { TraceFunctEnter("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(); // EnterCriticalSection( &m_cs ); AcquireSpinLock( &m_spinlockCompletion ); for (pli = m_listPendingRequests.Flink; pli != &m_listPendingRequests; pli = m_listPendingRequests.Flink) { preq = CONTAINING_RECORD(pli, PENDING_REQUEST, li); ErrorTrace(0, "Calling ldap_abandon for msgid %ld", preq->msgid); AbandonRequest(preq); if (pISMTPServer) { pISMTPServer->ServerStopHintFunction(); } RemoveEntryList( &preq->li ); InsertTailList( &listCancel, &preq->li ); } // // Inform ProcessAyncResult that we've cancelled everything // NotifyCancel(); ReleaseSpinLock( &m_spinlockCompletion ); // LeaveCriticalSection( &m_cs ); 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 ); CallCompletion( preq, NULL, hr, TRUE); if (pISMTPServer) { pISMTPServer->ServerStopHintFunction(); } delete preq; } TraceFunctLeave(); 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 pres, DWORD dwLdapError, BOOL *pfTerminateIndicator) { TraceFunctEnterEx((LPARAM)this, "CLdapConnection::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 _ASSERT(pfTerminateIndicator); // // 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 ) ); return; } // // 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)(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) { 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. // // // 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) // 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) return; // 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) // if(preq->pldap_search != NULL) { 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( GetPLDAP(), preq->pldap_search, &ulTotalCount, pres); if(dwLdapError == ERROR_SUCCESS) { // // Dispatch a search for the next page // dwLdapError = ldap_get_next_page( 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 are handing the last search result now -- free // the paged search // dwLdapError = ldap_search_abandon_page( GetPLDAP(), preq->pldap_search); } else { INCREMENT_LDAP_COUNTER(SearchFailures); INCREMENT_LDAP_COUNTER(PagedSearchFailures); } } } } // // Call the completion routine of the Request. // if (dwLdapError == ERROR_SUCCESS) { CallCompletion( preq, pres, S_OK, fFinalCompletion); } 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)(GetPLDAP())); INCREMENT_LDAP_COUNTER(SearchCompletionFailures); if(preq->pldap_search != NULL) INCREMENT_LDAP_COUNTER(PagedSearchCompletionFailures); CallCompletion( preq, NULL, LdapErrorToHr( dwLdapError ), fFinalCompletion); // // It is unsafe to touch CLdapConnection past here -- it may // be deleted (or waiting in the destructor) // } if(fFinalCompletion) { // // We are done with this search request // delete preq; } else { // // 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 // 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); } TraceFunctLeaveEx((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) { TraceFunctEnterEx((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 { 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 NULL, // Timeout &pres); if (fTerminate) break; // // 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) { ErrorTrace( (LPARAM)pConn, "LdapCompletionThread - error from ldap_result() for non NULL pres - 0x%x (%d)", dwError, dwError); pConn->ProcessAsyncResult( pres, dwError, &fTerminate); } else { 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); } } } while ( !fTerminate ); TraceFunctLeaveEx((LPARAM)pConn); return( 0 ); } //+---------------------------------------------------------------------------- // // 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) { TraceFunctEnter("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!"); TraceFunctLeave(); return( HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS) ); } else { TraceFunctLeave(); 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) { TraceFunctEnter("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) { TraceFunctLeave(); return( HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS) ); } else { TraceFunctLeave(); 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[]) { TraceFunctEnter("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) { TraceFunctLeave(); return( HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS) ); } else { TraceFunctLeave(); 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) { TraceFunctEnter("CLdapConnection::FreeResult"); _ASSERT( pResult != NULL ); ldap_msgfree( (PLDAPMessage) pResult ); TraceFunctLeave(); } //+---------------------------------------------------------------------------- // // 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[]) { TraceFunctEnter("CLdapConnection::FreeValues"); _ASSERT( rgszValues != NULL ); ldap_value_free( rgszValues ); TraceFunctLeave(); } //+---------------------------------------------------------------------------- // // 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[]) { TraceFunctEnter("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); TraceFunctLeave(); return( LdapErrorToHr( ldapErr) ); } else { TraceFunctLeave(); 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; TraceFunctEnter("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)); TraceFunctLeave(); 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; TraceFunctEnterEx((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()); 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()); goto CLEANUP; } } CLEANUP: if(fLocked) { ReleaseSpinLock( &m_spinlockCompletion ); // LeaveCriticalSection( &m_cs ); } DebugTrace((LPARAM)this, "returning %08lx", hr); TraceFunctLeaveEx((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 ); 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() { TraceFunctEnter("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)); InitializeFromRegistry(); TraceFunctLeave(); } //+---------------------------------------------------------------------------- // // 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() { TraceFunctEnter("CLdapConnectionCache::~CLdapConnectionCache"); unsigned short i; for (i = 0; i < LDAP_CONNECTION_CACHE_TABLE_SIZE; i++) { _ASSERT( IsListEmpty( &m_rgCache[i] ) ); } TraceFunctLeave(); } //+---------------------------------------------------------------------------- // // 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) { TraceFunctEnter("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)) { 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; } } 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; TraceFunctLeave(); 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) { TraceFunctEnterEx((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(); } } TraceFunctLeaveEx((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) { TraceFunctEnter("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 } TraceFunctLeave(); 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; TraceFunctEnterEx((LPARAM)this, "CLdapConnection::CallCompletion"); if(pres) { // // Wrap the result so that pres can be refcounted // nEntries = ldap_count_entries(GetPLDAP(), pres); pResultWrap = new CLdapResultWrap(m_pCPLDAPWrap, pres); if(pResultWrap == NULL) { hr = E_OUTOFMEMORY; ErrorTrace((LPARAM)this, "Out of memory Allocing 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"); 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"); 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; TraceFunctEnterEx((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(); TraceFunctLeaveEx((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; TraceFunctEnterEx((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()); // // 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); TraceFunctLeaveEx((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; TraceFunctEnterEx((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); } TraceFunctLeaveEx((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; TraceFunctEnterEx((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(); } TraceFunctLeaveEx((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) { HRESULT hr = S_OK; LPWSTR wszFilter = NULL; int cchFilter = 0; int i = 0; TraceFunctEnterEx((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()); ErrorTrace((LPARAM)this, "MultiByteToWideChar failed hr %08lx", hr); goto CLEANUP; } // // allocate space // wszFilter = (LPWSTR) alloca(cchFilter * sizeof(WCHAR)); if(wszFilter == NULL) { hr = E_OUTOFMEMORY; goto CLEANUP; } i = MultiByteToWideChar( CP_UTF8, 0, szFilterUTF8, -1, wszFilter, cchFilter); if(i == 0) { hr = HRESULT_FROM_WIN32(GetLastError()); ErrorTrace((LPARAM)this, "MultiByteToWideChar failed hr %08lx", hr); goto CLEANUP; } // // Call unicode based AsyncSearch // hr = AsyncSearch( szBaseDN, nScope, wszFilter, szAttributes, dwPageSize, fnCompletion, ctxCompletion); CLEANUP: DebugTrace((LPARAM)this, "returning %08lx", hr); TraceFunctLeaveEx((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; TraceFunctEnterEx((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()); ErrorTrace((LPARAM)this, "MultiByteToWideChar failed hr %08lx", hr); goto CLEANUP; } // // allocate space // wszBaseDN = (LPWSTR) alloca(cchBaseDN * sizeof(WCHAR)); if(wszBaseDN == NULL) { hr = E_OUTOFMEMORY; goto CLEANUP; } i = MultiByteToWideChar( CP_UTF8, 0, szBaseDN, -1, wszBaseDN, cchBaseDN); if(i == 0) { hr = HRESULT_FROM_WIN32(GetLastError()); ErrorTrace((LPARAM)this, "MultiByteToWideChar failed hr %08lx", hr); goto CLEANUP; } // // Call unicode based AsyncSearch // hr = AsyncSearch( wszBaseDN, nScope, szFilterUTF8, szAttributes, dwPageSize, fnCompletion, ctxCompletion); CLEANUP: DebugTrace((LPARAM)this, "returning %08lx", hr); TraceFunctLeaveEx((LPARAM)this); return hr; } // CLdapConnection::AsyncSearch