openldap/servers/lloadd/bind.c
Ondřej Kuzník c52328f63d Clear c_auth on every bind request
For a new bind request, this is obvious, for SASL bind requests, we do
not know the final identity until we have finished handling it, make
sure it stays empty until then.
2020-11-17 17:58:14 +00:00

640 lines
20 KiB
C

/* $OpenLDAP$ */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
*
* Copyright 1998-2020 The OpenLDAP Foundation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
#include "portable.h"
#include <ac/socket.h>
#include <ac/errno.h>
#include <ac/string.h>
#include <ac/time.h>
#include <ac/unistd.h>
#include "lutil.h"
#include "lload.h"
struct berval mech_external = BER_BVC("EXTERNAL");
int
bind_mech_external(
LloadConnection *client,
LloadOperation *op,
struct berval *credentials )
{
BerValue binddn;
void *ssl;
char *ptr;
client->c_state = LLOAD_C_READY;
client->c_type = LLOAD_C_OPEN;
/*
* We only support implicit assertion.
*
* Although RFC 4513 says the credentials field must be missing, RFC 4422
* doesn't and libsasl2 will pass a zero-length string to send. We have to
* allow that.
*/
if ( !BER_BVISEMPTY( credentials ) ) {
return operation_send_reject_locked( op, LDAP_UNWILLING_TO_PERFORM,
"proxy authorization is not supported", 1 );
}
ssl = ldap_pvt_tls_sb_ctx( client->c_sb );
if ( !ssl || ldap_pvt_tls_get_peer_dn( ssl, &binddn, NULL, 0 ) ) {
return operation_send_reject_locked( op, LDAP_INVALID_CREDENTIALS,
"no externally negotiated identity", 1 );
}
client->c_auth.bv_len = binddn.bv_len + STRLENOF("dn:");
client->c_auth.bv_val = ch_malloc( client->c_auth.bv_len + 1 );
ptr = lutil_strcopy( client->c_auth.bv_val, "dn:" );
ptr = lutil_strncopy( ptr, binddn.bv_val, binddn.bv_len );
*ptr = '\0';
ber_memfree( binddn.bv_val );
if ( !ber_bvstrcasecmp( &client->c_auth, &lloadd_identity ) ) {
client->c_type = LLOAD_C_PRIVILEGED;
}
return operation_send_reject_locked( op, LDAP_SUCCESS, "", 1 );
}
/*
* On entering the function, we've put a reference on both connections and hold
* upstream's c_io_mutex.
*/
static int
client_bind(
LloadOperation *op,
struct berval *binddn,
ber_tag_t tag,
struct berval *auth )
{
LloadConnection *upstream = op->o_upstream;
ber_printf( upstream->c_pendingber, "t{titOtO}", LDAP_TAG_MESSAGE,
LDAP_TAG_MSGID, op->o_upstream_msgid,
LDAP_REQ_BIND, &op->o_request,
LDAP_TAG_CONTROLS, BER_BV_OPTIONAL( &op->o_ctrls ) );
return 0;
}
#ifdef LDAP_API_FEATURE_VERIFY_CREDENTIALS
/*
* On entering the function, we've put a reference on both connections and hold
* upstream's c_io_mutex.
*/
static int
client_bind_as_vc(
LloadOperation *op,
struct berval *binddn,
ber_tag_t tag,
struct berval *auth )
{
LloadConnection *upstream = op->o_upstream;
CONNECTION_LOCK(upstream);
ber_printf( upstream->c_pendingber, "t{tit{tst{{tOOtOtO}}}}", LDAP_TAG_MESSAGE,
LDAP_TAG_MSGID, op->o_upstream_msgid,
LDAP_REQ_EXTENDED,
LDAP_TAG_EXOP_REQ_OID, LDAP_EXOP_VERIFY_CREDENTIALS,
LDAP_TAG_EXOP_REQ_VALUE,
LDAP_TAG_EXOP_VERIFY_CREDENTIALS_COOKIE, BER_BV_OPTIONAL( &upstream->c_vc_cookie ),
&binddn, tag, &auth,
LDAP_TAG_EXOP_VERIFY_CREDENTIALS_CONTROLS, BER_BV_OPTIONAL( &op->o_ctrls ) );
CONNECTION_UNLOCK(upstream);
return 0;
}
#endif /* LDAP_API_FEATURE_VERIFY_CREDENTIALS */
int
request_bind( LloadConnection *client, LloadOperation *op )
{
LloadConnection *upstream = NULL;
BerElement *ber, *copy;
struct berval binddn, auth;
ber_int_t version;
ber_tag_t tag;
unsigned long pin = client->c_pin_id;
int res, rc = LDAP_SUCCESS;
if ( pin ) {
LloadOperation *pinned_op, needle = {
.o_client_connid = client->c_connid,
.o_client_msgid = 0,
.o_pin_id = client->c_pin_id,
};
Debug( LDAP_DEBUG_CONNS, "request_bind: "
"client connid=%lu is pinned pin=%lu\n",
client->c_connid, pin );
pinned_op =
tavl_delete( &client->c_ops, &needle, operation_client_cmp );
if ( pinned_op ) {
assert( op->o_tag == pinned_op->o_tag );
pinned_op->o_client_msgid = op->o_client_msgid;
/* Preserve the new BerElement and its pointers, reclaim the old
* one in operation_destroy_from_client if it's still there */
needle.o_ber = pinned_op->o_ber;
pinned_op->o_ber = op->o_ber;
op->o_ber = needle.o_ber;
pinned_op->o_request = op->o_request;
pinned_op->o_ctrls = op->o_ctrls;
/*
* pinned_op is accessible from the upstream, protect it since we
* lose the client lock in operation_destroy_from_client temporarily
*/
pinned_op->o_client_refcnt++;
operation_destroy_from_client( op );
pinned_op->o_client_refcnt--;
op = pinned_op;
}
}
/* protect the Bind operation */
op->o_client_refcnt++;
tavl_delete( &client->c_ops, op, operation_client_cmp );
client_reset( client );
client->c_state = LLOAD_C_BINDING;
client->c_type = LLOAD_C_OPEN;
if ( (copy = ber_alloc()) == NULL ) {
goto fail;
}
ber_init2( copy, &op->o_request, 0 );
tag = ber_get_int( copy, &version );
if ( tag == LBER_ERROR ) {
Debug( LDAP_DEBUG_PACKETS, "request_bind: "
"failed to parse version field\n" );
goto fail;
} else if ( version != LDAP_VERSION3 ) {
operation_send_reject_locked(
op, LDAP_PROTOCOL_ERROR, "LDAP version unsupported", 1 );
ber_free( copy, 0 );
return LDAP_SUCCESS;
}
tag = ber_get_stringbv( copy, &binddn, LBER_BV_NOTERM );
if ( tag == LBER_ERROR ) {
Debug( LDAP_DEBUG_PACKETS, "request_bind: "
"failed to parse bind name field\n" );
goto fail;
}
if ( !BER_BVISNULL( &client->c_auth ) ) {
ch_free( client->c_auth.bv_val );
BER_BVZERO( &client->c_auth );
}
tag = ber_skip_element( copy, &auth );
if ( tag == LDAP_AUTH_SIMPLE ) {
if ( !BER_BVISEMPTY( &binddn ) ) {
char *ptr;
client->c_auth.bv_len = STRLENOF("dn:") + binddn.bv_len;
client->c_auth.bv_val = ch_malloc( client->c_auth.bv_len + 1 );
ptr = lutil_strcopy( client->c_auth.bv_val, "dn:" );
ptr = lutil_strncopy( ptr, binddn.bv_val, binddn.bv_len );
*ptr = '\0';
}
if ( !BER_BVISNULL( &client->c_sasl_bind_mech ) ) {
ber_memfree( client->c_sasl_bind_mech.bv_val );
BER_BVZERO( &client->c_sasl_bind_mech );
}
} else if ( tag == LDAP_AUTH_SASL ) {
struct berval mech;
ber_init2( copy, &auth, 0 );
if ( ber_get_stringbv( copy, &mech, LBER_BV_NOTERM ) == LBER_ERROR ) {
goto fail;
}
if ( !ber_bvcmp( &mech, &mech_external ) ) {
struct berval credentials = BER_BVNULL;
ber_get_stringbv( copy, &credentials, LBER_BV_NOTERM );
rc = bind_mech_external( client, op, &credentials );
/* terminate the upstream side if client switched mechanisms */
if ( !BER_BVISNULL( &client->c_sasl_bind_mech ) ) {
op->o_client_refcnt++;
CONNECTION_UNLOCK_INCREF(client);
operation_abandon( op );
CONNECTION_LOCK_DECREF(client);
ber_memfree( client->c_sasl_bind_mech.bv_val );
BER_BVZERO( &client->c_sasl_bind_mech );
}
ber_free( copy, 0 );
return rc;
} else if ( BER_BVISNULL( &client->c_sasl_bind_mech ) ) {
ber_dupbv( &client->c_sasl_bind_mech, &mech );
} else if ( ber_bvcmp( &mech, &client->c_sasl_bind_mech ) ) {
ber_bvreplace( &client->c_sasl_bind_mech, &mech );
}
} else {
goto fail;
}
rc = tavl_insert( &client->c_ops, op, operation_client_cmp, avl_dup_error );
assert( rc == LDAP_SUCCESS );
CONNECTION_UNLOCK_INCREF(client);
if ( pin ) {
ldap_pvt_thread_mutex_lock( &op->o_link_mutex );
upstream = op->o_upstream;
if ( upstream ) {
CONNECTION_LOCK(upstream);
if ( !upstream->c_live ) {
CONNECTION_UNLOCK(upstream);
upstream = NULL;
}
}
ldap_pvt_thread_mutex_unlock( &op->o_link_mutex );
}
/* If we were pinned but lost the link, don't look for a new upstream, we
* have to reject the op and clear pin */
if ( upstream ) {
CONNECTION_UNLOCK_INCREF(upstream);
ldap_pvt_thread_mutex_lock( &upstream->c_io_mutex );
} else if ( !pin ) {
upstream = backend_select( op, &res );
} else {
Debug( LDAP_DEBUG_STATS, "request_bind: "
"connid=%lu, msgid=%d pinned upstream lost\n",
op->o_client_connid, op->o_client_msgid );
operation_send_reject( op, LDAP_UNAVAILABLE,
"connection to the remote server has been severed", 1 );
pin = 0;
goto done;
}
if ( !upstream ) {
Debug( LDAP_DEBUG_STATS, "request_bind: "
"connid=%lu, msgid=%d no available connection found\n",
op->o_client_connid, op->o_client_msgid );
operation_send_reject( op, res, "no connections available", 1 );
assert( client->c_pin_id == 0 );
goto done;
}
ber = upstream->c_pendingber;
if ( ber == NULL && (ber = ber_alloc()) == NULL ) {
Debug( LDAP_DEBUG_ANY, "request_bind: "
"ber_alloc failed\n" );
ldap_pvt_thread_mutex_unlock( &upstream->c_io_mutex );
CONNECTION_LOCK_DECREF(upstream);
CONNECTION_UNLOCK_OR_DESTROY(upstream);
CONNECTION_LOCK_DECREF(client);
goto fail;
}
upstream->c_pendingber = ber;
CONNECTION_LOCK(upstream);
if ( pin ) {
tavl_delete( &upstream->c_ops, op, operation_upstream_cmp );
} else if ( tag == LDAP_AUTH_SASL && !op->o_pin_id ) {
ldap_pvt_thread_mutex_lock( &lload_pin_mutex );
pin = op->o_pin_id = lload_next_pin++;
Debug( LDAP_DEBUG_CONNS, "request_bind: "
"client connid=%lu allocated pin=%lu linking it to upstream "
"connid=%lu\n",
op->o_client_connid, pin, upstream->c_connid );
ldap_pvt_thread_mutex_unlock( &lload_pin_mutex );
}
op->o_upstream = upstream;
op->o_upstream_connid = upstream->c_connid;
op->o_upstream_msgid = upstream->c_next_msgid++;
Debug( LDAP_DEBUG_TRACE, "request_bind: "
"added bind from client connid=%lu to upstream connid=%lu "
"as msgid=%d\n",
op->o_client_connid, op->o_upstream_connid, op->o_upstream_msgid );
if ( tavl_insert( &upstream->c_ops, op, operation_upstream_cmp,
avl_dup_error ) ) {
assert(0);
}
upstream->c_state = LLOAD_C_BINDING;
CONNECTION_UNLOCK(upstream);
#ifdef LDAP_API_FEATURE_VERIFY_CREDENTIALS
if ( lload_features & LLOAD_FEATURE_VC ) {
rc = client_bind_as_vc( op, &binddn, tag, &auth );
} else
#endif /* LDAP_API_FEATURE_VERIFY_CREDENTIALS */
{
rc = client_bind( op, &binddn, tag, &auth );
}
done:
if ( rc == LDAP_SUCCESS ) {
CONNECTION_LOCK(client);
if ( upstream ) {
ldap_pvt_thread_mutex_unlock( &upstream->c_io_mutex );
}
client->c_pin_id = pin;
if ( !--op->o_client_refcnt || !upstream ) {
operation_destroy_from_client( op );
if ( client->c_state == LLOAD_C_BINDING ) {
client->c_state = LLOAD_C_READY;
client->c_type = LLOAD_C_OPEN;
client->c_pin_id = 0;
if ( !BER_BVISNULL( &client->c_auth ) ) {
ch_free( client->c_auth.bv_val );
BER_BVZERO( &client->c_auth );
}
if ( !BER_BVISNULL( &client->c_sasl_bind_mech ) ) {
ber_memfree( client->c_sasl_bind_mech.bv_val );
BER_BVZERO( &client->c_sasl_bind_mech );
}
}
}
CONNECTION_UNLOCK(client);
if ( upstream ) {
connection_write_cb( -1, 0, upstream );
CONNECTION_LOCK_DECREF(upstream);
CONNECTION_UNLOCK_OR_DESTROY(upstream);
}
CONNECTION_LOCK_DECREF(client);
} else {
fail:
rc = -1;
CONNECTION_LOCK_DECREF(client);
op->o_client_refcnt--;
operation_destroy_from_client( op );
client->c_pin_id = 0;
CONNECTION_DESTROY(client);
}
ber_free( copy, 0 );
return rc;
}
int
handle_bind_response(
LloadConnection *client,
LloadOperation *op,
BerElement *ber )
{
LloadConnection *upstream = op->o_upstream;
BerValue response;
BerElement *copy;
LloadOperation *removed;
ber_int_t result;
ber_tag_t tag;
int rc = LDAP_SUCCESS;
if ( (copy = ber_alloc()) == NULL ) {
rc = -1;
goto done;
}
tag = ber_peek_element( ber, &response );
assert( tag == LDAP_RES_BIND );
ber_init2( copy, &response, 0 );
tag = ber_get_enum( copy, &result );
ber_free( copy, 0 );
if ( tag == LBER_ERROR ) {
rc = -1;
goto done;
}
Debug( LDAP_DEBUG_STATS, "handle_bind_response: "
"received response for bind request msgid=%d by client "
"connid=%lu, result=%d\n",
op->o_client_msgid, op->o_client_connid, result );
CONNECTION_LOCK(upstream);
if ( result != LDAP_SASL_BIND_IN_PROGRESS ) {
upstream->c_state = LLOAD_C_READY;
op->o_pin_id = 0;
} else {
if ( tavl_delete( &upstream->c_ops, op, operation_upstream_cmp ) ) {
op->o_upstream_msgid = 0;
op->o_upstream_refcnt++;
rc = tavl_insert( &upstream->c_ops, op, operation_upstream_cmp,
avl_dup_error );
assert( rc == LDAP_SUCCESS );
}
}
CONNECTION_UNLOCK(upstream);
CONNECTION_LOCK(client);
removed = tavl_delete( &client->c_ops, op, operation_client_cmp );
assert( !removed || op == removed );
if ( client->c_state == LLOAD_C_BINDING ) {
switch ( result ) {
case LDAP_SASL_BIND_IN_PROGRESS:
op->o_client_msgid = 0;
rc = tavl_insert( &client->c_ops, op, operation_client_cmp,
avl_dup_error );
assert( rc == LDAP_SUCCESS );
break;
case LDAP_SUCCESS:
default: {
op->o_client = NULL;
client->c_state = LLOAD_C_READY;
client->c_type = LLOAD_C_OPEN;
client->c_pin_id = 0;
if ( !BER_BVISNULL( &client->c_auth ) ) {
if ( result != LDAP_SUCCESS ) {
ber_memfree( client->c_auth.bv_val );
BER_BVZERO( &client->c_auth );
} else if ( !ber_bvstrcasecmp(
&client->c_auth, &lloadd_identity ) ) {
client->c_type = LLOAD_C_PRIVILEGED;
}
}
if ( !BER_BVISNULL( &client->c_sasl_bind_mech ) ) {
ber_memfree( client->c_sasl_bind_mech.bv_val );
BER_BVZERO( &client->c_sasl_bind_mech );
}
break;
}
}
} else {
assert( client->c_state == LLOAD_C_INVALID ||
client->c_state == LLOAD_C_CLOSING );
}
CONNECTION_UNLOCK(client);
done:
if ( rc ) {
operation_send_reject( op, LDAP_OTHER, "internal error", 1 );
ber_free( ber, 1 );
return LDAP_SUCCESS;
}
return forward_final_response( client, op, ber );
}
#ifdef LDAP_API_FEATURE_VERIFY_CREDENTIALS
int
handle_vc_bind_response(
LloadConnection *client,
LloadOperation *op,
BerElement *ber )
{
BerElement *output;
BerValue matched, diagmsg, creds = BER_BVNULL, controls = BER_BVNULL;
ber_int_t result;
ber_tag_t tag;
ber_len_t len;
int rc = 0;
tag = ber_scanf( ber, "{emm" /* "}" */,
&result, &matched, &diagmsg );
if ( tag == LBER_ERROR ) {
rc = -1;
goto done;
}
tag = ber_peek_tag( ber, &len );
if ( result == LDAP_PROTOCOL_ERROR ) {
LloadConnection *upstream = op->o_upstream;
LloadBackend *b;
CONNECTION_LOCK(upstream);
b = (LloadBackend *)upstream->c_private;
Debug( LDAP_DEBUG_ANY, "handle_vc_bind_response: "
"VC extended operation not supported on backend %s\n",
b->b_uri.bv_val );
CONNECTION_UNLOCK(upstream);
}
Debug( LDAP_DEBUG_STATS, "handle_vc_bind_response: "
"received response for bind request msgid=%d by client "
"connid=%lu, result=%d\n",
op->o_client_msgid, op->o_client_connid, result );
CONNECTION_LOCK(client);
if ( tag == LDAP_TAG_EXOP_VERIFY_CREDENTIALS_COOKIE ) {
if ( !BER_BVISNULL( &client->c_vc_cookie ) ) {
ber_memfree( client->c_vc_cookie.bv_val );
}
tag = ber_scanf( ber, "o", &client->c_vc_cookie );
if ( tag == LBER_ERROR ) {
rc = -1;
CONNECTION_UNLOCK_INCREF(client);
goto done;
}
tag = ber_peek_tag( ber, &len );
}
if ( tag == LDAP_TAG_EXOP_VERIFY_CREDENTIALS_SCREDS ) {
tag = ber_scanf( ber, "m", &creds );
if ( tag == LBER_ERROR ) {
rc = -1;
CONNECTION_UNLOCK_INCREF(client);
goto done;
}
tag = ber_peek_tag( ber, &len );
}
if ( tag == LDAP_TAG_EXOP_VERIFY_CREDENTIALS_CONTROLS ) {
tag = ber_scanf( ber, "m", &controls );
if ( tag == LBER_ERROR ) {
rc = -1;
CONNECTION_UNLOCK_INCREF(client);
goto done;
}
}
if ( client->c_state == LLOAD_C_BINDING ) {
switch ( result ) {
case LDAP_SASL_BIND_IN_PROGRESS:
break;
case LDAP_SUCCESS:
default: {
client->c_state = LLOAD_C_READY;
client->c_type = LLOAD_C_OPEN;
client->c_pin_id = 0;
if ( result != LDAP_SUCCESS ) {
ber_memfree( client->c_auth.bv_val );
BER_BVZERO( &client->c_auth );
} else if ( !ber_bvstrcasecmp(
&client->c_auth, &lloadd_identity ) ) {
client->c_type = LLOAD_C_PRIVILEGED;
}
if ( !BER_BVISNULL( &client->c_vc_cookie ) ) {
ber_memfree( client->c_vc_cookie.bv_val );
BER_BVZERO( &client->c_vc_cookie );
}
if ( !BER_BVISNULL( &client->c_sasl_bind_mech ) ) {
ber_memfree( client->c_sasl_bind_mech.bv_val );
BER_BVZERO( &client->c_sasl_bind_mech );
}
break;
}
}
} else {
assert( client->c_state == LLOAD_C_INVALID ||
client->c_state == LLOAD_C_CLOSING );
}
CONNECTION_UNLOCK_INCREF(client);
ldap_pvt_thread_mutex_lock( &client->c_io_mutex );
output = client->c_pendingber;
if ( output == NULL && (output = ber_alloc()) == NULL ) {
rc = -1;
ldap_pvt_thread_mutex_unlock( &client->c_io_mutex );
goto done;
}
client->c_pendingber = output;
rc = ber_printf( output, "t{tit{eOOtO}tO}", LDAP_TAG_MESSAGE,
LDAP_TAG_MSGID, op->o_client_msgid, LDAP_RES_BIND,
result, &matched, &diagmsg,
LDAP_TAG_SASL_RES_CREDS, BER_BV_OPTIONAL( &creds ),
LDAP_TAG_CONTROLS, BER_BV_OPTIONAL( &controls ) );
ldap_pvt_thread_mutex_unlock( &client->c_io_mutex );
if ( rc >= 0 ) {
connection_write_cb( -1, 0, client );
rc = 0;
}
done:
CONNECTION_LOCK_DECREF(client);
operation_destroy_from_client( op );
CONNECTION_UNLOCK_OR_DESTROY(client);
ber_free( ber, 1 );
return rc;
}
#endif /* LDAP_API_FEATURE_VERIFY_CREDENTIALS */