ldap/apr_ldap.c (2,522 lines of code) (raw):

/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "apr.h" #include "apu.h" #include "apu_config.h" #if APU_DSO_BUILD #define APU_DSO_LDAP_BUILD #endif #include "apr_ldap.h" #include "apu_config.h" #include "apu_internal.h" #include "apr_errno.h" #include "apr_poll.h" #include "apr_pools.h" #include "apr_portable.h" #include "apr_cstr.h" #include "apr_strings.h" #include "apr_escape.h" #include "apr_skiplist.h" #include "apr_hash.h" #define APR_WANT_MEMFUNC #include "apr_want.h" #if APR_HAS_LDAP #ifdef HAVE_SASL_SASL_H #include <sasl/sasl.h> #else #ifdef HAVE_SASL_H #include <sasl.h> #endif #endif #include <assert.h> /* Older APIs don't define this from RFC4520 */ #ifndef LDAP_MOD_INCREMENT #define LDAP_MOD_INCREMENT 3 #endif /* Older APIs use LDAP_RES_MODRDN instead of LDAP_RES_RENAME */ #ifndef LDAP_RES_RENAME #define LDAP_RES_RENAME LDAP_RES_MODRDN #endif /* Older APIs use LDAP_RES_REFERRAL instead of LDAP_RES_SEARCH_REFERENCE */ #ifndef LDAP_RES_SEARCH_REFERENCE #define LDAP_RES_SEARCH_REFERENCE LDAP_RES_REFERRAL #endif #if APR_HAS_MICROSOFT_LDAPSDK #define MSGID_T ULONG #define TO_BV_LEN(a) (ULONG)(a) #else #define MSGID_T int #define TO_BV_LEN(a) (a) #endif struct apr_ldap_t { apr_pool_t *pool; #if !APR_HAS_OPENLDAP_LDAPSDK const char *uri; #endif LDAP *ld; apr_socket_t *socket; apr_skiplist *results; apr_array_header_t *abandons; apr_array_header_t *prepares; LDAPControl **serverctrls; LDAPControl **clientctrls; apu_err_t err; apr_status_t status; }; typedef struct apr_ldap_prepare_t { apr_pool_t *pool; apr_ldap_prepare_cb cb; void *ctx; } apr_ldap_prepare_t; typedef struct apr_ldap_result_t { apr_pool_t *pool; apr_ldap_t *ld; const char *mech; const char *rmech; LDAPMessage *message; MSGID_T msgid; int msgtype; union { apr_ldap_bind_cb bind; apr_ldap_compare_cb compare; apr_ldap_search_result_cb search; apr_ldap_add_cb add; apr_ldap_modify_cb modify; apr_ldap_rename_cb rename; apr_ldap_delete_cb delete; apr_ldap_extended_cb ext; } cb; union { apr_ldap_search_entry_cb search; } entry_cb; void *ctx; apr_size_t nentries; } apr_ldap_result_t; static apr_status_t apr_ldap_status(int rc) { if (LDAP_SUCCESS == rc) { return APR_SUCCESS; } switch (rc) { case LDAP_SUCCESS: return APR_SUCCESS; case LDAP_LOCAL_ERROR: return APR_LOCAL_ERROR; case LDAP_ENCODING_ERROR: return APR_ENCODING_ERROR; case LDAP_DECODING_ERROR: return APR_DECODING_ERROR; case LDAP_TIMEOUT: return APR_TIMEOUT; case LDAP_AUTH_UNKNOWN: return APR_AUTH_UNKNOWN; case LDAP_FILTER_ERROR: return APR_FILTER_ERROR; case LDAP_USER_CANCELLED: return APR_USER_CANCELLED; case LDAP_PARAM_ERROR: return APR_PARAM_ERROR; case LDAP_NO_MEMORY: return APR_NO_MEMORY; case LDAP_CONNECT_ERROR: return APR_CONNECT_ERROR; case LDAP_NOT_SUPPORTED: return APR_NOT_SUPPORTED; case LDAP_CONTROL_NOT_FOUND: return APR_CONTROL_NOT_FOUND; case LDAP_NO_RESULTS_RETURNED: return APR_NO_RESULTS_RETURNED; case LDAP_MORE_RESULTS_TO_RETURN: return APR_MORE_RESULTS_TO_RETURN; case LDAP_CLIENT_LOOP: return APR_CLIENT_LOOP; case LDAP_REFERRAL_LIMIT_EXCEEDED: return APR_REFERRAL_LIMIT_EXCEEDED; #ifdef LDAP_X_CONNECTING case LDAP_X_CONNECTING: return APR_CONNECTING; #endif #if defined(LDAP_SERVER_DOWN) case LDAP_SERVER_DOWN: return APR_SERVER_DOWN; #endif #if defined(LDAP_UNAVAILABLE) case LDAP_UNAVAILABLE: return APR_UNAVAILABLE; #endif #ifdef LDAP_X_PROXY_AUTHZ_FAILURE case LDAP_X_PROXY_AUTHZ_FAILURE: return APR_PROXY_AUTH; #endif case LDAP_INAPPROPRIATE_AUTH: return APR_INAPPROPRIATE_AUTH; case LDAP_INVALID_CREDENTIALS: return APR_INVALID_CREDENTIALS; #ifdef LDAP_INSUFFICIENT_ACCESS /* openldap */ case LDAP_INSUFFICIENT_ACCESS: return APR_INSUFFICIENT_ACCESS; #endif #ifdef LDAP_INSUFFICIENT_RIGHTS /* microsoftsdk */ case LDAP_INSUFFICIENT_RIGHTS: return APR_INSUFFICIENT_ACCESS; #endif #ifdef LDAP_CONSTRAINT_VIOLATION case LDAP_CONSTRAINT_VIOLATION: return APR_CONSTRAINT_VIOLATION; #endif #ifdef LDAP_OBJECT_CLASS_VIOLATION case LDAP_OBJECT_CLASS_VIOLATION: return APR_OBJECT_CLASS_VIOLATION; #endif case LDAP_COMPARE_TRUE: return APR_COMPARE_TRUE; case LDAP_COMPARE_FALSE: return APR_COMPARE_FALSE; case LDAP_NO_SUCH_OBJECT: return APR_NO_SUCH_OBJECT; case LDAP_NO_SUCH_ATTRIBUTE: return APR_NO_SUCH_ATTRIBUTE; case LDAP_ALREADY_EXISTS: return APR_ALREADY_EXISTS; case LDAP_OPERATIONS_ERROR: return APR_OPERATIONS_ERROR; case LDAP_PROTOCOL_ERROR: return APR_PROTOCOL_ERROR; case LDAP_TIMELIMIT_EXCEEDED: return APR_TIMELIMIT_EXCEEDED; case LDAP_SIZELIMIT_EXCEEDED: return APR_SIZELIMIT_EXCEEDED; default: return APR_UTIL_START_STATUS + 200 + rc; } } static apr_status_t results_cleanup(void *dptr); static apr_status_t prepare_cleanup(void *dptr) { apr_ldap_prepare_t *prepare = dptr; prepare->pool = NULL; prepare->cb = NULL; prepare->ctx = NULL; return APR_SUCCESS; } static apr_status_t apr_ldap_cleanup(void *dptr) { if (dptr) { apr_ldap_t *ldap = dptr; if (ldap->results) { results_cleanup(ldap->results); ldap->results = NULL; } if (ldap->ld) { #if APR_HAS_OPENLDAP_LDAPSDK ldap->status = ldap_unbind_ext(ldap->ld, ldap->serverctrls, ldap->clientctrls); #else ldap->status = ldap_unbind(ldap->ld); #endif ldap->serverctrls = NULL; ldap->clientctrls = NULL; ldap->ld = NULL; } while (ldap->prepares->nelts) { apr_ldap_prepare_t *prepare = apr_array_pop(ldap->prepares); if (prepare->pool) { apr_pool_cleanup_run(prepare->pool, prepare, prepare_cleanup); } } } return APR_SUCCESS; } static int result_comp(void *a, void *b) { MSGID_T m1 = ((apr_ldap_result_t *)a)->msgid; MSGID_T m2 = ((apr_ldap_result_t *)b)->msgid; return (m1 == m2) ? 0 : ((m1 < m2) ? -1 : 1); } APU_DECLARE_LDAP(apr_status_t) apr_ldap_initialise(apr_pool_t *pool, apr_ldap_t **ldap, apu_err_t *result) { memset(result, 0, sizeof(*result)); *ldap = apr_pcalloc(pool, sizeof(apr_ldap_t)); if (!*ldap) { return APR_ENOMEM; } (*ldap)->pool = pool; apr_skiplist_init(&(*ldap)->results, pool); apr_skiplist_set_compare((*ldap)->results, result_comp, result_comp); (*ldap)->abandons = apr_array_make(pool, 1, sizeof(MSGID_T)); (*ldap)->prepares = apr_array_make(pool, 1, sizeof(apr_ldap_prepare_t)); apr_pool_cleanup_register(pool, (*ldap), apr_ldap_cleanup, apr_pool_cleanup_null); return APR_SUCCESS; } static apr_status_t option_set_uri(apr_ldap_t *ldap, const char *uri, apu_err_t *err) { LDAP *ld = NULL; int rc = LDAP_SUCCESS; if (!ldap || ldap->ld) { /* already initialised? say no */ return APR_EINVAL; } #if APR_HAS_OPENLDAP_LDAPSDK rc = ldap_initialize(&ld, uri); #else { apr_ldap_url_desc_t *urld; apr_status_t status; int secure; status = apr_ldap_url_parse(ldap->pool, uri, &(urld), (apr_ldap_err_t *)&(err)); if (status != APR_SUCCESS) { return status; } secure = apr_ldap_is_ldaps_url(uri); #if APR_HAS_MICROSOFT_LDAPSDK ld = ldap_sslinit((char *)urld->lud_host, urld->lud_port, secure); #else ld = ldap_init((char *)urld->lud_host, urld->lud_port); #endif ldap->uri = apr_pstrdup(ldap->pool, uri); } #endif if (rc != LDAP_SUCCESS) { err->rc = rc; err->msg = ldap_err2string(err->rc); err->reason = "LDAP: Could not initialise"; return APR_EINVAL; } else { ldap->ld = ld; } return APR_SUCCESS; } /** * Handle APR_LDAP_OPT_REBIND_PROC * * OpenLDAP and Tivoli clients have diverging implementations for rebinding. * * OpenLDAP calls us back, expecting us to do the bind ourselves. Tivoli * calls us back asking for SASL parameters so it can do the bind for us. */ #if 0 /* Should we support rebind at all? * - Tivoli and OpenLDAP have wildly divergent implementations * - Microsoft does not support rebind at all * - We want async behaviour, openldap callback forces us to be synchronous * * Caller must detect referral and chase themselves * * For now, no. */ #if APR_HAS_TIVOLI_LDAPSDK /* LDAP_rebindproc() Tivoli LDAP style * Rebind callback function. Called when chasing referrals. See API docs. * ON ENTRY: * ld Pointer to an LDAP control structure. (input only) * binddnp Pointer to an Application DName used for binding (in *or* out) * passwdp Pointer to the password associated with the DName (in *or* out) * methodp Pointer to the Auth method (output only) * freeit Flag to indicate if this is a lookup or a free request (input only) */ static int LDAP_rebindproc(LDAP *ld, char **binddnp, char **passwdp, int *methodp, int freeit) { if (!freeit) { apr_ldap_rebind_entry_t *my_conn; *methodp = LDAP_AUTH_SIMPLE; my_conn = apr_ldap_rebind_lookup(ld); if ((my_conn) && (my_conn->bindDN != NULL)) { *binddnp = strdup(my_conn->bindDN); *passwdp = strdup(my_conn->bindPW); } else { *binddnp = NULL; *passwdp = NULL; } } else { if (*binddnp) { free(*binddnp); } if (*passwdp) { free(*passwdp); } } return LDAP_SUCCESS; } static int option_set_rebind_proc(apr_pool_t *pool, LDAP *ldap, const void *invalue, apu_err_t *result) { ldap_set_rebind_proc(ld, (LDAPRebindProc)LDAP_rebindproc); return APR_SUCCESS; } #elif APR_HAS_OPENLDAP_LDAPSDK /* LDAP_rebindproc() openLDAP V3 style * ON ENTRY: * ld Pointer to an LDAP control structure. (input only) * url Unused in this routine * request Unused in this routine * msgid Unused in this routine * params Unused in this routine * * or * * ld Pointer to an LDAP control structure. (input only) * url Unused in this routine * request Unused in this routine * msgid Unused in this routine */ static int LDAP_rebindproc(LDAP *ld, LDAP_CONST char *url, ber_tag_t request, ber_int_t msgid, void *params) { apr_ldap_rebind_entry_t *my_conn; const char *bindDN = NULL; const char *bindPW = NULL; my_conn = apr_ldap_rebind_lookup(ld); if ((my_conn) && (my_conn->bindDN != NULL)) { bindDN = my_conn->bindDN; bindPW = my_conn->bindPW; } return (ldap_bind_s(ld, bindDN, bindPW, LDAP_AUTH_SIMPLE)); } static int option_set_rebind_proc(apr_pool_t *pool, LDAP *ldap, const void *invalue, apu_err_t *result) { ldap_set_rebind_proc(ld, LDAP_rebindproc, NULL); return APR_SUCCESS; } #else static int option_set_rebind_proc(apr_pool_t *pool, LDAP *ldap, const void *invalue, apu_err_t *result) { return APR_ENOTIPL; } #endif #endif /** * Handle APR_LDAP_OPT_TLS * * This function sets the type of TLS to be applied to this connection. * The options are: * APR_LDAP_NONE: no encryption * APR_LDAP_SSL: SSL encryption (ldaps://) * APR_LDAP_STARTTLS: STARTTLS encryption * APR_LDAP_STOPTLS: Stop existing TLS connecttion */ static int option_set_tls(LDAP *ldap, const void *invalue, apu_err_t *result) { #if APR_HAS_LDAP_SSL /* compiled with ssl support */ int tls = * (const int *)invalue; /* OpenLDAP SDK */ #if APR_HAS_OPENLDAP_LDAPSDK #ifdef LDAP_OPT_X_TLS if (tls == APR_LDAP_SSL) { int SSLmode = LDAP_OPT_X_TLS_HARD; result->rc = ldap_set_option(ldap, LDAP_OPT_X_TLS, &SSLmode); if (result->rc != LDAP_SUCCESS) { result->reason = "LDAP: ldap_set_option failed. " "Could not set LDAP_OPT_X_TLS to " "LDAP_OPT_X_TLS_HARD"; result->msg = ldap_err2string(result->rc); } } else if (tls == APR_LDAP_STARTTLS) { result->rc = ldap_start_tls_s(ldap, NULL, NULL); if (result->rc != LDAP_SUCCESS) { result->reason = "LDAP: ldap_start_tls_s() failed"; result->msg = ldap_err2string(result->rc); } } else if (tls == APR_LDAP_STOPTLS) { result->reason = "LDAP: STOPTLS is not supported by the " "OpenLDAP SDK"; result->rc = -1; } #else if (tls != APR_LDAP_NONE) { result->reason = "LDAP: SSL/TLS not yet supported by APR on this " "version of the OpenLDAP toolkit"; result->rc = -1; } #endif #endif /* Microsoft SDK */ #if APR_HAS_MICROSOFT_LDAPSDK if (tls == APR_LDAP_NONE) { result->rc = ldap_set_option(ldap, LDAP_OPT_SSL, LDAP_OPT_OFF); if (result->rc != LDAP_SUCCESS) { result->reason = "LDAP: an attempt to set LDAP_OPT_SSL off " "failed."; result->msg = ldap_err2string(result->rc); } } else if (tls == APR_LDAP_SSL) { result->rc = ldap_set_option(ldap, LDAP_OPT_SSL, LDAP_OPT_ON); if (result->rc != LDAP_SUCCESS) { result->reason = "LDAP: an attempt to set LDAP_OPT_SSL on " "failed."; result->msg = ldap_err2string(result->rc); } } else if (tls == APR_LDAP_STARTTLS) { result->rc = ldap_start_tls_s(ldap, NULL, NULL, NULL, NULL); if (result->rc != LDAP_SUCCESS) { result->reason = "LDAP: ldap_start_tls_s() failed"; result->msg = ldap_err2string(result->rc); } } else if (tls == APR_LDAP_STOPTLS) { result->rc = ldap_stop_tls_s(ldap); if (result->rc != LDAP_SUCCESS) { result->reason = "LDAP: ldap_stop_tls_s() failed"; result->msg = ldap_err2string(result->rc); } } #endif #if APR_HAS_OTHER_LDAPSDK if (tls != APR_LDAP_NONE) { result->reason = "LDAP: SSL/TLS is currently not supported by " "APR on this LDAP SDK"; result->rc = -1; } #endif #endif /* APR_HAS_LDAP_SSL */ return result->rc; } /** * Handle APR_LDAP_OPT_TLS_CACERTFILE * * This function sets the CA certificate for further SSL/TLS connections. * * The file provided are in different formats depending on the toolkit used: * * OpenLDAP: PEM (others supported?) * Microsoft: unknown */ static int option_set_cert(LDAP *ldap, const void *invalue, apu_err_t *result) { #if APR_HAS_LDAP_SSL #if APR_HAS_LDAPSSL_CLIENT_INIT || APR_HAS_OPENLDAP_LDAPSDK apr_array_header_t *certs = (apr_array_header_t *)invalue; struct apr_ldap_opt_tls_cert_t *ents = (struct apr_ldap_opt_tls_cert_t *)certs->elts; int i = 0; #endif /* OpenLDAP SDK */ #if APR_HAS_OPENLDAP_LDAPSDK #ifdef LDAP_OPT_X_TLS_CACERTFILE /* set one or more certificates */ for (i = 0; i < certs->nelts; i++) { /* OpenLDAP SDK supports BASE64 files. */ switch (ents[i].type) { case APR_LDAP_CA_TYPE_BASE64: result->rc = ldap_set_option(ldap, LDAP_OPT_X_TLS_CACERTFILE, (void *)ents[i].path); result->msg = ldap_err2string(result->rc); break; case APR_LDAP_CERT_TYPE_BASE64: result->rc = ldap_set_option(ldap, LDAP_OPT_X_TLS_CERTFILE, (void *)ents[i].path); result->msg = ldap_err2string(result->rc); break; case APR_LDAP_KEY_TYPE_BASE64: result->rc = ldap_set_option(ldap, LDAP_OPT_X_TLS_KEYFILE, (void *)ents[i].path); result->msg = ldap_err2string(result->rc); break; #ifdef LDAP_OPT_X_TLS_CACERTDIR case APR_LDAP_CA_TYPE_CACERTDIR_BASE64: result->rc = ldap_set_option(ldap, LDAP_OPT_X_TLS_CACERTDIR, (void *)ents[i].path); result->msg = ldap_err2string(result->rc); break; #endif default: result->rc = -1; result->reason = "LDAP: The OpenLDAP SDK only understands the " "PEM (BASE64) file type."; break; } if (result->rc != LDAP_SUCCESS) { break; } } #else result->reason = "LDAP: LDAP_OPT_X_TLS_CACERTFILE not " "defined by this OpenLDAP SDK. Certificate " "authority file not set"; result->rc = -1; #endif #endif /* Microsoft SDK */ #if APR_HAS_MICROSOFT_LDAPSDK /* Microsoft SDK use the registry certificate store - error out * here with a message explaining this. */ result->reason = "LDAP: CA certificates cannot be set using this method, " "as they are stored in the registry instead."; result->rc = -1; #endif /* SDK not recognised */ #if APR_HAS_OTHER_LDAPSDK result->reason = "LDAP: LDAP_OPT_X_TLS_CACERTFILE not " "defined by this LDAP SDK. Certificate " "authority file not set"; result->rc = -1; #endif #else /* not compiled with SSL Support */ result->reason = "LDAP: Attempt to set certificate(s) failed. " "Not built with SSL support"; result->rc = -1; #endif /* APR_HAS_LDAP_SSL */ return result->rc; } /** * APR LDAP get option function * * This function gets option values from a given LDAP session if * one was specified. * * If result_err is NULL, no error detail is returned. If *result_err is * NULL, an error detail will be created and returned. If *result_err is * not NULL, an error detail will be written to this location. */ APU_DECLARE_LDAP(apr_status_t) apr_ldap_option_get(apr_pool_t *pool, apr_ldap_t *ldap, int option, apr_ldap_opt_t *outvalue, apu_err_t *result) { int rc; memset(result, 0, sizeof(*result)); switch (option) { case APR_LDAP_OPT_API_INFO: { #if defined(LDAP_OPT_API_INFO) LDAPAPIInfo info = { 0 }; info.ldapai_info_version = LDAP_API_INFO_VERSION; rc = ldap_get_option(NULL, LDAP_OPT_API_INFO, &info); outvalue->info.api_version = info.ldapai_api_version; outvalue->info.protocol_version = info.ldapai_protocol_version; outvalue->info.extensions = (const char **)info.ldapai_extensions; outvalue->info.vendor_name = info.ldapai_vendor_name; outvalue->info.vendor_version = info.ldapai_vendor_version; break; #else result->reason = "LDAP: API info not yet supported by APR on this " "LDAP SDK"; result->rc = LDAP_UNWILLING_TO_PERFORM; return APR_ENOTIMPL; #endif } case APR_LDAP_OPT_API_FEATURE_INFO: { #if defined(LDAP_OPT_API_FEATURE_INFO) LDAPAPIFeatureInfo ldfi = { 0 }; ldfi.ldapaif_info_version = LDAP_FEATURE_INFO_VERSION; ldfi.ldapaif_name = (char *)outvalue->ldfi.name; rc = ldap_get_option(NULL, LDAP_OPT_API_FEATURE_INFO, &ldfi); outvalue->ldfi.version = ldfi.ldapaif_version; break; #else result->reason = "LDAP: API feature info not yet supported by APR on this " "LDAP SDK"; result->rc = LDAP_UNWILLING_TO_PERFORM; return APR_ENOTIMPL; #endif } case APR_LDAP_OPT_PROTOCOL_VERSION: { rc = ldap_get_option(ldap ? ldap->ld : NULL, LDAP_OPT_PROTOCOL_VERSION, &outvalue->pv); break; } case APR_LDAP_OPT_HANDLE: { outvalue->handle = ldap ? ldap->ld : NULL; return APR_SUCCESS; } case APR_LDAP_OPT_DESC: { #if defined(LDAP_OPT_DESC) apr_status_t status = APR_SUCCESS; if (!ldap->socket) { apr_os_sock_t sock; rc = ldap_get_option(ldap ? ldap->ld : NULL, LDAP_OPT_DESC, &sock); if (rc == LDAP_SUCCESS) { status = apr_os_sock_put(&ldap->socket, &sock, ldap->pool); } else { status = apr_ldap_status(rc); } } outvalue->socket = ldap->socket; return status; #else result->reason = "LDAP: LDAP_OPT_DESC not yet supported by APR on this " "LDAP SDK"; result->rc = LDAP_UNWILLING_TO_PERFORM; return APR_ENOTIMPL; #endif } case APR_LDAP_OPT_URI: { #if APR_HAS_OPENLDAP_LDAPSDK rc = ldap_get_option(ldap ? ldap->ld : NULL, option, &outvalue->opt); break; #else outvalue->uri = ldap->uri; return APR_SUCCESS; #endif } case APR_LDAP_OPT_DEBUG_LEVEL: { #if defined(LDAP_OPT_DEBUG_LEVEL) rc = ldap_get_option(ldap ? ldap->ld : NULL, LDAP_OPT_DEBUG_LEVEL, &outvalue->debug); break; #else result->reason = "LDAP: Debug level not yet supported by APR on this " "LDAP SDK"; result->rc = LDAP_UNWILLING_TO_PERFORM; return APR_ENOTIMPL; #endif } case APR_LDAP_OPT_DEREF: { rc = ldap_get_option(ldap ? ldap->ld : NULL, LDAP_OPT_DEREF, &outvalue->deref); break; } case APR_LDAP_OPT_REFERRALS: { int refs; rc = ldap_get_option(ldap ? ldap->ld : NULL, LDAP_OPT_REFERRALS, &refs); if (rc == LDAP_SUCCESS) { outvalue->refs = refs ? APR_LDAP_OPT_ON : APR_LDAP_OPT_OFF; } break; } case APR_LDAP_OPT_REFHOPLIMIT: { #if defined(LDAP_OPT_REFERRAL_HOP_LIMIT) /* Microsoft SDK defines LDAP_OPT_REFERRAL_HOP_LIMIT */ rc = ldap_get_option(ldap ? ldap->ld : NULL, LDAP_OPT_REFERRAL_HOP_LIMIT, &outvalue->refhoplimit); #elif defined(LDAP_OPT_REFHOPLIMIT) /* Setting this option is supported on TIVOLI_SDK. */ rc = ldap_get_option(ldap ? ldap->ld : NULL, LDAP_OPT_REFHOPLIMIT, &outvalue->refhoplimit); #else result->reason = "LDAP: Referral hop limit not yet supported by APR on this " "LDAP SDK"; result->rc = LDAP_UNWILLING_TO_PERFORM; return APR_ENOTIMPL; #endif break; } case APR_LDAP_OPT_RESULT_CODE: { #ifdef LDAP_OPT_RESULT_CODE rc = ldap_get_option(ldap ? ldap->ld : NULL, LDAP_OPT_RESULT_CODE, &outvalue->result); #else #ifdef LDAP_OPT_ERROR_NUMBER rc = ldap_get_option(ldap ? ldap->ld : NULL, LDAP_OPT_ERROR_NUMBER, &outvalue->result); #endif #endif break; } case APR_LDAP_OPT_TLS_CERT: { result->reason = "LDAP: Could not get an option APR_LDAP_OPT_TLS_CERT: not implemented"; return APR_ENOTIMPL; } case APR_LDAP_OPT_TLS: { result->reason = "LDAP: Could not get an option APR_LDAP_OPT_TLS: not implemented"; return APR_ENOTIMPL; } case APR_LDAP_OPT_VERIFY_CERT: { result->reason = "LDAP: Could not get an option APR_LDAP_OPT_VERIFY_CERT: not implemented"; return APR_ENOTIMPL; } case APR_LDAP_OPT_NETWORK_TIMEOUT: { #if !defined(LDAP_OPT_NETWORK_TIMEOUT) && defined(LDAP_OPT_CONNECT_TIMEOUT) #define LDAP_OPT_NETWORK_TIMEOUT LDAP_OPT_CONNECT_TIMEOUT #endif #ifdef LDAP_OPT_NETWORK_TIMEOUT struct timeval networkTimeout = {0}; rc = ldap_get_option(ldap ? ldap->ld : NULL, LDAP_OPT_NETWORK_TIMEOUT, &networkTimeout); outvalue->timeout = apr_time_make(networkTimeout.tv_sec, networkTimeout.tv_usec); break; #else result->reason = "LDAP: Could not get an option APR_LDAP_OPT_NETWORK_TIMEOUT: not implemented"; return APR_ENOTIMPL; #endif } case APR_LDAP_OPT_TIMEOUT: { #ifdef LDAP_OPT_TIMEOUT /* * LDAP_OPT_TIMEOUT is not portable, but it influences all synchronous ldap * function calls and not just ldap_search_ext_s(), which accepts a timeout * parameter. * XXX: It would be possible to simulate LDAP_OPT_TIMEOUT by replacing all * XXX: synchronous ldap function calls with asynchronous calls and using * XXX: ldap_result() with a timeout. */ struct timeval timeout = {0}; rc = ldap_get_option(ldap ? ldap->ld : NULL, LDAP_OPT_TIMEOUT, &timeout); outvalue->timeout = apr_time_make(timeout.tv_sec, timeout.tv_usec); break; #else result->reason = "LDAP: Could not get an option APR_LDAP_OPT_TIMEOUT: not implemented"; return APR_ENOTIMPL; #endif } default: rc = ldap_get_option(ldap ? ldap->ld : NULL, option, &outvalue->opt); } if (rc != LDAP_SUCCESS) { result->rc = rc; result->msg = ldap_err2string(result->rc); result->reason = "LDAP: Could not get an option"; return APR_EINVAL; } return APR_SUCCESS; } /** * APR LDAP set option function * * This function sets option values to a given LDAP session if * one was specified. * * Where an option is not supported by an LDAP toolkit, this function * will try and apply legacy functions to achieve the same effect, * depending on the platform. */ APU_DECLARE_LDAP(apr_status_t) apr_ldap_option_set(apr_pool_t *pool, apr_ldap_t *ldap, int option, const apr_ldap_opt_t *invalue, apu_err_t *result) { int rc; memset(result, 0, sizeof(*result)); switch (option) { case APR_LDAP_OPT_DESC: /* * TODO: we want the option to use our own socket. This option is normally * read only, however we could allow this to somehow call ldap_init_fd() * for us. * * This means we can asynchronously perform DNS lookups and SSL handshakes * without expecting the LDAP library to cooperate with that. */ /* windows allows the socket to be set here */ rc = LDAP_UNWILLING_TO_PERFORM; goto end; case APR_LDAP_OPT_URI: rc = option_set_uri(ldap, invalue->uri, result); goto end; default: break; } if (ldap && !ldap->ld) { result->reason = "LDAP: URI or descriptor needs to be set first"; return APR_EINVAL; } switch (option) { case APR_LDAP_OPT_API_INFO: rc = LDAP_UNWILLING_TO_PERFORM; break; case APR_LDAP_OPT_API_FEATURE_INFO: rc = LDAP_UNWILLING_TO_PERFORM; break; case APR_LDAP_OPT_PROTOCOL_VERSION: rc = ldap_set_option(ldap ? ldap->ld : NULL, LDAP_OPT_PROTOCOL_VERSION, (void*)&invalue->pv); break; case APR_LDAP_OPT_HANDLE: rc = LDAP_UNWILLING_TO_PERFORM; break; case APR_LDAP_OPT_DEBUG_LEVEL: #if defined(LDAP_OPT_DEBUG_LEVEL) rc = ldap_set_option(ldap ? ldap->ld : NULL, LDAP_OPT_DEBUG_LEVEL, &invalue->debug); break; #else result->reason = "LDAP: Debug level not yet supported by APR on this " "LDAP SDK"; result->rc = LDAP_UNWILLING_TO_PERFORM; return APR_ENOTIMPL; #endif case APR_LDAP_OPT_DEREF: rc = ldap_set_option(ldap ? ldap->ld : NULL, LDAP_OPT_DEREF, (void*)&invalue->deref); break; case APR_LDAP_OPT_REFERRALS: { void *refs = invalue->refs ? (void *)LDAP_OPT_ON : (void *)LDAP_OPT_OFF; /* Setting this option is supported on at least TIVOLI_SDK and OpenLDAP. */ rc = ldap_set_option(ldap ? ldap->ld : NULL, LDAP_OPT_REFERRALS, refs); break; } case APR_LDAP_OPT_REFHOPLIMIT: #if defined(LDAP_OPT_REFERRAL_HOP_LIMIT) /* Microsoft SDK defines LDAP_OPT_REFERRAL_HOP_LIMIT */ rc = ldap_set_option(ldap ? ldap->ld : NULL, LDAP_OPT_REFERRAL_HOP_LIMIT, &invalue->refhoplimit); #elif defined(LDAP_OPT_REFHOPLIMIT) /* Setting this option is supported on TIVOLI_SDK. */ rc = ldap_set_option(ldap ? ldap->ld : NULL, LDAP_OPT_REFHOPLIMIT, (void*)&invalue->refhoplimit); #else /* If the LDAP_OPT_REFHOPLIMIT symbol is missing, assume that the * particular LDAP library has a reasonable default. So far certain * versions of the OpenLDAP SDK miss this symbol (but default to 5), * and the Microsoft SDK misses the symbol (the default is not known). */ result->reason = "LDAP: Referral hop limit not yet supported by APR on this " "LDAP SDK"; result->rc = LDAP_UNWILLING_TO_PERFORM; return APR_ENOTIMPL; #endif break; case APR_LDAP_OPT_RESULT_CODE: rc = LDAP_UNWILLING_TO_PERFORM; break; #if 0 case APR_LDAP_OPT_REBIND_PROC: rc = option_set_rebind_proc(ldap->pool, ldap ? ldap->ld : NULL, invalue->rebind, result); break; #endif case APR_LDAP_OPT_TLS_CERT: rc = option_set_cert(ldap ? ldap->ld : NULL, invalue->certs, result); break; case APR_LDAP_OPT_TLS: rc = option_set_tls(ldap ? ldap->ld : NULL, &invalue->tls, result); break; case APR_LDAP_OPT_VERIFY_CERT: #ifdef LDAP_OPT_X_TLS /* This is not a per-connection setting so just pass NULL for the Ldap connection handle */ if (invalue->verify) { int i = LDAP_OPT_X_TLS_DEMAND; result->rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &i); } else { int i = LDAP_OPT_X_TLS_NEVER; result->rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &i); } #else result->reason = "LDAP: SSL/TLS not yet supported by APR on this " "version of the OpenLDAP toolkit"; result->rc = LDAP_UNWILLING_TO_PERFORM; return APR_ENOTIMPL; #endif /* handle the error case */ if (result->rc != LDAP_SUCCESS) { result->msg = ldap_err2string(result->rc); result->reason = "LDAP: Could not set verify mode"; return APR_EINVAL; } return APR_SUCCESS; case APR_LDAP_OPT_NETWORK_TIMEOUT: { #if !defined(LDAP_OPT_NETWORK_TIMEOUT) && defined(LDAP_OPT_CONNECT_TIMEOUT) #define LDAP_OPT_NETWORK_TIMEOUT LDAP_OPT_CONNECT_TIMEOUT #endif #ifdef LDAP_OPT_NETWORK_TIMEOUT struct timeval networkTimeout = {0}; networkTimeout.tv_sec = apr_time_sec(invalue->timeout); networkTimeout.tv_usec = apr_time_usec(invalue->timeout); rc = ldap_set_option(ldap ? ldap->ld : NULL, LDAP_OPT_NETWORK_TIMEOUT, &networkTimeout); break; #else result->reason = "LDAP: Could not set an option APR_LDAP_OPT_NETWORK_TIMEOUT: not implemented"; return APR_ENOTIMPL; #endif } case APR_LDAP_OPT_TIMEOUT: { #ifdef LDAP_OPT_TIMEOUT /* * LDAP_OPT_TIMEOUT is not portable, but it influences all synchronous ldap * function calls and not just ldap_search_ext_s(), which accepts a timeout * parameter. * XXX: It would be possible to simulate LDAP_OPT_TIMEOUT by replacing all * XXX: synchronous ldap function calls with asynchronous calls and using * XXX: ldap_result() with a timeout. */ struct timeval timeout = {0}; timeout.tv_sec = apr_time_sec(invalue->timeout); timeout.tv_usec = apr_time_usec(invalue->timeout); rc = ldap_set_option(ldap ? ldap->ld : NULL, LDAP_OPT_TIMEOUT, &timeout); break; #else result->reason = "LDAP: Could not set an option APR_LDAP_OPT_TIMEOUT: not implemented"; return APR_ENOTIMPL; #endif } default: rc = ldap_set_option(ldap ? ldap->ld : NULL, option, invalue->opt); } end: #if defined (LDAP_OPT_SUCCESS) if (rc != LDAP_OPT_SUCCESS) { #else if (rc != LDAP_SUCCESS) { #endif result->rc = rc; if (!result->msg) { result->msg = ldap_err2string(result->rc); } if (result->reason) { result->reason = "LDAP: Could not set an option"; } return APR_EINVAL; } return APR_SUCCESS; } APU_DECLARE_LDAP(apr_status_t) apr_ldap_connect(apr_pool_t *pool, apr_ldap_t *ldap, apr_interval_time_t timeout, apu_err_t *err) { #if APR_HAS_LDAP_CONNECT LDAP *ld = ldap->ld; #if APR_HAS_MICROSOFT_LDAPSDK LDAP_TIMEVAL tv, *tvptr; if (timeout < 0) { tvptr = NULL; } else { tv.tv_sec = (long) apr_time_sec(timeout); tv.tv_usec = (long) apr_time_usec(timeout); tvptr = &tv; } err->rc = ldap_connect(ld, tvptr); #else #ifdef LDAP_OPT_NETWORK_TIMEOUT { struct timeval tv, *tvptr; if (timeout < 0) { tvptr = NULL; } else { tv.tv_sec = (long) apr_time_sec(timeout); tv.tv_usec = (long) apr_time_usec(timeout); tvptr = &tv; } err->rc = ldap_set_option(ldap->ld, LDAP_OPT_NETWORK_TIMEOUT, tvptr); if (err->rc != LDAP_SUCCESS) { err->msg = ldap_err2string(err->rc); err->reason = "LDAP: Could not set network timeout"; return APR_EINVAL; } } #endif err->rc = ldap_connect(ld); #endif if (err->rc != LDAP_SUCCESS) { err->msg = ldap_err2string(err->rc); err->reason = "LDAP: ldap_connect() failed"; return apr_ldap_status(err->rc); } else { memset(err, 0, sizeof(*err)); } return APR_SUCCESS; #else return APR_ENOTIMPL; #endif } APU_DECLARE_LDAP(apr_status_t) apr_ldap_prepare(apr_pool_t *pool, apr_ldap_t *ldap, apr_ldap_prepare_cb prepare_cb, void *prepare_ctx) { apr_ldap_prepare_t *prepare = apr_array_push(ldap->prepares); if (!prepare) { return APR_ENOMEM; } prepare->pool = pool; prepare->cb = prepare_cb; prepare->ctx = prepare_ctx; apr_pool_cleanup_register(pool, prepare, prepare_cleanup, apr_pool_cleanup_null); return APR_SUCCESS; } static apr_status_t ldap_control_cleanup(void *dptr) { if (dptr) { LDAPControl *ctl = dptr; ldap_control_free(ctl); } return APR_SUCCESS; } #if APR_HAS_OPENLDAP_LDAPSDK static apr_status_t ldap_berval_cleanup(void *dptr) { if (dptr) { struct berval *val = dptr; ber_bvfree(val); } return APR_SUCCESS; } static apr_status_t ldap_memfree_cleanup(void *dptr) { if (dptr) { ldap_memfree(dptr); } return APR_SUCCESS; } #endif static apr_status_t apr_ldap_control_parse(apr_pool_t *pool, apr_ldap_t *ldap, LDAPControl **ctls, apr_hash_t **controls, apu_err_t *err) { apr_hash_t *cs; int i = 0; if (!ctls || !ctls[0]) { *controls = NULL; return APR_SUCCESS; } cs = apr_hash_make(pool); if (!cs) { return APR_ENOMEM; } for (i = 0; ctls[i]; i++) { LDAPControl *ctl = (LDAPControl *)ctls[i]; apr_ldap_control_t *c = apr_pcalloc(pool, sizeof(apr_ldap_control_t)); c->critical = ctl->ldctl_iscritical ? 1 : 0; /* what controls do we recognise? */ #if APR_HAS_OPENLDAP_LDAPSDK if (!strcmp(ctl->ldctl_oid, LDAP_CONTROL_SORTRESPONSE)) { ber_int_t result; char *attr; err->rc = ldap_parse_sortresponse_control(ldap->ld, ctl, &result, &attr); if (err->rc != LDAP_SUCCESS) { err->msg = ldap_err2string(err->rc); err->reason = "LDAP: ldap_parse_sortresponse_control failed"; return apr_ldap_status(err->rc); } c->type = APR_LDAP_CONTROL_SORT_RESPONSE; if (attr) { apr_pool_cleanup_register(pool, attr, ldap_memfree_cleanup, apr_pool_cleanup_null); } c->c.sortrs.attribute = (const char *)attr; c->c.sortrs.result = apr_ldap_status(result); apr_hash_set(cs, ctl->ldctl_oid, APR_HASH_KEY_STRING, c); continue; } if (!strcmp(ctl->ldctl_oid, LDAP_CONTROL_PAGEDRESULTS)) { ber_int_t count; struct berval cookie; err->rc = ldap_parse_pageresponse_control(ldap->ld, ctl, &count, &cookie); if (err->rc != LDAP_SUCCESS) { err->msg = ldap_err2string(err->rc); err->reason = "LDAP: ldap_parse_pageresponse_control failed"; return apr_ldap_status(err->rc); } c->type = APR_LDAP_CONTROL_PAGE_RESPONSE; if (cookie.bv_val) { apr_buffer_mem_set(&c->c.pagers.cookie, cookie.bv_val, cookie.bv_len); apr_pool_cleanup_register(pool, cookie.bv_val, ldap_memfree_cleanup, apr_pool_cleanup_null); } else { apr_buffer_mem_set(&c->c.pagers.cookie, NULL, 0); } c->c.pagers.count = (apr_size_t)count; apr_hash_set(cs, ctl->ldctl_oid, APR_HASH_KEY_STRING, c); continue; } if (!strcmp(ctl->ldctl_oid, LDAP_CONTROL_VLVRESPONSE)) { ber_int_t target_posp; ber_int_t list_countp; int result; struct berval *context = NULL; err->rc = ldap_parse_vlvresponse_control(ldap->ld, ctl, &target_posp, &list_countp, &context, &result); if (err->rc != LDAP_SUCCESS) { err->msg = ldap_err2string(err->rc); err->reason = "LDAP: ldap_parse_vlvresponse_control failed"; return apr_ldap_status(err->rc); } c->type = APR_LDAP_CONTROL_VLV_RESPONSE; c->c.vlvrs.offset = (apr_size_t)target_posp; c->c.vlvrs.count = (apr_size_t)list_countp; if (context) { apr_buffer_mem_set(&c->c.vlvrs.context, context->bv_val, context->bv_len); apr_pool_cleanup_register(pool, context, ldap_berval_cleanup, apr_pool_cleanup_null); } else { apr_buffer_mem_set(&c->c.vlvrs.context, NULL, 0); } c->c.vlvrs.result = apr_ldap_status(result); apr_hash_set(cs, ctl->ldctl_oid, APR_HASH_KEY_STRING, c); continue; } #endif /* not recognised, return raw value */ c->type = APR_LDAP_CONTROL_OID; c->oid.oid = (const char *)ctl->ldctl_oid; apr_buffer_mem_set(&c->oid.val, ctl->ldctl_value.bv_val, ctl->ldctl_value.bv_len); apr_hash_set(cs, ctl->ldctl_oid, APR_HASH_KEY_STRING, c); } *controls = cs; return APR_SUCCESS; } static apr_status_t apr_ldap_control_create(apr_pool_t *pool, apr_ldap_t *ldap, LDAPControl ***ctrls, apr_array_header_t *controls, apu_err_t *err) { LDAPControl **cs; int i, count; if (!controls || !(count = controls->nelts)) { *ctrls = NULL; return APR_SUCCESS; } cs = apr_pcalloc(pool, (count + 1) * sizeof(LDAPControl *)); for (i = 0; i < count; ++i) { apr_ldap_control_t *control = &APR_ARRAY_IDX(controls, i, apr_ldap_control_t); /* what controls do we recognise? */ switch (control->type) { /* page control */ case APR_LDAP_CONTROL_PAGE_REQUEST: { #if APR_HAS_OPENLDAP_LDAPSDK LDAPControl *c; ber_int_t pagesize = control->c.pagerq.size; struct berval cookie; cookie.bv_val = apr_buffer_str(&control->c.pagerq.cookie); cookie.bv_len = TO_BV_LEN(apr_buffer_len(&control->c.pagerq.cookie)); err->rc = ldap_create_page_control(ldap->ld, pagesize, &cookie, control->critical ? 1 : 0, &c); if (err->rc != LDAP_SUCCESS) { err->msg = ldap_err2string(err->rc); err->reason = "LDAP: ldap_create_page_control failed"; return apr_ldap_status(err->rc); } apr_pool_cleanup_register(pool, c, ldap_control_cleanup, apr_pool_cleanup_null); cs[i] = c; break; #else err->reason = "LDAP: page control not supported"; return APR_ENOTIMPL; #endif } /* sort control */ case APR_LDAP_CONTROL_SORT_REQUEST: { #if APR_HAS_OPENLDAP_LDAPSDK LDAPControl *c; LDAPSortKey **sks; apr_array_header_t *keys = control->c.sortrq.keys; int j; if (!keys || !keys->nelts) { err->reason = "LDAP: no sort control keys specified"; return APR_EINVAL; } sks = apr_pcalloc(pool, (keys->nelts + 1) * sizeof(LDAPSortKey *)); for (j = 0; j < keys->nelts; ++j) { apr_ldap_control_sortkey_t *sortkey = &APR_ARRAY_IDX(keys, j, apr_ldap_control_sortkey_t); LDAPSortKey *sk = apr_pcalloc(pool, sizeof(LDAPSortKey)); sk->attributeType = (char *)sortkey->attribute; sk->orderingRule = (char *)sortkey->order; sk->reverseOrder = sortkey->direction == APR_LDAP_CONTROL_SORT_REVERSE ? 1 : 0; sks[j] = sk; } err->rc = ldap_create_sort_control(ldap->ld, sks, control->critical ? 1 : 0, &c); if (err->rc != LDAP_SUCCESS) { err->msg = ldap_err2string(err->rc); err->reason = "LDAP: ldap_create_sort_control failed"; return apr_ldap_status(err->rc); } apr_pool_cleanup_register(pool, c, ldap_control_cleanup, apr_pool_cleanup_null); cs[i] = c; break; #else err->reason = "LDAP: sort control not supported"; return APR_ENOTIMPL; #endif } /* vlv control */ case APR_LDAP_CONTROL_VLV_REQUEST: { #if APR_HAS_OPENLDAP_LDAPSDK LDAPControl *c; LDAPVLVInfo vlvInfo; struct berval attrvalue; struct berval context; vlvInfo.ldvlv_before_count = control->c.vlvrq.before; vlvInfo.ldvlv_after_count = control->c.vlvrq.after; vlvInfo.ldvlv_offset = control->c.vlvrq.offset; vlvInfo.ldvlv_count = control->c.vlvrq.count; if (apr_buffer_is_null(&control->c.vlvrq.attrvalue)) { vlvInfo.ldvlv_attrvalue = NULL; } else { attrvalue.bv_val = (char *)apr_buffer_mem(&control->c.vlvrq.attrvalue, NULL); attrvalue.bv_len = TO_BV_LEN(apr_buffer_len(&control->c.vlvrq.attrvalue)); vlvInfo.ldvlv_attrvalue = &attrvalue; } if (apr_buffer_is_null(&control->c.vlvrq.context)) { vlvInfo.ldvlv_context = NULL; } else { context.bv_val = (char *)apr_buffer_mem(&control->c.vlvrq.context, NULL); context.bv_len = TO_BV_LEN(apr_buffer_len(&control->c.vlvrq.context)); vlvInfo.ldvlv_context = &context; } vlvInfo.ldvlv_extradata = NULL; vlvInfo.ldvlv_version = 1; err->rc = ldap_create_vlv_control(ldap->ld, &vlvInfo, &c); if (err->rc != LDAP_SUCCESS) { err->msg = ldap_err2string(err->rc); err->reason = "LDAP: ldap_create_vlv_control failed"; return apr_ldap_status(err->rc); } apr_pool_cleanup_register(pool, c, ldap_control_cleanup, apr_pool_cleanup_null); cs[i] = c; break; #else err->reason = "LDAP: vlv control not supported"; return APR_ENOTIMPL; #endif } /* not recognised, return raw value */ case APR_LDAP_CONTROL_OID: { apr_size_t size; LDAPControl *c = apr_pcalloc(pool, count * sizeof(LDAPControl)); if (!control->oid.oid) { err->msg = NULL; err->reason = "LDAP: control oid missing"; return APR_EINVAL; } c->ldctl_oid = (char *)control->oid.oid; c->ldctl_value.bv_val = apr_buffer_mem(&control->oid.val, &size); c->ldctl_value.bv_len = TO_BV_LEN(size); c->ldctl_iscritical = control->critical ? 1 : 0; cs[i] = c; break; } default: err->reason = "LDAP: control not recognised"; return APR_EINVAL; } } *ctrls = (LDAPControl **)cs; return APR_SUCCESS; } /* * The results cleanup dance. * * We need to clean up each result when the pool belong to the request is * cleared. This involves removing the result from the skiplist, and freeing * linked data like LDAP messages. * * We need to clean up all the results when the pool belonging to the ldap * connection is cleared. This involves walking the results list, removing * the cleanups from the request pools, then freeing linked data like the * LDAP messages. */ static apr_status_t apr_ldap_result_clear(apr_ldap_result_t *res) { res->rmech = NULL; if (res->message) { ldap_msgfree(res->message); res->message = NULL; } return APR_SUCCESS; } static void result_result_cleanup(void *dptr) { apr_ldap_result_clear(dptr); } static apr_status_t result_cleanup(void *dptr) { apr_ldap_result_t *res = dptr; apr_skiplist_remove(res->ld->results, dptr, result_result_cleanup); return APR_SUCCESS; } static void results_result_cleanup(void *dptr) { apr_ldap_result_t *res = dptr; apr_pool_cleanup_kill(res->pool, res, result_cleanup); apr_ldap_result_clear(res); } static apr_status_t results_cleanup(void *dptr) { apr_skiplist *results = dptr; apr_skiplist_remove_all(results, results_result_cleanup); return APR_SUCCESS; } static void apr_ldap_result_add(apr_pool_t *pool, apr_ldap_t *ldap, apr_ldap_result_t *res, MSGID_T msgid) { res->pool = pool; res->ld = ldap; res->msgid = msgid; apr_pool_cleanup_register(pool, res, result_cleanup, apr_pool_cleanup_null); apr_skiplist_add(ldap->results, res); } static void apr_ldap_result_remove(apr_ldap_t *ldap, apr_ldap_result_t *res) { apr_pool_cleanup_run(res->pool, res, result_cleanup); } APU_DECLARE_LDAP(apr_status_t) apr_ldap_process(apr_pool_t *pool, apr_ldap_t *ldap, apr_interval_time_t timeout, apu_err_t *err) { apr_skiplistnode *iter = NULL; apr_ldap_result_t *res; apr_status_t status = APR_SUCCESS; MSGID_T msgid = 0; /* do we have a prepare callback outstanding? */ while (ldap->prepares->nelts) { apr_ldap_prepare_t *prepare = apr_array_pop(ldap->prepares); if (prepare->pool) { status = prepare->cb(ldap, status, prepare->ctx, err); apr_pool_cleanup_run(prepare->pool, prepare, prepare_cleanup); return status; } } /* any abandoned requests? handle them first */ if (ldap->abandons->nelts) { MSGID_T *msgid = apr_array_pop(ldap->abandons); #if APR_HAS_OPENLDAP_LDAPSDK err->rc = ldap_abandon_ext(ldap->ld, *msgid, NULL, NULL); #else err->rc = ldap_abandon(ldap->ld, *msgid); #endif if (err->rc == LDAP_SUCCESS) { /* anyone needs a read? */ if (apr_skiplist_size(ldap->results)) { return APR_WANT_READ; } /* otherwise we're done */ return APR_SUCCESS; } else { err->reason = "LDAP: ldap_abandon_ext() failed"; err->msg = ldap_err2string(err->rc); return apr_ldap_status(err->rc); } } /* iterate through skiplist, see if any response has outstanding work */ for (iter = apr_skiplist_getlist(ldap->results); iter; apr_skiplist_next(ldap->results, &iter)) { res = apr_skiplist_element(iter); if (!res->message) { continue; } switch(res->msgtype) { case LDAP_RES_BIND: { #if APR_HAS_OPENLDAP_LDAPSDK && APR_HAS_LDAP_SASL_INTERACTIVE_BIND /* handle binding */ LDAPControl *sctrls[] = { 0 }; LDAPControl *cctrls[] = { 0 }; unsigned int flags = LDAP_SASL_QUIET; err->rc = ldap_sasl_interactive_bind(ldap->ld, NULL, res->mech, sctrls, cctrls, flags, NULL, NULL, res->message, &res->rmech, &msgid); err->msg = ldap_err2string(err->rc); err->reason = "LDAP bind: ldap_sasl_interactive_bind()"; if (err->rc == LDAP_SASL_BIND_IN_PROGRESS) { apr_skiplist_remove(ldap->results, res, NULL); res->msgid = msgid; ldap_msgfree(res->message); res->message = NULL; apr_skiplist_add(ldap->results, res); return APR_WANT_READ; } else { /* we got a response, send the news, good or bad */ if (res->cb.bind) { status = res->cb.bind(ldap, apr_ldap_status(err->rc), NULL, NULL, res->ctx, err); } else { status = apr_ldap_status(err->rc); } apr_ldap_result_remove(ldap, res); } break; #else /* * for platforms that do not support ldap_sasl_interactive_bind(), alternative * implementations using ldap_sasl_bind() go here. */ err->reason = "LDAP: SASL bind not yet supported by APR on this " "LDAP SDK"; err->rc = LDAP_UNWILLING_TO_PERFORM; return APR_ENOTIMPL; #endif } case LDAP_RES_COMPARE: { /* handle comparing */ char *matcheddn = NULL; char *errmsg = NULL; LDAPControl **serverctrls = NULL; int rc; err->rc = ldap_parse_result(ldap->ld, res->message, &rc, &matcheddn, &errmsg, NULL, &serverctrls, 0); err->rc = rc != LDAP_SUCCESS ? rc : err->rc; err->msg = ldap_err2string(err->rc); err->reason = "LDAP compare: ldap_parse_result()"; if (res->cb.compare) { status = res->cb.compare(ldap, apr_ldap_status(err->rc), matcheddn, (apr_ldap_control_t **)serverctrls, res->ctx, err); } else { status = apr_ldap_status(err->rc); } apr_ldap_result_remove(ldap, res); if (matcheddn) { ldap_memfree(matcheddn); } if (errmsg) { ldap_memfree(errmsg); } if (serverctrls) { ldap_controls_free(serverctrls); } break; } case LDAP_RES_SEARCH_RESULT: { /* handle search result */ char *matcheddn = NULL; char *errmsg = NULL; LDAPControl **serverctrls = NULL; apr_hash_t *controls = NULL; int rc; err->rc = ldap_parse_result(ldap->ld, res->message, &rc, &matcheddn, &errmsg, NULL, &serverctrls, 0); status = apr_ldap_control_parse(res->pool, ldap, serverctrls, &controls, err); if (APR_SUCCESS == status) { err->rc = rc != LDAP_SUCCESS ? rc : err->rc; err->msg = ldap_err2string(err->rc); err->reason = "LDAP search: ldap_parse_result()"; status = apr_ldap_status(err->rc); } if (res->cb.search) { status = res->cb.search(ldap, status, 0, matcheddn, controls, res->ctx, err); } apr_ldap_result_remove(ldap, res); if (matcheddn) { ldap_memfree(matcheddn); } if (errmsg) { ldap_memfree(errmsg); } if (serverctrls) { ldap_controls_free(serverctrls); } break; } case LDAP_RES_ADD: { /* handle adding */ char *matcheddn = NULL; char *errmsg = NULL; LDAPControl **serverctrls = NULL; int rc; err->rc = ldap_parse_result(ldap->ld, res->message, &rc, &matcheddn, &errmsg, NULL, &serverctrls, 0); err->rc = rc != LDAP_SUCCESS ? rc : err->rc; err->msg = ldap_err2string(err->rc); err->reason = "LDAP add: ldap_parse_result()"; if (res->cb.add) { status = res->cb.add(ldap, apr_ldap_status(err->rc), matcheddn, (apr_ldap_control_t **)serverctrls, res->ctx, err); } else { status = apr_ldap_status(err->rc); } apr_ldap_result_remove(ldap, res); if (matcheddn) { ldap_memfree(matcheddn); } if (errmsg) { ldap_memfree(errmsg); } if (serverctrls) { ldap_controls_free(serverctrls); } break; } case LDAP_RES_MODIFY: { /* handle modification */ char *matcheddn = NULL; char *errmsg = NULL; LDAPControl **serverctrls = NULL; int rc; err->rc = ldap_parse_result(ldap->ld, res->message, &rc, &matcheddn, &errmsg, NULL, &serverctrls, 0); err->rc = rc != LDAP_SUCCESS ? rc : err->rc; err->msg = ldap_err2string(err->rc); err->reason = "LDAP modify: ldap_parse_result()"; if (res->cb.modify) { status = res->cb.modify(ldap, apr_ldap_status(err->rc), matcheddn, (apr_ldap_control_t **)serverctrls, res->ctx, err); } else { status = apr_ldap_status(err->rc); } apr_ldap_result_remove(ldap, res); if (matcheddn) { ldap_memfree(matcheddn); } if (errmsg) { ldap_memfree(errmsg); } if (serverctrls) { ldap_controls_free(serverctrls); } break; } case LDAP_RES_RENAME: { /* handle rename */ char *matcheddn = NULL; char *errmsg = NULL; LDAPControl **serverctrls = NULL; int rc; err->rc = ldap_parse_result(ldap->ld, res->message, &rc, &matcheddn, &errmsg, NULL, &serverctrls, 0); err->rc = rc != LDAP_SUCCESS ? rc : err->rc; err->msg = ldap_err2string(err->rc); err->reason = "LDAP rename: ldap_parse_result()"; if (res->cb.rename) { status = res->cb.rename(ldap, apr_ldap_status(err->rc), matcheddn, (apr_ldap_control_t **)serverctrls, res->ctx, err); } else { status = apr_ldap_status(err->rc); } apr_ldap_result_remove(ldap, res); if (matcheddn) { ldap_memfree(matcheddn); } if (errmsg) { ldap_memfree(errmsg); } if (serverctrls) { ldap_controls_free(serverctrls); } break; } case LDAP_RES_DELETE: { /* handle delete */ char *matcheddn = NULL; char *errmsg = NULL; LDAPControl **serverctrls = NULL; int rc; err->rc = ldap_parse_result(ldap->ld, res->message, &rc, &matcheddn, &errmsg, NULL, &serverctrls, 0); err->rc = rc != LDAP_SUCCESS ? rc : err->rc; err->msg = ldap_err2string(err->rc); err->reason = "LDAP delete: ldap_parse_result()"; if (res->cb.delete) { status = res->cb.delete(ldap, apr_ldap_status(err->rc), matcheddn, (apr_ldap_control_t **)serverctrls, res->ctx, err); } else { status = apr_ldap_status(err->rc); } apr_ldap_result_remove(ldap, res); if (matcheddn) { ldap_memfree(matcheddn); } if (errmsg) { ldap_memfree(errmsg); } if (serverctrls) { ldap_controls_free(serverctrls); } break; } case LDAP_RES_EXTENDED: { /* handle extended operation */ char *roid = NULL; struct berval *rd = NULL; apr_buffer_t rdata; err->rc = ldap_parse_extended_result(ldap->ld, res->message, &roid, &rd, 0); err->msg = ldap_err2string(err->rc); err->reason = "LDAP extended operation: ldap_parse_result()"; if (rd) { apr_buffer_mem_set(&rdata, rd->bv_val, rd->bv_len); } if (res->cb.ext) { status = res->cb.ext(ldap, apr_ldap_status(err->rc), roid, &rdata, res->ctx, err); } else { status = apr_ldap_status(err->rc); } apr_ldap_result_remove(ldap, res); break; } default: break; } } /* we're done with this task, is there more work outstanding? */ if (APR_SUCCESS == status && apr_skiplist_size(ldap->results)) { return APR_WANT_READ; } /* otherwise we're done */ return status; } APU_DECLARE_LDAP(apr_status_t) apr_ldap_result(apr_pool_t *pool, apr_ldap_t *ldap, apr_interval_time_t timeout, apu_err_t *err) { apr_ldap_result_t *res; LDAPMessage *msg; apr_ldap_result_t find; LDAP *ld = ldap->ld; apr_status_t status = APR_SUCCESS; #if APR_HAS_MICROSOFT_LDAPSDK LDAP_TIMEVAL tv, *tvptr; #else struct timeval tv, *tvptr; #endif if (timeout < 0) { tvptr = NULL; } else { tv.tv_sec = (long) apr_time_sec(timeout); tv.tv_usec = (long) apr_time_usec(timeout); tvptr = &tv; } err->rc = ldap_result(ld, LDAP_RES_ANY, LDAP_MSG_ONE, tvptr, &msg); if (err->rc == -1) { err->reason = "LDAP: ldap_result() retrieval failed"; // FIXME - trigger the skiplist now, we know this connection is toast #ifdef LDAP_OPT_ERROR_NUMBER ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &err->rc); #endif #ifdef LDAP_OPT_RESULT_CODE ldap_get_option(ld, LDAP_OPT_RESULT_CODE, &err->rc); #endif err->msg = ldap_err2string(err->rc); return apr_ldap_status(err->rc); } else if (err->rc == 0) { err->reason = "LDAP: ldap_result() timed out"; err->rc = LDAP_TIMEOUT; err->msg = ldap_err2string(err->rc); return APR_ETIMEDOUT; } #if APR_HAS_MICROSOFT_LDAPSDK find.msgid = msg->lm_msgid; #else find.msgid = ldap_msgid(msg); #endif res = apr_skiplist_find(ldap->results, &find, NULL); if (res) { /* we expect this message, fire off the relevant callback */ switch(err->rc) { case LDAP_RES_BIND: { /* * A subtle bug exists in the implementation of the ldap_sasl_bind() and * ldap_sasl_bind_interactive() functions: "are we done yet?" and "make * the next write in the conversation" are combined into the same * function. * * What this means for the async version of the API is that the "are * we done yet?" question can only be asked when we are next writable, * just in case the answer to the "are we done yet?" question is no * and a write subsequently occurs. * * We generally get away with this because the server doesn't typically * decide when the connection is closed, so we're almost always writable * so we don't see a problem. */ if (res->message) { /* two unprocessed bind messages would be weird, but don't leak */ ldap_msgfree(res->message); } res->message = msg; return APR_WANT_WRITE; } case LDAP_RES_SEARCH_ENTRY: { /* * Search entries are sent back immediately as we receive them. The * expectation is our caller will wait until the search result message * before trying to send any further LDAP requests. */ LDAPMessage *entry; const char *dn; char *attr; BerElement *ber; apr_ldap_search_entry_t e = { 0 }; entry = ldap_first_entry(ldap->ld, msg); dn = ldap_get_dn(ldap->ld, entry); for (attr = ldap_first_attribute(ldap->ld, entry, &ber); attr != NULL; attr = ldap_next_attribute(ldap->ld, entry, ber)) { e.nattrs++; ldap_memfree(attr); } if (ber) { ber_free(ber,0); } for (attr = ldap_first_attribute(ldap->ld, entry, &ber); attr != NULL; attr = ldap_next_attribute(ldap->ld, entry, ber)) { struct berval **vals = ldap_get_values_len(ldap->ld, entry, attr); e.attr = attr; if (vals) { int binary = 0; char *sc = attr; /* support for RFC4522 binary encoding option */ while ((sc = strchr(sc, ';'))) { if (!apr_cstr_casecmpn(sc, ";binary", 7) && (sc[7] == 0 || sc[7] == ';')) { binary = 1; break; } } e.nvals = ldap_count_values_len(vals); for (e.vidx = 0; e.vidx < e.nvals; e.vidx++) { char *str = NULL; if (binary) { apr_buffer_mem_set(&e.val, vals[e.vidx]->bv_val, vals[e.vidx]->bv_len); } else { str = malloc(vals[e.vidx]->bv_len + 1); str[vals[e.vidx]->bv_len] = 0; memcpy(str, vals[e.vidx]->bv_val, vals[e.vidx]->bv_len); apr_buffer_str_set(&e.val, str, vals[e.vidx]->bv_len); } if (res->entry_cb.search) { status = res->entry_cb.search(ldap, dn, res->nentries, &e, res->ctx, err); } else { status = apr_ldap_status(err->rc); } if (str) { free(str); } } } else { if (res->entry_cb.search) { status = res->entry_cb.search(ldap, dn, res->nentries, &e, res->ctx, err); } else { status = apr_ldap_status(err->rc); } } ldap_value_free_len(vals); ldap_memfree(attr); if (APR_SUCCESS != status) { break; } e.aidx++; } if (ber) { ber_free(ber,0); } res->nentries++; if (res->entry_cb.search) { status = res->entry_cb.search(ldap, dn, res->nentries, NULL, res->ctx, err); } else { status = apr_ldap_status(err->rc); } ldap_memfree((void *)dn); break; } case LDAP_RES_SEARCH_REFERENCE: { break; } case LDAP_RES_SEARCH_RESULT: case LDAP_RES_COMPARE: case LDAP_RES_ADD: case LDAP_RES_MODIFY: case LDAP_RES_RENAME: case LDAP_RES_DELETE: case LDAP_RES_EXTENDED: { /* * Set the result aside for callbacks to be fired when our LDAP socket * is next writable. This means that we can safely write the next LDAP * request in the callback without messing about. */ if (res->message) { /* two unprocessed messages would be weird, but don't leak */ ldap_msgfree(res->message); } res->message = msg; return APR_WANT_WRITE; } default: /* we don't (yet) recognise this message */ break; } } else { /* we are no longer interested in this message - a pool was cleaned up */ MSGID_T *msgid = apr_array_push(ldap->abandons); *msgid = find.msgid; ldap_msgfree(msg); return APR_WANT_WRITE; } ldap_msgfree(msg); /* we're done with this task, is there more work outstanding? */ if (APR_SUCCESS == status && apr_skiplist_size(ldap->results)) { return APR_WANT_READ; } /* otherwise we're done */ return status; } typedef struct apr_ldap_connection_t { apr_pool_t *pool; apr_ldap_t *ldap; apr_pollcb_t *poll; apr_pollfd_t socket_read; apr_pollfd_t socket_write; apu_err_t *err; } apr_ldap_connection_t; static apr_status_t apr_ldap_connection_cb(void *baton, apr_pollfd_t *descriptor) { apr_ldap_connection_t *conn = (apr_ldap_connection_t *) baton; apr_status_t status = APR_SUCCESS; /* remove our event */ apr_pollcb_remove(conn->poll, descriptor); /* are we ready to write? */ if (descriptor->rtnevents & APR_POLLOUT) { /* send oustanding request */ status = apr_ldap_process(conn->pool, conn->ldap, apr_time_from_sec(0), conn->err); } /* are we ready to read? */ else if (descriptor->rtnevents & APR_POLLIN) { /* read outstanding responses */ status = apr_ldap_result(conn->pool, conn->ldap, -1, conn->err); } return status; } APU_DECLARE_LDAP(apr_status_t) apr_ldap_poll(apr_pool_t *pool, apr_ldap_t *ldap, apr_pollcb_t *poll, apr_interval_time_t timeout, apu_err_t *err) { apr_ldap_connection_t conn; apr_ldap_opt_t opt; apr_status_t status; status = apr_ldap_option_get(pool, ldap, APR_LDAP_OPT_DESC, &opt, err); if (APR_SUCCESS != status) { return status; } /* set up read descriptor */ conn.socket_read.desc_type = APR_POLL_SOCKET; conn.socket_read.reqevents = APR_POLLIN; conn.socket_read.desc.s = opt.socket; conn.socket_read.client_data = opt.socket; /* set up write descriptor */ conn.socket_write.desc_type = APR_POLL_SOCKET; conn.socket_write.reqevents = APR_POLLOUT; conn.socket_write.desc.s = opt.socket; conn.socket_write.client_data = opt.socket; conn.ldap = ldap; conn.poll = poll; conn.err = err; status = APR_WANT_WRITE; do { if (APR_WANT_READ == status) { /* wait for socket to be readable, then process another result */ status = apr_pollcb_add(conn.poll, &conn.socket_read); if (APR_SUCCESS != status) { break; } } else if (APR_WANT_WRITE == status) { /* wait for socket to be writeable, then process result */ status = apr_pollcb_add(conn.poll, &conn.socket_write); if (APR_SUCCESS != status) { break; } } else { break; } status = apr_pollcb_poll(conn.poll, timeout, apr_ldap_connection_cb, &conn); } while (1); return status; } typedef struct apr_ldap_bind_ctx_t { apr_ldap_t *ld; apr_ldap_bind_interact_cb *interact; void *ctx; apr_status_t status; } apr_ldap_bind_ctx_t; #if APR_HAS_OPENLDAP_LDAPSDK && APR_HAS_LDAP_SASL_INTERACTIVE_BIND #if !defined(HAVE_SASL_H) && !defined(HAVE_SASL_SASL_H) #error OpenLDAP was built with SASL support, but the SASL headers are not installed as required. #endif static int bind_sasl_interact(LDAP *ld, unsigned flags, void *ctx, void *in) { apr_ldap_bind_ctx_t *payload = ctx; sasl_interact_t *sasl_interact = in; if (!ld) { return LDAP_PARAM_ERROR; } while( sasl_interact->id != SASL_CB_LIST_END ) { apr_ldap_bind_interact_t interaction; apr_status_t status; if (!payload->interact) { return LDAP_PARAM_ERROR; } memset(&interaction, 0, sizeof(apr_ldap_bind_interact_t)); interaction.id = sasl_interact->id; interaction.challenge = sasl_interact->challenge; interaction.prompt = sasl_interact->prompt; interaction.defresult = sasl_interact->defresult; status = payload->interact(payload->ld, flags, &interaction, payload->ctx); if (status != APR_SUCCESS) { payload->status = status; return LDAP_PARAM_ERROR; } sasl_interact->result = apr_buffer_mem(&interaction.result, NULL); sasl_interact->len = apr_buffer_len(&interaction.result); sasl_interact++; } return LDAP_SUCCESS; } #endif /** * APR LDAP bind function * * This function binds a previously initialised LDAP connection * to the directory. * * Binds are attempted as SASL interactive, falling back to a * standard SASL bind, falling back to a simple bind, depending * on the capabilities of the platform. * * Binds are attempted asynchronously. If APR_EAGAIN is returned, * this function must be called again. */ APU_DECLARE_LDAP(apr_status_t) apr_ldap_bind(apr_pool_t *pool, apr_ldap_t *ldap, const char *mech, apr_ldap_bind_interact_cb *interact_cb, void *interact_ctx, apr_interval_time_t timeout, apr_ldap_bind_cb bind_cb, void *bind_ctx, apu_err_t *err) { apr_ldap_result_t *res; LDAPControl *sctrls[] = { 0 }; LDAPControl *cctrls[] = { 0 }; apr_ldap_bind_ctx_t payload; payload.ld = ldap; payload.interact = interact_cb; payload.ctx = interact_ctx; payload.status = APR_SUCCESS; MSGID_T msgid = 0; #ifdef LDAP_OPT_NETWORK_TIMEOUT { struct timeval tv, *tvptr; if (timeout < 0) { tvptr = NULL; } else { tv.tv_sec = (long) apr_time_sec(timeout); tv.tv_usec = (long) apr_time_usec(timeout); tvptr = &tv; } err->rc = ldap_set_option(ldap->ld, LDAP_OPT_NETWORK_TIMEOUT, tvptr); if (err->rc != LDAP_SUCCESS) { err->msg = ldap_err2string(err->rc); err->reason = "LDAP: Could not set network timeout"; return APR_EINVAL; } } #endif if (!mech) { /* No mechanism means we want a simple bind */ const char *dn; struct berval cred; apr_ldap_bind_interact_t interaction = { 0 }; memset(err, 0, sizeof(*err)); interaction.id = APR_LDAP_INTERACT_DN; interaction.prompt = "Distinguished Name"; payload.status = interact_cb(ldap, 0, &interaction, interact_ctx); if (payload.status != APR_SUCCESS) { return payload.status; } /* avoid unnecessary duplication */ if (!apr_buffer_is_null(&interaction.result)) { if (apr_buffer_is_str(&interaction.result)) { dn = apr_buffer_str(&interaction.result); } else { dn = apr_buffer_pstrdup(pool, &interaction.result); } } else { dn = ""; } interaction.id = APR_LDAP_INTERACT_PASS; interaction.prompt = "Password"; payload.status = interact_cb(ldap, 0, &interaction, interact_ctx); if (payload.status != APR_SUCCESS) { return payload.status; } if (!apr_buffer_is_null(&interaction.result)) { cred.bv_val = (char *)apr_buffer_mem(&interaction.result, NULL); cred.bv_len = TO_BV_LEN(apr_buffer_len(&interaction.result)); } else { cred.bv_val = ""; cred.bv_len = 0; } /* * ldap_simple_bind() is deprecated, so use ldap_sasl_bind() instead. In this * mode mechanism is null, the username is passed in the dn, and the * password is passed as a buffer to cred. */ #if APR_HAS_MICROSOFT_LDAPSDK err->rc = ldap_sasl_bind(ldap->ld, (char *)dn, NULL, &cred, NULL, NULL, &msgid); #else err->rc = ldap_sasl_bind(ldap->ld, dn, LDAP_SASL_SIMPLE, &cred, NULL, NULL, &msgid); #endif if (err->rc != LDAP_SUCCESS) { err->msg = ldap_err2string(err->rc); err->reason = "LDAP: ldap_sasl_bind(SIMPLE) failed"; return apr_ldap_status(err->rc); } else { memset(err, 0, sizeof(*err)); } res = apr_pcalloc(pool, sizeof(apr_ldap_result_t)); if (!res) { return APR_ENOMEM; } res->msgtype = LDAP_RES_BIND; res->cb.bind = bind_cb; res->ctx = bind_ctx; apr_ldap_result_add(pool, ldap, res, msgid); return APR_WANT_READ; } else { #if APR_HAS_OPENLDAP_LDAPSDK && APR_HAS_LDAP_SASL_INTERACTIVE_BIND const char *rmech; unsigned int flags = LDAP_SASL_QUIET; /* No distinguished name is a SASL bind */ memset(err, 0, sizeof(*err)); err->rc = ldap_sasl_interactive_bind(ldap->ld, NULL, mech, sctrls, cctrls, flags, bind_sasl_interact, &payload, NULL, &rmech, &msgid); if (err->rc == LDAP_SASL_BIND_IN_PROGRESS) { res = apr_pcalloc(pool, sizeof(apr_ldap_result_t)); if (!res) { return APR_ENOMEM; } res->msgid = msgid; res->msgtype = LDAP_RES_BIND; res->rmech = rmech; res->cb.bind = bind_cb; res->ctx = bind_ctx; apr_ldap_result_add(pool, ldap, res, msgid); } if (APR_SUCCESS != payload.status) { return payload.status; } else if (err->rc == LDAP_SUCCESS) { return APR_SUCCESS; } else if (err->rc == LDAP_SASL_BIND_IN_PROGRESS) { return APR_WANT_READ; } else { err->msg = ldap_err2string(err->rc); err->reason = "LDAP: ldap_sasl_interactive_bind() failed"; return apr_ldap_status(err->rc); } #else /* * for platforms that do not support ldap_sasl_interactive_bind(), alternative * implementations using ldap_sasl_bind() go here. */ err->reason = "LDAP: SASL bind not yet supported by APR on this " "LDAP SDK"; err->rc = LDAP_UNWILLING_TO_PERFORM; return APR_ENOTIMPL; #endif } } APU_DECLARE_LDAP(apr_status_t) apr_ldap_compare(apr_pool_t *pool, apr_ldap_t *ldap, const char *dn, const char *attr, const apr_buffer_t *val, apr_array_header_t *serverctrls, apr_array_header_t *clientctrls, apr_interval_time_t timeout, apr_ldap_compare_cb compare_cb, void *compare_ctx, apu_err_t *err) { LDAPControl **sctrls = NULL; LDAPControl **cctrls = NULL; apr_ldap_result_t *res; struct berval bval; apr_size_t size; MSGID_T msgid = 0; apr_status_t status; status = apr_ldap_control_create(pool, ldap, &sctrls, serverctrls, err); if (APR_SUCCESS != status) { return status; } status = apr_ldap_control_create(pool, ldap, &cctrls, clientctrls, err); if (APR_SUCCESS != status) { return status; } bval.bv_val = apr_buffer_mem(val, &size); bval.bv_len = TO_BV_LEN(size); #ifdef LDAP_OPT_NETWORK_TIMEOUT { struct timeval tv, *tvptr; if (timeout < 0) { tvptr = NULL; } else { tv.tv_sec = (long) apr_time_sec(timeout); tv.tv_usec = (long) apr_time_usec(timeout); tvptr = &tv; } err->rc = ldap_set_option(ldap->ld, LDAP_OPT_NETWORK_TIMEOUT, tvptr); if (err->rc != LDAP_SUCCESS) { err->msg = ldap_err2string(err->rc); err->reason = "LDAP: Could not set network timeout"; return APR_EINVAL; } } #endif #if APR_HAS_MICROSOFT_LDAPSDK err->rc = ldap_compare_ext(ldap->ld, (char *)dn, (char *)attr, NULL, &bval, sctrls, cctrls, &msgid); #else err->rc = ldap_compare_ext(ldap->ld, dn, attr, &bval, sctrls, cctrls, &msgid); #endif if (err->rc != LDAP_SUCCESS) { err->msg = ldap_err2string(err->rc); err->reason = "LDAP: ldap_compare failed"; return apr_ldap_status(err->rc); } else { memset(err, 0, sizeof(*err)); } res = apr_pcalloc(pool, sizeof(apr_ldap_result_t)); if (!res) { return APR_ENOMEM; } res->msgtype = LDAP_RES_COMPARE; res->cb.compare = compare_cb; res->ctx = compare_ctx; apr_ldap_result_add(pool, ldap, res, msgid); return APR_WANT_READ; } APU_DECLARE_LDAP(apr_status_t) apr_ldap_search(apr_pool_t *pool, apr_ldap_t *ldap, const char *dn, apr_ldap_search_scope_e scope, const char *filter, const char **attrs, apr_ldap_switch_e attrsonly, apr_array_header_t *serverctrls, apr_array_header_t *clientctrls, apr_interval_time_t timeout, apr_ssize_t sizelimit, apr_ldap_search_result_cb search_result_cb, apr_ldap_search_entry_cb search_entry_cb, void *search_ctx, apu_err_t *err) { LDAPControl **sctrls = NULL; LDAPControl **cctrls = NULL; apr_ldap_result_t *res; #if APR_HAS_MICROSOFT_LDAPSDK ULONG timelimit; #else struct timeval tv, *tvptr; #endif MSGID_T msgid = 0; apr_status_t status; status = apr_ldap_control_create(pool, ldap, &sctrls, serverctrls, err); if (APR_SUCCESS != status) { return status; } status = apr_ldap_control_create(pool, ldap, &cctrls, clientctrls, err); if (APR_SUCCESS != status) { return status; } #if APR_HAS_MICROSOFT_LDAPSDK timelimit = (ULONG)apr_time_sec(timeout); err->rc = ldap_search_ext(ldap->ld, (char *)dn, scope, (char *)filter, (char **)attrs, attrsonly, sctrls, cctrls, timelimit, (ULONG)sizelimit, &msgid); #else if (timeout < 0) { tvptr = NULL; } else { tv.tv_sec = (long) apr_time_sec(timeout); tv.tv_usec = (long) apr_time_usec(timeout); tvptr = &tv; } err->rc = ldap_search_ext(ldap->ld, (char *)dn, scope, (char *)filter, (char **)attrs, attrsonly, sctrls, cctrls, tvptr, sizelimit, &msgid); #endif if (err->rc != LDAP_SUCCESS) { err->msg = ldap_err2string(err->rc); err->reason = "LDAP: ldap_search failed"; return apr_ldap_status(err->rc); } else { memset(err, 0, sizeof(*err)); } res = apr_pcalloc(pool, sizeof(apr_ldap_result_t)); if (!res) { return APR_ENOMEM; } res->msgtype = LDAP_RES_SEARCH_RESULT; res->cb.search = search_result_cb; res->entry_cb.search = search_entry_cb; res->ctx = search_ctx; apr_ldap_result_add(pool, ldap, res, msgid); return APR_WANT_READ; } APU_DECLARE_LDAP(apr_status_t) apr_ldap_add(apr_pool_t *pool, apr_ldap_t *ldap, const char *dn, apr_array_header_t *adds, apr_array_header_t *serverctrls, apr_array_header_t *clientctrls, apr_interval_time_t timeout, apr_ldap_add_cb add_cb, void *ctx, apu_err_t *err) { LDAPControl **sctrls = NULL; LDAPControl **cctrls = NULL; apr_pool_t *tpool; apr_ldap_result_t *res; LDAPMod **mps, *ms; MSGID_T msgid = 0; int i, j; apr_status_t status; #ifdef LDAP_OPT_NETWORK_TIMEOUT { struct timeval tv, *tvptr; if (timeout < 0) { tvptr = NULL; } else { tv.tv_sec = (long) apr_time_sec(timeout); tv.tv_usec = (long) apr_time_usec(timeout); tvptr = &tv; } err->rc = ldap_set_option(ldap->ld, LDAP_OPT_NETWORK_TIMEOUT, tvptr); if (err->rc != LDAP_SUCCESS) { err->msg = ldap_err2string(err->rc); err->reason = "LDAP: Could not set network timeout"; return APR_EINVAL; } } #endif /* sanity check - any binary inconsistency? */ for (i = 0; i < adds->nelts; ++i) { apr_ldap_pair_t *pair = &APR_ARRAY_IDX(adds, i, apr_ldap_pair_t); int is_str = 0; /* no adding attributes with no values */ if (!pair->vals->nelts) { return APR_EINVAL; } /* no adding mixed strings / binary */ for (j = 0; j < pair->vals->nelts; ++j) { apr_buffer_t *buf = &APR_ARRAY_IDX(pair->vals, j, apr_buffer_t); if (0 == j) { is_str = apr_buffer_is_str(buf); } else if (apr_buffer_is_str(buf) != is_str) { return APR_EINVAL; } } } /* all sane, let's translate into the LDAP world */ apr_pool_create(&tpool, pool); status = apr_ldap_control_create(tpool, ldap, &sctrls, serverctrls, err); if (APR_SUCCESS != status) { return status; } status = apr_ldap_control_create(tpool, ldap, &cctrls, clientctrls, err); if (APR_SUCCESS != status) { return status; } mps = apr_pcalloc(tpool, (adds->nelts + 1) * sizeof(LDAPMod *)); ms = apr_pcalloc(tpool, (adds->nelts) * sizeof(LDAPMod)); /* walk our attributes */ for (i = 0; i < adds->nelts; ++i) { apr_ldap_pair_t *pair = &APR_ARRAY_IDX(adds, i, apr_ldap_pair_t); ms->mod_op = 0; /* entry add, no operation on attributes */ ms->mod_type = (char *)pair->attr; for (j = 0; j < pair->vals->nelts; ++j) { apr_buffer_t *buf = &APR_ARRAY_IDX(pair->vals, j, apr_buffer_t); if (apr_buffer_is_str(buf)) { if (0 == j) { ms->mod_vals.modv_strvals = apr_pcalloc(tpool, (pair->vals->nelts + 1) * sizeof(void *)); } ms->mod_vals.modv_strvals[j] = apr_buffer_str(buf); } else { if (0 == j) { ms->mod_op |= LDAP_MOD_BVALUES; ms->mod_vals.modv_bvals = apr_pcalloc(tpool, (pair->vals->nelts + 1) * sizeof(void *)); } ms->mod_vals.modv_bvals[j]->bv_val = apr_buffer_mem(buf, NULL); ms->mod_vals.modv_bvals[j]->bv_len = TO_BV_LEN(apr_buffer_len(buf)); } } mps[i] = ms++; } #if APR_HAS_MICROSOFT_LDAPSDK err->rc = ldap_add_ext(ldap->ld, (char *)dn, mps, sctrls, cctrls, &msgid); #else err->rc = ldap_add_ext(ldap->ld, dn, mps, sctrls, cctrls, &msgid); #endif apr_pool_destroy(tpool); if (err->rc != LDAP_SUCCESS) { err->msg = ldap_err2string(err->rc); err->reason = "LDAP: ldap_add failed"; return apr_ldap_status(err->rc); } else { memset(err, 0, sizeof(*err)); } res = apr_pcalloc(pool, sizeof(apr_ldap_result_t)); if (!res) { return APR_ENOMEM; } res->msgtype = LDAP_RES_ADD; res->cb.add = add_cb; res->ctx = ctx; apr_ldap_result_add(pool, ldap, res, msgid); return APR_WANT_READ; } APU_DECLARE_LDAP(apr_status_t) apr_ldap_modify(apr_pool_t *pool, apr_ldap_t *ldap, const char *dn, apr_array_header_t *mods, apr_array_header_t *serverctrls, apr_array_header_t *clientctrls, apr_interval_time_t timeout, apr_ldap_modify_cb modify_cb, void *ctx, apu_err_t *err) { LDAPControl **sctrls = NULL; LDAPControl **cctrls = NULL; apr_pool_t *tpool; apr_ldap_result_t *res; LDAPMod **mps, *ms; MSGID_T msgid = 0; int i, j; apr_status_t status; #ifdef LDAP_OPT_NETWORK_TIMEOUT { struct timeval tv, *tvptr; if (timeout < 0) { tvptr = NULL; } else { tv.tv_sec = (long) apr_time_sec(timeout); tv.tv_usec = (long) apr_time_usec(timeout); tvptr = &tv; } err->rc = ldap_set_option(ldap->ld, LDAP_OPT_NETWORK_TIMEOUT, tvptr); if (err->rc != LDAP_SUCCESS) { err->msg = ldap_err2string(err->rc); err->reason = "LDAP: Could not set network timeout"; return APR_EINVAL; } } #endif /* sanity check - any binary inconsistency? */ for (i = 0; i < mods->nelts; ++i) { apr_ldap_modify_t *mod = &APR_ARRAY_IDX(mods, i, apr_ldap_modify_t); int is_str = 0; /* non recognised operations */ switch (mod->op) { case APR_LDAP_MOD_ADD: case APR_LDAP_MOD_DELETE: case APR_LDAP_MOD_REPLACE: case APR_LDAP_MOD_INCREMENT: break; default: return APR_EINVAL; } /* no attribute */ if (!mod->pair.attr) { return APR_EINVAL; } /* no adding attributes with no values */ if (mod->op == APR_LDAP_MOD_ADD && !mod->pair.vals->nelts) { return APR_EINVAL; } /* no adding mixed strings / binary */ for (j = 0; j < mod->pair.vals->nelts; ++j) { apr_buffer_t *buf = &APR_ARRAY_IDX(mod->pair.vals, j, apr_buffer_t); if (0 == j) { is_str = apr_buffer_is_str(buf); } else if (apr_buffer_is_str(buf) != is_str) { return APR_EINVAL; } } } /* all sane, let's translate into the LDAP world */ apr_pool_create(&tpool, pool); status = apr_ldap_control_create(tpool, ldap, &sctrls, serverctrls, err); if (APR_SUCCESS != status) { return status; } status = apr_ldap_control_create(tpool, ldap, &cctrls, clientctrls, err); if (APR_SUCCESS != status) { return status; } mps = apr_pcalloc(tpool, (mods->nelts + 1) * sizeof(LDAPMod *)); ms = apr_pcalloc(tpool, (mods->nelts) * sizeof(LDAPMod)); /* walk our attributes */ for (i = 0; i < mods->nelts; ++i) { apr_ldap_modify_t *mod = &APR_ARRAY_IDX(mods, i, apr_ldap_modify_t); switch (mod->op) { case APR_LDAP_MOD_ADD: ms->mod_op = LDAP_MOD_ADD; break; case APR_LDAP_MOD_DELETE: ms->mod_op = LDAP_MOD_DELETE; break; case APR_LDAP_MOD_REPLACE: ms->mod_op = LDAP_MOD_REPLACE; break; case APR_LDAP_MOD_INCREMENT: ms->mod_op = LDAP_MOD_INCREMENT; break; } ms->mod_type = (char *)mod->pair.attr; for (j = 0; j < mod->pair.vals->nelts; ++j) { apr_buffer_t *buf = &APR_ARRAY_IDX(mod->pair.vals, j, apr_buffer_t); if (apr_buffer_is_str(buf)) { if (0 == j) { ms->mod_vals.modv_strvals = apr_pcalloc(tpool, (mod->pair.vals->nelts + 1) * sizeof(void *)); } ms->mod_vals.modv_strvals[j] = apr_buffer_str(buf); } else { if (0 == j) { ms->mod_op |= LDAP_MOD_BVALUES; ms->mod_vals.modv_bvals = apr_pcalloc(tpool, (mod->pair.vals->nelts + 1) * sizeof(void *)); } ms->mod_vals.modv_bvals[j]->bv_val = apr_buffer_mem(buf, NULL); ms->mod_vals.modv_bvals[j]->bv_len = TO_BV_LEN(apr_buffer_len(buf)); } } mps[i] = ms++; } #if APR_HAS_MICROSOFT_LDAPSDK err->rc = ldap_modify_ext(ldap->ld, (char *)dn, mps, sctrls, cctrls, &msgid); #else err->rc = ldap_modify_ext(ldap->ld, dn, mps, sctrls, cctrls, &msgid); #endif apr_pool_destroy(tpool); if (err->rc != LDAP_SUCCESS) { err->msg = ldap_err2string(err->rc); err->reason = "LDAP: ldap_modify failed"; return apr_ldap_status(err->rc); } else { memset(err, 0, sizeof(*err)); } res = apr_pcalloc(pool, sizeof(apr_ldap_result_t)); if (!res) { return APR_ENOMEM; } res->msgtype = LDAP_RES_MODIFY; res->cb.modify = modify_cb; res->ctx = ctx; apr_ldap_result_add(pool, ldap, res, msgid); return APR_WANT_READ; } APU_DECLARE_LDAP(apr_status_t) apr_ldap_rename(apr_pool_t *pool, apr_ldap_t *ldap, const char *dn, const char *newrdn, const char *newparent, apr_ldap_rename_e flags, apr_array_header_t *serverctrls, apr_array_header_t *clientctrls, apr_interval_time_t timeout, apr_ldap_rename_cb rename_cb, void *ctx, apu_err_t *err) { LDAPControl **sctrls = NULL; LDAPControl **cctrls = NULL; apr_ldap_result_t *res; MSGID_T msgid = 0; apr_status_t status; status = apr_ldap_control_create(pool, ldap, &sctrls, serverctrls, err); if (APR_SUCCESS != status) { return status; } status = apr_ldap_control_create(pool, ldap, &cctrls, clientctrls, err); if (APR_SUCCESS != status) { return status; } #ifdef LDAP_OPT_NETWORK_TIMEOUT { struct timeval tv, *tvptr; if (timeout < 0) { tvptr = NULL; } else { tv.tv_sec = (long) apr_time_sec(timeout); tv.tv_usec = (long) apr_time_usec(timeout); tvptr = &tv; } err->rc = ldap_set_option(ldap->ld, LDAP_OPT_NETWORK_TIMEOUT, tvptr); if (err->rc != LDAP_SUCCESS) { err->msg = ldap_err2string(err->rc); err->reason = "LDAP: Could not set network timeout"; return APR_EINVAL; } } #endif #if APR_HAS_MICROSOFT_LDAPSDK err->rc = ldap_rename_ext(ldap->ld, (char *)dn, (char *)newrdn, (char *)newparent, flags, sctrls, cctrls, &msgid); #else err->rc = ldap_rename(ldap->ld, dn, newrdn, newparent, flags, sctrls, cctrls, &msgid); #endif if (err->rc != LDAP_SUCCESS) { err->msg = ldap_err2string(err->rc); err->reason = "LDAP: ldap_rename failed"; return apr_ldap_status(err->rc); } else { memset(err, 0, sizeof(*err)); } res = apr_pcalloc(pool, sizeof(apr_ldap_result_t)); if (!res) { return APR_ENOMEM; } res->msgtype = LDAP_RES_RENAME; res->cb.rename = rename_cb; res->ctx = ctx; apr_ldap_result_add(pool, ldap, res, msgid); return APR_WANT_READ; } APU_DECLARE_LDAP(apr_status_t) apr_ldap_delete(apr_pool_t *pool, apr_ldap_t *ldap, const char *dn, apr_array_header_t *serverctrls, apr_array_header_t *clientctrls, apr_interval_time_t timeout, apr_ldap_delete_cb delete_cb, void *ctx, apu_err_t *err) { LDAPControl **sctrls = NULL; LDAPControl **cctrls = NULL; apr_ldap_result_t *res; MSGID_T msgid = 0; apr_status_t status; status = apr_ldap_control_create(pool, ldap, &sctrls, serverctrls, err); if (APR_SUCCESS != status) { return status; } status = apr_ldap_control_create(pool, ldap, &cctrls, clientctrls, err); if (APR_SUCCESS != status) { return status; } #ifdef LDAP_OPT_NETWORK_TIMEOUT { struct timeval tv, *tvptr; if (timeout < 0) { tvptr = NULL; } else { tv.tv_sec = (long) apr_time_sec(timeout); tv.tv_usec = (long) apr_time_usec(timeout); tvptr = &tv; } err->rc = ldap_set_option(ldap->ld, LDAP_OPT_NETWORK_TIMEOUT, tvptr); if (err->rc != LDAP_SUCCESS) { err->msg = ldap_err2string(err->rc); err->reason = "LDAP: Could not set network timeout"; return APR_EINVAL; } } #endif #if APR_HAS_MICROSOFT_LDAPSDK err->rc = ldap_delete_ext(ldap->ld, (char *)dn, sctrls, cctrls, &msgid); #else err->rc = ldap_delete_ext(ldap->ld, dn, sctrls, cctrls, &msgid); #endif if (err->rc != LDAP_SUCCESS) { err->msg = ldap_err2string(err->rc); err->reason = "LDAP: ldap_delete failed"; return apr_ldap_status(err->rc); } else { memset(err, 0, sizeof(*err)); } res = apr_pcalloc(pool, sizeof(apr_ldap_result_t)); if (!res) { return APR_ENOMEM; } res->msgtype = LDAP_RES_DELETE; res->cb.delete = delete_cb; res->ctx = ctx; apr_ldap_result_add(pool, ldap, res, msgid); return APR_WANT_READ; } APU_DECLARE_LDAP(apr_status_t) apr_ldap_extended(apr_pool_t *pool, apr_ldap_t *ldap, const char *oid, apr_buffer_t *data, apr_array_header_t *serverctrls, apr_array_header_t *clientctrls, apr_interval_time_t timeout, apr_ldap_extended_cb ext_cb, void *ctx, apu_err_t *err) { LDAPControl **sctrls = NULL; LDAPControl **cctrls = NULL; apr_ldap_result_t *res; struct berval reqdata; struct berval *rd; MSGID_T msgid = 0; apr_status_t status; status = apr_ldap_control_create(pool, ldap, &sctrls, serverctrls, err); if (APR_SUCCESS != status) { return status; } status = apr_ldap_control_create(pool, ldap, &cctrls, clientctrls, err); if (APR_SUCCESS != status) { return status; } #ifdef LDAP_OPT_NETWORK_TIMEOUT { struct timeval tv, *tvptr; if (timeout < 0) { tvptr = NULL; } else { tv.tv_sec = (long) apr_time_sec(timeout); tv.tv_usec = (long) apr_time_usec(timeout); tvptr = &tv; } err->rc = ldap_set_option(ldap->ld, LDAP_OPT_NETWORK_TIMEOUT, tvptr); if (err->rc != LDAP_SUCCESS) { err->msg = ldap_err2string(err->rc); err->reason = "LDAP: Could not set network timeout"; return APR_EINVAL; } } #endif if (!data || apr_buffer_is_null(data)) { rd = NULL; } else { reqdata.bv_val = apr_buffer_mem(data, NULL); reqdata.bv_len = TO_BV_LEN(apr_buffer_len(data)); rd = &reqdata; } #if APR_HAS_MICROSOFT_LDAPSDK err->rc = ldap_extended_operation(ldap->ld, (char *)oid, rd, sctrls, cctrls, &msgid); #else err->rc = ldap_extended_operation(ldap->ld, oid, rd, sctrls, cctrls, &msgid); #endif if (err->rc != LDAP_SUCCESS) { err->msg = ldap_err2string(err->rc); err->reason = "LDAP: ldap_extended_operation failed"; return apr_ldap_status(err->rc); } else { memset(err, 0, sizeof(*err)); } res = apr_pcalloc(pool, sizeof(apr_ldap_result_t)); if (!res) { return APR_ENOMEM; } res->msgtype = LDAP_RES_EXTENDED; res->cb.ext = ext_cb; res->ctx = ctx; apr_ldap_result_add(pool, ldap, res, msgid); return APR_WANT_READ; } APU_DECLARE_LDAP(apr_status_t) apr_ldap_unbind(apr_ldap_t *ldap, apr_array_header_t *serverctrls, apr_array_header_t *clientctrls, apu_err_t *err) { apr_status_t status; status = apr_ldap_control_create(ldap->pool, ldap, &ldap->serverctrls, serverctrls, err); if (APR_SUCCESS != status) { return status; } status = apr_ldap_control_create(ldap->pool, ldap, &ldap->clientctrls, clientctrls, err); if (APR_SUCCESS != status) { return status; } apr_pool_cleanup_run(ldap->pool, ldap, apr_ldap_cleanup); memcpy(err, &ldap->err, sizeof(apu_err_t)); return ldap->status; } #endif /* APR_HAS_LDAP */