+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;
+#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)' "
+ "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, olcDatabaseDummy, 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, 0 );
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+
+ } else
+#endif
+ 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;
+ }
+ }
+
+ 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;
+}
+
+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 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 (! avl_delete( &lc->lc_lai.lai_tree, li, ldap_chain_uri_cmp ) ) {
+ Debug( LDAP_DEBUG_ANY, "slapd-chain: avl_delete failed. "
+ "\"%s\" not found.\n", li->li_uri, 0, 0 );
+ 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", 0, 0, 0 );
+ 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 ], 0 );
+ return 1;