+ 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;
+
+ /* we need this to know if back-ldap returned any result */
+ lb.lb_lc = lc;
+ 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, lback->bi_op_bind, ref, 0 );
+ op->o_req_ndn = rndn;
+ op->o_conn = conn;
+ }
+ break;
+
+ case LDAP_REQ_ADD:
+ rc = ldap_chain_op( op, rs, lback->bi_op_add, ref, 0 );
+ break;
+
+ case LDAP_REQ_DELETE:
+ rc = ldap_chain_op( op, rs, lback->bi_op_delete, ref, 0 );
+ break;
+
+ case LDAP_REQ_MODRDN:
+ rc = ldap_chain_op( op, rs, lback->bi_op_modrdn, ref, 0 );
+ break;
+
+ case LDAP_REQ_MODIFY:
+ rc = ldap_chain_op( op, rs, lback->bi_op_modify, ref, 0 );
+ break;
+
+ case LDAP_REQ_COMPARE:
+ rc = ldap_chain_op( op, rs, lback->bi_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 ) {
+ 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, lback->bi_op_search, ref, 0 );
+
+ } else {
+ rc = SLAP_CB_CONTINUE;
+ }
+ }
+ break;
+
+ case LDAP_REQ_EXTENDED:
+ rc = ldap_chain_op( op, rs, lback->bi_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:
+ /* 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, 0, 0 );
+ }
+ 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 ) ) {
+ rs->sr_err = rc;
+ 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;
+ }
+#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
+ break;
+ }
+#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
+ }
+
+ if ( lb.lb_status == LDAP_CH_NONE && rc != SLAPD_ABANDON ) {
+ op->o_callback = NULL;
+ 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;
+ op->o_bd = bd;
+ op->o_callback = sc;
+ op->o_ndn = ndn;
+
+ 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;
+
+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)' "
+ "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' "
+ "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' "
+ "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, chaincfg, chain_ldadd },
+ { 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 ( 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, 0 );
+ 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-chain: "
+ "subsequent underlying database \"%s\" "
+ "must contain attribute \"%s\".\n",
+ e->e_name.bv_val, ad->ad_cname.bv_val, 0 );
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+ }
+
+ if ( lc->lc_common_li == NULL ) {
+ rc = ldap_chain_db_init_common( ca->be );
+
+ } else {
+ rc = ldap_chain_db_init_one( ca->be );
+ }
+
+ if ( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "slapd-chain: "
+ "unable to init %sunderlying database \"%s\".\n",
+ lc->lc_common_li == NULL ? "common " : "", e->e_name.bv_val, 0 );
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+
+ li = ca->be->be_private;
+
+ if ( lc->lc_common_li == NULL ) {
+ lc->lc_common_li = li;
+
+ } else {
+ li->li_uri = ch_strdup( at->a_vals[ 0 ].bv_val );
+ value_add_one( &li->li_bvuri, &at->a_vals[ 0 ] );
+ if ( avl_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, 0, 0 );
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+ }
+ }
+
+done:;
+ if ( rc != LDAP_SUCCESS ) {
+ (void)ldap_chain_db_destroy_one( ca->be, NULL );
+ ch_free( ca->be );
+ ca->be = NULL;
+ }
+
+ return rc;
+}
+
+typedef struct ldap_chain_cfadd_apply_t {
+ Operation *op;
+ SlapReply *rs;
+ Entry *p;
+ ConfigArgs *ca;
+ int count;
+} ldap_chain_cfadd_apply_t;
+
+static int
+ldap_chain_cfadd_apply( void *datum, void *arg )
+{
+ ldapinfo_t *li = (ldapinfo_t *)datum;
+ ldap_chain_cfadd_apply_t *lca = (ldap_chain_cfadd_apply_t *)arg;
+
+ struct berval bv;
+
+ /* FIXME: should not hardcode "olcDatabase" here */
+ bv.bv_len = snprintf( lca->ca->cr_msg, sizeof( lca->ca->cr_msg ),
+ "olcDatabase={%d}%s", lca->count, lback->bi_type );
+ bv.bv_val = lca->ca->cr_msg;
+
+ lca->ca->be->be_private = (void *)li;
+ config_build_entry( lca->op, lca->rs, lca->p->e_private, lca->ca,
+ &bv, lback->bi_cf_ocs, &chainocs[1] );
+
+ lca->count++;
+
+ return 0;
+}
+
+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;
+
+ if ( lback->bi_cf_ocs ) {
+ ldap_chain_cfadd_apply_t lca = { 0 };
+
+ lca.op = op;
+ lca.rs = rs;
+ lca.p = p;
+ lca.ca = ca;
+ lca.count = 0;
+
+ (void)ldap_chain_cfadd_apply( (void *)lc->lc_common_li, (void *)&lca );
+
+ (void)avl_apply( lc->lc_lai.lai_tree, ldap_chain_cfadd_apply,
+ &lca, 1, AVL_INORDER );
+
+ ca->be->be_private = priv;
+ }
+
+ return 0;
+}
+
+#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 ], 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 ], 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, 0, 0 );
+ 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, 0, 0 );
+ 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, 0, 0 );
+ 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, 0, 0 );
+ 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, 0, 0 );
+#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, 0 );
+ rc = 1;