+ return( ERR_OK( rs->sr_err ) ? 0 : -1 );
+}
+
+/* return true if bound, false if failed */
+int
+ldap_back_retry( struct ldapconn *lc, Operation *op, SlapReply *rs, ldap_back_send_t sendok )
+{
+ int rc = 0;
+ struct ldapinfo *li = (struct ldapinfo *)op->o_bd->be_private;
+
+ ldap_pvt_thread_mutex_lock( &li->conn_mutex );
+
+ if ( lc->lc_refcnt == 1 ) {
+ ldap_unbind_ext( lc->lc_ld, NULL, NULL );
+ lc->lc_ld = NULL;
+ lc->lc_bound = 0;
+
+ /* lc here must be the regular lc, reset and ready for init */
+ rc = ldap_back_prepare_conn( &lc, op, rs, sendok );
+ if ( rc == LDAP_SUCCESS ) {
+ rc = ldap_back_dobind_int( lc, op, rs, sendok, 0, 0 );
+ }
+ }
+
+ ldap_pvt_thread_mutex_unlock( &li->conn_mutex );
+
+ return rc;
+}
+
+static int
+ldap_back_proxy_authz_bind( struct ldapconn *lc, Operation *op, SlapReply *rs )
+{
+ struct ldapinfo *li = (struct ldapinfo *)op->o_bd->be_private;
+ struct berval binddn = slap_empty_bv;
+ struct berval bindcred = slap_empty_bv;
+ int dobind = 0;
+ int msgid;
+ int rc;
+
+ /*
+ * 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 "proxyauthzdn" is set, then bind as
+ * "proxyauthzdn" and explicitly add the proxyAuthz
+ * control to every operation with the dn bound
+ * to the connection as control value.
+ */
+
+ /* bind as proxyauthzdn only if no idassert mode
+ * is requested, or if the client's identity
+ * is authorized */
+ switch ( li->idassert_mode ) {
+ case LDAP_BACK_IDASSERT_LEGACY:
+ if ( !BER_BVISNULL( &op->o_conn->c_ndn ) && !BER_BVISEMPTY( &op->o_conn->c_ndn ) ) {
+ if ( !BER_BVISNULL( &li->idassert_authcDN ) && !BER_BVISEMPTY( &li->idassert_authcDN ) )
+ {
+ binddn = li->idassert_authcDN;
+ bindcred = li->idassert_passwd;
+ dobind = 1;
+ }
+ }
+ break;
+
+ default:
+ /* NOTE: rootdn can always idassert */
+ if ( li->idassert_authz && !be_isroot( op ) ) {
+ struct berval authcDN;
+
+ if ( BER_BVISNULL( &op->o_conn->c_ndn ) ) {
+ authcDN = slap_empty_bv;
+
+ } else {
+ authcDN = op->o_conn->c_ndn;
+ }
+ rs->sr_err = slap_sasl_matches( op, li->idassert_authz,
+ &authcDN, &authcDN );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ if ( li->idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) {
+ send_ldap_result( op, rs );
+ lc->lc_bound = 0;
+
+ } else {
+ rs->sr_err = LDAP_SUCCESS;
+ binddn = slap_empty_bv;
+ bindcred = slap_empty_bv;
+ break;
+ }
+
+ goto done;
+ }
+ }
+
+ binddn = li->idassert_authcDN;
+ bindcred = li->idassert_passwd;
+ dobind = 1;
+ break;
+ }
+
+ if ( dobind && li->idassert_authmethod == LDAP_AUTH_SASL ) {
+#ifdef HAVE_CYRUS_SASL
+ void *defaults = NULL;
+ struct berval authzID = BER_BVNULL;
+ int freeauthz = 0;
+
+ /* if SASL supports native authz, prepare for it */
+ if ( ( !op->o_do_not_cache || !op->o_is_auth_check ) &&
+ ( li->idassert_flags & LDAP_BACK_AUTH_NATIVE_AUTHZ ) )
+ {
+ switch ( li->idassert_mode ) {
+ case LDAP_BACK_IDASSERT_OTHERID:
+ case LDAP_BACK_IDASSERT_OTHERDN:
+ authzID = li->idassert_authzID;
+ break;
+
+ case LDAP_BACK_IDASSERT_ANONYMOUS:
+ BER_BVSTR( &authzID, "dn:" );
+ break;
+
+ case LDAP_BACK_IDASSERT_SELF:
+ if ( BER_BVISNULL( &op->o_conn->c_ndn ) ) {
+ /* connection is not authc'd, so don't idassert */
+ BER_BVSTR( &authzID, "dn:" );
+ break;
+ }
+ authzID.bv_len = STRLENOF( "dn:" ) + op->o_conn->c_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:" ),
+ op->o_conn->c_ndn.bv_val, op->o_conn->c_ndn.bv_len + 1 );
+ freeauthz = 1;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+#if 0 /* will deal with this later... */
+ if ( sasl_secprops != NULL ) {
+ rs->sr_err = ldap_set_option( lc->lc_ld, LDAP_OPT_X_SASL_SECPROPS,
+ (void *) sasl_secprops );
+
+ if ( rs->sr_err != LDAP_OPT_SUCCESS ) {
+ send_ldap_result( op, rs );
+ lc->lc_bound = 0;
+ goto done;
+ }
+ }
+#endif
+
+ defaults = lutil_sasl_defaults( lc->lc_ld,
+ li->idassert_sasl_mech.bv_val,
+ li->idassert_sasl_realm.bv_val,
+ li->idassert_authcID.bv_val,
+ li->idassert_passwd.bv_val,
+ authzID.bv_val );
+
+ rs->sr_err = ldap_sasl_interactive_bind_s( lc->lc_ld, binddn.bv_val,
+ li->idassert_sasl_mech.bv_val, NULL, NULL,
+ LDAP_SASL_QUIET, lutil_sasl_interact,
+ defaults );
+
+ lutil_sasl_freedefs( defaults );
+ if ( freeauthz ) {
+ slap_sl_free( authzID.bv_val, op->o_tmpmemctx );
+ }
+
+ rs->sr_err = slap_map_api2result( rs );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ lc->lc_bound = 0;
+ send_ldap_result( op, rs );
+
+ } else {
+ lc->lc_bound = 1;
+ }
+ goto done;
+#endif /* HAVE_CYRUS_SASL */
+ }
+
+ switch ( li->idassert_authmethod ) {
+ case LDAP_AUTH_SIMPLE:
+ rs->sr_err = ldap_sasl_bind( lc->lc_ld,
+ binddn.bv_val, LDAP_SASL_SIMPLE,
+ &bindcred, NULL, NULL, &msgid );
+ break;
+
+ case LDAP_AUTH_NONE:
+ lc->lc_bound = 1;
+ goto done;
+
+ default:
+ /* unsupported! */
+ lc->lc_bound = 0;
+ rs->sr_err = LDAP_AUTH_METHOD_NOT_SUPPORTED;
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ rc = ldap_back_op_result( lc, op, rs, msgid, LDAP_BACK_SENDERR );
+ if ( rc == LDAP_SUCCESS ) {
+ lc->lc_bound = 1;
+ }
+done:;
+ return lc->lc_bound;
+}
+
+/*
+ * 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(
+ struct ldapconn *lc,
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl ***pctrls )
+{
+ struct ldapinfo *li = (struct ldapinfo *) op->o_bd->be_private;
+ LDAPControl **ctrls = NULL;
+ int i = 0,
+ mode;
+ struct berval assertedID;
+
+ *pctrls = NULL;
+
+ rs->sr_err = LDAP_SUCCESS;
+
+ /* 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( &li->idassert_authcID ) || BER_BVISEMPTY( &li->idassert_authcID ) )
+ && ( BER_BVISNULL( &li->idassert_authcDN ) || BER_BVISEMPTY( &li->idassert_authcDN ) ) )
+ {
+ goto done;
+ }
+
+ if ( !op->o_conn || op->o_do_not_cache || be_isroot( op ) ) {
+ goto done;
+ }
+
+ if ( li->idassert_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( &lc->lc_bound_ndn ) ) {
+ goto done;
+ }
+
+ if ( BER_BVISNULL( &op->o_conn->c_ndn ) ) {
+ goto done;
+ }
+
+ if ( BER_BVISNULL( &li->idassert_authcDN ) ) {
+ goto done;
+ }
+
+ } else if ( li->idassert_authmethod == LDAP_AUTH_SASL ) {
+ if ( ( li->idassert_flags & LDAP_BACK_AUTH_NATIVE_AUTHZ )
+ /* && ( !BER_BVISNULL( &op->o_conn->c_ndn ) || lc->lc_bound ) */ )
+ {
+ /* already asserted in SASL via native authz */
+ /* NOTE: the test on lc->lc_bound is used to trap
+ * native authorization of anonymous users,
+ * since in that case op->o_conn->c_ndn is NULL */
+ goto done;
+ }
+
+ } else if ( li->idassert_authz && !be_isroot( op ) ) {
+ int rc;
+ struct berval authcDN;
+
+ if ( BER_BVISNULL( &op->o_conn->c_ndn ) ) {
+ authcDN = slap_empty_bv;
+ } else {
+ authcDN = op->o_conn->c_ndn;
+ }
+ rc = slap_sasl_matches( op, li->idassert_authz,
+ &authcDN, & authcDN );
+ if ( rc != LDAP_SUCCESS ) {
+ if ( li->idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE )
+ {
+ /* op->o_conn->c_ndn is not authorized
+ * to use idassert */
+ return rc;
+ }
+ return rs->sr_err;
+ }
+ }
+
+ 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 reques.
+ *
+ * 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_do_not_cache && op->o_is_auth_check ) {
+ mode = LDAP_BACK_IDASSERT_NOASSERT;
+
+ } else {
+ mode = li->idassert_mode;
+ }
+
+ switch ( mode ) {
+ case LDAP_BACK_IDASSERT_LEGACY:
+ case LDAP_BACK_IDASSERT_SELF:
+ /* original behavior:
+ * assert the client's identity */
+ if ( BER_BVISNULL( &op->o_conn->c_ndn ) ) {
+ assertedID = slap_empty_bv;
+ } else {
+ assertedID = op->o_conn->c_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 = li->idassert_authzID;
+ break;
+
+ default:
+ assert( 0 );
+ }
+
+ if ( BER_BVISNULL( &assertedID ) ) {
+ assertedID = slap_empty_bv;
+ }
+
+ if ( op->o_ctrls ) {
+ for ( i = 0; op->o_ctrls[ i ]; i++ )
+ /* just count ctrls */ ;
+ }
+
+ ctrls = ch_malloc( sizeof( LDAPControl * ) * (i + 2) );
+ ctrls[ 0 ] = ch_malloc( sizeof( LDAPControl ) );
+
+ ctrls[ 0 ]->ldctl_oid = LDAP_CONTROL_PROXY_AUTHZ;
+ ctrls[ 0 ]->ldctl_iscritical = 1;
+
+ switch ( li->idassert_mode ) {
+ /* already in u:ID or dn:DN form */
+ case LDAP_BACK_IDASSERT_OTHERID:
+ case LDAP_BACK_IDASSERT_OTHERDN:
+ ber_dupbv( &ctrls[ 0 ]->ldctl_value, &assertedID );
+ break;
+
+ /* needs the dn: prefix */
+ default:
+ ctrls[ 0 ]->ldctl_value.bv_len = assertedID.bv_len + STRLENOF( "dn:" );
+ ctrls[ 0 ]->ldctl_value.bv_val = ch_malloc( ctrls[ 0 ]->ldctl_value.bv_len + 1 );
+ AC_MEMCPY( ctrls[ 0 ]->ldctl_value.bv_val, "dn:", STRLENOF( "dn:" ) );
+ AC_MEMCPY( &ctrls[ 0 ]->ldctl_value.bv_val[ STRLENOF( "dn:" ) ],
+ assertedID.bv_val, assertedID.bv_len + 1 );
+ break;
+ }
+
+ if ( op->o_ctrls ) {
+ for ( i = 0; op->o_ctrls[ i ]; i++ ) {
+ ctrls[ i + 1 ] = op->o_ctrls[ i ];
+ }
+ }
+ ctrls[ i + 1 ] = NULL;
+
+done:;
+ if ( ctrls == NULL ) {
+ ctrls = op->o_ctrls;
+ }
+
+ *pctrls = ctrls;
+
+ return rs->sr_err;