blob: a7c30410a9f1170b29f39609b4e77e921fcbadbd [file] [log] [blame]
/* 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"
#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>
typedef struct apr_ldap_t {
apr_pool_t *pool;
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;
} apr_ldap_t;
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;
int msgid;
int msgtype;
union {
apr_ldap_bind_cb bind;
apr_ldap_compare_cb compare;
apr_ldap_search_result_cb search;
} cb;
union {
apr_ldap_search_entry_cb search;
} entry_cb;
void *ctx;
apr_size_t nentries;
} apr_ldap_result_t;
APU_DECLARE_LDAP(apr_status_t) apr_ldap_status(int rc, apr_status_t status)
{
switch (rc) {
case LDAP_SUCCESS:
return APR_SUCCESS;
case LDAP_TIMEOUT:
return APR_ETIMEDOUT;
#if defined(LDAP_SERVER_DOWN)
case LDAP_SERVER_DOWN:
return APR_SERVER_DOWN;
#endif
#if defined(LDAP_UNAVAILABLE)
case LDAP_UNAVAILABLE:
return APR_SERVER_DOWN;
#endif
case LDAP_AUTH_UNKNOWN:
return APR_AUTH_UNKNOWN;
#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;
case LDAP_INSUFFICIENT_ACCESS:
return APR_INSUFFICIENT_ACCESS;
#ifdef LDAP_INSUFFICIENT_RIGHTS
case LDAP_INSUFFICIENT_RIGHTS:
return APR_INSUFFICIENT_RIGHTS;
#endif
#ifdef LDAP_CONSTRAINT_VIOLATION
case LDAP_CONSTRAINT_VIOLATION:
return APR_CONSTRAINT_VIOLATION;
#endif
case LDAP_NO_RESULTS_RETURNED:
return APR_NO_RESULTS_RETURNED;
case LDAP_COMPARE_TRUE:
return APR_COMPARE_TRUE;
case LDAP_COMPARE_FALSE:
return APR_COMPARE_FALSE;
case LDAP_OPERATIONS_ERROR:
case LDAP_PROTOCOL_ERROR:
case LDAP_TIMELIMIT_EXCEEDED:
case LDAP_SIZELIMIT_EXCEEDED:
case LDAP_OTHER:
return APR_EGENERAL;
default:
return status;
}
}
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 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)
{
int m1 = ((apr_ldap_result_t *)a)->msgid;
int 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(int));
(*ldap)->prepares = apr_array_make(pool, 1, sizeof(apr_ldap_prepare_t));
apr_pool_cleanup_register(pool, (*ldap), 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), &(result));
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
}
#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) {
ULONG ul = (ULONG) LDAP_OPT_OFF;
result->rc = ldap_set_option(ldap, LDAP_OPT_SSL, &ul);
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) {
ULONG ul = (ULONG) LDAP_OPT_ON;
result->rc = ldap_set_option(ldap, LDAP_OPT_SSL, &ul);
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: {
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;
}
case APR_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;
}
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: {
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, APR_EGENERAL);
}
}
outvalue->socket = ldap->socket;
return status;
}
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;
break;
case APR_LDAP_OPT_URI:
rc = option_set_uri(ldap, invalue->uri, result);
break;
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, &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, &invalue->deref);
break;
case APR_LDAP_OPT_REFERRALS: {
void *refs = invalue->refs ? LDAP_OPT_ON : 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, &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);
}
if (rc != LDAP_OPT_SUCCESS) {
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)
{
LDAP *ld = ldap->ld;
#if APR_HAS_MICROSOFT_LDAPSDK
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_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, APR_EGENERAL);
}
else {
memset(err, 0, sizeof(*err));
}
return APR_SUCCESS;
}
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;
}
/*
* 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;
}
APU_DECLARE_LDAP(void) apr_ldap_result_add(apr_pool_t *pool,
apr_ldap_t *ldap,
apr_ldap_result_t *res,
int 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);
}
APU_DECLARE_LDAP(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;
int 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) {
int *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, APR_EGENERAL);
}
}
/* 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: {
/* 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, APR_EGENERAL),
NULL, NULL, res->ctx, err);
}
else {
status = apr_ldap_status(err->rc, APR_EGENERAL);
}
apr_ldap_result_remove(ldap, res);
}
break;
}
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, APR_EGENERAL),
matcheddn, (apr_ldap_control_t **)serverctrls,
res->ctx, err);
}
else {
status = apr_ldap_status(err->rc, APR_EGENERAL);
}
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;
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 search: ldap_parse_result()";
if (res->cb.search) {
status = res->cb.search(ldap, apr_ldap_status(err->rc, APR_EGENERAL), 0,
matcheddn, (apr_ldap_control_t **)serverctrls,
res->ctx, err);
}
else {
status = apr_ldap_status(err->rc, APR_EGENERAL);
}
apr_ldap_result_remove(ldap, res);
if (matcheddn) {
ldap_memfree(matcheddn);
}
if (errmsg) {
ldap_memfree(errmsg);
}
if (serverctrls) {
ldap_controls_free(serverctrls);
}
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;
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_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, APR_EGENERAL);
}
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_COMPARE: {
/*
* 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 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;
int nattrs = 0, j = 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)) {
nattrs++;
}
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);
if (vals) {
int k, nvals, 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;
}
}
nvals = ldap_count_values_len(vals);
for (k = 0; k < nvals; k++) {
apr_buffer_t buf;
char *str = NULL;
if (binary) {
apr_buffer_mem_set(&buf, vals[k]->bv_val, vals[k]->bv_len);
}
else {
str = strndup(vals[k]->bv_val, vals[k]->bv_len);
apr_buffer_str_set(&buf, str, vals[k]->bv_len);
}
if (res->entry_cb.search) {
status = res->entry_cb.search(ldap, dn, res->nentries, nattrs, j,
attr, nvals, k, &buf, 0, res->ctx, err);
}
else {
status = apr_ldap_status(err->rc, APR_EGENERAL);
}
if (str) {
free(str);
}
}
}
else {
if (res->entry_cb.search) {
status = res->entry_cb.search(ldap, dn, res->nentries, nattrs, j,
attr, 0, 0, NULL, 0, res->ctx, err);
}
else {
status = apr_ldap_status(err->rc, APR_EGENERAL);
}
}
ldap_value_free_len(vals);
ldap_memfree(attr);
if (APR_SUCCESS != status) {
break;
}
j++;
}
res->nentries++;
if (res->entry_cb.search) {
status = res->entry_cb.search(ldap, dn, res->nentries, 0, 0, NULL,
0, 0, NULL, 0, res->ctx, err);
}
else {
status = apr_ldap_status(err->rc, APR_EGENERAL);
}
ldap_memfree((void *)dn);
break;
}
case LDAP_RES_SEARCH_REFERENCE: {
break;
}
case LDAP_RES_SEARCH_RESULT: {
/*
* 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 bind 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 */
int *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;
}
#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
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;
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 };
unsigned int flags = LDAP_SASL_QUIET;
apr_ldap_bind_ctx_t payload;
payload.ld = ldap;
payload.interact = interact_cb;
payload.ctx = interact_ctx;
payload.status = APR_SUCCESS;
int 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 = 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.
*/
err->rc = ldap_sasl_bind(ldap->ld, dn, LDAP_SASL_SIMPLE, &cred,
NULL, NULL, &msgid);
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, APR_EGENERAL);
}
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;
/* 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, APR_EGENERAL);
}
#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_ldap_control_t *) apr_ldap_control_make(apr_pool_t *pool,
apr_ldap_t *ldap,
const char *oid,
apr_buffer_t *val,
int iscritical)
{
apr_ldap_control_t *ctl;
LDAPControl *c;
ctl = apr_pcalloc(pool, sizeof(apr_ldap_result_t));
c = (LDAPControl *)ctl;
if (c) {
apr_size_t size;
c->ldctl_oid = (char *)oid;
c->ldctl_value.bv_val = apr_buffer_mem(val, &size);
c->ldctl_value.bv_len = size;
c->ldctl_iscritical = iscritical ? 1 : 0;
}
return ctl;
}
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_ldap_control_t **serverctrls,
apr_ldap_control_t **clientctrls,
apr_interval_time_t timeout,
apr_ldap_compare_cb compare_cb, void *compare_ctx,
apu_err_t *err)
{
apr_ldap_result_t *res;
struct berval bval;
apr_size_t size;
int msgid = 0;
bval.bv_val = apr_buffer_mem(val, &size);
bval.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
err->rc = ldap_compare_ext(ldap->ld, dn, attr, &bval,
(LDAPControl **)serverctrls, (LDAPControl **)clientctrls, &msgid);
if (err->rc != LDAP_SUCCESS) {
err->msg = ldap_err2string(err->rc);
err->reason = "LDAP: ldap_compare failed";
return apr_ldap_status(err->rc, APR_EGENERAL);
}
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_ldap_control_t **serverctrls,
apr_ldap_control_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)
{
apr_ldap_result_t *res;
struct timeval tv, *tvptr;
int msgid = 0;
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,
(LDAPControl **)serverctrls, (LDAPControl **)clientctrls, tvptr, sizelimit, &msgid);
if (err->rc != LDAP_SUCCESS) {
err->msg = ldap_err2string(err->rc);
err->reason = "LDAP: ldap_search failed";
return apr_ldap_status(err->rc, APR_EGENERAL);
}
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_unbind(apr_ldap_t *ldap,
apr_ldap_control_t **serverctrls,
apr_ldap_control_t **clientctrls,
apu_err_t *err)
{
ldap->serverctrls = (LDAPControl **)serverctrls;
ldap->clientctrls = (LDAPControl **)clientctrls;
apr_pool_cleanup_run(ldap->pool, ldap, ldap_cleanup);
memcpy(err, &ldap->err, sizeof(apu_err_t));
return ldap->status;
}
#endif /* APR_HAS_LDAP */