#include "exldap.h" #include "folders.h" #include "treg.hpp" #include "errdct.hpp" #include "resstr.h" #include "LSAUtils.h" using namespace nsFolders; #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif extern TErrorDct err; CLdapConnection::CLdapConnection() { m_exchServer[0] = 0; m_LD = NULL; m_port = LDAP_PORT; m_bUseSSL = FALSE; // try to dynamically load the LDAP DLL m_hDll = LoadLibrary(L"wldap32.dll"); ldap_open = NULL; ldap_parse_result = NULL; ldap_parse_page_control = NULL; ldap_controls_free = NULL; ber_bvfree = NULL; ldap_first_entry = NULL; ldap_next_entry = NULL; ldap_value_free = NULL; ldap_get_values = NULL; ldap_create_page_control = NULL; ldap_search_ext_s = NULL; ldap_count_entries = NULL; ldap_msgfree = NULL; ldap_modify_s = NULL; LdapGetLastError = NULL; ldap_bind_sW = NULL; ldap_simple_bind_sW = NULL; ldap_unbind = NULL; ldap_connect = NULL; ldap_get_option = NULL; ldap_set_option = NULL; LdapMapErrorToWin32 = NULL; ldap_init = NULL; ldap_sslinit = NULL; if ( m_hDll ) { ldap_open = (LDAP_OPEN *)GetProcAddress(m_hDll,"ldap_openW"); ldap_parse_result = (LDAP_PARSE_RESULT *)GetProcAddress(m_hDll,"ldap_parse_resultW"); ldap_parse_page_control = (LDAP_PARSE_PAGE_CONTROL*)GetProcAddress(m_hDll,"ldap_parse_page_controlW"); ldap_controls_free = (LDAP_CONTROLS_FREE*)GetProcAddress(m_hDll,"ldap_controls_freeW"); ber_bvfree = (BER_BVFREE*)GetProcAddress(m_hDll,"ber_bvfree"); ldap_first_entry = (LDAP_FIRST_ENTRY*)GetProcAddress(m_hDll,"ldap_first_entry"); ldap_next_entry = (LDAP_NEXT_ENTRY*)GetProcAddress(m_hDll,"ldap_next_entry"); ldap_value_free = (LDAP_VALUE_FREE*)GetProcAddress(m_hDll,"ldap_value_freeW"); ldap_get_values = (LDAP_GET_VALUES*)GetProcAddress(m_hDll,"ldap_get_valuesW"); ldap_create_page_control = (LDAP_CREATE_PAGE_CONTROL*)GetProcAddress(m_hDll,"ldap_create_page_controlW"); ldap_search_ext_s = (LDAP_SEARCH_EXT_S*)GetProcAddress(m_hDll,"ldap_search_ext_sW"); ldap_count_entries = (LDAP_COUNT_ENTRIES*)GetProcAddress(m_hDll,"ldap_count_entries"); ldap_msgfree = (LDAP_MSGFREE*)GetProcAddress(m_hDll,"ldap_msgfree"); ldap_modify_s = (LDAP_MODIFY_S*)GetProcAddress(m_hDll,"ldap_modify_sW"); LdapGetLastError = (LDAPGETLASTERROR*)GetProcAddress(m_hDll,"LdapGetLastError"); ldap_bind_sW = (LDAP_BIND*)GetProcAddress(m_hDll,"ldap_bind_sW"); ldap_simple_bind_sW = (LDAP_SIMPLE_BIND*)GetProcAddress(m_hDll,"ldap_simple_bind_sW"); ldap_unbind = (LDAP_UNBIND*)GetProcAddress(m_hDll,"ldap_unbind"); ldap_connect = (LDAP_CONNECT *)GetProcAddress(m_hDll,"ldap_connect"); ldap_get_option = (LDAP_GET_OPTION*)GetProcAddress(m_hDll,"ldap_get_option"); ldap_set_option = (LDAP_SET_OPTION*)GetProcAddress(m_hDll,"ldap_set_option"); LdapMapErrorToWin32 = (LDAPMAPERRORTOWIN32*)GetProcAddress(m_hDll,"LdapMapErrorToWin32"); ldap_init = (LDAP_INIT *)GetProcAddress(m_hDll,"ldap_initW"); ldap_sslinit = (LDAP_SSLINIT *)GetProcAddress(m_hDll,"ldap_sslinitW"); } } CLdapConnection::~CLdapConnection() { Close(); if ( m_hDll ) { FreeLibrary(m_hDll); ldap_open = NULL; ldap_parse_result = NULL; ldap_parse_page_control = NULL; ldap_controls_free = NULL; ber_bvfree = NULL; ldap_first_entry = NULL; ldap_next_entry = NULL; ldap_value_free = NULL; ldap_get_values = NULL; ldap_create_page_control = NULL; ldap_search_ext_s = NULL; ldap_count_entries = NULL; ldap_msgfree = NULL; ldap_modify_s = NULL; LdapGetLastError = NULL; ldap_bind_sW = NULL; ldap_unbind = NULL; ldap_simple_bind_sW = NULL; ldap_connect = NULL; LdapMapErrorToWin32 = NULL; ldap_init = NULL; ldap_sslinit = NULL; } } void CLdapConnection::SetCredentials(WCHAR const * domain, WCHAR const * user, WCHAR const * pwd) { WCHAR creds[LEN_Account + LEN_Domain + 6]; // set the following for the simple bind swprintf(creds,L"cn=%ls,cn=%ls",(WCHAR*)user,(WCHAR*)domain); safecopy(m_credentials,creds); safecopy(m_password,pwd); // set the following for using SSPI m_creds.User = const_cast(user); m_creds.UserLength = (user == NULL) ? 0 : wcslen(user); m_creds.Password = NULL; m_creds.PasswordLength = 0; m_creds.Domain = const_cast(domain); m_creds.DomainLength = (domain == NULL) ? 0 : wcslen(domain); m_creds.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; } DWORD CLdapConnection::Connect(WCHAR const * server, ULONG port = LDAP_PORT) { DWORD rc = 0; safecopy(m_exchServer,server); // m_LD = CLdapConnection::ldap_open(m_exchServer,LDAP_SSL_PORT); // replace ldap_open(servername,..) with ldap_init and set LDAP_OPT_AREC_EXCLUSIVE // flag so that the following ldap calls (i.e. ldap_bind) will not need to // unnecessarily query for the domain controller if (m_LD) CLdapConnection::ldap_unbind(m_LD); m_LD = CLdapConnection::ldap_init(m_exchServer,port); if (!m_LD) { rc = CLdapConnection::LdapGetLastError(); rc = CLdapConnection::LdapMapErrorToWin32(rc); } else { ULONG flags = 0; //set LDAP_OPT_AREC_EXCLUSIVE flag so that the following calls tp //ldap_open will not need to unnecessarily query for the domain controller flags = PtrToUlong(LDAP_OPT_ON); rc = ldap_set_option(m_LD, LDAP_OPT_AREC_EXCLUSIVE, &flags); if (!rc) { flags = LDAP_VERSION3; rc = ldap_set_option(m_LD,LDAP_OPT_VERSION, &flags); } if (!rc) rc = ldap_connect(m_LD,NULL); if (!rc) { if (m_creds.User != NULL) { // // Retrieve specified password from LSA secret. As credentials have been specified the // password is required therefore if unable to retrieve password an error is returned. // WCHAR szPassword[LEN_Password]; rc = RetrievePassword(m_password, szPassword, sizeof(szPassword) / sizeof(szPassword[0])); if (rc == ERROR_SUCCESS) { m_creds.Password = szPassword; m_creds.PasswordLength = wcslen(szPassword); // use full credential and try only SSPI here and it will fall back on NTLM rc = CLdapConnection::ldap_bind_sW(m_LD,NULL,(PWCHAR)&m_creds,LDAP_AUTH_SSPI); if (rc) { rc = CLdapConnection::LdapMapErrorToWin32(rc); } m_creds.PasswordLength = 0; m_creds.Password = NULL; SecureZeroMemory(szPassword, sizeof(szPassword)); } } else { // use NULL credential and try only SSPI here and it will fall back on NTLM rc = CLdapConnection::ldap_bind_sW(m_LD,NULL,NULL,LDAP_AUTH_SSPI); if (rc) { rc = CLdapConnection::LdapMapErrorToWin32(rc); } } } else { rc = CLdapConnection::LdapMapErrorToWin32(rc); } } return rc; } DWORD CLdapConnection::SSLConnect(WCHAR const * server, BOOL *sslEnabled, ULONG port) { DWORD rc = 0; safecopy(m_exchServer,server); *sslEnabled = FALSE; // m_LD = CLdapConnection::ldap_open(m_exchServer,LDAP_SSL_PORT); // replace ldap_open(servername,..) with ldap_init and set LDAP_OPT_AREC_EXCLUSIVE // flag so that the following ldap calls (i.e. ldap_bind) will not need to // unnecessarily query for the domain controller if (m_LD) CLdapConnection::ldap_unbind(m_LD); m_LD = CLdapConnection::ldap_sslinit(m_exchServer,port,1); if (!m_LD) { rc = CLdapConnection::LdapGetLastError(); rc = CLdapConnection::LdapMapErrorToWin32(rc); } else { ULONG flags = 0; //set LDAP_OPT_AREC_EXCLUSIVE flag so that the following calls tp //ldap_open will not need to unnecessarily query for the domain controller flags = PtrToUlong(LDAP_OPT_ON); rc = ldap_set_option(m_LD, LDAP_OPT_AREC_EXCLUSIVE, (void*)&flags); if (!rc) { flags = LDAP_VERSION3; rc = ldap_set_option(m_LD,LDAP_OPT_VERSION,&flags); if (!rc) { // we need to check whether SSL is truly enabled rc = ldap_get_option(m_LD,LDAP_OPT_SSL,&flags); if (!rc && flags == 0) return rc; } } if (!rc) rc = ldap_connect(m_LD,NULL); if (!rc) { if (m_creds.User != NULL) { // // Retrieve specified password from LSA secret. As credentials have been specified the // password is required therefore if unable to retrieve password an error is returned. // WCHAR szPassword[LEN_Password]; rc = RetrievePassword(m_password, szPassword, sizeof(szPassword) / sizeof(szPassword[0])); if (rc == ERROR_SUCCESS) { m_creds.Password = szPassword; m_creds.PasswordLength = wcslen(szPassword); // use full credential and try SSPI here and it will fall back on NTLM rc = CLdapConnection::ldap_bind_sW(m_LD,NULL,(PWCHAR)&m_creds,LDAP_AUTH_SSPI); if (rc) // it is Ok to use simple bind here since we're protected by SSL rc = CLdapConnection::ldap_simple_bind_sW(m_LD,m_credentials,szPassword); if (rc) { rc = CLdapConnection::LdapMapErrorToWin32(rc); } m_creds.PasswordLength = 0; m_creds.Password = NULL; SecureZeroMemory(szPassword, sizeof(szPassword)); } } else { // use NULL credential and try only SSPI here and it will fall back on NTLM rc = CLdapConnection::ldap_bind_sW(m_LD,NULL,NULL,LDAP_AUTH_SSPI); if (rc) { rc = CLdapConnection::LdapMapErrorToWin32(rc); } } } else { rc = CLdapConnection::LdapMapErrorToWin32(rc); } } if (!rc) *sslEnabled = TRUE; return rc; } void CLdapConnection::Close() { if ( m_LD ) { CLdapConnection::ldap_unbind(m_LD); m_LD = NULL; } } DWORD CLdapConnection::UpdateSimpleStringValue(WCHAR const * dn, WCHAR const * property, WCHAR const * value) { DWORD rc = ERROR_NOT_FOUND; if ( m_LD ) { LDAPMod * mods[2]; LDAPMod mod1; WCHAR * strVals[] = { const_cast(value),NULL }; mods[0] = &mod1; mods[0]->mod_op = LDAP_MOD_REPLACE; mods[0]->mod_type = const_cast(property); mods[0]->mod_vals.modv_strvals = strVals; mods[1] = NULL; rc = CLdapConnection::ldap_modify_s(m_LD,const_cast(dn),mods); if ( rc ) { rc = CLdapConnection::LdapMapErrorToWin32(rc); } } return rc; } // Helper function for SidToString - converts one BYTE of the SID into a string representation void CLdapConnection::AddByteToString( WCHAR ** string, // i/o- pointer to current location in string BYTE value // in - value (from SID) to add to the string ) { WCHAR hi, lo; BYTE hiVal, loVal; loVal = value & 0x0F; hiVal = value & 0xF0; hiVal = hiVal >> 4; if ( hiVal < 10 ) { hi=L'0' + hiVal; } else { hi=L'A' + ( hiVal - 10 ); } if ( loVal < 10 ) { lo=L'0' + loVal; } else { lo=L'A' + (loVal - 10 ); } swprintf(*string,L"%c%c",hi,lo); *string+=2; } BYTE // ret- value for the digit, or 0 if value is not a valid hex digit CLdapConnection::HexValue( WCHAR value // in - character representing a hex digit ) { BYTE val = 0; switch ( toupper((char)value) ) { case L'1': val = 1; break; case L'2': val = 2; break; case L'3': val = 3; break; case L'4': val = 4; break; case L'5': val = 5; break; case L'6': val = 6; break; case L'7': val = 7; break; case L'8': val = 8; break; case L'9': val = 9; break; case L'A': val = 0xA; break; case L'B': val = 0xB; break; case L'C': val = 0xC; break; case L'D': val = 0xD; break; case L'E': val = 0xE; break; case L'F': val = 0xF; break; } return val; } BOOL // ret- 0=success, or ERROR_INSUFFICIENT_BUFFER CLdapConnection::BytesToString( BYTE * pBytes, // in - SID to represent as a string WCHAR * sidString, // out- buffer that will contain the DWORD numBytes // in - number of bytes in the buffer to copy ) { BOOL bSuccess = TRUE; WCHAR * curr = sidString; // add each byte of the SID to the output string for ( int i = 0 ; i < (int)numBytes ; i++) { AddByteToString(&curr,pBytes[i]); } return bSuccess; } BOOL CLdapConnection::StringToBytes( WCHAR const * pString, // in - string representing the data BYTE * pBytes // out- binary representation of the data ) { BOOL bSuccess = TRUE; int len = UStrLen(pString) / 2; for ( int i = 0 ; i < len ; i++, pString += 2 ) { // each byte is represented by 2 characters WCHAR str[3]; BYTE hi,lo; safecopy(str,pString); hi = HexValue(str[0]); lo = HexValue(str[1]); pBytes[i] = ((hi << 4)+lo); } return bSuccess; } CLdapEnum::~CLdapEnum() { if ( m_message ) { m_connection.ldap_msgfree(m_message); m_message = NULL; } } DWORD CLdapEnum::Open( WCHAR const * query, // in - query to execute WCHAR const * basePoint, // in - basepoint for query short scope, // in - scope: 0=base only, 1=one level, 2=recursive long pageSize, // in - page size to use for large searches int numAttributes, // in - number of attributes to retrieve for each matching item WCHAR ** attrs // in - array of attribute names to retrieve for each matching item ) { // open and bind before calling this function ULONG result; // PLDAPSearch searchBlock = NULL; PLDAPControl serverControls[2]; // l_timeval timeout = { 1000,1000 }; // ULONG totalCount = 0; berval cookie1 = { 0, NULL }; // DWORD numRead = 0; if ( m_message ) { m_connection.ldap_msgfree(m_message); m_message = NULL; } LDAP * ld = m_connection.GetHandle(); safecopy(m_query,query); safecopy(m_basepoint,basePoint); m_scope = scope; m_pageSize = pageSize; m_nAttributes = numAttributes; m_AttrNames = attrs; result = m_connection.ldap_create_page_control(ld, pageSize, &cookie1, FALSE, // is critical &serverControls[0] ); serverControls[1] = NULL; result = m_connection.ldap_search_ext_s(ld, m_basepoint, m_scope, m_query, m_AttrNames, FALSE, serverControls, NULL, NULL, 0, &m_message); if ( ! result ) { m_nReturned = m_connection.ldap_count_entries(ld,m_message); m_nCurrent = 0; m_bOpen = TRUE; } return m_connection.LdapMapErrorToWin32(result); } DWORD CLdapEnum::Next( PWCHAR ** ppAttrs // out- array of values for the next matching item ) { DWORD rc = 0; if ( ! m_bOpen ) { rc = ERROR_NOT_FOUND; } else { if ( m_nReturned > m_nCurrent ) { // return the next entry from the current page return GetNextEntry(ppAttrs); } else { // see if there are more pages of results to get rc = GetNextPage(); if (! rc ) { return GetNextEntry(ppAttrs); } } } return rc; } void CLdapEnum::FreeData(WCHAR ** values) { for ( int i = 0 ; m_AttrNames[i] ; i++ ) { if ( values[i] ) { delete [] values[i]; values[i] = NULL; } } delete [] values; } DWORD CLdapEnum::GetNextEntry( PWCHAR ** ppAttrs ) { DWORD rc = 0; WCHAR ** pValues = new PWCHAR[m_nAttributes+1]; if (!pValues) return ERROR_NOT_ENOUGH_MEMORY; if ( m_nCurrent == 0 ) { m_currMsg = m_connection.ldap_first_entry(m_connection.GetHandle(),m_message); } else { m_currMsg = m_connection.ldap_next_entry(m_connection.GetHandle(),m_currMsg); } if ( m_currMsg ) { int curr; for ( curr = 0 ; m_AttrNames[curr] ; curr++ ) { pValues[curr] = NULL; WCHAR ** allvals = m_connection.ldap_get_values(m_connection.GetHandle(),m_currMsg,m_AttrNames[curr] ); if ( allvals ) { try { pValues[curr] = new WCHAR[UStrLen(allvals[0])+1]; } catch (...) { for (int j=0; jbv_len == 0 && currCookie->bv_val == 0 ) { // under Exchange 5.5, SP 2, this means we're at the end of the results. // if we pass in this cookie again, we will start over at the beginning of the search results. result = LDAP_CONTROL_NOT_FOUND; } serverControls[0] = NULL; serverControls[1] = NULL; if ( ! result ) { result = m_connection.ldap_create_page_control(ld, m_pageSize, currCookie, FALSE, serverControls); } m_connection.ldap_controls_free(currControls); currControls = NULL; m_connection.ber_bvfree(currCookie); currCookie = NULL; } // continue the search with the new cookie if ( ! result ) { result = m_connection.ldap_search_ext_s(ld, m_basepoint, m_scope, m_query, m_AttrNames, FALSE, serverControls, NULL, NULL, 0, &m_message); if ( result && result != LDAP_CONTROL_NOT_FOUND ) { // LDAP_CONTROL_NOT_FOUND means that we have reached the end of the search results // in Exchange 5.5, before SP 2 (the server doesn't return a page control when there // are no more pages, so we get LDAP_CONTROL_NOT_FOUND when we try to extract the page // control from the search results). } } if ( ! result ) { m_nReturned = m_connection.ldap_count_entries(ld,m_message); m_nCurrent = 0; } return m_connection.LdapMapErrorToWin32(result); } void GetLDAPPort( DWORD * LDAPPort, DWORD * SSLPort ) { TRegKey admtRegKey; DWORD rc; // assign the default values *LDAPPort = LDAP_PORT; *SSLPort = LDAP_SSL_PORT; // get ADMT key rc = admtRegKey.Open(GET_STRING(IDS_HKLM_DomainAdmin_Key),HKEY_LOCAL_MACHINE); if (rc == ERROR_SUCCESS) { DWORD portNumber; rc = admtRegKey.ValueGetDWORD(REGVAL_EXCHANGE_LDAP_PORT, &portNumber); if (rc == ERROR_SUCCESS) { *LDAPPort = portNumber; } else { err.MsgWrite(ErrW, DCT_MSG_UNABLE_TO_READ_EXCHANGE_LDAP_PORT_SSD, REGVAL_EXCHANGE_LDAP_PORT, GET_STRING(IDS_HKLM_DomainAdmin_Key), LDAP_PORT); } rc = admtRegKey.ValueGetDWORD(REGVAL_EXCHANGE_SSL_PORT, &portNumber); if (rc == ERROR_SUCCESS) { *SSLPort = portNumber; } else { err.MsgWrite(ErrW, DCT_MSG_UNABLE_TO_READ_EXCHANGE_SSL_PORT_SSD, REGVAL_EXCHANGE_SSL_PORT, GET_STRING(IDS_HKLM_DomainAdmin_Key), LDAP_SSL_PORT); } } else { err.MsgWrite(ErrW, DCT_MSG_UNABLE_TO_READ_EXCHANGE_LDAP_PORT_SSD, REGVAL_EXCHANGE_LDAP_PORT, GET_STRING(IDS_HKLM_DomainAdmin_Key), LDAP_PORT); err.MsgWrite(ErrW, DCT_MSG_UNABLE_TO_READ_EXCHANGE_SSL_PORT_SSD, REGVAL_EXCHANGE_SSL_PORT, GET_STRING(IDS_HKLM_DomainAdmin_Key), LDAP_SSL_PORT); } }