/*++ Copyright (c) 1997-2000 Microsoft Corporation Module Name: rndnt.cpp Abstract: This module contains implementation of CNTDirectory. --*/ #include "stdafx.h" #include "rndnt.h" #include "rndldap.h" #include "rndcoll.h" HRESULT CNTDirectory::FinalConstruct(void) { LOG((MSP_TRACE, "CNTDirectory::FinalConstruct - enter")); HRESULT hr = CoCreateFreeThreadedMarshaler( GetControllingUnknown(), & m_pFTM ); if ( FAILED(hr) ) { LOG((MSP_INFO, "CNTDirectory::FinalConstruct - " "create FTM returned 0x%08x; exit", hr)); return hr; } LOG((MSP_TRACE, "CNTDirectory::FinalConstruct - exit S_OK")); return S_OK; } ///////////////////////////////////////////////////////////////////////////// // ldap helper functions ///////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // GetGlobalCatalogName (local helper funcion) // // This function asks the domain controller for the name of a server with a // Global Catalog. That's the server we actually do ldap_open() on below // in CNTDirectory::Connect(). // // Argument: receives a pointer to a new'ed string containing the name // of the global catalog. This is a fully qualified domain name in // the format "foo.bar.com.", NOT "\\foo.bar.com.". // // Returns an HRESULT: // S_OK : it worked // E_OUTOFMEMORY : not enough memory to allocate the string // other : reason for failure of ::DsGetDcName() // ////////////////////////////////////////////////////////////////////////////// HRESULT GetGlobalCatalogName(WCHAR ** ppszGlobalCatalogName) { return GetDomainControllerName(DS_GC_SERVER_REQUIRED, ppszGlobalCatalogName); } ///////////////////////////////////////////////////////////////////////////// // private functions ///////////////////////////////////////////////////////////////////////////// HRESULT CNTDirectory::LdapSearchUser( IN TCHAR * pName, OUT LDAPMessage ** ppLdapMsg ) /*++ Routine Description: Search a user in the Global Catalog. Arguments: pName - the user name. ppLdapMsg - the result of the search. Return Value: HRESULT. --*/ { CTstr pFilter = new TCHAR [lstrlen(DS_USER_FILTER_FORMAT) + lstrlen(pName) + 1]; BAIL_IF_NULL((TCHAR*)pFilter, E_OUTOFMEMORY); wsprintf(pFilter, DS_USER_FILTER_FORMAT, pName); // attribute to look for. TCHAR *Attributes[] = { (WCHAR *)UserAttributeName(UA_USERNAME), (WCHAR *)UserAttributeName(UA_TELEPHONE_NUMBER), (WCHAR *)UserAttributeName(UA_IPPHONE_PRIMARY), NULL }; // do the search. ULONG res = DoLdapSearch( m_ldap, // ldap handle L"", // base dn is root, because it is Global catalog. LDAP_SCOPE_SUBTREE, // subtree search pFilter, // filter; see rndnt.h for the format Attributes, // array of attribute names FALSE, // return the attribute values ppLdapMsg // search results ); BAIL_IF_LDAP_FAIL(res, "search for objects"); return S_OK; } HRESULT CNTDirectory::MakeUserDNs( IN TCHAR * pName, OUT TCHAR *** pppDNs, OUT DWORD * pdwNumDNs ) /*++ Routine Description: Look for the DN of a user in the DS. Arguments: pName - the user name. ppDN - the user's DN. Return Value: HRESULT. --*/ { LOG((MSP_INFO, "DS: MakeUserDNs: enter")); CLdapMsgPtr pLdapMsg; // auto release message. *pppDNs = NULL; *pdwNumDNs = 0; // // First find the desired user via the Global Catalog. // BAIL_IF_FAIL(LdapSearchUser(pName, &pLdapMsg), "DS: MakeUserDNs: Ldap Search User failed"); // // Make sure we got the right number of entries. If we get 0, we're stuck. // The DS enforces domain-wide uniqueness on the samAccountName attribute, // so if we get more than one it means the same username is present in // more than one domain in our enterprise. // DWORD dwEntries = ldap_count_entries(m_ldap, pLdapMsg); if (dwEntries == 0) { LOG((MSP_ERROR, "DS: MakeUserDNs: entry count is 0 - no match")); return E_FAIL; } // // Allocate an array of pointers in which to return the DNs. // *pppDNs = new PTCHAR [ dwEntries ]; if ( (*pppDNs) == NULL ) { LOG((MSP_ERROR, "DS: MakeUserDNs: Not enough memory to allocate array of pointers")); return E_OUTOFMEMORY; } // // For each DN returned, allocate space for a private copy of the DN and // stick a pointer to that space in the array of pointers allocated // above. // // // Note that dwEntries is the number of entries in the ldap // message. *pdwNumDNs is the number of DNs we are able to // extract. For various reasons it is possible for // *pdwNumDNs to eventually become < dwEntries. // LDAPMessage * pEntry = NULL; for ( DWORD i = 0; i < dwEntries; i++ ) { // // Get the entry from the ldap message. // if ( i == 0 ) { pEntry = ldap_first_entry(m_ldap, pLdapMsg); } else { pEntry = ldap_next_entry(m_ldap, pEntry); } // // Get the DN from the message. // TCHAR * p = ldap_get_dn(m_ldap, pEntry); if ( p == NULL ) { LOG((MSP_ERROR, "DS: MakeUserDNs: could not get DN - skipping")); continue; } LOG((MSP_INFO, "DS: MakeUserDNs: found user DN: %S", p)); // // Allocate space for a copy of the DN. // TCHAR * pDN = new TCHAR [ lstrlen(p) + 1 ]; if ( pDN == NULL ) { ldap_memfree( p ); LOG((MSP_ERROR, "DS: MakeUserDNs: could not allocate copy of " "DN - skipping")); continue; } // // Copy the DN and free the one ldap constructed. // lstrcpy( pDN, p ); ldap_memfree( p ); // // Save the DN in our array of DNs and update the size of the array. // (*pppDNs)[ *pdwNumDNs ] = pDN; (*pdwNumDNs) ++; } // // Check if we have anything to return. // if ( (*pdwNumDNs) == 0 ) { LOG((MSP_ERROR, "DS: MakeUserDNs: had entries but could not " "retrieve any DNs - returning E_FAIL")); delete (*pppDNs); *pppDNs = NULL; return E_FAIL; } LOG((MSP_INFO, "DS: MakeUserDNs: exit S_OK")); return S_OK; } HRESULT CNTDirectory::AddUserIPPhone( IN ITDirectoryObject *pDirectoryObject ) /*++ Routine Description: Modify the user's IPPhone-Primary attribute. Arguments: pDirectoryObject - the object that has the user name and IP phone. Return Value: HRESULT. --*/ { HRESULT hr; // // First get the private interface for attributes. // ITDirectoryObjectPrivate * pObjectPrivate; hr = pDirectoryObject->QueryInterface( IID_ITDirectoryObjectPrivate, (void **)&pObjectPrivate ); if ( FAILED(hr) ) { LOG((MSP_ERROR, "CNTDirectory::AddUserIPPhone - can't get the " "private directory object interface - exit 0x%08x", hr)); return hr; } // // Get the user name. // BSTR bName; hr = pDirectoryObject->get_Name( & bName ); if ( FAILED(hr) ) { LOG((MSP_ERROR, "CNTDirectory::AddUserIPPhone - " "can't get user name - exit 0x%08x", hr)); pObjectPrivate->Release(); return hr; } // // Get the IP phone(machine name). // BSTR bIPPhone; hr = pObjectPrivate->GetAttribute( UA_IPPHONE_PRIMARY, &bIPPhone ); pObjectPrivate->Release(); if ( FAILED(hr) ) { LOG((MSP_ERROR, "CNTDirectory::AddUserIPPhone - " "can't get IPPhone attribute - exit 0x%08x", hr)); SysFreeString( bName ); return hr; } // // resolve the machine name and get the fully qualified DNS name. // this is a pointer into a static hostp structure so we do not // need to free it // char * pchFullDNSName; hr = ResolveHostName(0, bIPPhone, &pchFullDNSName, NULL); SysFreeString(bIPPhone); if ( FAILED(hr) ) { LOG((MSP_ERROR, "CNTDirectory::AddUserIPPhone - " "can't resolve hostname - exit 0x%08x", hr)); SysFreeString( bName ); return hr; } // // Convert the ASCII string into unicode string. // conversion memory allocates memory on stack // USES_CONVERSION; TCHAR * pFullDNSName = A2T(pchFullDNSName); if ( pFullDNSName == NULL) { LOG((MSP_ERROR, "CNTDirectory::AddUserIPPhone - " "can't convert to tchar - exit E_FAIL")); SysFreeString( bName ); return E_FAIL; } // // Find the DNs of the user in DS. // TCHAR ** ppDNs; DWORD dwNumDNs; hr = MakeUserDNs( bName, & ppDNs, & dwNumDNs ); SysFreeString( bName ); if ( FAILED(hr) ) { LOG((MSP_ERROR, "CNTDirectory::AddUserIPPhone - " "can't construct any DNs for user - exit 0x%08x", hr)); return hr; } LOG((MSP_INFO, "%d DNs to try", dwNumDNs )); hr = E_FAIL; for ( DWORD i = 0; i < dwNumDNs; i++ ) { // // If one of them worked, then don't bother with any more. // But we still need to delete the leftover DN strings. // if ( SUCCEEDED(hr) ) { LOG((MSP_INFO, "skipping extra DN %S", ppDNs[i])); } else { // // Modify the user object. // LDAPMod mod[1]; // The modify sturctures used by LDAP // // the IPPhone-Primary attribute. // TCHAR * IPPhone[2] = {pFullDNSName, NULL}; mod[0].mod_values = IPPhone; mod[0].mod_op = LDAP_MOD_REPLACE; mod[0].mod_type = (WCHAR *)UserAttributeName(UA_IPPHONE_PRIMARY); LDAPMod* mods[] = {&mod[0], NULL}; LOG((MSP_INFO, "modifying %S", ppDNs[i] )); // // Call the modify function to modify the object. // hr = GetLdapHResultIfFailed(DoLdapModify(TRUE, m_ldapNonGC, ppDNs[i], mods)); if ( SUCCEEDED(hr) ) { LOG((MSP_INFO, "modifying %S succeeded; done", ppDNs[i] )); } else { LOG((MSP_INFO, "modifying %S failed 0x%08x; trying next", ppDNs[i], hr )); } } // // Skipping or not, we need to delete the string. // delete ppDNs[i]; } // // Delete the array that holds the DNs. // delete ppDNs; return hr; } HRESULT CNTDirectory::DeleteUserIPPhone( IN ITDirectoryObject *pDirectoryObject ) /*++ Routine Description: Remove the user's IPPhone-Primary attribute. Arguments: pDirectoryObject - the object that has the user name. Return Value: HRESULT. --*/ { // // Get the name of the user. // HRESULT hr; BSTR bName; hr = pDirectoryObject->get_Name(&bName); if ( FAILED(hr) ) { LOG((MSP_ERROR, "CNTDirectory::DeleteUserIPPHone - " "can't get user name - exit 0x%08x", hr)); return hr; } // // Get an array of DNs for this user name. // TCHAR ** ppDNs; DWORD dwNumDNs; hr = MakeUserDNs( bName, & ppDNs, & dwNumDNs ); SysFreeString( bName ); if ( FAILED(hr) ) { LOG((MSP_ERROR, "CNTDirectory::DeleteUserIPPHone - " "can't get any DNs - exit 0x%08x", hr)); return hr; } LOG((MSP_INFO, "CNTDirectory::DeleteUserIPPhone - " "%d DNs to try", dwNumDNs )); // // Loop through all the available DNs. Try each one // until one succeeds, then continue looping just // to delete the strings. // hr = E_FAIL; for ( DWORD i = 0; i < dwNumDNs; i++ ) { // // If one of them worked, then don't bother with any more. // But we still need to delete the leftover DN strings. // if ( SUCCEEDED(hr) ) { LOG((MSP_INFO, "skipping extra DN %S", ppDNs[i])); } else { LDAPMod mod; // The modify sturctures used by LDAP mod.mod_values = NULL; mod.mod_op = LDAP_MOD_DELETE; mod.mod_type = (WCHAR *)UserAttributeName(UA_IPPHONE_PRIMARY); LDAPMod* mods[] = {&mod, NULL}; LOG((MSP_INFO, "modifying %S", ppDNs[i] )); // // Call the modify function to remove the attribute. // hr = GetLdapHResultIfFailed(DoLdapModify(TRUE, m_ldapNonGC, ppDNs[i], mods)); if ( SUCCEEDED(hr) ) { LOG((MSP_INFO, "modifying %S succeeded; done", ppDNs[i] )); } else { LOG((MSP_INFO, "modifying %S failed 0x%08x; trying next", ppDNs[i], hr )); } } // // Skipping or not, we need to delete the string. // delete ppDNs[i]; } // // Delete the array that holds the DNs. // delete ppDNs; return hr; } HRESULT CNTDirectory::CreateUser( IN LDAPMessage * pEntry, IN ITDirectoryObject ** ppObject ) /*++ Routine Description: Create a user object based on the info in DS. Arguments: pEntry - the returned entry from DS. pObject - the created object that has the user name and IP phone. Return Value: HRESULT. --*/ { // Get the name of the user. CBstr bName; BAIL_IF_FAIL( ::GetAttributeValue( m_ldap, pEntry, UserAttributeName(UA_USERNAME), &bName ), "get the user name" ); // Create an empty user object. CComPtr pObject; BAIL_IF_FAIL(::CreateEmptyUser(bName, &pObject), "CreateEmptyUser"); // get the private interface for attributes. CComPtr pObjectPrivate; BAIL_IF_FAIL( pObject->QueryInterface( IID_ITDirectoryObjectPrivate, (void **)&pObjectPrivate ), "can't get the private directory object interface"); // Get the machine name of the user. CBstr bAddress; if (SUCCEEDED(::GetAttributeValue( m_ldap, pEntry, UserAttributeName(UA_IPPHONE_PRIMARY), &bAddress ))) { // Set the ipphone attribute. BAIL_IF_FAIL(pObjectPrivate->SetAttribute(UA_IPPHONE_PRIMARY, bAddress), "set ipPhone attribute"); } // Get and set the phonenumber of the user. (optional) CBstr bPhone; if (SUCCEEDED(::GetAttributeValue( m_ldap, pEntry, UserAttributeName(UA_TELEPHONE_NUMBER), &bPhone ))) { // Set the telephone attribute. BAIL_IF_FAIL(pObjectPrivate->SetAttribute(UA_TELEPHONE_NUMBER, bPhone), "set phone number"); } *ppObject = pObject; (*ppObject)->AddRef(); return S_OK; } HRESULT CNTDirectory::SearchUser( IN BSTR pName, OUT ITDirectoryObject *** pppDirectoryObject, OUT DWORD * pdwSize ) /*++ Routine Description: Search user and create an array of user object to return. Arguments: pName - the user name. pppDirectoryObject - the created array of user objects that have the user name and IP phone. pdwSize - the size of the array. Return Value: HRESULT. --*/ { CLdapMsgPtr pLdapMsg; // auto release message. BAIL_IF_FAIL(LdapSearchUser(pName, &pLdapMsg), "Ldap Search User failed"); DWORD dwEntries = ldap_count_entries(m_ldap, pLdapMsg); ITDirectoryObject ** pObjects = new PDIRECTORYOBJECT [dwEntries]; BAIL_IF_NULL(pObjects, E_OUTOFMEMORY); DWORD dwCount = 0; LDAPMessage *pEntry = ldap_first_entry(m_ldap, pLdapMsg); while (pEntry != NULL) { HRESULT hr; hr = CreateUser(pEntry, &pObjects[dwCount]); if (SUCCEEDED(hr)) { dwCount ++; } // Get next entry. pEntry = ldap_next_entry(m_ldap, pEntry); } *pppDirectoryObject = pObjects; *pdwSize = dwCount; return S_OK; } ///////////////////////////////////////////////////////////////////////////// // NT Directory implementation ///////////////////////////////////////////////////////////////////////////// STDMETHODIMP CNTDirectory::get_DirectoryType ( OUT DIRECTORY_TYPE * pDirectoryType ) // get the type of the directory. { if ( IsBadWritePtr(pDirectoryType, sizeof(DIRECTORY_TYPE) ) ) { LOG((MSP_ERROR, "Directory.get_DirectoryType, invalid pointer")); return E_POINTER; } *pDirectoryType = m_Type; return S_OK; } STDMETHODIMP CNTDirectory::get_DisplayName ( OUT BSTR *ppName ) // get the display name of the directory. { BAIL_IF_BAD_WRITE_PTR(ppName, E_POINTER); *ppName = SysAllocString(L"NTDS"); if (*ppName == NULL) { LOG((MSP_ERROR, "get_DisplayName: out of memory.")); return E_OUTOFMEMORY; } return S_OK; } STDMETHODIMP CNTDirectory::get_IsDynamic( OUT VARIANT_BOOL *pfDynamic ) // find out if the directory requires refresh. For NTDS, it is FALSE. { if ( IsBadWritePtr(pfDynamic, sizeof(VARIANT_BOOL) ) ) { LOG((MSP_ERROR, "Directory.get_IsDynamic, invalid pointer")); return E_POINTER; } *pfDynamic = VARIANT_FALSE; return S_OK; } STDMETHODIMP CNTDirectory::get_DefaultObjectTTL( OUT long *pTTL // in seconds ) // Since NTDS is not dynamic, this shouldn't be called. { return E_FAIL; // ZoltanS changed from E_UNEXPECTED } STDMETHODIMP CNTDirectory::put_DefaultObjectTTL( IN long TTL // in sechods ) // Since NTDS is not dynamic, this shouldn't be called. { return E_FAIL; // ZoltanS changed from E_UNEXPECTED } STDMETHODIMP CNTDirectory::EnableAutoRefresh( IN VARIANT_BOOL fEnable ) // Since NTDS is not dynamic, this shouldn't be called. { return E_FAIL; // ZoltanS changed from E_UNEXPECTED } ////////////////////////////////////////////////////////////////////////////// STDMETHODIMP CNTDirectory::Connect( IN VARIANT_BOOL fSecure ) // make ldap connection. Use ssl port or normal port. { CLock Lock(m_lock); if (m_ldap != NULL) { LOG((MSP_ERROR, "already connected.")); return RND_ALREADY_CONNECTED; } // ZoltanS: either VARIANT_TRUE or TRUE will work // in case the caller doesn't know better if (fSecure) { // the port is flipped from regular port to ssl port. m_wPort = GetOtherPort(m_wPort); m_IsSsl = TRUE; } // // ZoltanS: Get the name of the global catalog. If there is not at least // one global catalog in this enterprise then we are toast. // HRESULT hr; WCHAR * pszGlobalCatalogName; // this allocates pszGlobalCatalogName BAIL_IF_FAIL(::GetGlobalCatalogName( &pszGlobalCatalogName ), "GetGlobalCatalogName failed"); // // associate the ldap handle with the handle holder. in case of an error // and subsequent return (without being reset), the ldap handle is closed // ZoltanS: changed to use GC instead of NULL // CLdapPtr hLdap = ldap_init(pszGlobalCatalogName, m_wPort); if (hLdap == NULL) { LOG((MSP_ERROR, "ldap_init error: %d", GetLastError())); } // // ZoltanS: Deallocate the string that holds the name of the global // catalog; we are sure we won't need it anymore. // delete pszGlobalCatalogName; // // Now back to our regularly scheduled programming... // BAIL_IF_NULL((LDAP*)hLdap, HRESULT_FROM_WIN32(ERROR_BAD_NETPATH)); LDAP_TIMEVAL Timeout; Timeout.tv_sec = REND_LDAP_TIMELIMIT; Timeout.tv_usec = 0; DWORD res = ldap_connect((LDAP*)hLdap, &Timeout); BAIL_IF_LDAP_FAIL(res, "connect to the server."); DWORD LdapVersion = 3; res = ldap_set_option((LDAP*)hLdap, LDAP_OPT_VERSION, &LdapVersion); BAIL_IF_LDAP_FAIL(res, "set ldap version to 3"); res = ldap_set_option((LDAP*)hLdap, LDAP_OPT_TIMELIMIT, &Timeout); BAIL_IF_LDAP_FAIL(res, "set ldap timelimit"); ULONG ldapOptionOn = PtrToUlong(LDAP_OPT_ON); res = ldap_set_option((LDAP*)hLdap, LDAP_OPT_AREC_EXCLUSIVE, &ldapOptionOn); BAIL_IF_LDAP_FAIL(res, "set ldap arec exclusive"); if (m_IsSsl) { res = ldap_set_option(hLdap, LDAP_OPT_SSL, LDAP_OPT_ON); if( fSecure ) { if( res != LDAP_SUCCESS ) { LOG((MSP_ERROR, "Invalid Secure flag")); return E_INVALIDARG; } } else { BAIL_IF_LDAP_FAIL(res, "set ssl option"); } } // if no directory path is specified, query the server // to determine the correct path BAIL_IF_FAIL( ::GetNamingContext(hLdap, &m_NamingContext), "can't get default naming context" ); m_ldap = hLdap; // reset the holders so that they don't release anyting. hLdap = NULL; CLdapPtr hLdapNonGC = ldap_init(NULL, LDAP_PORT); if (hLdapNonGC == NULL) { LOG((MSP_ERROR, "ldap_init non-GC error: %d", GetLastError())); } BAIL_IF_NULL((LDAP*)hLdapNonGC, HRESULT_FROM_WIN32(ERROR_BAD_NETPATH)); res = ldap_connect((LDAP*)hLdapNonGC, &Timeout); BAIL_IF_LDAP_FAIL(res, "connect to the server."); res = ldap_set_option((LDAP*)hLdapNonGC, LDAP_OPT_VERSION, &LdapVersion); BAIL_IF_LDAP_FAIL(res, "set ldap version to 3"); res = ldap_set_option((LDAP*)hLdapNonGC, LDAP_OPT_TIMELIMIT, &Timeout); BAIL_IF_LDAP_FAIL(res, "set ldap timelimit"); res = ldap_set_option((LDAP*)hLdapNonGC, LDAP_OPT_AREC_EXCLUSIVE, &ldapOptionOn); BAIL_IF_LDAP_FAIL(res, "set ldap arec exclusive"); // res = ldap_set_option((LDAP*)hLdapNonGC, LDAP_OPT_REFERRALS, LDAP_OPT_ON); // BAIL_IF_LDAP_FAIL(res, "set chase referrals to on"); if (m_IsSsl) { res = ldap_set_option(hLdapNonGC, LDAP_OPT_SSL, LDAP_OPT_ON); BAIL_IF_LDAP_FAIL(res, "set ssl option"); } m_ldapNonGC = hLdapNonGC; // reset the holders so that they don't release anyting. hLdapNonGC = NULL; return S_OK; } // // ITDirectory::Bind // // Bind to the server. // // Currently recognized flags: // // RENDBIND_AUTHENTICATE 0x00000001 // RENDBIND_DEFAULTDOMAINNAME 0x00000002 // RENDBIND_DEFAULTUSERNAME 0x00000004 // RENDBIND_DEFAULTPASSWORD 0x00000008 // // "Meta-flags" for convenience: // RENDBIND_DEFAULTCREDENTIALS 0x0000000e // // // All of this together means that the following three // forms are all equivalent: // // BSTR es = SysAllocString(L""); // hr = pITDirectory->Bind(es, es, es, RENDBIND_AUTHENTICATE | // RENDBIND_DEFAULTCREDENTIALS); // SysFreeString(es); // // // BSTR es = SysAllocString(L""); // hr = pITDirectory->Bind(es, es, es, RENDBIND_AUTHENTICATE | // RENDBIND_DEFAULTDOMAINNAME | // RENDBIND_DEFAULTUSERNAME | // RENDBIND_DEFAULTPASSWORD); // SysFreeString(es); // // // hr = pITDirectory->Bind(NULL, NULL, NULL, RENDBIND_AUTHENTICATE); // // STDMETHODIMP CNTDirectory::Bind ( IN BSTR pDomainName, IN BSTR pUserName, IN BSTR pPassword, IN long lFlags ) { LOG((MSP_TRACE, "CNTDirectory Bind - enter")); // // Determine if we should authenticate. // BOOL fAuthenticate = FALSE; if ( lFlags & RENDBIND_AUTHENTICATE ) { fAuthenticate = TRUE; } // // For scripting compatibility, force string parameters to NULL based // on flags. // if ( lFlags & RENDBIND_DEFAULTDOMAINNAME ) { pDomainName = NULL; } if ( lFlags & RENDBIND_DEFAULTUSERNAME ) { pUserName = NULL; } if ( lFlags & RENDBIND_DEFAULTPASSWORD ) { pPassword = NULL; } LOG((MSP_INFO, "Bind parameters: domain: `%S' user: `%S' pass: `%S'" "authenticate: %S)", (pDomainName) ? pDomainName : L"", (pUserName) ? pUserName : L"", (pPassword) ? pPassword : L"", (fAuthenticate) ? L"yes" : L"no")); // // All flags processed -- lock and proceed with bind if connected. // CLock Lock(m_lock); if (m_ldap == NULL) { LOG((MSP_ERROR, "not connected.")); return RND_NOT_CONNECTED; } // // ZoltanS: check the arguments. NULL has meaning in each case, so they are // OK for now. In each case we want to check any length string, so we // specify (UINT) -1 as the length. // if ( (pDomainName != NULL) && IsBadStringPtr(pDomainName, (UINT) -1 ) ) { LOG((MSP_ERROR, "CNTDirectory::Bind: bad non-NULL pDomainName argument")); return E_POINTER; } if ( (pUserName != NULL) && IsBadStringPtr(pUserName, (UINT) -1 ) ) { LOG((MSP_ERROR, "CNTDirectory::Bind: bad non-NULL pUserName argument")); return E_POINTER; } if ( (pPassword != NULL) && IsBadStringPtr(pPassword, (UINT) -1 ) ) { LOG((MSP_ERROR, "CNTDirectory::Bind: bad non-NULL pPassword argument")); return E_POINTER; } ULONG res; if ( m_IsSsl || (!fAuthenticate) ) { // if encrypted or no secure authentication is required, // simple bind is sufficient // ldap_simple_bind_s does not use sspi to get default credentials. We are // just specifying what we will actually pass on the wire. if (pPassword == NULL) { LOG((MSP_ERROR, "invalid Bind parameters: no password specified")); return E_INVALIDARG; } WCHAR * wszFullName; if ( (pDomainName == NULL) && (pUserName == NULL) ) { // No domain / user doesn't make sense. LOG((MSP_ERROR, "invalid Bind paramters: domain and user not specified")); return E_INVALIDARG; } else if (pDomainName == NULL) { // username only is okay wszFullName = pUserName; } else if (pUserName == NULL) { // It doesn't make sense to specify domain but not user... LOG((MSP_ERROR, "invalid Bind paramters: domain specified but not user")); return E_INVALIDARG; } else { // We need domain\user. Allocate a string and sprintf into it. // The + 2 is for the "\" and for the null termination. wszFullName = new WCHAR[wcslen(pDomainName) + wcslen(pUserName) + 2]; BAIL_IF_NULL(wszFullName, E_OUTOFMEMORY); wsprintf(wszFullName, L"%s\\%s", pDomainName, pUserName); } // // Do the simple bind. // res = ldap_simple_bind_s(m_ldap, wszFullName, pPassword); ULONG res2 = ldap_simple_bind_s(m_ldapNonGC, wszFullName, pPassword); // // If we constructed the full name string, we now need to delete it. // if (wszFullName != pUserName) { delete wszFullName; } // // Bail if the simple bind failed. // BAIL_IF_LDAP_FAIL(res, "ldap simple bind"); BAIL_IF_LDAP_FAIL(res2, "ldap simple bind - non gc"); } else // try an SSPI bind { // ZoltanS Note: the ldap bind code does not process NULL, NULL, NULL // in the SEC_WINNT_AUTH_IDENTITY blob, therefore it is special-cased. // ZoltanS: We used to use LDAP_AUTH_NTLM; now we use // LDAP_AUTH_NEGOTIATE to make sure we use the right domain for the // bind. if ( pDomainName || pUserName || pPassword ) { // fill the credential structure SEC_WINNT_AUTH_IDENTITY AuthI; AuthI.User = (PTCHAR)pUserName; AuthI.UserLength = (pUserName == NULL)? 0: wcslen(pUserName); AuthI.Domain = (PTCHAR)pDomainName; AuthI.DomainLength = (pDomainName == NULL)? 0: wcslen(pDomainName); AuthI.Password = (PTCHAR)pPassword; AuthI.PasswordLength = (pPassword == NULL)? 0: wcslen(pPassword); AuthI.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; res = ldap_bind_s(m_ldap, NULL, (TCHAR*)(&AuthI), LDAP_AUTH_NEGOTIATE); BAIL_IF_LDAP_FAIL(res, "bind with authentication"); res = ldap_bind_s(m_ldapNonGC, NULL, (TCHAR*)(&AuthI), LDAP_AUTH_NEGOTIATE); BAIL_IF_LDAP_FAIL(res, "bind with authentication - non gc"); } else { // Otherwise we've come in with NULL, NULL, NULL - // pass in NULL, NULL. The reason do this is that ldap bind code // does not process NULL, NULL, NULL in the // SEC_WINNT_AUTH_IDENTITY blob !!! ULONG res = ldap_bind_s(m_ldap, NULL, NULL, LDAP_AUTH_NEGOTIATE); BAIL_IF_LDAP_FAIL(res, "bind with NULL NULL NULL"); res = ldap_bind_s(m_ldapNonGC, NULL, NULL, LDAP_AUTH_NEGOTIATE); BAIL_IF_LDAP_FAIL(res, "bind with NULL NULL NULL - non gc"); } } LOG((MSP_TRACE, "CNTDirectory::Bind - exiting OK")); return S_OK; } STDMETHODIMP CNTDirectory::AddDirectoryObject ( IN ITDirectoryObject *pDirectoryObject ) // add an object to the DS. { BAIL_IF_BAD_READ_PTR(pDirectoryObject, E_POINTER); CLock Lock(m_lock); if (m_ldap == NULL) { LOG((MSP_ERROR, "not connected.")); return RND_NOT_CONNECTED; } HRESULT hr; DIRECTORY_OBJECT_TYPE type; if (FAILED(hr = pDirectoryObject->get_ObjectType(&type))) { return hr; } switch (type) { case OT_CONFERENCE: hr = E_NOTIMPL; break; case OT_USER: hr = AddUserIPPhone(pDirectoryObject); break; } return hr; } STDMETHODIMP CNTDirectory::ModifyDirectoryObject ( IN ITDirectoryObject *pDirectoryObject ) // modify an object in the DS { BAIL_IF_BAD_READ_PTR(pDirectoryObject, E_POINTER); CLock Lock(m_lock); if (m_ldap == NULL) { LOG((MSP_ERROR, "not connected.")); return RND_NOT_CONNECTED; } HRESULT hr; DIRECTORY_OBJECT_TYPE type; if (FAILED(hr = pDirectoryObject->get_ObjectType(&type))) { return hr; } switch (type) { case OT_CONFERENCE: hr = E_NOTIMPL; break; case OT_USER: hr = AddUserIPPhone(pDirectoryObject); break; } return hr; } STDMETHODIMP CNTDirectory::RefreshDirectoryObject ( IN ITDirectoryObject *pDirectoryObject ) // no refresh is necessary. { return S_OK; } STDMETHODIMP CNTDirectory::DeleteDirectoryObject ( IN ITDirectoryObject *pDirectoryObject ) // delete an object in the DS. { BAIL_IF_BAD_READ_PTR(pDirectoryObject, E_POINTER); CLock Lock(m_lock); if (m_ldap == NULL) { LOG((MSP_ERROR, "not connected.")); return RND_NOT_CONNECTED; } HRESULT hr; DIRECTORY_OBJECT_TYPE type; if (FAILED(hr = pDirectoryObject->get_ObjectType(&type))) { return hr; } switch (type) { case OT_CONFERENCE: hr = E_NOTIMPL; break; case OT_USER: hr = DeleteUserIPPhone(pDirectoryObject); break; } return hr; } STDMETHODIMP CNTDirectory::get_DirectoryObjects ( IN DIRECTORY_OBJECT_TYPE DirectoryObjectType, IN BSTR pName, OUT VARIANT * pVariant ) // look for objects in the ds. returns a collection used in VB. { BAIL_IF_BAD_READ_PTR(pName, E_POINTER); BAIL_IF_BAD_WRITE_PTR(pVariant, E_POINTER); CLock Lock(m_lock); if (m_ldap == NULL) { LOG((MSP_ERROR, "not connected.")); return RND_NOT_CONNECTED; } HRESULT hr; ITDirectoryObject **pObjects; DWORD dwSize; switch (DirectoryObjectType) { case OT_CONFERENCE: hr = E_NOTIMPL; break; case OT_USER: hr = SearchUser(pName, &pObjects, &dwSize); break; } BAIL_IF_FAIL(hr, "Search for objects"); hr = CreateInterfaceCollection(dwSize, // count &pObjects[0], // begin ptr &pObjects[dwSize], // end ptr pVariant); // return value for (DWORD i = 0; i < dwSize; i ++) { pObjects[i]->Release(); } delete pObjects; BAIL_IF_FAIL(hr, "Create collection of directory objects"); return hr; } STDMETHODIMP CNTDirectory::EnumerateDirectoryObjects ( IN DIRECTORY_OBJECT_TYPE DirectoryObjectType, IN BSTR pName, OUT IEnumDirectoryObject ** ppEnumObject ) // Enumerated object in ds. { BAIL_IF_BAD_READ_PTR(pName, E_POINTER); BAIL_IF_BAD_WRITE_PTR(ppEnumObject, E_POINTER); CLock Lock(m_lock); if (m_ldap == NULL) { LOG((MSP_ERROR, "not connected.")); return RND_NOT_CONNECTED; } HRESULT hr; ITDirectoryObject **pObjects; DWORD dwSize; switch (DirectoryObjectType) { case OT_CONFERENCE: hr = E_NOTIMPL; break; case OT_USER: hr = SearchUser(pName, &pObjects, &dwSize); break; } BAIL_IF_FAIL(hr, "Search for objects"); hr = ::CreateDirectoryObjectEnumerator( &pObjects[0], &pObjects[dwSize], ppEnumObject ); for (DWORD i = 0; i < dwSize; i ++) { pObjects[i]->Release(); } delete pObjects; BAIL_IF_FAIL(hr, "Create enumerator of directory objects"); return hr; }