/* Editor Settings: expandtabs and use 4 spaces for indentation * ex: set softtabstop=4 tabstop=8 expandtab shiftwidth=4: * */ /* * Copyright (c) Likewise Software. All rights Reserved. * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the license, or (at * your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * General Public License for more details. You should have received a copy * of the GNU Lesser General Public License along with this program. If * not, see . * * LIKEWISE SOFTWARE MAKES THIS SOFTWARE AVAILABLE UNDER OTHER LICENSING * TERMS AS WELL. IF YOU HAVE ENTERED INTO A SEPARATE LICENSE AGREEMENT * WITH LIKEWISE SOFTWARE, THEN YOU MAY ELECT TO USE THE SOFTWARE UNDER THE * TERMS OF THAT SOFTWARE LICENSE AGREEMENT INSTEAD OF THE TERMS OF THE GNU * LESSER GENERAL PUBLIC LICENSE, NOTWITHSTANDING THE ABOVE NOTICE. IF YOU * HAVE QUESTIONS, OR WISH TO REQUEST A COPY OF THE ALTERNATE LICENSING * TERMS OFFERED BY LIKEWISE SOFTWARE, PLEASE CONTACT LIKEWISE SOFTWARE AT * license@likewisesoftware.com */ /* * Copyright (C) Likewise Software. All rights reserved. * * Module Name: * * lwldap.c * * Abstract: * * Likewise Advanced API (lwadvapi) * * LDAP API * * Authors: Krishna Ganugapati (krishnag@likewisesoftware.com) * Sriram Nambakam (snambakam@likewisesoftware.com) * Wei Fu (wfu@likewisesoftware.com) * Kyle Stemen (kstemen@likewisesoftware.com) * Brian Dunstan (bdunstan@likewisesoftware.com) */ #include "includes.h" /* used by inet_addr, not defined on Solaris anywhere!? */ #ifndef INADDR_NONE #define INADDR_NONE ((in_addr_t) -1) #endif DWORD LwCLdapOpenDirectory( IN PCSTR pszServerName, OUT PHANDLE phDirectory ) { DWORD dwError = LW_ERROR_SUCCESS; LDAP * ld = NULL; PLW_LDAP_DIRECTORY_CONTEXT pDirectory = NULL; int rc = LDAP_VERSION3; PSTR pszURL = NULL; LW_BAIL_ON_INVALID_STRING(pszServerName); dwError = LwAllocateStringPrintf(&pszURL, "cldap://%s", pszServerName); BAIL_ON_LW_ERROR(dwError); dwError = ldap_initialize(&ld, pszURL); BAIL_ON_LDAP_ERROR(dwError); dwError = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &rc); BAIL_ON_LDAP_ERROR(dwError); dwError = ldap_set_option(ld, LDAP_OPT_REFERRALS, (void *)LDAP_OPT_OFF); BAIL_ON_LDAP_ERROR(dwError); dwError = LwAllocateMemory(sizeof(*pDirectory), OUT_PPVOID(&pDirectory)); BAIL_ON_LW_ERROR(dwError); pDirectory->ld = ld; error: LW_SAFE_FREE_STRING(pszURL); if (dwError) { if (pDirectory) { LwLdapCloseDirectory(pDirectory); pDirectory = NULL; } } *phDirectory = (HANDLE)pDirectory; return dwError; } DWORD LwLdapPingTcp( PCSTR pszHostAddress, DWORD dwTimeoutSeconds ) { DWORD dwError = 0; int sysRet = 0; int fd = -1; struct in_addr addr; struct sockaddr_in socketAddress; struct timeval timeout; fd_set fds; int socketError; SOCKLEN_T socketErrorLength = 0; addr.s_addr = inet_addr(pszHostAddress); if (addr.s_addr == INADDR_NONE) { LW_LOG_ERROR("Could not convert address'%s' to in_addr", pszHostAddress); dwError = LW_ERROR_DNS_RESOLUTION_FAILED; BAIL_ON_LW_ERROR(dwError); } socketAddress.sin_family = AF_INET; socketAddress.sin_port = htons(389); socketAddress.sin_addr = addr; fd = socket(PF_INET, SOCK_STREAM, 0); if (fd < 0) { dwError = LwMapErrnoToLwError(errno); BAIL_ON_LW_ERROR(dwError); } sysRet = fcntl(fd, F_SETFL, O_NONBLOCK); if (sysRet < 0) { dwError = LwMapErrnoToLwError(errno); BAIL_ON_LW_ERROR(dwError); } sysRet = connect(fd, (struct sockaddr *)&socketAddress, sizeof(socketAddress)); { dwError = LwMapErrnoToLwError(errno); // We typically expect EINPROGRESS dwError = (LW_ERROR_ERRNO_EINPROGRESS == dwError) ? 0 : dwError; BAIL_ON_LW_ERROR(dwError); } FD_ZERO(&fds); FD_SET(fd, &fds); timeout.tv_sec = dwTimeoutSeconds; timeout.tv_usec = 0; sysRet = select(fd + 1, NULL, &fds, NULL, &timeout); if (sysRet < 0) { dwError = LwMapErrnoToLwError(errno); BAIL_ON_LW_ERROR(dwError); } switch (sysRet) { case 0: // We timed out LW_LOG_DEBUG("Timed out connecting to '%s'", pszHostAddress); // ISSUE-2008/09/16-dalmeida -- Technically, not a "domain"... dwError = LW_ERROR_DOMAIN_IS_OFFLINE; BAIL_ON_LW_ERROR(dwError); break; case 1: // Normal case break; default: // This should never happen. LW_LOG_DEBUG("Unexpected number of file descriptors returned (%d)", sysRet); dwError = LW_ERROR_INVALID_PARAMETER; BAIL_ON_LW_ERROR(dwError); break; } if (!FD_ISSET(fd, &fds)) { // ISSUE-2008/07/15-dalmeida -- Suitable error code? dwError = LW_ERROR_INVALID_PARAMETER; BAIL_ON_LW_ERROR(dwError); } socketError = 0; socketErrorLength = sizeof(socketError); sysRet = getsockopt(fd, SOL_SOCKET, SO_ERROR, &socketError, &socketErrorLength); if (sysRet < 0) { dwError = LwMapErrnoToLwError(errno); BAIL_ON_LW_ERROR(dwError); } if (socketErrorLength != sizeof(socketError)) { dwError = LW_ERROR_ERRNO_EMSGSIZE; BAIL_ON_LW_ERROR(dwError); } if (socketError) { dwError = LwMapErrnoToLwError(socketError); BAIL_ON_LW_ERROR(dwError); } error: if (fd != -1) { close(fd); } return dwError; } DWORD LwLdapOpenDirectoryServerSingleAttempt( IN PCSTR pszServerAddress, IN PCSTR pszServerName, IN DWORD dwTimeoutSec, IN DWORD dwFlags, OUT PLW_LDAP_DIRECTORY_CONTEXT* ppDirectory ) { DWORD dwError = LW_ERROR_SUCCESS; LDAP * ld = NULL; PLW_LDAP_DIRECTORY_CONTEXT pDirectory = NULL; int rc = LDAP_VERSION3; DWORD dwPort = 389; struct timeval timeout = {0}; DWORD dwSecurity = GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG; timeout.tv_sec = dwTimeoutSec; LW_BAIL_ON_INVALID_STRING(pszServerName); LW_BAIL_ON_INVALID_STRING(pszServerAddress); if (dwFlags & LW_LDAP_OPT_GLOBAL_CATALOG) { dwPort = 3268; } // This creates the ld without immediately connecting to the server. // That way a connection timeout can be set first. ld = (LDAP *)ldap_init(pszServerAddress, dwPort); if (!ld) { dwError = LwMapErrnoToLwError(errno); LW_LOG_ERROR("Failed to open LDAP connection to domain controller"); BAIL_ON_LW_ERROR(dwError); LW_LOG_ERROR("Failed to get errno for failed open LDAP connection"); dwError = LW_ERROR_LDAP_ERROR; BAIL_ON_LW_ERROR(dwError); } dwError = ldap_set_option(ld, LDAP_OPT_NETWORK_TIMEOUT, &timeout); BAIL_ON_LDAP_ERROR(dwError); dwError = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &rc); if (dwError) { LW_LOG_ERROR("Failed to set LDAP option protocol version"); BAIL_ON_LDAP_ERROR(dwError); } dwError = ldap_set_option( ld, LDAP_OPT_REFERRALS, (void *)LDAP_OPT_OFF); if (dwError) { LW_LOG_ERROR("Failed to set LDAP option to not follow referrals"); BAIL_ON_LDAP_ERROR(dwError); } /* This tells ldap to retry when select returns with EINTR */ dwError = ldap_set_option( ld, LDAP_OPT_RESTART, (void *)LDAP_OPT_ON); if (dwError) { LW_LOG_ERROR("Failed to set LDAP option to auto retry "); BAIL_ON_LDAP_ERROR(dwError); } dwSecurity |= GSS_C_INTEG_FLAG; if (dwFlags & LW_LDAP_OPT_SIGN_AND_SEAL) { dwSecurity |= GSS_C_CONF_FLAG; } dwError = ldap_set_option(ld, LDAP_OPT_SSPI_FLAGS, (void*)&dwSecurity); if (dwError) { LW_LOG_ERROR("Failed to set LDAP GSS-API option to" " sign and/or seal"); BAIL_ON_LDAP_ERROR(dwError); } dwError = ldap_set_option(ld, LDAP_OPT_X_GSSAPI_ALLOW_REMOTE_PRINCIPAL, LDAP_OPT_ON); if (dwError) { LW_LOG_ERROR("Failed to set LDAP GSS-API option to allow" " remote principals"); BAIL_ON_LDAP_ERROR(dwError); } dwError = ldap_set_option(ld, LDAP_OPT_HOST_NAME, pszServerName); if (dwError) { LW_LOG_ERROR("Failed to set LDAP host name option"); BAIL_ON_LDAP_ERROR(dwError); } dwError = LwAllocateMemory(sizeof(*pDirectory), OUT_PPVOID(&pDirectory)); BAIL_ON_LW_ERROR(dwError); pDirectory->ld = ld; ld = NULL; if (dwFlags & LW_LDAP_OPT_ANNONYMOUS) { dwError = LwLdapBindDirectoryAnonymous((HANDLE)pDirectory); } else { dwError = LwLdapBindDirectory((HANDLE)pDirectory, pszServerName); } // The above functions return -1 when a connection times out. if (dwError == (DWORD)-1) { dwError = ETIMEDOUT; } BAIL_ON_LW_ERROR(dwError); *ppDirectory = pDirectory; cleanup: return(dwError); error: if (pDirectory) { LwLdapCloseDirectory(pDirectory); } if (ld) { ldap_unbind_s(ld); } *ppDirectory = (HANDLE)NULL; goto cleanup; } DWORD LwLdapOpenDirectoryServer( IN PCSTR pszServerAddress, IN PCSTR pszServerName, IN DWORD dwFlags, OUT PHANDLE phDirectory ) { DWORD dwError = LW_ERROR_SUCCESS; PLW_LDAP_DIRECTORY_CONTEXT pDirectory = NULL; DWORD dwAttempt = 0; struct timespec sleepTime; DWORD dwTimeoutSec = 15; LW_BAIL_ON_INVALID_STRING(pszServerName); LW_BAIL_ON_INVALID_STRING(pszServerAddress); for (dwAttempt = 1; dwAttempt <= 3; dwAttempt++) { // dwTimeoutSec controls how long openldap will wait for the connection // to be established. For the first attempt, this is set to 15 seconds // (which is the same amount of time netlogon will wait to get the dc // name). The second attempt halves the value to 7 seconds. The third // attempt halves it again to 3 seconds. dwError = LwLdapOpenDirectoryServerSingleAttempt( pszServerAddress, pszServerName, dwTimeoutSec, dwFlags, &pDirectory); if (dwError == ETIMEDOUT) { LW_ASSERT(pDirectory == NULL); LW_LOG_ERROR("The ldap connection to %s was disconnected. This was attempt #%d", pszServerAddress, dwAttempt); dwTimeoutSec /= 2; // This is the amount of time to sleep before trying to reconnect // again. It is: .1 seconds * dwAttempt sleepTime.tv_sec = 0; sleepTime.tv_nsec = dwAttempt * 100000000; while (nanosleep(&sleepTime, &sleepTime) == -1) { if (errno != EINTR) { dwError = LwMapErrnoToLwError(errno); BAIL_ON_LW_ERROR(dwError); } } continue; } BAIL_ON_LW_ERROR(dwError); break; } *phDirectory = (HANDLE)pDirectory; cleanup: return(dwError); error: if (pDirectory) { LwLdapCloseDirectory(pDirectory); } *phDirectory = (HANDLE)NULL; goto cleanup; } DWORD LwLdapBindDirectoryAnonymous( HANDLE hDirectory ) { DWORD dwError = 0; PLW_LDAP_DIRECTORY_CONTEXT pDirectory = (PLW_LDAP_DIRECTORY_CONTEXT)hDirectory; LW_BAIL_ON_INVALID_HANDLE(hDirectory); dwError = ldap_bind_s( pDirectory->ld, NULL, NULL, LDAP_AUTH_SIMPLE); BAIL_ON_LDAP_ERROR(dwError); cleanup: return dwError; error: LW_LOG_ERROR("Failed on LDAP simple bind (Error code: %u)", dwError); if(pDirectory->ld != NULL) { ldap_unbind_s(pDirectory->ld); pDirectory->ld = NULL; } goto cleanup; } DWORD LwLdapBindDirectory( HANDLE hDirectory, PCSTR pszServerName ) { DWORD dwError = LW_ERROR_SUCCESS; DWORD dwMajorStatus = 0; DWORD dwMinorStatus = 0; CtxtHandle GSSContext = {0}; PCtxtHandle pGSSContext = &GSSContext; PSTR pszTargetName = NULL; PLW_LDAP_DIRECTORY_CONTEXT pDirectory = NULL; gss_buffer_desc input_name = GSS_C_EMPTY_BUFFER; gss_buffer_desc input_desc = GSS_C_EMPTY_BUFFER; gss_buffer_desc output_desc = GSS_C_EMPTY_BUFFER; gss_buffer_desc token = GSS_C_EMPTY_BUFFER; // PCtxtHandle pContextHandle = NULL; OM_uint32 ret_flags = 0; gss_name_t targ_name = GSS_C_NO_NAME; input_desc.value = NULL; input_desc.length = 0; //Leave the realm empty so that kerberos referrals are turned on. dwError = LwAllocateStringPrintf(&pszTargetName,"ldap/%s@", pszServerName); BAIL_ON_LW_ERROR(dwError); pDirectory = (PLW_LDAP_DIRECTORY_CONTEXT)hDirectory; input_name.value = pszTargetName; input_name.length = strlen(pszTargetName); dwMajorStatus = gss_import_name((OM_uint32 *)&dwMinorStatus, &input_name, (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME, &targ_name); display_status("gss_import_name", dwMajorStatus, dwMinorStatus); BAIL_ON_SEC_ERROR(dwMajorStatus); token.value = "{1 3 6 1 5 5 2}"; token.length = strlen(token.value); memset(pGSSContext, 0, sizeof(CtxtHandle)); *pGSSContext = GSS_C_NO_CONTEXT; dwMajorStatus = gss_init_sec_context((OM_uint32 *)&dwMinorStatus, NULL, pGSSContext, targ_name, (gss_OID)gss_mech_krb5, GSS_C_REPLAY_FLAG | GSS_C_MUTUAL_FLAG, 0, NULL, &input_desc, NULL, &output_desc, &ret_flags, NULL); display_status("gss_init_context", dwMajorStatus, dwMinorStatus); if ( (dwMajorStatus == GSS_S_FAILURE && (dwMinorStatus == (DWORD)KRB5KRB_AP_ERR_TKT_EXPIRED || dwMinorStatus == (DWORD)KRB5KDC_ERR_NEVER_VALID)) || (dwMajorStatus == GSS_S_CRED_UNAVAIL && dwMinorStatus == 0x25ea10c /* This is KG_EMPTY_CCACHE * inside of gssapi, but that symbol * is not exposed externally. This * code means that the credentials * cache does not have a TGT inside * of it. */ ) ) { /* The kerberos ticket expired or is about to expire (The * machine password sync thread didn't do its job). */ LW_LOG_INFO("Renewing machine tgt outside of password sync thread"); dwError = LwKrb5RefreshMachineTGT(NULL); BAIL_ON_LW_ERROR(dwError); } if (dwMajorStatus == GSS_S_FAILURE) { switch (dwMinorStatus) { case KRB5KRB_AP_ERR_SKEW: dwError = ERROR_TIME_SKEW; BAIL_ON_LW_ERROR(dwError); break; default: BAIL_ON_SEC_ERROR(dwMajorStatus); break; } } if (dwMajorStatus != 0 && dwMajorStatus != GSS_S_CONTINUE_NEEDED) { BAIL_ON_SEC_ERROR(dwMajorStatus); } dwError = ldap_gssapi_bind_s(pDirectory->ld, NULL, NULL); if (dwError != 0) { LW_LOG_ERROR("ldap_gssapi_bind_s failed with error code %d", dwError); BAIL_ON_LDAP_ERROR(dwError); } error: if (targ_name) { gss_release_name((OM_uint32*)&dwMinorStatus, &targ_name); } if (output_desc.value) { gss_release_buffer((OM_uint32 *)&dwMinorStatus, &output_desc); } if (*pGSSContext != GSS_C_NO_CONTEXT) { gss_delete_sec_context((OM_uint32*)&dwMinorStatus, pGSSContext, GSS_C_NO_BUFFER); } LW_SAFE_FREE_STRING(pszTargetName); return(dwError); } void display_status(char *msg, OM_uint32 maj_stat, OM_uint32 min_stat) { // BAIL_ON_SEC_ERROR shows this code // display_status_1(msg, maj_stat, GSS_C_GSS_CODE); display_status_1(msg, min_stat, GSS_C_MECH_CODE); } void display_status_1(char *m, OM_uint32 code, int type) { OM_uint32 maj_stat, min_stat; gss_buffer_desc msg; OM_uint32 msg_ctx; if ( code == 0 ) { return; } msg_ctx = 0; while (1) { maj_stat = gss_display_status(&min_stat, code, type, GSS_C_NULL_OID, &msg_ctx, &msg); switch(code) { #ifdef WIN32 case SEC_E_OK: case SEC_I_CONTINUE_NEEDED: #else case GSS_S_COMPLETE: case GSS_S_CONTINUE_NEEDED: #endif LW_LOG_DEBUG("GSS-API error calling %s: %d (%s)", m, code, (char *)msg.value); break; default: LW_LOG_DEBUG("GSS-API error calling %s: %d (%s)", m, code, (char *)msg.value); } (void) gss_release_buffer(&min_stat, &msg); if (!msg_ctx) break; } } void LwLdapCloseDirectory( HANDLE hDirectory ) { PLW_LDAP_DIRECTORY_CONTEXT pDirectory = (PLW_LDAP_DIRECTORY_CONTEXT)hDirectory; if (pDirectory) { if(pDirectory->ld) { ldap_unbind_s(pDirectory->ld); } LwFreeMemory(pDirectory); } return; } DWORD LwLdapGetParentDN( PCSTR pszObjectDN, PSTR* ppszParentDN ) { DWORD dwError = LW_ERROR_SUCCESS; PSTR pszParentDN = NULL; PSTR pComma = NULL; if (!pszObjectDN || !*pszObjectDN) { dwError = LW_ERROR_INVALID_PARAMETER; BAIL_ON_LW_ERROR(dwError); } pComma = strchr(pszObjectDN,','); if (!pComma) { dwError = LW_ERROR_LDAP_NO_PARENT_DN; BAIL_ON_LW_ERROR(dwError); } pComma++; dwError= LwAllocateString(pComma, &pszParentDN); BAIL_ON_LW_ERROR(dwError); *ppszParentDN = pszParentDN; return(dwError); error: *ppszParentDN = NULL; return(dwError); } DWORD LwLdapDirectorySearch( HANDLE hDirectory, PCSTR pszObjectDN, int scope, PCSTR pszQuery, PSTR* ppszAttributeList, LDAPMessage** ppMessage ) { DWORD dwError = LW_ERROR_SUCCESS; PLW_LDAP_DIRECTORY_CONTEXT pDirectory = (PLW_LDAP_DIRECTORY_CONTEXT)hDirectory; struct timeval timeout = {0}; LDAPMessage* pMessage = NULL; timeout.tv_sec = 15; timeout.tv_usec = 0; dwError = ldap_search_st(pDirectory->ld, pszObjectDN, scope, pszQuery, ppszAttributeList, 0, &timeout, &pMessage); if (dwError) { if (dwError==LDAP_NO_SUCH_OBJECT) { LW_LOG_VERBOSE("Caught LDAP_NO_SUCH_OBJECT Error on ldap search"); BAIL_ON_LDAP_ERROR(dwError); } if (dwError == LDAP_FILTER_ERROR) { LW_LOG_ERROR("Caught LDAP_FILTER_ERROR on ldap search"); LW_LOG_ERROR("LDAP Search Info: DN: [%s]", LW_IS_NULL_OR_EMPTY_STR(pszObjectDN) ? "" : pszObjectDN); LW_LOG_ERROR("LDAP Search Info: scope: [%d]", scope); LW_LOG_ERROR("LDAP Search Info: query: [%s]", LW_IS_NULL_OR_EMPTY_STR(pszQuery) ? "" : pszQuery); BAIL_ON_LDAP_ERROR(dwError); } if (dwError == LDAP_REFERRAL) { LW_LOG_ERROR("Caught LDAP_REFERRAL Error on ldap search"); LW_LOG_ERROR("LDAP Search Info: DN: [%s]", LW_IS_NULL_OR_EMPTY_STR(pszObjectDN) ? "" : pszObjectDN); LW_LOG_ERROR("LDAP Search Info: scope: [%d]", scope); LW_LOG_ERROR("LDAP Search Info: query: [%s]", LW_IS_NULL_OR_EMPTY_STR(pszQuery) ? "" : pszQuery); if (ppszAttributeList) { size_t i; for (i = 0; ppszAttributeList[i] != NULL; i++) { LW_LOG_ERROR("LDAP Search Info: attribute: [%s]", ppszAttributeList[i]); } } else { LW_LOG_ERROR("Error: LDAP Search Info: no attributes were specified"); } } if (dwError == LDAP_SERVER_DOWN) { /* Note: Do not log as ERROR, since this can occur in normal operations */ LW_LOG_INFO("Caught LDAP_SERVER_DOWN Error on ldap search"); dwError = LW_ERROR_LDAP_SERVER_DOWN; goto error; } if (dwError == LDAP_TIMEOUT) { LW_LOG_ERROR("Caught LDAP_TIMEOUT Error on ldap search"); dwError = LW_ERROR_LDAP_TIMEOUT; goto error; } if (dwError == LDAP_CONNECT_ERROR) { LW_LOG_ERROR("Caught LDAP_CONNECT_ERROR on ldap search"); dwError = LW_ERROR_LDAP_SERVER_UNAVAILABLE; goto error; } LW_LOG_ERROR("Caught ldap error %d on search [%s]", dwError, LW_SAFE_LOG_STRING(pszQuery)); BAIL_ON_LDAP_ERROR(dwError); } *ppMessage = pMessage; cleanup: return(dwError); error: *ppMessage = NULL; if (pMessage) { ldap_msgfree(pMessage); } goto cleanup; } DWORD LwLdapDirectorySearchEx( HANDLE hDirectory, PCSTR pszObjectDN, int scope, PCSTR pszQuery, PSTR* ppszAttributeList, LDAPControl** ppServerControls, DWORD dwNumMaxEntries, LDAPMessage** ppMessage ) { DWORD dwError = LW_ERROR_SUCCESS; PLW_LDAP_DIRECTORY_CONTEXT pDirectory = (PLW_LDAP_DIRECTORY_CONTEXT)hDirectory; struct timeval timeout = {0}; LDAPMessage* pMessage = NULL; // Set timeout to 60 seconds to be able to deal with large group // Instead of bailing on errors timeout.tv_sec = 60; timeout.tv_usec = 0; dwError = ldap_search_ext_s( pDirectory->ld, pszObjectDN, scope, pszQuery, ppszAttributeList, 0, ppServerControls, NULL, &timeout, dwNumMaxEntries, &pMessage); if (dwError) { if (dwError == LDAP_NO_SUCH_OBJECT) { LW_LOG_VERBOSE("Caught LDAP_NO_SUCH_OBJECT Error on ldap search"); BAIL_ON_LDAP_ERROR(dwError); } if (dwError == LDAP_REFERRAL) { LW_LOG_ERROR("Caught LDAP_REFERRAL Error on ldap search"); LW_LOG_ERROR("LDAP Search Info: DN: [%s]", LW_IS_NULL_OR_EMPTY_STR(pszObjectDN) ? "" : pszObjectDN); LW_LOG_ERROR("LDAP Search Info: scope: [%d]", scope); LW_LOG_ERROR("LDAP Search Info: query: [%s]", LW_IS_NULL_OR_EMPTY_STR(pszQuery) ? "" : pszQuery); if (ppszAttributeList) { size_t i; for (i = 0; ppszAttributeList[i] != NULL; i++) { LW_LOG_ERROR("LDAP Search Info: attribute: [%s]", ppszAttributeList[i]); } } else { LW_LOG_ERROR("Error: LDAP Search Info: no attributes were specified"); } } BAIL_ON_LDAP_ERROR(dwError); } *ppMessage = pMessage; cleanup: return(dwError); error: *ppMessage = NULL; if (pMessage) { ldap_msgfree(pMessage); } goto cleanup; } DWORD LwLdapEnablePageControlOption( HANDLE hDirectory ) { DWORD dwError = LW_ERROR_SUCCESS; PLW_LDAP_DIRECTORY_CONTEXT pDirectory = NULL; LDAPControl serverControl = {0}; LDAPControl *ppServerPageCtrls[2] = {NULL, NULL}; serverControl.ldctl_value.bv_val = NULL; serverControl.ldctl_value.bv_len = 0; serverControl.ldctl_oid = LDAP_CONTROL_PAGEDRESULTS; serverControl.ldctl_iscritical = 'T'; ppServerPageCtrls[0] = &serverControl; pDirectory = (PLW_LDAP_DIRECTORY_CONTEXT)hDirectory; dwError = ldap_set_option(pDirectory->ld, LDAP_OPT_SERVER_CONTROLS, OUT_PPVOID(&ppServerPageCtrls)); BAIL_ON_LDAP_ERROR(dwError); cleanup: return dwError; error: goto cleanup; } DWORD LwLdapDisablePageControlOption( HANDLE hDirectory ) { DWORD dwError = LW_ERROR_SUCCESS; PLW_LDAP_DIRECTORY_CONTEXT pDirectory = (PLW_LDAP_DIRECTORY_CONTEXT)hDirectory; LDAPControl *ppServerPageCtrls[1] = {NULL}; dwError = ldap_set_option(pDirectory->ld, LDAP_OPT_SERVER_CONTROLS, OUT_PPVOID(&ppServerPageCtrls)); BAIL_ON_LDAP_ERROR(dwError); cleanup: return dwError; error: goto cleanup; } DWORD LwLdapDirectoryOnePagedSearch( HANDLE hDirectory, PCSTR pszObjectDN, PCSTR pszQuery, PSTR* ppszAttributeList, DWORD dwPageSize, PLW_SEARCH_COOKIE pCookie, int scope, LDAPMessage** ppMessage ) { DWORD dwError = LW_ERROR_SUCCESS; PLW_LDAP_DIRECTORY_CONTEXT pDirectory = NULL; ber_int_t pageCount = 0; CHAR pagingCriticality = 'T'; LDAPControl *pPageControl = NULL; LDAPControl *ppInputControls[2] = { NULL, NULL }; LDAPControl **ppReturnedControls = NULL; int errorcodep = 0; LDAPMessage* pMessage = NULL; BOOLEAN bSearchFinished = FALSE; struct berval * pBerCookie = (struct berval *)pCookie->pvData; LW_ASSERT(pCookie->pfnFree == NULL || pCookie->pfnFree == LwLdapFreeCookie); pDirectory = (PLW_LDAP_DIRECTORY_CONTEXT)hDirectory; // dwError = ADEnablePageControlOption(hDirectory); // BAIL_ON_LW_ERROR(dwError); dwError = ldap_create_page_control(pDirectory->ld, dwPageSize, pBerCookie, pagingCriticality, &pPageControl); BAIL_ON_LDAP_ERROR(dwError); ppInputControls[0] = pPageControl; dwError = LwLdapDirectorySearchEx( hDirectory, pszObjectDN, scope, pszQuery, ppszAttributeList, ppInputControls, 0, &pMessage); BAIL_ON_LW_ERROR(dwError); dwError = ldap_parse_result(pDirectory->ld, pMessage, &errorcodep, NULL, NULL, NULL, &ppReturnedControls, 0); BAIL_ON_LDAP_ERROR(dwError); if (pBerCookie != NULL) { ber_bvfree(pBerCookie); pBerCookie = NULL; } dwError = ldap_parse_page_control(pDirectory->ld, ppReturnedControls, &pageCount, &pBerCookie); BAIL_ON_LDAP_ERROR(dwError); if (pBerCookie == NULL || pBerCookie->bv_len < 1) { bSearchFinished = TRUE; } if (ppReturnedControls) { ldap_controls_free(ppReturnedControls); ppReturnedControls = NULL; } ppInputControls[0] = NULL; ldap_control_free(pPageControl); pPageControl = NULL; pCookie->bSearchFinished = bSearchFinished; *ppMessage = pMessage; pCookie->pvData = pBerCookie; pCookie->pfnFree = LwLdapFreeCookie; cleanup: /* dwError_disable = ADDisablePageControlOption(hDirectory); if (dwError_disable) LW_LOG_ERROR("Error: LDAP Disable PageControl Info: failed");*/ if (ppReturnedControls) { ldap_controls_free(ppReturnedControls); } ppInputControls[0] = NULL; if (pPageControl) { ldap_control_free(pPageControl); } return (dwError); error: *ppMessage = NULL; pCookie->pvData = NULL; pCookie->pfnFree = NULL; pCookie->bSearchFinished = TRUE; if (pBerCookie != NULL) { ber_bvfree(pBerCookie); pBerCookie = NULL; } goto cleanup; } LDAPMessage* LwLdapFirstEntry( HANDLE hDirectory, LDAPMessage* pMessage ) { PLW_LDAP_DIRECTORY_CONTEXT pDirectory = NULL; pDirectory = (PLW_LDAP_DIRECTORY_CONTEXT)hDirectory; return ldap_first_entry(pDirectory->ld, pMessage); } LDAPMessage* LwLdapNextEntry( HANDLE hDirectory, LDAPMessage* pMessage ) { PLW_LDAP_DIRECTORY_CONTEXT pDirectory = NULL; pDirectory = (PLW_LDAP_DIRECTORY_CONTEXT)hDirectory; return ldap_next_entry(pDirectory->ld, pMessage); } LDAP * LwLdapGetSession( HANDLE hDirectory ) { PLW_LDAP_DIRECTORY_CONTEXT pDirectory = (PLW_LDAP_DIRECTORY_CONTEXT)hDirectory; return(pDirectory->ld); } DWORD LwLdapGetBytes( HANDLE hDirectory, LDAPMessage* pMessage, PSTR pszFieldName, PBYTE* ppszByteValue, PDWORD pszByteLen ) { DWORD dwError = LW_ERROR_SUCCESS; PLW_LDAP_DIRECTORY_CONTEXT pDirectory = NULL; struct berval **ppszValues = NULL; PBYTE pszByteValue = NULL; DWORD szByteLen = 0; pDirectory = (PLW_LDAP_DIRECTORY_CONTEXT)hDirectory; ppszValues = ldap_get_values_len(pDirectory->ld, pMessage, pszFieldName); if (ppszValues && ppszValues[0]){ if (ppszValues[0]->bv_len != 0){ dwError = LwAllocateMemory( sizeof(BYTE) * ppszValues[0]->bv_len, OUT_PPVOID(&pszByteValue)); BAIL_ON_LW_ERROR(dwError); memcpy (pszByteValue, ppszValues[0]->bv_val, ppszValues[0]->bv_len * sizeof (BYTE)); szByteLen = ppszValues[0]->bv_len; } } *ppszByteValue = pszByteValue; *pszByteLen = szByteLen; cleanup: if (ppszValues) { ldap_value_free_len(ppszValues); } return dwError; error: *ppszByteValue = NULL; *pszByteLen = 0; LW_SAFE_FREE_MEMORY(pszByteValue); goto cleanup; } DWORD LwLdapGetString( HANDLE hDirectory, LDAPMessage* pMessage, PCSTR pszFieldName, PSTR* ppszValue ) { DWORD dwError = LW_ERROR_SUCCESS; PLW_LDAP_DIRECTORY_CONTEXT pDirectory = NULL; PSTR *ppszValues = NULL; PSTR pszValue = NULL; pDirectory = (PLW_LDAP_DIRECTORY_CONTEXT)hDirectory; ppszValues = (PSTR*)ldap_get_values(pDirectory->ld, pMessage, pszFieldName); if (ppszValues && ppszValues[0]) { dwError = LwAllocateString(ppszValues[0], &pszValue); BAIL_ON_LW_ERROR(dwError); } *ppszValue = pszValue; cleanup: if (ppszValues) { ldap_value_free(ppszValues); } return dwError; error: *ppszValue = NULL; LW_SAFE_FREE_STRING(pszValue); goto cleanup; } DWORD LwLdapGetDN( HANDLE hDirectory, LDAPMessage* pMessage, PSTR* ppszValue ) { DWORD dwError = LW_ERROR_SUCCESS; PLW_LDAP_DIRECTORY_CONTEXT pDirectory = NULL; PSTR pszLdapValue = NULL; PSTR pszValue = NULL; pDirectory = (PLW_LDAP_DIRECTORY_CONTEXT)hDirectory; pszLdapValue = ldap_get_dn(pDirectory->ld, pMessage); if (LW_IS_NULL_OR_EMPTY_STR(pszLdapValue)) { dwError = LW_ERROR_INVALID_LDAP_ATTR_VALUE; BAIL_ON_LW_ERROR(dwError); } dwError = LwAllocateString(pszLdapValue, &pszValue); BAIL_ON_LW_ERROR(dwError); *ppszValue = pszValue; cleanup: if (pszLdapValue) { ldap_memfree(pszLdapValue); } return dwError; error: *ppszValue = NULL; LW_SAFE_FREE_STRING(pszValue); goto cleanup; } DWORD LwLdapIsValidADEntry( HANDLE hDirectory, LDAPMessage* pMessage, PBOOLEAN pbValidADEntry ) { DWORD dwError = LW_ERROR_SUCCESS; PSTR pszValue = NULL; dwError = LwLdapGetDN( hDirectory, pMessage, &pszValue); BAIL_ON_LW_ERROR(dwError); if (LW_IS_NULL_OR_EMPTY_STR(pszValue)) { dwError = LW_ERROR_INVALID_LDAP_ATTR_VALUE; BAIL_ON_LW_ERROR(dwError); } *pbValidADEntry = TRUE; cleanup: LW_SAFE_FREE_STRING(pszValue); return dwError; error: *pbValidADEntry = FALSE; goto cleanup; } DWORD LwLdapGetUInt32( HANDLE hDirectory, LDAPMessage* pMessage, PCSTR pszFieldName, PDWORD pdwValue ) { DWORD dwError = 0; PSTR pszValue = NULL; dwError = LwLdapGetString(hDirectory, pMessage, pszFieldName, &pszValue); BAIL_ON_LW_ERROR(dwError); if (pszValue) { *pdwValue = atoi(pszValue); } else { dwError = LW_ERROR_INVALID_LDAP_ATTR_VALUE; // This error occurs very frequently (every time an unenabled user // or group is queried in default schema mode). So in order to avoid // log noise, BAIL_ON_LW_ERROR is not used here. goto error; } cleanup: LW_SAFE_FREE_STRING(pszValue); return dwError; error: *pdwValue = 0; goto cleanup; } DWORD LwLdapGetUInt64( IN HANDLE hDirectory, IN LDAPMessage* pMessage, IN PCSTR pszFieldName, OUT UINT64* pqwValue ) { DWORD dwError = 0; PSTR pszValue = NULL; PSTR pszEndPtr = NULL; dwError = LwLdapGetString(hDirectory, pMessage, pszFieldName, &pszValue); BAIL_ON_LW_ERROR(dwError); if (pszValue) { #if SIZEOF_UNSIGNED_LONG == 8 *pqwValue = strtoul(pszValue, &pszEndPtr, 10); #else *pqwValue = strtoull(pszValue, &pszEndPtr, 10); #endif if (pszEndPtr == NULL || pszEndPtr == pszValue || *pszEndPtr != '\0') { dwError = LW_ERROR_DATA_ERROR; BAIL_ON_LW_ERROR(dwError); } } else { dwError = LW_ERROR_INVALID_LDAP_ATTR_VALUE; // This error occurs very frequently (every time an unenabled user // or group is queried in default schema mode). So in order to avoid // log noise, BAIL_ON_LW_ERROR is not used here. goto error; } cleanup: LW_SAFE_FREE_STRING(pszValue); return dwError; error: *pqwValue = 0; goto cleanup; } DWORD LwLdapGetInt64( IN HANDLE hDirectory, IN LDAPMessage* pMessage, IN PCSTR pszFieldName, OUT int64_t * pqwValue ) { DWORD dwError = 0; PSTR pszValue = NULL; PSTR pszEndPtr = NULL; dwError = LwLdapGetString(hDirectory, pMessage, pszFieldName, &pszValue); BAIL_ON_LW_ERROR(dwError); if (pszValue) { #if SIZEOF_LONG == 8 *pqwValue = strtol(pszValue, &pszEndPtr, 10); #else *pqwValue = strtoll(pszValue, &pszEndPtr, 10); #endif if (pszEndPtr == NULL || pszEndPtr == pszValue || *pszEndPtr != '\0') { dwError = LW_ERROR_DATA_ERROR; BAIL_ON_LW_ERROR(dwError); } } else { dwError = LW_ERROR_INVALID_LDAP_ATTR_VALUE; // This error occurs very frequently (every time an unenabled user // or group is queried in default schema mode). So in order to avoid // log noise, BAIL_ON_LW_ERROR is not used here. goto error; } cleanup: LW_SAFE_FREE_STRING(pszValue); return dwError; error: *pqwValue = 0; goto cleanup; } // This utility function parse a ldap result in the format of // ;;distinguishedName (hexadecimal) // It also handles the case when AD object does not have a SID, // Hence, ;distinguishedName DWORD LwLdapParseExtendedDNResult( IN PCSTR pszExtDnResult, OUT PSTR* ppszSid ) { DWORD dwError = 0; PCSTR pszSidHex = NULL; PCSTR pszCurrExtDnResult = pszExtDnResult; DWORD dwSidLength = 0; PSTR pszSid = NULL; UCHAR* pucSIDByteArr = NULL; DWORD dwSIDByteCount = 0; PLW_SECURITY_IDENTIFIER pSID = NULL; LW_BAIL_ON_INVALID_STRING(pszCurrExtDnResult); if (strncasecmp(pszCurrExtDnResult, "pfnFree) { pCookie->pfnFree(pCookie->pvData); pCookie->pfnFree = NULL; } pCookie->pvData = NULL; pCookie->bSearchFinished = FALSE; } VOID LwInitCookie( OUT PLW_SEARCH_COOKIE pCookie ) { memset(pCookie, 0, sizeof(*pCookie)); } DWORD LwLdapDirectoryExtendedDNSearch( IN HANDLE hDirectory, IN PCSTR pszObjectDN, IN PCSTR pszQuery, IN PSTR* ppszAttributeList, IN int scope, OUT LDAPMessage** ppMessage ) { DWORD dwError = LW_ERROR_SUCCESS; PLW_LDAP_DIRECTORY_CONTEXT pDirectory = NULL; CHAR ExtDNCriticality = 'T'; LDAPControl *pExtDNControl = NULL; LDAPControl *ppInputControls[2] = { NULL, NULL }; LDAPMessage* pMessage = NULL; struct berval value = {0}; pDirectory = (PLW_LDAP_DIRECTORY_CONTEXT)hDirectory; // Setup the extended DN control, in order to be windows 2000 compatible, // Do not specify control value, hence, the return result will always be in hexadecimal string format. value.bv_len = 0; value.bv_val = NULL; dwError = ldap_control_create(LDAP_CONTROL_X_EXTENDED_DN, ExtDNCriticality, &value, 0, &pExtDNControl); BAIL_ON_LDAP_ERROR(dwError); ppInputControls[0] = pExtDNControl; dwError = LwLdapDirectorySearchEx( hDirectory, pszObjectDN, scope, pszQuery, ppszAttributeList, ppInputControls, 0, &pMessage); BAIL_ON_LW_ERROR(dwError); LW_ASSERT(pMessage != NULL); *ppMessage = pMessage; cleanup: ppInputControls[0] = NULL; if (pExtDNControl) { ldap_control_free(pExtDNControl); } return (dwError); error: if (pMessage) { ldap_msgfree(pMessage); } *ppMessage = NULL; goto cleanup; } /* local variables: mode: c c-basic-offset: 4 indent-tabs-mode: nil tab-width: 4 end: */