diff options
Diffstat (limited to '')
-rw-r--r-- | servers/slapd/back-ldap/Makefile.in | 45 | ||||
-rw-r--r-- | servers/slapd/back-ldap/TODO.proxy | 101 | ||||
-rw-r--r-- | servers/slapd/back-ldap/add.c | 139 | ||||
-rw-r--r-- | servers/slapd/back-ldap/back-ldap.h | 479 | ||||
-rw-r--r-- | servers/slapd/back-ldap/bind.c | 3204 | ||||
-rw-r--r-- | servers/slapd/back-ldap/chain.c | 2356 | ||||
-rw-r--r-- | servers/slapd/back-ldap/compare.c | 88 | ||||
-rw-r--r-- | servers/slapd/back-ldap/config.c | 2214 | ||||
-rw-r--r-- | servers/slapd/back-ldap/delete.c | 85 | ||||
-rw-r--r-- | servers/slapd/back-ldap/distproc.c | 998 | ||||
-rw-r--r-- | servers/slapd/back-ldap/extended.c | 410 | ||||
-rw-r--r-- | servers/slapd/back-ldap/init.c | 374 | ||||
-rw-r--r-- | servers/slapd/back-ldap/modify.c | 136 | ||||
-rw-r--r-- | servers/slapd/back-ldap/modrdn.c | 123 | ||||
-rw-r--r-- | servers/slapd/back-ldap/monitor.c | 1074 | ||||
-rw-r--r-- | servers/slapd/back-ldap/pbind.c | 173 | ||||
-rw-r--r-- | servers/slapd/back-ldap/proto-ldap.h | 124 | ||||
-rw-r--r-- | servers/slapd/back-ldap/search.c | 1042 | ||||
-rw-r--r-- | servers/slapd/back-ldap/unbind.c | 78 |
19 files changed, 13243 insertions, 0 deletions
diff --git a/servers/slapd/back-ldap/Makefile.in b/servers/slapd/back-ldap/Makefile.in new file mode 100644 index 0000000..4b10d77 --- /dev/null +++ b/servers/slapd/back-ldap/Makefile.in @@ -0,0 +1,45 @@ +# Makefile.in for back-ldap +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2022 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>. + +SRCS = init.c config.c search.c bind.c unbind.c add.c compare.c \ + delete.c modify.c modrdn.c extended.c chain.c \ + distproc.c monitor.c pbind.c +OBJS = init.lo config.lo search.lo bind.lo unbind.lo add.lo compare.lo \ + delete.lo modify.lo modrdn.lo extended.lo chain.lo \ + distproc.lo monitor.lo pbind.lo + +LDAP_INCDIR= ../../../include +LDAP_LIBDIR= ../../../libraries + +BUILD_OPT = "--enable-ldap" +BUILD_MOD = @BUILD_LDAP@ + +mod_DEFS = -DSLAPD_IMPORT +MOD_DEFS = $(@BUILD_LDAP@_DEFS) + +shared_LDAP_LIBS = $(LDAP_LIBLDAP_LA) $(LDAP_LIBLBER_LA) +NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) +UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) + +LIBBASE = back_ldap + +XINCPATH = -I.. -I$(srcdir)/.. +XDEFS = $(MODULES_CPPFLAGS) + +all-local-lib: ../.backend + +../.backend: lib$(LIBBASE).a + @touch $@ + diff --git a/servers/slapd/back-ldap/TODO.proxy b/servers/slapd/back-ldap/TODO.proxy new file mode 100644 index 0000000..01406c5 --- /dev/null +++ b/servers/slapd/back-ldap/TODO.proxy @@ -0,0 +1,101 @@ +back-proxy + +A proxy that handles a pool of URI associated to a unique suffix. +Each request is spread over the different URIs and results are +masqueraded to appear as coming from a unique server. + +Suppose a company has two branches, whose existing DS have URIs + +"ldap://ldap.branch1.com/o=Branch 1, c=US" +"ldap://ldap.branch2.it/o=Branch 2, c=IT" + +and it wants to propose to the outer world as a unique URI + +"ldap://ldap.company.net/dc=company, dc=net" + +It could do some rewriting to map everything that comes in with a base DN +of "o=Branch 1, dc=company, dc=net" as the URI of the Branch 1, and +everything that comes in with a base DN of "o=Branch 2, dc=company, dc=net" +as the URI of Branch 2, and by rewriting all the DNs back to the new, uniform +base. Everything that comes in with a base DN of "dc=company, dc=net" should +be handled locally and propagated to the two branch URIs if a subtree +(or at least onelevel) search is required. + +Operations: + +- bind +- unbind +- search +- compare +- add +- modify +- modrdn +- delete +- abandon + +The input of each operation may be related to: + + exact DN exact parent ancestor +------------------------------------------------------------- +bind x +unbind +search x x x +compare x +add x +modify x +modrdn x +delete x +abandon + +The backend must rely on a DN fetching mechanism. Each operation requires +to determine as early as possible which URI will be able to satisfy it. +Apart from searches, which by definition are usually allowed to return +multiple results, and apart from unbind and abandon, which do not return any +result, all the remaining operations require the related entry to be unique. + +A major problem isposed by the uniqueness of the DNs. As far as the suffixes +are masqueraded by a common suffix, the DNs are no longer guaranteed to be +unique. This backend relies on the assumption that the uniqueness of the +DNs is guaranteed. + +Two layers of depth in DN fetching are envisaged. +The first layer is provided by a backend-side cache made of previously +retrieved entries. The cache relates each RDN (i.e. the DN apart from the +common suffix) to the pool of URIs that are expected to contain a subset +of its children. + +The second layer is provided by a fetching function that spawns a search for +each URI in the pool determined by the cache if the correct URI has not been +directly determined. + +Note that, as the remote servers may have been updated by some direct +operation, this mechanism does not guarantee the uniqueness of the result. +So write operations will require to skip the cache search and to perform +the exhaustive search of all the URIs unless some hint mechanism is provided +to the backend (e.g. a server is read-only). + +Again, the lag between the fetching of the required DN and the actual +read/write may result in a failure; however, this applies to any LDAP +operation AFAIK. + +- bind +if updates are to be strictly honored, a bind operation is performed against +each URI; otherwise, it is performed against the URIs resulting from a +cache-level DN fetch. + +- unbind +nothing to say; all the open handles related to the connection are reset. + +- search +if updates are to be strictly honored, a search operation is performed against +each URI. Note that this needs be performed also when the backend suffix +is used as base. In case the base is stricter, the URI pool may be restricted +by performing a cache DN fetch of the base first. + +- compare +the same applies to the compare DN. + +- add +this operation is delicate. Unless the DN up to the top-level part excluded +can be uniquely associated to a URI, and unless its uniqueness can be trusted, +no add operation should be allowed. diff --git a/servers/slapd/back-ldap/add.c b/servers/slapd/back-ldap/add.c new file mode 100644 index 0000000..32ceda2 --- /dev/null +++ b/servers/slapd/back-ldap/add.c @@ -0,0 +1,139 @@ +/* add.c - ldap backend add function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2022 The OpenLDAP Foundation. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * 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>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "back-ldap.h" + +int +ldap_back_add( + Operation *op, + SlapReply *rs ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + + ldapconn_t *lc = NULL; + int i = 0, + j = 0; + Attribute *a; + LDAPMod **attrs = NULL, + *attrs2 = NULL; + ber_int_t msgid; + int isupdate; + ldap_back_send_t retrying = LDAP_BACK_RETRYING; + LDAPControl **ctrls = NULL; + + rs->sr_err = LDAP_SUCCESS; + + Debug( LDAP_DEBUG_ARGS, "==> ldap_back_add(\"%s\")\n", + op->o_req_dn.bv_val ); + + if ( !ldap_back_dobind( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + lc = NULL; + goto cleanup; + } + + /* Count number of attributes in entry */ + for ( i = 1, a = op->oq_add.rs_e->e_attrs; a; i++, a = a->a_next ) + /* just count attrs */ ; + + /* Create array of LDAPMods for ldap_add() */ + attrs = (LDAPMod **)ch_malloc( sizeof( LDAPMod * )*i + + sizeof( LDAPMod )*( i - 1 ) ); + attrs2 = ( LDAPMod * )&attrs[ i ]; + + isupdate = be_shadow_update( op ); + for ( i = 0, a = op->oq_add.rs_e->e_attrs; a; a = a->a_next ) { + if ( !isupdate && !get_relax( op ) && a->a_desc->ad_type->sat_no_user_mod ) + { + continue; + } + + attrs[ i ] = &attrs2[ i ]; + attrs[ i ]->mod_op = LDAP_MOD_BVALUES; + attrs[ i ]->mod_type = a->a_desc->ad_cname.bv_val; + + for ( j = 0; a->a_vals[ j ].bv_val; j++ ) + /* just count vals */ ; + attrs[i]->mod_vals.modv_bvals = + ch_malloc( ( j + 1 )*sizeof( struct berval * ) ); + for ( j = 0; a->a_vals[ j ].bv_val; j++ ) { + attrs[ i ]->mod_vals.modv_bvals[ j ] = &a->a_vals[ j ]; + } + attrs[ i ]->mod_vals.modv_bvals[ j ] = NULL; + i++; + } + attrs[ i ] = NULL; + +retry: + ctrls = op->o_ctrls; + rs->sr_err = ldap_back_controls_add( op, rs, lc, &ctrls ); + if ( rs->sr_err != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto cleanup; + } + + rs->sr_err = ldap_add_ext( lc->lc_ld, op->o_req_dn.bv_val, attrs, + ctrls, NULL, &msgid ); + rs->sr_err = ldap_back_op_result( lc, op, rs, msgid, + li->li_timeout[ SLAP_OP_ADD ], + ( LDAP_BACK_SENDRESULT | retrying ) ); + if ( rs->sr_err == LDAP_UNAVAILABLE && retrying ) { + retrying &= ~LDAP_BACK_RETRYING; + if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + /* if the identity changed, there might be need to re-authz */ + (void)ldap_back_controls_free( op, rs, &ctrls ); + goto retry; + } + } + + ldap_pvt_thread_mutex_lock( &li->li_counter_mutex ); + ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_ADD ], 1 ); + ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex ); + +cleanup: + (void)ldap_back_controls_free( op, rs, &ctrls ); + + if ( attrs ) { + for ( --i; i >= 0; --i ) { + ch_free( attrs[ i ]->mod_vals.modv_bvals ); + } + ch_free( attrs ); + } + + if ( lc ) { + ldap_back_release_conn( li, lc ); + } + + Debug( LDAP_DEBUG_ARGS, "<== ldap_back_add(\"%s\"): %d\n", + op->o_req_dn.bv_val, rs->sr_err ); + + return rs->sr_err; +} + diff --git a/servers/slapd/back-ldap/back-ldap.h b/servers/slapd/back-ldap/back-ldap.h new file mode 100644 index 0000000..96bc6f3 --- /dev/null +++ b/servers/slapd/back-ldap/back-ldap.h @@ -0,0 +1,479 @@ +/* back-ldap.h - ldap backend header file */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2022 The OpenLDAP Foundation. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * 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>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#ifndef SLAPD_LDAP_H +#define SLAPD_LDAP_H + +#include "../back-monitor/back-monitor.h" + +LDAP_BEGIN_DECL + +struct ldapinfo_t; + +/* stuff required for monitoring */ +typedef struct ldap_monitor_info_t { + monitor_subsys_t lmi_mss[2]; + + struct berval lmi_ndn; + struct berval lmi_conn_rdn; + struct berval lmi_ops_rdn; +} ldap_monitor_info_t; + +enum { + /* even numbers are connection types */ + LDAP_BACK_PCONN_FIRST = 0, + LDAP_BACK_PCONN_ROOTDN = LDAP_BACK_PCONN_FIRST, + LDAP_BACK_PCONN_ANON = 2, + LDAP_BACK_PCONN_BIND = 4, + + /* add the TLS bit */ + LDAP_BACK_PCONN_TLS = 0x1U, + + LDAP_BACK_PCONN_ROOTDN_TLS = (LDAP_BACK_PCONN_ROOTDN|LDAP_BACK_PCONN_TLS), + LDAP_BACK_PCONN_ANON_TLS = (LDAP_BACK_PCONN_ANON|LDAP_BACK_PCONN_TLS), + LDAP_BACK_PCONN_BIND_TLS = (LDAP_BACK_PCONN_BIND|LDAP_BACK_PCONN_TLS), + + LDAP_BACK_PCONN_LAST +}; + +typedef struct ldapconn_base_t { + Connection *lcb_conn; +#define LDAP_BACK_CONN2PRIV(lc) ((unsigned long)(lc)->lc_conn) +#define LDAP_BACK_PCONN_ISPRIV(lc) (((void *)(lc)->lc_conn) >= ((void *)LDAP_BACK_PCONN_FIRST) \ + && ((void *)(lc)->lc_conn) < ((void *)LDAP_BACK_PCONN_LAST)) +#define LDAP_BACK_PCONN_ISROOTDN(lc) (LDAP_BACK_PCONN_ISPRIV((lc)) \ + && (LDAP_BACK_CONN2PRIV((lc)) < LDAP_BACK_PCONN_ANON)) +#define LDAP_BACK_PCONN_ISANON(lc) (LDAP_BACK_PCONN_ISPRIV((lc)) \ + && (LDAP_BACK_CONN2PRIV((lc)) < LDAP_BACK_PCONN_BIND) \ + && (LDAP_BACK_CONN2PRIV((lc)) >= LDAP_BACK_PCONN_ANON)) +#define LDAP_BACK_PCONN_ISBIND(lc) (LDAP_BACK_PCONN_ISPRIV((lc)) \ + && (LDAP_BACK_CONN2PRIV((lc)) >= LDAP_BACK_PCONN_BIND)) +#define LDAP_BACK_PCONN_ISTLS(lc) (LDAP_BACK_PCONN_ISPRIV((lc)) \ + && (LDAP_BACK_CONN2PRIV((lc)) & LDAP_BACK_PCONN_TLS)) +#ifdef HAVE_TLS +#define LDAP_BACK_PCONN_ROOTDN_SET(lc, op) \ + ((lc)->lc_conn = (void *)((op)->o_conn->c_is_tls ? (void *) LDAP_BACK_PCONN_ROOTDN_TLS : (void *) LDAP_BACK_PCONN_ROOTDN)) +#define LDAP_BACK_PCONN_ANON_SET(lc, op) \ + ((lc)->lc_conn = (void *)((op)->o_conn->c_is_tls ? (void *) LDAP_BACK_PCONN_ANON_TLS : (void *) LDAP_BACK_PCONN_ANON)) +#define LDAP_BACK_PCONN_BIND_SET(lc, op) \ + ((lc)->lc_conn = (void *)((op)->o_conn->c_is_tls ? (void *) LDAP_BACK_PCONN_BIND_TLS : (void *) LDAP_BACK_PCONN_BIND)) +#else /* ! HAVE_TLS */ +#define LDAP_BACK_PCONN_ROOTDN_SET(lc, op) \ + ((lc)->lc_conn = (void *)LDAP_BACK_PCONN_ROOTDN) +#define LDAP_BACK_PCONN_ANON_SET(lc, op) \ + ((lc)->lc_conn = (void *)LDAP_BACK_PCONN_ANON) +#define LDAP_BACK_PCONN_BIND_SET(lc, op) \ + ((lc)->lc_conn = (void *)LDAP_BACK_PCONN_BIND) +#endif /* ! HAVE_TLS */ +#define LDAP_BACK_PCONN_SET(lc, op) \ + (BER_BVISEMPTY(&(op)->o_ndn) ? \ + LDAP_BACK_PCONN_ANON_SET((lc), (op)) : LDAP_BACK_PCONN_ROOTDN_SET((lc), (op))) + + struct ldapinfo_t *lcb_ldapinfo; + struct berval lcb_local_ndn; + unsigned lcb_refcnt; + time_t lcb_create_time; + time_t lcb_time; +} ldapconn_base_t; + +typedef struct ldapconn_t { + ldapconn_base_t lc_base; +#define lc_conn lc_base.lcb_conn +#define lc_ldapinfo lc_base.lcb_ldapinfo +#define lc_local_ndn lc_base.lcb_local_ndn +#define lc_refcnt lc_base.lcb_refcnt +#define lc_create_time lc_base.lcb_create_time +#define lc_time lc_base.lcb_time + + LDAP_TAILQ_ENTRY(ldapconn_t) lc_q; + + unsigned lc_lcflags; +#define LDAP_BACK_CONN_ISSET_F(fp,f) (*(fp) & (f)) +#define LDAP_BACK_CONN_SET_F(fp,f) (*(fp) |= (f)) +#define LDAP_BACK_CONN_CLEAR_F(fp,f) (*(fp) &= ~(f)) +#define LDAP_BACK_CONN_CPY_F(fp,f,mfp) \ + do { \ + if ( ((f) & *(mfp)) == (f) ) { \ + *(fp) |= (f); \ + } else { \ + *(fp) &= ~(f); \ + } \ + } while ( 0 ) + +#define LDAP_BACK_CONN_ISSET(lc,f) LDAP_BACK_CONN_ISSET_F(&(lc)->lc_lcflags, (f)) +#define LDAP_BACK_CONN_SET(lc,f) LDAP_BACK_CONN_SET_F(&(lc)->lc_lcflags, (f)) +#define LDAP_BACK_CONN_CLEAR(lc,f) LDAP_BACK_CONN_CLEAR_F(&(lc)->lc_lcflags, (f)) +#define LDAP_BACK_CONN_CPY(lc,f,mlc) LDAP_BACK_CONN_CPY_F(&(lc)->lc_lcflags, (f), &(mlc)->lc_lcflags) + +/* 0xFFF00000U are reserved for back-meta */ + +#define LDAP_BACK_FCONN_ISBOUND (0x00000001U) +#define LDAP_BACK_FCONN_ISANON (0x00000002U) +#define LDAP_BACK_FCONN_ISBMASK (LDAP_BACK_FCONN_ISBOUND|LDAP_BACK_FCONN_ISANON) +#define LDAP_BACK_FCONN_ISPRIV (0x00000004U) +#define LDAP_BACK_FCONN_ISTLS (0x00000008U) +#define LDAP_BACK_FCONN_BINDING (0x00000010U) +#define LDAP_BACK_FCONN_TAINTED (0x00000020U) +#define LDAP_BACK_FCONN_ABANDON (0x00000040U) +#define LDAP_BACK_FCONN_ISIDASR (0x00000080U) +#define LDAP_BACK_FCONN_CACHED (0x00000100U) + +#define LDAP_BACK_CONN_ISBOUND(lc) LDAP_BACK_CONN_ISSET((lc), LDAP_BACK_FCONN_ISBOUND) +#define LDAP_BACK_CONN_ISBOUND_SET(lc) LDAP_BACK_CONN_SET((lc), LDAP_BACK_FCONN_ISBOUND) +#define LDAP_BACK_CONN_ISBOUND_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), LDAP_BACK_FCONN_ISBMASK) +#define LDAP_BACK_CONN_ISBOUND_CPY(lc, mlc) LDAP_BACK_CONN_CPY((lc), LDAP_BACK_FCONN_ISBOUND, (mlc)) +#define LDAP_BACK_CONN_ISANON(lc) LDAP_BACK_CONN_ISSET((lc), LDAP_BACK_FCONN_ISANON) +#define LDAP_BACK_CONN_ISANON_SET(lc) LDAP_BACK_CONN_SET((lc), LDAP_BACK_FCONN_ISANON) +#define LDAP_BACK_CONN_ISANON_CLEAR(lc) LDAP_BACK_CONN_ISBOUND_CLEAR((lc)) +#define LDAP_BACK_CONN_ISANON_CPY(lc, mlc) LDAP_BACK_CONN_CPY((lc), LDAP_BACK_FCONN_ISANON, (mlc)) +#define LDAP_BACK_CONN_ISPRIV(lc) LDAP_BACK_CONN_ISSET((lc), LDAP_BACK_FCONN_ISPRIV) +#define LDAP_BACK_CONN_ISPRIV_SET(lc) LDAP_BACK_CONN_SET((lc), LDAP_BACK_FCONN_ISPRIV) +#define LDAP_BACK_CONN_ISPRIV_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), LDAP_BACK_FCONN_ISPRIV) +#define LDAP_BACK_CONN_ISPRIV_CPY(lc, mlc) LDAP_BACK_CONN_CPY((lc), LDAP_BACK_FCONN_ISPRIV, (mlc)) +#define LDAP_BACK_CONN_ISTLS(lc) LDAP_BACK_CONN_ISSET((lc), LDAP_BACK_FCONN_ISTLS) +#define LDAP_BACK_CONN_ISTLS_SET(lc) LDAP_BACK_CONN_SET((lc), LDAP_BACK_FCONN_ISTLS) +#define LDAP_BACK_CONN_ISTLS_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), LDAP_BACK_FCONN_ISTLS) +#define LDAP_BACK_CONN_ISTLS_CPY(lc, mlc) LDAP_BACK_CONN_CPY((lc), LDAP_BACK_FCONN_ISTLS, (mlc)) +#define LDAP_BACK_CONN_BINDING(lc) LDAP_BACK_CONN_ISSET((lc), LDAP_BACK_FCONN_BINDING) +#define LDAP_BACK_CONN_BINDING_SET(lc) LDAP_BACK_CONN_SET((lc), LDAP_BACK_FCONN_BINDING) +#define LDAP_BACK_CONN_BINDING_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), LDAP_BACK_FCONN_BINDING) +#define LDAP_BACK_CONN_TAINTED(lc) LDAP_BACK_CONN_ISSET((lc), LDAP_BACK_FCONN_TAINTED) +#define LDAP_BACK_CONN_TAINTED_SET(lc) LDAP_BACK_CONN_SET((lc), LDAP_BACK_FCONN_TAINTED) +#define LDAP_BACK_CONN_TAINTED_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), LDAP_BACK_FCONN_TAINTED) +#define LDAP_BACK_CONN_ABANDON(lc) LDAP_BACK_CONN_ISSET((lc), LDAP_BACK_FCONN_ABANDON) +#define LDAP_BACK_CONN_ABANDON_SET(lc) LDAP_BACK_CONN_SET((lc), LDAP_BACK_FCONN_ABANDON) +#define LDAP_BACK_CONN_ABANDON_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), LDAP_BACK_FCONN_ABANDON) +#define LDAP_BACK_CONN_ISIDASSERT(lc) LDAP_BACK_CONN_ISSET((lc), LDAP_BACK_FCONN_ISIDASR) +#define LDAP_BACK_CONN_ISIDASSERT_SET(lc) LDAP_BACK_CONN_SET((lc), LDAP_BACK_FCONN_ISIDASR) +#define LDAP_BACK_CONN_ISIDASSERT_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), LDAP_BACK_FCONN_ISIDASR) +#define LDAP_BACK_CONN_ISIDASSERT_CPY(lc, mlc) LDAP_BACK_CONN_CPY((lc), LDAP_BACK_FCONN_ISIDASR, (mlc)) +#define LDAP_BACK_CONN_CACHED(lc) LDAP_BACK_CONN_ISSET((lc), LDAP_BACK_FCONN_CACHED) +#define LDAP_BACK_CONN_CACHED_SET(lc) LDAP_BACK_CONN_SET((lc), LDAP_BACK_FCONN_CACHED) +#define LDAP_BACK_CONN_CACHED_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), LDAP_BACK_FCONN_CACHED) + + LDAP *lc_ld; + unsigned long lc_connid; + struct berval lc_cred; + struct berval lc_bound_ndn; + unsigned lc_flags; +} ldapconn_t; + +typedef struct ldap_avl_info_t { + ldap_pvt_thread_mutex_t lai_mutex; + TAvlnode *lai_tree; +} ldap_avl_info_t; + +typedef struct slap_retry_info_t { + time_t *ri_interval; + int *ri_num; + int ri_idx; + int ri_count; + time_t ri_last; + +#define SLAP_RETRYNUM_FOREVER (-1) /* retry forever */ +#define SLAP_RETRYNUM_TAIL (-2) /* end of retrynum array */ +#define SLAP_RETRYNUM_VALID(n) ((n) >= SLAP_RETRYNUM_FOREVER) /* valid retrynum */ +#define SLAP_RETRYNUM_FINITE(n) ((n) > SLAP_RETRYNUM_FOREVER) /* not forever */ +} slap_retry_info_t; + +/* + * identity assertion modes + */ +typedef enum { + LDAP_BACK_IDASSERT_LEGACY = 1, + LDAP_BACK_IDASSERT_NOASSERT, + LDAP_BACK_IDASSERT_ANONYMOUS, + LDAP_BACK_IDASSERT_SELF, + LDAP_BACK_IDASSERT_OTHERDN, + LDAP_BACK_IDASSERT_OTHERID +} slap_idassert_mode_t; + +/* ID assert stuff */ +typedef struct slap_idassert_t { + slap_idassert_mode_t si_mode; +#define li_idassert_mode li_idassert.si_mode + + slap_bindconf si_bc; +#define li_idassert_authcID li_idassert.si_bc.sb_authcId +#define li_idassert_authcDN li_idassert.si_bc.sb_binddn +#define li_idassert_passwd li_idassert.si_bc.sb_cred +#define li_idassert_authzID li_idassert.si_bc.sb_authzId +#define li_idassert_authmethod li_idassert.si_bc.sb_method +#define li_idassert_sasl_mech li_idassert.si_bc.sb_saslmech +#define li_idassert_sasl_realm li_idassert.si_bc.sb_realm +#define li_idassert_secprops li_idassert.si_bc.sb_secprops +#define li_idassert_tls li_idassert.si_bc.sb_tls + + unsigned si_flags; +#define LDAP_BACK_AUTH_NONE (0x00U) +#define LDAP_BACK_AUTH_NATIVE_AUTHZ (0x01U) +#define LDAP_BACK_AUTH_OVERRIDE (0x02U) +#define LDAP_BACK_AUTH_PRESCRIPTIVE (0x04U) +#define LDAP_BACK_AUTH_OBSOLETE_PROXY_AUTHZ (0x08U) +#define LDAP_BACK_AUTH_OBSOLETE_ENCODING_WORKAROUND (0x10U) +#define LDAP_BACK_AUTH_AUTHZ_ALL (0x20U) +#define LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL (0x40U) +#define LDAP_BACK_AUTH_DN_AUTHZID (0x100U) +#define LDAP_BACK_AUTH_DN_WHOAMI (0x200U) +#define LDAP_BACK_AUTH_DN_MASK (LDAP_BACK_AUTH_DN_AUTHZID|LDAP_BACK_AUTH_DN_WHOAMI) +#define li_idassert_flags li_idassert.si_flags + + BerVarray si_authz; +#define li_idassert_authz li_idassert.si_authz + + BerVarray si_passthru; +#define li_idassert_passthru li_idassert.si_passthru +} slap_idassert_t; + +/* + * Hook to allow mucking with ldapinfo_t when quarantine is over + */ +typedef int (*ldap_back_quarantine_f)( struct ldapinfo_t *, void * ); + +typedef struct ldapinfo_t { + /* li_uri: the string that goes into ldap_initialize() + * TODO: use li_acl.sb_uri instead */ + char *li_uri; + /* li_bvuri: an array of each single URI that is equivalent; + * to be checked for the presence of a certain item */ + BerVarray li_bvuri; + ldap_pvt_thread_mutex_t li_uri_mutex; + /* hack because when TLS is used we need to lock and let + * the li_urllist_f function to know it's locked */ + int li_uri_mutex_do_not_lock; + + LDAP_REBIND_PROC *li_rebind_f; + LDAP_URLLIST_PROC *li_urllist_f; + void *li_urllist_p; + + /* we only care about the TLS options here */ + slap_bindconf li_tls; + + slap_bindconf li_acl; +#define li_acl_authcID li_acl.sb_authcId +#define li_acl_authcDN li_acl.sb_binddn +#define li_acl_passwd li_acl.sb_cred +#define li_acl_authzID li_acl.sb_authzId +#define li_acl_authmethod li_acl.sb_method +#define li_acl_sasl_mech li_acl.sb_saslmech +#define li_acl_sasl_realm li_acl.sb_realm +#define li_acl_secprops li_acl.sb_secprops + + /* ID assert stuff */ + slap_idassert_t li_idassert; + /* end of ID assert stuff */ + + int li_nretries; +#define LDAP_BACK_RETRY_UNDEFINED (-2) +#define LDAP_BACK_RETRY_FOREVER (-1) +#define LDAP_BACK_RETRY_NEVER (0) +#define LDAP_BACK_RETRY_DEFAULT (3) + + unsigned li_flags; + +/* 0xFF000000U are reserved for back-meta */ + +#define LDAP_BACK_F_NONE (0x00000000U) +#define LDAP_BACK_F_SAVECRED (0x00000001U) +#define LDAP_BACK_F_USE_TLS (0x00000002U) +#define LDAP_BACK_F_PROPAGATE_TLS (0x00000004U) +#define LDAP_BACK_F_TLS_CRITICAL (0x00000008U) +#define LDAP_BACK_F_TLS_LDAPS (0x00000010U) + +#define LDAP_BACK_F_TLS_USE_MASK (LDAP_BACK_F_USE_TLS|LDAP_BACK_F_TLS_CRITICAL) +#define LDAP_BACK_F_TLS_PROPAGATE_MASK (LDAP_BACK_F_PROPAGATE_TLS|LDAP_BACK_F_TLS_CRITICAL) +#define LDAP_BACK_F_TLS_MASK (LDAP_BACK_F_TLS_USE_MASK|LDAP_BACK_F_TLS_PROPAGATE_MASK|LDAP_BACK_F_TLS_LDAPS) +#define LDAP_BACK_F_CHASE_REFERRALS (0x00000020U) +#define LDAP_BACK_F_PROXY_WHOAMI (0x00000040U) + +#define LDAP_BACK_F_T_F (0x00000080U) +#define LDAP_BACK_F_T_F_DISCOVER (0x00000100U) +#define LDAP_BACK_F_T_F_MASK (LDAP_BACK_F_T_F) +#define LDAP_BACK_F_T_F_MASK2 (LDAP_BACK_F_T_F_MASK|LDAP_BACK_F_T_F_DISCOVER) + +#define LDAP_BACK_F_MONITOR (0x00000200U) +#define LDAP_BACK_F_SINGLECONN (0x00000400U) +#define LDAP_BACK_F_USE_TEMPORARIES (0x00000800U) + +#define LDAP_BACK_F_ISOPEN (0x00001000U) + +#define LDAP_BACK_F_CANCEL_ABANDON (0x00000000U) +#define LDAP_BACK_F_CANCEL_IGNORE (0x00002000U) +#define LDAP_BACK_F_CANCEL_EXOP (0x00004000U) +#define LDAP_BACK_F_CANCEL_EXOP_DISCOVER (0x00008000U) +#define LDAP_BACK_F_CANCEL_MASK (LDAP_BACK_F_CANCEL_IGNORE|LDAP_BACK_F_CANCEL_EXOP) +#define LDAP_BACK_F_CANCEL_MASK2 (LDAP_BACK_F_CANCEL_MASK|LDAP_BACK_F_CANCEL_EXOP_DISCOVER) + +#define LDAP_BACK_F_QUARANTINE (0x00010000U) + +#ifdef SLAP_CONTROL_X_SESSION_TRACKING +#define LDAP_BACK_F_ST_REQUEST (0x00020000U) +#define LDAP_BACK_F_ST_RESPONSE (0x00040000U) +#endif /* SLAP_CONTROL_X_SESSION_TRACKING */ + +#define LDAP_BACK_F_NOREFS (0x00080000U) +#define LDAP_BACK_F_NOUNDEFFILTER (0x00100000U) +#define LDAP_BACK_F_OMIT_UNKNOWN_SCHEMA (0x00200000U) + +#define LDAP_BACK_F_ONERR_STOP (0x00400000U) + +#define LDAP_BACK_ISSET_F(ff,f) ( ( (ff) & (f) ) == (f) ) +#define LDAP_BACK_ISMASK_F(ff,m,f) ( ( (ff) & (m) ) == (f) ) + +#define LDAP_BACK_ISSET(li,f) LDAP_BACK_ISSET_F( (li)->li_flags, (f) ) +#define LDAP_BACK_ISMASK(li,m,f) LDAP_BACK_ISMASK_F( (li)->li_flags, (m), (f) ) + +#define LDAP_BACK_SAVECRED(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_SAVECRED ) +#define LDAP_BACK_USE_TLS(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_USE_TLS ) +#define LDAP_BACK_PROPAGATE_TLS(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_PROPAGATE_TLS ) +#define LDAP_BACK_TLS_CRITICAL(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_TLS_CRITICAL ) +#define LDAP_BACK_CHASE_REFERRALS(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_CHASE_REFERRALS ) +#define LDAP_BACK_PROXY_WHOAMI(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_PROXY_WHOAMI ) + +#define LDAP_BACK_USE_TLS_F(ff) LDAP_BACK_ISSET_F( (ff), LDAP_BACK_F_USE_TLS ) +#define LDAP_BACK_PROPAGATE_TLS_F(ff) LDAP_BACK_ISSET_F( (ff), LDAP_BACK_F_PROPAGATE_TLS ) +#define LDAP_BACK_TLS_CRITICAL_F(ff) LDAP_BACK_ISSET_F( (ff), LDAP_BACK_F_TLS_CRITICAL ) + +#define LDAP_BACK_T_F(li) LDAP_BACK_ISMASK( (li), LDAP_BACK_F_T_F_MASK, LDAP_BACK_F_T_F ) +#define LDAP_BACK_T_F_DISCOVER(li) LDAP_BACK_ISMASK( (li), LDAP_BACK_F_T_F_MASK2, LDAP_BACK_F_T_F_DISCOVER ) + +#define LDAP_BACK_MONITOR(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_MONITOR ) +#define LDAP_BACK_SINGLECONN(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_SINGLECONN ) +#define LDAP_BACK_USE_TEMPORARIES(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_USE_TEMPORARIES) + +#define LDAP_BACK_ISOPEN(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_ISOPEN ) + +#define LDAP_BACK_ABANDON(li) LDAP_BACK_ISMASK( (li), LDAP_BACK_F_CANCEL_MASK, LDAP_BACK_F_CANCEL_ABANDON ) +#define LDAP_BACK_IGNORE(li) LDAP_BACK_ISMASK( (li), LDAP_BACK_F_CANCEL_MASK, LDAP_BACK_F_CANCEL_IGNORE ) +#define LDAP_BACK_CANCEL(li) LDAP_BACK_ISMASK( (li), LDAP_BACK_F_CANCEL_MASK, LDAP_BACK_F_CANCEL_EXOP ) +#define LDAP_BACK_CANCEL_DISCOVER(li) LDAP_BACK_ISMASK( (li), LDAP_BACK_F_CANCEL_MASK2, LDAP_BACK_F_CANCEL_EXOP_DISCOVER ) + +#define LDAP_BACK_QUARANTINE(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_QUARANTINE ) + +#ifdef SLAP_CONTROL_X_SESSION_TRACKING +#define LDAP_BACK_ST_REQUEST(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_ST_REQUEST) +#define LDAP_BACK_ST_RESPONSE(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_ST_RESPONSE) +#endif /* SLAP_CONTROL_X_SESSION_TRACKING */ + +#define LDAP_BACK_NOREFS(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_NOREFS) +#define LDAP_BACK_NOUNDEFFILTER(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_NOUNDEFFILTER) +#define LDAP_BACK_OMIT_UNKNOWN_SCHEMA(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_OMIT_UNKNOWN_SCHEMA) +#define LDAP_BACK_ONERR_STOP(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_ONERR_STOP) + + int li_version; + + unsigned long li_conn_nextid; + + /* cached connections; + * special conns are in tailq rather than in tree */ + ldap_avl_info_t li_conninfo; + struct { + int lic_num; + LDAP_TAILQ_HEAD(lc_conn_priv_q, ldapconn_t) lic_priv; + } li_conn_priv[ LDAP_BACK_PCONN_LAST ]; + int li_conn_priv_max; +#define LDAP_BACK_CONN_PRIV_MIN (1) +#define LDAP_BACK_CONN_PRIV_MAX (256) + /* must be between LDAP_BACK_CONN_PRIV_MIN + * and LDAP_BACK_CONN_PRIV_MAX ! */ +#define LDAP_BACK_CONN_PRIV_DEFAULT (16) + + ldap_monitor_info_t li_monitor_info; + + sig_atomic_t li_isquarantined; +#define LDAP_BACK_FQ_NO (0) +#define LDAP_BACK_FQ_YES (1) +#define LDAP_BACK_FQ_RETRYING (2) + + slap_retry_info_t li_quarantine; + ldap_pvt_thread_mutex_t li_quarantine_mutex; + ldap_back_quarantine_f li_quarantine_f; + void *li_quarantine_p; + + time_t li_network_timeout; + time_t li_conn_ttl; + time_t li_idle_timeout; + time_t li_timeout[ SLAP_OP_LAST ]; + + ldap_pvt_thread_mutex_t li_counter_mutex; + ldap_pvt_mp_t li_ops_completed[SLAP_OP_LAST]; + struct re_s* li_conn_expire_task; +} ldapinfo_t; + +#define LDAP_ERR_OK(err) ((err) == LDAP_SUCCESS || (err) == LDAP_COMPARE_FALSE || (err) == LDAP_COMPARE_TRUE) + +typedef enum ldap_back_send_t { + LDAP_BACK_DONTSEND = 0x00, + LDAP_BACK_SENDOK = 0x01, + LDAP_BACK_SENDERR = 0x02, + LDAP_BACK_SENDRESULT = (LDAP_BACK_SENDOK|LDAP_BACK_SENDERR), + LDAP_BACK_BINDING = 0x04, + + LDAP_BACK_BIND_DONTSEND = (LDAP_BACK_BINDING), + LDAP_BACK_BIND_SOK = (LDAP_BACK_BINDING|LDAP_BACK_SENDOK), + LDAP_BACK_BIND_SERR = (LDAP_BACK_BINDING|LDAP_BACK_SENDERR), + LDAP_BACK_BIND_SRES = (LDAP_BACK_BINDING|LDAP_BACK_SENDRESULT), + + LDAP_BACK_RETRYING = 0x08, + LDAP_BACK_RETRY_DONTSEND = (LDAP_BACK_RETRYING), + LDAP_BACK_RETRY_SOK = (LDAP_BACK_RETRYING|LDAP_BACK_SENDOK), + LDAP_BACK_RETRY_SERR = (LDAP_BACK_RETRYING|LDAP_BACK_SENDERR), + LDAP_BACK_RETRY_SRES = (LDAP_BACK_RETRYING|LDAP_BACK_SENDRESULT), + + LDAP_BACK_GETCONN = 0x10 +} ldap_back_send_t; + +/* define to use asynchronous StartTLS */ +#define SLAP_STARTTLS_ASYNCHRONOUS + +/* timeout to use when calling ldap_result() */ +#define LDAP_BACK_RESULT_TIMEOUT (0) +#define LDAP_BACK_RESULT_UTIMEOUT (100000) +#define LDAP_BACK_TV_SET(tv) \ + do { \ + (tv)->tv_sec = LDAP_BACK_RESULT_TIMEOUT; \ + (tv)->tv_usec = LDAP_BACK_RESULT_UTIMEOUT; \ + } while ( 0 ) + +#ifndef LDAP_BACK_PRINT_CONNTREE +#define LDAP_BACK_PRINT_CONNTREE 0 +#endif /* !LDAP_BACK_PRINT_CONNTREE */ + +typedef struct ldap_extra_t { + int (*proxy_authz_ctrl)( Operation *op, SlapReply *rs, struct berval *bound_ndn, + int version, slap_idassert_t *si, LDAPControl *ctrl ); + int (*controls_free)( Operation *op, SlapReply *rs, LDAPControl ***pctrls ); + int (*idassert_authzfrom_parse)( struct config_args_s *ca, slap_idassert_t *si ); + int (*idassert_passthru_parse_cf)( const char *fname, int lineno, const char *arg, slap_idassert_t *si ); + int (*idassert_parse)( struct config_args_s *ca, slap_idassert_t *si ); + void (*retry_info_destroy)( slap_retry_info_t *ri ); + int (*retry_info_parse)( char *in, slap_retry_info_t *ri, char *buf, ber_len_t buflen ); + int (*retry_info_unparse)( slap_retry_info_t *ri, struct berval *bvout ); + int (*connid2str)( const ldapconn_base_t *lc, char *buf, ber_len_t buflen ); +} ldap_extra_t; + +LDAP_END_DECL + +#include "proto-ldap.h" + +#endif /* SLAPD_LDAP_H */ diff --git a/servers/slapd/back-ldap/bind.c b/servers/slapd/back-ldap/bind.c new file mode 100644 index 0000000..2da66c4 --- /dev/null +++ b/servers/slapd/back-ldap/bind.c @@ -0,0 +1,3204 @@ +/* bind.c - ldap backend bind function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2022 The OpenLDAP Foundation. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * 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>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/errno.h> +#include <ac/socket.h> +#include <ac/string.h> + +#define AVL_INTERNAL +#include "slap.h" +#include "back-ldap.h" +#include "lutil.h" +#include "lutil_ldap.h" +#include "ldap_rq.h" + +#define LDAP_CONTROL_OBSOLETE_PROXY_AUTHZ "2.16.840.1.113730.3.4.12" + +#ifdef LDAP_DEVEL +#define SLAP_AUTH_DN 1 +#endif + +#if LDAP_BACK_PRINT_CONNTREE > 0 + +static const struct { + slap_mask_t f; + char c; +} flagsmap[] = { + { LDAP_BACK_FCONN_ISBOUND, 'B' }, + { LDAP_BACK_FCONN_ISANON, 'A' }, + { LDAP_BACK_FCONN_ISPRIV, 'P' }, + { LDAP_BACK_FCONN_ISTLS, 'T' }, + { LDAP_BACK_FCONN_BINDING, 'X' }, + { LDAP_BACK_FCONN_TAINTED, 'E' }, + { LDAP_BACK_FCONN_ABANDON, 'N' }, + { LDAP_BACK_FCONN_ISIDASR, 'S' }, + { LDAP_BACK_FCONN_CACHED, 'C' }, + { 0, '\0' } +}; + +static void +ldap_back_conn_print( ldapconn_t *lc ) +{ + char buf[ SLAP_TEXT_BUFLEN ]; + char fbuf[ sizeof("BAPTIENSC") ]; + int i; + + ldap_back_conn2str( &lc->lc_base, buf, sizeof( buf ) ); + for ( i = 0; flagsmap[ i ].c != '\0'; i++ ) { + if ( lc->lc_lcflags & flagsmap[i].f ) { + fbuf[i] = flagsmap[i].c; + + } else { + fbuf[i] = '.'; + } + } + fbuf[i] = '\0'; + + fprintf( stderr, "lc=%p %s flags=0x%08x (%s)\n", + (void *)lc, buf, lc->lc_lcflags, fbuf ); +} + + +static char* priv2str[] = { + "privileged", + "privileged/TLS", + "anonymous", + "anonymous/TLS", + "bind", + "bind/TLS", + NULL +}; + +void +ldap_back_print_conntree( ldapinfo_t *li, char *msg ) +{ + int c; + + fprintf( stderr, "========> %s\n", msg ); + + for ( c = LDAP_BACK_PCONN_FIRST; c < LDAP_BACK_PCONN_LAST; c++ ) { + int i = 0; + ldapconn_t *lc; + + fprintf( stderr, " %s[%d]\n", priv2str[ c ], li->li_conn_priv[ c ].lic_num ); + + LDAP_TAILQ_FOREACH( lc, &li->li_conn_priv[ c ].lic_priv, lc_q ) + { + fprintf( stderr, " [%d] ", i ); + ldap_back_conn_print( lc ); + i++; + } + } + + if ( li->li_conninfo.lai_tree == 0 ) { + fprintf( stderr, "\t(empty)\n" ); + + } else { + TAvlnode *edge = ldap_tavl_end( li->li_conninfo.lai_tree, TAVL_DIR_LEFT ); + while ( edge ) { + ldap_back_conn_print( (ldapconn_t *)edge->avl_data ); + edge = ldap_tavl_next( edge, TAVL_DIR_RIGHT ); + } + } + + fprintf( stderr, "<======== %s\n", msg ); +} +#endif /* LDAP_BACK_PRINT_CONNTREE */ + +static int +ldap_back_freeconn( ldapinfo_t *li, ldapconn_t *lc, int dolock ); + +static ldapconn_t * +ldap_back_getconn( Operation *op, SlapReply *rs, ldap_back_send_t sendok, + struct berval *binddn, struct berval *bindcred ); + +static int +ldap_back_is_proxy_authz( Operation *op, SlapReply *rs, ldap_back_send_t sendok, + struct berval *binddn, struct berval *bindcred ); + +static int +ldap_back_proxy_authz_bind( ldapconn_t *lc, Operation *op, SlapReply *rs, + ldap_back_send_t sendok, struct berval *binddn, struct berval *bindcred ); + +static int +ldap_back_prepare_conn( ldapconn_t *lc, Operation *op, SlapReply *rs, + ldap_back_send_t sendok ); + +static int +ldap_back_conndnlc_cmp( const void *c1, const void *c2 ); + +static void +ldap_back_conn_prune( ldapinfo_t *li ); + +static void +ldap_back_schedule_conn_expiry( ldapinfo_t *li, ldapconn_t *lc ); + +ldapconn_t * +ldap_back_conn_delete( ldapinfo_t *li, ldapconn_t *lc ) +{ + if ( LDAP_BACK_PCONN_ISPRIV( lc ) ) { + if ( LDAP_BACK_CONN_CACHED( lc ) ) { + assert( lc->lc_q.tqe_prev != NULL ); + assert( li->li_conn_priv[ LDAP_BACK_CONN2PRIV( lc ) ].lic_num > 0 ); + li->li_conn_priv[ LDAP_BACK_CONN2PRIV( lc ) ].lic_num--; + LDAP_TAILQ_REMOVE( &li->li_conn_priv[ LDAP_BACK_CONN2PRIV( lc ) ].lic_priv, lc, lc_q ); + LDAP_TAILQ_ENTRY_INIT( lc, lc_q ); + LDAP_BACK_CONN_CACHED_CLEAR( lc ); + + } else { + assert( LDAP_BACK_CONN_TAINTED( lc ) ); + assert( lc->lc_q.tqe_prev == NULL ); + } + + } else { + ldapconn_t *tmplc = NULL; + + if ( LDAP_BACK_CONN_CACHED( lc ) ) { + assert( !LDAP_BACK_CONN_TAINTED( lc ) ); + tmplc = ldap_tavl_delete( &li->li_conninfo.lai_tree, (caddr_t)lc, + ldap_back_conndnlc_cmp ); + assert( tmplc == lc ); + LDAP_BACK_CONN_CACHED_CLEAR( lc ); + } + + assert( LDAP_BACK_CONN_TAINTED( lc ) || tmplc == lc ); + } + + return lc; +} + +int +ldap_back_bind( Operation *op, SlapReply *rs ) +{ + ldapinfo_t *li = (ldapinfo_t *) op->o_bd->be_private; + ldapconn_t *lc; + + LDAPControl **ctrls = NULL; + struct berval save_o_dn; + int save_o_do_not_cache, + rc = 0; + ber_int_t msgid; + ldap_back_send_t retrying = LDAP_BACK_RETRYING; + + /* allow rootdn as a means to auth without the need to actually + * contact the proxied DSA */ + switch ( be_rootdn_bind( op, rs ) ) { + case SLAP_CB_CONTINUE: + break; + + default: + return rs->sr_err; + } + + lc = ldap_back_getconn( op, rs, LDAP_BACK_BIND_SERR, NULL, NULL ); + if ( !lc ) { + return rs->sr_err; + } + + /* we can do (almost) whatever we want with this conn, + * because either it's temporary, or it's marked as binding */ + if ( !BER_BVISNULL( &lc->lc_bound_ndn ) ) { + ch_free( lc->lc_bound_ndn.bv_val ); + BER_BVZERO( &lc->lc_bound_ndn ); + } + if ( !BER_BVISNULL( &lc->lc_cred ) ) { + memset( lc->lc_cred.bv_val, 0, lc->lc_cred.bv_len ); + ch_free( lc->lc_cred.bv_val ); + BER_BVZERO( &lc->lc_cred ); + } + LDAP_BACK_CONN_ISBOUND_CLEAR( lc ); + + /* don't add proxyAuthz; set the bindDN */ + save_o_dn = op->o_dn; + save_o_do_not_cache = op->o_do_not_cache; + op->o_dn = op->o_req_dn; + op->o_do_not_cache = 1; + + ctrls = op->o_ctrls; + rc = ldap_back_controls_add( op, rs, lc, &ctrls ); + op->o_dn = save_o_dn; + op->o_do_not_cache = save_o_do_not_cache; + if ( rc != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + ldap_back_release_conn( li, lc ); + return( rc ); + } + +retry:; + /* method is always LDAP_AUTH_SIMPLE if we got here */ + rs->sr_err = ldap_sasl_bind( lc->lc_ld, op->o_req_dn.bv_val, + LDAP_SASL_SIMPLE, + &op->orb_cred, ctrls, NULL, &msgid ); + /* FIXME: should we always retry, or only when piping the bind + * in the "override" connection pool? */ + rc = ldap_back_op_result( lc, op, rs, msgid, + li->li_timeout[ SLAP_OP_BIND ], + LDAP_BACK_BIND_SERR | retrying ); + if ( rc == LDAP_UNAVAILABLE && retrying ) { + retrying &= ~LDAP_BACK_RETRYING; + if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_BIND_SERR ) ) { + goto retry; + } + if ( !lc ) + return( rc ); + } + + ldap_pvt_thread_mutex_lock( &li->li_counter_mutex ); + ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_BIND ], 1 ); + ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex ); + + ldap_back_controls_free( op, rs, &ctrls ); + + if ( rc == LDAP_SUCCESS ) { + op->o_conn->c_authz_cookie = op->o_bd->be_private; + + /* If defined, proxyAuthz will be used also when + * back-ldap is the authorizing backend; for this + * purpose, after a successful bind the connection + * is left for further binds, and further operations + * on this client connection will use a default + * connection with identity assertion */ + /* NOTE: use with care */ + if ( li->li_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) { + ldap_back_release_conn( li, lc ); + return( rc ); + } + + /* rebind is now done inside ldap_back_proxy_authz_bind() + * in case of success */ + LDAP_BACK_CONN_ISBOUND_SET( lc ); + ber_dupbv( &lc->lc_bound_ndn, &op->o_req_ndn ); + + if ( !BER_BVISNULL( &lc->lc_cred ) ) { + memset( lc->lc_cred.bv_val, 0, + lc->lc_cred.bv_len ); + } + + if ( LDAP_BACK_SAVECRED( li ) ) { + ber_bvreplace( &lc->lc_cred, &op->orb_cred ); + ldap_set_rebind_proc( lc->lc_ld, li->li_rebind_f, lc ); + + } else { + lc->lc_cred.bv_len = 0; + } + } + + /* must re-insert if local DN changed as result of bind */ + if ( !LDAP_BACK_CONN_ISBOUND( lc ) + || ( !dn_match( &op->o_req_ndn, &lc->lc_local_ndn ) + && !LDAP_BACK_PCONN_ISPRIV( lc ) ) ) + { + int lerr = -1; + ldapconn_t *tmplc; + + /* wait for all other ops to release the connection */ +retry_lock:; + ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex ); + if ( lc->lc_refcnt > 1 ) { + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + ldap_pvt_thread_yield(); + goto retry_lock; + } + +#if LDAP_BACK_PRINT_CONNTREE > 0 + ldap_back_print_conntree( li, ">>> ldap_back_bind" ); +#endif /* LDAP_BACK_PRINT_CONNTREE */ + + assert( lc->lc_refcnt == 1 ); + ldap_back_conn_delete( li, lc ); + + /* delete all cached connections with the current connection */ + if ( LDAP_BACK_SINGLECONN( li ) ) { + while ( ( tmplc = ldap_tavl_delete( &li->li_conninfo.lai_tree, (caddr_t)lc, ldap_back_conn_cmp ) ) != NULL ) + { + assert( !LDAP_BACK_PCONN_ISPRIV( lc ) ); + Debug( LDAP_DEBUG_TRACE, + "=>ldap_back_bind: destroying conn %lu (refcnt=%u)\n", + lc->lc_conn->c_connid, lc->lc_refcnt ); + + if ( tmplc->lc_refcnt != 0 ) { + /* taint it */ + LDAP_BACK_CONN_TAINTED_SET( tmplc ); + LDAP_BACK_CONN_CACHED_CLEAR( tmplc ); + + } else { + /* + * Needs a test because the handler may be corrupted, + * and calling ldap_unbind on a corrupted header results + * in a segmentation fault + */ + ldap_back_conn_free( tmplc ); + } + } + } + + if ( LDAP_BACK_CONN_ISBOUND( lc ) ) { + ber_bvreplace( &lc->lc_local_ndn, &op->o_req_ndn ); + if ( be_isroot_dn( op->o_bd, &op->o_req_ndn ) ) { + LDAP_BACK_PCONN_ROOTDN_SET( lc, op ); + } + lerr = ldap_tavl_insert( &li->li_conninfo.lai_tree, (caddr_t)lc, + ldap_back_conndn_cmp, ldap_back_conndn_dup ); + } + +#if LDAP_BACK_PRINT_CONNTREE > 0 + ldap_back_print_conntree( li, "<<< ldap_back_bind" ); +#endif /* LDAP_BACK_PRINT_CONNTREE */ + + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + switch ( lerr ) { + case 0: + LDAP_BACK_CONN_CACHED_SET( lc ); + break; + + case -1: + /* duplicate; someone else successfully bound + * on the same connection with the same identity; + * we can do this because lc_refcnt == 1 */ + ldap_back_conn_free( lc ); + lc = NULL; + } + } + + if ( lc != NULL ) { + ldap_back_release_conn( li, lc ); + } + + return( rc ); +} + +/* + * ldap_back_conndn_cmp + * + * compares two ldapconn_t based on the value of the conn pointer + * and of the local DN; used by avl stuff for insert, lookup + * and direct delete + */ +int +ldap_back_conndn_cmp( const void *c1, const void *c2 ) +{ + const ldapconn_t *lc1 = (const ldapconn_t *)c1; + const ldapconn_t *lc2 = (const ldapconn_t *)c2; + int rc; + + /* If local DNs don't match, it is definitely not a match */ + /* For shared sessions, conn is NULL. Only explicitly + * bound sessions will have non-NULL conn. + */ + rc = SLAP_PTRCMP( lc1->lc_conn, lc2->lc_conn ); + if ( rc == 0 ) { + rc = ber_bvcmp( &lc1->lc_local_ndn, &lc2->lc_local_ndn ); + } + + return rc; +} + +/* + * ldap_back_conndnlc_cmp + * + * compares two ldapconn_t based on the value of the conn pointer, + * the local DN and the lc pointer; used by avl stuff for insert, lookup + * and direct delete + */ +static int +ldap_back_conndnlc_cmp( const void *c1, const void *c2 ) +{ + const ldapconn_t *lc1 = (const ldapconn_t *)c1; + const ldapconn_t *lc2 = (const ldapconn_t *)c2; + int rc; + + /* If local DNs don't match, it is definitely not a match */ + /* For shared sessions, conn is NULL. Only explicitly + * bound sessions will have non-NULL conn. + */ + rc = SLAP_PTRCMP( lc1->lc_conn, lc2->lc_conn ); + if ( rc == 0 ) { + rc = ber_bvcmp( &lc1->lc_local_ndn, &lc2->lc_local_ndn ); + if ( rc == 0 ) { + rc = SLAP_PTRCMP( lc1, lc2 ); + } + } + + return rc; +} + +/* + * ldap_back_conn_cmp + * + * compares two ldapconn_t based on the value of the conn pointer; + * used by avl stuff for delete of all conns with the same connid + */ +int +ldap_back_conn_cmp( const void *c1, const void *c2 ) +{ + const ldapconn_t *lc1 = (const ldapconn_t *)c1; + const ldapconn_t *lc2 = (const ldapconn_t *)c2; + + /* For shared sessions, conn is NULL. Only explicitly + * bound sessions will have non-NULL conn. + */ + return SLAP_PTRCMP( lc1->lc_conn, lc2->lc_conn ); +} + +/* + * ldap_back_conndn_dup + * + * returns -1 in case a duplicate ldapconn_t has been inserted; + * used by avl stuff + */ +int +ldap_back_conndn_dup( void *c1, void *c2 ) +{ + ldapconn_t *lc1 = (ldapconn_t *)c1; + ldapconn_t *lc2 = (ldapconn_t *)c2; + + /* Cannot have more than one shared session with same DN */ + if ( lc1->lc_conn == lc2->lc_conn && + dn_match( &lc1->lc_local_ndn, &lc2->lc_local_ndn ) ) + { + return -1; + } + + return 0; +} + +static int +ldap_back_freeconn( ldapinfo_t *li, ldapconn_t *lc, int dolock ) +{ + if ( dolock ) { + ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex ); + } + +#if LDAP_BACK_PRINT_CONNTREE > 0 + ldap_back_print_conntree( li, ">>> ldap_back_freeconn" ); +#endif /* LDAP_BACK_PRINT_CONNTREE */ + + (void)ldap_back_conn_delete( li, lc ); + + if ( lc->lc_refcnt == 0 ) { + ldap_back_conn_free( (void *)lc ); + } + +#if LDAP_BACK_PRINT_CONNTREE > 0 + ldap_back_print_conntree( li, "<<< ldap_back_freeconn" ); +#endif /* LDAP_BACK_PRINT_CONNTREE */ + + if ( dolock ) { + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + } + + return 0; +} + +#ifdef HAVE_TLS +static int +ldap_back_start_tls( + LDAP *ld, + int protocol, + int *is_tls, + const char *url, + unsigned flags, + int timeout, + const char **text ) +{ + int rc = LDAP_SUCCESS; + + /* start TLS ("tls-[try-]{start,propagate}" statements) */ + if ( ( LDAP_BACK_USE_TLS_F( flags ) || ( *is_tls && LDAP_BACK_PROPAGATE_TLS_F( flags ) ) ) + && !ldap_is_ldaps_url( url ) ) + { +#ifdef SLAP_STARTTLS_ASYNCHRONOUS + /* + * use asynchronous StartTLS + * in case, chase referral (not implemented yet) + */ + int msgid; + + if ( protocol == 0 ) { + ldap_get_option( ld, LDAP_OPT_PROTOCOL_VERSION, + (void *)&protocol ); + } + + if ( protocol < LDAP_VERSION3 ) { + /* we should rather bail out... */ + rc = LDAP_UNWILLING_TO_PERFORM; + *text = "invalid protocol version"; + } + + if ( rc == LDAP_SUCCESS ) { + rc = ldap_start_tls( ld, NULL, NULL, &msgid ); + } + + if ( rc == LDAP_SUCCESS ) { + LDAPMessage *res = NULL; + struct timeval tv; + + if ( timeout ) { + tv.tv_sec = timeout; + tv.tv_usec = 0; + } else { + LDAP_BACK_TV_SET( &tv ); + } + rc = ldap_result( ld, msgid, LDAP_MSG_ALL, &tv, &res ); + if ( rc <= 0 ) { + rc = LDAP_UNAVAILABLE; + + } else if ( rc == LDAP_RES_EXTENDED ) { + struct berval *data = NULL; + + rc = ldap_parse_extended_result( ld, res, + NULL, &data, 0 ); + if ( rc == LDAP_SUCCESS ) { + SlapReply rs; + rc = ldap_parse_result( ld, res, &rs.sr_err, + NULL, NULL, NULL, NULL, 1 ); + if ( rc != LDAP_SUCCESS ) { + rs.sr_err = rc; + } + rc = slap_map_api2result( &rs ); + res = NULL; + + /* FIXME: in case a referral + * is returned, should we try + * using it instead of the + * configured URI? */ + if ( rc == LDAP_SUCCESS ) { + rc = ldap_install_tls( ld ); + + } else if ( rc == LDAP_REFERRAL ) { + rc = LDAP_UNWILLING_TO_PERFORM; + *text = "unwilling to chase referral returned by Start TLS exop"; + } + + if ( data ) { + if ( data->bv_val ) { + ber_memfree( data->bv_val ); + } + ber_memfree( data ); + } + } + + } else { + rc = LDAP_OTHER; + } + + if ( res != NULL ) { + ldap_msgfree( res ); + } + } +#else /* ! SLAP_STARTTLS_ASYNCHRONOUS */ + /* + * use synchronous StartTLS + */ + rc = ldap_start_tls_s( ld, NULL, NULL ); +#endif /* ! SLAP_STARTTLS_ASYNCHRONOUS */ + + /* if StartTLS is requested, only attempt it if the URL + * is not "ldaps://"; this may occur not only in case + * of misconfiguration, but also when used in the chain + * overlay, where the "uri" can be parsed out of a referral */ + switch ( rc ) { + case LDAP_SUCCESS: + *is_tls = 1; + break; + + case LDAP_SERVER_DOWN: + break; + + default: + if ( LDAP_BACK_TLS_CRITICAL_F( flags ) ) { + *text = "could not start TLS"; + break; + } + + /* in case Start TLS is not critical */ + *is_tls = 0; + rc = LDAP_SUCCESS; + break; + } + + } else { + *is_tls = 0; + } + + return rc; +} +#endif /* HAVE_TLS */ + +static int +ldap_back_prepare_conn( ldapconn_t *lc, Operation *op, SlapReply *rs, ldap_back_send_t sendok ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + int version; + LDAP *ld = NULL; +#ifdef HAVE_TLS + int is_tls = op->o_conn->c_is_tls; + int flags = li->li_flags; + time_t lctime = (time_t)(-1); + slap_bindconf *sb; +#endif /* HAVE_TLS */ + + ldap_pvt_thread_mutex_lock( &li->li_uri_mutex ); + rs->sr_err = ldap_initialize( &ld, li->li_uri ); + ldap_pvt_thread_mutex_unlock( &li->li_uri_mutex ); + if ( rs->sr_err != LDAP_SUCCESS ) { + goto error_return; + } + + if ( li->li_urllist_f ) { + ldap_set_urllist_proc( ld, li->li_urllist_f, li->li_urllist_p ); + } + + /* Set LDAP version. This will always succeed: If the client + * bound with a particular version, then so can we. + */ + if ( li->li_version != 0 ) { + version = li->li_version; + + } else if ( op->o_protocol != 0 ) { + version = op->o_protocol; + + } else { + /* assume it's an internal op; set to LDAPv3 */ + version = LDAP_VERSION3; + } + ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, (const void *)&version ); + + /* automatically chase referrals ("chase-referrals [{yes|no}]" statement) */ + ldap_set_option( ld, LDAP_OPT_REFERRALS, + LDAP_BACK_CHASE_REFERRALS( li ) ? LDAP_OPT_ON : LDAP_OPT_OFF ); + + if ( li->li_network_timeout > 0 ) { + struct timeval tv; + + tv.tv_sec = li->li_network_timeout; + tv.tv_usec = 0; + ldap_set_option( ld, LDAP_OPT_NETWORK_TIMEOUT, (const void *)&tv ); + } + + /* turn on network keepalive, if configured so */ + slap_client_keepalive(ld, &li->li_tls.sb_keepalive); + + if ( li->li_tls.sb_tcp_user_timeout > 0 ) { + ldap_set_option( ld, LDAP_OPT_TCP_USER_TIMEOUT, + &li->li_tls.sb_tcp_user_timeout ); + } + +#ifdef HAVE_TLS + if ( LDAP_BACK_CONN_ISPRIV( lc ) ) { + /* See "rationale" comment in ldap_back_getconn() */ + if ( li->li_acl_authmethod == LDAP_AUTH_NONE && + li->li_idassert_authmethod != LDAP_AUTH_NONE ) + sb = &li->li_idassert.si_bc; + else + sb = &li->li_acl; + + } else if ( LDAP_BACK_CONN_ISIDASSERT( lc ) ) { + sb = &li->li_idassert.si_bc; + + } else { + sb = &li->li_tls; + } + + bindconf_tls_set( sb, ld ); + + /* if required by the bindconf configuration, force TLS */ + if ( ( sb == &li->li_acl || sb == &li->li_idassert.si_bc ) && + sb->sb_tls_ctx ) + { + flags |= LDAP_BACK_F_USE_TLS; + } + + ldap_pvt_thread_mutex_lock( &li->li_uri_mutex ); + assert( li->li_uri_mutex_do_not_lock == 0 ); + li->li_uri_mutex_do_not_lock = 1; + rs->sr_err = ldap_back_start_tls( ld, op->o_protocol, &is_tls, + li->li_uri, flags, li->li_timeout[ SLAP_OP_BIND ], &rs->sr_text ); + li->li_uri_mutex_do_not_lock = 0; + ldap_pvt_thread_mutex_unlock( &li->li_uri_mutex ); + if ( rs->sr_err != LDAP_SUCCESS ) { + ldap_unbind_ext( ld, NULL, NULL ); + rs->sr_text = "Start TLS failed"; + goto error_return; + + } else if ( li->li_idle_timeout ) { + /* only touch when activity actually took place... */ + lctime = op->o_time; + } +#endif /* HAVE_TLS */ + + lc->lc_ld = ld; + lc->lc_refcnt = 1; +#ifdef HAVE_TLS + if ( is_tls ) { + LDAP_BACK_CONN_ISTLS_SET( lc ); + } else { + LDAP_BACK_CONN_ISTLS_CLEAR( lc ); + } + if ( lctime != (time_t)(-1) ) { + lc->lc_time = lctime; + } +#endif /* HAVE_TLS */ + +error_return:; + if ( rs->sr_err != LDAP_SUCCESS ) { + rs->sr_err = slap_map_api2result( rs ); + if ( sendok & LDAP_BACK_SENDERR ) { + if ( rs->sr_text == NULL ) { + rs->sr_text = "Proxy connection initialization failed"; + } + send_ldap_result( op, rs ); + } + + } else { + if ( li->li_conn_ttl > 0 ) { + lc->lc_create_time = op->o_time; + } + } + + return rs->sr_err; +} + +static ldapconn_t * +ldap_back_getconn( + Operation *op, + SlapReply *rs, + ldap_back_send_t sendok, + struct berval *binddn, + struct berval *bindcred ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + ldapconn_t *lc = NULL, + lc_curr = {{ 0 }}; + int refcnt = 1, + lookupconn = !( sendok & LDAP_BACK_BINDING ); + + /* if the server is quarantined, and + * - the current interval did not expire yet, or + * - no more retries should occur, + * don't return the connection */ + if ( li->li_isquarantined ) { + slap_retry_info_t *ri = &li->li_quarantine; + int dont_retry = 1; + + if ( li->li_quarantine.ri_interval ) { + ldap_pvt_thread_mutex_lock( &li->li_quarantine_mutex ); + if ( li->li_isquarantined == LDAP_BACK_FQ_YES ) { + dont_retry = ( ri->ri_num[ ri->ri_idx ] == SLAP_RETRYNUM_TAIL + || slap_get_time() < ri->ri_last + ri->ri_interval[ ri->ri_idx ] ); + if ( !dont_retry ) { + Debug( LDAP_DEBUG_ANY, + "%s: ldap_back_getconn quarantine " + "retry block #%d try #%d.\n", + op->o_log_prefix, ri->ri_idx, ri->ri_count ); + li->li_isquarantined = LDAP_BACK_FQ_RETRYING; + } + } + ldap_pvt_thread_mutex_unlock( &li->li_quarantine_mutex ); + } + + if ( dont_retry ) { + rs->sr_err = LDAP_UNAVAILABLE; + if ( op->o_conn && ( sendok & LDAP_BACK_SENDERR ) ) { + rs->sr_text = "Target is quarantined"; + send_ldap_result( op, rs ); + } + return NULL; + } + } + + /* Internal searches are privileged and shared. So is root. */ + if ( op->o_do_not_cache || be_isroot( op ) ) { + LDAP_BACK_CONN_ISPRIV_SET( &lc_curr ); + lc_curr.lc_local_ndn = op->o_bd->be_rootndn; + LDAP_BACK_PCONN_ROOTDN_SET( &lc_curr, op ); + + } else { + struct berval tmpbinddn, + tmpbindcred, + save_o_dn, + save_o_ndn; + int isproxyauthz; + + /* need cleanup */ + if ( binddn == NULL ) { + binddn = &tmpbinddn; + } + if ( bindcred == NULL ) { + bindcred = &tmpbindcred; + } + if ( op->o_tag == LDAP_REQ_BIND ) { + save_o_dn = op->o_dn; + save_o_ndn = op->o_ndn; + op->o_dn = op->o_req_dn; + op->o_ndn = op->o_req_ndn; + } + isproxyauthz = ldap_back_is_proxy_authz( op, rs, sendok, binddn, bindcred ); + if ( op->o_tag == LDAP_REQ_BIND ) { + op->o_dn = save_o_dn; + op->o_ndn = save_o_ndn; + } + if ( isproxyauthz == -1 ) { + return NULL; + } + + lc_curr.lc_local_ndn = op->o_ndn; + /* Explicit binds must not be shared; + * however, explicit binds are piped in a special connection + * when idassert is to occur with "override" set */ + if ( op->o_tag == LDAP_REQ_BIND && !isproxyauthz ) { + lc_curr.lc_conn = op->o_conn; + + } else { + if ( isproxyauthz && !( sendok & LDAP_BACK_BINDING ) ) { + lc_curr.lc_local_ndn = *binddn; + LDAP_BACK_PCONN_ROOTDN_SET( &lc_curr, op ); + LDAP_BACK_CONN_ISIDASSERT_SET( &lc_curr ); + + } else if ( isproxyauthz && ( li->li_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) ) { + lc_curr.lc_local_ndn = slap_empty_bv; + LDAP_BACK_PCONN_BIND_SET( &lc_curr, op ); + LDAP_BACK_CONN_ISIDASSERT_SET( &lc_curr ); + lookupconn = 1; + + } else if ( SLAP_IS_AUTHZ_BACKEND( op ) ) { + lc_curr.lc_conn = op->o_conn; + + } else { + LDAP_BACK_PCONN_ANON_SET( &lc_curr, op ); + } + } + } + + /* Explicit Bind requests always get their own conn */ + if ( lookupconn ) { +retry_lock: + ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex ); + if ( LDAP_BACK_PCONN_ISPRIV( &lc_curr ) ) { + /* lookup a conn that's not binding */ + LDAP_TAILQ_FOREACH( lc, + &li->li_conn_priv[ LDAP_BACK_CONN2PRIV( &lc_curr ) ].lic_priv, + lc_q ) + { + if ( !LDAP_BACK_CONN_BINDING( lc ) && lc->lc_refcnt == 0 ) { + break; + } + } + + if ( lc != NULL ) { + if ( lc != LDAP_TAILQ_LAST( &li->li_conn_priv[ LDAP_BACK_CONN2PRIV( lc ) ].lic_priv, + lc_conn_priv_q ) ) + { + LDAP_TAILQ_REMOVE( &li->li_conn_priv[ LDAP_BACK_CONN2PRIV( lc ) ].lic_priv, + lc, lc_q ); + LDAP_TAILQ_ENTRY_INIT( lc, lc_q ); + LDAP_TAILQ_INSERT_TAIL( &li->li_conn_priv[ LDAP_BACK_CONN2PRIV( lc ) ].lic_priv, + lc, lc_q ); + } + + } else if ( !LDAP_BACK_USE_TEMPORARIES( li ) + && li->li_conn_priv[ LDAP_BACK_CONN2PRIV( &lc_curr ) ].lic_num == li->li_conn_priv_max ) + { + lc = LDAP_TAILQ_FIRST( &li->li_conn_priv[ LDAP_BACK_CONN2PRIV( &lc_curr ) ].lic_priv ); + } + + } else { + + /* Searches for a ldapconn in the avl tree */ + lc = (ldapconn_t *)ldap_tavl_find( li->li_conninfo.lai_tree, + (caddr_t)&lc_curr, ldap_back_conndn_cmp ); + } + + if ( lc != NULL ) { + /* Don't reuse connections while they're still binding */ + if ( LDAP_BACK_CONN_BINDING( lc ) ) { + if ( !LDAP_BACK_USE_TEMPORARIES( li ) ) { + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + + ldap_pvt_thread_yield(); + goto retry_lock; + } + lc = NULL; + } + + if ( lc != NULL ) { + if ( op->o_tag == LDAP_REQ_BIND ) { + /* right now, this is the only possible case */ + assert( ( li->li_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) ); + LDAP_BACK_CONN_BINDING_SET( lc ); + } + + refcnt = ++lc->lc_refcnt; + } + } + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + } + + /* Looks like we didn't get a bind. Open a new session... */ + if ( lc == NULL ) { + lc = (ldapconn_t *)ch_calloc( 1, sizeof( ldapconn_t ) ); + lc->lc_flags = li->li_flags; + lc->lc_lcflags = lc_curr.lc_lcflags; + lc->lc_ldapinfo = li; + if ( ldap_back_prepare_conn( lc, op, rs, sendok ) != LDAP_SUCCESS ) { + ch_free( lc ); + return NULL; + } + + if ( sendok & LDAP_BACK_BINDING ) { + LDAP_BACK_CONN_BINDING_SET( lc ); + } + + lc->lc_conn = lc_curr.lc_conn; + ber_dupbv( &lc->lc_local_ndn, &lc_curr.lc_local_ndn ); + + /* + * the rationale is: connections as the rootdn are privileged, + * so li_acl is to be used; however, in some cases + * one already configured identity assertion with a highly + * privileged idassert_authcDN, so if li_acl is not configured + * and idassert is, use idassert instead. + * + * might change in the future, because it's preferable + * to make clear what identity is being used, since + * the only drawback is that one risks to configure + * the same identity twice... + */ + if ( LDAP_BACK_CONN_ISPRIV( &lc_curr ) ) { + if ( li->li_acl_authmethod == LDAP_AUTH_NONE && + li->li_idassert_authmethod != LDAP_AUTH_NONE ) { + ber_dupbv( &lc->lc_bound_ndn, &li->li_idassert_authcDN ); + ber_dupbv( &lc->lc_cred, &li->li_idassert_passwd ); + + } else { + ber_dupbv( &lc->lc_bound_ndn, &li->li_acl_authcDN ); + ber_dupbv( &lc->lc_cred, &li->li_acl_passwd ); + } + LDAP_BACK_CONN_ISPRIV_SET( lc ); + + } else if ( LDAP_BACK_CONN_ISIDASSERT( &lc_curr ) ) { + if ( !LDAP_BACK_PCONN_ISBIND( &lc_curr ) ) { + ber_dupbv( &lc->lc_bound_ndn, &li->li_idassert_authcDN ); + ber_dupbv( &lc->lc_cred, &li->li_idassert_passwd ); + } + LDAP_BACK_CONN_ISIDASSERT_SET( lc ); + + } else { + BER_BVZERO( &lc->lc_cred ); + BER_BVZERO( &lc->lc_bound_ndn ); + if ( !BER_BVISEMPTY( &op->o_ndn ) + && SLAP_IS_AUTHZ_BACKEND( op ) ) + { + ber_dupbv( &lc->lc_bound_ndn, &op->o_ndn ); + } + } + +#ifdef HAVE_TLS + /* if start TLS failed but it was not mandatory, + * check if the non-TLS connection was already + * in cache; in case, destroy the newly created + * connection and use the existing one */ + if ( LDAP_BACK_PCONN_ISTLS( lc ) + && !ldap_tls_inplace( lc->lc_ld ) ) + { + ldapconn_t *tmplc = NULL; + int idx = LDAP_BACK_CONN2PRIV( &lc_curr ) - 1; + + ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex ); + LDAP_TAILQ_FOREACH( tmplc, + &li->li_conn_priv[ idx ].lic_priv, + lc_q ) + { + if ( !LDAP_BACK_CONN_BINDING( tmplc ) ) { + break; + } + } + + if ( tmplc != NULL ) { + refcnt = ++tmplc->lc_refcnt; + ldap_back_conn_free( lc ); + lc = tmplc; + } + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + + if ( tmplc != NULL ) { + goto done; + } + } +#endif /* HAVE_TLS */ + + /* Inserts the newly created ldapconn in the avl tree */ + ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex ); + + LDAP_BACK_CONN_ISBOUND_CLEAR( lc ); + lc->lc_connid = li->li_conn_nextid++; + + assert( lc->lc_refcnt == 1 ); + +#if LDAP_BACK_PRINT_CONNTREE > 0 + ldap_back_print_conntree( li, ">>> ldap_back_getconn(insert)" ); +#endif /* LDAP_BACK_PRINT_CONNTREE */ + + if ( LDAP_BACK_PCONN_ISPRIV( lc ) ) { + if ( li->li_conn_priv[ LDAP_BACK_CONN2PRIV( lc ) ].lic_num < li->li_conn_priv_max ) { + LDAP_TAILQ_INSERT_TAIL( &li->li_conn_priv[ LDAP_BACK_CONN2PRIV( lc ) ].lic_priv, lc, lc_q ); + li->li_conn_priv[ LDAP_BACK_CONN2PRIV( lc ) ].lic_num++; + LDAP_BACK_CONN_CACHED_SET( lc ); + + } else { + LDAP_BACK_CONN_TAINTED_SET( lc ); + } + rs->sr_err = 0; + + } else { + rs->sr_err = ldap_tavl_insert( &li->li_conninfo.lai_tree, (caddr_t)lc, + ldap_back_conndn_cmp, ldap_back_conndn_dup ); + LDAP_BACK_CONN_CACHED_SET( lc ); + } + +#if LDAP_BACK_PRINT_CONNTREE > 0 + ldap_back_print_conntree( li, "<<< ldap_back_getconn(insert)" ); +#endif /* LDAP_BACK_PRINT_CONNTREE */ + + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + + Debug(LDAP_DEBUG_TRACE, + "=>ldap_back_getconn: %s: lc=%p inserted refcnt=%u rc=%d\n", + op->o_log_prefix, (void *)lc, refcnt, + rs->sr_err ); + + if ( !LDAP_BACK_PCONN_ISPRIV( lc ) ) { + /* Err could be -1 in case a duplicate ldapconn is inserted */ + switch ( rs->sr_err ) { + case 0: + break; + + case -1: + LDAP_BACK_CONN_CACHED_CLEAR( lc ); + if ( !( sendok & LDAP_BACK_BINDING ) && !LDAP_BACK_USE_TEMPORARIES( li ) ) { + /* duplicate: free and try to get the newly created one */ + ldap_back_conn_free( lc ); + lc = NULL; + goto retry_lock; + } + + /* taint connection, so that it'll be freed when released */ + LDAP_BACK_CONN_TAINTED_SET( lc ); + break; + + default: + LDAP_BACK_CONN_CACHED_CLEAR( lc ); + ldap_back_conn_free( lc ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "Proxy bind collision"; + if ( op->o_conn && ( sendok & LDAP_BACK_SENDERR ) ) { + send_ldap_result( op, rs ); + } + return NULL; + } + } + ldap_back_schedule_conn_expiry( li, lc ); + + } else { + int expiring = 0; + + if ( ( li->li_idle_timeout != 0 && op->o_time > lc->lc_time + li->li_idle_timeout ) + || ( li->li_conn_ttl != 0 && op->o_time > lc->lc_create_time + li->li_conn_ttl ) ) + { + expiring = 1; + + /* let it be used, but taint/delete it so that + * no-one else can look it up any further */ + ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex ); + +#if LDAP_BACK_PRINT_CONNTREE > 0 + ldap_back_print_conntree( li, ">>> ldap_back_getconn(timeout)" ); +#endif /* LDAP_BACK_PRINT_CONNTREE */ + + (void)ldap_back_conn_delete( li, lc ); + LDAP_BACK_CONN_TAINTED_SET( lc ); + +#if LDAP_BACK_PRINT_CONNTREE > 0 + ldap_back_print_conntree( li, "<<< ldap_back_getconn(timeout)" ); +#endif /* LDAP_BACK_PRINT_CONNTREE */ + + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + } + + Debug(LDAP_DEBUG_TRACE, + "=>ldap_back_getconn: conn %p fetched refcnt=%u%s.\n", + (void *)lc, refcnt, expiring ? " expiring" : "" ); + } + +#ifdef HAVE_TLS +done:; +#endif /* HAVE_TLS */ + + return lc; +} + +void +ldap_back_release_conn_lock( + ldapinfo_t *li, + ldapconn_t **lcp, + int dolock ) +{ + + ldapconn_t *lc = *lcp; + + if ( dolock ) { + ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex ); + } + assert( lc->lc_refcnt > 0 ); + LDAP_BACK_CONN_BINDING_CLEAR( lc ); + lc->lc_refcnt--; + if ( LDAP_BACK_CONN_TAINTED( lc ) ) { + ldap_back_freeconn( li, lc, 0 ); + *lcp = NULL; + } + if ( dolock ) { + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + } +} + +void +ldap_back_quarantine( + Operation *op, + SlapReply *rs ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + + slap_retry_info_t *ri = &li->li_quarantine; + + ldap_pvt_thread_mutex_lock( &li->li_quarantine_mutex ); + + if ( rs->sr_err == LDAP_UNAVAILABLE ) { + time_t new_last = slap_get_time(); + + switch ( li->li_isquarantined ) { + case LDAP_BACK_FQ_NO: + if ( ri->ri_last == new_last ) { + goto done; + } + + Debug( LDAP_DEBUG_ANY, + "%s: ldap_back_quarantine enter.\n", + op->o_log_prefix ); + + ri->ri_idx = 0; + ri->ri_count = 0; + break; + + case LDAP_BACK_FQ_RETRYING: + Debug( LDAP_DEBUG_ANY, + "%s: ldap_back_quarantine block #%d try #%d failed.\n", + op->o_log_prefix, ri->ri_idx, ri->ri_count ); + + ++ri->ri_count; + if ( ri->ri_num[ ri->ri_idx ] != SLAP_RETRYNUM_FOREVER + && ri->ri_count == ri->ri_num[ ri->ri_idx ] ) + { + ri->ri_count = 0; + ++ri->ri_idx; + } + break; + + default: + goto done; + } + + li->li_isquarantined = LDAP_BACK_FQ_YES; + ri->ri_last = new_last; + + } else if ( li->li_isquarantined != LDAP_BACK_FQ_NO ) { + if ( ri->ri_last == slap_get_time() ) { + goto done; + } + + Debug( LDAP_DEBUG_ANY, + "%s: ldap_back_quarantine exit (%d) err=%d.\n", + op->o_log_prefix, li->li_isquarantined, rs->sr_err ); + + if ( li->li_quarantine_f ) { + (void)li->li_quarantine_f( li, li->li_quarantine_p ); + } + + ri->ri_count = 0; + ri->ri_idx = 0; + li->li_isquarantined = LDAP_BACK_FQ_NO; + } + +done:; + ldap_pvt_thread_mutex_unlock( &li->li_quarantine_mutex ); +} + +static int +ldap_back_dobind_cb( + Operation *op, + SlapReply *rs +) +{ + ber_tag_t *tptr = op->o_callback->sc_private; + op->o_tag = *tptr; + rs->sr_tag = slap_req2res( op->o_tag ); + + return SLAP_CB_CONTINUE; +} + +/* + * ldap_back_dobind_int + * + * Note: dolock indicates whether li->li_conninfo.lai_mutex must be locked or not + */ +static int +ldap_back_dobind_int( + ldapconn_t **lcp, + Operation *op, + SlapReply *rs, + ldap_back_send_t sendok, + int retries, + int dolock ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + + ldapconn_t *lc; + struct berval binddn = slap_empty_bv, + bindcred = slap_empty_bv; + + int rc = 0, + isbound, + binding = 0; + ber_int_t msgid; + ber_tag_t o_tag = op->o_tag; + slap_callback cb = {0}; + char *tmp_dn; + + assert( lcp != NULL ); + assert( retries >= 0 ); + + if ( sendok & LDAP_BACK_GETCONN ) { + assert( *lcp == NULL ); + + lc = ldap_back_getconn( op, rs, sendok, &binddn, &bindcred ); + if ( lc == NULL ) { + return 0; + } + *lcp = lc; + + } else { + lc = *lcp; + } + + assert( lc != NULL ); + +retry_lock:; + if ( dolock ) { + ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex ); + } + + if ( binding == 0 ) { + /* check if already bound */ + rc = isbound = LDAP_BACK_CONN_ISBOUND( lc ); + if ( isbound ) { + if ( dolock ) { + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + } + return rc; + } + + if ( LDAP_BACK_CONN_BINDING( lc ) ) { + /* if someone else is about to bind it, give up and retry */ + if ( dolock ) { + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + } + ldap_pvt_thread_yield(); + goto retry_lock; + + } else { + /* otherwise this thread will bind it */ + LDAP_BACK_CONN_BINDING_SET( lc ); + binding = 1; + } + } + + if ( dolock ) { + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + } + + /* + * FIXME: we need to let clients use proxyAuthz + * otherwise we cannot do symmetric pools of servers; + * we have to live with the fact that a user can + * authorize itself as any ID that is allowed + * by the authzTo directive of the "proxyauthzdn". + */ + /* + * NOTE: current Proxy Authorization specification + * and implementation do not allow proxy authorization + * control to be provided with Bind requests + */ + /* + * if no bind took place yet, but the connection is bound + * and the "idassert-authcDN" (or other ID) is set, + * then bind as the asserting identity and explicitly + * add the proxyAuthz control to every operation with the + * dn bound to the connection as control value. + * This is done also if this is the authorizing backend, + * but the "override" flag is given to idassert. + * It allows to use SASL bind and yet proxyAuthz users + */ + op->o_tag = LDAP_REQ_BIND; + cb.sc_next = op->o_callback; + cb.sc_private = &o_tag; + cb.sc_response = ldap_back_dobind_cb; + op->o_callback = &cb; + + if ( LDAP_BACK_CONN_ISIDASSERT( lc ) ) { + if ( BER_BVISEMPTY( &binddn ) && BER_BVISEMPTY( &bindcred ) ) { + /* if we got here, it shouldn't return result */ + rc = ldap_back_is_proxy_authz( op, rs, + LDAP_BACK_DONTSEND, &binddn, &bindcred ); + if ( rc != 1 ) { + Debug( LDAP_DEBUG_ANY, "Error: ldap_back_is_proxy_authz " + "returned %d, misconfigured URI?\n", rc ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "misconfigured URI?"; + LDAP_BACK_CONN_ISBOUND_CLEAR( lc ); + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + goto done; + } + } + rc = ldap_back_proxy_authz_bind( lc, op, rs, sendok, &binddn, &bindcred ); + goto done; + } + +#ifdef HAVE_CYRUS_SASL + if ( LDAP_BACK_CONN_ISPRIV( lc )) { + slap_bindconf *sb; + if ( li->li_acl_authmethod != LDAP_AUTH_NONE ) + sb = &li->li_acl; + else + sb = &li->li_idassert.si_bc; + + if ( sb->sb_method == LDAP_AUTH_SASL ) { + void *defaults = NULL; + + if ( sb->sb_secprops != NULL ) { + rc = ldap_set_option( lc->lc_ld, + LDAP_OPT_X_SASL_SECPROPS, sb->sb_secprops ); + + if ( rc != LDAP_OPT_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "Error: ldap_set_option " + "(SECPROPS,\"%s\") failed!\n", + sb->sb_secprops ); + goto done; + } + } + + defaults = lutil_sasl_defaults( lc->lc_ld, + sb->sb_saslmech.bv_val, + sb->sb_realm.bv_val, + sb->sb_authcId.bv_val, + sb->sb_cred.bv_val, + NULL ); + if ( defaults == NULL ) { + rs->sr_err = LDAP_OTHER; + LDAP_BACK_CONN_ISBOUND_CLEAR( lc ); + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + goto done; + } + + rs->sr_err = ldap_sasl_interactive_bind_s( lc->lc_ld, + sb->sb_binddn.bv_val, + sb->sb_saslmech.bv_val, NULL, NULL, + LDAP_SASL_QUIET, lutil_sasl_interact, + defaults ); + + ldap_pvt_thread_mutex_lock( &li->li_counter_mutex ); + ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_BIND ], 1 ); + ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex ); + + lutil_sasl_freedefs( defaults ); + + switch ( rs->sr_err ) { + case LDAP_SUCCESS: + LDAP_BACK_CONN_ISBOUND_SET( lc ); + break; + + case LDAP_LOCAL_ERROR: + /* list client API error codes that require + * to taint the connection */ + /* FIXME: should actually retry? */ + LDAP_BACK_CONN_TAINTED_SET( lc ); + + /* fallthru */ + + default: + LDAP_BACK_CONN_ISBOUND_CLEAR( lc ); + rs->sr_err = slap_map_api2result( rs ); + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + break; + } + + if ( LDAP_BACK_QUARANTINE( li ) ) { + ldap_back_quarantine( op, rs ); + } + + goto done; + } + } +#endif /* HAVE_CYRUS_SASL */ + +retry:; + if ( BER_BVISNULL( &lc->lc_cred ) ) { + tmp_dn = ""; + if ( !BER_BVISNULL( &lc->lc_bound_ndn ) && !BER_BVISEMPTY( &lc->lc_bound_ndn ) ) { + Debug( LDAP_DEBUG_ANY, "%s ldap_back_dobind_int: DN=\"%s\" without creds, binding anonymously", + op->o_log_prefix, lc->lc_bound_ndn.bv_val ); + } + + } else { + tmp_dn = lc->lc_bound_ndn.bv_val; + } + rs->sr_err = ldap_sasl_bind( lc->lc_ld, + tmp_dn, + LDAP_SASL_SIMPLE, &lc->lc_cred, + NULL, NULL, &msgid ); + + ldap_pvt_thread_mutex_lock( &li->li_counter_mutex ); + ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_BIND ], 1 ); + ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex ); + + if ( rs->sr_err == LDAP_SERVER_DOWN ) { + if ( retries != LDAP_BACK_RETRY_NEVER ) { + if ( dolock ) { + ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex ); + } + + assert( lc->lc_refcnt > 0 ); + if ( lc->lc_refcnt == 1 ) { + ldap_unbind_ext( lc->lc_ld, NULL, NULL ); + lc->lc_ld = NULL; + + /* lc here must be the regular lc, reset and ready for init */ + rs->sr_err = ldap_back_prepare_conn( lc, op, rs, sendok ); + if ( rs->sr_err != LDAP_SUCCESS ) { + sendok &= ~LDAP_BACK_SENDERR; + lc->lc_refcnt = 0; + } + } + + if ( dolock ) { + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + } + + if ( rs->sr_err == LDAP_SUCCESS ) { + if ( retries > 0 ) { + retries--; + } + goto retry; + } + } + + assert( lc->lc_refcnt == 1 ); + lc->lc_refcnt = 0; + ldap_back_freeconn( li, lc, dolock ); + *lcp = NULL; + rs->sr_err = slap_map_api2result( rs ); + + if ( LDAP_BACK_QUARANTINE( li ) ) { + ldap_back_quarantine( op, rs ); + } + + if ( rs->sr_err != LDAP_SUCCESS && + ( sendok & LDAP_BACK_SENDERR ) ) + { + if ( op->o_callback == &cb ) + op->o_callback = cb.sc_next; + op->o_tag = o_tag; + rs->sr_text = "Proxy can't contact remote server"; + send_ldap_result( op, rs ); + /* if we originally bound and wanted rebind-as-user, must drop + * the connection now because we just discarded the credentials. + * ITS#7464, #8142 + */ + if ( LDAP_BACK_SAVECRED( li ) && SLAP_IS_AUTHZ_BACKEND( op ) ) + rs->sr_err = SLAPD_DISCONNECT; + } + + rc = 0; + goto func_leave; + } + + rc = ldap_back_op_result( lc, op, rs, msgid, + -1, ( sendok | LDAP_BACK_BINDING ) ); + if ( rc == LDAP_SUCCESS ) { + LDAP_BACK_CONN_ISBOUND_SET( lc ); + } + +done:; + LDAP_BACK_CONN_BINDING_CLEAR( lc ); + rc = LDAP_BACK_CONN_ISBOUND( lc ); + if ( !rc ) { + ldap_back_release_conn_lock( li, lcp, dolock ); + + } else if ( LDAP_BACK_SAVECRED( li ) ) { + ldap_set_rebind_proc( lc->lc_ld, li->li_rebind_f, lc ); + } + +func_leave:; + if ( op->o_callback == &cb ) + op->o_callback = cb.sc_next; + op->o_tag = o_tag; + + return rc; +} + +/* + * ldap_back_dobind + * + * Note: dolock indicates whether li->li_conninfo.lai_mutex must be locked or not + */ +int +ldap_back_dobind( ldapconn_t **lcp, Operation *op, SlapReply *rs, ldap_back_send_t sendok ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + + return ldap_back_dobind_int( lcp, op, rs, + ( sendok | LDAP_BACK_GETCONN ), li->li_nretries, 1 ); +} + +/* + * ldap_back_default_rebind + * + * This is a callback used for chasing referrals using the same + * credentials as the original user on this session. + */ +int +ldap_back_default_rebind( LDAP *ld, LDAP_CONST char *url, ber_tag_t request, + ber_int_t msgid, void *params ) +{ + ldapconn_t *lc = (ldapconn_t *)params; + +#ifdef HAVE_TLS + /* ... otherwise we couldn't get here */ + assert( lc != NULL ); + + if ( !ldap_tls_inplace( ld ) ) { + int is_tls = LDAP_BACK_CONN_ISTLS( lc ), + rc; + const char *text = NULL; + + rc = ldap_back_start_tls( ld, 0, &is_tls, url, lc->lc_flags, + lc->lc_ldapinfo->li_timeout[ SLAP_OP_BIND ], &text ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + } +#endif /* HAVE_TLS */ + + /* FIXME: add checks on the URL/identity? */ + /* TODO: would like to count this bind operation for monitoring + * too, but where do we get the ldapinfo_t? */ + + return ldap_sasl_bind_s( ld, + BER_BVISNULL( &lc->lc_cred ) ? "" : lc->lc_bound_ndn.bv_val, + LDAP_SASL_SIMPLE, &lc->lc_cred, NULL, NULL, NULL ); +} + +/* + * ldap_back_default_urllist + */ +int +ldap_back_default_urllist( + LDAP *ld, + LDAPURLDesc **urllist, + LDAPURLDesc **url, + void *params ) +{ + ldapinfo_t *li = (ldapinfo_t *)params; + LDAPURLDesc **urltail; + + if ( urllist == url ) { + return LDAP_SUCCESS; + } + + for ( urltail = &(*url)->lud_next; *urltail; urltail = &(*urltail)->lud_next ) + /* count */ ; + + *urltail = *urllist; + *urllist = *url; + *url = NULL; + + if ( !li->li_uri_mutex_do_not_lock ) { + ldap_pvt_thread_mutex_lock( &li->li_uri_mutex ); + } + + if ( li->li_uri ) { + ch_free( li->li_uri ); + } + + ldap_get_option( ld, LDAP_OPT_URI, (void *)&li->li_uri ); + + if ( !li->li_uri_mutex_do_not_lock ) { + ldap_pvt_thread_mutex_unlock( &li->li_uri_mutex ); + } + + return LDAP_SUCCESS; +} + +int +ldap_back_cancel( + ldapconn_t *lc, + Operation *op, + SlapReply *rs, + ber_int_t msgid, + ldap_back_send_t sendok ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + + /* default behavior */ + if ( LDAP_BACK_ABANDON( li ) ) { + return ldap_abandon_ext( lc->lc_ld, msgid, NULL, NULL ); + } + + if ( LDAP_BACK_IGNORE( li ) ) { + return ldap_pvt_discard( lc->lc_ld, msgid ); + } + + if ( LDAP_BACK_CANCEL( li ) ) { + /* FIXME: asynchronous? */ + return ldap_cancel_s( lc->lc_ld, msgid, NULL, NULL ); + } + + assert( 0 ); + + return LDAP_OTHER; +} + +int +ldap_back_op_result( + ldapconn_t *lc, + Operation *op, + SlapReply *rs, + ber_int_t msgid, + time_t timeout, + ldap_back_send_t sendok ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + + char *match = NULL; + char *text = NULL; + char **refs = NULL; + LDAPControl **ctrls = NULL; + + rs->sr_text = NULL; + rs->sr_matched = NULL; + rs->sr_ref = NULL; + rs->sr_ctrls = NULL; + + /* if the error recorded in the reply corresponds + * to a successful state, get the error from the + * remote server response */ + if ( LDAP_ERR_OK( rs->sr_err ) ) { + int rc; + struct timeval tv; + LDAPMessage *res = NULL; + time_t stoptime = (time_t)(-1); + int timeout_err = op->o_protocol >= LDAP_VERSION3 ? + LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER; + const char *timeout_text = "Operation timed out"; + + /* if timeout is not specified, compute and use + * the one specific to the ongoing operation */ + if ( timeout == (time_t)(-1) ) { + slap_op_t opidx = slap_req2op( op->o_tag ); + + if ( opidx == SLAP_OP_SEARCH ) { + if ( op->ors_tlimit <= 0 ) { + timeout = 0; + + } else { + timeout = op->ors_tlimit; + timeout_err = LDAP_TIMELIMIT_EXCEEDED; + timeout_text = NULL; + } + + } else { + timeout = li->li_timeout[ opidx ]; + } + } + + /* better than nothing :) */ + if ( timeout == 0 ) { + if ( li->li_idle_timeout ) { + timeout = li->li_idle_timeout; + + } else if ( li->li_conn_ttl ) { + timeout = li->li_conn_ttl; + } + } + + if ( timeout ) { + stoptime = op->o_time + timeout; + } + + LDAP_BACK_TV_SET( &tv ); + +retry:; + /* if result parsing fails, note the failure reason */ + rc = ldap_result( lc->lc_ld, msgid, LDAP_MSG_ALL, &tv, &res ); + switch ( rc ) { + case 0: + if ( timeout && slap_get_time() > stoptime ) { + if ( sendok & LDAP_BACK_BINDING ) { + ldap_unbind_ext( lc->lc_ld, NULL, NULL ); + lc->lc_ld = NULL; + + /* let it be used, but taint/delete it so that + * no-one else can look it up any further */ + ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex ); + +#if LDAP_BACK_PRINT_CONNTREE > 0 + ldap_back_print_conntree( li, ">>> ldap_back_getconn(timeout)" ); +#endif /* LDAP_BACK_PRINT_CONNTREE */ + + (void)ldap_back_conn_delete( li, lc ); + LDAP_BACK_CONN_TAINTED_SET( lc ); + +#if LDAP_BACK_PRINT_CONNTREE > 0 + ldap_back_print_conntree( li, "<<< ldap_back_getconn(timeout)" ); +#endif /* LDAP_BACK_PRINT_CONNTREE */ + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + + } else { + (void)ldap_back_cancel( lc, op, rs, msgid, sendok ); + } + rs->sr_err = timeout_err; + rs->sr_text = timeout_text; + break; + } + + /* timeout == 0 */ + LDAP_BACK_TV_SET( &tv ); + ldap_pvt_thread_yield(); + goto retry; + + case -1: + ldap_get_option( lc->lc_ld, LDAP_OPT_ERROR_NUMBER, + &rs->sr_err ); + break; + + + /* otherwise get the result; if it is not + * LDAP_SUCCESS, record it in the reply + * structure (this includes + * LDAP_COMPARE_{TRUE|FALSE}) */ + default: + /* only touch when activity actually took place... */ + if ( li->li_idle_timeout ) { + lc->lc_time = op->o_time; + } + + rc = ldap_parse_result( lc->lc_ld, res, &rs->sr_err, + &match, &text, &refs, &ctrls, 1 ); + if ( rc == LDAP_SUCCESS ) { + rs->sr_text = text; + } else { + rs->sr_err = rc; + } + rs->sr_err = slap_map_api2result( rs ); + + /* RFC 4511: referrals can only appear + * if result code is LDAP_REFERRAL */ + if ( refs != NULL + && refs[ 0 ] != NULL + && refs[ 0 ][ 0 ] != '\0' ) + { + if ( rs->sr_err != LDAP_REFERRAL ) { + Debug( LDAP_DEBUG_ANY, + "%s ldap_back_op_result: " + "got referrals with err=%d\n", + op->o_log_prefix, + rs->sr_err ); + + } else { + int i; + + for ( i = 0; refs[ i ] != NULL; i++ ) + /* count */ ; + rs->sr_ref = op->o_tmpalloc( sizeof( struct berval ) * ( i + 1 ), + op->o_tmpmemctx ); + for ( i = 0; refs[ i ] != NULL; i++ ) { + ber_str2bv( refs[ i ], 0, 0, &rs->sr_ref[ i ] ); + } + BER_BVZERO( &rs->sr_ref[ i ] ); + } + + } else if ( rs->sr_err == LDAP_REFERRAL ) { + Debug( LDAP_DEBUG_ANY, + "%s ldap_back_op_result: " + "got err=%d with null " + "or empty referrals\n", + op->o_log_prefix, + rs->sr_err ); + + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } + + if ( ctrls != NULL ) { + rs->sr_ctrls = ctrls; + } + } + } + + /* if the error in the reply structure is not + * LDAP_SUCCESS, try to map it from client + * to server error */ + if ( !LDAP_ERR_OK( rs->sr_err ) ) { + rs->sr_err = slap_map_api2result( rs ); + + /* internal ops ( op->o_conn == NULL ) + * must not reply to client */ + if ( op->o_conn && !op->o_do_not_cache && match ) { + + /* record the (massaged) matched + * DN into the reply structure */ + rs->sr_matched = match; + } + } + + if ( rs->sr_err == LDAP_UNAVAILABLE ) { + if ( !( sendok & LDAP_BACK_RETRYING ) ) { + if ( LDAP_BACK_QUARANTINE( li ) ) { + ldap_back_quarantine( op, rs ); + } + if ( op->o_conn && ( sendok & LDAP_BACK_SENDERR ) ) { + if ( rs->sr_text == NULL ) rs->sr_text = "Proxy operation retry failed"; + send_ldap_result( op, rs ); + } + } + + } else if ( op->o_conn && + ( ( ( sendok & LDAP_BACK_SENDOK ) && LDAP_ERR_OK( rs->sr_err ) ) + || ( ( sendok & LDAP_BACK_SENDERR ) && !LDAP_ERR_OK( rs->sr_err ) ) ) ) + { + send_ldap_result( op, rs ); + } + + if ( text ) { + ldap_memfree( text ); + } + rs->sr_text = NULL; + + /* there can't be refs with a (successful) bind */ + if ( rs->sr_ref ) { + op->o_tmpfree( rs->sr_ref, op->o_tmpmemctx ); + rs->sr_ref = NULL; + } + + if ( refs ) { + ber_memvfree( (void **)refs ); + } + + /* match should not be possible with a successful bind */ + if ( match ) { + if ( rs->sr_matched != match ) { + free( (char *)rs->sr_matched ); + } + rs->sr_matched = NULL; + ldap_memfree( match ); + } + + if ( ctrls != NULL ) { + if ( op->o_tag == LDAP_REQ_BIND && rs->sr_err == LDAP_SUCCESS ) { + int i; + + for ( i = 0; ctrls[i] != NULL; i++ ); + + rs->sr_ctrls = op->o_tmpalloc( sizeof( LDAPControl * )*( i + 1 ), + op->o_tmpmemctx ); + for ( i = 0; ctrls[ i ] != NULL; i++ ) { + char *ptr; + ber_len_t oidlen = strlen( ctrls[i]->ldctl_oid ); + ber_len_t size = sizeof( LDAPControl ) + + oidlen + 1 + + ctrls[i]->ldctl_value.bv_len + 1; + + rs->sr_ctrls[ i ] = op->o_tmpalloc( size, op->o_tmpmemctx ); + rs->sr_ctrls[ i ]->ldctl_oid = (char *)&rs->sr_ctrls[ i ][ 1 ]; + lutil_strcopy( rs->sr_ctrls[ i ]->ldctl_oid, ctrls[i]->ldctl_oid ); + rs->sr_ctrls[ i ]->ldctl_value.bv_val + = (char *)&rs->sr_ctrls[ i ]->ldctl_oid[oidlen + 1]; + rs->sr_ctrls[ i ]->ldctl_value.bv_len + = ctrls[i]->ldctl_value.bv_len; + ptr = lutil_memcopy( rs->sr_ctrls[ i ]->ldctl_value.bv_val, + ctrls[i]->ldctl_value.bv_val, ctrls[i]->ldctl_value.bv_len ); + *ptr = '\0'; + } + rs->sr_ctrls[ i ] = NULL; + rs->sr_flags |= REP_CTRLS_MUSTBEFREED; + + } else { + assert( rs->sr_ctrls != NULL ); + rs->sr_ctrls = NULL; + } + + ldap_controls_free( ctrls ); + } + + return( LDAP_ERR_OK( rs->sr_err ) ? LDAP_SUCCESS : rs->sr_err ); +} + +/* return true if bound, false if failed */ +int +ldap_back_retry( ldapconn_t **lcp, Operation *op, SlapReply *rs, ldap_back_send_t sendok ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + int rc = 0; + + assert( lcp != NULL ); + assert( *lcp != NULL ); + + ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex ); + + if ( (*lcp)->lc_refcnt == 1 ) { + int binding = LDAP_BACK_CONN_BINDING( *lcp ); + + ldap_pvt_thread_mutex_lock( &li->li_uri_mutex ); + Debug( LDAP_DEBUG_ANY, + "%s ldap_back_retry: retrying URI=\"%s\" DN=\"%s\"\n", + op->o_log_prefix, li->li_uri, + BER_BVISNULL( &(*lcp)->lc_bound_ndn ) ? + "" : (*lcp)->lc_bound_ndn.bv_val ); + ldap_pvt_thread_mutex_unlock( &li->li_uri_mutex ); + + ldap_unbind_ext( (*lcp)->lc_ld, NULL, NULL ); + (*lcp)->lc_ld = NULL; + LDAP_BACK_CONN_ISBOUND_CLEAR( (*lcp) ); + + /* lc here must be the regular lc, reset and ready for init */ + rc = ldap_back_prepare_conn( *lcp, op, rs, sendok ); + if ( rc != LDAP_SUCCESS ) { + /* freeit, because lc_refcnt == 1 */ + (*lcp)->lc_refcnt = 0; + (void)ldap_back_freeconn( li, *lcp, 0 ); + *lcp = NULL; + rc = 0; + + } else if ( ( sendok & LDAP_BACK_BINDING ) ) { + if ( binding ) { + LDAP_BACK_CONN_BINDING_SET( *lcp ); + } + rc = 1; + + } else { + rc = ldap_back_dobind_int( lcp, op, rs, sendok, 0, 0 ); + if ( rc == 0 && *lcp != NULL ) { + /* freeit, because lc_refcnt == 1 */ + (*lcp)->lc_refcnt = 0; + (void)ldap_back_freeconn( li, *lcp, 0 ); + *lcp = NULL; + } + } + + } else { + Debug( LDAP_DEBUG_TRACE, + "ldap_back_retry: conn %p refcnt=%u unable to retry.\n", + (void *)(*lcp), (*lcp)->lc_refcnt ); + + LDAP_BACK_CONN_TAINTED_SET( *lcp ); + ldap_back_release_conn_lock( li, lcp, 0 ); + assert( *lcp == NULL ); + + if ( sendok & LDAP_BACK_SENDERR ) { + rs->sr_err = LDAP_UNAVAILABLE; + rs->sr_text = "Unable to retry"; + send_ldap_result( op, rs ); + } + } + + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + + return rc; +} + +static int +ldap_back_is_proxy_authz( Operation *op, SlapReply *rs, ldap_back_send_t sendok, + struct berval *binddn, struct berval *bindcred ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + struct berval ndn; + int dobind = 0; + + if ( op->o_conn == NULL || op->o_do_not_cache ) { + goto done; + } + + /* don't proxyAuthz if protocol is not LDAPv3 */ + switch ( li->li_version ) { + case LDAP_VERSION3: + break; + + case 0: + if ( op->o_protocol == 0 || op->o_protocol == LDAP_VERSION3 ) { + break; + } + /* fall thru */ + + default: + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + dobind = -1; + } + goto done; + } + + /* safe default */ + *binddn = slap_empty_bv; + *bindcred = slap_empty_bv; + + if ( !BER_BVISNULL( &op->o_conn->c_ndn ) ) { + ndn = op->o_conn->c_ndn; + + } else { + ndn = op->o_ndn; + } + + if ( !( li->li_idassert_flags & LDAP_BACK_AUTH_OVERRIDE )) { + if ( op->o_tag == LDAP_REQ_BIND && ( sendok & LDAP_BACK_SENDERR )) { + if ( !BER_BVISEMPTY( &ndn )) { + dobind = 0; + goto done; + } + } else if ( SLAP_IS_AUTHZ_BACKEND( op )) { + dobind = 0; + goto done; + } + } + + switch ( li->li_idassert_mode ) { + case LDAP_BACK_IDASSERT_LEGACY: + if ( !BER_BVISNULL( &ndn ) && !BER_BVISEMPTY( &ndn ) ) { + if ( !BER_BVISNULL( &li->li_idassert_authcDN ) && !BER_BVISEMPTY( &li->li_idassert_authcDN ) ) + { + *binddn = li->li_idassert_authcDN; + *bindcred = li->li_idassert_passwd; + dobind = 1; + } + } + break; + + default: + /* NOTE: rootdn can always idassert */ + if ( BER_BVISNULL( &ndn ) + && li->li_idassert_authz == NULL + && !( li->li_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL ) ) + { + if ( li->li_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) { + rs->sr_err = LDAP_INAPPROPRIATE_AUTH; + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + dobind = -1; + } + + } else { + rs->sr_err = LDAP_SUCCESS; + *binddn = slap_empty_bv; + *bindcred = slap_empty_bv; + break; + } + + goto done; + + } else if ( !be_isroot( op ) ) { + if ( li->li_idassert_passthru ) { + struct berval authcDN; + + if ( BER_BVISNULL( &ndn ) ) { + authcDN = slap_empty_bv; + + } else { + authcDN = ndn; + } + rs->sr_err = slap_sasl_matches( op, li->li_idassert_passthru, + &authcDN, &authcDN ); + if ( rs->sr_err == LDAP_SUCCESS ) { + dobind = 0; + break; + } + } + + if ( li->li_idassert_authz ) { + struct berval authcDN; + + if ( BER_BVISNULL( &ndn ) ) { + authcDN = slap_empty_bv; + + } else { + authcDN = ndn; + } + rs->sr_err = slap_sasl_matches( op, li->li_idassert_authz, + &authcDN, &authcDN ); + if ( rs->sr_err != LDAP_SUCCESS ) { + if ( li->li_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) { + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + dobind = -1; + } + + } else { + rs->sr_err = LDAP_SUCCESS; + *binddn = slap_empty_bv; + *bindcred = slap_empty_bv; + break; + } + + goto done; + } + } + } + + *binddn = li->li_idassert_authcDN; + *bindcred = li->li_idassert_passwd; + dobind = 1; + break; + } + +done:; + return dobind; +} + +static int +ldap_back_proxy_authz_bind( + ldapconn_t *lc, + Operation *op, + SlapReply *rs, + ldap_back_send_t sendok, + struct berval *binddn, + struct berval *bindcred ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + struct berval ndn; + int msgid; + int rc; + + if ( !BER_BVISNULL( &op->o_conn->c_ndn ) ) { + ndn = op->o_conn->c_ndn; + + } else { + ndn = op->o_ndn; + } + + if ( li->li_idassert_authmethod == LDAP_AUTH_SASL ) { +#ifdef HAVE_CYRUS_SASL + void *defaults = NULL; + struct berval authzID = BER_BVNULL; + int freeauthz = 0; + LDAPControl **ctrlsp = NULL; + LDAPMessage *result = NULL; + const char *rmech = NULL; + const char *save_text = rs->sr_text; + +#ifdef SLAP_AUTH_DN + LDAPControl ctrl, *ctrls[2]; + int msgid; +#endif /* SLAP_AUTH_DN */ + + /* if SASL supports native authz, prepare for it */ + if ( ( !op->o_do_not_cache || !op->o_is_auth_check ) && + ( li->li_idassert_flags & LDAP_BACK_AUTH_NATIVE_AUTHZ ) ) + { + switch ( li->li_idassert_mode ) { + case LDAP_BACK_IDASSERT_OTHERID: + case LDAP_BACK_IDASSERT_OTHERDN: + authzID = li->li_idassert_authzID; + break; + + case LDAP_BACK_IDASSERT_ANONYMOUS: + BER_BVSTR( &authzID, "dn:" ); + break; + + case LDAP_BACK_IDASSERT_SELF: + if ( BER_BVISNULL( &ndn ) ) { + /* connection is not authc'd, so don't idassert */ + BER_BVSTR( &authzID, "dn:" ); + break; + } + authzID.bv_len = STRLENOF( "dn:" ) + ndn.bv_len; + authzID.bv_val = slap_sl_malloc( authzID.bv_len + 1, op->o_tmpmemctx ); + AC_MEMCPY( authzID.bv_val, "dn:", STRLENOF( "dn:" ) ); + AC_MEMCPY( authzID.bv_val + STRLENOF( "dn:" ), + ndn.bv_val, ndn.bv_len + 1 ); + freeauthz = 1; + break; + + default: + break; + } + } + + if ( li->li_idassert_secprops != NULL ) { + rs->sr_err = ldap_set_option( lc->lc_ld, + LDAP_OPT_X_SASL_SECPROPS, + (void *)li->li_idassert_secprops ); + + if ( rs->sr_err != LDAP_OPT_SUCCESS ) { + rs->sr_err = LDAP_OTHER; + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + LDAP_BACK_CONN_ISBOUND_CLEAR( lc ); + goto done; + } + } + + defaults = lutil_sasl_defaults( lc->lc_ld, + li->li_idassert_sasl_mech.bv_val, + li->li_idassert_sasl_realm.bv_val, + li->li_idassert_authcID.bv_val, + li->li_idassert_passwd.bv_val, + authzID.bv_val ); + if ( defaults == NULL ) { + rs->sr_err = LDAP_OTHER; + LDAP_BACK_CONN_ISBOUND_CLEAR( lc ); + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + goto done; + } + +#ifdef SLAP_AUTH_DN + if ( li->li_idassert_flags & LDAP_BACK_AUTH_DN_AUTHZID ) { + assert( BER_BVISNULL( binddn ) ); + + ctrl.ldctl_oid = LDAP_CONTROL_AUTHZID_REQUEST; + ctrl.ldctl_iscritical = 0; + BER_BVZERO( &ctrl.ldctl_value ); + ctrls[0] = &ctrl; + ctrls[1] = NULL; + ctrlsp = ctrls; + } +#endif /* SLAP_AUTH_DN */ + + do { + rs->sr_err = ldap_sasl_interactive_bind( lc->lc_ld, binddn->bv_val, + li->li_idassert_sasl_mech.bv_val, + ctrlsp, NULL, LDAP_SASL_QUIET, lutil_sasl_interact, defaults, + result, &rmech, &msgid ); + + if ( rs->sr_err != LDAP_SASL_BIND_IN_PROGRESS ) + break; + + ldap_msgfree( result ); + + if ( ldap_result( lc->lc_ld, msgid, LDAP_MSG_ALL, NULL, &result ) == -1 || !result ) { + ldap_get_option( lc->lc_ld, LDAP_OPT_RESULT_CODE, (void*)&rs->sr_err ); + ldap_get_option( lc->lc_ld, LDAP_OPT_DIAGNOSTIC_MESSAGE, (void*)&rs->sr_text ); + break; + } + } while ( rs->sr_err == LDAP_SASL_BIND_IN_PROGRESS ); + + ldap_pvt_thread_mutex_lock( &li->li_counter_mutex ); + ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_BIND ], 1 ); + ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex ); + + switch ( rs->sr_err ) { + case LDAP_SUCCESS: +#ifdef SLAP_AUTH_DN + /* FIXME: right now, the only reason to check + * response controls is RFC 3829 authzid */ + if ( li->li_idassert_flags & LDAP_BACK_AUTH_DN_AUTHZID ) { + ctrlsp = NULL; + rc = ldap_parse_result( lc->lc_ld, result, NULL, NULL, NULL, NULL, + &ctrlsp, 0 ); + if ( rc == LDAP_SUCCESS && ctrlsp ) { + LDAPControl *ctrl; + + ctrl = ldap_control_find( LDAP_CONTROL_AUTHZID_RESPONSE, + ctrlsp, NULL ); + if ( ctrl ) { + Debug( LDAP_DEBUG_TRACE, "%s: ldap_back_proxy_authz_bind: authzID=\"%s\" (authzid)\n", + op->o_log_prefix, ctrl->ldctl_value.bv_val ); + if ( ctrl->ldctl_value.bv_len > STRLENOF("dn:") && + strncasecmp( ctrl->ldctl_value.bv_val, "dn:", STRLENOF("dn:") ) == 0 ) + { + struct berval bv; + bv.bv_val = &ctrl->ldctl_value.bv_val[STRLENOF("dn:")]; + bv.bv_len = ctrl->ldctl_value.bv_len - STRLENOF("dn:"); + ber_bvreplace( &lc->lc_bound_ndn, &bv ); + } + } + } + + ldap_controls_free( ctrlsp ); + + } else if ( li->li_idassert_flags & LDAP_BACK_AUTH_DN_WHOAMI ) { + struct berval *val = NULL; + rc = ldap_whoami_s( lc->lc_ld, &val, NULL, NULL ); + if ( rc == LDAP_SUCCESS && val != NULL ) { + Debug( LDAP_DEBUG_TRACE, "%s: ldap_back_proxy_authz_bind: authzID=\"%s\" (whoami)\n", + op->o_log_prefix, val->bv_val ); + if ( val->bv_len > STRLENOF("dn:") && + strncasecmp( val->bv_val, "dn:", STRLENOF("dn:") ) == 0 ) + { + struct berval bv; + bv.bv_val = &val->bv_val[STRLENOF("dn:")]; + bv.bv_len = val->bv_len - STRLENOF("dn:"); + ber_bvreplace( &lc->lc_bound_ndn, &bv ); + } + ber_bvfree( val ); + } + } + + if ( ( li->li_idassert_flags & LDAP_BACK_AUTH_DN_MASK ) && + BER_BVISNULL( &lc->lc_bound_ndn ) ) + { + /* all in all, we only need it to be non-null */ + /* FIXME: should this be configurable? */ + static struct berval bv = BER_BVC("cn=authzdn"); + ber_bvreplace( &lc->lc_bound_ndn, &bv ); + } +#endif /* SLAP_AUTH_DN */ + LDAP_BACK_CONN_ISBOUND_SET( lc ); + break; + + case LDAP_LOCAL_ERROR: + /* list client API error codes that require + * to taint the connection */ + /* FIXME: should actually retry? */ + LDAP_BACK_CONN_TAINTED_SET( lc ); + + /* fallthru */ + + default: + LDAP_BACK_CONN_ISBOUND_CLEAR( lc ); + rs->sr_err = slap_map_api2result( rs ); + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + break; + } + + if ( save_text != rs->sr_text ) { + ldap_memfree( (char *)rs->sr_text ); + rs->sr_text = save_text; + } + + ldap_msgfree( result ); + + lutil_sasl_freedefs( defaults ); + if ( freeauthz ) { + slap_sl_free( authzID.bv_val, op->o_tmpmemctx ); + } + + goto done; +#endif /* HAVE_CYRUS_SASL */ + } + + switch ( li->li_idassert_authmethod ) { + case LDAP_AUTH_NONE: + /* FIXME: do we really need this? */ + BER_BVSTR( binddn, "" ); + BER_BVSTR( bindcred, "" ); + /* fallthru */ + + case LDAP_AUTH_SIMPLE: + rs->sr_err = ldap_sasl_bind( lc->lc_ld, + binddn->bv_val, LDAP_SASL_SIMPLE, + bindcred, NULL, NULL, &msgid ); + rc = ldap_back_op_result( lc, op, rs, msgid, + -1, ( sendok | LDAP_BACK_BINDING ) ); + + ldap_pvt_thread_mutex_lock( &li->li_counter_mutex ); + ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_BIND ], 1 ); + ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex ); + break; + + default: + /* unsupported! */ + LDAP_BACK_CONN_ISBOUND_CLEAR( lc ); + rs->sr_err = LDAP_AUTH_METHOD_NOT_SUPPORTED; + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + goto done; + } + + if ( rc == LDAP_SUCCESS ) { + /* set rebind stuff in case of successful proxyAuthz bind, + * so that referral chasing is attempted using the right + * identity */ + LDAP_BACK_CONN_ISBOUND_SET( lc ); + if ( !BER_BVISNULL( binddn ) ) { + ber_bvreplace( &lc->lc_bound_ndn, binddn ); + } + + if ( !BER_BVISNULL( &lc->lc_cred ) ) { + memset( lc->lc_cred.bv_val, 0, + lc->lc_cred.bv_len ); + } + + if ( LDAP_BACK_SAVECRED( li ) ) { + if ( !BER_BVISNULL( bindcred ) ) { + ber_bvreplace( &lc->lc_cred, bindcred ); + ldap_set_rebind_proc( lc->lc_ld, li->li_rebind_f, lc ); + } + + } else { + lc->lc_cred.bv_len = 0; + } + } + +done:; + return LDAP_BACK_CONN_ISBOUND( lc ); +} + +/* + * ldap_back_proxy_authz_ctrl() prepends a proxyAuthz control + * to existing server-side controls if required; if not, + * the existing server-side controls are placed in *pctrls. + * The caller, after using the controls in client API + * operations, if ( *pctrls != op->o_ctrls ), should + * free( (*pctrls)[ 0 ] ) and free( *pctrls ). + * The function returns success if the control could + * be added if required, or if it did nothing; in the future, + * it might return some error if it failed. + * + * if no bind took place yet, but the connection is bound + * and the "proxyauthzdn" is set, then bind as "proxyauthzdn" + * and explicitly add proxyAuthz the control to every operation + * with the dn bound to the connection as control value. + * + * If no server-side controls are defined for the operation, + * simply add the proxyAuthz control; otherwise, if the + * proxyAuthz control is not already set, add it as + * the first one + * + * FIXME: is controls order significant for security? + * ANSWER: controls ordering and interoperability + * must be indicated by the specs of each control; if none + * is specified, the order is irrelevant. + */ +int +ldap_back_proxy_authz_ctrl( + Operation *op, + SlapReply *rs, + struct berval *bound_ndn, + int version, + slap_idassert_t *si, + LDAPControl *ctrl ) +{ + slap_idassert_mode_t mode; + struct berval assertedID, + ndn; + int isroot = 0; + + rs->sr_err = SLAP_CB_CONTINUE; + + /* FIXME: SASL/EXTERNAL over ldapi:// doesn't honor the authcID, + * but if it is not set this test fails. We need a different + * means to detect if idassert is enabled */ + if ( ( BER_BVISNULL( &si->si_bc.sb_authcId ) || BER_BVISEMPTY( &si->si_bc.sb_authcId ) ) + && ( BER_BVISNULL( &si->si_bc.sb_binddn ) || BER_BVISEMPTY( &si->si_bc.sb_binddn ) ) + && BER_BVISNULL( &si->si_bc.sb_saslmech ) ) + { + goto done; + } + + if ( !op->o_conn || op->o_do_not_cache || ( isroot = be_isroot( op ) ) ) { + goto done; + } + + if ( op->o_tag == LDAP_REQ_BIND ) { + ndn = op->o_req_ndn; + } else { + ndn = op->o_ndn; + } + + if ( si->si_mode == LDAP_BACK_IDASSERT_LEGACY ) { + if ( op->o_proxy_authz ) { + /* + * FIXME: we do not want to perform proxyAuthz + * on behalf of the client, because this would + * be performed with "proxyauthzdn" privileges. + * + * This might actually be too strict, since + * the "proxyauthzdn" authzTo, and each entry's + * authzFrom attributes may be crafted + * to avoid unwanted proxyAuthz to take place. + */ +#if 0 + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "proxyAuthz not allowed within namingContext"; +#endif + goto done; + } + + if ( !BER_BVISNULL( bound_ndn ) ) { + goto done; + } + + if ( BER_BVISNULL( &ndn ) ) { + goto done; + } + + if ( BER_BVISNULL( &si->si_bc.sb_binddn ) ) { + goto done; + } + + } else if ( si->si_bc.sb_method == LDAP_AUTH_SASL ) { + if ( ( si->si_flags & LDAP_BACK_AUTH_NATIVE_AUTHZ ) ) + { + /* already asserted in SASL via native authz */ + goto done; + } + + } else if ( si->si_authz && !isroot ) { + int rc; + struct berval authcDN; + + if ( BER_BVISNULL( &ndn ) ) { + authcDN = slap_empty_bv; + } else { + authcDN = ndn; + } + rc = slap_sasl_matches( op, si->si_authz, + &authcDN, &authcDN ); + if ( rc != LDAP_SUCCESS ) { + if ( si->si_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) { + /* ndn is not authorized + * to use idassert */ + rs->sr_err = rc; + } + goto done; + } + } + + if ( op->o_proxy_authz ) { + /* + * FIXME: we can: + * 1) ignore the already set proxyAuthz control + * 2) leave it in place, and don't set ours + * 3) add both + * 4) reject the operation + * + * option (4) is very drastic + * option (3) will make the remote server reject + * the operation, thus being equivalent to (4) + * option (2) will likely break the idassert + * assumptions, so we cannot accept it; + * option (1) means that we are contradicting + * the client's request. + * + * I think (4) is the only correct choice. + */ + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "proxyAuthz not allowed within namingContext"; + } + + if ( op->o_is_auth_check ) { + mode = LDAP_BACK_IDASSERT_NOASSERT; + + } else { + mode = si->si_mode; + } + + switch ( mode ) { + case LDAP_BACK_IDASSERT_LEGACY: + /* original behavior: + * assert the client's identity */ + case LDAP_BACK_IDASSERT_SELF: + assertedID = ndn; + break; + + case LDAP_BACK_IDASSERT_ANONYMOUS: + /* assert "anonymous" */ + assertedID = slap_empty_bv; + break; + + case LDAP_BACK_IDASSERT_NOASSERT: + /* don't assert; bind as proxyauthzdn */ + goto done; + + case LDAP_BACK_IDASSERT_OTHERID: + case LDAP_BACK_IDASSERT_OTHERDN: + /* assert idassert DN */ + assertedID = si->si_bc.sb_authzId; + break; + + default: + assert( 0 ); + } + + /* if we got here, "" is allowed to proxyAuthz */ + if ( BER_BVISNULL( &assertedID ) ) { + assertedID = slap_empty_bv; + } + + /* don't idassert the bound DN (ITS#4497) */ + if ( dn_match( &assertedID, bound_ndn ) ) { + goto done; + } + + ctrl->ldctl_oid = LDAP_CONTROL_PROXY_AUTHZ; + ctrl->ldctl_iscritical = ( ( si->si_flags & LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL ) == LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL ); + + switch ( si->si_mode ) { + /* already in u:ID or dn:DN form */ + case LDAP_BACK_IDASSERT_OTHERID: + case LDAP_BACK_IDASSERT_OTHERDN: + ber_dupbv_x( &ctrl->ldctl_value, &assertedID, op->o_tmpmemctx ); + rs->sr_err = LDAP_SUCCESS; + break; + + /* needs the dn: prefix */ + default: + ctrl->ldctl_value.bv_len = assertedID.bv_len + STRLENOF( "dn:" ); + ctrl->ldctl_value.bv_val = op->o_tmpalloc( ctrl->ldctl_value.bv_len + 1, + op->o_tmpmemctx ); + AC_MEMCPY( ctrl->ldctl_value.bv_val, "dn:", STRLENOF( "dn:" ) ); + AC_MEMCPY( &ctrl->ldctl_value.bv_val[ STRLENOF( "dn:" ) ], + assertedID.bv_val, assertedID.bv_len + 1 ); + rs->sr_err = LDAP_SUCCESS; + break; + } + + /* Older versions of <draft-weltman-ldapv3-proxy> required + * to encode the value of the authzID (and called it proxyDN); + * this hack provides compatibility with those DSAs that + * implement it this way */ + if ( si->si_flags & LDAP_BACK_AUTH_OBSOLETE_ENCODING_WORKAROUND ) { + struct berval authzID = ctrl->ldctl_value; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + ber_tag_t tag; + + ber_init2( ber, 0, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + + tag = ber_printf( ber, "O", &authzID ); + if ( tag == LBER_ERROR ) { + rs->sr_err = LDAP_OTHER; + goto free_ber; + } + + if ( ber_flatten2( ber, &ctrl->ldctl_value, 1 ) == -1 ) { + rs->sr_err = LDAP_OTHER; + goto free_ber; + } + + rs->sr_err = LDAP_SUCCESS; + +free_ber:; + op->o_tmpfree( authzID.bv_val, op->o_tmpmemctx ); + ber_free_buf( ber ); + + if ( rs->sr_err != LDAP_SUCCESS ) { + goto done; + } + + } else if ( si->si_flags & LDAP_BACK_AUTH_OBSOLETE_PROXY_AUTHZ ) { + struct berval authzID = ctrl->ldctl_value, + tmp; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + ber_tag_t tag; + + if ( strncasecmp( authzID.bv_val, "dn:", STRLENOF( "dn:" ) ) != 0 ) { + rs->sr_err = LDAP_PROTOCOL_ERROR; + goto done; + } + + tmp = authzID; + tmp.bv_val += STRLENOF( "dn:" ); + tmp.bv_len -= STRLENOF( "dn:" ); + + ber_init2( ber, 0, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + + /* apparently, Mozilla API encodes this + * as "SEQUENCE { LDAPDN }" */ + tag = ber_printf( ber, "{O}", &tmp ); + if ( tag == LBER_ERROR ) { + rs->sr_err = LDAP_OTHER; + goto free_ber2; + } + + if ( ber_flatten2( ber, &ctrl->ldctl_value, 1 ) == -1 ) { + rs->sr_err = LDAP_OTHER; + goto free_ber2; + } + + ctrl->ldctl_oid = LDAP_CONTROL_OBSOLETE_PROXY_AUTHZ; + rs->sr_err = LDAP_SUCCESS; + +free_ber2:; + op->o_tmpfree( authzID.bv_val, op->o_tmpmemctx ); + ber_free_buf( ber ); + + if ( rs->sr_err != LDAP_SUCCESS ) { + goto done; + } + } + +done:; + + return rs->sr_err; +} + +/* + * Add controls; + * + * if any needs to be added, it is prepended to existing ones, + * in a newly allocated array. The companion function + * ldap_back_controls_free() must be used to restore the original + * status of op->o_ctrls. + */ +int +ldap_back_controls_add( + Operation *op, + SlapReply *rs, + ldapconn_t *lc, + LDAPControl ***pctrls ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + + LDAPControl **ctrls = NULL; + /* set to the maximum number of controls this backend can add */ + LDAPControl c[ 2 ] = { { 0 } }; + int n = 0, i, j1 = 0, j2 = 0, skipped = 0; + + *pctrls = NULL; + + rs->sr_err = LDAP_SUCCESS; + + /* don't add controls if protocol is not LDAPv3 */ + switch ( li->li_version ) { + case LDAP_VERSION3: + break; + + case 0: + if ( op->o_protocol == 0 || op->o_protocol == LDAP_VERSION3 ) { + break; + } + /* fall thru */ + + default: + goto done; + } + + /* put controls that go __before__ existing ones here */ + + /* proxyAuthz for identity assertion */ + switch ( ldap_back_proxy_authz_ctrl( op, rs, &lc->lc_bound_ndn, + li->li_version, &li->li_idassert, &c[ j1 ] ) ) + { + case SLAP_CB_CONTINUE: + break; + + case LDAP_SUCCESS: + j1++; + break; + + default: + goto done; + } + + /* put controls that go __after__ existing ones here */ + +#ifdef SLAP_CONTROL_X_SESSION_TRACKING + /* FIXME: according to <draft-wahl-ldap-session>, + * the server should check if the control can be added + * based on the identity of the client and so */ + + /* session tracking */ + if ( LDAP_BACK_ST_REQUEST( li ) ) { + switch ( slap_ctrl_session_tracking_request_add( op, rs, &c[ j1 + j2 ] ) ) { + case SLAP_CB_CONTINUE: + break; + + case LDAP_SUCCESS: + j2++; + break; + + default: + goto done; + } + } +#endif /* SLAP_CONTROL_X_SESSION_TRACKING */ + + if ( rs->sr_err == SLAP_CB_CONTINUE ) { + rs->sr_err = LDAP_SUCCESS; + } + + /* if nothing to do, just bail out */ + if ( j1 == 0 && j2 == 0 ) { + goto done; + } + + assert( j1 + j2 <= (int) (sizeof( c )/sizeof( c[0] )) ); + + if ( op->o_ctrls ) { + for ( n = 0; op->o_ctrls[ n ]; n++ ) + /* just count ctrls */ ; + } + + ctrls = op->o_tmpalloc( (n + j1 + j2 + 1) * sizeof( LDAPControl * ) + ( j1 + j2 ) * sizeof( LDAPControl ), + op->o_tmpmemctx ); + if ( j1 ) { + ctrls[ 0 ] = (LDAPControl *)&ctrls[ n + j1 + j2 + 1 ]; + *ctrls[ 0 ] = c[ 0 ]; + for ( i = 1; i < j1; i++ ) { + ctrls[ i ] = &ctrls[ 0 ][ i ]; + *ctrls[ i ] = c[ i ]; + } + } + + i = 0; + if ( op->o_ctrls ) { + LDAPControl *proxyauthz = ldap_control_find( + LDAP_CONTROL_PROXY_AUTHZ, op->o_ctrls, NULL ); + + for ( i = 0; op->o_ctrls[ i ]; i++ ) { + /* Only replace it if we generated one */ + if ( j1 && proxyauthz && proxyauthz == op->o_ctrls[ i ] ) { + /* Frontend has already checked only one is present */ + assert( skipped == 0 ); + skipped++; + continue; + } + ctrls[ i + j1 - skipped ] = op->o_ctrls[ i ]; + } + } + + n += j1 - skipped; + if ( j2 ) { + ctrls[ n ] = (LDAPControl *)&ctrls[ n + j2 + 1 ] + j1; + *ctrls[ n ] = c[ j1 ]; + for ( i = 1; i < j2; i++ ) { + ctrls[ n + i ] = &ctrls[ n ][ i ]; + *ctrls[ n + i ] = c[ i ]; + } + } + + ctrls[ n + j2 ] = NULL; + +done:; + if ( ctrls == NULL ) { + ctrls = op->o_ctrls; + } + + *pctrls = ctrls; + + return rs->sr_err; +} + +int +ldap_back_controls_free( Operation *op, SlapReply *rs, LDAPControl ***pctrls ) +{ + LDAPControl **ctrls = *pctrls; + + /* we assume that the controls added by the proxy come first, + * so as soon as we find op->o_ctrls[ 0 ] we can stop */ + if ( ctrls && ctrls != op->o_ctrls ) { + int i = 0, n = 0, n_added; + LDAPControl *lower, *upper; + + assert( ctrls[ 0 ] != NULL ); + + for ( n = 0; ctrls[ n ] != NULL; n++ ) + /* count 'em */ ; + + if ( op->o_ctrls ) { + for ( i = 0; op->o_ctrls[ i ] != NULL; i++ ) + /* count 'em */ ; + } + + n_added = n - i; + lower = (LDAPControl *)&ctrls[ n ]; + upper = &lower[ n_added ]; + + for ( i = 0; ctrls[ i ] != NULL; i++ ) { + if ( ctrls[ i ] < lower || ctrls[ i ] >= upper ) { + /* original; don't touch */ + continue; + } + + if ( !BER_BVISNULL( &ctrls[ i ]->ldctl_value ) ) { + op->o_tmpfree( ctrls[ i ]->ldctl_value.bv_val, op->o_tmpmemctx ); + } + } + + op->o_tmpfree( ctrls, op->o_tmpmemctx ); + } + + *pctrls = NULL; + + return 0; +} + +int +ldap_back_conn2str( const ldapconn_base_t *lc, char *buf, ber_len_t buflen ) +{ + char tbuf[ SLAP_TEXT_BUFLEN ]; + char *ptr = buf, *end = buf + buflen; + int len; + + if ( ptr + sizeof("conn=") > end ) return -1; + ptr = lutil_strcopy( ptr, "conn=" ); + + len = ldap_back_connid2str( lc, ptr, (ber_len_t)(end - ptr) ); + ptr += len; + if ( ptr >= end ) return -1; + + if ( !BER_BVISNULL( &lc->lcb_local_ndn ) ) { + if ( ptr + sizeof(" DN=\"\"") + lc->lcb_local_ndn.bv_len > end ) return -1; + ptr = lutil_strcopy( ptr, " DN=\"" ); + ptr = lutil_strncopy( ptr, lc->lcb_local_ndn.bv_val, lc->lcb_local_ndn.bv_len ); + *ptr++ = '"'; + } + + if ( lc->lcb_create_time != 0 ) { + len = snprintf( tbuf, sizeof(tbuf), "%ld", lc->lcb_create_time ); + if ( ptr + sizeof(" created=") + len >= end ) return -1; + ptr = lutil_strcopy( ptr, " created=" ); + ptr = lutil_strcopy( ptr, tbuf ); + } + + if ( lc->lcb_time != 0 ) { + len = snprintf( tbuf, sizeof(tbuf), "%ld", lc->lcb_time ); + if ( ptr + sizeof(" modified=") + len >= end ) return -1; + ptr = lutil_strcopy( ptr, " modified=" ); + ptr = lutil_strcopy( ptr, tbuf ); + } + + len = snprintf( tbuf, sizeof(tbuf), "%u", lc->lcb_refcnt ); + if ( ptr + sizeof(" refcnt=") + len >= end ) return -1; + ptr = lutil_strcopy( ptr, " refcnt=" ); + ptr = lutil_strcopy( ptr, tbuf ); + + return ptr - buf; +} + +int +ldap_back_connid2str( const ldapconn_base_t *lc, char *buf, ber_len_t buflen ) +{ + static struct berval conns[] = { + BER_BVC("ROOTDN"), + BER_BVC("ROOTDN-TLS"), + BER_BVC("ANON"), + BER_BVC("ANON-TLS"), + BER_BVC("BIND"), + BER_BVC("BIND-TLS"), + BER_BVNULL + }; + + int len = 0; + + if ( LDAP_BACK_PCONN_ISPRIV( (const ldapconn_t *)lc ) ) { + long cid; + struct berval *bv; + + cid = (long)lc->lcb_conn; + assert( cid >= LDAP_BACK_PCONN_FIRST && cid < LDAP_BACK_PCONN_LAST ); + + bv = &conns[ cid ]; + + if ( bv->bv_len >= buflen ) { + return bv->bv_len + 1; + } + + len = bv->bv_len; + lutil_strncopy( buf, bv->bv_val, bv->bv_len + 1 ); + + } else { + len = snprintf( buf, buflen, "%lu", lc->lcb_conn->c_connid ); + } + + return len; +} + +void * +ldap_back_conn_expire_fn( void *ctx, void *arg ) +{ + struct re_s *rtask = arg; + ldapinfo_t *li = (ldapinfo_t *)rtask->arg; + ldap_back_conn_prune( li ); + + return NULL; +} + +/* Pick which expires first: connection TTL or idle timeout */ +static time_t +ldap_back_conn_expire_time( ldapinfo_t *li, ldapconn_t *lc) { + if ( li->li_conn_ttl != 0 && li->li_idle_timeout != 0 ) { + return ( lc->lc_create_time + li->li_conn_ttl ) < ( lc->lc_time + li->li_idle_timeout ) ? + ( lc->lc_create_time + li->li_conn_ttl ) : ( lc->lc_time + li->li_idle_timeout ); + } else if ( li->li_conn_ttl != 0 ) { + return lc->lc_create_time + li->li_conn_ttl; + } else if ( li->li_idle_timeout != 0 ) { + return lc->lc_time + li->li_idle_timeout; + } + return -1; +} + +static void +ldap_back_conn_prune( ldapinfo_t *li ) +{ + time_t now = slap_get_time(); + time_t next_timeout = -1; /* -1 means uninitialized */ + TAvlnode *edge; + int c; + + /* + * Iterate though connections and close those that are pass the expiry time. + * Also calculate the time for next connection to to expire. + */ + ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex ); + + for ( c = LDAP_BACK_PCONN_FIRST; c < LDAP_BACK_PCONN_LAST; c++ ) { + ldapconn_t *lc = LDAP_TAILQ_FIRST( &li->li_conn_priv[ c ].lic_priv ); + + while ( lc ) { + ldapconn_t *next = LDAP_TAILQ_NEXT( lc, lc_q ); + time_t conn_expires = ldap_back_conn_expire_time( li, lc ); + + if ( now >= conn_expires ) { + if ( lc->lc_refcnt == 0 ) { + Debug( LDAP_DEBUG_TRACE, + "ldap_back_conn_prune: closing expired connection lc=%p\n", + lc ); + ldap_back_freeconn( li, lc, 0 ); + } else { + Debug( LDAP_DEBUG_TRACE, + "ldap_back_conn_prune: tainting expired connection lc=%p\n", + lc ); + LDAP_BACK_CONN_TAINTED_SET( lc ); + } + } else if ( next_timeout == -1 || conn_expires < next_timeout ) { + /* next_timeout was not yet initialized or current connection expires sooner */ + next_timeout = conn_expires; + } + + lc = next; + } + } + + edge = ldap_tavl_end( li->li_conninfo.lai_tree, TAVL_DIR_LEFT ); + while ( edge ) { + TAvlnode *next = ldap_tavl_next( edge, TAVL_DIR_RIGHT ); + ldapconn_t *lc = (ldapconn_t *)edge->avl_data; + time_t conn_expires = ldap_back_conn_expire_time( li, lc ); + + if ( now >= conn_expires ) { + if ( lc->lc_refcnt == 0 ) { + Debug( LDAP_DEBUG_TRACE, + "ldap_back_conn_prune: closing expired connection lc=%p\n", + lc ); + ldap_back_freeconn( li, lc, 0 ); + } else { + Debug( LDAP_DEBUG_TRACE, + "ldap_back_conn_prune: tainting expired connection lc=%p\n", + lc ); + LDAP_BACK_CONN_TAINTED_SET( lc ); + } + } else if ( next_timeout == -1 || conn_expires < next_timeout ) { + next_timeout = conn_expires; + } + + edge = next; + } + + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + + /* Reschedule for next timeout or cancel the task */ + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + if ( next_timeout > 0 ) { + if ( ldap_pvt_runqueue_isrunning( &slapd_rq, li->li_conn_expire_task ) ) { + ldap_pvt_runqueue_stoptask( &slapd_rq, li->li_conn_expire_task ); + } + li->li_conn_expire_task->interval.tv_sec = next_timeout - now; + ldap_pvt_runqueue_resched( &slapd_rq, li->li_conn_expire_task, 0 ); + + /* + * The thread that handles runqueue might have already processed all tasks + * before we insertered new task or rescheduled the existing task with new + * timeout period. Wake it up to ensure that the task will be picked up. + */ + slap_wake_listener(); + Debug( LDAP_DEBUG_TRACE, + "ldap_back_conn_prune: scheduled connection expiry timer to %ld sec\n", + li->li_conn_expire_task->interval.tv_sec ); + } else if ( next_timeout == -1 && li->li_conn_expire_task != NULL ) { + if ( ldap_pvt_runqueue_isrunning( &slapd_rq, li->li_conn_expire_task ) ) { + ldap_pvt_runqueue_stoptask( &slapd_rq, li->li_conn_expire_task ); + } + ldap_pvt_runqueue_remove( &slapd_rq, li->li_conn_expire_task ); + li->li_conn_expire_task = NULL; + } + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + + return; +} + +static void +ldap_back_schedule_conn_expiry( ldapinfo_t *li, ldapconn_t *lc ) { + /* Do nothing if timeouts are not set. */ + if ( li->li_conn_ttl == 0 && li->li_idle_timeout == 0 ) { + return; + } + + /* + * If connection expire task is not running, create it and schedule for + * timeout of this connection. + * + * If the task is already running, this connection cannot be next one + * to expire and therefore timeout does not need to be re-calculated. + */ + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + if ( li->li_conn_expire_task == NULL ) { + li->li_conn_expire_task = ldap_pvt_runqueue_insert( &slapd_rq, + ldap_back_conn_expire_time( li, lc ) - slap_get_time(), + ldap_back_conn_expire_fn, li, "ldap_back_conn_expire_fn", + "ldap_back_conn_expire_timer" ); + slap_wake_listener(); + Debug( LDAP_DEBUG_TRACE, + "ldap_back_conn_prune: scheduled connection expiry timer to %ld sec\n", + li->li_conn_expire_task->interval.tv_sec ); + } + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + + return; +}
\ No newline at end of file diff --git a/servers/slapd/back-ldap/chain.c b/servers/slapd/back-ldap/chain.c new file mode 100644 index 0000000..d6ffd1a --- /dev/null +++ b/servers/slapd/back-ldap/chain.c @@ -0,0 +1,2356 @@ +/* chain.c - chain LDAP operations */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2022 The OpenLDAP Foundation. + * Portions Copyright 2003 Howard Chu. + * 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>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software. + * This work was subsequently modified by Pierangelo Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "lutil.h" +#include "slap.h" +#include "back-ldap.h" +#include "slap-config.h" + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR +#define SLAP_CHAINING_DEFAULT LDAP_CHAINING_PREFERRED +#define SLAP_CH_RESOLVE_SHIFT SLAP_CONTROL_SHIFT +#define SLAP_CH_RESOLVE_MASK (0x3 << SLAP_CH_RESOLVE_SHIFT) +#define SLAP_CH_RESOLVE_CHAINING_PREFERRED (LDAP_CHAINING_PREFERRED << SLAP_CH_RESOLVE_SHIFT) +#define SLAP_CH_RESOLVE_CHAINING_REQUIRED (LDAP_CHAINING_REQUIRED << SLAP_CH_RESOLVE_SHIFT) +#define SLAP_CH_RESOLVE_REFERRALS_PREFERRED (LDAP_REFERRALS_PREFERRED << SLAP_CH_RESOLVE_SHIFT) +#define SLAP_CH_RESOLVE_REFERRALS_REQUIRED (LDAP_REFERRALS_REQUIRED << SLAP_CH_RESOLVE_SHIFT) +#define SLAP_CH_RESOLVE_DEFAULT (SLAP_CHAINING_DEFAULT << SLAP_CH_RESOLVE_SHIFT) +#define SLAP_CH_CONTINUATION_SHIFT (SLAP_CH_RESOLVE_SHIFT + 2) +#define SLAP_CH_CONTINUATION_MASK (0x3 << SLAP_CH_CONTINUATION_SHIFT) +#define SLAP_CH_CONTINUATION_CHAINING_PREFERRED (LDAP_CHAINING_PREFERRED << SLAP_CH_CONTINUATION_SHIFT) +#define SLAP_CH_CONTINUATION_CHAINING_REQUIRED (LDAP_CHAINING_REQUIRED << SLAP_CH_CONTINUATION_SHIFT) +#define SLAP_CH_CONTINUATION_REFERRALS_PREFERRED (LDAP_REFERRALS_PREFERRED << SLAP_CH_CONTINUATION_SHIFT) +#define SLAP_CH_CONTINUATION_REFERRALS_REQUIRED (LDAP_REFERRALS_REQUIRED << SLAP_CH_CONTINUATION_SHIFT) +#define SLAP_CH_CONTINUATION_DEFAULT (SLAP_CHAINING_DEFAULT << SLAP_CH_CONTINUATION_SHIFT) + +#define o_chaining o_ctrlflag[sc_chainingBehavior] +#define get_chaining(op) ((op)->o_chaining & SLAP_CONTROL_MASK) +#define get_chainingBehavior(op) ((op)->o_chaining & (SLAP_CH_RESOLVE_MASK|SLAP_CH_CONTINUATION_MASK)) +#define get_resolveBehavior(op) ((op)->o_chaining & SLAP_CH_RESOLVE_MASK) +#define get_continuationBehavior(op) ((op)->o_chaining & SLAP_CH_CONTINUATION_MASK) + +static int sc_chainingBehavior; +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + +typedef enum { + LDAP_CH_NONE = 0, + LDAP_CH_RES, + LDAP_CH_ERR +} ldap_chain_status_t; + +static BackendInfo *lback; + +typedef struct ldap_chain_t { + /* + * A "template" ldapinfo_t gets all common configuration items; + * then, for each configured URI, an entry is created in the tree; + * all the specific configuration items get in the current URI + * structure. + * + * Then, for each referral, extract the URI and lookup the + * related structure. If configured to do so, allow URIs + * not found in the structure to create a temporary one + * that chains anonymously; maybe it can also be added to + * the tree? Should be all configurable. + */ + + /* "common" configuration info (anything occurring before an "uri") */ + ldapinfo_t *lc_common_li; + + /* current configuration info */ + ldapinfo_t *lc_cfg_li; + + /* tree of configured[/generated?] "uri" info */ + ldap_avl_info_t lc_lai; + + /* max depth in nested referrals chaining */ + int lc_max_depth; + + unsigned lc_flags; +#define LDAP_CHAIN_F_NONE (0x00U) +#define LDAP_CHAIN_F_CHAINING (0x01U) +#define LDAP_CHAIN_F_CACHE_URI (0x02U) +#define LDAP_CHAIN_F_RETURN_ERR (0x04U) + +#define LDAP_CHAIN_ISSET(lc, f) ( ( (lc)->lc_flags & (f) ) == (f) ) +#define LDAP_CHAIN_CHAINING( lc ) LDAP_CHAIN_ISSET( (lc), LDAP_CHAIN_F_CHAINING ) +#define LDAP_CHAIN_CACHE_URI( lc ) LDAP_CHAIN_ISSET( (lc), LDAP_CHAIN_F_CACHE_URI ) +#define LDAP_CHAIN_RETURN_ERR( lc ) LDAP_CHAIN_ISSET( (lc), LDAP_CHAIN_F_RETURN_ERR ) + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + LDAPControl lc_chaining_ctrl; + char lc_chaining_ctrlflag; +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ +} ldap_chain_t; + +static int ldap_chain_db_init_common( BackendDB *be ); +static int ldap_chain_db_init_one( BackendDB *be ); +static int ldap_chain_db_open_one( BackendDB *be ); +#define ldap_chain_db_close_one(be) (0) +#define ldap_chain_db_destroy_one(be, rs) (lback)->bi_db_destroy( (be), (rs) ) + +typedef struct ldap_chain_cb_t { + ldap_chain_status_t lb_status; + ldap_chain_t *lb_lc; + slap_operation_t lb_op_type; + char *lb_text; + int lb_depth; +} ldap_chain_cb_t; + +static int +ldap_chain_op( + Operation *op, + SlapReply *rs, + slap_operation_t op_type, + BerVarray ref, + int depth ); + +static int +ldap_chain_search( + Operation *op, + SlapReply *rs, + BerVarray ref, + int depth ); + +static slap_overinst ldapchain; + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR +static int +chaining_control_add( + ldap_chain_t *lc, + Operation *op, + LDAPControl ***oldctrlsp ) +{ + LDAPControl **ctrls = NULL; + int c = 0; + + *oldctrlsp = op->o_ctrls; + + /* default chaining control not defined */ + if ( !LDAP_CHAIN_CHAINING( lc ) ) { + return 0; + } + + /* already present */ + if ( get_chaining( op ) > SLAP_CONTROL_IGNORED ) { + return 0; + } + + /* FIXME: check other incompatibilities */ + + /* add to other controls */ + if ( op->o_ctrls ) { + for ( c = 0; op->o_ctrls[ c ]; c++ ) + /* count them */ ; + } + + ctrls = ch_calloc( sizeof( LDAPControl *), c + 2 ); + ctrls[ 0 ] = &lc->lc_chaining_ctrl; + if ( op->o_ctrls ) { + for ( c = 0; op->o_ctrls[ c ]; c++ ) { + ctrls[ c + 1 ] = op->o_ctrls[ c ]; + } + } + ctrls[ c + 1 ] = NULL; + + op->o_ctrls = ctrls; + + op->o_chaining = lc->lc_chaining_ctrlflag; + + return 0; +} + +static int +chaining_control_remove( + Operation *op, + LDAPControl ***oldctrlsp ) +{ + LDAPControl **oldctrls = *oldctrlsp; + + /* we assume that the first control is the chaining control + * added by the chain overlay, so it's the only one we explicitly + * free */ + if ( op->o_ctrls != oldctrls ) { + if ( op->o_ctrls != NULL ) { + assert( op->o_ctrls[ 0 ] != NULL ); + + free( op->o_ctrls ); + + op->o_chaining = 0; + } + op->o_ctrls = oldctrls; + } + + *oldctrlsp = NULL; + + return 0; +} +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + +static int +ldap_chain_uri_cmp( const void *c1, const void *c2 ) +{ + const ldapinfo_t *li1 = (const ldapinfo_t *)c1; + const ldapinfo_t *li2 = (const ldapinfo_t *)c2; + + assert( li1->li_bvuri != NULL ); + assert( !BER_BVISNULL( &li1->li_bvuri[ 0 ] ) ); + assert( BER_BVISNULL( &li1->li_bvuri[ 1 ] ) ); + + assert( li2->li_bvuri != NULL ); + assert( !BER_BVISNULL( &li2->li_bvuri[ 0 ] ) ); + assert( BER_BVISNULL( &li2->li_bvuri[ 1 ] ) ); + + return ber_bvcmp( &li1->li_bvuri[ 0 ], &li2->li_bvuri[ 0 ] ); +} + +static int +ldap_chain_uri_dup( void *c1, void *c2 ) +{ + ldapinfo_t *li1 = (ldapinfo_t *)c1; + ldapinfo_t *li2 = (ldapinfo_t *)c2; + + assert( li1->li_bvuri != NULL ); + assert( !BER_BVISNULL( &li1->li_bvuri[ 0 ] ) ); + assert( BER_BVISNULL( &li1->li_bvuri[ 1 ] ) ); + + assert( li2->li_bvuri != NULL ); + assert( !BER_BVISNULL( &li2->li_bvuri[ 0 ] ) ); + assert( BER_BVISNULL( &li2->li_bvuri[ 1 ] ) ); + + if ( ber_bvcmp( &li1->li_bvuri[ 0 ], &li2->li_bvuri[ 0 ] ) == 0 ) { + return -1; + } + + return 0; +} + +/* + * Search specific response that strips entryDN from entries + */ +static int +ldap_chain_cb_search_response( Operation *op, SlapReply *rs ) +{ + ldap_chain_cb_t *lb = (ldap_chain_cb_t *)op->o_callback->sc_private; + + assert( op->o_tag == LDAP_REQ_SEARCH ); + + /* if in error, don't proceed any further */ + if ( lb->lb_status == LDAP_CH_ERR ) { + return 0; + } + + if ( rs->sr_type == REP_SEARCH ) { + Attribute **ap = &rs->sr_entry->e_attrs; + + for ( ; *ap != NULL; ap = &(*ap)->a_next ) { + /* will be generated later by frontend + * (a cleaner solution would be that + * the frontend checks if it already exists */ + if ( ad_cmp( (*ap)->a_desc, slap_schema.si_ad_entryDN ) == 0 ) + { + Attribute *a = *ap; + + *ap = (*ap)->a_next; + attr_free( a ); + + /* there SHOULD be one only! */ + break; + } + } + + /* tell the frontend not to add generated + * operational attributes */ + rs->sr_flags |= REP_NO_OPERATIONALS; + + return SLAP_CB_CONTINUE; + + } else if ( rs->sr_type == REP_SEARCHREF ) { + /* if we get it here, it means the library was unable + * to chase the referral... */ + if ( lb->lb_depth < lb->lb_lc->lc_max_depth && rs->sr_ref != NULL ) { + rs->sr_err = ldap_chain_search( op, rs, rs->sr_ref, lb->lb_depth ); + } + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + if ( rs->sr_err == LDAP_REFERRAL && get_chaining( op ) > SLAP_CONTROL_IGNORED ) { + switch ( get_continuationBehavior( op ) ) { + case SLAP_CH_RESOLVE_CHAINING_REQUIRED: + lb->lb_status = LDAP_CH_ERR; + return rs->sr_err = LDAP_X_CANNOT_CHAIN; + + default: + break; + } + } +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + return SLAP_CB_CONTINUE; + + } else if ( rs->sr_type == REP_RESULT ) { + if ( rs->sr_err == LDAP_REFERRAL + && lb->lb_depth < lb->lb_lc->lc_max_depth + && rs->sr_ref != NULL ) + { + rs->sr_err = ldap_chain_op( op, rs, lb->lb_op_type, + rs->sr_ref, lb->lb_depth ); + } + + /* back-ldap tried to send result */ + lb->lb_status = LDAP_CH_RES; + /* don't let other callbacks run, this isn't + * the real result for this op. + */ + op->o_callback->sc_next = NULL; + } + + return 0; +} + +/* + * Dummy response that simply traces if back-ldap tried to send + * anything to the client + */ +static int +ldap_chain_cb_response( Operation *op, SlapReply *rs ) +{ + ldap_chain_cb_t *lb = (ldap_chain_cb_t *)op->o_callback->sc_private; + + /* if in error, don't proceed any further */ + if ( lb->lb_status == LDAP_CH_ERR ) { + return 0; + } + + if ( rs->sr_type == REP_RESULT ) { +retry:; + switch ( rs->sr_err ) { + case LDAP_COMPARE_TRUE: + case LDAP_COMPARE_FALSE: + if ( op->o_tag != LDAP_REQ_COMPARE ) { + return rs->sr_err; + } + /* fallthru */ + + case LDAP_SUCCESS: + lb->lb_status = LDAP_CH_RES; + break; + + case LDAP_REFERRAL: + if ( lb->lb_depth < lb->lb_lc->lc_max_depth && rs->sr_ref != NULL ) { + rs->sr_err = ldap_chain_op( op, rs, lb->lb_op_type, + rs->sr_ref, lb->lb_depth ); + goto retry; + } + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + if ( get_chaining( op ) > SLAP_CONTROL_IGNORED ) { + switch ( get_continuationBehavior( op ) ) { + case SLAP_CH_RESOLVE_CHAINING_REQUIRED: + lb->lb_status = LDAP_CH_ERR; + return rs->sr_err = LDAP_X_CANNOT_CHAIN; + + default: + break; + } + } +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + break; + + default: + /* remember the text before it's freed in ldap_back_op_result */ + if ( lb->lb_text ) { + ber_memfree_x( lb->lb_text, op->o_tmpmemctx ); + } + lb->lb_text = ber_strdup_x( rs->sr_text, op->o_tmpmemctx ); + return rs->sr_err; + } + + } else if ( op->o_tag == LDAP_REQ_SEARCH && rs->sr_type == REP_SEARCH ) + { + /* strip the entryDN attribute, but keep returning results */ + (void)ldap_chain_cb_search_response( op, rs ); + } + + return SLAP_CB_CONTINUE; +} + +static int +ldap_chain_op( + Operation *op, + SlapReply *rs, + slap_operation_t op_type, + BerVarray ref, + int depth ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + ldap_chain_cb_t *lb = (ldap_chain_cb_t *)op->o_callback->sc_private; + ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private; + struct berval odn = op->o_req_dn, + ondn = op->o_req_ndn; + ldapinfo_t li = { 0 }, *lip = NULL; + struct berval bvuri[ 2 ] = { { 0 } }; + + /* NOTE: returned if ref is empty... */ + int rc = LDAP_OTHER, + first_rc; + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + LDAPControl **ctrls = NULL; + + (void)chaining_control_add( lc, op, &ctrls ); +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + + li.li_bvuri = bvuri; + first_rc = -1; + for ( ; !BER_BVISNULL( ref ); ref++ ) { + SlapReply rs2 = { 0 }; + LDAPURLDesc *srv = NULL; + req_search_s save_oq_search = op->oq_search, + tmp_oq_search = { 0 }; + struct berval dn = BER_BVNULL, + pdn = odn, + ndn = ondn; + char *filter = NULL; + int temporary = 0; + int free_dn = 0; + + /* We're setting the URI of the first referral; + * what if there are more? + +Document: RFC 4511 + +4.1.10. Referral + ... + If the client wishes to progress the operation, it MUST follow the + referral by contacting one of the supported services. If multiple + URIs are present, the client assumes that any supported URI may be + used to progress the operation. + + * so we actually need to follow exactly one, + * and we can assume any is fine. + */ + + /* parse reference and use + * proto://[host][:port]/ only */ + rc = ldap_url_parse_ext( ref->bv_val, &srv, LDAP_PVT_URL_PARSE_NONE ); + if ( rc != LDAP_URL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_op: unable to parse ref=\"%s\"\n", + op->o_log_prefix, ref->bv_val ); + + /* try next */ + rc = LDAP_OTHER; + continue; + } + + if ( op->o_tag == LDAP_REQ_SEARCH ) { + if ( srv->lud_scope != LDAP_SCOPE_DEFAULT ) { + /* RFC 4511: if scope is present, use it */ + tmp_oq_search.rs_scope = srv->lud_scope; + + } else { + /* RFC 4511: if scope is absent, use original */ + tmp_oq_search.rs_scope = op->ors_scope; + } + } + + rc = LDAP_SUCCESS; + srv->lud_scope = LDAP_SCOPE_DEFAULT; + dn.bv_val = srv->lud_dn; + filter = srv->lud_filter; + + /* normalize DN */ + if ( srv->lud_dn == NULL || srv->lud_dn[0] == '\0' ) { + if ( srv->lud_dn == NULL ) { + srv->lud_dn = ""; + } + + } else { + ber_str2bv( srv->lud_dn, 0, 0, &dn ); + rc = dnPrettyNormal( NULL, &dn, &pdn, &ndn, op->o_tmpmemctx ); + if ( rc == LDAP_SUCCESS ) { + /* remove DN essentially because later on + * ldap_initialize() will parse the URL + * as a comma-separated URL list */ + srv->lud_dn = ""; + free_dn = 1; + } + } + + /* prepare filter */ + if ( rc == LDAP_SUCCESS && op->o_tag == LDAP_REQ_SEARCH ) { + /* filter */ + if ( srv->lud_filter != NULL + && srv->lud_filter[0] != '\0' + && strcasecmp( srv->lud_filter, "(objectClass=*)" ) != 0 ) + { + /* RFC 4511: if filter is present, use it; + * otherwise, use original */ + tmp_oq_search.rs_filter = str2filter_x( op, srv->lud_filter ); + if ( tmp_oq_search.rs_filter != NULL ) { + filter2bv_x( op, tmp_oq_search.rs_filter, &tmp_oq_search.rs_filterstr ); + + } else { + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_op: ref=\"%s\": unable to parse filter=\"%s\"\n", + op->o_log_prefix, ref->bv_val, srv->lud_filter ); + rc = LDAP_OTHER; + } + } + } + srv->lud_filter = NULL; + + if ( rc == LDAP_SUCCESS ) { + li.li_uri = ldap_url_desc2str( srv ); + } + + srv->lud_dn = dn.bv_val; + srv->lud_filter = filter; + ldap_free_urldesc( srv ); + + if ( rc != LDAP_SUCCESS ) { + /* try next */ + rc = LDAP_OTHER; + continue; + } + + if ( li.li_uri == NULL ) { + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_op: ref=\"%s\" unable to reconstruct URI\n", + op->o_log_prefix, ref->bv_val ); + + /* try next */ + rc = LDAP_OTHER; + goto further_cleanup; + } + + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_op: ref=\"%s\" -> \"%s\"\n", + op->o_log_prefix, ref->bv_val, li.li_uri ); + + op->o_req_dn = pdn; + op->o_req_ndn = ndn; + + if ( op->o_tag == LDAP_REQ_SEARCH ) { + op->ors_scope = tmp_oq_search.rs_scope; + if ( tmp_oq_search.rs_filter != NULL ) { + op->ors_filter = tmp_oq_search.rs_filter; + op->ors_filterstr = tmp_oq_search.rs_filterstr; + } + } + + ber_str2bv( li.li_uri, 0, 0, &li.li_bvuri[ 0 ] ); + + /* Searches for a ldapinfo in the avl tree */ + ldap_pvt_thread_mutex_lock( &lc->lc_lai.lai_mutex ); + lip = (ldapinfo_t *)ldap_tavl_find( lc->lc_lai.lai_tree, + (caddr_t)&li, ldap_chain_uri_cmp ); + ldap_pvt_thread_mutex_unlock( &lc->lc_lai.lai_mutex ); + + if ( lip != NULL ) { + op->o_bd->be_private = (void *)lip; + + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_op: ref=\"%s\": URI=\"%s\" found in cache\n", + op->o_log_prefix, ref->bv_val, li.li_uri ); + + } else { + rc = ldap_chain_db_init_one( op->o_bd ); + if ( rc != 0 ) { + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_op: ref=\"%s\" unable to init back-ldap for URI=\"%s\"\n", + op->o_log_prefix, ref->bv_val, li.li_uri ); + goto cleanup; + } + lip = (ldapinfo_t *)op->o_bd->be_private; + lip->li_uri = li.li_uri; + lip->li_bvuri = bvuri; + rc = ldap_chain_db_open_one( op->o_bd ); + if ( rc != 0 ) { + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_op: ref=\"%s\" unable to open back-ldap for URI=\"%s\"\n", + op->o_log_prefix, ref->bv_val, li.li_uri ); + lip->li_uri = NULL; + lip->li_bvuri = NULL; + (void)ldap_chain_db_destroy_one( op->o_bd, NULL); + goto cleanup; + } + + if ( LDAP_CHAIN_CACHE_URI( lc ) ) { + ldap_pvt_thread_mutex_lock( &lc->lc_lai.lai_mutex ); + if ( ldap_tavl_insert( &lc->lc_lai.lai_tree, + (caddr_t)lip, ldap_chain_uri_cmp, ldap_chain_uri_dup ) ) + { + /* someone just inserted another; + * don't bother, use this and then + * just free it */ + temporary = 1; + } + ldap_pvt_thread_mutex_unlock( &lc->lc_lai.lai_mutex ); + + } else { + temporary = 1; + } + + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_op: ref=\"%s\" %s\n", + op->o_log_prefix, ref->bv_val, temporary ? "temporary" : "caching" ); + } + + lb->lb_op_type = op_type; + lb->lb_depth = depth + 1; + + rc = (&lback->bi_op_bind)[ op_type ]( op, &rs2 ); + + /* note the first error */ + if ( first_rc == -1 ) { + first_rc = rc; + } + +cleanup:; + ldap_memfree( li.li_uri ); + li.li_uri = NULL; + + if ( temporary ) { + lip->li_uri = NULL; + lip->li_bvuri = NULL; + (void)ldap_chain_db_close_one( op->o_bd ); + (void)ldap_chain_db_destroy_one( op->o_bd, NULL ); + } + +further_cleanup:; + if ( op->o_req_dn.bv_val == pdn.bv_val ) { + op->o_req_dn = odn; + op->o_req_ndn = ondn; + } + + if ( free_dn ) { + op->o_tmpfree( pdn.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( ndn.bv_val, op->o_tmpmemctx ); + } + + if ( op->o_tag == LDAP_REQ_SEARCH ) { + if ( tmp_oq_search.rs_filter != NULL ) { + filter_free_x( op, tmp_oq_search.rs_filter, 1 ); + } + + if ( !BER_BVISNULL( &tmp_oq_search.rs_filterstr ) ) { + slap_sl_free( tmp_oq_search.rs_filterstr.bv_val, op->o_tmpmemctx ); + } + + op->oq_search = save_oq_search; + } + + if ( rc == LDAP_SUCCESS && rs2.sr_err == LDAP_SUCCESS ) { + *rs = rs2; + break; + } + + rc = rs2.sr_err; + } + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + (void)chaining_control_remove( op, &ctrls ); +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + + if ( rc != LDAP_SUCCESS && first_rc > 0 ) { + rc = first_rc; + } + + return rc; +} + +static int +ldap_chain_search( + Operation *op, + SlapReply *rs, + BerVarray ref, + int depth ) + +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + ldap_chain_cb_t *lb = (ldap_chain_cb_t *)op->o_callback->sc_private; + ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private; + ldapinfo_t li = { 0 }, *lip = NULL; + struct berval bvuri[ 2 ] = { { 0 } }; + + struct berval odn = op->o_req_dn, + ondn = op->o_req_ndn; + Entry *save_entry = rs->sr_entry; + slap_mask_t save_flags = rs->sr_flags; + + int rc = LDAP_OTHER, + first_rc = -1; + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + LDAPControl **ctrls = NULL; + + (void)chaining_control_add( lc, op, &ctrls ); +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + + assert( rs->sr_type == REP_SEARCHREF ); + + rs->sr_type = REP_SEARCH; + + /* if we parse the URI then by no means + * we can cache stuff or reuse connections, + * because in back-ldap there's no caching + * based on the URI value, which is supposed + * to be set once for all (correct?) */ + li.li_bvuri = bvuri; + for ( ; !BER_BVISNULL( &ref[0] ); ref++ ) { + SlapReply rs2 = { REP_RESULT }; + LDAPURLDesc *srv; + req_search_s save_oq_search = op->oq_search, + tmp_oq_search = { 0 }; + struct berval dn, + pdn = op->o_req_dn, + ndn = op->o_req_ndn; + char *filter = NULL; + int temporary = 0; + int free_dn = 0; + + /* parse reference and use + * proto://[host][:port]/ only */ + rc = ldap_url_parse_ext( ref[0].bv_val, &srv, LDAP_PVT_URL_PARSE_NONE ); + if ( rc != LDAP_URL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_search: unable to parse ref=\"%s\"\n", + op->o_log_prefix, ref->bv_val ); + + /* try next */ + rs->sr_err = LDAP_OTHER; + continue; + } + + if ( srv->lud_scope != LDAP_SCOPE_DEFAULT ) { + /* RFC 4511: if scope is present, use it */ + tmp_oq_search.rs_scope = srv->lud_scope; + + } else { + /* RFC 4511: if scope is absent, use original */ + /* Section 4.5.3: if scope is onelevel, use base */ + if ( op->ors_scope == LDAP_SCOPE_ONELEVEL ) + tmp_oq_search.rs_scope = LDAP_SCOPE_BASE; + else + tmp_oq_search.rs_scope = op->ors_scope; + } + + rc = LDAP_SUCCESS; + srv->lud_scope = LDAP_SCOPE_DEFAULT; + dn.bv_val = srv->lud_dn; + filter = srv->lud_filter; + + /* normalize DN */ + if ( srv->lud_dn == NULL || srv->lud_dn[0] == '\0' ) { + if ( srv->lud_dn == NULL ) { + srv->lud_dn = ""; + } + + if ( save_entry != NULL ) { + /* use the "right" DN, if available */ + pdn = save_entry->e_name; + ndn = save_entry->e_nname; + } /* else leave the original req DN in place, if any RFC 4511 */ + + } else { + /* RFC 4511: if DN is present, use it */ + ber_str2bv( srv->lud_dn, 0, 0, &dn ); + rc = dnPrettyNormal( NULL, &dn, &pdn, &ndn, op->o_tmpmemctx ); + if ( rc == LDAP_SUCCESS ) { + /* remove DN essentially because later on + * ldap_initialize() will parse the URL + * as a comma-separated URL list */ + srv->lud_dn = ""; + free_dn = 1; + } + } + + /* prepare filter */ + if ( rc == LDAP_SUCCESS ) { + /* filter */ + if ( srv->lud_filter != NULL + && srv->lud_filter[0] != '\0' + && strcasecmp( srv->lud_filter, "(objectClass=*)" ) != 0 ) + { + /* RFC 4511: if filter is present, use it; + * otherwise, use original */ + tmp_oq_search.rs_filter = str2filter_x( op, srv->lud_filter ); + if ( tmp_oq_search.rs_filter != NULL ) { + filter2bv_x( op, tmp_oq_search.rs_filter, &tmp_oq_search.rs_filterstr ); + + } else { + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_search: ref=\"%s\": unable to parse filter=\"%s\"\n", + op->o_log_prefix, ref->bv_val, srv->lud_filter ); + rc = LDAP_OTHER; + } + } + } + srv->lud_filter = NULL; + + if ( rc == LDAP_SUCCESS ) { + li.li_uri = ldap_url_desc2str( srv ); + } + + srv->lud_dn = dn.bv_val; + srv->lud_filter = filter; + ldap_free_urldesc( srv ); + + if ( rc != LDAP_SUCCESS || li.li_uri == NULL ) { + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_search: ref=\"%s\" unable to reconstruct URI\n", + op->o_log_prefix, ref->bv_val ); + + /* try next */ + rc = LDAP_OTHER; + goto further_cleanup; + } + + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_search: ref=\"%s\" -> \"%s\"\n", + op->o_log_prefix, ref->bv_val, li.li_uri ); + + op->o_req_dn = pdn; + op->o_req_ndn = ndn; + op->ors_scope = tmp_oq_search.rs_scope; + if ( tmp_oq_search.rs_filter != NULL ) { + op->ors_filter = tmp_oq_search.rs_filter; + op->ors_filterstr = tmp_oq_search.rs_filterstr; + } + + ber_str2bv( li.li_uri, 0, 0, &li.li_bvuri[ 0 ] ); + + /* Searches for a ldapinfo in the avl tree */ + ldap_pvt_thread_mutex_lock( &lc->lc_lai.lai_mutex ); + lip = (ldapinfo_t *)ldap_tavl_find( lc->lc_lai.lai_tree, + (caddr_t)&li, ldap_chain_uri_cmp ); + ldap_pvt_thread_mutex_unlock( &lc->lc_lai.lai_mutex ); + + if ( lip != NULL ) { + op->o_bd->be_private = (void *)lip; + + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_search: ref=\"%s\": URI=\"%s\" found in cache\n", + op->o_log_prefix, ref->bv_val, li.li_uri ); + + } else { + /* if none is found, create a temporary... */ + rc = ldap_chain_db_init_one( op->o_bd ); + if ( rc != 0 ) { + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_search: ref=\"%s\" unable to init back-ldap for URI=\"%s\"\n", + op->o_log_prefix, ref->bv_val, li.li_uri ); + goto cleanup; + } + lip = (ldapinfo_t *)op->o_bd->be_private; + lip->li_uri = li.li_uri; + lip->li_bvuri = bvuri; + rc = ldap_chain_db_open_one( op->o_bd ); + if ( rc != 0 ) { + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_search: ref=\"%s\" unable to open back-ldap for URI=\"%s\"\n", + op->o_log_prefix, ref->bv_val, li.li_uri ); + lip->li_uri = NULL; + lip->li_bvuri = NULL; + (void)ldap_chain_db_destroy_one( op->o_bd, NULL ); + goto cleanup; + } + + if ( LDAP_CHAIN_CACHE_URI( lc ) ) { + ldap_pvt_thread_mutex_lock( &lc->lc_lai.lai_mutex ); + if ( ldap_tavl_insert( &lc->lc_lai.lai_tree, + (caddr_t)lip, ldap_chain_uri_cmp, ldap_chain_uri_dup ) ) + { + /* someone just inserted another; + * don't bother, use this and then + * just free it */ + temporary = 1; + } + ldap_pvt_thread_mutex_unlock( &lc->lc_lai.lai_mutex ); + + } else { + temporary = 1; + } + + Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_search: ref=\"%s\" %s\n", + op->o_log_prefix, ref->bv_val, temporary ? "temporary" : "caching" ); + } + + lb->lb_op_type = op_search; + lb->lb_depth = depth + 1; + + /* FIXME: should we also copy filter and scope? + * according to RFC3296, no */ + rc = lback->bi_op_search( op, &rs2 ); + if ( first_rc == -1 ) { + first_rc = rc; + } + +cleanup:; + ldap_memfree( li.li_uri ); + li.li_uri = NULL; + + if ( temporary ) { + lip->li_uri = NULL; + lip->li_bvuri = NULL; + (void)ldap_chain_db_close_one( op->o_bd ); + (void)ldap_chain_db_destroy_one( op->o_bd, NULL ); + } + +further_cleanup:; + if ( op->o_req_dn.bv_val == pdn.bv_val ) { + op->o_req_dn = odn; + op->o_req_ndn = ondn; + } + + if ( free_dn ) { + op->o_tmpfree( pdn.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( ndn.bv_val, op->o_tmpmemctx ); + } + + if ( tmp_oq_search.rs_filter != NULL ) { + filter_free_x( op, tmp_oq_search.rs_filter, 1 ); + } + + if ( !BER_BVISNULL( &tmp_oq_search.rs_filterstr ) ) { + slap_sl_free( tmp_oq_search.rs_filterstr.bv_val, op->o_tmpmemctx ); + } + + op->oq_search = save_oq_search; + + if ( rc == LDAP_SUCCESS && rs2.sr_err == LDAP_SUCCESS ) { + *rs = rs2; + break; + } + + rc = rs2.sr_err; + } + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + (void)chaining_control_remove( op, &ctrls ); +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + + rs->sr_type = REP_SEARCHREF; + rs->sr_entry = save_entry; + rs->sr_flags = save_flags; + + if ( rc != LDAP_SUCCESS ) { + /* couldn't chase any of the referrals */ + if ( first_rc != -1 ) { + rc = first_rc; + + } else { + rc = SLAP_CB_CONTINUE; + } + } + + return rc; +} + +static int +ldap_chain_response( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private; + BackendDB db, *bd = op->o_bd; + ldap_chain_cb_t lb = { 0 }; + slap_callback *sc = op->o_callback, + sc2 = { 0 }; + int rc = 0; + const char *text = NULL; + const char *matched; + BerVarray ref; + slap_mask_t flags = 0; + struct berval ndn = op->o_ndn; + + int sr_err = rs->sr_err; + slap_reply_t sr_type = rs->sr_type; +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + slap_mask_t chain_mask = 0; + ber_len_t chain_shift = 0; +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + + if ( rs->sr_err != LDAP_REFERRAL && rs->sr_type != REP_SEARCHREF ) { + return SLAP_CB_CONTINUE; + } + if ( !rs->sr_ref ) { + return SLAP_CB_CONTINUE; + } + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + if ( rs->sr_err == LDAP_REFERRAL && get_chaining( op ) > SLAP_CONTROL_IGNORED ) { + switch ( get_resolveBehavior( op ) ) { + case SLAP_CH_RESOLVE_REFERRALS_PREFERRED: + case SLAP_CH_RESOLVE_REFERRALS_REQUIRED: + return SLAP_CB_CONTINUE; + + default: + chain_mask = SLAP_CH_RESOLVE_MASK; + chain_shift = SLAP_CH_RESOLVE_SHIFT; + break; + } + + } else if ( rs->sr_type == REP_SEARCHREF && get_chaining( op ) > SLAP_CONTROL_IGNORED ) { + switch ( get_continuationBehavior( op ) ) { + case SLAP_CH_CONTINUATION_REFERRALS_PREFERRED: + case SLAP_CH_CONTINUATION_REFERRALS_REQUIRED: + return SLAP_CB_CONTINUE; + + default: + chain_mask = SLAP_CH_CONTINUATION_MASK; + chain_shift = SLAP_CH_CONTINUATION_SHIFT; + break; + } + } +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + + /* + * TODO: add checks on who/when chain operations; e.g.: + * a) what identities are authorized + * b) what request DN (e.g. only chain requests rooted at <DN>) + * c) what referral URIs + * d) what protocol scheme (e.g. only ldaps://) + * e) what ssf + */ + + db = *op->o_bd; + SLAP_DBFLAGS( &db ) &= ~SLAP_DBFLAG_MONITORING; + op->o_bd = &db; + + text = rs->sr_text; + rs->sr_text = NULL; + matched = rs->sr_matched; + rs->sr_matched = NULL; + ref = rs->sr_ref; + rs->sr_ref = NULL; + + flags = rs->sr_flags & (REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED); + rs->sr_flags &= ~flags; + + /* we need this to know if back-ldap returned any result */ + lb.lb_lc = lc; + sc2.sc_next = sc->sc_next; + sc2.sc_private = &lb; + sc2.sc_response = ldap_chain_cb_response; + op->o_callback = &sc2; + + /* Chaining can be performed by a privileged user on behalf + * of normal users, using the ProxyAuthz control, by exploiting + * the identity assertion feature of back-ldap; see idassert-* + * directives in slapd-ldap(5). + * + * FIXME: the idassert-authcDN is one, will it be fine regardless + * of the URI we obtain from the referral? + */ + + switch ( op->o_tag ) { + case LDAP_REQ_BIND: { + struct berval rndn = op->o_req_ndn; + Connection *conn = op->o_conn; + + /* FIXME: can we really get a referral for binds? */ + op->o_req_ndn = slap_empty_bv; + op->o_conn = NULL; + rc = ldap_chain_op( op, rs, op_bind, ref, 0 ); + op->o_req_ndn = rndn; + op->o_conn = conn; + } + break; + + case LDAP_REQ_ADD: + rc = ldap_chain_op( op, rs, op_add, ref, 0 ); + break; + + case LDAP_REQ_DELETE: + rc = ldap_chain_op( op, rs, op_delete, ref, 0 ); + break; + + case LDAP_REQ_MODRDN: + rc = ldap_chain_op( op, rs, op_modrdn, ref, 0 ); + break; + + case LDAP_REQ_MODIFY: + rc = ldap_chain_op( op, rs, op_modify, ref, 0 ); + break; + + case LDAP_REQ_COMPARE: + rc = ldap_chain_op( op, rs, op_compare, ref, 0 ); + if ( rs->sr_err == LDAP_COMPARE_TRUE || rs->sr_err == LDAP_COMPARE_FALSE ) { + rc = LDAP_SUCCESS; + } + break; + + case LDAP_REQ_SEARCH: + if ( rs->sr_type == REP_SEARCHREF ) { + sc2.sc_response = ldap_chain_cb_search_response; + rc = ldap_chain_search( op, rs, ref, 0 ); + + } else { + /* we might get here before any database actually + * performed a search; in those cases, we need + * to check limits, to make sure safe defaults + * are in place */ + if ( op->ors_limit != NULL || limits_check( op, rs ) == 0 ) { + rc = ldap_chain_op( op, rs, op_search, ref, 0 ); + } else { + rc = SLAP_CB_CONTINUE; + } + } + break; + + case LDAP_REQ_EXTENDED: + rc = ldap_chain_op( op, rs, op_extended, ref, 0 ); + /* FIXME: ldap_back_extended() by design + * doesn't send result; frontend is expected + * to send it... */ + /* FIXME: what about chaining? */ + if ( rc != SLAPD_ABANDON ) { + rs->sr_err = rc; + send_ldap_extended( op, rs ); + rc = LDAP_SUCCESS; + } + lb.lb_status = LDAP_CH_RES; + break; + + default: + rc = SLAP_CB_CONTINUE; + break; + } + + switch ( rc ) { + case SLAPD_ABANDON: + goto dont_chain; + + case LDAP_SUCCESS: + case LDAP_REFERRAL: + sr_err = rs->sr_err; + /* slapd-ldap sent response */ + if ( !op->o_abandon && lb.lb_status != LDAP_CH_RES ) { + /* FIXME: should we send response? */ + Debug( LDAP_DEBUG_ANY, + "%s: ldap_chain_response: " + "overlay should have sent result.\n", + op->o_log_prefix ); + } + break; + + default: +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + if ( lb.lb_status == LDAP_CH_ERR && rs->sr_err == LDAP_X_CANNOT_CHAIN ) { + goto cannot_chain; + } + + switch ( ( get_chainingBehavior( op ) & chain_mask ) >> chain_shift ) { + case LDAP_CHAINING_REQUIRED: +cannot_chain:; + op->o_callback = NULL; + send_ldap_error( op, rs, LDAP_X_CANNOT_CHAIN, + "operation cannot be completed without chaining" ); + goto dont_chain; + + default: +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + if ( LDAP_CHAIN_RETURN_ERR( lc ) ) { + sr_err = rs->sr_err = rc; + rs->sr_text = lb.lb_text; + rs->sr_type = sr_type; + + } else { + rc = SLAP_CB_CONTINUE; + rs->sr_err = sr_err; + rs->sr_type = sr_type; + rs->sr_text = text; + rs->sr_matched = matched; + rs->sr_ref = ref; + rs->sr_flags |= flags; + } +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + break; + } +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + } + + if ( lb.lb_status == LDAP_CH_NONE && rc != SLAPD_ABANDON ) { + /* give the remaining callbacks a chance */ + op->o_callback = sc->sc_next; + rc = rs->sr_err = slap_map_api2result( rs ); + send_ldap_result( op, rs ); + } + +dont_chain:; + rs->sr_err = sr_err; + rs->sr_type = sr_type; + rs->sr_text = text; + rs->sr_matched = matched; + rs->sr_ref = ref; + rs->sr_flags |= flags; + + op->o_bd = bd; + op->o_callback = sc; + op->o_ndn = ndn; + + if ( rs->sr_text == lb.lb_text ) { + rs->sr_text = NULL; + } + if ( lb.lb_text ) { + ber_memfree_x( lb.lb_text, op->o_tmpmemctx ); + } + + return rc; +} + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR +static int +ldap_chain_parse_ctrl( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ); + +static int +str2chain( const char *s ) +{ + if ( strcasecmp( s, "chainingPreferred" ) == 0 ) { + return LDAP_CHAINING_PREFERRED; + + } else if ( strcasecmp( s, "chainingRequired" ) == 0 ) { + return LDAP_CHAINING_REQUIRED; + + } else if ( strcasecmp( s, "referralsPreferred" ) == 0 ) { + return LDAP_REFERRALS_PREFERRED; + + } else if ( strcasecmp( s, "referralsRequired" ) == 0 ) { + return LDAP_REFERRALS_REQUIRED; + } + + return -1; +} +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + +/* + * configuration... + */ + +enum { + CH_CHAINING = 1, + CH_CACHE_URI, + CH_MAX_DEPTH, + CH_RETURN_ERR, + + CH_LAST +}; + +static ConfigDriver chain_cf_gen; +static ConfigCfAdd chain_cfadd; +static ConfigLDAPadd chain_ldadd; +#ifdef SLAP_CONFIG_DELETE +static ConfigLDAPdel chain_lddel; +#endif + +static ConfigTable chaincfg[] = { +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + { "chain-chaining", "args", + 2, 4, 0, ARG_MAGIC|ARG_BERVAL|CH_CHAINING, chain_cf_gen, + "( OLcfgOvAt:3.1 NAME 'olcChainingBehavior' " + "DESC 'Chaining behavior control parameters (draft-sermersheim-ldap-chaining)' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + { "chain-cache-uri", "TRUE/FALSE", + 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|CH_CACHE_URI, chain_cf_gen, + "( OLcfgOvAt:3.2 NAME 'olcChainCacheURI' " + "DESC 'Enables caching of URIs not present in configuration' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "chain-max-depth", "args", + 2, 2, 0, ARG_MAGIC|ARG_INT|CH_MAX_DEPTH, chain_cf_gen, + "( OLcfgOvAt:3.3 NAME 'olcChainMaxReferralDepth' " + "DESC 'max referral depth' " + "SYNTAX OMsInteger " + "EQUALITY integerMatch " + "SINGLE-VALUE )", NULL, NULL }, + { "chain-return-error", "TRUE/FALSE", + 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|CH_RETURN_ERR, chain_cf_gen, + "( OLcfgOvAt:3.4 NAME 'olcChainReturnError' " + "DESC 'Errors are returned instead of the original referral' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs chainocs[] = { + { "( OLcfgOvOc:3.1 " + "NAME 'olcChainConfig' " + "DESC 'Chain configuration' " + "SUP olcOverlayConfig " + "MAY ( " +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + "olcChainingBehavior $ " +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + "olcChainCacheURI $ " + "olcChainMaxReferralDepth $ " + "olcChainReturnError " + ") )", + Cft_Overlay, chaincfg, NULL, chain_cfadd }, + { "( OLcfgOvOc:3.2 " + "NAME 'olcChainDatabase' " + "DESC 'Chain remote server configuration' " + "AUXILIARY )", + Cft_Misc, NULL, chain_ldadd +#ifdef SLAP_CONFIG_DELETE + , NULL, chain_lddel +#endif + }, + { NULL, 0, NULL } +}; + +static int +chain_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *ca ) +{ + slap_overinst *on; + ldap_chain_t *lc; + + ldapinfo_t *li; + + AttributeDescription *ad = NULL; + Attribute *at; + const char *text; + + int rc; + + if ( p->ce_type != Cft_Overlay + || !p->ce_bi + || p->ce_bi->bi_cf_ocs != chainocs ) + { + return LDAP_CONSTRAINT_VIOLATION; + } + + on = (slap_overinst *)p->ce_bi; + lc = (ldap_chain_t *)on->on_bi.bi_private; + + assert( ca->be == NULL ); + ca->be = (BackendDB *)ch_calloc( 1, sizeof( BackendDB ) ); + + ca->be->bd_info = (BackendInfo *)on; + + rc = slap_str2ad( "olcDbURI", &ad, &text ); + assert( rc == LDAP_SUCCESS ); + + at = attr_find( e->e_attrs, ad ); +#if 0 + if ( lc->lc_common_li == NULL && at != NULL ) { + /* FIXME: we should generate an empty default entry + * if none is supplied */ + Debug( LDAP_DEBUG_ANY, "slapd-chain: " + "first underlying database \"%s\" " + "cannot contain attribute \"%s\".\n", + e->e_name.bv_val, ad->ad_cname.bv_val ); + rc = LDAP_CONSTRAINT_VIOLATION; + goto done; + + } else +#endif + if ( lc->lc_common_li != NULL && lc->lc_common_li != lc->lc_cfg_li && at == NULL ) { + /* FIXME: we should generate an empty default entry + * if none is supplied */ + Debug( LDAP_DEBUG_ANY, "slapd-chain: " + "subsequent underlying database \"%s\" " + "must contain attribute \"%s\".\n", + e->e_name.bv_val, ad->ad_cname.bv_val ); + rc = LDAP_CONSTRAINT_VIOLATION; + goto done; + } + + if ( lc->lc_common_li == NULL ) { + rc = ldap_chain_db_init_common( ca->be ); + if ( rc != 0 ) + goto fail; + li = ca->be->be_private; + lc->lc_common_li = lc->lc_cfg_li = li; + + } + rc = ldap_chain_db_init_one( ca->be ); + lc->lc_cfg_li = NULL; + + if ( rc != 0 ) { +fail: + Debug( LDAP_DEBUG_ANY, "slapd-chain: " + "unable to init %sunderlying database \"%s\".\n", + lc->lc_common_li == NULL ? "common " : "", e->e_name.bv_val ); + return LDAP_CONSTRAINT_VIOLATION; + } + + li = ca->be->be_private; + + if ( at ) { + char **urls; + + urls = ldap_str2charray( at->a_vals[ 0 ].bv_val, ", \t" ); + if ( !urls || !urls[0] || urls[1] ) { + ldap_charray_free( urls ); + Debug( LDAP_DEBUG_ANY, "slapd-chain: " + "olcDbURI must contain exactly one url, got %s\n", + at->a_vals[ 0 ].bv_val ); + rc = LDAP_CONSTRAINT_VIOLATION; + goto done; + } + ldap_charray_free( urls ); + + li->li_uri = ch_strdup( at->a_vals[ 0 ].bv_val ); + value_add_one( &li->li_bvuri, &at->a_vals[ 0 ] ); + if ( ldap_tavl_insert( &lc->lc_lai.lai_tree, (caddr_t)li, + ldap_chain_uri_cmp, ldap_chain_uri_dup ) ) + { + Debug( LDAP_DEBUG_ANY, "slapd-chain: " + "database \"%s\" insert failed.\n", + e->e_name.bv_val ); + rc = LDAP_CONSTRAINT_VIOLATION; + goto done; + } + } + + ca->ca_private = on; + +done:; + if ( rc != LDAP_SUCCESS ) { + (void)ldap_chain_db_destroy_one( ca->be, NULL ); + ch_free( ca->be ); + ca->be = NULL; + } + + return rc; +} + +static void +ldap_chain_cfadd_apply( + ldapinfo_t *li, + Operation *op, + SlapReply *rs, + Entry *p, + ConfigArgs *ca, + int count ) +{ + struct berval bv; + + /* FIXME: should not hardcode "olcDatabase" here */ + bv.bv_len = snprintf( ca->cr_msg, sizeof( ca->cr_msg ), + "olcDatabase={%d}%s", count, lback->bi_type ); + bv.bv_val = ca->cr_msg; + + ca->be->be_private = (void *)li; + config_build_entry( op, rs, p->e_private, ca, + &bv, lback->bi_cf_ocs, &chainocs[1] ); + + return; +} + +static int +chain_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *ca ) +{ + CfEntryInfo *pe = p->e_private; + slap_overinst *on = (slap_overinst *)pe->ce_bi; + ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private; + void *priv = (void *)ca->be->be_private; + TAvlnode *edge; + int count = 0; + + if ( lback->bi_cf_ocs ) { + + ldap_chain_cfadd_apply( lc->lc_common_li, op, rs, p, ca, count++ ); + + edge = ldap_tavl_end( lc->lc_lai.lai_tree, TAVL_DIR_LEFT ); + while ( edge ) { + TAvlnode *next = ldap_tavl_next( edge, TAVL_DIR_RIGHT ); + ldapinfo_t *li = (ldapinfo_t *)edge->avl_data; + ldap_chain_cfadd_apply( li, op, rs, p, ca, count++ ); + edge = next; + } + + ca->be->be_private = priv; + } + + lc->lc_cfg_li = NULL; + + return 0; +} + +#ifdef SLAP_CONFIG_DELETE +static int +chain_lddel( CfEntryInfo *ce, Operation *op ) +{ + CfEntryInfo *pe = ce->ce_parent; + slap_overinst *on = (slap_overinst *)pe->ce_bi; + ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private; + ldapinfo_t *li = (ldapinfo_t *) ce->ce_be->be_private; + + if ( li != lc->lc_common_li ) { + if (! ldap_tavl_delete( &lc->lc_lai.lai_tree, li, ldap_chain_uri_cmp ) ) { + Debug( LDAP_DEBUG_ANY, "slapd-chain: ldap_avl_delete failed. " + "\"%s\" not found.\n", li->li_uri ); + return -1; + } + } else if ( lc->lc_lai.lai_tree ) { + Debug( LDAP_DEBUG_ANY, "slapd-chain: cannot delete first underlying " + "LDAP database when other databases are still present.\n" ); + return -1; + } else { + lc->lc_common_li = NULL; + } + + ce->ce_be->bd_info = lback; + + if ( ce->ce_be->bd_info->bi_db_close ) { + ce->ce_be->bd_info->bi_db_close( ce->ce_be, NULL ); + } + if ( ce->ce_be->bd_info->bi_db_destroy ) { + ce->ce_be->bd_info->bi_db_destroy( ce->ce_be, NULL ); + } + + ch_free(ce->ce_be); + ce->ce_be = NULL; + + return LDAP_SUCCESS; +} +#endif /* SLAP_CONFIG_DELETE */ + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR +static slap_verbmasks chaining_mode[] = { + { BER_BVC("referralsRequired"), LDAP_REFERRALS_REQUIRED }, + { BER_BVC("referralsPreferred"), LDAP_REFERRALS_PREFERRED }, + { BER_BVC("chainingRequired"), LDAP_CHAINING_REQUIRED }, + { BER_BVC("chainingPreferred"), LDAP_CHAINING_PREFERRED }, + { BER_BVNULL, 0 } +}; +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + +static int +chain_cf_gen( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private; + + int rc = 0; + + if ( c->op == SLAP_CONFIG_EMIT ) { + switch( c->type ) { +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + case CH_CHAINING: { + struct berval resolve = BER_BVNULL, + continuation = BER_BVNULL; + + if ( !LDAP_CHAIN_CHAINING( lc ) ) { + return 1; + } + + enum_to_verb( chaining_mode, ( ( lc->lc_chaining_ctrlflag & SLAP_CH_RESOLVE_MASK ) >> SLAP_CH_RESOLVE_SHIFT ), &resolve ); + enum_to_verb( chaining_mode, ( ( lc->lc_chaining_ctrlflag & SLAP_CH_CONTINUATION_MASK ) >> SLAP_CH_CONTINUATION_SHIFT ), &continuation ); + + c->value_bv.bv_len = STRLENOF( "resolve=" ) + resolve.bv_len + + STRLENOF( " " ) + + STRLENOF( "continuation=" ) + continuation.bv_len; + c->value_bv.bv_val = ch_malloc( c->value_bv.bv_len + 1 ); + snprintf( c->value_bv.bv_val, c->value_bv.bv_len + 1, + "resolve=%s continuation=%s", + resolve.bv_val, continuation.bv_val ); + + if ( lc->lc_chaining_ctrl.ldctl_iscritical ) { + c->value_bv.bv_val = ch_realloc( c->value_bv.bv_val, + c->value_bv.bv_len + STRLENOF( " critical" ) + 1 ); + AC_MEMCPY( &c->value_bv.bv_val[ c->value_bv.bv_len ], + " critical", STRLENOF( " critical" ) + 1 ); + c->value_bv.bv_len += STRLENOF( " critical" ); + } + + break; + } +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + + case CH_CACHE_URI: + c->value_int = LDAP_CHAIN_CACHE_URI( lc ); + break; + + case CH_MAX_DEPTH: + c->value_int = lc->lc_max_depth; + break; + + case CH_RETURN_ERR: + c->value_int = LDAP_CHAIN_RETURN_ERR( lc ); + break; + + default: + assert( 0 ); + rc = 1; + } + return rc; + + } else if ( c->op == LDAP_MOD_DELETE ) { + switch( c->type ) { + case CH_CHAINING: + return 1; + + case CH_CACHE_URI: + lc->lc_flags &= ~LDAP_CHAIN_F_CACHE_URI; + break; + + case CH_MAX_DEPTH: + c->value_int = 0; + break; + + case CH_RETURN_ERR: + lc->lc_flags &= ~LDAP_CHAIN_F_RETURN_ERR; + break; + + default: + return 1; + } + return rc; + } + + switch( c->type ) { + case CH_CHAINING: { +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + char **argv = c->argv; + int argc = c->argc; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + int resolve = -1, + continuation = -1, + iscritical = 0; + Operation op = { 0 }; + SlapReply rs = { 0 }; + + lc->lc_chaining_ctrlflag = 0; + + for ( argc--, argv++; argc > 0; argc--, argv++ ) { + if ( strncasecmp( argv[ 0 ], "resolve=", STRLENOF( "resolve=" ) ) == 0 ) { + resolve = str2chain( argv[ 0 ] + STRLENOF( "resolve=" ) ); + if ( resolve == -1 ) { + Debug( LDAP_DEBUG_ANY, "%s: " + "illegal <resolve> value %s " + "in \"chain-chaining>\".\n", + c->log, argv[ 0 ] ); + return 1; + } + + } else if ( strncasecmp( argv[ 0 ], "continuation=", STRLENOF( "continuation=" ) ) == 0 ) { + continuation = str2chain( argv[ 0 ] + STRLENOF( "continuation=" ) ); + if ( continuation == -1 ) { + Debug( LDAP_DEBUG_ANY, "%s: " + "illegal <continuation> value %s " + "in \"chain-chaining\".\n", + c->log, argv[ 0 ] ); + return 1; + } + + } else if ( strcasecmp( argv[ 0 ], "critical" ) == 0 ) { + iscritical = 1; + + } else { + Debug( LDAP_DEBUG_ANY, "%s: " + "unknown option in \"chain-chaining\".\n", + c->log ); + return 1; + } + } + + if ( resolve != -1 || continuation != -1 ) { + int err; + + if ( resolve == -1 ) { + /* default */ + resolve = SLAP_CHAINING_DEFAULT; + } + + ber_init2( ber, NULL, LBER_USE_DER ); + + err = ber_printf( ber, "{e" /* } */, resolve ); + if ( err == -1 ) { + ber_free( ber, 1 ); + Debug( LDAP_DEBUG_ANY, "%s: " + "chaining behavior control encoding error!\n", + c->log ); + return 1; + } + + if ( continuation > -1 ) { + err = ber_printf( ber, "e", continuation ); + if ( err == -1 ) { + ber_free( ber, 1 ); + Debug( LDAP_DEBUG_ANY, "%s: " + "chaining behavior control encoding error!\n", + c->log ); + return 1; + } + } + + err = ber_printf( ber, /* { */ "N}" ); + if ( err == -1 ) { + ber_free( ber, 1 ); + Debug( LDAP_DEBUG_ANY, "%s: " + "chaining behavior control encoding error!\n", + c->log ); + return 1; + } + + if ( ber_flatten2( ber, &lc->lc_chaining_ctrl.ldctl_value, 0 ) == -1 ) { + exit( EXIT_FAILURE ); + } + + } else { + BER_BVZERO( &lc->lc_chaining_ctrl.ldctl_value ); + } + + lc->lc_chaining_ctrl.ldctl_oid = LDAP_CONTROL_X_CHAINING_BEHAVIOR; + lc->lc_chaining_ctrl.ldctl_iscritical = iscritical; + + if ( ldap_chain_parse_ctrl( &op, &rs, &lc->lc_chaining_ctrl ) != LDAP_SUCCESS ) + { + Debug( LDAP_DEBUG_ANY, "%s: " + "unable to parse chaining control%s%s.\n", + c->log, rs.sr_text ? ": " : "", + rs.sr_text ? rs.sr_text : "" ); + return 1; + } + + lc->lc_chaining_ctrlflag = op.o_chaining; + + lc->lc_flags |= LDAP_CHAIN_F_CHAINING; + + rc = 0; +#else /* ! LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + Debug( LDAP_DEBUG_ANY, "%s: " + "\"chaining\" control unsupported (ignored).\n", + c->log ); +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + } break; + + case CH_CACHE_URI: + if ( c->value_int ) { + lc->lc_flags |= LDAP_CHAIN_F_CACHE_URI; + } else { + lc->lc_flags &= ~LDAP_CHAIN_F_CACHE_URI; + } + break; + + case CH_MAX_DEPTH: + if ( c->value_int < 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "<%s> invalid max referral depth %d", + c->argv[0], c->value_int ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg ); + rc = 1; + break; + } + lc->lc_max_depth = c->value_int; + + case CH_RETURN_ERR: + if ( c->value_int ) { + lc->lc_flags |= LDAP_CHAIN_F_RETURN_ERR; + } else { + lc->lc_flags &= ~LDAP_CHAIN_F_RETURN_ERR; + } + break; + + default: + assert( 0 ); + return 1; + } + return rc; +} + +static int +ldap_chain_db_init( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + ldap_chain_t *lc = NULL; + + if ( lback == NULL ) { + lback = backend_info( "ldap" ); + + if ( lback == NULL ) { + return 1; + } + } + + lc = ch_malloc( sizeof( ldap_chain_t ) ); + if ( lc == NULL ) { + return 1; + } + memset( lc, 0, sizeof( ldap_chain_t ) ); + lc->lc_max_depth = 1; + ldap_pvt_thread_mutex_init( &lc->lc_lai.lai_mutex ); + + on->on_bi.bi_private = (void *)lc; + + return 0; +} + +static int +ldap_chain_db_config( + BackendDB *be, + const char *fname, + int lineno, + int argc, + char **argv ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private; + + int rc = SLAP_CONF_UNKNOWN; + + if ( lc->lc_common_li == NULL ) { + BackendDB db = *be; + ldap_chain_db_init_common( &db ); + lc->lc_common_li = lc->lc_cfg_li = (ldapinfo_t *)db.be_private; + } + + /* Something for the chain database? */ + if ( strncasecmp( argv[ 0 ], "chain-", STRLENOF( "chain-" ) ) == 0 ) { + char *save_argv0 = argv[ 0 ]; + BackendDB db = *be; + static char *allowed_argv[] = { + /* special: put URI here, so in the meanwhile + * it detects whether a new URI is being provided */ + "uri", + "nretries", + "timeout", + /* flags */ + "tls", + /* FIXME: maybe rebind-as-user should be allowed + * only within known URIs... */ + "rebind-as-user", + "chase-referrals", + "t-f-support", + "proxy-whoami", + NULL + }; + int which_argv = -1; + + argv[ 0 ] += STRLENOF( "chain-" ); + + for ( which_argv = 0; allowed_argv[ which_argv ]; which_argv++ ) { + if ( strcasecmp( argv[ 0 ], allowed_argv[ which_argv ] ) == 0 ) { + break; + } + } + + if ( allowed_argv[ which_argv ] == NULL ) { + which_argv = -1; + + if ( lc->lc_cfg_li == lc->lc_common_li ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "\"%s\" only allowed within a URI directive.\n.", + fname, lineno, argv[ 0 ] ); + return 1; + } + } + + if ( which_argv == 0 ) { + rc = ldap_chain_db_init_one( &db ); + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "underlying slapd-ldap initialization failed.\n.", + fname, lineno ); + return 1; + } + lc->lc_cfg_li = db.be_private; + } + + /* TODO: add checks on what other slapd-ldap(5) args + * should be put in the template; this is not quite + * harmful, because attributes that shouldn't don't + * get actually used, but the user should at least + * be warned. + */ + + db.bd_info = lback; + db.be_private = (void *)lc->lc_cfg_li; + db.be_cf_ocs = lback->bi_cf_ocs; + + rc = config_generic_wrapper( &db, fname, lineno, argc, argv ); + + argv[ 0 ] = save_argv0; + + if ( which_argv == 0 ) { +private_destroy:; + if ( rc != 0 ) { + db.bd_info = lback; + db.be_private = (void *)lc->lc_cfg_li; + ldap_chain_db_destroy_one( &db, NULL ); + lc->lc_cfg_li = NULL; + } else { + if ( lc->lc_cfg_li->li_bvuri == NULL + || BER_BVISNULL( &lc->lc_cfg_li->li_bvuri[ 0 ] ) + || !BER_BVISNULL( &lc->lc_cfg_li->li_bvuri[ 1 ] ) ) + { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "no URI list allowed in slapo-chain.\n", + fname, lineno ); + rc = 1; + goto private_destroy; + } + + if ( ldap_tavl_insert( &lc->lc_lai.lai_tree, + (caddr_t)lc->lc_cfg_li, + ldap_chain_uri_cmp, ldap_chain_uri_dup ) ) + { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "duplicate URI in slapo-chain.\n", + fname, lineno ); + rc = 1; + goto private_destroy; + } + } + } + } + + return rc; +} + +enum db_which { + db_open = 0, + db_close, + db_destroy, + + db_last +}; + +static int +ldap_chain_db_func( + BackendDB *be, + enum db_which which +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private; + + int rc = 0; + + if ( lc ) { + BI_db_func *func = (&lback->bi_db_open)[ which ]; + + if ( func != NULL && lc->lc_common_li != NULL ) { + BackendDB db = *be; + + db.bd_info = lback; + db.be_private = lc->lc_common_li; + + rc = func( &db, NULL ); + + if ( rc != 0 ) { + return rc; + } + + if ( lc->lc_lai.lai_tree != NULL ) { + TAvlnode *edge = ldap_tavl_end( lc->lc_lai.lai_tree, TAVL_DIR_LEFT ); + while ( edge ) { + TAvlnode *next = ldap_tavl_next( edge, TAVL_DIR_RIGHT ); + ldapinfo_t *li = (ldapinfo_t *)edge->avl_data; + db.be_private = (void *)li; + rc = func( &db, NULL ); + if ( rc == 1 ) { + break; + } + edge = next; + } + } + } + } + + return rc; +} + +static int +ldap_chain_db_open( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private; + slap_mask_t monitoring; + int rc = 0; + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + rc = overlay_register_control( be, LDAP_CONTROL_X_CHAINING_BEHAVIOR ); + if ( rc != 0 ) { + return rc; + } +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + + if ( lc->lc_common_li == NULL ) { + void *be_private = be->be_private; + ldap_chain_db_init_common( be ); + lc->lc_common_li = lc->lc_cfg_li = (ldapinfo_t *)be->be_private; + be->be_private = be_private; + } + + /* filter out and restore monitoring */ + monitoring = ( SLAP_DBFLAGS( be ) & SLAP_DBFLAG_MONITORING ); + SLAP_DBFLAGS( be ) &= ~SLAP_DBFLAG_MONITORING; + rc = ldap_chain_db_func( be, db_open ); + SLAP_DBFLAGS( be ) |= monitoring; + + return rc; +} + +static int +ldap_chain_db_close( + BackendDB *be, + ConfigReply *cr ) +{ +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR +#ifdef SLAP_CONFIG_DELETE + overlay_unregister_control( be, LDAP_CONTROL_X_CHAINING_BEHAVIOR ); +#endif /* SLAP_CONFIG_DELETE */ +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + return ldap_chain_db_func( be, db_close ); +} + +static int +ldap_chain_db_destroy( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private; + + int rc; + + rc = ldap_chain_db_func( be, db_destroy ); + + if ( lc ) { + ldap_tavl_free( lc->lc_lai.lai_tree, NULL ); + ldap_pvt_thread_mutex_destroy( &lc->lc_lai.lai_mutex ); + ch_free( lc ); + } + + return rc; +} + +/* + * inits one instance of the slapd-ldap backend, and stores + * the private info in be_private of the arg + */ +static int +ldap_chain_db_init_common( + BackendDB *be ) +{ + BackendInfo *bi = be->bd_info; + ldapinfo_t *li; + int rc; + + be->bd_info = lback; + be->be_private = NULL; + rc = lback->bi_db_init( be, NULL ); + if ( rc != 0 ) { + return rc; + } + li = (ldapinfo_t *)be->be_private; + li->li_urllist_f = NULL; + li->li_urllist_p = NULL; + + be->bd_info = bi; + + return 0; +} + +/* + * inits one instance of the slapd-ldap backend, stores + * the private info in be_private of the arg and fills + * selected fields with data from the template. + * + * NOTE: add checks about the other fields of the template, + * which are ignored and SHOULD NOT be configured by the user. + */ +static int +ldap_chain_db_init_one( + BackendDB *be ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private; + + BackendInfo *bi = be->bd_info; + ldapinfo_t *li; + + slap_op_t t; + + be->bd_info = lback; + be->be_private = NULL; + t = lback->bi_db_init( be, NULL ); + if ( t != 0 ) { + return t; + } + li = (ldapinfo_t *)be->be_private; + li->li_urllist_f = NULL; + li->li_urllist_p = NULL; + + /* copy common data */ + li->li_nretries = lc->lc_common_li->li_nretries; + li->li_flags = lc->lc_common_li->li_flags; + li->li_version = lc->lc_common_li->li_version; + for ( t = 0; t < SLAP_OP_LAST; t++ ) { + li->li_timeout[ t ] = lc->lc_common_li->li_timeout[ t ]; + } + be->bd_info = bi; + + return 0; +} + +static int +ldap_chain_db_open_one( + BackendDB *be ) +{ + if ( SLAP_DBMONITORING( be ) ) { + ldapinfo_t *li = (ldapinfo_t *)be->be_private; + + if ( li->li_uri == NULL ) { + ber_str2bv( "cn=Common Connections", 0, 1, + &li->li_monitor_info.lmi_conn_rdn ); + ber_str2bv( "cn=Operations on Common Connections", 0, 1, + &li->li_monitor_info.lmi_conn_rdn ); + + } else { + char *ptr; + + li->li_monitor_info.lmi_conn_rdn.bv_len + = STRLENOF( "cn=" ) + strlen( li->li_uri ); + ptr = li->li_monitor_info.lmi_conn_rdn.bv_val + = ch_malloc( li->li_monitor_info.lmi_conn_rdn.bv_len + 1 ); + ptr = lutil_strcopy( ptr, "cn=" ); + ptr = lutil_strcopy( ptr, li->li_uri ); + ptr[ 0 ] = '\0'; + + li->li_monitor_info.lmi_ops_rdn.bv_len + = STRLENOF( "cn=Operations on " ) + strlen( li->li_uri ); + ptr = li->li_monitor_info.lmi_ops_rdn.bv_val + = ch_malloc( li->li_monitor_info.lmi_ops_rdn.bv_len + 1 ); + ptr = lutil_strcopy( ptr, "cn=Operations on " ); + ptr = lutil_strcopy( ptr, li->li_uri ); + ptr[ 0 ] = '\0'; + } + } + + return lback->bi_db_open( be, NULL ); +} + +static int +ldap_chain_connection_destroy( + BackendDB *be, + Connection *conn +) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private; + void *private = be->be_private; + TAvlnode *edge; + int rc; + + be->be_private = NULL; + ldap_pvt_thread_mutex_lock( &lc->lc_lai.lai_mutex ); + edge = ldap_tavl_end( lc->lc_lai.lai_tree, TAVL_DIR_LEFT ); + while ( edge ) { + TAvlnode *next = ldap_tavl_next( edge, TAVL_DIR_RIGHT ); + ldapinfo_t *li = (ldapinfo_t *)edge->avl_data; + be->be_private = (void *)li; + rc = lback->bi_connection_destroy( be, conn ); + if ( rc == 1 ) { + break; + } + edge = next; + } + + + ldap_pvt_thread_mutex_unlock( &lc->lc_lai.lai_mutex ); + be->be_private = private; + + return rc; +} + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR +static int +ldap_chain_parse_ctrl( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + ber_tag_t tag; + BerElement *ber; + ber_int_t mode, + behavior; + + if ( get_chaining( op ) != SLAP_CONTROL_NONE ) { + rs->sr_text = "Chaining behavior control specified multiple times"; + return LDAP_PROTOCOL_ERROR; + } + + if ( op->o_pagedresults != SLAP_CONTROL_NONE ) { + rs->sr_text = "Chaining behavior control specified with pagedResults control"; + return LDAP_PROTOCOL_ERROR; + } + + if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) { + mode = (SLAP_CH_RESOLVE_DEFAULT|SLAP_CH_CONTINUATION_DEFAULT); + + } else { + ber_len_t len; + + /* Parse the control value + * ChainingBehavior ::= SEQUENCE { + * resolveBehavior Behavior OPTIONAL, + * continuationBehavior Behavior OPTIONAL } + * + * Behavior :: = ENUMERATED { + * chainingPreferred (0), + * chainingRequired (1), + * referralsPreferred (2), + * referralsRequired (3) } + */ + + ber = ber_init( &ctrl->ldctl_value ); + if( ber == NULL ) { + rs->sr_text = "internal error"; + return LDAP_OTHER; + } + + tag = ber_scanf( ber, "{e" /* } */, &behavior ); + /* FIXME: since the whole SEQUENCE is optional, + * should we accept no enumerations at all? */ + if ( tag != LBER_ENUMERATED ) { + rs->sr_text = "Chaining behavior control: resolveBehavior decoding error"; + return LDAP_PROTOCOL_ERROR; + } + + switch ( behavior ) { + case LDAP_CHAINING_PREFERRED: + mode = SLAP_CH_RESOLVE_CHAINING_PREFERRED; + break; + + case LDAP_CHAINING_REQUIRED: + mode = SLAP_CH_RESOLVE_CHAINING_REQUIRED; + break; + + case LDAP_REFERRALS_PREFERRED: + mode = SLAP_CH_RESOLVE_REFERRALS_PREFERRED; + break; + + case LDAP_REFERRALS_REQUIRED: + mode = SLAP_CH_RESOLVE_REFERRALS_REQUIRED; + break; + + default: + rs->sr_text = "Chaining behavior control: unknown resolveBehavior"; + return LDAP_PROTOCOL_ERROR; + } + + tag = ber_peek_tag( ber, &len ); + if ( tag == LBER_ENUMERATED ) { + tag = ber_scanf( ber, "e", &behavior ); + if ( tag == LBER_ERROR ) { + rs->sr_text = "Chaining behavior control: continuationBehavior decoding error"; + return LDAP_PROTOCOL_ERROR; + } + } + + if ( tag == LBER_DEFAULT ) { + mode |= SLAP_CH_CONTINUATION_DEFAULT; + + } else { + switch ( behavior ) { + case LDAP_CHAINING_PREFERRED: + mode |= SLAP_CH_CONTINUATION_CHAINING_PREFERRED; + break; + + case LDAP_CHAINING_REQUIRED: + mode |= SLAP_CH_CONTINUATION_CHAINING_REQUIRED; + break; + + case LDAP_REFERRALS_PREFERRED: + mode |= SLAP_CH_CONTINUATION_REFERRALS_PREFERRED; + break; + + case LDAP_REFERRALS_REQUIRED: + mode |= SLAP_CH_CONTINUATION_REFERRALS_REQUIRED; + break; + + default: + rs->sr_text = "Chaining behavior control: unknown continuationBehavior"; + return LDAP_PROTOCOL_ERROR; + } + } + + if ( ( ber_scanf( ber, /* { */ "}") ) == LBER_ERROR ) { + rs->sr_text = "Chaining behavior control: decoding error"; + return LDAP_PROTOCOL_ERROR; + } + + (void) ber_free( ber, 1 ); + } + + op->o_chaining = mode | ( ctrl->ldctl_iscritical + ? SLAP_CONTROL_CRITICAL + : SLAP_CONTROL_NONCRITICAL ); + + return LDAP_SUCCESS; +} +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + +int +chain_initialize( void ) +{ + int rc; + + /* Make sure we don't exceed the bits reserved for userland */ + config_check_userland( CH_LAST ); + + /* olcDatabaseDummy is defined in slapd, and Windows + will not let us initialize a struct element with a data pointer + from another library, so we have to initialize this element + "by hand". */ + chainocs[1].co_table = olcDatabaseDummy; + +#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR + rc = register_supported_control( LDAP_CONTROL_X_CHAINING_BEHAVIOR, + /* SLAP_CTRL_GLOBAL| */ SLAP_CTRL_ACCESS|SLAP_CTRL_HIDE, NULL, + ldap_chain_parse_ctrl, &sc_chainingBehavior ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "slapd-chain: " + "unable to register chaining behavior control: %d.\n", + rc ); + return rc; + } +#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */ + + ldapchain.on_bi.bi_type = "chain"; + ldapchain.on_bi.bi_db_init = ldap_chain_db_init; + ldapchain.on_bi.bi_db_config = ldap_chain_db_config; + ldapchain.on_bi.bi_db_open = ldap_chain_db_open; + ldapchain.on_bi.bi_db_close = ldap_chain_db_close; + ldapchain.on_bi.bi_db_destroy = ldap_chain_db_destroy; + + ldapchain.on_bi.bi_connection_destroy = ldap_chain_connection_destroy; + + ldapchain.on_response = ldap_chain_response; + + ldapchain.on_bi.bi_cf_ocs = chainocs; + + rc = config_register_schema( chaincfg, chainocs ); + if ( rc ) { + return rc; + } + + return overlay_register( &ldapchain ); +} + diff --git a/servers/slapd/back-ldap/compare.c b/servers/slapd/back-ldap/compare.c new file mode 100644 index 0000000..1e410db --- /dev/null +++ b/servers/slapd/back-ldap/compare.c @@ -0,0 +1,88 @@ +/* compare.c - ldap backend compare function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2022 The OpenLDAP Foundation. + * Portions Copyright 1999-2003 Howard Chu. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * 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>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "back-ldap.h" + +int +ldap_back_compare( + Operation *op, + SlapReply *rs ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + + ldapconn_t *lc = NULL; + ber_int_t msgid; + ldap_back_send_t retrying = LDAP_BACK_RETRYING; + LDAPControl **ctrls = NULL; + int rc = LDAP_SUCCESS; + + if ( !ldap_back_dobind( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + lc = NULL; + goto cleanup; + } + +retry: + ctrls = op->o_ctrls; + rc = ldap_back_controls_add( op, rs, lc, &ctrls ); + if ( rc != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto cleanup; + } + + rs->sr_err = ldap_compare_ext( lc->lc_ld, op->o_req_dn.bv_val, + op->orc_ava->aa_desc->ad_cname.bv_val, + &op->orc_ava->aa_value, + ctrls, NULL, &msgid ); + rc = ldap_back_op_result( lc, op, rs, msgid, + li->li_timeout[ SLAP_OP_COMPARE ], + ( LDAP_BACK_SENDRESULT | retrying ) ); + if ( rc == LDAP_UNAVAILABLE && retrying ) { + retrying &= ~LDAP_BACK_RETRYING; + if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + /* if the identity changed, there might be need to re-authz */ + (void)ldap_back_controls_free( op, rs, &ctrls ); + goto retry; + } + } + + ldap_pvt_thread_mutex_lock( &li->li_counter_mutex ); + ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_COMPARE ], 1 ); + ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex ); + +cleanup: + (void)ldap_back_controls_free( op, rs, &ctrls ); + + if ( lc != NULL ) { + ldap_back_release_conn( li, lc ); + } + + return rs->sr_err; +} diff --git a/servers/slapd/back-ldap/config.c b/servers/slapd/back-ldap/config.c new file mode 100644 index 0000000..fb97e8e --- /dev/null +++ b/servers/slapd/back-ldap/config.c @@ -0,0 +1,2214 @@ +/* config.c - ldap backend configuration file routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2022 The OpenLDAP Foundation. + * Portions Copyright 1999-2003 Howard Chu. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * 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>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/ctype.h> +#include <ac/socket.h> + +#include "slap.h" +#include "slap-config.h" +#include "back-ldap.h" +#include "lutil.h" +#include "ldif.h" + +static SLAP_EXTOP_MAIN_FN ldap_back_exop_whoami; + +static ConfigDriver ldap_back_cf_gen; +static ConfigDriver ldap_pbind_cf_gen; + +enum { + LDAP_BACK_CFG_URI = 1, + LDAP_BACK_CFG_TLS, + LDAP_BACK_CFG_ACL_BIND, + LDAP_BACK_CFG_IDASSERT_AUTHZFROM, + LDAP_BACK_CFG_IDASSERT_PASSTHRU, + LDAP_BACK_CFG_IDASSERT_BIND, + LDAP_BACK_CFG_REBIND, + LDAP_BACK_CFG_CHASE, + LDAP_BACK_CFG_T_F, + LDAP_BACK_CFG_WHOAMI, + LDAP_BACK_CFG_TIMEOUT, + LDAP_BACK_CFG_IDLE_TIMEOUT, + LDAP_BACK_CFG_CONN_TTL, + LDAP_BACK_CFG_NETWORK_TIMEOUT, + LDAP_BACK_CFG_VERSION, + LDAP_BACK_CFG_SINGLECONN, + LDAP_BACK_CFG_USETEMP, + LDAP_BACK_CFG_CONNPOOLMAX, + LDAP_BACK_CFG_CANCEL, + LDAP_BACK_CFG_QUARANTINE, + LDAP_BACK_CFG_ST_REQUEST, + LDAP_BACK_CFG_NOREFS, + LDAP_BACK_CFG_NOUNDEFFILTER, + LDAP_BACK_CFG_ONERR, + + LDAP_BACK_CFG_KEEPALIVE, + LDAP_BACK_CFG_TCP_USER_TIMEOUT, + + LDAP_BACK_CFG_OMIT_UNKNOWN_SCHEMA, + + LDAP_BACK_CFG_LAST +}; + +static ConfigTable ldapcfg[] = { + { "uri", "uri", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_URI, + ldap_back_cf_gen, "( OLcfgDbAt:0.14 " + "NAME 'olcDbURI' " + "DESC 'URI (list) for remote DSA' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "tls", "what", 2, 0, 0, + ARG_MAGIC|LDAP_BACK_CFG_TLS, + ldap_back_cf_gen, "( OLcfgDbAt:3.1 " + "NAME 'olcDbStartTLS' " + "DESC 'StartTLS' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "acl-bind", "args", 2, 0, 0, + ARG_MAGIC|LDAP_BACK_CFG_ACL_BIND, + ldap_back_cf_gen, "( OLcfgDbAt:3.4 " + "NAME 'olcDbACLBind' " + "DESC 'Remote ACL administrative identity auth bind configuration' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "idassert-bind", "args", 2, 0, 0, + ARG_MAGIC|LDAP_BACK_CFG_IDASSERT_BIND, + ldap_back_cf_gen, "( OLcfgDbAt:3.7 " + "NAME 'olcDbIDAssertBind' " + "DESC 'Remote Identity Assertion administrative identity auth bind configuration' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "idassert-authzFrom", "authzRule", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_IDASSERT_AUTHZFROM, + ldap_back_cf_gen, "( OLcfgDbAt:3.9 " + "NAME 'olcDbIDAssertAuthzFrom' " + "DESC 'Remote Identity Assertion authz rules' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "X-ORDERED 'VALUES' )", + NULL, NULL }, + { "rebind-as-user", "true|FALSE", 1, 2, 0, + ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_REBIND, + ldap_back_cf_gen, "( OLcfgDbAt:3.10 " + "NAME 'olcDbRebindAsUser' " + "DESC 'Rebind as user' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + { "chase-referrals", "true|FALSE", 2, 2, 0, + ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_CHASE, + ldap_back_cf_gen, "( OLcfgDbAt:3.11 " + "NAME 'olcDbChaseReferrals' " + "DESC 'Chase referrals' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + { "t-f-support", "true|FALSE|discover", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_T_F, + ldap_back_cf_gen, "( OLcfgDbAt:3.12 " + "NAME 'olcDbTFSupport' " + "DESC 'Absolute filters support' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "proxy-whoami", "true|FALSE", 1, 2, 0, + ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_WHOAMI, + ldap_back_cf_gen, "( OLcfgDbAt:3.13 " + "NAME 'olcDbProxyWhoAmI' " + "DESC 'Proxy whoAmI exop' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + { "timeout", "timeout(list)", 2, 0, 0, + ARG_MAGIC|LDAP_BACK_CFG_TIMEOUT, + ldap_back_cf_gen, "( OLcfgDbAt:3.14 " + "NAME 'olcDbTimeout' " + "DESC 'Per-operation timeouts' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "idle-timeout", "timeout", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_IDLE_TIMEOUT, + ldap_back_cf_gen, "( OLcfgDbAt:3.15 " + "NAME 'olcDbIdleTimeout' " + "DESC 'connection idle timeout' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "conn-ttl", "ttl", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_CONN_TTL, + ldap_back_cf_gen, "( OLcfgDbAt:3.16 " + "NAME 'olcDbConnTtl' " + "DESC 'connection ttl' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "network-timeout", "timeout", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_NETWORK_TIMEOUT, + ldap_back_cf_gen, "( OLcfgDbAt:3.17 " + "NAME 'olcDbNetworkTimeout' " + "DESC 'connection network timeout' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "protocol-version", "version", 2, 2, 0, + ARG_MAGIC|ARG_INT|LDAP_BACK_CFG_VERSION, + ldap_back_cf_gen, "( OLcfgDbAt:3.18 " + "NAME 'olcDbProtocolVersion' " + "DESC 'protocol version' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", + NULL, NULL }, + { "single-conn", "true|FALSE", 2, 2, 0, + ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_SINGLECONN, + ldap_back_cf_gen, "( OLcfgDbAt:3.19 " + "NAME 'olcDbSingleConn' " + "DESC 'cache a single connection per identity' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + { "cancel", "ABANDON|ignore|exop", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_CANCEL, + ldap_back_cf_gen, "( OLcfgDbAt:3.20 " + "NAME 'olcDbCancel' " + "DESC 'abandon/ignore/exop operations when appropriate' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "quarantine", "retrylist", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_QUARANTINE, + ldap_back_cf_gen, "( OLcfgDbAt:3.21 " + "NAME 'olcDbQuarantine' " + "DESC 'Quarantine database if connection fails and retry according to rule' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "use-temporary-conn", "true|FALSE", 2, 2, 0, + ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_USETEMP, + ldap_back_cf_gen, "( OLcfgDbAt:3.22 " + "NAME 'olcDbUseTemporaryConn' " + "DESC 'Use temporary connections if the cached one is busy' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + { "conn-pool-max", "<n>", 2, 2, 0, + ARG_MAGIC|ARG_INT|LDAP_BACK_CFG_CONNPOOLMAX, + ldap_back_cf_gen, "( OLcfgDbAt:3.23 " + "NAME 'olcDbConnectionPoolMax' " + "DESC 'Max size of privileged connections pool' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", + NULL, NULL }, +#ifdef SLAP_CONTROL_X_SESSION_TRACKING + { "session-tracking-request", "true|FALSE", 2, 2, 0, + ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_ST_REQUEST, + ldap_back_cf_gen, "( OLcfgDbAt:3.24 " + "NAME 'olcDbSessionTrackingRequest' " + "DESC 'Add session tracking control to proxied requests' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, +#endif /* SLAP_CONTROL_X_SESSION_TRACKING */ + { "norefs", "true|FALSE", 2, 2, 0, + ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_NOREFS, + ldap_back_cf_gen, "( OLcfgDbAt:3.25 " + "NAME 'olcDbNoRefs' " + "DESC 'Do not return search reference responses' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + { "noundeffilter", "true|FALSE", 2, 2, 0, + ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_NOUNDEFFILTER, + ldap_back_cf_gen, "( OLcfgDbAt:3.26 " + "NAME 'olcDbNoUndefFilter' " + "DESC 'Do not propagate undefined search filters' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + { "onerr", "CONTINUE|report|stop", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_ONERR, + ldap_back_cf_gen, "( OLcfgDbAt:3.108 " + "NAME 'olcDbOnErr' " + "DESC 'error handling' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "idassert-passThru", "authzRule", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_IDASSERT_PASSTHRU, + ldap_back_cf_gen, "( OLcfgDbAt:3.27 " + "NAME 'olcDbIDAssertPassThru' " + "DESC 'Remote Identity Assertion passthru rules' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "X-ORDERED 'VALUES' )", + NULL, NULL }, + { "omit-unknown-schema", "true|FALSE", 2, 2, 0, + ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_OMIT_UNKNOWN_SCHEMA, + ldap_back_cf_gen, "( OLcfgDbAt:3.28 " + "NAME 'olcDbRemoveUnknownSchema' " + "DESC 'Omit unknown schema when returning search results' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + { "keepalive", "keepalive", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_KEEPALIVE, + ldap_back_cf_gen, "( OLcfgDbAt:3.29 " + "NAME 'olcDbKeepalive' " + "DESC 'TCP keepalive' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "tcp-user-timeout", "milliseconds", 2, 2, 0, + ARG_MAGIC|ARG_UINT|LDAP_BACK_CFG_TCP_USER_TIMEOUT, + ldap_back_cf_gen, "( OLcfgDbAt:3.30 " + "NAME 'olcDbTcpUserTimeout' " + "DESC 'TCP User Timeout' " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", + NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED, + NULL, NULL, NULL, NULL } +}; + +static ConfigOCs ldapocs[] = { + { "( OLcfgDbOc:3.1 " + "NAME 'olcLDAPConfig' " + "DESC 'LDAP backend configuration' " + "SUP olcDatabaseConfig " + "MAY ( olcDbURI " + "$ olcDbStartTLS " + "$ olcDbACLBind " + "$ olcDbIDAssertBind " + "$ olcDbIDAssertAuthzFrom " + "$ olcDbIDAssertPassThru " + "$ olcDbRebindAsUser " + "$ olcDbChaseReferrals " + "$ olcDbTFSupport " + "$ olcDbProxyWhoAmI " + "$ olcDbTimeout " + "$ olcDbIdleTimeout " + "$ olcDbConnTtl " + "$ olcDbNetworkTimeout " + "$ olcDbProtocolVersion " + "$ olcDbSingleConn " + "$ olcDbCancel " + "$ olcDbQuarantine " + "$ olcDbUseTemporaryConn " + "$ olcDbConnectionPoolMax " +#ifdef SLAP_CONTROL_X_SESSION_TRACKING + "$ olcDbSessionTrackingRequest " +#endif /* SLAP_CONTROL_X_SESSION_TRACKING */ + "$ olcDbNoRefs " + "$ olcDbNoUndefFilter " + "$ olcDbOnErr " + "$ olcDbKeepalive " + ") )", + Cft_Database, ldapcfg}, + { NULL, 0, NULL } +}; + +static ConfigTable pbindcfg[] = { + { "uri", "uri", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_URI, + ldap_pbind_cf_gen, "( OLcfgDbAt:0.14 " + "NAME 'olcDbURI' " + "DESC 'URI (list) for remote DSA' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "tls", "what", 2, 0, 0, + ARG_MAGIC|LDAP_BACK_CFG_TLS, + ldap_pbind_cf_gen, "( OLcfgDbAt:3.1 " + "NAME 'olcDbStartTLS' " + "DESC 'StartTLS' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "network-timeout", "timeout", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_NETWORK_TIMEOUT, + ldap_pbind_cf_gen, "( OLcfgDbAt:3.17 " + "NAME 'olcDbNetworkTimeout' " + "DESC 'connection network timeout' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "quarantine", "retrylist", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_QUARANTINE, + ldap_pbind_cf_gen, "( OLcfgDbAt:3.21 " + "NAME 'olcDbQuarantine' " + "DESC 'Quarantine database if connection fails and retry according to rule' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED, + NULL, NULL, NULL, NULL } +}; + +static ConfigOCs pbindocs[] = { + { "( OLcfgOvOc:3.3 " + "NAME 'olcPBindConfig' " + "DESC 'Proxy Bind configuration' " + "SUP olcOverlayConfig " + "MUST olcDbURI " + "MAY ( olcDbStartTLS " + "$ olcDbNetworkTimeout " + "$ olcDbQuarantine " + ") )", + Cft_Overlay, pbindcfg}, + { NULL, 0, NULL } +}; + +static slap_verbmasks idassert_mode[] = { + { BER_BVC("self"), LDAP_BACK_IDASSERT_SELF }, + { BER_BVC("anonymous"), LDAP_BACK_IDASSERT_ANONYMOUS }, + { BER_BVC("none"), LDAP_BACK_IDASSERT_NOASSERT }, + { BER_BVC("legacy"), LDAP_BACK_IDASSERT_LEGACY }, + { BER_BVNULL, 0 } +}; + +static slap_verbmasks tls_mode[] = { + { BER_BVC( "propagate" ), LDAP_BACK_F_TLS_PROPAGATE_MASK }, + { BER_BVC( "try-propagate" ), LDAP_BACK_F_PROPAGATE_TLS }, + { BER_BVC( "start" ), LDAP_BACK_F_TLS_USE_MASK }, + { BER_BVC( "try-start" ), LDAP_BACK_F_USE_TLS }, + { BER_BVC( "ldaps" ), LDAP_BACK_F_TLS_LDAPS }, + { BER_BVC( "none" ), LDAP_BACK_F_NONE }, + { BER_BVNULL, 0 } +}; + +static slap_verbmasks t_f_mode[] = { + { BER_BVC( "yes" ), LDAP_BACK_F_T_F }, + { BER_BVC( "discover" ), LDAP_BACK_F_T_F_DISCOVER }, + { BER_BVC( "no" ), LDAP_BACK_F_NONE }, + { BER_BVNULL, 0 } +}; + +static slap_verbmasks cancel_mode[] = { + { BER_BVC( "ignore" ), LDAP_BACK_F_CANCEL_IGNORE }, + { BER_BVC( "exop" ), LDAP_BACK_F_CANCEL_EXOP }, + { BER_BVC( "exop-discover" ), LDAP_BACK_F_CANCEL_EXOP_DISCOVER }, + { BER_BVC( "abandon" ), LDAP_BACK_F_CANCEL_ABANDON }, + { BER_BVNULL, 0 } +}; + +static slap_verbmasks onerr_mode[] = { + { BER_BVC( "stop" ), LDAP_BACK_F_ONERR_STOP }, + { BER_BVC( "report" ), LDAP_BACK_F_ONERR_STOP }, /* same behavior */ + { BER_BVC( "continue" ), LDAP_BACK_F_NONE }, + { BER_BVNULL, 0 } +}; + +/* see enum in slap.h */ +static slap_cf_aux_table timeout_table[] = { + { BER_BVC("bind="), SLAP_OP_BIND * sizeof( time_t ), 'u', 0, NULL }, + /* unbind makes no sense */ + { BER_BVC("add="), SLAP_OP_ADD * sizeof( time_t ), 'u', 0, NULL }, + { BER_BVC("delete="), SLAP_OP_DELETE * sizeof( time_t ), 'u', 0, NULL }, + { BER_BVC("modrdn="), SLAP_OP_MODRDN * sizeof( time_t ), 'u', 0, NULL }, + { BER_BVC("modify="), SLAP_OP_MODIFY * sizeof( time_t ), 'u', 0, NULL }, + { BER_BVC("compare="), SLAP_OP_COMPARE * sizeof( time_t ), 'u', 0, NULL }, + { BER_BVC("search="), SLAP_OP_SEARCH * sizeof( time_t ), 'u', 0, NULL }, + /* abandon makes little sense */ +#if 0 /* not implemented yet */ + { BER_BVC("extended="), SLAP_OP_EXTENDED * sizeof( time_t ), 'u', 0, NULL }, +#endif + { BER_BVNULL, 0, 0, 0, NULL } +}; + +int +slap_retry_info_parse( + char *in, + slap_retry_info_t *ri, + char *buf, + ber_len_t buflen ) +{ + char **retrylist = NULL; + int rc = 0; + int i; + + slap_str2clist( &retrylist, in, " ;" ); + if ( retrylist == NULL ) { + return 1; + } + + for ( i = 0; retrylist[ i ] != NULL; i++ ) + /* count */ ; + + ri->ri_interval = ch_calloc( sizeof( time_t ), i + 1 ); + ri->ri_num = ch_calloc( sizeof( int ), i + 1 ); + + for ( i = 0; retrylist[ i ] != NULL; i++ ) { + unsigned long t; + char *sep = strchr( retrylist[ i ], ',' ); + + if ( sep == NULL ) { + snprintf( buf, buflen, + "missing comma in retry pattern #%d \"%s\"", + i, retrylist[ i ] ); + rc = 1; + goto done; + } + + *sep++ = '\0'; + + if ( lutil_parse_time( retrylist[ i ], &t ) ) { + snprintf( buf, buflen, + "unable to parse interval #%d \"%s\"", + i, retrylist[ i ] ); + rc = 1; + goto done; + } + ri->ri_interval[ i ] = (time_t)t; + + if ( strcmp( sep, "+" ) == 0 ) { + if ( retrylist[ i + 1 ] != NULL ) { + snprintf( buf, buflen, + "extra cruft after retry pattern " + "#%d \"%s,+\" with \"forever\" mark", + i, retrylist[ i ] ); + rc = 1; + goto done; + } + ri->ri_num[ i ] = SLAP_RETRYNUM_FOREVER; + + } else if ( lutil_atoi( &ri->ri_num[ i ], sep ) ) { + snprintf( buf, buflen, + "unable to parse retry num #%d \"%s\"", + i, sep ); + rc = 1; + goto done; + } + } + + ri->ri_num[ i ] = SLAP_RETRYNUM_TAIL; + + ri->ri_idx = 0; + ri->ri_count = 0; + ri->ri_last = (time_t)(-1); + +done:; + ldap_charray_free( retrylist ); + + if ( rc ) { + slap_retry_info_destroy( ri ); + } + + return rc; +} + +int +slap_retry_info_unparse( + slap_retry_info_t *ri, + struct berval *bvout ) +{ + char buf[ BUFSIZ * 2 ], + *ptr = buf; + int i, len, restlen = (int) sizeof( buf ); + struct berval bv; + + assert( ri != NULL ); + assert( bvout != NULL ); + + BER_BVZERO( bvout ); + + for ( i = 0; ri->ri_num[ i ] != SLAP_RETRYNUM_TAIL; i++ ) { + if ( i > 0 ) { + if ( --restlen <= 0 ) { + return 1; + } + *ptr++ = ';'; + } + + if ( lutil_unparse_time( ptr, restlen, ri->ri_interval[i] ) < 0 ) { + return 1; + } + len = (int) strlen( ptr ); + if ( (restlen -= len + 1) <= 0 ) { + return 1; + } + ptr += len; + *ptr++ = ','; + + if ( ri->ri_num[i] == SLAP_RETRYNUM_FOREVER ) { + if ( --restlen <= 0 ) { + return 1; + } + *ptr++ = '+'; + + } else { + len = snprintf( ptr, restlen, "%d", ri->ri_num[i] ); + if ( (restlen -= len) <= 0 || len < 0 ) { + return 1; + } + ptr += len; + } + } + + bv.bv_val = buf; + bv.bv_len = ptr - buf; + ber_dupbv( bvout, &bv ); + + return 0; +} + +void +slap_retry_info_destroy( + slap_retry_info_t *ri ) +{ + assert( ri != NULL ); + + assert( ri->ri_interval != NULL ); + ch_free( ri->ri_interval ); + ri->ri_interval = NULL; + + assert( ri->ri_num != NULL ); + ch_free( ri->ri_num ); + ri->ri_num = NULL; +} + +int +slap_idassert_authzfrom_parse( ConfigArgs *c, slap_idassert_t *si ) +{ + struct berval bv; + struct berval in; + int rc; + + if ( strcmp( c->argv[ 1 ], "*" ) == 0 + || strcmp( c->argv[ 1 ], "dn:*" ) == 0 + || strcasecmp( c->argv[ 1 ], "dn.regex:.*" ) == 0 ) + { + if ( si->si_authz != NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"%s <authz>\": " + "\"%s\" conflicts with existing authz rules", + c->argv[ 0 ], c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + si->si_flags |= LDAP_BACK_AUTH_AUTHZ_ALL; + + return 0; + + } else if ( ( si->si_flags & LDAP_BACK_AUTH_AUTHZ_ALL ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"%s <authz>\": " + "\"<authz>\" conflicts with \"*\"", c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + ber_str2bv( c->argv[ 1 ], 0, 0, &in ); + rc = authzNormalize( 0, NULL, NULL, &in, &bv, NULL ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"%s <authz>\": " + "invalid syntax", c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + if ( c->valx == -1 ) { + ber_bvarray_add( &si->si_authz, &bv ); + + } else { + int i = 0; + if ( si->si_authz != NULL ) { + for ( ; !BER_BVISNULL( &si->si_authz[ i ] ); i++ ) + ; + } + + if ( i <= c->valx ) { + ber_bvarray_add( &si->si_authz, &bv ); + + } else { + BerVarray tmp = ber_memrealloc( si->si_authz, + sizeof( struct berval )*( i + 2 ) ); + if ( tmp == NULL ) { + return -1; + } + si->si_authz = tmp; + for ( ; i > c->valx; i-- ) { + si->si_authz[ i ] = si->si_authz[ i - 1 ]; + } + si->si_authz[ c->valx ] = bv; + } + } + + return 0; +} + +static int +slap_idassert_passthru_parse( ConfigArgs *c, slap_idassert_t *si ) +{ + struct berval bv; + struct berval in; + int rc; + + ber_str2bv( c->argv[ 1 ], 0, 0, &in ); + rc = authzNormalize( 0, NULL, NULL, &in, &bv, NULL ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"%s <authz>\": " + "invalid syntax", c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + if ( c->valx == -1 ) { + ber_bvarray_add( &si->si_passthru, &bv ); + + } else { + int i = 0; + if ( si->si_passthru != NULL ) { + for ( ; !BER_BVISNULL( &si->si_passthru[ i ] ); i++ ) + ; + } + + if ( i <= c->valx ) { + ber_bvarray_add( &si->si_passthru, &bv ); + + } else { + BerVarray tmp = ber_memrealloc( si->si_passthru, + sizeof( struct berval )*( i + 2 ) ); + if ( tmp == NULL ) { + return -1; + } + si->si_passthru = tmp; + for ( ; i > c->valx; i-- ) { + si->si_passthru[ i ] = si->si_passthru[ i - 1 ]; + } + si->si_passthru[ c->valx ] = bv; + } + } + + return 0; +} + +int +slap_idassert_parse( ConfigArgs *c, slap_idassert_t *si ) +{ + int i; + + /* set default */ + si->si_mode = LDAP_BACK_IDASSERT_LEGACY; + + for ( i = 1; i < c->argc; i++ ) { + if ( strncasecmp( c->argv[ i ], "mode=", STRLENOF( "mode=" ) ) == 0 ) { + char *argvi = c->argv[ i ] + STRLENOF( "mode=" ); + int j; + + j = verb_to_mask( argvi, idassert_mode ); + if ( BER_BVISNULL( &idassert_mode[ j ].word ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"%s <args>\": " + "unknown mode \"%s\"", + c->argv[0], argvi ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + si->si_mode = idassert_mode[ j ].mask; + + } else if ( strncasecmp( c->argv[ i ], "authz=", STRLENOF( "authz=" ) ) == 0 ) { + char *argvi = c->argv[ i ] + STRLENOF( "authz=" ); + + if ( strcasecmp( argvi, "native" ) == 0 ) { + if ( si->si_bc.sb_method != LDAP_AUTH_SASL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"%s <args>\": " + "authz=\"native\" incompatible " + "with auth method", c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + si->si_flags |= LDAP_BACK_AUTH_NATIVE_AUTHZ; + + } else if ( strcasecmp( argvi, "proxyAuthz" ) == 0 ) { + si->si_flags &= ~LDAP_BACK_AUTH_NATIVE_AUTHZ; + + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"%s <args>\": " + "unknown authz \"%s\"", + c->argv[0], argvi ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + } else if ( strncasecmp( c->argv[ i ], "flags=", STRLENOF( "flags=" ) ) == 0 ) { + char *argvi = c->argv[ i ] + STRLENOF( "flags=" ); + char **flags = ldap_str2charray( argvi, "," ); + int j, err = 0; + + if ( flags == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"%s <args>\": " + "unable to parse flags \"%s\"", + c->argv[0], argvi ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + for ( j = 0; flags[ j ] != NULL; j++ ) { + + if ( strcasecmp( flags[ j ], "override" ) == 0 ) { + si->si_flags |= LDAP_BACK_AUTH_OVERRIDE; + + } else if ( strcasecmp( flags[ j ], "prescriptive" ) == 0 ) { + si->si_flags |= LDAP_BACK_AUTH_PRESCRIPTIVE; + + } else if ( strcasecmp( flags[ j ], "non-prescriptive" ) == 0 ) { + si->si_flags &= ( ~LDAP_BACK_AUTH_PRESCRIPTIVE ); + + } else if ( strcasecmp( flags[ j ], "obsolete-proxy-authz" ) == 0 ) { + if ( si->si_flags & LDAP_BACK_AUTH_OBSOLETE_ENCODING_WORKAROUND ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"%s <args>\": " + "\"obsolete-proxy-authz\" flag " + "incompatible with previously issued \"obsolete-encoding-workaround\" flag.", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + err = 1; + break; + + } else { + si->si_flags |= LDAP_BACK_AUTH_OBSOLETE_PROXY_AUTHZ; + } + + } else if ( strcasecmp( flags[ j ], "obsolete-encoding-workaround" ) == 0 ) { + if ( si->si_flags & LDAP_BACK_AUTH_OBSOLETE_PROXY_AUTHZ ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"%s <args>\": " + "\"obsolete-encoding-workaround\" flag " + "incompatible with previously issued \"obsolete-proxy-authz\" flag.", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + err = 1; + break; + + } else { + si->si_flags |= LDAP_BACK_AUTH_OBSOLETE_ENCODING_WORKAROUND; + } + + } else if ( strcasecmp( flags[ j ], "proxy-authz-critical" ) == 0 ) { + si->si_flags |= LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL; + + } else if ( strcasecmp( flags[ j ], "proxy-authz-non-critical" ) == 0 ) { + si->si_flags &= ~LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL; + + } else if ( strcasecmp( flags[ j ], "dn-none" ) == 0 ) { + si->si_flags &= ~LDAP_BACK_AUTH_DN_MASK; + + } else if ( strcasecmp( flags[ j ], "dn-authzid" ) == 0 ) { + si->si_flags &= ~LDAP_BACK_AUTH_DN_MASK; + si->si_flags |= LDAP_BACK_AUTH_DN_AUTHZID; + + } else if ( strcasecmp( flags[ j ], "dn-whoami" ) == 0 ) { + si->si_flags &= ~LDAP_BACK_AUTH_DN_MASK; + si->si_flags |= LDAP_BACK_AUTH_DN_WHOAMI; + + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"%s <args>\": " + "unknown flag \"%s\"", + c->argv[0], flags[ j ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + err = 1; + break; + } + } + + ldap_charray_free( flags ); + if ( err ) { + return 1; + } + + } else if ( bindconf_parse( c->argv[ i ], &si->si_bc ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"%s <args>\": " + "unable to parse field \"%s\"", + c->argv[0], c->argv[ i ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + } + + if ( si->si_bc.sb_method == LDAP_AUTH_SIMPLE ) { + if ( BER_BVISNULL( &si->si_bc.sb_binddn ) + || BER_BVISNULL( &si->si_bc.sb_cred ) ) + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"%s <args>\": " + "SIMPLE needs \"binddn\" and \"credentials\"", c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + } else if ( si->si_bc.sb_method == LDAP_AUTH_SASL ) { + if ( BER_BVISNULL( &si->si_bc.sb_binddn ) && + !(si->si_flags & LDAP_BACK_AUTH_DN_MASK) ) + { + static struct berval authid = BER_BVC("cn=auth"); + ber_dupbv( &si->si_bc.sb_binddn, &authid ); + } + } + + bindconf_tls_defaults( &si->si_bc ); +#ifdef HAVE_TLS + if ( si->si_bc.sb_tls_ctx ) { + ldap_pvt_tls_ctx_free( si->si_bc.sb_tls_ctx ); + si->si_bc.sb_tls_ctx = NULL; + } +#endif + + return 0; +} + +/* NOTE: temporary, until back-meta is ported to back-config */ +int +slap_idassert_passthru_parse_cf( const char *fname, int lineno, const char *arg, slap_idassert_t *si ) +{ + ConfigArgs c = { 0 }; + char *argv[ 3 ]; + + snprintf( c.log, sizeof( c.log ), "%s: line %d", fname, lineno ); + c.argc = 2; + c.argv = argv; + argv[ 0 ] = "idassert-passThru"; + argv[ 1 ] = (char *)arg; + argv[ 2 ] = NULL; + + return slap_idassert_passthru_parse( &c, si ); +} + +static int +ldap_back_cf_gen( ConfigArgs *c ) +{ + ldapinfo_t *li = ( ldapinfo_t * )c->be->be_private; + int rc = 0; + int i; + + if ( c->op == SLAP_CONFIG_EMIT ) { + struct berval bv = BER_BVNULL; + + if ( li == NULL ) { + return 1; + } + + switch( c->type ) { + case LDAP_BACK_CFG_URI: + if ( li->li_uri != NULL ) { + struct berval bv, bv2; + + ber_str2bv( li->li_uri, 0, 0, &bv ); + bv2.bv_len = bv.bv_len + STRLENOF( "\"\"" ); + bv2.bv_val = ch_malloc( bv2.bv_len + 1 ); + snprintf( bv2.bv_val, bv2.bv_len + 1, + "\"%s\"", bv.bv_val ); + ber_bvarray_add( &c->rvalue_vals, &bv2 ); + + } else { + rc = 1; + } + break; + + case LDAP_BACK_CFG_TLS: { + struct berval bc = BER_BVNULL, bv2; + enum_to_verb( tls_mode, ( li->li_flags & LDAP_BACK_F_TLS_MASK ), &bv ); + assert( !BER_BVISNULL( &bv ) ); + bindconf_tls_unparse( &li->li_tls, &bc ); + + if ( !BER_BVISEMPTY( &bc )) { + bv2.bv_len = bv.bv_len + bc.bv_len + 1; + bv2.bv_val = ch_malloc( bv2.bv_len + 1 ); + strcpy( bv2.bv_val, bv.bv_val ); + bv2.bv_val[bv.bv_len] = ' '; + strcpy( &bv2.bv_val[bv.bv_len + 1], bc.bv_val ); + ber_bvarray_add( &c->rvalue_vals, &bv2 ); + + } else { + value_add_one( &c->rvalue_vals, &bv ); + } + ber_memfree( bc.bv_val ); + } + break; + + case LDAP_BACK_CFG_ACL_BIND: { + int i; + + if ( li->li_acl_authmethod == LDAP_AUTH_NONE ) { + return 1; + } + + bindconf_unparse( &li->li_acl, &bv ); + + for ( i = 0; isspace( (unsigned char) bv.bv_val[ i ] ); i++ ) + /* count spaces */ ; + + if ( i ) { + bv.bv_len -= i; + AC_MEMCPY( bv.bv_val, &bv.bv_val[ i ], + bv.bv_len + 1 ); + } + + ber_bvarray_add( &c->rvalue_vals, &bv ); + break; + } + + case LDAP_BACK_CFG_IDASSERT_AUTHZFROM: + case LDAP_BACK_CFG_IDASSERT_PASSTHRU: { + BerVarray *bvp; + int i; + struct berval bv = BER_BVNULL; + char buf[SLAP_TEXT_BUFLEN]; + + switch ( c->type ) { + case LDAP_BACK_CFG_IDASSERT_AUTHZFROM: bvp = &li->li_idassert_authz; break; + case LDAP_BACK_CFG_IDASSERT_PASSTHRU: bvp = &li->li_idassert_passthru; break; + default: assert( 0 ); break; + } + + if ( *bvp == NULL ) { + if ( bvp == &li->li_idassert_authz + && ( li->li_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL ) ) + { + BER_BVSTR( &bv, "*" ); + value_add_one( &c->rvalue_vals, &bv ); + + } else { + rc = 1; + } + break; + } + + for ( i = 0; !BER_BVISNULL( &((*bvp)[ i ]) ); i++ ) { + char *ptr; + int len = snprintf( buf, sizeof( buf ), SLAP_X_ORDERED_FMT, i ); + bv.bv_len = ((*bvp)[ i ]).bv_len + len; + bv.bv_val = ber_memrealloc( bv.bv_val, bv.bv_len + 1 ); + ptr = bv.bv_val; + ptr = lutil_strcopy( ptr, buf ); + ptr = lutil_strncopy( ptr, ((*bvp)[ i ]).bv_val, ((*bvp)[ i ]).bv_len ); + value_add_one( &c->rvalue_vals, &bv ); + } + if ( bv.bv_val ) { + ber_memfree( bv.bv_val ); + } + break; + } + + case LDAP_BACK_CFG_IDASSERT_BIND: { + int i; + struct berval bc = BER_BVNULL; + char *ptr; + + if ( li->li_idassert_authmethod == LDAP_AUTH_NONE ) { + return 1; + } + + if ( li->li_idassert_authmethod != LDAP_AUTH_NONE ) { + ber_len_t len; + + switch ( li->li_idassert_mode ) { + case LDAP_BACK_IDASSERT_OTHERID: + case LDAP_BACK_IDASSERT_OTHERDN: + break; + + default: { + struct berval mode = BER_BVNULL; + + enum_to_verb( idassert_mode, li->li_idassert_mode, &mode ); + if ( BER_BVISNULL( &mode ) ) { + /* there's something wrong... */ + assert( 0 ); + rc = 1; + + } else { + bv.bv_len = STRLENOF( "mode=" ) + mode.bv_len; + bv.bv_val = ch_malloc( bv.bv_len + 1 ); + + ptr = lutil_strcopy( bv.bv_val, "mode=" ); + ptr = lutil_strcopy( ptr, mode.bv_val ); + } + break; + } + } + + if ( li->li_idassert_flags & LDAP_BACK_AUTH_NATIVE_AUTHZ ) { + len = bv.bv_len + STRLENOF( "authz=native" ); + + if ( !BER_BVISEMPTY( &bv ) ) { + len += STRLENOF( " " ); + } + + bv.bv_val = ch_realloc( bv.bv_val, len + 1 ); + + ptr = &bv.bv_val[ bv.bv_len ]; + + if ( !BER_BVISEMPTY( &bv ) ) { + ptr = lutil_strcopy( ptr, " " ); + } + + (void)lutil_strcopy( ptr, "authz=native" ); + } + + len = bv.bv_len + STRLENOF( "flags=non-prescriptive,override,obsolete-encoding-workaround,proxy-authz-non-critical,dn-authzid" ); + /* flags */ + if ( !BER_BVISEMPTY( &bv ) ) { + len += STRLENOF( " " ); + } + + bv.bv_val = ch_realloc( bv.bv_val, len + 1 ); + + ptr = &bv.bv_val[ bv.bv_len ]; + + if ( !BER_BVISEMPTY( &bv ) ) { + ptr = lutil_strcopy( ptr, " " ); + } + + ptr = lutil_strcopy( ptr, "flags=" ); + + if ( li->li_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) { + ptr = lutil_strcopy( ptr, "prescriptive" ); + } else { + ptr = lutil_strcopy( ptr, "non-prescriptive" ); + } + + if ( li->li_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) { + ptr = lutil_strcopy( ptr, ",override" ); + } + + if ( li->li_idassert_flags & LDAP_BACK_AUTH_OBSOLETE_PROXY_AUTHZ ) { + ptr = lutil_strcopy( ptr, ",obsolete-proxy-authz" ); + + } else if ( li->li_idassert_flags & LDAP_BACK_AUTH_OBSOLETE_ENCODING_WORKAROUND ) { + ptr = lutil_strcopy( ptr, ",obsolete-encoding-workaround" ); + } + + if ( li->li_idassert_flags & LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL ) { + ptr = lutil_strcopy( ptr, ",proxy-authz-critical" ); + + } else { + ptr = lutil_strcopy( ptr, ",proxy-authz-non-critical" ); + } + + switch ( li->li_idassert_flags & LDAP_BACK_AUTH_DN_MASK ) { + case LDAP_BACK_AUTH_DN_AUTHZID: + ptr = lutil_strcopy( ptr, ",dn-authzid" ); + break; + + case LDAP_BACK_AUTH_DN_WHOAMI: + ptr = lutil_strcopy( ptr, ",dn-whoami" ); + break; + + default: +#if 0 /* implicit */ + ptr = lutil_strcopy( ptr, ",dn-none" ); +#endif + break; + } + + bv.bv_len = ( ptr - bv.bv_val ); + /* end-of-flags */ + } + + bindconf_unparse( &li->li_idassert.si_bc, &bc ); + + if ( !BER_BVISNULL( &bv ) ) { + ber_len_t len = bv.bv_len + bc.bv_len; + + bv.bv_val = ch_realloc( bv.bv_val, len + 1 ); + + assert( bc.bv_val[ 0 ] == ' ' ); + + ptr = lutil_strcopy( &bv.bv_val[ bv.bv_len ], bc.bv_val ); + free( bc.bv_val ); + bv.bv_len = ptr - bv.bv_val; + + } else { + for ( i = 0; isspace( (unsigned char) bc.bv_val[ i ] ); i++ ) + /* count spaces */ ; + + if ( i ) { + bc.bv_len -= i; + AC_MEMCPY( bc.bv_val, &bc.bv_val[ i ], bc.bv_len + 1 ); + } + + bv = bc; + } + + ber_bvarray_add( &c->rvalue_vals, &bv ); + + break; + } + + case LDAP_BACK_CFG_REBIND: + c->value_int = LDAP_BACK_SAVECRED( li ); + break; + + case LDAP_BACK_CFG_CHASE: + c->value_int = LDAP_BACK_CHASE_REFERRALS( li ); + break; + + case LDAP_BACK_CFG_T_F: + enum_to_verb( t_f_mode, (li->li_flags & LDAP_BACK_F_T_F_MASK2), &bv ); + if ( BER_BVISNULL( &bv ) ) { + /* there's something wrong... */ + assert( 0 ); + rc = 1; + + } else { + value_add_one( &c->rvalue_vals, &bv ); + } + break; + + case LDAP_BACK_CFG_WHOAMI: + c->value_int = LDAP_BACK_PROXY_WHOAMI( li ); + break; + + case LDAP_BACK_CFG_TIMEOUT: + BER_BVZERO( &bv ); + + for ( i = 0; i < SLAP_OP_LAST; i++ ) { + if ( li->li_timeout[ i ] != 0 ) { + break; + } + } + + if ( i == SLAP_OP_LAST ) { + return 1; + } + + slap_cf_aux_table_unparse( li->li_timeout, &bv, timeout_table ); + + if ( BER_BVISNULL( &bv ) ) { + return 1; + } + + for ( i = 0; isspace( (unsigned char) bv.bv_val[ i ] ); i++ ) + /* count spaces */ ; + + if ( i ) { + bv.bv_len -= i; + AC_MEMCPY( bv.bv_val, &bv.bv_val[ i ], + bv.bv_len + 1 ); + } + + ber_bvarray_add( &c->rvalue_vals, &bv ); + break; + + case LDAP_BACK_CFG_IDLE_TIMEOUT: { + char buf[ SLAP_TEXT_BUFLEN ]; + + if ( li->li_idle_timeout == 0 ) { + return 1; + } + + lutil_unparse_time( buf, sizeof( buf ), li->li_idle_timeout ); + ber_str2bv( buf, 0, 0, &bv ); + value_add_one( &c->rvalue_vals, &bv ); + } break; + + case LDAP_BACK_CFG_CONN_TTL: { + char buf[ SLAP_TEXT_BUFLEN ]; + + if ( li->li_conn_ttl == 0 ) { + return 1; + } + + lutil_unparse_time( buf, sizeof( buf ), li->li_conn_ttl ); + ber_str2bv( buf, 0, 0, &bv ); + value_add_one( &c->rvalue_vals, &bv ); + } break; + + case LDAP_BACK_CFG_NETWORK_TIMEOUT: { + char buf[ SLAP_TEXT_BUFLEN ]; + + if ( li->li_network_timeout == 0 ) { + return 1; + } + + lutil_unparse_time( buf, sizeof( buf ), li->li_network_timeout ); + ber_str2bv( buf, 0, 0, &bv ); + value_add_one( &c->rvalue_vals, &bv ); + } break; + + case LDAP_BACK_CFG_VERSION: + if ( li->li_version == 0 ) { + return 1; + } + + c->value_int = li->li_version; + break; + + case LDAP_BACK_CFG_SINGLECONN: + c->value_int = LDAP_BACK_SINGLECONN( li ); + break; + + case LDAP_BACK_CFG_USETEMP: + c->value_int = LDAP_BACK_USE_TEMPORARIES( li ); + break; + + case LDAP_BACK_CFG_CONNPOOLMAX: + c->value_int = li->li_conn_priv_max; + break; + + case LDAP_BACK_CFG_CANCEL: { + slap_mask_t mask = LDAP_BACK_F_CANCEL_MASK2; + + if ( LDAP_BACK_CANCEL_DISCOVER( li ) ) { + mask &= ~LDAP_BACK_F_CANCEL_EXOP; + } + enum_to_verb( cancel_mode, (li->li_flags & mask), &bv ); + if ( BER_BVISNULL( &bv ) ) { + /* there's something wrong... */ + assert( 0 ); + rc = 1; + + } else { + value_add_one( &c->rvalue_vals, &bv ); + } + } break; + + case LDAP_BACK_CFG_QUARANTINE: + if ( !LDAP_BACK_QUARANTINE( li ) ) { + rc = 1; + break; + } + + rc = slap_retry_info_unparse( &li->li_quarantine, &bv ); + if ( rc == 0 ) { + ber_bvarray_add( &c->rvalue_vals, &bv ); + } + break; + +#ifdef SLAP_CONTROL_X_SESSION_TRACKING + case LDAP_BACK_CFG_ST_REQUEST: + c->value_int = LDAP_BACK_ST_REQUEST( li ); + break; +#endif /* SLAP_CONTROL_X_SESSION_TRACKING */ + + case LDAP_BACK_CFG_NOREFS: + c->value_int = LDAP_BACK_NOREFS( li ); + break; + + case LDAP_BACK_CFG_NOUNDEFFILTER: + c->value_int = LDAP_BACK_NOUNDEFFILTER( li ); + break; + + case LDAP_BACK_CFG_OMIT_UNKNOWN_SCHEMA: + c->value_int = LDAP_BACK_OMIT_UNKNOWN_SCHEMA( li ); + break; + + case LDAP_BACK_CFG_ONERR: + enum_to_verb( onerr_mode, li->li_flags & LDAP_BACK_F_ONERR_STOP, &bv ); + if ( BER_BVISNULL( &bv )) { + rc = 1; + } else { + value_add_one( &c->rvalue_vals, &bv ); + } + break; + + case LDAP_BACK_CFG_KEEPALIVE: { + struct berval bv; + char buf[AC_LINE_MAX]; + bv.bv_len = AC_LINE_MAX; + bv.bv_val = &buf[0]; + slap_keepalive_parse(&bv, &li->li_tls.sb_keepalive, 0, 0, 1); + value_add_one( &c->rvalue_vals, &bv ); + break; + } + + case LDAP_BACK_CFG_TCP_USER_TIMEOUT: + c->value_uint = li->li_tls.sb_tcp_user_timeout; + break; + + default: + /* FIXME: we need to handle all... */ + assert( 0 ); + break; + } + return rc; + + } else if ( c->op == LDAP_MOD_DELETE ) { + switch( c->type ) { + case LDAP_BACK_CFG_URI: + if ( li->li_uri != NULL ) { + ch_free( li->li_uri ); + li->li_uri = NULL; + + assert( li->li_bvuri != NULL ); + ber_bvarray_free( li->li_bvuri ); + li->li_bvuri = NULL; + } + + /* better cleanup the cached connections... */ + /* NOTE: don't worry about locking: if we got here, + * other threads are suspended. */ + if ( li->li_conninfo.lai_tree != NULL ) { + ldap_tavl_free( li->li_conninfo.lai_tree, ldap_back_conn_free ); + li->li_conninfo.lai_tree = NULL; + } + + break; + + case LDAP_BACK_CFG_TLS: + rc = 1; + break; + + case LDAP_BACK_CFG_ACL_BIND: + bindconf_free( &li->li_acl ); + break; + + case LDAP_BACK_CFG_IDASSERT_AUTHZFROM: + case LDAP_BACK_CFG_IDASSERT_PASSTHRU: { + BerVarray *bvp; + + switch ( c->type ) { + case LDAP_BACK_CFG_IDASSERT_AUTHZFROM: bvp = &li->li_idassert_authz; break; + case LDAP_BACK_CFG_IDASSERT_PASSTHRU: bvp = &li->li_idassert_passthru; break; + default: assert( 0 ); break; + } + + if ( c->valx < 0 ) { + if ( *bvp != NULL ) { + ber_bvarray_free( *bvp ); + *bvp = NULL; + } + + } else { + int i; + + if ( *bvp == NULL ) { + rc = 1; + break; + } + + for ( i = 0; !BER_BVISNULL( &((*bvp)[ i ]) ); i++ ) + ; + + if ( i >= c->valx ) { + rc = 1; + break; + } + ber_memfree( ((*bvp)[ c->valx ]).bv_val ); + for ( i = c->valx; !BER_BVISNULL( &((*bvp)[ i + 1 ]) ); i++ ) { + (*bvp)[ i ] = (*bvp)[ i + 1 ]; + } + BER_BVZERO( &((*bvp)[ i ]) ); + } + } break; + + case LDAP_BACK_CFG_IDASSERT_BIND: + bindconf_free( &li->li_idassert.si_bc ); + memset( &li->li_idassert, 0, sizeof( slap_idassert_t ) ); + break; + + case LDAP_BACK_CFG_REBIND: + case LDAP_BACK_CFG_CHASE: + case LDAP_BACK_CFG_T_F: + case LDAP_BACK_CFG_WHOAMI: + case LDAP_BACK_CFG_CANCEL: + rc = 1; + break; + + case LDAP_BACK_CFG_TIMEOUT: + for ( i = 0; i < SLAP_OP_LAST; i++ ) { + li->li_timeout[ i ] = 0; + } + break; + + case LDAP_BACK_CFG_IDLE_TIMEOUT: + li->li_idle_timeout = 0; + break; + + case LDAP_BACK_CFG_CONN_TTL: + li->li_conn_ttl = 0; + break; + + case LDAP_BACK_CFG_NETWORK_TIMEOUT: + li->li_network_timeout = 0; + break; + + case LDAP_BACK_CFG_VERSION: + li->li_version = 0; + break; + + case LDAP_BACK_CFG_SINGLECONN: + li->li_flags &= ~LDAP_BACK_F_SINGLECONN; + break; + + case LDAP_BACK_CFG_USETEMP: + li->li_flags &= ~LDAP_BACK_F_USE_TEMPORARIES; + break; + + case LDAP_BACK_CFG_CONNPOOLMAX: + li->li_conn_priv_max = LDAP_BACK_CONN_PRIV_MIN; + break; + + case LDAP_BACK_CFG_QUARANTINE: + if ( !LDAP_BACK_QUARANTINE( li ) ) { + break; + } + + slap_retry_info_destroy( &li->li_quarantine ); + ldap_pvt_thread_mutex_destroy( &li->li_quarantine_mutex ); + li->li_isquarantined = 0; + li->li_flags &= ~LDAP_BACK_F_QUARANTINE; + break; + +#ifdef SLAP_CONTROL_X_SESSION_TRACKING + case LDAP_BACK_CFG_ST_REQUEST: + li->li_flags &= ~LDAP_BACK_F_ST_REQUEST; + break; +#endif /* SLAP_CONTROL_X_SESSION_TRACKING */ + + case LDAP_BACK_CFG_NOREFS: + li->li_flags &= ~LDAP_BACK_F_NOREFS; + break; + + case LDAP_BACK_CFG_NOUNDEFFILTER: + li->li_flags &= ~LDAP_BACK_F_NOUNDEFFILTER; + break; + + case LDAP_BACK_CFG_OMIT_UNKNOWN_SCHEMA: + li->li_flags &= ~LDAP_BACK_F_OMIT_UNKNOWN_SCHEMA; + break; + + case LDAP_BACK_CFG_ONERR: + li->li_flags &= ~LDAP_BACK_F_ONERR_STOP; + break; + + case LDAP_BACK_CFG_KEEPALIVE: + li->li_tls.sb_keepalive.sk_idle = 0; + li->li_tls.sb_keepalive.sk_probes = 0; + li->li_tls.sb_keepalive.sk_interval = 0; + break; + + case LDAP_BACK_CFG_TCP_USER_TIMEOUT: + li->li_tls.sb_tcp_user_timeout = 0; + break; + + default: + /* FIXME: we need to handle all... */ + assert( 0 ); + break; + } + return rc; + + } + + switch( c->type ) { + case LDAP_BACK_CFG_URI: { + LDAPURLDesc *tmpludp, *lud; + char **urllist = NULL; + int urlrc = LDAP_URL_SUCCESS, i; + + if ( li->li_uri != NULL ) { + ch_free( li->li_uri ); + li->li_uri = NULL; + + assert( li->li_bvuri != NULL ); + ber_bvarray_free( li->li_bvuri ); + li->li_bvuri = NULL; + } + + /* PARANOID: DN and more are not required nor allowed */ + urlrc = ldap_url_parselist_ext( &lud, c->argv[ 1 ], ", \t", LDAP_PVT_URL_PARSE_NONE ); + if ( urlrc != LDAP_URL_SUCCESS ) { + char *why; + + switch ( urlrc ) { + case LDAP_URL_ERR_MEM: + why = "no memory"; + break; + case LDAP_URL_ERR_PARAM: + why = "parameter is bad"; + break; + case LDAP_URL_ERR_BADSCHEME: + why = "URL doesn't begin with \"[c]ldap[si]://\""; + break; + case LDAP_URL_ERR_BADENCLOSURE: + why = "URL is missing trailing \">\""; + break; + case LDAP_URL_ERR_BADURL: + why = "URL is bad"; + break; + case LDAP_URL_ERR_BADHOST: + why = "host/port is bad"; + break; + case LDAP_URL_ERR_BADATTRS: + why = "bad (or missing) attributes"; + break; + case LDAP_URL_ERR_BADSCOPE: + why = "scope string is invalid (or missing)"; + break; + case LDAP_URL_ERR_BADFILTER: + why = "bad or missing filter"; + break; + case LDAP_URL_ERR_BADEXTS: + why = "bad or missing extensions"; + break; + default: + why = "unknown reason"; + break; + } + snprintf( c->cr_msg, sizeof( c->cr_msg), + "unable to parse uri \"%s\" " + "in \"uri <uri>\" line: %s", + c->value_string, why ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + urlrc = 1; + goto done_url; + } + + for ( i = 0, tmpludp = lud; + tmpludp; + i++, tmpludp = tmpludp->lud_next ) + { + if ( ( tmpludp->lud_dn != NULL + && tmpludp->lud_dn[0] != '\0' ) + || tmpludp->lud_attrs != NULL + /* || tmpludp->lud_scope != LDAP_SCOPE_DEFAULT */ + || tmpludp->lud_filter != NULL + || tmpludp->lud_exts != NULL ) + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "warning, only protocol, " + "host and port allowed " + "in \"uri <uri>\" statement " + "for uri #%d of \"%s\"", + i, c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + } + } + + for ( i = 0, tmpludp = lud; + tmpludp; + i++, tmpludp = tmpludp->lud_next ) + /* just count */ + ; + urllist = ch_calloc( sizeof( char * ), i + 1 ); + + for ( i = 0, tmpludp = lud; + tmpludp; + i++, tmpludp = tmpludp->lud_next ) + { + LDAPURLDesc tmplud; + + tmplud = *tmpludp; + tmplud.lud_dn = ""; + tmplud.lud_attrs = NULL; + tmplud.lud_filter = NULL; + if ( !ldap_is_ldapi_url( tmplud.lud_scheme ) ) { + tmplud.lud_exts = NULL; + tmplud.lud_crit_exts = 0; + } + + urllist[ i ] = ldap_url_desc2str( &tmplud ); + + if ( urllist[ i ] == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "unable to rebuild uri " + "in \"uri <uri>\" statement " + "for \"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + urlrc = 1; + goto done_url; + } + } + + li->li_uri = ldap_charray2str( urllist, " " ); + for ( i = 0; urllist[ i ] != NULL; i++ ) { + struct berval bv; + + ber_str2bv( urllist[ i ], 0, 0, &bv ); + ber_bvarray_add( &li->li_bvuri, &bv ); + urllist[ i ] = NULL; + } + ldap_memfree( urllist ); + urllist = NULL; + +done_url:; + if ( urllist ) { + ldap_charray_free( urllist ); + } + if ( lud ) { + ldap_free_urllist( lud ); + } + if ( urlrc != LDAP_URL_SUCCESS ) { + return 1; + } + break; + } + + case LDAP_BACK_CFG_TLS: + i = verb_to_mask( c->argv[1], tls_mode ); + if ( BER_BVISNULL( &tls_mode[i].word ) ) { + return 1; + } + li->li_flags &= ~LDAP_BACK_F_TLS_MASK; + li->li_flags |= tls_mode[i].mask; + if ( c->argc > 2 ) { + for ( i=2; i<c->argc; i++ ) { + if ( bindconf_tls_parse( c->argv[i], &li->li_tls )) + return 1; + } + bindconf_tls_defaults( &li->li_tls ); + } +#ifdef HAVE_TLS + if ( li->li_tls.sb_tls_ctx ) { + ldap_pvt_tls_ctx_free( li->li_tls.sb_tls_ctx ); + li->li_tls.sb_tls_ctx = NULL; + } +#endif + break; + + case LDAP_BACK_CFG_ACL_BIND: + for ( i = 1; i < c->argc; i++ ) { + if ( bindconf_parse( c->argv[ i ], &li->li_acl ) ) { + return 1; + } + } + bindconf_tls_defaults( &li->li_acl ); +#ifdef HAVE_TLS + if ( li->li_acl.sb_tls_ctx ) { + ldap_pvt_tls_ctx_free( li->li_acl.sb_tls_ctx ); + li->li_acl.sb_tls_ctx = NULL; + } +#endif + break; + + case LDAP_BACK_CFG_IDASSERT_AUTHZFROM: + rc = slap_idassert_authzfrom_parse( c, &li->li_idassert ); + break; + + case LDAP_BACK_CFG_IDASSERT_PASSTHRU: + rc = slap_idassert_passthru_parse( c, &li->li_idassert ); + break; + + case LDAP_BACK_CFG_IDASSERT_BIND: + rc = slap_idassert_parse( c, &li->li_idassert ); + break; + + case LDAP_BACK_CFG_REBIND: + if ( c->argc == 1 || c->value_int ) { + li->li_flags |= LDAP_BACK_F_SAVECRED; + + } else { + li->li_flags &= ~LDAP_BACK_F_SAVECRED; + } + break; + + case LDAP_BACK_CFG_CHASE: + if ( c->argc == 1 || c->value_int ) { + li->li_flags |= LDAP_BACK_F_CHASE_REFERRALS; + + } else { + li->li_flags &= ~LDAP_BACK_F_CHASE_REFERRALS; + } + break; + + case LDAP_BACK_CFG_T_F: { + slap_mask_t mask; + + i = verb_to_mask( c->argv[1], t_f_mode ); + if ( BER_BVISNULL( &t_f_mode[i].word ) ) { + return 1; + } + + mask = t_f_mode[i].mask; + + if ( LDAP_BACK_ISOPEN( li ) + && mask == LDAP_BACK_F_T_F_DISCOVER + && !LDAP_BACK_T_F( li ) ) + { + slap_bindconf sb = { BER_BVNULL }; + int rc; + + if ( li->li_uri == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "need URI to discover absolute filters support " + "in \"t-f-support discover\"" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + ber_str2bv( li->li_uri, 0, 0, &sb.sb_uri ); + sb.sb_version = li->li_version; + sb.sb_method = LDAP_AUTH_SIMPLE; + BER_BVSTR( &sb.sb_binddn, "" ); + + rc = slap_discover_feature( &sb, + slap_schema.si_ad_supportedFeatures->ad_cname.bv_val, + LDAP_FEATURE_ABSOLUTE_FILTERS ); + if ( rc == LDAP_COMPARE_TRUE ) { + mask |= LDAP_BACK_F_T_F; + } + } + + li->li_flags &= ~LDAP_BACK_F_T_F_MASK2; + li->li_flags |= mask; + } break; + + case LDAP_BACK_CFG_WHOAMI: + if ( c->argc == 1 || c->value_int ) { + li->li_flags |= LDAP_BACK_F_PROXY_WHOAMI; + load_extop( (struct berval *)&slap_EXOP_WHOAMI, + 0, ldap_back_exop_whoami ); + + } else { + li->li_flags &= ~LDAP_BACK_F_PROXY_WHOAMI; + } + break; + + case LDAP_BACK_CFG_TIMEOUT: + for ( i = 1; i < c->argc; i++ ) { + if ( isdigit( (unsigned char) c->argv[ i ][ 0 ] ) ) { + int j; + unsigned u; + + if ( lutil_atoux( &u, c->argv[ i ], 0 ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "unable to parse timeout \"%s\"", + c->argv[ i ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + for ( j = 0; j < SLAP_OP_LAST; j++ ) { + li->li_timeout[ j ] = u; + } + + continue; + } + + if ( slap_cf_aux_table_parse( c->argv[ i ], li->li_timeout, timeout_table, "slapd-ldap timeout" ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "unable to parse timeout \"%s\"", + c->argv[ i ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + } + break; + + case LDAP_BACK_CFG_IDLE_TIMEOUT: { + unsigned long t; + + if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "unable to parse idle timeout \"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + li->li_idle_timeout = (time_t)t; + } break; + + case LDAP_BACK_CFG_CONN_TTL: { + unsigned long t; + + if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "unable to parse conn ttl\"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + li->li_conn_ttl = (time_t)t; + } break; + + case LDAP_BACK_CFG_NETWORK_TIMEOUT: { + unsigned long t; + + if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "unable to parse network timeout \"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + li->li_network_timeout = (time_t)t; + } break; + + case LDAP_BACK_CFG_VERSION: + if ( c->value_int != 0 && ( c->value_int < LDAP_VERSION_MIN || c->value_int > LDAP_VERSION_MAX ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unsupported version \"%s\" " + "in \"protocol-version <version>\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + li->li_version = c->value_int; + break; + + case LDAP_BACK_CFG_SINGLECONN: + if ( c->value_int ) { + li->li_flags |= LDAP_BACK_F_SINGLECONN; + + } else { + li->li_flags &= ~LDAP_BACK_F_SINGLECONN; + } + break; + + case LDAP_BACK_CFG_USETEMP: + if ( c->value_int ) { + li->li_flags |= LDAP_BACK_F_USE_TEMPORARIES; + + } else { + li->li_flags &= ~LDAP_BACK_F_USE_TEMPORARIES; + } + break; + + case LDAP_BACK_CFG_CONNPOOLMAX: + if ( c->value_int < LDAP_BACK_CONN_PRIV_MIN + || c->value_int > LDAP_BACK_CONN_PRIV_MAX ) + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "invalid max size " "of privileged " + "connections pool \"%s\" " + "in \"conn-pool-max <n> " + "(must be between %d and %d)\"", + c->argv[ 1 ], + LDAP_BACK_CONN_PRIV_MIN, + LDAP_BACK_CONN_PRIV_MAX ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + li->li_conn_priv_max = c->value_int; + break; + + case LDAP_BACK_CFG_CANCEL: { + slap_mask_t mask; + + i = verb_to_mask( c->argv[1], cancel_mode ); + if ( BER_BVISNULL( &cancel_mode[i].word ) ) { + return 1; + } + + mask = cancel_mode[i].mask; + + if ( LDAP_BACK_ISOPEN( li ) + && mask == LDAP_BACK_F_CANCEL_EXOP_DISCOVER + && !LDAP_BACK_CANCEL( li ) ) + { + slap_bindconf sb = { BER_BVNULL }; + int rc; + + if ( li->li_uri == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "need URI to discover \"cancel\" support " + "in \"cancel exop-discover\"" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + ber_str2bv( li->li_uri, 0, 0, &sb.sb_uri ); + sb.sb_version = li->li_version; + sb.sb_method = LDAP_AUTH_SIMPLE; + BER_BVSTR( &sb.sb_binddn, "" ); + + rc = slap_discover_feature( &sb, + slap_schema.si_ad_supportedExtension->ad_cname.bv_val, + LDAP_EXOP_CANCEL ); + if ( rc == LDAP_COMPARE_TRUE ) { + mask |= LDAP_BACK_F_CANCEL_EXOP; + } + } + + li->li_flags &= ~LDAP_BACK_F_CANCEL_MASK2; + li->li_flags |= mask; + } break; + + case LDAP_BACK_CFG_QUARANTINE: + if ( LDAP_BACK_QUARANTINE( li ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "quarantine already defined" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + rc = slap_retry_info_parse( c->argv[1], &li->li_quarantine, + c->cr_msg, sizeof( c->cr_msg ) ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + + } else { + ldap_pvt_thread_mutex_init( &li->li_quarantine_mutex ); + /* give it a chance to retry if the pattern gets reset + * via back-config */ + li->li_isquarantined = 0; + li->li_flags |= LDAP_BACK_F_QUARANTINE; + } + break; + +#ifdef SLAP_CONTROL_X_SESSION_TRACKING + case LDAP_BACK_CFG_ST_REQUEST: + if ( c->value_int ) { + li->li_flags |= LDAP_BACK_F_ST_REQUEST; + + } else { + li->li_flags &= ~LDAP_BACK_F_ST_REQUEST; + } + break; +#endif /* SLAP_CONTROL_X_SESSION_TRACKING */ + + case LDAP_BACK_CFG_NOREFS: + if ( c->value_int ) { + li->li_flags |= LDAP_BACK_F_NOREFS; + + } else { + li->li_flags &= ~LDAP_BACK_F_NOREFS; + } + break; + + case LDAP_BACK_CFG_NOUNDEFFILTER: + if ( c->value_int ) { + li->li_flags |= LDAP_BACK_F_NOUNDEFFILTER; + + } else { + li->li_flags &= ~LDAP_BACK_F_NOUNDEFFILTER; + } + break; + + case LDAP_BACK_CFG_ONERR: + /* onerr? */ + i = verb_to_mask( c->argv[1], onerr_mode ); + if ( BER_BVISNULL( &onerr_mode[i].word ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s unknown argument \"%s\"", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + li->li_flags &= ~LDAP_BACK_F_ONERR_STOP; + li->li_flags |= onerr_mode[i].mask; + break; + + case LDAP_BACK_CFG_OMIT_UNKNOWN_SCHEMA: + if ( c->value_int ) { + li->li_flags |= LDAP_BACK_F_OMIT_UNKNOWN_SCHEMA; + + } else { + li->li_flags &= ~LDAP_BACK_F_OMIT_UNKNOWN_SCHEMA; + } + break; + + case LDAP_BACK_CFG_KEEPALIVE: { + struct berval bv; + ber_str2bv( c->argv[1], 0, 1, &bv ); + slap_keepalive_parse( &bv, &li->li_tls.sb_keepalive, 0, 0, 0 ); + } + break; + + case LDAP_BACK_CFG_TCP_USER_TIMEOUT: + li->li_tls.sb_tcp_user_timeout = c->value_uint; + break; + + default: + /* FIXME: try to catch inconsistencies */ + assert( 0 ); + break; + } + + return rc; +} + +int +ldap_back_init_cf( BackendInfo *bi ) +{ + int rc; + + /* Make sure we don't exceed the bits reserved for userland */ + config_check_userland( LDAP_BACK_CFG_LAST ); + + bi->bi_cf_ocs = ldapocs; + + rc = config_register_schema( ldapcfg, ldapocs ); + if ( rc ) { + return rc; + } + + return 0; +} + +static int +ldap_pbind_cf_gen( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + void *private = c->be->be_private; + int rc; + + c->be->be_private = on->on_bi.bi_private; + rc = ldap_back_cf_gen( c ); + c->be->be_private = private; + return rc; +} + +int +ldap_pbind_init_cf( BackendInfo *bi ) +{ + bi->bi_cf_ocs = pbindocs; + + return config_register_schema( pbindcfg, pbindocs ); +} + +static int +ldap_back_exop_whoami( + Operation *op, + SlapReply *rs ) +{ + struct berval *bv = NULL; + + if ( op->oq_extended.rs_reqdata != NULL ) { + /* no request data should be provided */ + rs->sr_text = "no request data expected"; + return rs->sr_err = LDAP_PROTOCOL_ERROR; + } + + Debug( LDAP_DEBUG_STATS, "%s WHOAMI\n", + op->o_log_prefix ); + + rs->sr_err = backend_check_restrictions( op, rs, + (struct berval *)&slap_EXOP_WHOAMI ); + if( rs->sr_err != LDAP_SUCCESS ) return rs->sr_err; + + /* if auth'd by back-ldap and request is proxied, forward it */ + if ( op->o_conn->c_authz_backend + && !strcmp( op->o_conn->c_authz_backend->be_type, "ldap" ) + && !dn_match( &op->o_ndn, &op->o_conn->c_ndn ) ) + { + ldapconn_t *lc = NULL; + LDAPControl c, *ctrls[2] = {NULL, NULL}; + LDAPMessage *res; + Operation op2 = *op; + ber_int_t msgid; + int doretry = 1; + char *ptr; + + ctrls[0] = &c; + op2.o_ndn = op->o_conn->c_ndn; + if ( !ldap_back_dobind( &lc, &op2, rs, LDAP_BACK_SENDERR ) ) { + return -1; + } + c.ldctl_oid = LDAP_CONTROL_PROXY_AUTHZ; + c.ldctl_iscritical = 1; + c.ldctl_value.bv_val = op->o_tmpalloc( + op->o_ndn.bv_len + STRLENOF( "dn:" ) + 1, + op->o_tmpmemctx ); + c.ldctl_value.bv_len = op->o_ndn.bv_len + 3; + ptr = c.ldctl_value.bv_val; + ptr = lutil_strcopy( ptr, "dn:" ); + ptr = lutil_strncopy( ptr, op->o_ndn.bv_val, op->o_ndn.bv_len ); + ptr[ 0 ] = '\0'; + +retry: + rs->sr_err = ldap_whoami( lc->lc_ld, ctrls, NULL, &msgid ); + if ( rs->sr_err == LDAP_SUCCESS ) { + /* by now, make sure no timeout is used (ITS#6282) */ + struct timeval tv = { -1, 0 }; + if ( ldap_result( lc->lc_ld, msgid, LDAP_MSG_ALL, &tv, &res ) == -1 ) { + ldap_get_option( lc->lc_ld, LDAP_OPT_ERROR_NUMBER, + &rs->sr_err ); + if ( rs->sr_err == LDAP_SERVER_DOWN && doretry ) { + doretry = 0; + if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + goto retry; + } + } + + } else { + /* NOTE: are we sure "bv" will be malloc'ed + * with the appropriate memory? */ + rs->sr_err = ldap_parse_whoami( lc->lc_ld, res, &bv ); + ldap_msgfree(res); + } + } + op->o_tmpfree( c.ldctl_value.bv_val, op->o_tmpmemctx ); + if ( rs->sr_err != LDAP_SUCCESS ) { + rs->sr_err = slap_map_api2result( rs ); + } + + if ( lc != NULL ) { + ldap_back_release_conn( (ldapinfo_t *)op2.o_bd->be_private, lc ); + } + + } else { + /* else just do the same as before */ + bv = (struct berval *) ch_malloc( sizeof( struct berval ) ); + if ( !BER_BVISEMPTY( &op->o_dn ) ) { + bv->bv_len = op->o_dn.bv_len + STRLENOF( "dn:" ); + bv->bv_val = ch_malloc( bv->bv_len + 1 ); + AC_MEMCPY( bv->bv_val, "dn:", STRLENOF( "dn:" ) ); + AC_MEMCPY( &bv->bv_val[ STRLENOF( "dn:" ) ], op->o_dn.bv_val, + op->o_dn.bv_len ); + bv->bv_val[ bv->bv_len ] = '\0'; + + } else { + bv->bv_len = 0; + bv->bv_val = NULL; + } + } + + rs->sr_rspdata = bv; + return rs->sr_err; +} + + diff --git a/servers/slapd/back-ldap/delete.c b/servers/slapd/back-ldap/delete.c new file mode 100644 index 0000000..470aa0b --- /dev/null +++ b/servers/slapd/back-ldap/delete.c @@ -0,0 +1,85 @@ +/* delete.c - ldap backend delete function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2022 The OpenLDAP Foundation. + * Portions Copyright 1999-2003 Howard Chu. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * 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>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "back-ldap.h" + +int +ldap_back_delete( + Operation *op, + SlapReply *rs ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + + ldapconn_t *lc = NULL; + ber_int_t msgid; + LDAPControl **ctrls = NULL; + ldap_back_send_t retrying = LDAP_BACK_RETRYING; + int rc = LDAP_SUCCESS; + + if ( !ldap_back_dobind( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + return rs->sr_err; + } + +retry: + ctrls = op->o_ctrls; + rc = ldap_back_controls_add( op, rs, lc, &ctrls ); + if ( rc != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto cleanup; + } + + rs->sr_err = ldap_delete_ext( lc->lc_ld, op->o_req_dn.bv_val, + ctrls, NULL, &msgid ); + rc = ldap_back_op_result( lc, op, rs, msgid, + li->li_timeout[ SLAP_OP_DELETE ], + ( LDAP_BACK_SENDRESULT | retrying ) ); + if ( rs->sr_err == LDAP_UNAVAILABLE && retrying ) { + retrying &= ~LDAP_BACK_RETRYING; + if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + /* if the identity changed, there might be need to re-authz */ + (void)ldap_back_controls_free( op, rs, &ctrls ); + goto retry; + } + } + + ldap_pvt_thread_mutex_lock( &li->li_counter_mutex ); + ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_DELETE ], 1 ); + ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex ); + +cleanup: + (void)ldap_back_controls_free( op, rs, &ctrls ); + + if ( lc != NULL ) { + ldap_back_release_conn( li, lc ); + } + + return rs->sr_err; +} diff --git a/servers/slapd/back-ldap/distproc.c b/servers/slapd/back-ldap/distproc.c new file mode 100644 index 0000000..a2417a3 --- /dev/null +++ b/servers/slapd/back-ldap/distproc.c @@ -0,0 +1,998 @@ +/* distproc.c - implement distributed procedures */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2005-2022 The OpenLDAP Foundation. + * Portions Copyright 2003 Howard Chu. + * 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>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + * Based on back-ldap and slapo-chain, developed by Howard Chu + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" + +#ifdef SLAP_DISTPROC + +#include "back-ldap.h" + +#include "slap-config.h" + +/* + * From <draft-sermersheim-ldap-distproc> + * + + ContinuationReference ::= SET { + referralURI [0] SET SIZE (1..MAX) OF URI, + localReference [2] LDAPDN, + referenceType [3] ReferenceType, + remainingName [4] RelativeLDAPDN OPTIONAL, + searchScope [5] SearchScope OPTIONAL, + searchedSubtrees [6] SearchedSubtrees OPTIONAL, + failedName [7] LDAPDN OPTIONAL, + ... } + + ReferenceType ::= ENUMERATED { + superior (0), + subordinate (1), + cross (2), + nonSpecificSubordinate (3), + supplier (4), + master (5), + immediateSuperior (6), + self (7), + ... } + + SearchScope ::= ENUMERATED { + baseObject (0), + singleLevel (1), + wholeSubtree (2), + subordinateSubtree (3), + ... } + + SearchedSubtrees ::= SET OF RelativeLDAPDN + + LDAPDN, RelativeLDAPDN, and LDAPString, are defined in [RFC2251]. + + */ + +typedef enum ReferenceType_t { + LDAP_DP_RT_UNKNOWN = -1, + LDAP_DP_RT_SUPERIOR = 0, + LDAP_DP_RT_SUBORDINATE = 1, + LDAP_DP_RT_CROSS = 2, + LDAP_DP_RT_NONSPECIFICSUBORDINATE = 3, + LDAP_DP_RT_SUPPLIER = 4, + LDAP_DP_RT_MASTER = 5, + LDAP_DP_RT_IMMEDIATESUPERIOR = 6, + LDAP_DP_RT_SELF = 7, + LDAP_DP_RT_LAST +} ReferenceType_t; + +typedef enum SearchScope_t { + LDAP_DP_SS_UNKNOWN = -1, + LDAP_DP_SS_BASEOBJECT = 0, + LDAP_DP_SS_SINGLELEVEL = 1, + LDAP_DP_SS_WHOLESUBTREE = 2, + LDAP_DP_SS_SUBORDINATESUBTREE = 3, + LDAP_DP_SS_LAST +} SearchScope_t; + +typedef struct ContinuationReference_t { + BerVarray cr_referralURI; + /* ? [1] ? */ + struct berval cr_localReference; + ReferenceType_t cr_referenceType; + struct berval cr_remainingName; + SearchScope_t cr_searchScope; + BerVarray cr_searchedSubtrees; + struct berval cr_failedName; +} ContinuationReference_t; +#define CR_INIT { NULL, BER_BVNULL, LDAP_DP_RT_UNKNOWN, BER_BVNULL, LDAP_DP_SS_UNKNOWN, NULL, BER_BVNULL } + +#ifdef unused +static struct berval bv2rt[] = { + BER_BVC( "superior" ), + BER_BVC( "subordinate" ), + BER_BVC( "cross" ), + BER_BVC( "nonSpecificSubordinate" ), + BER_BVC( "supplier" ), + BER_BVC( "master" ), + BER_BVC( "immediateSuperior" ), + BER_BVC( "self" ), + BER_BVNULL +}; + +static struct berval bv2ss[] = { + BER_BVC( "baseObject" ), + BER_BVC( "singleLevel" ), + BER_BVC( "wholeSubtree" ), + BER_BVC( "subordinateSubtree" ), + BER_BVNULL +}; + +static struct berval * +ldap_distproc_rt2bv( ReferenceType_t rt ) +{ + return &bv2rt[ rt ]; +} + +static const char * +ldap_distproc_rt2str( ReferenceType_t rt ) +{ + return bv2rt[ rt ].bv_val; +} + +static ReferenceType_t +ldap_distproc_bv2rt( struct berval *bv ) +{ + ReferenceType_t rt; + + for ( rt = 0; !BER_BVISNULL( &bv2rt[ rt ] ); rt++ ) { + if ( ber_bvstrcasecmp( bv, &bv2rt[ rt ] ) == 0 ) { + return rt; + } + } + + return LDAP_DP_RT_UNKNOWN; +} + +static ReferenceType_t +ldap_distproc_str2rt( const char *s ) +{ + struct berval bv; + + ber_str2bv( s, 0, 0, &bv ); + return ldap_distproc_bv2rt( &bv ); +} + +static struct berval * +ldap_distproc_ss2bv( SearchScope_t ss ) +{ + return &bv2ss[ ss ]; +} + +static const char * +ldap_distproc_ss2str( SearchScope_t ss ) +{ + return bv2ss[ ss ].bv_val; +} + +static SearchScope_t +ldap_distproc_bv2ss( struct berval *bv ) +{ + ReferenceType_t ss; + + for ( ss = 0; !BER_BVISNULL( &bv2ss[ ss ] ); ss++ ) { + if ( ber_bvstrcasecmp( bv, &bv2ss[ ss ] ) == 0 ) { + return ss; + } + } + + return LDAP_DP_SS_UNKNOWN; +} + +static SearchScope_t +ldap_distproc_str2ss( const char *s ) +{ + struct berval bv; + + ber_str2bv( s, 0, 0, &bv ); + return ldap_distproc_bv2ss( &bv ); +} +#endif /* unused */ + +/* + * NOTE: this overlay assumes that the chainingBehavior control + * is registered by the chain overlay; it may move here some time. + * This overlay provides support for that control as well. + */ + + +static int sc_returnContRef; +#define o_returnContRef o_ctrlflag[sc_returnContRef] +#define get_returnContRef(op) ((op)->o_returnContRef & SLAP_CONTROL_MASK) + +static struct berval slap_EXOP_CHAINEDREQUEST = BER_BVC( LDAP_EXOP_X_CHAINEDREQUEST ); +static struct berval slap_FEATURE_CANCHAINOPS = BER_BVC( LDAP_FEATURE_X_CANCHAINOPS ); + +static BackendInfo *lback; + +typedef struct ldap_distproc_t { + /* "common" configuration info (anything occurring before an "uri") */ + ldapinfo_t *lc_common_li; + + /* current configuration info */ + ldapinfo_t *lc_cfg_li; + + /* tree of configured[/generated?] "uri" info */ + ldap_avl_info_t lc_lai; + + unsigned lc_flags; +#define LDAP_DISTPROC_F_NONE (0x00U) +#define LDAP_DISTPROC_F_CHAINING (0x01U) +#define LDAP_DISTPROC_F_CACHE_URI (0x10U) + +#define LDAP_DISTPROC_CHAINING( lc ) ( ( (lc)->lc_flags & LDAP_DISTPROC_F_CHAINING ) == LDAP_DISTPROC_F_CHAINING ) +#define LDAP_DISTPROC_CACHE_URI( lc ) ( ( (lc)->lc_flags & LDAP_DISTPROC_F_CACHE_URI ) == LDAP_DISTPROC_F_CACHE_URI ) + +} ldap_distproc_t; + +static int ldap_distproc_db_init_common( BackendDB *be ); +static int ldap_distproc_db_init_one( BackendDB *be ); +#define ldap_distproc_db_open_one(be) (lback)->bi_db_open( (be) ) +#define ldap_distproc_db_close_one(be) (0) +#define ldap_distproc_db_destroy_one(be, ca) (lback)->bi_db_destroy( (be), (ca) ) + +static int +ldap_distproc_uri_cmp( const void *c1, const void *c2 ) +{ + const ldapinfo_t *li1 = (const ldapinfo_t *)c1; + const ldapinfo_t *li2 = (const ldapinfo_t *)c2; + + assert( li1->li_bvuri != NULL ); + assert( !BER_BVISNULL( &li1->li_bvuri[ 0 ] ) ); + assert( BER_BVISNULL( &li1->li_bvuri[ 1 ] ) ); + + assert( li2->li_bvuri != NULL ); + assert( !BER_BVISNULL( &li2->li_bvuri[ 0 ] ) ); + assert( BER_BVISNULL( &li2->li_bvuri[ 1 ] ) ); + + /* If local DNs don't match, it is definitely not a match */ + return ber_bvcmp( &li1->li_bvuri[ 0 ], &li2->li_bvuri[ 0 ] ); +} + +static int +ldap_distproc_uri_dup( void *c1, void *c2 ) +{ + ldapinfo_t *li1 = (ldapinfo_t *)c1; + ldapinfo_t *li2 = (ldapinfo_t *)c2; + + assert( li1->li_bvuri != NULL ); + assert( !BER_BVISNULL( &li1->li_bvuri[ 0 ] ) ); + assert( BER_BVISNULL( &li1->li_bvuri[ 1 ] ) ); + + assert( li2->li_bvuri != NULL ); + assert( !BER_BVISNULL( &li2->li_bvuri[ 0 ] ) ); + assert( BER_BVISNULL( &li2->li_bvuri[ 1 ] ) ); + + /* Cannot have more than one shared session with same DN */ + if ( ber_bvcmp( &li1->li_bvuri[ 0 ], &li2->li_bvuri[ 0 ] ) == 0 ) { + return -1; + } + + return 0; +} + +static int +ldap_distproc_operational( Operation *op, SlapReply *rs ) +{ + /* Trap entries generated by back-ldap. + * + * FIXME: we need a better way to recognize them; a cleaner + * solution would be to be able to intercept the response + * of be_operational(), so that we can divert only those + * calls that fail because operational attributes were + * requested for entries that do not belong to the underlying + * database. This fix is likely to intercept also entries + * generated by back-perl and so. */ + if ( rs->sr_entry->e_private == NULL ) { + return LDAP_SUCCESS; + } + + return SLAP_CB_CONTINUE; +} + +static int +ldap_distproc_response( Operation *op, SlapReply *rs ) +{ + return SLAP_CB_CONTINUE; +} + +/* + * configuration... + */ + +enum { + /* NOTE: the chaining behavior control is registered + * by the chain overlay; it may move here some time */ + DP_CHAINING = 1, + DP_CACHE_URI, + + DP_LAST +}; + +static ConfigDriver distproc_cfgen; +static ConfigCfAdd distproc_cfadd; +static ConfigLDAPadd distproc_ldadd; + +static ConfigTable distproc_cfg[] = { + { "distproc-chaining", "args", + 2, 4, 0, ARG_MAGIC|ARG_BERVAL|DP_CHAINING, distproc_cfgen, + /* NOTE: using the same attributeTypes defined + * for the "chain" overlay */ + "( OLcfgOvAt:3.1 NAME 'olcChainingBehavior' " + "DESC 'Chaining behavior control parameters (draft-sermersheim-ldap-chaining)' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "distproc-cache-uri", "TRUE/FALSE", + 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|DP_CACHE_URI, distproc_cfgen, + "( OLcfgOvAt:3.2 NAME 'olcChainCacheURI' " + "DESC 'Enables caching of URIs not present in configuration' " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs distproc_ocs[] = { + { "( OLcfgOvOc:7.1 " + "NAME 'olcDistProcConfig' " + "DESC 'Distributed procedures <draft-sermersheim-ldap-distproc> configuration' " + "SUP olcOverlayConfig " + "MAY ( " + "olcChainingBehavior $ " + "olcChainCacheURI " + ") )", + Cft_Overlay, distproc_cfg, NULL, distproc_cfadd }, + { "( OLcfgOvOc:7.2 " + "NAME 'olcDistProcDatabase' " + "DESC 'Distributed procedure remote server configuration' " + "AUXILIARY )", + Cft_Misc, distproc_cfg, distproc_ldadd }, + { NULL, 0, NULL } +}; + +static int +distproc_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *ca ) +{ + slap_overinst *on; + ldap_distproc_t *lc; + + ldapinfo_t *li; + + AttributeDescription *ad = NULL; + Attribute *at; + const char *text; + + int rc; + + if ( p->ce_type != Cft_Overlay + || !p->ce_bi + || p->ce_bi->bi_cf_ocs != distproc_ocs ) + { + return LDAP_CONSTRAINT_VIOLATION; + } + + on = (slap_overinst *)p->ce_bi; + lc = (ldap_distproc_t *)on->on_bi.bi_private; + + assert( ca->be == NULL ); + ca->be = (BackendDB *)ch_calloc( 1, sizeof( BackendDB ) ); + + ca->be->bd_info = (BackendInfo *)on; + + rc = slap_str2ad( "olcDbURI", &ad, &text ); + assert( rc == LDAP_SUCCESS ); + + at = attr_find( e->e_attrs, ad ); + if ( lc->lc_common_li == NULL && at != NULL ) { + /* FIXME: we should generate an empty default entry + * if none is supplied */ + Debug( LDAP_DEBUG_ANY, "slapd-distproc: " + "first underlying database \"%s\" " + "cannot contain attribute \"%s\".\n", + e->e_name.bv_val, ad->ad_cname.bv_val ); + rc = LDAP_CONSTRAINT_VIOLATION; + goto done; + + } else if ( lc->lc_common_li != NULL && at == NULL ) { + /* FIXME: we should generate an empty default entry + * if none is supplied */ + Debug( LDAP_DEBUG_ANY, "slapd-distproc: " + "subsequent underlying database \"%s\" " + "must contain attribute \"%s\".\n", + e->e_name.bv_val, ad->ad_cname.bv_val ); + rc = LDAP_CONSTRAINT_VIOLATION; + goto done; + } + + if ( lc->lc_common_li == NULL ) { + rc = ldap_distproc_db_init_common( ca->be ); + + } else { + rc = ldap_distproc_db_init_one( ca->be ); + } + + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "slapd-distproc: " + "unable to init %sunderlying database \"%s\".\n", + lc->lc_common_li == NULL ? "common " : "", e->e_name.bv_val ); + rc = LDAP_CONSTRAINT_VIOLATION; + goto done; + } + + li = ca->be->be_private; + + if ( lc->lc_common_li == NULL ) { + lc->lc_common_li = li; + + } else if ( ldap_tavl_insert( &lc->lc_lai.lai_tree, (caddr_t)li, + ldap_distproc_uri_cmp, ldap_distproc_uri_dup ) ) + { + Debug( LDAP_DEBUG_ANY, "slapd-distproc: " + "database \"%s\" insert failed.\n", + e->e_name.bv_val ); + rc = LDAP_CONSTRAINT_VIOLATION; + goto done; + } + +done:; + if ( rc != LDAP_SUCCESS ) { + (void)ldap_distproc_db_destroy_one( ca->be, NULL ); + ch_free( ca->be ); + ca->be = NULL; + } + + return rc; +} + +typedef struct ldap_distproc_cfadd_apply_t { + Operation *op; + SlapReply *rs; + Entry *p; + ConfigArgs *ca; + int count; +} ldap_distproc_cfadd_apply_t; + +static void +ldap_distproc_cfadd_apply( + ldapinfo_t *li, + Operation *op, + SlapReply *rs, + Entry *p, + ConfigArgs *ca, + int count ) +{ + struct berval bv; + + /* FIXME: should not hardcode "olcDatabase" here */ + bv.bv_len = snprintf( ca->cr_msg, sizeof( ca->cr_msg ), + "olcDatabase={%d}%s", count, lback->bi_type ); + bv.bv_val = ca->cr_msg; + + ca->be->be_private = (void *)li; + config_build_entry( op, rs, p->e_private, ca, + &bv, lback->bi_cf_ocs, &distproc_ocs[ 1 ] ); + + return; +} + +static int +distproc_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *ca ) +{ + CfEntryInfo *pe = p->e_private; + slap_overinst *on = (slap_overinst *)pe->ce_bi; + ldap_distproc_t *lc = (ldap_distproc_t *)on->on_bi.bi_private; + void *priv = (void *)ca->be->be_private; + TAvlnode *edge; + int count = 0; + + if ( lback->bi_cf_ocs ) { + ldap_distproc_cfadd_apply_t lca = { 0 }; + + lca.op = op; + lca.rs = rs; + lca.p = p; + lca.ca = ca; + lca.count = 0; + + ldap_distproc_cfadd_apply( lc->lc_common_li, op, rs, p, ca, count++ ); + + edge = ldap_tavl_end( lc->lc_lai.lai_tree, TAVL_DIR_LEFT ); + while ( edge ) { + TAvlnode *next = ldap_tavl_next( edge, TAVL_DIR_RIGHT ); + ldapinfo_t *li = (ldapinfo_t *)edge->avl_data; + ldap_distproc_cfadd_apply( li, op, rs, p, ca, count++ ); + edge = next; + } + + ca->be->be_private = priv; + } + + return 0; +} + +static int +distproc_cfgen( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + ldap_distproc_t *lc = (ldap_distproc_t *)on->on_bi.bi_private; + + int rc = 0; + + if ( c->op == SLAP_CONFIG_EMIT ) { + switch( c->type ) { + case DP_CACHE_URI: + c->value_int = LDAP_DISTPROC_CACHE_URI( lc ); + break; + + default: + assert( 0 ); + rc = 1; + } + return rc; + + } else if ( c->op == LDAP_MOD_DELETE ) { + switch( c->type ) { + case DP_CHAINING: + return 1; + + case DP_CACHE_URI: + lc->lc_flags &= ~LDAP_DISTPROC_F_CACHE_URI; + break; + + default: + return 1; + } + return rc; + } + + switch( c->type ) { + case DP_CACHE_URI: + if ( c->value_int ) { + lc->lc_flags |= LDAP_DISTPROC_F_CACHE_URI; + } else { + lc->lc_flags &= ~LDAP_DISTPROC_F_CACHE_URI; + } + break; + + default: + assert( 0 ); + return 1; + } + + return rc; +} + +static int +ldap_distproc_db_init( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + ldap_distproc_t *lc = NULL; + + if ( lback == NULL ) { + lback = backend_info( "ldap" ); + + if ( lback == NULL ) { + return 1; + } + } + + lc = ch_malloc( sizeof( ldap_distproc_t ) ); + if ( lc == NULL ) { + return 1; + } + memset( lc, 0, sizeof( ldap_distproc_t ) ); + ldap_pvt_thread_mutex_init( &lc->lc_lai.lai_mutex ); + + on->on_bi.bi_private = (void *)lc; + + return 0; +} + +static int +ldap_distproc_db_config( + BackendDB *be, + const char *fname, + int lineno, + int argc, + char **argv ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + ldap_distproc_t *lc = (ldap_distproc_t *)on->on_bi.bi_private; + + int rc = SLAP_CONF_UNKNOWN; + + if ( lc->lc_common_li == NULL ) { + void *be_private = be->be_private; + ldap_distproc_db_init_common( be ); + lc->lc_common_li = lc->lc_cfg_li = (ldapinfo_t *)be->be_private; + be->be_private = be_private; + } + + /* Something for the distproc database? */ + if ( strncasecmp( argv[ 0 ], "distproc-", STRLENOF( "distproc-" ) ) == 0 ) { + char *save_argv0 = argv[ 0 ]; + BackendInfo *bd_info = be->bd_info; + void *be_private = be->be_private; + ConfigOCs *be_cf_ocs = be->be_cf_ocs; + int is_uri = 0; + + argv[ 0 ] += STRLENOF( "distproc-" ); + + if ( strcasecmp( argv[ 0 ], "uri" ) == 0 ) { + rc = ldap_distproc_db_init_one( be ); + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "underlying slapd-ldap initialization failed.\n.", + fname, lineno ); + return 1; + } + lc->lc_cfg_li = be->be_private; + is_uri = 1; + } + + /* TODO: add checks on what other slapd-ldap(5) args + * should be put in the template; this is not quite + * harmful, because attributes that shouldn't don't + * get actually used, but the user should at least + * be warned. + */ + + be->bd_info = lback; + be->be_private = (void *)lc->lc_cfg_li; + be->be_cf_ocs = lback->bi_cf_ocs; + + rc = config_generic_wrapper( be, fname, lineno, argc, argv ); + + argv[ 0 ] = save_argv0; + be->be_cf_ocs = be_cf_ocs; + be->be_private = be_private; + be->bd_info = bd_info; + + if ( is_uri ) { +private_destroy:; + if ( rc != 0 ) { + BackendDB db = *be; + + db.bd_info = lback; + db.be_private = (void *)lc->lc_cfg_li; + ldap_distproc_db_destroy_one( &db, NULL ); + lc->lc_cfg_li = NULL; + + } else { + if ( lc->lc_cfg_li->li_bvuri == NULL + || BER_BVISNULL( &lc->lc_cfg_li->li_bvuri[ 0 ] ) + || !BER_BVISNULL( &lc->lc_cfg_li->li_bvuri[ 1 ] ) ) + { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "no URI list allowed in slapo-distproc.\n", + fname, lineno ); + rc = 1; + goto private_destroy; + } + + if ( ldap_tavl_insert( &lc->lc_lai.lai_tree, + (caddr_t)lc->lc_cfg_li, + ldap_distproc_uri_cmp, ldap_distproc_uri_dup ) ) + { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + "duplicate URI in slapo-distproc.\n", + fname, lineno ); + rc = 1; + goto private_destroy; + } + } + } + } + + return rc; +} + +enum db_which { + db_open = 0, + db_close, + db_destroy, + + db_last +}; + +static int +ldap_distproc_db_func( + BackendDB *be, + enum db_which which +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + ldap_distproc_t *lc = (ldap_distproc_t *)on->on_bi.bi_private; + + int rc = 0; + + if ( lc ) { + BI_db_func *func = (&lback->bi_db_open)[ which ]; + + if ( func != NULL && lc->lc_common_li != NULL ) { + BackendDB db = *be; + + db.bd_info = lback; + db.be_private = lc->lc_common_li; + + rc = func( &db, NULL ); + + if ( rc != 0 ) { + return rc; + } + + if ( lc->lc_lai.lai_tree != NULL ) { + TAvlnode *edge = ldap_tavl_end( lc->lc_lai.lai_tree, TAVL_DIR_LEFT ); + while ( edge ) { + TAvlnode *next = ldap_tavl_next( edge, TAVL_DIR_RIGHT ); + ldapinfo_t *li = (ldapinfo_t *)edge->avl_data; + be->be_private = (void *)li; + rc = func( &db, NULL ); + if ( rc == 1 ) { + break; + } + edge = next; + } + } + } + } + + return rc; +} + +static int +ldap_distproc_db_open( + BackendDB *be, + ConfigReply *cr ) +{ + return ldap_distproc_db_func( be, db_open ); +} + +static int +ldap_distproc_db_close( + BackendDB *be, + ConfigReply *cr ) +{ + return ldap_distproc_db_func( be, db_close ); +} + +static int +ldap_distproc_db_destroy( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + ldap_distproc_t *lc = (ldap_distproc_t *)on->on_bi.bi_private; + + int rc; + + rc = ldap_distproc_db_func( be, db_destroy ); + + if ( lc ) { + ldap_tavl_free( lc->lc_lai.lai_tree, NULL ); + ldap_pvt_thread_mutex_destroy( &lc->lc_lai.lai_mutex ); + ch_free( lc ); + } + + return rc; +} + +/* + * inits one instance of the slapd-ldap backend, and stores + * the private info in be_private of the arg + */ +static int +ldap_distproc_db_init_common( + BackendDB *be ) +{ + BackendInfo *bi = be->bd_info; + int t; + + be->bd_info = lback; + be->be_private = NULL; + t = lback->bi_db_init( be, NULL ); + if ( t != 0 ) { + return t; + } + be->bd_info = bi; + + return 0; +} + +/* + * inits one instance of the slapd-ldap backend, stores + * the private info in be_private of the arg and fills + * selected fields with data from the template. + * + * NOTE: add checks about the other fields of the template, + * which are ignored and SHOULD NOT be configured by the user. + */ +static int +ldap_distproc_db_init_one( + BackendDB *be ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + ldap_distproc_t *lc = (ldap_distproc_t *)on->on_bi.bi_private; + + BackendInfo *bi = be->bd_info; + ldapinfo_t *li; + + slap_op_t t; + + be->bd_info = lback; + be->be_private = NULL; + t = lback->bi_db_init( be, NULL ); + if ( t != 0 ) { + return t; + } + li = (ldapinfo_t *)be->be_private; + + /* copy common data */ + li->li_nretries = lc->lc_common_li->li_nretries; + li->li_flags = lc->lc_common_li->li_flags; + li->li_version = lc->lc_common_li->li_version; + for ( t = 0; t < SLAP_OP_LAST; t++ ) { + li->li_timeout[ t ] = lc->lc_common_li->li_timeout[ t ]; + } + be->bd_info = bi; + + return 0; +} + +static int +ldap_distproc_connection_destroy( + BackendDB *be, + Connection *conn +) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + ldap_distproc_t *lc = (ldap_distproc_t *)on->on_bi.bi_private; + void *private = be->be_private; + int rc; + TAvlnode *edge; + + be->be_private = NULL; + ldap_pvt_thread_mutex_lock( &lc->lc_lai.lai_mutex ); + edge = ldap_tavl_end( lc->lc_lai.lai_tree, TAVL_DIR_LEFT ); + while ( edge ) { + TAvlnode *next = ldap_tavl_next( edge, TAVL_DIR_RIGHT ); + ldapinfo_t *li = (ldapinfo_t *)edge->avl_data; + be->be_private = (void *)li; + rc = lback->bi_connection_destroy( be, conn ); + if ( rc == 1 ) { + break; + } + edge = next; + } + ldap_pvt_thread_mutex_unlock( &lc->lc_lai.lai_mutex ); + be->be_private = private; + + return rc; +} + +static int +ldap_distproc_parse_returnContRef_ctrl( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + if ( get_returnContRef( op ) != SLAP_CONTROL_NONE ) { + rs->sr_text = "returnContinuationReference control specified multiple times"; + return LDAP_PROTOCOL_ERROR; + } + + if ( op->o_pagedresults != SLAP_CONTROL_NONE ) { + rs->sr_text = "returnContinuationReference control specified with pagedResults control"; + return LDAP_PROTOCOL_ERROR; + } + + if ( !BER_BVISEMPTY( &ctrl->ldctl_value ) ) { + rs->sr_text = "returnContinuationReference control: value must be NULL"; + return LDAP_PROTOCOL_ERROR; + } + + op->o_returnContRef = ctrl->ldctl_iscritical ? SLAP_CONTROL_CRITICAL : SLAP_CONTROL_NONCRITICAL; + + return LDAP_SUCCESS; +} + +static int +ldap_exop_chained_request( + Operation *op, + SlapReply *rs ) +{ + Debug( LDAP_DEBUG_STATS, "%s CHAINED REQUEST\n", + op->o_log_prefix ); + + rs->sr_err = backend_check_restrictions( op, rs, + (struct berval *)&slap_EXOP_CHAINEDREQUEST ); + if ( rs->sr_err != LDAP_SUCCESS ) { + return rs->sr_err; + } + + /* by now, just reject requests */ + rs->sr_text = "under development"; + return LDAP_UNWILLING_TO_PERFORM; +} + + +static slap_overinst distproc; + +int +distproc_initialize( void ) +{ + int rc; + + /* Make sure we don't exceed the bits reserved for userland */ + config_check_userland( DP_LAST ); + + rc = load_extop( (struct berval *)&slap_EXOP_CHAINEDREQUEST, + SLAP_EXOP_HIDE, ldap_exop_chained_request ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "slapd-distproc: " + "unable to register chainedRequest exop: %d.\n", + rc ); + return rc; + } + + rc = supported_feature_load( &slap_FEATURE_CANCHAINOPS ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "slapd-distproc: " + "unable to register canChainOperations supported feature: %d.\n", + rc ); + return rc; + } + + rc = register_supported_control( LDAP_CONTROL_X_RETURNCONTREF, + SLAP_CTRL_GLOBAL|SLAP_CTRL_ACCESS|SLAP_CTRL_HIDE, NULL, + ldap_distproc_parse_returnContRef_ctrl, &sc_returnContRef ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "slapd-distproc: " + "unable to register returnContinuationReference control: %d.\n", + rc ); + return rc; + } + + distproc.on_bi.bi_type = "distproc"; + distproc.on_bi.bi_db_init = ldap_distproc_db_init; + distproc.on_bi.bi_db_config = ldap_distproc_db_config; + distproc.on_bi.bi_db_open = ldap_distproc_db_open; + distproc.on_bi.bi_db_close = ldap_distproc_db_close; + distproc.on_bi.bi_db_destroy = ldap_distproc_db_destroy; + + /* ... otherwise the underlying backend's function would be called, + * likely passing an invalid entry; on the contrary, the requested + * operational attributes should have been returned while chasing + * the referrals. This all in all is a bit messy, because part + * of the operational attributes are generated by the backend; + * part by the frontend; back-ldap should receive all the available + * ones from the remote server, but then, on its own, it strips those + * it assumes will be (re)generated by the frontend (e.g. + * subschemaSubentry, entryDN, ...) */ + distproc.on_bi.bi_operational = ldap_distproc_operational; + + distproc.on_bi.bi_connection_destroy = ldap_distproc_connection_destroy; + + distproc.on_response = ldap_distproc_response; + + distproc.on_bi.bi_cf_ocs = distproc_ocs; + + rc = config_register_schema( distproc_cfg, distproc_ocs ); + if ( rc ) { + return rc; + } + + return overlay_register( &distproc ); +} + +#endif /* SLAP_DISTPROC */ diff --git a/servers/slapd/back-ldap/extended.c b/servers/slapd/back-ldap/extended.c new file mode 100644 index 0000000..9b60cad --- /dev/null +++ b/servers/slapd/back-ldap/extended.c @@ -0,0 +1,410 @@ +/* extended.c - ldap backend extended routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2022 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>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "slap.h" +#include "back-ldap.h" +#include "lber_pvt.h" + +typedef int (ldap_back_exop_f)( Operation *op, SlapReply *rs, ldapconn_t **lc ); + +static ldap_back_exop_f ldap_back_exop_passwd; +static ldap_back_exop_f ldap_back_exop_generic; + +static struct exop { + struct berval oid; + ldap_back_exop_f *extended; +} exop_table[] = { + { BER_BVC(LDAP_EXOP_MODIFY_PASSWD), ldap_back_exop_passwd }, + { BER_BVNULL, NULL } +}; + +static int +ldap_back_extended_one( Operation *op, SlapReply *rs, ldap_back_exop_f exop ) +{ + ldapinfo_t *li = (ldapinfo_t *) op->o_bd->be_private; + + ldapconn_t *lc = NULL; + LDAPControl **ctrls = NULL, **oldctrls = NULL; + int rc; + + /* FIXME: this needs to be called here, so it is + * called twice; maybe we could avoid the + * ldap_back_dobind() call inside each extended() + * call ... */ + if ( !ldap_back_dobind( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + return -1; + } + + ctrls = oldctrls = op->o_ctrls; + if ( ldap_back_controls_add( op, rs, lc, &ctrls ) ) + { + op->o_ctrls = oldctrls; + send_ldap_extended( op, rs ); + rs->sr_text = NULL; + /* otherwise frontend resends result */ + rc = rs->sr_err = SLAPD_ABANDON; + goto done; + } + + op->o_ctrls = ctrls; + rc = exop( op, rs, &lc ); + + op->o_ctrls = oldctrls; + (void)ldap_back_controls_free( op, rs, &ctrls ); + +done:; + if ( lc != NULL ) { + ldap_back_release_conn( li, lc ); + } + + return rc; +} + +int +ldap_back_extended( + Operation *op, + SlapReply *rs ) +{ + int i; + + RS_ASSERT( !(rs->sr_flags & REP_ENTRY_MASK) ); + rs->sr_flags &= ~REP_ENTRY_MASK; /* paranoia */ + + for ( i = 0; exop_table[i].extended != NULL; i++ ) { + if ( bvmatch( &exop_table[i].oid, &op->oq_extended.rs_reqoid ) ) + { + return ldap_back_extended_one( op, rs, exop_table[i].extended ); + } + } + + /* if we get here, the exop is known; the best that we can do + * is pass it thru as is */ + /* FIXME: maybe a list of OIDs to pass thru would be safer */ + return ldap_back_extended_one( op, rs, ldap_back_exop_generic ); +} + +static int +ldap_back_exop_passwd( + Operation *op, + SlapReply *rs, + ldapconn_t **lcp ) +{ + ldapinfo_t *li = (ldapinfo_t *) op->o_bd->be_private; + + ldapconn_t *lc = *lcp; + req_pwdexop_s *qpw = &op->oq_pwdexop; + LDAPMessage *res; + ber_int_t msgid; + int rc, isproxy, freedn = 0; + int do_retry = 1; + char *text = NULL; + struct berval dn = op->o_req_dn, + ndn = op->o_req_ndn; + + assert( lc != NULL ); + assert( rs->sr_ctrls == NULL ); + + if ( BER_BVISNULL( &ndn ) && op->ore_reqdata != NULL ) { + /* NOTE: most of this code is mutated + * from slap_passwd_parse(); + * But here we only need + * the first berval... */ + + ber_tag_t tag; + ber_len_t len = -1; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + + struct berval tmpid = BER_BVNULL; + + if ( op->ore_reqdata->bv_len == 0 ) { + return LDAP_PROTOCOL_ERROR; + } + + /* ber_init2 uses reqdata directly, doesn't allocate new buffers */ + ber_init2( ber, op->ore_reqdata, 0 ); + + tag = ber_scanf( ber, "{" /*}*/ ); + + if ( tag == LBER_ERROR ) { + return LDAP_PROTOCOL_ERROR; + } + + tag = ber_peek_tag( ber, &len ); + if ( tag == LDAP_TAG_EXOP_MODIFY_PASSWD_ID ) { + tag = ber_get_stringbv( ber, &tmpid, LBER_BV_NOTERM ); + + if ( tag == LBER_ERROR ) { + return LDAP_PROTOCOL_ERROR; + } + } + + if ( !BER_BVISEMPTY( &tmpid ) ) { + char idNull = tmpid.bv_val[tmpid.bv_len]; + tmpid.bv_val[tmpid.bv_len] = '\0'; + rs->sr_err = dnPrettyNormal( NULL, &tmpid, &dn, + &ndn, op->o_tmpmemctx ); + tmpid.bv_val[tmpid.bv_len] = idNull; + if ( rs->sr_err != LDAP_SUCCESS ) { + /* should have been successfully parsed earlier! */ + return rs->sr_err; + } + freedn = 1; + + } else { + dn = op->o_dn; + ndn = op->o_ndn; + } + } + + isproxy = ber_bvcmp( &ndn, &op->o_ndn ); + + Debug( LDAP_DEBUG_ARGS, "==> ldap_back_exop_passwd(\"%s\")%s\n", + dn.bv_val, isproxy ? " (proxy)" : "" ); + +retry: + rc = ldap_passwd( lc->lc_ld, &dn, + qpw->rs_old.bv_val ? &qpw->rs_old : NULL, + qpw->rs_new.bv_val ? &qpw->rs_new : NULL, + op->o_ctrls, NULL, &msgid ); + + if ( rc == LDAP_SUCCESS ) { + /* TODO: set timeout? */ + /* by now, make sure no timeout is used (ITS#6282) */ + struct timeval tv = { -1, 0 }; + if ( ldap_result( lc->lc_ld, msgid, LDAP_MSG_ALL, &tv, &res ) == -1 ) { + ldap_get_option( lc->lc_ld, LDAP_OPT_ERROR_NUMBER, &rc ); + rs->sr_err = rc; + + } else { + /* only touch when activity actually took place... */ + if ( li->li_idle_timeout ) { + lc->lc_time = op->o_time; + } + + /* sigh. parse twice, because parse_passwd + * doesn't give us the err / match / msg info. + */ + rc = ldap_parse_result( lc->lc_ld, res, &rs->sr_err, + (char **)&rs->sr_matched, + &text, + NULL, &rs->sr_ctrls, 0 ); + + if ( rc == LDAP_SUCCESS ) { + if ( rs->sr_err == LDAP_SUCCESS ) { + struct berval newpw; + + /* this never happens because + * the frontend is generating + * the new password, so when + * the passwd exop is proxied, + * it never delegates password + * generation to the remote server + */ + rc = ldap_parse_passwd( lc->lc_ld, res, + &newpw ); + if ( rc == LDAP_SUCCESS && + !BER_BVISNULL( &newpw ) ) + { + rs->sr_type = REP_EXTENDED; + rs->sr_rspdata = slap_passwd_return( &newpw ); + free( newpw.bv_val ); + } + + } else { + rc = rs->sr_err; + } + } + ldap_msgfree( res ); + } + } + + if ( rc != LDAP_SUCCESS ) { + rs->sr_err = slap_map_api2result( rs ); + if ( rs->sr_err == LDAP_UNAVAILABLE && do_retry ) { + do_retry = 0; + if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + goto retry; + } + } + + if ( LDAP_BACK_QUARANTINE( li ) ) { + ldap_back_quarantine( op, rs ); + } + + if ( text ) rs->sr_text = text; + send_ldap_extended( op, rs ); + /* otherwise frontend resends result */ + rc = rs->sr_err = SLAPD_ABANDON; + + } else if ( LDAP_BACK_QUARANTINE( li ) ) { + ldap_back_quarantine( op, rs ); + } + + ldap_pvt_thread_mutex_lock( &li->li_counter_mutex ); + ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_EXTENDED ], 1 ); + ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex ); + + if ( freedn ) { + op->o_tmpfree( dn.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( ndn.bv_val, op->o_tmpmemctx ); + } + + /* these have to be freed anyway... */ + if ( rs->sr_matched ) { + free( (char *)rs->sr_matched ); + rs->sr_matched = NULL; + } + + if ( rs->sr_ctrls ) { + ldap_controls_free( rs->sr_ctrls ); + rs->sr_ctrls = NULL; + } + + if ( text ) { + free( text ); + rs->sr_text = NULL; + } + + /* in case, cleanup handler */ + if ( lc == NULL ) { + *lcp = NULL; + } + + return rc; +} + +static int +ldap_back_exop_generic( + Operation *op, + SlapReply *rs, + ldapconn_t **lcp ) +{ + ldapinfo_t *li = (ldapinfo_t *) op->o_bd->be_private; + + ldapconn_t *lc = *lcp; + LDAPMessage *res; + ber_int_t msgid; + int rc; + int do_retry = 1; + char *text = NULL; + + Debug( LDAP_DEBUG_ARGS, "==> ldap_back_exop_generic(%s, \"%s\")\n", + op->ore_reqoid.bv_val, op->o_req_dn.bv_val ); + assert( lc != NULL ); + assert( rs->sr_ctrls == NULL ); + +retry: + rc = ldap_extended_operation( lc->lc_ld, + op->ore_reqoid.bv_val, op->ore_reqdata, + op->o_ctrls, NULL, &msgid ); + + if ( rc == LDAP_SUCCESS ) { + /* TODO: set timeout? */ + /* by now, make sure no timeout is used (ITS#6282) */ + struct timeval tv = { -1, 0 }; + if ( ldap_result( lc->lc_ld, msgid, LDAP_MSG_ALL, &tv, &res ) == -1 ) { + ldap_get_option( lc->lc_ld, LDAP_OPT_ERROR_NUMBER, &rc ); + rs->sr_err = rc; + + } else { + /* only touch when activity actually took place... */ + if ( li->li_idle_timeout ) { + lc->lc_time = op->o_time; + } + + /* sigh. parse twice, because parse_passwd + * doesn't give us the err / match / msg info. + */ + rc = ldap_parse_result( lc->lc_ld, res, &rs->sr_err, + (char **)&rs->sr_matched, + &text, + NULL, &rs->sr_ctrls, 0 ); + if ( rc == LDAP_SUCCESS ) { + if ( rs->sr_err == LDAP_SUCCESS ) { + rc = ldap_parse_extended_result( lc->lc_ld, res, + (char **)&rs->sr_rspoid, &rs->sr_rspdata, 0 ); + if ( rc == LDAP_SUCCESS ) { + rs->sr_type = REP_EXTENDED; + } + + } else { + rc = rs->sr_err; + } + } + ldap_msgfree( res ); + } + } + + if ( rc != LDAP_SUCCESS ) { + rs->sr_err = slap_map_api2result( rs ); + if ( rs->sr_err == LDAP_UNAVAILABLE && do_retry ) { + do_retry = 0; + if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + goto retry; + } + } + + if ( LDAP_BACK_QUARANTINE( li ) ) { + ldap_back_quarantine( op, rs ); + } + + if ( text ) rs->sr_text = text; + send_ldap_extended( op, rs ); + /* otherwise frontend resends result */ + rc = rs->sr_err = SLAPD_ABANDON; + + } else if ( LDAP_BACK_QUARANTINE( li ) ) { + ldap_back_quarantine( op, rs ); + } + + ldap_pvt_thread_mutex_lock( &li->li_counter_mutex ); + ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_EXTENDED ], 1 ); + ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex ); + + /* these have to be freed anyway... */ + if ( rs->sr_matched ) { + free( (char *)rs->sr_matched ); + rs->sr_matched = NULL; + } + + if ( rs->sr_ctrls ) { + ldap_controls_free( rs->sr_ctrls ); + rs->sr_ctrls = NULL; + } + + if ( text ) { + free( text ); + rs->sr_text = NULL; + } + + /* in case, cleanup handler */ + if ( lc == NULL ) { + *lcp = NULL; + } + + return rc; +} diff --git a/servers/slapd/back-ldap/init.c b/servers/slapd/back-ldap/init.c new file mode 100644 index 0000000..e2db2a0 --- /dev/null +++ b/servers/slapd/back-ldap/init.c @@ -0,0 +1,374 @@ +/* init.c - initialize ldap backend */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2022 The OpenLDAP Foundation. + * Portions Copyright 1999-2003 Howard Chu. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * 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>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "slap-config.h" +#include "back-ldap.h" +#include "ldap_rq.h" + +static const ldap_extra_t ldap_extra = { + ldap_back_proxy_authz_ctrl, + ldap_back_controls_free, + slap_idassert_authzfrom_parse, + slap_idassert_passthru_parse_cf, + slap_idassert_parse, + slap_retry_info_destroy, + slap_retry_info_parse, + slap_retry_info_unparse, + ldap_back_connid2str +}; + +int +ldap_back_open( BackendInfo *bi ) +{ + bi->bi_controls = slap_known_controls; + return 0; +} + +int +ldap_back_initialize( BackendInfo *bi ) +{ + int rc; + + bi->bi_flags = +#ifdef LDAP_DYNAMIC_OBJECTS + /* this is set because all the support a proxy has to provide + * is the capability to forward the refresh exop, and to + * pass thru entries that contain the dynamicObject class + * and the entryTtl attribute */ + SLAP_BFLAG_DYNAMIC | +#endif /* LDAP_DYNAMIC_OBJECTS */ + + /* back-ldap recognizes RFC4525 increment; + * let the remote server complain, if needed (ITS#5912) */ + SLAP_BFLAG_INCREMENT; + + bi->bi_open = ldap_back_open; + bi->bi_config = 0; + bi->bi_close = 0; + bi->bi_destroy = 0; + + bi->bi_db_init = ldap_back_db_init; + bi->bi_db_config = config_generic_wrapper; + bi->bi_db_open = ldap_back_db_open; + bi->bi_db_close = ldap_back_db_close; + bi->bi_db_destroy = ldap_back_db_destroy; + + bi->bi_op_bind = ldap_back_bind; + bi->bi_op_unbind = 0; + bi->bi_op_search = ldap_back_search; + bi->bi_op_compare = ldap_back_compare; + bi->bi_op_modify = ldap_back_modify; + bi->bi_op_modrdn = ldap_back_modrdn; + bi->bi_op_add = ldap_back_add; + bi->bi_op_delete = ldap_back_delete; + bi->bi_op_abandon = 0; + + bi->bi_extended = ldap_back_extended; + + bi->bi_chk_referrals = 0; + bi->bi_entry_get_rw = ldap_back_entry_get; + + bi->bi_connection_init = 0; + bi->bi_connection_destroy = ldap_back_conn_destroy; + + bi->bi_extra = (void *)&ldap_extra; + + rc = ldap_back_init_cf( bi ); + if ( rc ) { + return rc; + } + + rc = chain_initialize(); + if ( rc ) { + return rc; + } + + rc = pbind_initialize(); + if ( rc ) { + return rc; + } + +#ifdef SLAP_DISTPROC + rc = distproc_initialize(); + if ( rc ) { + return rc; + } +#endif + return rc; +} + +int +ldap_back_db_init( Backend *be, ConfigReply *cr ) +{ + ldapinfo_t *li; + int rc; + unsigned i; + + li = (ldapinfo_t *)ch_calloc( 1, sizeof( ldapinfo_t ) ); + if ( li == NULL ) { + return -1; + } + + li->li_rebind_f = ldap_back_default_rebind; + li->li_urllist_f = ldap_back_default_urllist; + li->li_urllist_p = li; + ldap_pvt_thread_mutex_init( &li->li_uri_mutex ); + + BER_BVZERO( &li->li_acl_authcID ); + BER_BVZERO( &li->li_acl_authcDN ); + BER_BVZERO( &li->li_acl_passwd ); + + li->li_acl_authmethod = LDAP_AUTH_NONE; + BER_BVZERO( &li->li_acl_sasl_mech ); + li->li_acl.sb_tls = SB_TLS_DEFAULT; + + li->li_idassert_mode = LDAP_BACK_IDASSERT_LEGACY; + + BER_BVZERO( &li->li_idassert_authcID ); + BER_BVZERO( &li->li_idassert_authcDN ); + BER_BVZERO( &li->li_idassert_passwd ); + + BER_BVZERO( &li->li_idassert_authzID ); + + li->li_idassert_authmethod = LDAP_AUTH_NONE; + BER_BVZERO( &li->li_idassert_sasl_mech ); + li->li_idassert_tls = SB_TLS_DEFAULT; + + /* by default, use proxyAuthz control on each operation */ + li->li_idassert_flags = LDAP_BACK_AUTH_PRESCRIPTIVE; + + li->li_idassert_authz = NULL; + + /* initialize flags */ + li->li_flags = LDAP_BACK_F_CHASE_REFERRALS; + + /* initialize version */ + li->li_version = LDAP_VERSION3; + + ldap_pvt_thread_mutex_init( &li->li_conninfo.lai_mutex ); + + for ( i = LDAP_BACK_PCONN_FIRST; i < LDAP_BACK_PCONN_LAST; i++ ) { + li->li_conn_priv[ i ].lic_num = 0; + LDAP_TAILQ_INIT( &li->li_conn_priv[ i ].lic_priv ); + } + li->li_conn_priv_max = LDAP_BACK_CONN_PRIV_DEFAULT; + + ldap_pvt_thread_mutex_init( &li->li_counter_mutex ); + for ( i = 0; i < SLAP_OP_LAST; i++ ) { + ldap_pvt_mp_init( li->li_ops_completed[ i ] ); + } + + li->li_conn_expire_task = NULL; + + be->be_private = li; + SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_NOLASTMOD; + + be->be_cf_ocs = be->bd_info->bi_cf_ocs; + + rc = ldap_back_monitor_db_init( be ); + if ( rc != 0 ) { + /* ignore, by now */ + rc = 0; + } + + return rc; +} + +int +ldap_back_db_open( BackendDB *be, ConfigReply *cr ) +{ + ldapinfo_t *li = (ldapinfo_t *)be->be_private; + + slap_bindconf sb = { BER_BVNULL }; + int rc = 0; + + Debug( LDAP_DEBUG_TRACE, + "ldap_back_db_open: URI=%s\n", + li->li_uri != NULL ? li->li_uri : "" ); + + /* by default, use proxyAuthz control on each operation */ + switch ( li->li_idassert_mode ) { + case LDAP_BACK_IDASSERT_LEGACY: + case LDAP_BACK_IDASSERT_SELF: + /* however, since admin connections are pooled and shared, + * only static authzIDs can be native */ + li->li_idassert_flags &= ~LDAP_BACK_AUTH_NATIVE_AUTHZ; + break; + + default: + break; + } + + ber_str2bv( li->li_uri, 0, 0, &sb.sb_uri ); + sb.sb_version = li->li_version; + sb.sb_method = LDAP_AUTH_SIMPLE; + BER_BVSTR( &sb.sb_binddn, "" ); + + if ( LDAP_BACK_T_F_DISCOVER( li ) && !LDAP_BACK_T_F( li ) ) { + rc = slap_discover_feature( &sb, + slap_schema.si_ad_supportedFeatures->ad_cname.bv_val, + LDAP_FEATURE_ABSOLUTE_FILTERS ); + if ( rc == LDAP_COMPARE_TRUE ) { + li->li_flags |= LDAP_BACK_F_T_F; + } + } + + if ( LDAP_BACK_CANCEL_DISCOVER( li ) && !LDAP_BACK_CANCEL( li ) ) { + rc = slap_discover_feature( &sb, + slap_schema.si_ad_supportedExtension->ad_cname.bv_val, + LDAP_EXOP_CANCEL ); + if ( rc == LDAP_COMPARE_TRUE ) { + li->li_flags |= LDAP_BACK_F_CANCEL_EXOP; + } + } + + /* monitor setup */ + rc = ldap_back_monitor_db_open( be ); + if ( rc != 0 ) { + /* ignore by now */ + rc = 0; + } + + li->li_flags |= LDAP_BACK_F_ISOPEN; + + return rc; +} + +void +ldap_back_conn_free( void *v_lc ) +{ + ldapconn_t *lc = v_lc; + + if ( lc->lc_ld != NULL ) { + ldap_unbind_ext( lc->lc_ld, NULL, NULL ); + } + if ( !BER_BVISNULL( &lc->lc_bound_ndn ) ) { + ch_free( lc->lc_bound_ndn.bv_val ); + } + if ( !BER_BVISNULL( &lc->lc_cred ) ) { + memset( lc->lc_cred.bv_val, 0, lc->lc_cred.bv_len ); + ch_free( lc->lc_cred.bv_val ); + } + if ( !BER_BVISNULL( &lc->lc_local_ndn ) ) { + ch_free( lc->lc_local_ndn.bv_val ); + } + lc->lc_q.tqe_prev = NULL; + lc->lc_q.tqe_next = NULL; + ch_free( lc ); +} + +int +ldap_back_db_close( Backend *be, ConfigReply *cr ) +{ + int rc = 0; + + if ( be->be_private ) { + rc = ldap_back_monitor_db_close( be ); + } + + return rc; +} + +int +ldap_back_db_destroy( Backend *be, ConfigReply *cr ) +{ + if ( be->be_private ) { + ldapinfo_t *li = ( ldapinfo_t * )be->be_private; + unsigned i; + + (void)ldap_back_monitor_db_destroy( be ); + + /* Stop and remove the task that prunes expired connections */ + if ( li->li_conn_expire_task != NULL ) { + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + if ( ldap_pvt_runqueue_isrunning( &slapd_rq, li->li_conn_expire_task ) ) { + ldap_pvt_runqueue_stoptask( &slapd_rq, li->li_conn_expire_task ); + } + ldap_pvt_runqueue_remove( &slapd_rq, li->li_conn_expire_task ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + + ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex ); + + if ( li->li_uri != NULL ) { + ch_free( li->li_uri ); + li->li_uri = NULL; + + assert( li->li_bvuri != NULL ); + ber_bvarray_free( li->li_bvuri ); + li->li_bvuri = NULL; + } + + bindconf_free( &li->li_tls ); + bindconf_free( &li->li_acl ); + bindconf_free( &li->li_idassert.si_bc ); + + if ( li->li_idassert_authz != NULL ) { + ber_bvarray_free( li->li_idassert_authz ); + li->li_idassert_authz = NULL; + } + if ( li->li_conninfo.lai_tree ) { + ldap_tavl_free( li->li_conninfo.lai_tree, ldap_back_conn_free ); + } + for ( i = LDAP_BACK_PCONN_FIRST; i < LDAP_BACK_PCONN_LAST; i++ ) { + while ( !LDAP_TAILQ_EMPTY( &li->li_conn_priv[ i ].lic_priv ) ) { + ldapconn_t *lc = LDAP_TAILQ_FIRST( &li->li_conn_priv[ i ].lic_priv ); + + LDAP_TAILQ_REMOVE( &li->li_conn_priv[ i ].lic_priv, lc, lc_q ); + ldap_back_conn_free( lc ); + } + } + if ( LDAP_BACK_QUARANTINE( li ) ) { + slap_retry_info_destroy( &li->li_quarantine ); + ldap_pvt_thread_mutex_destroy( &li->li_quarantine_mutex ); + } + + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + ldap_pvt_thread_mutex_destroy( &li->li_conninfo.lai_mutex ); + ldap_pvt_thread_mutex_destroy( &li->li_uri_mutex ); + + for ( i = 0; i < SLAP_OP_LAST; i++ ) { + ldap_pvt_mp_clear( li->li_ops_completed[ i ] ); + } + ldap_pvt_thread_mutex_destroy( &li->li_counter_mutex ); + } + + ch_free( be->be_private ); + + return 0; +} + +#if SLAPD_LDAP == SLAPD_MOD_DYNAMIC + +/* conditionally define the init_module() function */ +SLAP_BACKEND_INIT_MODULE( ldap ) + +#endif /* SLAPD_LDAP == SLAPD_MOD_DYNAMIC */ diff --git a/servers/slapd/back-ldap/modify.c b/servers/slapd/back-ldap/modify.c new file mode 100644 index 0000000..53e8a68 --- /dev/null +++ b/servers/slapd/back-ldap/modify.c @@ -0,0 +1,136 @@ +/* modify.c - ldap backend modify function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2022 The OpenLDAP Foundation. + * Portions Copyright 1999-2003 Howard Chu. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * 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>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "back-ldap.h" + +int +ldap_back_modify( + Operation *op, + SlapReply *rs ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + + ldapconn_t *lc = NULL; + LDAPMod **modv = NULL, + *mods = NULL; + Modifications *ml; + int i, j, rc; + ber_int_t msgid; + int isupdate; + ldap_back_send_t retrying = LDAP_BACK_RETRYING; + LDAPControl **ctrls = NULL; + + if ( !ldap_back_dobind( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + return rs->sr_err; + } + + for ( i = 0, ml = op->orm_modlist; ml; i++, ml = ml->sml_next ) + /* just count mods */ ; + + modv = (LDAPMod **)ch_malloc( ( i + 1 )*sizeof( LDAPMod * ) + + i*sizeof( LDAPMod ) ); + if ( modv == NULL ) { + rc = LDAP_NO_MEMORY; + goto cleanup; + } + mods = (LDAPMod *)&modv[ i + 1 ]; + + isupdate = be_shadow_update( op ); + for ( i = 0, ml = op->orm_modlist; ml; ml = ml->sml_next ) { + if ( !isupdate && !get_relax( op ) && ml->sml_desc->ad_type->sat_no_user_mod ) + { + continue; + } + + modv[ i ] = &mods[ i ]; + mods[ i ].mod_op = ( ml->sml_op | LDAP_MOD_BVALUES ); + mods[ i ].mod_type = ml->sml_desc->ad_cname.bv_val; + + if ( ml->sml_values != NULL ) { + for ( j = 0; !BER_BVISNULL( &ml->sml_values[ j ] ); j++ ) + /* just count mods */ ; + mods[ i ].mod_bvalues = + (struct berval **)ch_malloc( ( j + 1 )*sizeof( struct berval * ) ); + for ( j = 0; !BER_BVISNULL( &ml->sml_values[ j ] ); j++ ) + { + mods[ i ].mod_bvalues[ j ] = &ml->sml_values[ j ]; + } + mods[ i ].mod_bvalues[ j ] = NULL; + + } else { + mods[ i ].mod_bvalues = NULL; + } + + i++; + } + modv[ i ] = 0; + +retry:; + ctrls = op->o_ctrls; + rc = ldap_back_controls_add( op, rs, lc, &ctrls ); + if ( rc != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto cleanup; + } + + rs->sr_err = ldap_modify_ext( lc->lc_ld, op->o_req_dn.bv_val, modv, + ctrls, NULL, &msgid ); + rc = ldap_back_op_result( lc, op, rs, msgid, + li->li_timeout[ SLAP_OP_MODIFY ], + ( LDAP_BACK_SENDRESULT | retrying ) ); + if ( rs->sr_err == LDAP_UNAVAILABLE && retrying ) { + retrying &= ~LDAP_BACK_RETRYING; + if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + /* if the identity changed, there might be need to re-authz */ + (void)ldap_back_controls_free( op, rs, &ctrls ); + goto retry; + } + } + + ldap_pvt_thread_mutex_lock( &li->li_counter_mutex ); + ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_MODIFY ], 1 ); + ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex ); + +cleanup:; + (void)ldap_back_controls_free( op, rs, &ctrls ); + + for ( i = 0; modv[ i ]; i++ ) { + ch_free( modv[ i ]->mod_bvalues ); + } + ch_free( modv ); + + if ( lc != NULL ) { + ldap_back_release_conn( li, lc ); + } + + return rs->sr_err; +} + diff --git a/servers/slapd/back-ldap/modrdn.c b/servers/slapd/back-ldap/modrdn.c new file mode 100644 index 0000000..9c441eb --- /dev/null +++ b/servers/slapd/back-ldap/modrdn.c @@ -0,0 +1,123 @@ +/* modrdn.c - ldap backend modrdn function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2022 The OpenLDAP Foundation. + * Portions Copyright 1999-2003 Howard Chu. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * 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>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> + +#include "slap.h" +#include "back-ldap.h" + +int +ldap_back_modrdn( + Operation *op, + SlapReply *rs ) +{ + ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private; + + ldapconn_t *lc = NULL; + ber_int_t msgid; + LDAPControl **ctrls = NULL; + ldap_back_send_t retrying = LDAP_BACK_RETRYING; + int rc = LDAP_SUCCESS; + char *newSup = NULL; + struct berval newrdn = BER_BVNULL; + + if ( !ldap_back_dobind( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + return rs->sr_err; + } + + if ( op->orr_newSup ) { + /* needs LDAPv3 */ + switch ( li->li_version ) { + case LDAP_VERSION3: + break; + + case 0: + if ( op->o_protocol == 0 || op->o_protocol == LDAP_VERSION3 ) { + break; + } + /* fall thru */ + + default: + /* op->o_protocol cannot be anything but LDAPv3, + * otherwise wouldn't be here */ + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + send_ldap_result( op, rs ); + goto cleanup; + } + + newSup = op->orr_newSup->bv_val; + } + + /* NOTE: we need to copy the newRDN in case it was formed + * from a DN by simply changing the length (ITS#5397) */ + newrdn = op->orr_newrdn; + if ( newrdn.bv_val[ newrdn.bv_len ] != '\0' ) { + ber_dupbv_x( &newrdn, &op->orr_newrdn, op->o_tmpmemctx ); + } + +retry: + ctrls = op->o_ctrls; + rc = ldap_back_controls_add( op, rs, lc, &ctrls ); + if ( rc != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto cleanup; + } + + rs->sr_err = ldap_rename( lc->lc_ld, op->o_req_dn.bv_val, + newrdn.bv_val, newSup, + op->orr_deleteoldrdn, ctrls, NULL, &msgid ); + rc = ldap_back_op_result( lc, op, rs, msgid, + li->li_timeout[ SLAP_OP_MODRDN ], + ( LDAP_BACK_SENDRESULT | retrying ) ); + if ( rs->sr_err == LDAP_UNAVAILABLE && retrying ) { + retrying &= ~LDAP_BACK_RETRYING; + if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + /* if the identity changed, there might be need to re-authz */ + (void)ldap_back_controls_free( op, rs, &ctrls ); + goto retry; + } + } + + ldap_pvt_thread_mutex_lock( &li->li_counter_mutex ); + ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_MODRDN ], 1 ); + ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex ); + +cleanup: + (void)ldap_back_controls_free( op, rs, &ctrls ); + + if ( newrdn.bv_val != op->orr_newrdn.bv_val ) { + op->o_tmpfree( newrdn.bv_val, op->o_tmpmemctx ); + } + + if ( lc != NULL ) { + ldap_back_release_conn( li, lc ); + } + + return rs->sr_err; +} + diff --git a/servers/slapd/back-ldap/monitor.c b/servers/slapd/back-ldap/monitor.c new file mode 100644 index 0000000..16c2d6f --- /dev/null +++ b/servers/slapd/back-ldap/monitor.c @@ -0,0 +1,1074 @@ +/* monitor.c - monitor ldap backend */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2022 The OpenLDAP Foundation. + * Portions Copyright 1999-2003 Howard Chu. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * 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>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/unistd.h> +#include <ac/stdlib.h> +#include <ac/errno.h> +#include <sys/stat.h> +#include "lutil.h" +#include "back-ldap.h" + +#include "slap-config.h" + +static ObjectClass *oc_olmLDAPDatabase; +static ObjectClass *oc_olmLDAPConnection; + +static ObjectClass *oc_monitorContainer; +static ObjectClass *oc_monitorCounterObject; + +static AttributeDescription *ad_olmDbURIList; +static AttributeDescription *ad_olmDbOperations; +static AttributeDescription *ad_olmDbBoundDN; +static AttributeDescription *ad_olmDbConnFlags; +static AttributeDescription *ad_olmDbConnURI; +static AttributeDescription *ad_olmDbPeerAddress; + +/* + * Stolen from back-monitor/operations.c + * We don't need the normalized rdn's though. + */ +struct ldap_back_monitor_ops_t { + struct berval rdn; +} ldap_back_monitor_op[] = { + { BER_BVC( "cn=Bind" ) }, + { BER_BVC( "cn=Unbind" ) }, + { BER_BVC( "cn=Search" ) }, + { BER_BVC( "cn=Compare" ) }, + { BER_BVC( "cn=Modify" ) }, + { BER_BVC( "cn=Modrdn" ) }, + { BER_BVC( "cn=Add" ) }, + { BER_BVC( "cn=Delete" ) }, + { BER_BVC( "cn=Abandon" ) }, + { BER_BVC( "cn=Extended" ) }, + + { BER_BVNULL } +}; + +/* Corresponds to connection flags in back-ldap.h */ +static struct { + unsigned flag; + struct berval name; +} s_flag[] = { + { LDAP_BACK_FCONN_ISBOUND, BER_BVC( "bound" ) }, + { LDAP_BACK_FCONN_ISANON, BER_BVC( "anonymous" ) }, + { LDAP_BACK_FCONN_ISPRIV, BER_BVC( "privileged" ) }, + { LDAP_BACK_FCONN_ISTLS, BER_BVC( "TLS" ) }, + { LDAP_BACK_FCONN_BINDING, BER_BVC( "binding" ) }, + { LDAP_BACK_FCONN_TAINTED, BER_BVC( "tainted" ) }, + { LDAP_BACK_FCONN_ABANDON, BER_BVC( "abandon" ) }, + { LDAP_BACK_FCONN_ISIDASR, BER_BVC( "idassert" ) }, + { LDAP_BACK_FCONN_CACHED, BER_BVC( "cached" ) }, + + { 0 } +}; + + +/* + * NOTE: there's some confusion in monitor OID arc; + * by now, let's consider: + * + * Subsystems monitor attributes 1.3.6.1.4.1.4203.666.1.55.0 + * Databases monitor attributes 1.3.6.1.4.1.4203.666.1.55.0.1 + * LDAP database monitor attributes 1.3.6.1.4.1.4203.666.1.55.0.1.2 + * + * Subsystems monitor objectclasses 1.3.6.1.4.1.4203.666.3.16.0 + * Databases monitor objectclasses 1.3.6.1.4.1.4203.666.3.16.0.1 + * LDAP database monitor objectclasses 1.3.6.1.4.1.4203.666.3.16.0.1.2 + */ + +static struct { + char *name; + char *oid; +} s_oid[] = { + { "olmLDAPAttributes", "olmDatabaseAttributes:2" }, + { "olmLDAPObjectClasses", "olmDatabaseObjectClasses:2" }, + + { NULL } +}; + +static struct { + char *desc; + AttributeDescription **ad; +} s_at[] = { + { "( olmLDAPAttributes:1 " + "NAME ( 'olmDbURIList' ) " + "DESC 'List of URIs a proxy is serving; can be modified run-time' " + "SUP managedInfo )", + &ad_olmDbURIList }, + { "( olmLDAPAttributes:2 " + "NAME ( 'olmDbOperation' ) " + "DESC 'monitor operations performed' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmDbOperations }, + { "( olmLDAPAttributes:3 " + "NAME ( 'olmDbBoundDN' ) " + "DESC 'monitor connection authorization DN' " + "SUP monitorConnectionAuthzDN " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmDbBoundDN }, + { "( olmLDAPAttributes:4 " + "NAME ( 'olmDbConnFlags' ) " + "DESC 'monitor connection flags' " + "SUP monitoredInfo " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmDbConnFlags }, + { "( olmLDAPAttributes:5 " + "NAME ( 'olmDbConnURI' ) " + "DESC 'monitor connection URI' " + "SUP monitorConnectionPeerAddress " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmDbConnURI }, + { "( olmLDAPAttributes:6 " + "NAME ( 'olmDbConnPeerAddress' ) " + "DESC 'monitor connection peer address' " + "SUP monitorConnectionPeerAddress " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmDbPeerAddress }, + + { NULL } +}; + +static struct { + char *name; + ObjectClass **oc; +} s_moc[] = { + { "monitorContainer", &oc_monitorContainer }, + { "monitorCounterObject", &oc_monitorCounterObject }, + + { NULL } +}; + +static struct { + char *desc; + ObjectClass **oc; +} s_oc[] = { + /* augments an existing object, so it must be AUXILIARY + * FIXME: derive from some ABSTRACT "monitoredEntity"? */ + { "( olmLDAPObjectClasses:1 " + "NAME ( 'olmLDAPDatabase' ) " + "SUP top AUXILIARY " + "MAY ( " + "olmDbURIList " + ") )", + &oc_olmLDAPDatabase }, + { "( olmLDAPObjectClasses:2 " + "NAME ( 'olmLDAPConnection' ) " + "SUP monitorConnection STRUCTURAL " + "MAY ( " + "olmDbBoundDN " + "$ olmDbConnFlags " + "$ olmDbConnURI " + "$ olmDbConnPeerAddress " + ") )", + &oc_olmLDAPConnection }, + + { NULL } +}; + +static int +ldap_back_monitor_update( + Operation *op, + SlapReply *rs, + Entry *e, + void *priv ) +{ + ldapinfo_t *li = (ldapinfo_t *)priv; + + Attribute *a; + + /* update olmDbURIList */ + a = attr_find( e->e_attrs, ad_olmDbURIList ); + if ( a != NULL ) { + struct berval bv; + + assert( a->a_vals != NULL ); + assert( !BER_BVISNULL( &a->a_vals[ 0 ] ) ); + assert( BER_BVISNULL( &a->a_vals[ 1 ] ) ); + + ldap_pvt_thread_mutex_lock( &li->li_uri_mutex ); + if ( li->li_uri ) { + ber_str2bv( li->li_uri, 0, 0, &bv ); + if ( !bvmatch( &a->a_vals[ 0 ], &bv ) ) { + ber_bvreplace( &a->a_vals[ 0 ], &bv ); + } + } + ldap_pvt_thread_mutex_unlock( &li->li_uri_mutex ); + } + + return SLAP_CB_CONTINUE; +} + +static int +ldap_back_monitor_modify( + Operation *op, + SlapReply *rs, + Entry *e, + void *priv ) +{ + ldapinfo_t *li = (ldapinfo_t *) priv; + + Attribute *save_attrs = NULL; + Modifications *ml, + *ml_olmDbURIList = NULL; + struct berval ul = BER_BVNULL; + int got = 0; + + for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) { + if ( ml->sml_desc == ad_olmDbURIList ) { + if ( ml_olmDbURIList != NULL ) { + rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + rs->sr_text = "conflicting modifications"; + goto done; + } + + if ( ml->sml_op != LDAP_MOD_REPLACE ) { + rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + rs->sr_text = "modification not allowed"; + goto done; + } + + ml_olmDbURIList = ml; + got++; + continue; + } + } + + if ( got == 0 ) { + return SLAP_CB_CONTINUE; + } + + save_attrs = attrs_dup( e->e_attrs ); + + if ( ml_olmDbURIList != NULL ) { + Attribute *a = NULL; + LDAPURLDesc *ludlist = NULL; + int rc; + + ml = ml_olmDbURIList; + assert( ml->sml_nvalues != NULL ); + + if ( BER_BVISNULL( &ml->sml_nvalues[ 0 ] ) ) { + rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + rs->sr_text = "no value provided"; + goto done; + } + + if ( !BER_BVISNULL( &ml->sml_nvalues[ 1 ] ) ) { + rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + rs->sr_text = "multiple values provided"; + goto done; + } + + rc = ldap_url_parselist_ext( &ludlist, + ml->sml_nvalues[ 0 ].bv_val, NULL, + LDAP_PVT_URL_PARSE_NOEMPTY_HOST + | LDAP_PVT_URL_PARSE_DEF_PORT ); + if ( rc != LDAP_URL_SUCCESS ) { + rs->sr_err = LDAP_INVALID_SYNTAX; + rs->sr_text = "unable to parse URI list"; + goto done; + } + + ul.bv_val = ldap_url_list2urls( ludlist ); + ldap_free_urllist( ludlist ); + if ( ul.bv_val == NULL ) { + rs->sr_err = LDAP_OTHER; + goto done; + } + ul.bv_len = strlen( ul.bv_val ); + + a = attr_find( e->e_attrs, ad_olmDbURIList ); + if ( a != NULL ) { + if ( a->a_nvals == a->a_vals ) { + a->a_nvals = ch_calloc( sizeof( struct berval ), 2 ); + } + + ber_bvreplace( &a->a_vals[ 0 ], &ul ); + ber_bvreplace( &a->a_nvals[ 0 ], &ul ); + + } else { + attr_merge_normalize_one( e, ad_olmDbURIList, &ul, NULL ); + } + } + + /* apply changes */ + if ( !BER_BVISNULL( &ul ) ) { + ldap_pvt_thread_mutex_lock( &li->li_uri_mutex ); + if ( li->li_uri ) { + ch_free( li->li_uri ); + } + li->li_uri = ul.bv_val; + ldap_pvt_thread_mutex_unlock( &li->li_uri_mutex ); + + BER_BVZERO( &ul ); + } + +done:; + if ( !BER_BVISNULL( &ul ) ) { + ldap_memfree( ul.bv_val ); + } + + if ( rs->sr_err == LDAP_SUCCESS ) { + attrs_free( save_attrs ); + return SLAP_CB_CONTINUE; + } + + attrs_free( e->e_attrs ); + e->e_attrs = save_attrs; + + return rs->sr_err; +} + +static int +ldap_back_monitor_free( + Entry *e, + void **priv ) +{ + ldapinfo_t *li = (ldapinfo_t *)(*priv); + + *priv = NULL; + + if ( !slapd_shutdown ) { + memset( &li->li_monitor_info, 0, sizeof( li->li_monitor_info ) ); + } + + return SLAP_CB_CONTINUE; +} + +static int +ldap_back_monitor_subsystem_destroy( + BackendDB *be, + monitor_subsys_t *ms) +{ + free(ms->mss_dn.bv_val); + BER_BVZERO(&ms->mss_dn); + + free(ms->mss_ndn.bv_val); + BER_BVZERO(&ms->mss_ndn); + + return LDAP_SUCCESS; +} + +/* + * Connection monitoring subsystem: + * Tries to mimic what the cn=connections,cn=monitor subsystem does + * by creating volatile entries for each connection and populating them + * according to the information attached to the connection. + * At this moment the only exposed information is the DN used to bind it. + * Also note that the connection IDs are not and probably never will be + * stable. + */ + +struct ldap_back_monitor_conn_arg { + Operation *op; + monitor_subsys_t *ms; + Entry **ep; +}; + +/* code stolen from daemon.c */ +static int +ldap_back_monitor_conn_peername( + LDAP *ld, + struct berval *bv) +{ + Sockbuf *sockbuf; + ber_socket_t socket; + Sockaddr sa; + socklen_t salen = sizeof(sa); + const char *peeraddr = NULL; + /* we assume INET6_ADDRSTRLEN > INET_ADDRSTRLEN */ + char addr[INET6_ADDRSTRLEN]; +#ifdef LDAP_PF_LOCAL + char peername[MAXPATHLEN + sizeof("PATH=")]; +#elif defined(LDAP_PF_INET6) + char peername[sizeof("IP=[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:65535")]; +#else /* ! LDAP_PF_LOCAL && ! LDAP_PF_INET6 */ + char peername[sizeof("IP=255.255.255.255:65336")]; +#endif /* LDAP_PF_LOCAL */ + + assert( bv != NULL ); + + ldap_get_option( ld, LDAP_OPT_SOCKBUF, (void **)&sockbuf ); + ber_sockbuf_ctrl( sockbuf, LBER_SB_OPT_GET_FD, &socket ); + getpeername( socket, (struct sockaddr *)&sa, &salen ); + + switch ( sa.sa_addr.sa_family ) { +#ifdef LDAP_PF_LOCAL + case AF_LOCAL: + sprintf( peername, "PATH=%s", sa.sa_un_addr.sun_path ); + break; +#endif /* LDAP_PF_LOCAL */ + +#ifdef LDAP_PF_INET6 + case AF_INET6: + if ( IN6_IS_ADDR_V4MAPPED(&sa.sa_in6_addr.sin6_addr) ) { +#if defined( HAVE_GETADDRINFO ) && defined( HAVE_INET_NTOP ) + peeraddr = inet_ntop( AF_INET, + ((struct in_addr *)&sa.sa_in6_addr.sin6_addr.s6_addr[12]), + addr, sizeof(addr) ); +#else /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */ + peeraddr = inet_ntoa( *((struct in_addr *) + &sa.sa_in6_addr.sin6_addr.s6_addr[12]) ); +#endif /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */ + if ( !peeraddr ) peeraddr = SLAP_STRING_UNKNOWN; + sprintf( peername, "IP=%s:%d", peeraddr, + (unsigned) ntohs( sa.sa_in6_addr.sin6_port ) ); + } else { + peeraddr = inet_ntop( AF_INET6, + &sa.sa_in6_addr.sin6_addr, + addr, sizeof addr ); + if ( !peeraddr ) peeraddr = SLAP_STRING_UNKNOWN; + sprintf( peername, "IP=[%s]:%d", peeraddr, + (unsigned) ntohs( sa.sa_in6_addr.sin6_port ) ); + } + break; +#endif /* LDAP_PF_INET6 */ + + case AF_INET: { +#if defined( HAVE_GETADDRINFO ) && defined( HAVE_INET_NTOP ) + peeraddr = inet_ntop( AF_INET, &sa.sa_in_addr.sin_addr, + addr, sizeof(addr) ); +#else /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */ + peeraddr = inet_ntoa( sa.sa_in_addr.sin_addr ); +#endif /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */ + if ( !peeraddr ) peeraddr = SLAP_STRING_UNKNOWN; + sprintf( peername, "IP=%s:%d", peeraddr, + (unsigned) ntohs( sa.sa_in_addr.sin_port ) ); + } break; + + default: + sprintf( peername, SLAP_STRING_UNKNOWN ); + } + + ber_str2bv( peername, 0, 1, bv ); + return LDAP_SUCCESS; +} + +static int +ldap_back_monitor_conn_entry( + ldapconn_t *lc, + struct ldap_back_monitor_conn_arg *arg ) +{ + Entry *e; + monitor_entry_t *mp; + monitor_extra_t *mbe = arg->op->o_bd->bd_info->bi_extra; + char buf[SLAP_TEXT_BUFLEN]; + char *ptr; + struct berval bv; + int i; + + bv.bv_val = buf; + bv.bv_len = snprintf( bv.bv_val, SLAP_TEXT_BUFLEN, + "cn=Connection %lu", lc->lc_connid ); + + e = mbe->entry_stub( &arg->ms->mss_dn, &arg->ms->mss_ndn, &bv, + oc_monitorContainer, NULL, NULL ); + + attr_merge_normalize_one( e, ad_olmDbBoundDN, &lc->lc_bound_ndn, NULL ); + + for ( i = 0; s_flag[i].flag; i++ ) + { + if ( lc->lc_flags & s_flag[i].flag ) + { + attr_merge_normalize_one( e, ad_olmDbConnFlags, &s_flag[i].name, NULL ); + } + } + + ldap_get_option( lc->lc_ld, LDAP_OPT_URI, &bv.bv_val ); + ptr = strchr( bv.bv_val, ' ' ); + bv.bv_len = ptr ? ptr - bv.bv_val : strlen(bv.bv_val); + attr_merge_normalize_one( e, ad_olmDbConnURI, &bv, NULL ); + ch_free( bv.bv_val ); + + ldap_back_monitor_conn_peername( lc->lc_ld, &bv ); + attr_merge_normalize_one( e, ad_olmDbPeerAddress, &bv, NULL ); + ch_free( bv.bv_val ); + + mp = mbe->entrypriv_create(); + e->e_private = mp; + mp->mp_info = arg->ms; + mp->mp_flags = MONITOR_F_SUB | MONITOR_F_VOLATILE; + + *arg->ep = e; + arg->ep = &mp->mp_next; + + return 0; +} + +static int +ldap_back_monitor_conn_create( + Operation *op, + SlapReply *rs, + struct berval *ndn, + Entry *e_parent, + Entry **ep ) +{ + monitor_entry_t *mp_parent; + monitor_subsys_t *ms; + ldapinfo_t *li; + ldapconn_t *lc; + + struct ldap_back_monitor_conn_arg *arg; + int conn_type; + TAvlnode *edge; + + assert( e_parent->e_private != NULL ); + + mp_parent = e_parent->e_private; + ms = (monitor_subsys_t *)mp_parent->mp_info; + li = (ldapinfo_t *)ms->mss_private; + + arg = ch_calloc( 1, sizeof(struct ldap_back_monitor_conn_arg) ); + arg->op = op; + arg->ep = ep; + arg->ms = ms; + + for ( conn_type = LDAP_BACK_PCONN_FIRST; + conn_type < LDAP_BACK_PCONN_LAST; + conn_type++ ) + { + LDAP_TAILQ_FOREACH( lc, + &li->li_conn_priv[ conn_type ].lic_priv, + lc_q ) + { + ldap_back_monitor_conn_entry( lc, arg ); + } + } + + edge = ldap_tavl_end( li->li_conninfo.lai_tree, TAVL_DIR_LEFT ); + while ( edge ) { + TAvlnode *next = ldap_tavl_next( edge, TAVL_DIR_RIGHT ); + ldapconn_t *lc = (ldapconn_t *)edge->avl_data; + ldap_back_monitor_conn_entry( lc, arg ); + edge = next; + } + + ch_free( arg ); + + return 0; +} + +static int +ldap_back_monitor_conn_init( + BackendDB *be, + monitor_subsys_t *ms ) +{ + ldapinfo_t *li = (ldapinfo_t *) ms->mss_private; + monitor_extra_t *mbe; + + Entry *e; + int rc; + + assert( be != NULL ); + mbe = (monitor_extra_t *) be->bd_info->bi_extra; + + ms->mss_dn = ms->mss_ndn = li->li_monitor_info.lmi_ndn; + ms->mss_rdn = li->li_monitor_info.lmi_conn_rdn; + ms->mss_create = ldap_back_monitor_conn_create; + ms->mss_destroy = ldap_back_monitor_subsystem_destroy; + + e = mbe->entry_stub( &ms->mss_dn, &ms->mss_ndn, + &ms->mss_rdn, oc_monitorContainer, NULL, NULL ); + if ( e == NULL ) { + Debug( LDAP_DEBUG_ANY, + "ldap_back_monitor_conn_init: " + "unable to create entry \"%s,%s\"\n", + li->li_monitor_info.lmi_conn_rdn.bv_val, + ms->mss_ndn.bv_val ); + return( -1 ); + } + + ber_dupbv( &ms->mss_dn, &e->e_name ); + ber_dupbv( &ms->mss_ndn, &e->e_nname ); + + rc = mbe->register_entry( e, NULL, ms, MONITOR_F_VOLATILE_CH ); + + /* add labeledURI and special, modifiable URI value */ + if ( rc == LDAP_SUCCESS && li->li_uri != NULL ) { + struct berval bv; + Attribute *a; + LDAPURLDesc *ludlist = NULL; + monitor_callback_t *cb = NULL; + + a = attr_alloc( ad_olmDbURIList ); + + ber_str2bv( li->li_uri, 0, 0, &bv ); + attr_valadd( a, &bv, NULL, 1 ); + attr_normalize( a->a_desc, a->a_vals, &a->a_nvals, NULL ); + + rc = ldap_url_parselist_ext( &ludlist, + li->li_uri, NULL, + LDAP_PVT_URL_PARSE_NOEMPTY_HOST + | LDAP_PVT_URL_PARSE_DEF_PORT ); + if ( rc != LDAP_URL_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "ldap_back_monitor_db_open: " + "unable to parse URI list (ignored)\n" ); + } else { + Attribute *a2 = attr_alloc( slap_schema.si_ad_labeledURI ); + + a->a_next = a2; + + for ( ; ludlist != NULL; ) { + LDAPURLDesc *next = ludlist->lud_next; + + bv.bv_val = ldap_url_desc2str( ludlist ); + assert( bv.bv_val != NULL ); + ldap_free_urldesc( ludlist ); + bv.bv_len = strlen( bv.bv_val ); + attr_valadd( a2, &bv, NULL, 1 ); + ch_free( bv.bv_val ); + + ludlist = next; + } + + attr_normalize( a2->a_desc, a2->a_vals, &a2->a_nvals, NULL ); + } + + cb = ch_calloc( sizeof( monitor_callback_t ), 1 ); + cb->mc_update = ldap_back_monitor_update; + cb->mc_modify = ldap_back_monitor_modify; + cb->mc_free = ldap_back_monitor_free; + cb->mc_private = (void *)li; + + rc = mbe->register_entry_attrs( &ms->mss_ndn, a, cb, NULL, -1, NULL ); + + attr_free( a->a_next ); + attr_free( a ); + + if ( rc != LDAP_SUCCESS ) + { + ch_free( cb ); + } + } + + entry_free( e ); + + return rc; +} + +/* + * Operation monitoring subsystem: + * Looks a lot like the cn=operations,cn=monitor subsystem except that at this + * moment, only completed operations are counted. Each entry has a separate + * callback with all the needed information linked there in the structure + * below so that the callback need not locate it over and over again. + */ + +struct ldap_back_monitor_op_counter { + ldap_pvt_mp_t *data; + ldap_pvt_thread_mutex_t *mutex; +}; + +static void +ldap_back_monitor_ops_dispose( + void **priv) +{ + struct ldap_back_monitor_op_counter *counter = *priv; + + ch_free( counter ); + counter = NULL; +} + +static int +ldap_back_monitor_ops_free( + Entry *e, + void **priv) +{ + ldap_back_monitor_ops_dispose( priv ); + return LDAP_SUCCESS; +} + +static int +ldap_back_monitor_ops_update( + Operation *op, + SlapReply *rs, + Entry *e, + void *priv ) +{ + struct ldap_back_monitor_op_counter *counter = priv; + Attribute *a; + + /*TODO + * what about initiated/completed? + */ + a = attr_find( e->e_attrs, ad_olmDbOperations ); + assert( a != NULL ); + + ldap_pvt_thread_mutex_lock( counter->mutex ); + UI2BV( &a->a_vals[ 0 ], *counter->data ); + ldap_pvt_thread_mutex_unlock( counter->mutex ); + + return SLAP_CB_CONTINUE; +} + +static int +ldap_back_monitor_ops_init( + BackendDB *be, + monitor_subsys_t *ms ) +{ + ldapinfo_t *li = (ldapinfo_t *) ms->mss_private; + + monitor_extra_t *mbe; + Entry *e, *parent; + int rc; + slap_op_t op; + struct berval value = BER_BVC( "0" ); + + assert( be != NULL ); + + mbe = (monitor_extra_t *) be->bd_info->bi_extra; + + ms->mss_dn = ms->mss_ndn = li->li_monitor_info.lmi_ndn; + ms->mss_rdn = li->li_monitor_info.lmi_ops_rdn; + ms->mss_destroy = ldap_back_monitor_subsystem_destroy; + + parent = mbe->entry_stub( &ms->mss_dn, &ms->mss_ndn, + &ms->mss_rdn, oc_monitorContainer, NULL, NULL ); + if ( parent == NULL ) { + Debug( LDAP_DEBUG_ANY, + "ldap_back_monitor_ops_init: " + "unable to create entry \"%s,%s\"\n", + li->li_monitor_info.lmi_ops_rdn.bv_val, + ms->mss_ndn.bv_val ); + return( -1 ); + } + + ber_dupbv( &ms->mss_dn, &parent->e_name ); + ber_dupbv( &ms->mss_ndn, &parent->e_nname ); + + rc = mbe->register_entry( parent, NULL, ms, MONITOR_F_PERSISTENT_CH ); + if ( rc != LDAP_SUCCESS ) + { + Debug( LDAP_DEBUG_ANY, + "ldap_back_monitor_ops_init: " + "unable to register entry \"%s\" for monitoring\n", + parent->e_name.bv_val ); + goto done; + } + + for ( op = 0; op < SLAP_OP_LAST; op++ ) + { + monitor_callback_t *cb; + struct ldap_back_monitor_op_counter *counter; + + e = mbe->entry_stub( &parent->e_name, &parent->e_nname, + &ldap_back_monitor_op[op].rdn, + oc_monitorCounterObject, NULL, NULL ); + if ( e == NULL ) { + Debug( LDAP_DEBUG_ANY, + "ldap_back_monitor_ops_init: " + "unable to create entry \"%s,%s\"\n", + ldap_back_monitor_op[op].rdn.bv_val, + parent->e_nname.bv_val ); + return( -1 ); + } + + attr_merge_normalize_one( e, ad_olmDbOperations, &value, NULL ); + + counter = ch_malloc( sizeof( struct ldap_back_monitor_op_counter ) ); + counter->data = &li->li_ops_completed[ op ]; + counter->mutex = &li->li_counter_mutex; + + /* + * We cannot share a single callback between entries. + * + * monitor_cache_destroy() tries to free all callbacks and it's called + * before mss_destroy() so we have no chance of handling it ourselves + */ + cb = ch_calloc( sizeof( monitor_callback_t ), 1 ); + cb->mc_update = ldap_back_monitor_ops_update; + cb->mc_free = ldap_back_monitor_ops_free; + cb->mc_dispose = ldap_back_monitor_ops_dispose; + cb->mc_private = (void *)counter; + + rc = mbe->register_entry( e, cb, ms, 0 ); + + /* TODO: register_entry has stored a duplicate so we might actually reuse it + * instead of recreating it every time... */ + entry_free( e ); + + if ( rc != LDAP_SUCCESS ) + { + Debug( LDAP_DEBUG_ANY, + "ldap_back_monitor_ops_init: " + "unable to register entry \"%s\" for monitoring\n", + e->e_name.bv_val ); + ch_free( cb ); + break; + } + } + +done: + entry_free( parent ); + + return rc; +} + +/* + * call from within ldap_back_initialize() + */ +static int +ldap_back_monitor_initialize( void ) +{ + int i, code; + ConfigArgs c; + char *argv[ 3 ]; + + static int ldap_back_monitor_initialized = 0; + + /* set to 0 when successfully initialized; otherwise, remember failure */ + static int ldap_back_monitor_initialized_failure = 1; + + /* register schema here */ + + if ( ldap_back_monitor_initialized++ ) { + return ldap_back_monitor_initialized_failure; + } + + if ( backend_info( "monitor" ) == NULL ) { + return -1; + } + + argv[ 0 ] = "back-ldap monitor"; + c.argv = argv; + c.argc = 3; + c.fname = argv[0]; + for ( i = 0; s_oid[ i ].name; i++ ) { + + argv[ 1 ] = s_oid[ i ].name; + argv[ 2 ] = s_oid[ i ].oid; + + if ( parse_oidm( &c, 0, NULL ) != 0 ) { + Debug( LDAP_DEBUG_ANY, + "ldap_back_monitor_initialize: unable to add " + "objectIdentifier \"%s=%s\"\n", + s_oid[ i ].name, s_oid[ i ].oid ); + return 2; + } + } + + for ( i = 0; s_at[ i ].desc != NULL; i++ ) { + code = register_at( s_at[ i ].desc, s_at[ i ].ad, 1 ); + if ( code != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "ldap_back_monitor_initialize: register_at failed for attributeType (%s)\n", + s_at[ i ].desc ); + return 3; + + } else { + (*s_at[ i ].ad)->ad_type->sat_flags |= SLAP_AT_HIDE; + } + } + + for ( i = 0; s_oc[ i ].desc != NULL; i++ ) { + code = register_oc( s_oc[ i ].desc, s_oc[ i ].oc, 1 ); + if ( code != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "ldap_back_monitor_initialize: register_oc failed for objectClass (%s)\n", + s_oc[ i ].desc ); + return 4; + + } else { + (*s_oc[ i ].oc)->soc_flags |= SLAP_OC_HIDE; + } + } + + for ( i = 0; s_moc[ i ].name != NULL; i++ ) { + *s_moc[i].oc = oc_find( s_moc[ i ].name ); + if ( ! *s_moc[i].oc ) { + Debug( LDAP_DEBUG_ANY, + "ldap_back_monitor_initialize: failed to find objectClass (%s)\n", + s_moc[ i ].name ); + return 5; + + } + } + + return ( ldap_back_monitor_initialized_failure = LDAP_SUCCESS ); +} + +/* + * call from within ldap_back_db_init() + */ +int +ldap_back_monitor_db_init( BackendDB *be ) +{ + int rc; + + rc = ldap_back_monitor_initialize(); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + +#if 0 /* uncomment to turn monitoring on by default */ + SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_MONITORING; +#endif + + return 0; +} + +/* + * call from within ldap_back_db_open() + */ +int +ldap_back_monitor_db_open( BackendDB *be ) +{ + ldapinfo_t *li = (ldapinfo_t *) be->be_private; + monitor_subsys_t *mss = li->li_monitor_info.lmi_mss; + int rc = 0; + BackendInfo *mi; + monitor_extra_t *mbe; + + if ( !SLAP_DBMONITORING( be ) ) { + return 0; + } + + /* check if monitor is configured and usable */ + mi = backend_info( "monitor" ); + if ( !mi || !mi->bi_extra ) { + SLAP_DBFLAGS( be ) ^= SLAP_DBFLAG_MONITORING; + return 0; + } + mbe = mi->bi_extra; + + /* don't bother if monitor is not configured */ + if ( !mbe->is_configured() ) { + static int warning = 0; + + if ( warning++ == 0 ) { + Debug( LDAP_DEBUG_CONFIG, "ldap_back_monitor_db_open: " + "monitoring disabled; " + "configure monitor database to enable\n" ); + } + + return 0; + } + + /* caller (e.g. an overlay based on back-ldap) may want to use + * a different DN and RDNs... */ + if ( BER_BVISNULL( &li->li_monitor_info.lmi_ndn ) ) { + rc = mbe->register_database( be, &li->li_monitor_info.lmi_ndn ); + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "ldap_back_monitor_db_open: " + "failed to register the database with back-monitor\n" ); + } + } + if ( BER_BVISNULL( &li->li_monitor_info.lmi_conn_rdn ) ) { + ber_str2bv( "cn=Connections", 0, 1, + &li->li_monitor_info.lmi_conn_rdn ); + } + if ( BER_BVISNULL( &li->li_monitor_info.lmi_ops_rdn ) ) { + ber_str2bv( "cn=Operations", 0, 1, + &li->li_monitor_info.lmi_ops_rdn ); + } + + /* set up the subsystems used to create the operation and + * volatile connection entries */ + + mss->mss_name = "back-ldap connections"; + mss->mss_flags = MONITOR_F_VOLATILE_CH; + mss->mss_open = ldap_back_monitor_conn_init; + mss->mss_private = li; + + if ( mbe->register_subsys_late( mss ) ) + { + Debug( LDAP_DEBUG_ANY, + "ldap_back_monitor_db_open: " + "failed to register connection subsystem" ); + return -1; + } + + mss++; + + mss->mss_name = "back-ldap operations"; + mss->mss_flags = MONITOR_F_PERSISTENT_CH; + mss->mss_open = ldap_back_monitor_ops_init; + mss->mss_private = li; + + if ( mbe->register_subsys_late( mss ) ) + { + Debug( LDAP_DEBUG_ANY, + "ldap_back_monitor_db_open: " + "failed to register operation subsystem" ); + return -1; + } + + return rc; +} + +/* + * call from within ldap_back_db_close() + */ +int +ldap_back_monitor_db_close( BackendDB *be ) +{ + ldapinfo_t *li = (ldapinfo_t *) be->be_private; + + if ( li && !BER_BVISNULL( &li->li_monitor_info.lmi_ndn ) ) { + BackendInfo *mi; + monitor_extra_t *mbe; + + /* check if monitor is configured and usable */ + mi = backend_info( "monitor" ); + if ( mi && mi->bi_extra ) { + mbe = mi->bi_extra; + + /*TODO + * Unregister all entries our subsystems have created. + * Will only really be necessary when + * SLAPD_CONFIG_DELETE is enabled. + * + * Might need a way to unregister subsystems instead. + */ + } + } + + return 0; +} + +/* + * call from within ldap_back_db_destroy() + */ +int +ldap_back_monitor_db_destroy( BackendDB *be ) +{ + ldapinfo_t *li = (ldapinfo_t *) be->be_private; + + if ( li ) { + memset( &li->li_monitor_info, 0, sizeof( li->li_monitor_info ) ); + } + + return 0; +} + diff --git a/servers/slapd/back-ldap/pbind.c b/servers/slapd/back-ldap/pbind.c new file mode 100644 index 0000000..f5841e9 --- /dev/null +++ b/servers/slapd/back-ldap/pbind.c @@ -0,0 +1,173 @@ +/* pbind.c - passthru Bind overlay */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2022 The OpenLDAP Foundation. + * Portions Copyright 2003-2010 Howard Chu. + * 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>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "lutil.h" +#include "slap.h" +#include "back-ldap.h" +#include "slap-config.h" + +static BackendInfo *lback; + +static slap_overinst ldappbind; + +static int +ldap_pbind_bind( + Operation *op, + SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + void *private = op->o_bd->be_private; + void *bi = op->o_bd->bd_info; + int rc; + + op->o_bd->bd_info = lback; + op->o_bd->be_private = on->on_bi.bi_private; + rc = lback->bi_op_bind( op, rs ); + op->o_bd->be_private = private; + op->o_bd->bd_info = bi; + + return rc; +} + +static int +ldap_pbind_db_init( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + ConfigOCs *be_cf_ocs = be->be_cf_ocs; + void *private = be->be_private; + int rc; + + if ( lback == NULL ) { + lback = backend_info( "ldap" ); + + if ( lback == NULL ) { + return 1; + } + } + + rc = lback->bi_db_init( be, cr ); + on->on_bi.bi_private = be->be_private; + be->be_cf_ocs = be_cf_ocs; + be->be_private = private; + + return rc; +} + +static int +ldap_pbind_db_open( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + void *private = be->be_private; + int rc; + int monitoring; + + be->be_private = on->on_bi.bi_private; + monitoring = ( SLAP_DBFLAGS( be ) & SLAP_DBFLAG_MONITORING ); + SLAP_DBFLAGS( be ) &= ~SLAP_DBFLAG_MONITORING; + rc = lback->bi_db_open( be, cr ); + SLAP_DBFLAGS( be ) |= monitoring; + be->be_private = private; + + return rc; +} + +static int +ldap_pbind_db_close( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + void *private = be->be_private; + int rc; + + be->be_private = on->on_bi.bi_private; + rc = lback->bi_db_close( be, cr ); + be->be_private = private; + + return rc; +} + +static int +ldap_pbind_db_destroy( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + void *private = be->be_private; + int rc; + + be->be_private = on->on_bi.bi_private; + rc = lback->bi_db_close( be, cr ); + on->on_bi.bi_private = be->be_private; + be->be_private = private; + + return rc; +} + +static int +ldap_pbind_connection_destroy( + BackendDB *be, + Connection *conn +) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + void *private = be->be_private; + int rc; + + be->be_private = on->on_bi.bi_private; + rc = lback->bi_connection_destroy( be, conn ); + be->be_private = private; + + return rc; +} + +int +pbind_initialize( void ) +{ + int rc; + + ldappbind.on_bi.bi_type = "pbind"; + ldappbind.on_bi.bi_db_init = ldap_pbind_db_init; + ldappbind.on_bi.bi_db_open = ldap_pbind_db_open; + ldappbind.on_bi.bi_db_close = ldap_pbind_db_close; + ldappbind.on_bi.bi_db_destroy = ldap_pbind_db_destroy; + + ldappbind.on_bi.bi_op_bind = ldap_pbind_bind; + ldappbind.on_bi.bi_connection_destroy = ldap_pbind_connection_destroy; + + rc = ldap_pbind_init_cf( &ldappbind.on_bi ); + if ( rc ) { + return rc; + } + + return overlay_register( &ldappbind ); +} diff --git a/servers/slapd/back-ldap/proto-ldap.h b/servers/slapd/back-ldap/proto-ldap.h new file mode 100644 index 0000000..445d551 --- /dev/null +++ b/servers/slapd/back-ldap/proto-ldap.h @@ -0,0 +1,124 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2022 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>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#ifndef PROTO_LDAP_H +#define PROTO_LDAP_H + +LDAP_BEGIN_DECL + +extern BI_init ldap_back_initialize; +extern BI_open ldap_back_open; + +extern BI_db_init ldap_back_db_init; +extern BI_db_open ldap_back_db_open; +extern BI_db_close ldap_back_db_close; +extern BI_db_destroy ldap_back_db_destroy; + +extern BI_op_bind ldap_back_bind; +extern BI_op_search ldap_back_search; +extern BI_op_compare ldap_back_compare; +extern BI_op_modify ldap_back_modify; +extern BI_op_modrdn ldap_back_modrdn; +extern BI_op_add ldap_back_add; +extern BI_op_delete ldap_back_delete; +extern BI_op_abandon ldap_back_abandon; +extern BI_op_extended ldap_back_extended; + +extern BI_connection_destroy ldap_back_conn_destroy; + +extern BI_entry_get_rw ldap_back_entry_get; + +void ldap_back_release_conn_lock( ldapinfo_t *li, ldapconn_t **lcp, int dolock ); +#define ldap_back_release_conn(li, lc) ldap_back_release_conn_lock((li), &(lc), 1) +int ldap_back_dobind( ldapconn_t **lcp, Operation *op, SlapReply *rs, ldap_back_send_t sendok ); +int ldap_back_retry( ldapconn_t **lcp, Operation *op, SlapReply *rs, ldap_back_send_t sendok ); +int ldap_back_map_result( SlapReply *rs ); +int ldap_back_op_result( ldapconn_t *lc, Operation *op, SlapReply *rs, + ber_int_t msgid, time_t timeout, ldap_back_send_t sendok ); +int ldap_back_cancel( ldapconn_t *lc, Operation *op, SlapReply *rs, ber_int_t msgid, ldap_back_send_t sendok ); + +int ldap_back_init_cf( BackendInfo *bi ); +int ldap_pbind_init_cf( BackendInfo *bi ); + +extern int ldap_back_conndn_cmp( const void *c1, const void *c2); +extern int ldap_back_conn_cmp( const void *c1, const void *c2); +extern int ldap_back_conndn_dup( void *c1, void *c2 ); +extern void ldap_back_conn_free( void *c ); + +extern ldapconn_t * ldap_back_conn_delete( ldapinfo_t *li, ldapconn_t *lc ); + +extern int ldap_back_conn2str( const ldapconn_base_t *lc, char *buf, ber_len_t buflen ); +extern int ldap_back_connid2str( const ldapconn_base_t *lc, char *buf, ber_len_t buflen ); + +extern int +ldap_back_proxy_authz_ctrl( + Operation *op, + SlapReply *rs, + struct berval *bound_ndn, + int version, + slap_idassert_t *si, + LDAPControl *ctrl ); + +extern int +ldap_back_controls_add( + Operation *op, + SlapReply *rs, + ldapconn_t *lc, + LDAPControl ***pctrls ); + +extern int +ldap_back_controls_free( Operation *op, SlapReply *rs, LDAPControl ***pctrls ); + +extern void +ldap_back_quarantine( + Operation *op, + SlapReply *rs ); + +#ifdef LDAP_BACK_PRINT_CONNTREE +extern void +ldap_back_print_conntree( ldapinfo_t *li, char *msg ); +#endif /* LDAP_BACK_PRINT_CONNTREE */ + +extern void slap_retry_info_destroy( slap_retry_info_t *ri ); +extern int slap_retry_info_parse( char *in, slap_retry_info_t *ri, + char *buf, ber_len_t buflen ); +extern int slap_retry_info_unparse( slap_retry_info_t *ri, struct berval *bvout ); + +extern int slap_idassert_authzfrom_parse( struct config_args_s *ca, slap_idassert_t *si ); +extern int slap_idassert_passthru_parse_cf( const char *fname, int lineno, const char *arg, slap_idassert_t *si ); +extern int slap_idassert_parse( struct config_args_s *ca, slap_idassert_t *si ); + +extern int chain_initialize( void ); +extern int pbind_initialize( void ); +#ifdef SLAP_DISTPROC +extern int distproc_initialize( void ); +#endif + +extern int ldap_back_monitor_db_init( BackendDB *be ); +extern int ldap_back_monitor_db_open( BackendDB *be ); +extern int ldap_back_monitor_db_close( BackendDB *be ); +extern int ldap_back_monitor_db_destroy( BackendDB *be ); + +extern LDAP_REBIND_PROC ldap_back_default_rebind; +extern LDAP_URLLIST_PROC ldap_back_default_urllist; + +LDAP_END_DECL + +#endif /* PROTO_LDAP_H */ diff --git a/servers/slapd/back-ldap/search.c b/servers/slapd/back-ldap/search.c new file mode 100644 index 0000000..90b5b65 --- /dev/null +++ b/servers/slapd/back-ldap/search.c @@ -0,0 +1,1042 @@ +/* search.c - ldap backend search function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2022 The OpenLDAP Foundation. + * Portions Copyright 1999-2003 Howard Chu. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * 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>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "slap.h" +#include "back-ldap.h" +#include "../../../libraries/liblber/lber-int.h" + +#include "lutil.h" + +static int +ldap_build_entry( Operation *op, LDAPMessage *e, Entry *ent, + struct berval *bdn, int remove_unknown_schema ); + + +static ObjectClass * +oc_bvfind_undef_ex( struct berval *ocname, int flag ) +{ + ObjectClass *oc = oc_bvfind( ocname ); + + if ( oc || flag ) { + /* oc defined or remove-unknown-schema flag set */ + return oc; + } + + return oc_bvfind_undef( ocname ); +} + + +/* + * replaces (&) with (objectClass=*) and (|) with (!(objectClass=*)) + * as the best replacement for RFC 4526 absolute true/absolute false + * filters; the only difference (AFAIK) is that they require search + * access to objectClass. + * + * filter->bv_val may be alloc'd on the thread's slab, if equal to + * op->ors_filterstr.bv_val, or realloc'd on the thread's slab otherwise. + */ +static int +ldap_back_munge_filter( + Operation *op, + struct berval *filter ) +{ + char *ptr; + int gotit = 0; + + Debug( LDAP_DEBUG_ARGS, "=> ldap_back_munge_filter \"%s\"\n", + filter->bv_val ); + + for ( ptr = strchr( filter->bv_val, '(' ); + ptr; + ptr = strchr( ptr, '(' ) ) + { + static struct berval + bv_t = BER_BVC( "(&)" ), + bv_f = BER_BVC( "(|)" ), + bv_T = BER_BVC( "(objectClass=*)" ), + bv_F = BER_BVC( "(!(objectClass=*))" ); + struct berval *oldbv = NULL, + *newbv = NULL, + oldfilter = BER_BVNULL; + + if ( ptr[2] != ')' ) { + ptr++; + continue; + } + + switch ( ptr[1] ) { + case '&': + oldbv = &bv_t; + newbv = &bv_T; + break; + + case '|': + oldbv = &bv_f; + newbv = &bv_F; + break; + + default: + /* should be an error */ + continue; + } + + oldfilter = *filter; + filter->bv_len += newbv->bv_len - oldbv->bv_len; + if ( filter->bv_val == op->ors_filterstr.bv_val ) { + filter->bv_val = op->o_tmpalloc( filter->bv_len + 1, + op->o_tmpmemctx ); + + AC_MEMCPY( filter->bv_val, op->ors_filterstr.bv_val, + ptr - oldfilter.bv_val ); + + } else { + filter->bv_val = op->o_tmprealloc( filter->bv_val, + filter->bv_len + 1, op->o_tmpmemctx ); + } + + ptr = filter->bv_val + ( ptr - oldfilter.bv_val ); + + AC_MEMCPY( &ptr[ newbv->bv_len ], + &ptr[ oldbv->bv_len ], + oldfilter.bv_len - ( ptr - filter->bv_val ) - oldbv->bv_len + 1 ); + AC_MEMCPY( ptr, newbv->bv_val, newbv->bv_len ); + + ptr += newbv->bv_len; + + gotit++; + } + + Debug( LDAP_DEBUG_ARGS, "<= ldap_back_munge_filter \"%s\" (%d)\n", + filter->bv_val, gotit ); + + return gotit; +} + +int +ldap_back_search( + Operation *op, + SlapReply *rs ) +{ + ldapinfo_t *li = (ldapinfo_t *) op->o_bd->be_private; + + ldapconn_t *lc = NULL; + struct timeval tv; + time_t stoptime = (time_t)(-1); + LDAPMessage *res, + *e; + int rc = 0, + msgid; + struct berval match = BER_BVNULL, + filter = BER_BVNULL; + int i, x; + char **attrs = NULL; + int freetext = 0, filter_undef = 0; + int do_retry = 1, dont_retry = 0; + LDAPControl **ctrls = NULL; + char **references = NULL; + int remove_unknown_schema = + LDAP_BACK_OMIT_UNKNOWN_SCHEMA (li); + + rs_assert_ready( rs ); + rs->sr_flags &= ~REP_ENTRY_MASK; /* paranoia, we can set rs = non-entry */ + + if ( !ldap_back_dobind( &lc, op, rs, LDAP_BACK_SENDERR ) ) { + return rs->sr_err; + } + + /* + * FIXME: in case of values return filter, we might want + * to map attrs and maybe rewrite value + */ + + if ( op->ors_tlimit != SLAP_NO_LIMIT ) { + tv.tv_sec = op->ors_tlimit; + tv.tv_usec = 0; + stoptime = op->o_time + op->ors_tlimit; + + } else { + LDAP_BACK_TV_SET( &tv ); + } + + i = 0; + if ( op->ors_attrs ) { + for ( ; !BER_BVISNULL( &op->ors_attrs[i].an_name ); i++ ) + /* just count attrs */ ; + } + + x = 0; + if ( op->o_bd->be_extra_anlist ) { + for ( ; !BER_BVISNULL( &op->o_bd->be_extra_anlist[x].an_name ); x++ ) + /* just count attrs */ ; + } + + if ( i > 0 || x > 0 ) { + int j = 0; + + attrs = op->o_tmpalloc( ( i + x + 1 )*sizeof( char * ), + op->o_tmpmemctx ); + if ( attrs == NULL ) { + rs->sr_err = LDAP_NO_MEMORY; + rc = -1; + goto finish; + } + + if ( i > 0 ) { + for ( i = 0; !BER_BVISNULL( &op->ors_attrs[i].an_name ); i++, j++ ) { + attrs[ j ] = op->ors_attrs[i].an_name.bv_val; + } + } + + if ( x > 0 ) { + for ( x = 0; !BER_BVISNULL( &op->o_bd->be_extra_anlist[x].an_name ); x++, j++ ) { + if ( op->o_bd->be_extra_anlist[x].an_desc && + ad_inlist( op->o_bd->be_extra_anlist[x].an_desc, op->ors_attrs ) ) + { + continue; + } + + attrs[ j ] = op->o_bd->be_extra_anlist[x].an_name.bv_val; + } + } + + attrs[ j ] = NULL; + } + + ctrls = op->o_ctrls; + rc = ldap_back_controls_add( op, rs, lc, &ctrls ); + if ( rc != LDAP_SUCCESS ) { + goto finish; + } + + /* deal with <draft-zeilenga-ldap-t-f> filters */ + filter = op->ors_filterstr; +retry: + /* this goes after retry because ldap_back_munge_filter() + * optionally replaces RFC 4526 T-F filters (&) (|) + * if already computed, they will be re-installed + * by filter2bv_undef_x() later */ + if ( !LDAP_BACK_T_F( li ) ) { + ldap_back_munge_filter( op, &filter ); + } + + rs->sr_err = ldap_pvt_search( lc->lc_ld, op->o_req_dn.bv_val, + op->ors_scope, filter.bv_val, + attrs, op->ors_attrsonly, ctrls, NULL, + tv.tv_sec ? &tv : NULL, + op->ors_slimit, op->ors_deref, &msgid ); + + ldap_pvt_thread_mutex_lock( &li->li_counter_mutex ); + ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_SEARCH ], 1 ); + ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex ); + + if ( rs->sr_err != LDAP_SUCCESS ) { + switch ( rs->sr_err ) { + case LDAP_SERVER_DOWN: + if ( do_retry ) { + do_retry = 0; + if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_DONTSEND ) ) { + goto retry; + } + } + + if ( lc == NULL ) { + /* reset by ldap_back_retry ... */ + rs->sr_err = slap_map_api2result( rs ); + + } else { + rc = ldap_back_op_result( lc, op, rs, msgid, 0, LDAP_BACK_DONTSEND ); + } + + goto finish; + + case LDAP_FILTER_ERROR: + /* first try? */ + if ( !filter_undef && + strstr( filter.bv_val, "(?" ) && + !LDAP_BACK_NOUNDEFFILTER( li ) ) + { + BER_BVZERO( &filter ); + filter2bv_undef_x( op, op->ors_filter, 1, &filter ); + filter_undef = 1; + goto retry; + } + + /* invalid filters return success with no data */ + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + goto finish; + + default: + rs->sr_err = slap_map_api2result( rs ); + rs->sr_text = NULL; + goto finish; + } + } + + /* if needed, initialize timeout */ + if ( li->li_timeout[ SLAP_OP_SEARCH ] ) { + if ( tv.tv_sec == 0 || tv.tv_sec > li->li_timeout[ SLAP_OP_SEARCH ] ) { + tv.tv_sec = li->li_timeout[ SLAP_OP_SEARCH ]; + tv.tv_usec = 0; + } + } + + /* We pull apart the ber result, stuff it into a slapd entry, and + * let send_search_entry stuff it back into ber format. Slow & ugly, + * but this is necessary for version matching, and for ACL processing. + */ + + for ( rc = -2; rc != -1; rc = ldap_result( lc->lc_ld, msgid, LDAP_MSG_ONE, &tv, &res ) ) + { + /* check for abandon */ + if ( op->o_abandon || LDAP_BACK_CONN_ABANDON( lc ) ) { + if ( rc > 0 ) { + ldap_msgfree( res ); + } + (void)ldap_back_cancel( lc, op, rs, msgid, LDAP_BACK_DONTSEND ); + rc = SLAPD_ABANDON; + goto finish; + } + + if ( rc == 0 || rc == -2 ) { + ldap_pvt_thread_yield(); + + /* check timeout */ + if ( li->li_timeout[ SLAP_OP_SEARCH ] ) { + if ( rc == 0 ) { + (void)ldap_back_cancel( lc, op, rs, msgid, LDAP_BACK_DONTSEND ); + rs->sr_text = "Operation timed out"; + rc = rs->sr_err = op->o_protocol >= LDAP_VERSION3 ? + LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER; + goto finish; + } + + } else { + LDAP_BACK_TV_SET( &tv ); + } + + /* check time limit */ + if ( op->ors_tlimit != SLAP_NO_LIMIT + && slap_get_time() > stoptime ) + { + (void)ldap_back_cancel( lc, op, rs, msgid, LDAP_BACK_DONTSEND ); + rc = rs->sr_err = LDAP_TIMELIMIT_EXCEEDED; + goto finish; + } + continue; + + } else { + /* only touch when activity actually took place... */ + if ( li->li_idle_timeout ) { + lc->lc_time = op->o_time; + } + + /* don't retry any more */ + dont_retry = 1; + } + + + if ( rc == LDAP_RES_SEARCH_ENTRY ) { + Entry ent = { 0 }; + struct berval bdn = BER_BVNULL; + + do_retry = 0; + + e = ldap_first_entry( lc->lc_ld, res ); + rc = ldap_build_entry( op, e, &ent, &bdn, + remove_unknown_schema); + if ( rc == LDAP_SUCCESS ) { + ldap_get_entry_controls( lc->lc_ld, res, &rs->sr_ctrls ); + rs->sr_entry = &ent; + rs->sr_attrs = op->ors_attrs; + rs->sr_operational_attrs = NULL; + rs->sr_flags = 0; + rs->sr_err = LDAP_SUCCESS; + rc = rs->sr_err = send_search_entry( op, rs ); + if ( rs->sr_ctrls ) { + ldap_controls_free( rs->sr_ctrls ); + rs->sr_ctrls = NULL; + } + rs->sr_entry = NULL; + rs->sr_flags = 0; + if ( !BER_BVISNULL( &ent.e_name ) ) { + assert( ent.e_name.bv_val != bdn.bv_val ); + op->o_tmpfree( ent.e_name.bv_val, op->o_tmpmemctx ); + BER_BVZERO( &ent.e_name ); + } + if ( !BER_BVISNULL( &ent.e_nname ) ) { + op->o_tmpfree( ent.e_nname.bv_val, op->o_tmpmemctx ); + BER_BVZERO( &ent.e_nname ); + } + entry_clean( &ent ); + } + ldap_msgfree( res ); + switch ( rc ) { + case LDAP_SUCCESS: + case LDAP_INSUFFICIENT_ACCESS: + break; + + default: + if ( rc == LDAP_UNAVAILABLE ) { + rc = rs->sr_err = LDAP_OTHER; + } else { + (void)ldap_back_cancel( lc, op, rs, msgid, LDAP_BACK_DONTSEND ); + } + goto finish; + } + + } else if ( rc == LDAP_RES_SEARCH_REFERENCE ) { + if ( LDAP_BACK_NOREFS( li ) ) { + ldap_msgfree( res ); + continue; + } + + do_retry = 0; + rc = ldap_parse_reference( lc->lc_ld, res, + &references, &rs->sr_ctrls, 1 ); + + if ( rc != LDAP_SUCCESS ) { + continue; + } + + /* FIXME: there MUST be at least one */ + if ( references && references[ 0 ] && references[ 0 ][ 0 ] ) { + int cnt; + + for ( cnt = 0; references[ cnt ]; cnt++ ) + /* NO OP */ ; + + /* FIXME: there MUST be at least one */ + rs->sr_ref = op->o_tmpalloc( ( cnt + 1 ) * sizeof( struct berval ), + op->o_tmpmemctx ); + + for ( cnt = 0; references[ cnt ]; cnt++ ) { + ber_str2bv( references[ cnt ], 0, 0, &rs->sr_ref[ cnt ] ); + } + BER_BVZERO( &rs->sr_ref[ cnt ] ); + + /* ignore return value by now */ + RS_ASSERT( !(rs->sr_flags & REP_ENTRY_MASK) ); + rs->sr_entry = NULL; + ( void )send_search_reference( op, rs ); + + } else { + Debug( LDAP_DEBUG_ANY, + "%s ldap_back_search: " + "got SEARCH_REFERENCE " + "with no referrals\n", + op->o_log_prefix ); + } + + /* cleanup */ + if ( references ) { + ber_memvfree( (void **)references ); + op->o_tmpfree( rs->sr_ref, op->o_tmpmemctx ); + rs->sr_ref = NULL; + references = NULL; + } + + if ( rs->sr_ctrls ) { + ldap_controls_free( rs->sr_ctrls ); + rs->sr_ctrls = NULL; + } + + } else if ( rc == LDAP_RES_INTERMEDIATE ) { + /* FIXME: response controls + * are passed without checks */ + rc = ldap_parse_intermediate( lc->lc_ld, + res, + (char **)&rs->sr_rspoid, + &rs->sr_rspdata, + &rs->sr_ctrls, + 0 ); + if ( rc != LDAP_SUCCESS ) { + continue; + } + + slap_send_ldap_intermediate( op, rs ); + + if ( rs->sr_rspoid != NULL ) { + ber_memfree( (char *)rs->sr_rspoid ); + rs->sr_rspoid = NULL; + } + + if ( rs->sr_rspdata != NULL ) { + ber_bvfree( rs->sr_rspdata ); + rs->sr_rspdata = NULL; + } + + if ( rs->sr_ctrls != NULL ) { + ldap_controls_free( rs->sr_ctrls ); + rs->sr_ctrls = NULL; + } + + } else { + char *err = NULL; + + rc = ldap_parse_result( lc->lc_ld, res, &rs->sr_err, + &match.bv_val, &err, + &references, &rs->sr_ctrls, 1 ); + if ( rc == LDAP_SUCCESS ) { + if ( err ) { + rs->sr_text = err; + freetext = 1; + } + } else { + rs->sr_err = rc; + } + rs->sr_err = slap_map_api2result( rs ); + + /* RFC 4511: referrals can only appear + * if result code is LDAP_REFERRAL */ + if ( references + && references[ 0 ] + && references[ 0 ][ 0 ] ) + { + if ( rs->sr_err != LDAP_REFERRAL ) { + Debug( LDAP_DEBUG_ANY, + "%s ldap_back_search: " + "got referrals with err=%d\n", + op->o_log_prefix, + rs->sr_err ); + + } else { + int cnt; + + for ( cnt = 0; references[ cnt ]; cnt++ ) + /* NO OP */ ; + + rs->sr_ref = op->o_tmpalloc( ( cnt + 1 ) * sizeof( struct berval ), + op->o_tmpmemctx ); + + for ( cnt = 0; references[ cnt ]; cnt++ ) { + /* duplicating ...*/ + ber_str2bv( references[ cnt ], 0, 0, &rs->sr_ref[ cnt ] ); + } + BER_BVZERO( &rs->sr_ref[ cnt ] ); + } + + } else if ( rs->sr_err == LDAP_REFERRAL ) { + Debug( LDAP_DEBUG_ANY, + "%s ldap_back_search: " + "got err=%d with null " + "or empty referrals\n", + op->o_log_prefix, + rs->sr_err ); + + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } + + if ( match.bv_val != NULL ) { + match.bv_len = strlen( match.bv_val ); + } + + rc = 0; + break; + } + + /* if needed, restore timeout */ + if ( li->li_timeout[ SLAP_OP_SEARCH ] ) { + if ( tv.tv_sec == 0 || tv.tv_sec > li->li_timeout[ SLAP_OP_SEARCH ] ) { + tv.tv_sec = li->li_timeout[ SLAP_OP_SEARCH ]; + tv.tv_usec = 0; + } + } + } + + if ( rc == -1 ) { + if ( dont_retry == 0 ) { + if ( do_retry ) { + do_retry = 0; + if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_DONTSEND ) ) { + goto retry; + } + } + + rs->sr_err = LDAP_SERVER_DOWN; + rs->sr_err = slap_map_api2result( rs ); + goto finish; + + } else if ( LDAP_BACK_ONERR_STOP( li ) ) { + /* if onerr == STOP */ + rs->sr_err = LDAP_SERVER_DOWN; + rs->sr_err = slap_map_api2result( rs ); + goto finish; + } + } + + /* + * Rewrite the matched portion of the search base, if required + */ + if ( !BER_BVISNULL( &match ) && !BER_BVISEMPTY( &match ) ) { + struct berval pmatch; + + if ( dnPretty( NULL, &match, &pmatch, op->o_tmpmemctx ) != LDAP_SUCCESS ) { + pmatch.bv_val = match.bv_val; + match.bv_val = NULL; + } + rs->sr_matched = pmatch.bv_val; + rs->sr_flags |= REP_MATCHED_MUSTBEFREED; + } + +finish:; + if ( !BER_BVISNULL( &match ) ) { + ber_memfree( match.bv_val ); + } + + if ( rs->sr_v2ref ) { + rs->sr_err = LDAP_REFERRAL; + } + + if ( LDAP_BACK_QUARANTINE( li ) ) { + ldap_back_quarantine( op, rs ); + } + + if ( filter.bv_val != op->ors_filterstr.bv_val ) { + op->o_tmpfree( filter.bv_val, op->o_tmpmemctx ); + } + +#if 0 + /* let send_ldap_result play cleanup handlers (ITS#4645) */ + if ( rc != SLAPD_ABANDON ) +#endif + { + send_ldap_result( op, rs ); + } + + (void)ldap_back_controls_free( op, rs, &ctrls ); + + if ( rs->sr_ctrls ) { + ldap_controls_free( rs->sr_ctrls ); + rs->sr_ctrls = NULL; + } + + if ( rs->sr_text ) { + if ( freetext ) { + ber_memfree( (char *)rs->sr_text ); + } + rs->sr_text = NULL; + } + + if ( rs->sr_ref ) { + op->o_tmpfree( rs->sr_ref, op->o_tmpmemctx ); + rs->sr_ref = NULL; + } + + if ( references ) { + ber_memvfree( (void **)references ); + } + + if ( attrs ) { + op->o_tmpfree( attrs, op->o_tmpmemctx ); + } + + if ( lc != NULL ) { + ldap_back_release_conn( li, lc ); + } + + if ( rs->sr_err == LDAP_UNAVAILABLE && + /* if we originally bound and wanted rebind-as-user, must drop + * the connection now because we just discarded the credentials. + * ITS#7464, #8142 + */ + LDAP_BACK_SAVECRED( li ) && SLAP_IS_AUTHZ_BACKEND( op ) ) + rs->sr_err = SLAPD_DISCONNECT; + return rs->sr_err; +} + +static int +ldap_build_entry( + Operation *op, + LDAPMessage *e, + Entry *ent, + struct berval *bdn, + int remove_unknown_schema) +{ + struct berval a; + BerElement ber = *ldap_get_message_ber( e ); + Attribute *attr, **attrp; + const char *text; + int last; + char *lastb; + ber_len_t len; + + /* safe assumptions ... */ + assert( ent != NULL ); + BER_BVZERO( &ent->e_bv ); + + if ( ber_scanf( &ber, "{m", bdn ) == LBER_ERROR ) { + return LDAP_DECODING_ERROR; + } + + /* + * Note: this may fail if the target host(s) schema differs + * from the one known to the meta, and a DN with unknown + * attributes is returned. + * + * FIXME: should we log anything, or delegate to dnNormalize? + */ + /* Note: if the distinguished values or the naming attributes + * change, should we massage them as well? + */ + if ( dnPrettyNormal( NULL, bdn, &ent->e_name, &ent->e_nname, + op->o_tmpmemctx ) != LDAP_SUCCESS ) + { + return LDAP_INVALID_DN_SYNTAX; + } + + ent->e_attrs = NULL; + if ( ber_first_element( &ber, &len, &lastb ) != LBER_SEQUENCE ) { + return LDAP_SUCCESS; + } + + attrp = &ent->e_attrs; + while ( ber_next_element( &ber, &len, lastb ) == LBER_SEQUENCE && + ber_scanf( &ber, "{m", &a ) != LBER_ERROR ) { + int i; + slap_syntax_validate_func *validate; + slap_syntax_transform_func *pretty; + + attr = attr_alloc( NULL ); + if ( attr == NULL ) { + return LDAP_OTHER; + } + if ( slap_bv2ad( &a, &attr->a_desc, &text ) + != LDAP_SUCCESS ) + { + if ( slap_bv2undef_ad( &a, &attr->a_desc, &text, + (remove_unknown_schema ? SLAP_AD_NOINSERT : SLAP_AD_PROXIED )) != LDAP_SUCCESS ) + { + Debug( LDAP_DEBUG_ANY, + "%s ldap_build_entry: " + "slap_bv2undef_ad(%s): %s\n", + op->o_log_prefix, a.bv_val, text ); + + ( void )ber_scanf( &ber, "x" /* [W] */ ); + attr_free( attr ); + continue; + } + } + + /* no subschemaSubentry */ + if ( attr->a_desc == slap_schema.si_ad_subschemaSubentry + || attr->a_desc == slap_schema.si_ad_entryDN ) + { + + /* + * We eat target's subschemaSubentry because + * a search for this value is likely not + * to resolve to the appropriate backend; + * later, the local subschemaSubentry is + * added. + * + * We also eat entryDN because the frontend + * will reattach it without checking if already + * present... + */ + ( void )ber_scanf( &ber, "x" /* [W] */ ); + attr_free( attr ); + continue; + } + + if ( ber_scanf( &ber, "[W]", &attr->a_vals ) == LBER_ERROR + || attr->a_vals == NULL ) + { + /* + * Note: attr->a_vals can be null when using + * values result filter + */ + attr->a_vals = (struct berval *)&slap_dummy_bv; + } + + validate = attr->a_desc->ad_type->sat_syntax->ssyn_validate; + pretty = attr->a_desc->ad_type->sat_syntax->ssyn_pretty; + + if ( !validate && !pretty ) { + attr->a_nvals = NULL; + attr_free( attr ); + goto next_attr; + } + + for ( i = 0; !BER_BVISNULL( &attr->a_vals[i] ); i++ ) ; + last = i; + + /* + * check that each value is valid per syntax + * and pretty if appropriate + */ + for ( i = 0; i<last; i++ ) { + struct berval pval; + int rc; + + if ( pretty ) { + rc = ordered_value_pretty( attr->a_desc, + &attr->a_vals[i], &pval, NULL ); + + } else { + rc = ordered_value_validate( attr->a_desc, + &attr->a_vals[i], 0 ); + } + + if ( rc != LDAP_SUCCESS ) { + ObjectClass *oc; + + /* check if, by chance, it's an undefined objectClass */ + if ( attr->a_desc == slap_schema.si_ad_objectClass && + ( oc = oc_bvfind_undef_ex( &attr->a_vals[i], + remove_unknown_schema ) ) != NULL ) + { + ber_dupbv( &pval, &oc->soc_cname ); + rc = LDAP_SUCCESS; + + } else { + ber_memfree( attr->a_vals[i].bv_val ); + if ( --last == i ) { + BER_BVZERO( &attr->a_vals[i] ); + break; + } + attr->a_vals[i] = attr->a_vals[last]; + BER_BVZERO( &attr->a_vals[last] ); + i--; + } + } + + if ( rc == LDAP_SUCCESS && pretty ) { + ber_memfree( attr->a_vals[i].bv_val ); + attr->a_vals[i] = pval; + } + } + attr->a_numvals = last = i; + if ( last == 0 && attr->a_vals != &slap_dummy_bv ) { + attr->a_nvals = NULL; + attr_free( attr ); + goto next_attr; + } + + if ( last && attr->a_desc->ad_type->sat_equality && + attr->a_desc->ad_type->sat_equality->smr_normalize ) + { + attr->a_nvals = ch_malloc( ( last + 1 )*sizeof( struct berval ) ); + for ( i = 0; i < last; i++ ) { + int rc; + + rc = ordered_value_normalize( + SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + attr->a_desc, + attr->a_desc->ad_type->sat_equality, + &attr->a_vals[i], &attr->a_nvals[i], + NULL ); + + if ( rc != LDAP_SUCCESS ) { + ber_memfree( attr->a_vals[i].bv_val ); + if ( --last == i ) { + BER_BVZERO( &attr->a_vals[i] ); + break; + } + attr->a_vals[i] = attr->a_vals[last]; + BER_BVZERO( &attr->a_vals[last] ); + i--; + } + } + BER_BVZERO( &attr->a_nvals[i] ); + if ( last == 0 ) { + attr_free( attr ); + goto next_attr; + } + + } else { + attr->a_nvals = attr->a_vals; + } + + attr->a_numvals = last; + + /* Handle sorted vals, strip dups but keep the attr */ + if ( attr->a_desc->ad_type->sat_flags & SLAP_AT_SORTED_VAL ) { + while ( attr->a_numvals > 1 ) { + int rc = slap_sort_vals( (Modifications *)attr, &text, &i, op->o_tmpmemctx ); + if ( rc != LDAP_TYPE_OR_VALUE_EXISTS ) + break; + + /* Strip duplicate values */ + if ( attr->a_nvals != attr->a_vals ) + ber_memfree( attr->a_nvals[i].bv_val ); + ber_memfree( attr->a_vals[i].bv_val ); + attr->a_numvals--; + + assert( i >= 0 ); + if ( (unsigned)i < attr->a_numvals ) { + attr->a_vals[i] = attr->a_vals[attr->a_numvals]; + if ( attr->a_nvals != attr->a_vals ) + attr->a_nvals[i] = attr->a_nvals[attr->a_numvals]; + } + BER_BVZERO(&attr->a_vals[attr->a_numvals]); + if ( attr->a_nvals != attr->a_vals ) + BER_BVZERO(&attr->a_nvals[attr->a_numvals]); + } + attr->a_flags |= SLAP_ATTR_SORTED_VALS; + } + + *attrp = attr; + attrp = &attr->a_next; + +next_attr:; + } + + return LDAP_SUCCESS; +} + +/* return 0 IFF we can retrieve the entry with ndn + */ +int +ldap_back_entry_get( + Operation *op, + struct berval *ndn, + ObjectClass *oc, + AttributeDescription *at, + int rw, + Entry **ent ) +{ + ldapinfo_t *li = (ldapinfo_t *) op->o_bd->be_private; + + ldapconn_t *lc = NULL; + int rc; + struct berval bdn; + LDAPMessage *result = NULL, + *e = NULL; + char *attr[3], **attrp = NULL; + char *filter = NULL; + SlapReply rs; + int do_retry = 1; + LDAPControl **ctrls = NULL; + Operation op2 = *op; + + int remove_unknown_schema = + LDAP_BACK_OMIT_UNKNOWN_SCHEMA (li); + *ent = NULL; + + /* Tell getconn this is a privileged op */ + op2.o_do_not_cache = 1; + /* use rootdn to be doubly explicit this is privileged */ + op2.o_dn = op->o_bd->be_rootdn; + op2.o_ndn = op->o_bd->be_rootndn; + /* ldap_back_entry_get() is an entry lookup, so it does not need + * to know what the entry is being looked up for */ + op2.o_tag = LDAP_REQ_SEARCH; + op2.o_ctrls = NULL; + rc = ldap_back_dobind( &lc, &op2, &rs, LDAP_BACK_DONTSEND ); + if ( !rc ) { + return rs.sr_err; + } + + if ( at ) { + attrp = attr; + if ( oc && at != slap_schema.si_ad_objectClass ) { + attr[0] = slap_schema.si_ad_objectClass->ad_cname.bv_val; + attr[1] = at->ad_cname.bv_val; + attr[2] = NULL; + + } else { + attr[0] = at->ad_cname.bv_val; + attr[1] = NULL; + } + } + + if ( oc ) { + char *ptr; + + filter = op->o_tmpalloc( STRLENOF( "(objectClass=" ")" ) + + oc->soc_cname.bv_len + 1, op->o_tmpmemctx ); + ptr = lutil_strcopy( filter, "(objectClass=" ); + ptr = lutil_strcopy( ptr, oc->soc_cname.bv_val ); + *ptr++ = ')'; + *ptr++ = '\0'; + } + +retry: + ctrls = NULL; + rc = ldap_back_controls_add( &op2, &rs, lc, &ctrls ); + if ( rc != LDAP_SUCCESS ) { + goto cleanup; + } + + /* TODO: timeout? */ + rc = ldap_pvt_search_s( lc->lc_ld, ndn->bv_val, LDAP_SCOPE_BASE, filter, + attrp, LDAP_DEREF_NEVER, ctrls, NULL, + NULL, LDAP_NO_LIMIT, 0, &result ); + if ( rc != LDAP_SUCCESS ) { + if ( rc == LDAP_SERVER_DOWN && do_retry ) { + do_retry = 0; + if ( ldap_back_retry( &lc, &op2, &rs, LDAP_BACK_DONTSEND ) ) { + /* if the identity changed, there might be need to re-authz */ + (void)ldap_back_controls_free( &op2, &rs, &ctrls ); + goto retry; + } + } + goto cleanup; + } + + e = ldap_first_entry( lc->lc_ld, result ); + if ( e == NULL ) { + /* the entry exists, but it doesn't match the filter? */ + rc = LDAP_NO_RESULTS_RETURNED; + goto cleanup; + } + + *ent = entry_alloc(); + if ( *ent == NULL ) { + rc = LDAP_NO_MEMORY; + goto cleanup; + } + + rc = ldap_build_entry( op, e, *ent, &bdn, remove_unknown_schema ); + + if ( rc != LDAP_SUCCESS ) { + entry_free( *ent ); + *ent = NULL; + } + +cleanup: + (void)ldap_back_controls_free( &op2, &rs, &ctrls ); + + if ( result ) { + ldap_msgfree( result ); + } + + if ( filter ) { + op->o_tmpfree( filter, op->o_tmpmemctx ); + } + + if ( lc != NULL ) { + ldap_back_release_conn( li, lc ); + } + + return rc; +} diff --git a/servers/slapd/back-ldap/unbind.c b/servers/slapd/back-ldap/unbind.c new file mode 100644 index 0000000..071380e --- /dev/null +++ b/servers/slapd/back-ldap/unbind.c @@ -0,0 +1,78 @@ +/* unbind.c - ldap backend unbind function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2022 The OpenLDAP Foundation. + * Portions Copyright 1999-2003 Howard Chu. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * 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>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/errno.h> +#include <ac/socket.h> +#include <ac/string.h> + +#include "slap.h" +#include "back-ldap.h" + +int +ldap_back_conn_destroy( + Backend *be, + Connection *conn +) +{ + ldapinfo_t *li = (ldapinfo_t *) be->be_private; + ldapconn_t *lc = NULL, lc_curr; + + Debug( LDAP_DEBUG_TRACE, + "=>ldap_back_conn_destroy: fetching conn %ld\n", + conn->c_connid ); + + lc_curr.lc_conn = conn; + + ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex ); +#if LDAP_BACK_PRINT_CONNTREE > 0 + ldap_back_print_conntree( li, ">>> ldap_back_conn_destroy" ); +#endif /* LDAP_BACK_PRINT_CONNTREE */ + while ( ( lc = ldap_tavl_delete( &li->li_conninfo.lai_tree, (caddr_t)&lc_curr, ldap_back_conn_cmp ) ) != NULL ) + { + assert( !LDAP_BACK_PCONN_ISPRIV( lc ) ); + Debug( LDAP_DEBUG_TRACE, + "=>ldap_back_conn_destroy: destroying conn %lu " + "refcnt=%d flags=0x%08x\n", + lc->lc_conn->c_connid, lc->lc_refcnt, lc->lc_lcflags ); + + if ( lc->lc_refcnt > 0 ) { + /* someone else might be accessing the connection; + * mark for deletion */ + LDAP_BACK_CONN_CACHED_CLEAR( lc ); + LDAP_BACK_CONN_TAINTED_SET( lc ); + + } else { + ldap_back_conn_free( lc ); + } + } +#if LDAP_BACK_PRINT_CONNTREE > 0 + ldap_back_print_conntree( li, "<<< ldap_back_conn_destroy" ); +#endif /* LDAP_BACK_PRINT_CONNTREE */ + ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex ); + + return 0; +} |