1 /* chain.c - chain LDAP operations */
3 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
5 * Copyright 2003-2005 The OpenLDAP Foundation.
6 * Portions Copyright 2003 Howard Chu.
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted only as authorized by the OpenLDAP
13 * A copy of this license is available in the file LICENSE in the
14 * top-level directory of the distribution or, alternatively, at
15 * <http://www.OpenLDAP.org/license.html>.
18 * This work was initially developed by the Howard Chu for inclusion
19 * in OpenLDAP Software.
26 #include <ac/string.h>
27 #include <ac/socket.h>
30 #include "back-ldap.h"
32 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
33 #define SLAP_CH_RESOLVE_SHIFT SLAP_CONTROL_SHIFT
34 #define SLAP_CH_RESOLVE_MASK (0x3 << SLAP_CH_RESOLVE_SHIFT)
35 #define SLAP_CH_RESOLVE_CHAINING_PREFERRED (LDAP_CHAINING_PREFERRED << SLAP_CH_RESOLVE_SHIFT)
36 #define SLAP_CH_RESOLVE_CHAINING_REQUIRED (LDAP_CHAINING_REQUIRED << SLAP_CH_RESOLVE_SHIFT)
37 #define SLAP_CH_RESOLVE_REFERRALS_PREFERRED (LDAP_REFERRALS_PREFERRED << SLAP_CH_RESOLVE_SHIFT)
38 #define SLAP_CH_RESOLVE_REFERRALS_REQUIRED (LDAP_REFERRALS_REQUIRED << SLAP_CH_RESOLVE_SHIFT)
39 #define SLAP_CH_RESOLVE_DEFAULT SLAP_CH_RESOLVE_CHAINING_PREFERRED
40 #define SLAP_CH_CONTINUATION_SHIFT (SLAP_CH_RESOLVE_SHIFT + 2)
41 #define SLAP_CH_CONTINUATION_MASK (0x3 << SLAP_CH_CONTINUATION_SHIFT)
42 #define SLAP_CH_CONTINUATION_CHAINING_PREFERRED (LDAP_CHAINING_PREFERRED << SLAP_CH_CONTINUATION_SHIFT)
43 #define SLAP_CH_CONTINUATION_CHAINING_REQUIRED (LDAP_CHAINING_REQUIRED << SLAP_CH_CONTINUATION_SHIFT)
44 #define SLAP_CH_CONTINUATION_REFERRALS_PREFERRED (LDAP_REFERRALS_PREFERRED << SLAP_CH_CONTINUATION_SHIFT)
45 #define SLAP_CH_CONTINUATION_REFERRALS_REQUIRED (LDAP_REFERRALS_REQUIRED << SLAP_CH_CONTINUATION_SHIFT)
46 #define SLAP_CH_CONTINUATION_DEFAULT SLAP_CH_CONTINUATION_CHAINING_PREFERRED
48 #define o_chaining o_ctrlflag[sc_chainingBehavior]
49 #define get_chaining(op) ((op)->o_chaining & SLAP_CONTROL_MASK)
50 #define get_chainingBehavior(op) ((op)->o_chaining & (SLAP_CH_RESOLVE_MASK|SLAP_CH_CONTINUATION_MASK))
51 #define get_resolveBehavior(op) ((op)->o_chaining & SLAP_CH_RESOLVE_MASK)
52 #define get_continuationBehavior(op) ((op)->o_chaining & SLAP_CH_CONTINUATION_MASK)
53 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
55 #define LDAP_CH_NONE ((void *)(0))
56 #define LDAP_CH_RES ((void *)(1))
57 #define LDAP_CH_ERR ((void *)(2))
59 static int sc_chainingBehavior;
60 static BackendInfo *lback;
63 ldap_chain_operational( Operation *op, SlapReply *rs )
65 /* Trap entries generated by back-ldap.
67 * FIXME: we need a better way to recognize them; a cleaner
68 * solution would be to be able to intercept the response
69 * of be_operational(), so that we can divert only those
70 * calls that fail because operational attributes were
71 * requested for entries that do not belong to the underlying
72 * database. This fix is likely to intercept also entries
73 * generated by back-perl and so. */
74 if ( rs->sr_entry->e_private == NULL ) {
78 return SLAP_CB_CONTINUE;
82 * Search specific response that strips entryDN from entries
85 ldap_chain_cb_search_response( Operation *op, SlapReply *rs )
87 assert( op->o_tag == LDAP_REQ_SEARCH );
89 /* if in error, don't proceed any further */
90 if ( op->o_callback->sc_private == LDAP_CH_ERR ) {
94 if ( rs->sr_type == REP_SEARCH ) {
95 Attribute **ap = &rs->sr_entry->e_attrs;
97 for ( ; *ap != NULL; ap = &(*ap)->a_next ) {
98 /* will be generated later by frontend
99 * (a cleaner solution would be that
100 * the frontend checks if it already exists */
101 if ( ad_cmp( (*ap)->a_desc, slap_schema.si_ad_entryDN ) == 0 )
108 /* there SHOULD be one only! */
113 return SLAP_CB_CONTINUE;
115 } else if ( rs->sr_type == REP_SEARCHREF ) {
116 /* if we get it here, it means the library was unable
117 * to chase the referral... */
119 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
120 if ( get_chaining( op ) > SLAP_CONTROL_IGNORED ) {
121 switch ( get_continuationBehavior( op ) ) {
122 case SLAP_CH_RESOLVE_CHAINING_REQUIRED:
123 op->o_callback->sc_private = LDAP_CH_ERR;
130 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
131 return SLAP_CB_CONTINUE;
133 } else if ( rs->sr_type == REP_RESULT ) {
134 /* back-ldap tried to send result */
135 op->o_callback->sc_private = LDAP_CH_RES;
142 * Dummy response that simply traces if back-ldap tried to send
143 * anything to the client
146 ldap_chain_cb_response( Operation *op, SlapReply *rs )
148 /* if in error, don't proceed any further */
149 if ( op->o_callback->sc_private == LDAP_CH_ERR ) {
153 if ( rs->sr_type == REP_RESULT ) {
154 op->o_callback->sc_private = LDAP_CH_RES;
156 } else if ( op->o_tag == LDAP_REQ_SEARCH && rs->sr_type == REP_SEARCH )
158 /* strip the entryDN attribute, but keep returning results */
159 (void)ldap_chain_cb_search_response( op, rs );
162 return SLAP_CB_CONTINUE;
169 int ( *op_f )( Operation *op, SlapReply *rs ),
172 slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
173 struct ldapinfo li, *lip = (struct ldapinfo *)on->on_bi.bi_private;
175 /* NOTE: returned if ref is empty... */
178 if ( lip->url != NULL ) {
179 op->o_bd->be_private = on->on_bi.bi_private;
180 return ( *op_f )( op, rs );
184 op->o_bd->be_private = &li;
186 /* if we parse the URI then by no means
187 * we can cache stuff or reuse connections,
188 * because in back-ldap there's no caching
189 * based on the URI value, which is supposed
190 * to be set once for all (correct?) */
191 op->o_do_not_cache = 1;
193 for ( ; !BER_BVISNULL( ref ); ref++ ) {
197 /* We're setting the URI of the first referral;
198 * what if there are more?
200 Document: draft-ietf-ldapbis-protocol-27.txt
204 If the client wishes to progress the operation, it MUST follow the
205 referral by contacting one of the supported services. If multiple
206 URIs are present, the client assumes that any supported URI may be
207 used to progress the operation.
209 * so we actually need to follow exactly one,
210 * and we can assume any is fine.
213 /* parse reference and use
214 * proto://[host][:port]/ only */
215 rc = ldap_url_parse_ext( ref->bv_val, &srv );
216 if ( rc != LDAP_URL_SUCCESS ) {
222 /* remove DN essentially because later on
223 * ldap_initialize() will parse the URL
224 * as a comma-separated URL list */
225 save_dn = srv->lud_dn;
227 srv->lud_scope = LDAP_SCOPE_DEFAULT;
228 li.url = ldap_url_desc2str( srv );
229 srv->lud_dn = save_dn;
230 ldap_free_urldesc( srv );
232 if ( li.url == NULL ) {
238 rc = ( *op_f )( op, rs );
240 ldap_memfree( li.url );
243 if ( rc == LDAP_SUCCESS && rs->sr_err == LDAP_SUCCESS ) {
252 ldap_chain_response( Operation *op, SlapReply *rs )
254 slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
255 void *private = op->o_bd->be_private;
256 slap_callback *sc = op->o_callback,
259 int cache = op->o_do_not_cache;
261 struct berval ndn = op->o_ndn;
263 struct ldapinfo li, *lip = (struct ldapinfo *)on->on_bi.bi_private;
265 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
266 int sr_err = rs->sr_err;
267 slap_reply_t sr_type = rs->sr_type;
268 slap_mask_t chain_mask = 0;
269 ber_len_t chain_shift = 0;
270 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
272 if ( rs->sr_err != LDAP_REFERRAL && rs->sr_type != REP_SEARCHREF ) {
273 return SLAP_CB_CONTINUE;
276 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
277 if ( rs->sr_err == LDAP_REFERRAL && get_chaining( op ) > SLAP_CONTROL_IGNORED ) {
278 switch ( get_resolveBehavior( op ) ) {
279 case SLAP_CH_RESOLVE_REFERRALS_PREFERRED:
280 case SLAP_CH_RESOLVE_REFERRALS_REQUIRED:
281 return SLAP_CB_CONTINUE;
284 chain_mask = SLAP_CH_RESOLVE_MASK;
285 chain_shift = SLAP_CH_RESOLVE_SHIFT;
289 } else if ( rs->sr_type == REP_SEARCHREF && get_chaining( op ) > SLAP_CONTROL_IGNORED ) {
290 switch ( get_continuationBehavior( op ) ) {
291 case SLAP_CH_CONTINUATION_REFERRALS_PREFERRED:
292 case SLAP_CH_CONTINUATION_REFERRALS_REQUIRED:
293 return SLAP_CB_CONTINUE;
296 chain_mask = SLAP_CH_CONTINUATION_MASK;
297 chain_shift = SLAP_CH_CONTINUATION_SHIFT;
301 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
304 * TODO: add checks on who/when chain operations; e.g.:
305 * a) what identities are authorized
306 * b) what request DN (e.g. only chain requests rooted at <DN>)
307 * c) what referral URIs
308 * d) what protocol scheme (e.g. only ldaps://)
315 /* we need this to know if back-ldap returned any result */
316 sc2.sc_response = ldap_chain_cb_response;
317 op->o_callback = &sc2;
319 /* Chaining can be performed by a privileged user on behalf
320 * of normal users, using the ProxyAuthz control, by exploiting
321 * the identity assertion feature of back-ldap; see idassert-*
322 * directives in slapd-ldap(5).
324 * FIXME: the idassert-authcDN is one, will it be fine regardless
325 * of the URI we obtain from the referral?
328 switch ( op->o_tag ) {
329 case LDAP_REQ_BIND: {
330 struct berval rndn = op->o_req_ndn;
331 Connection *conn = op->o_conn;
333 /* FIXME: can we really get a referral for binds? */
334 op->o_req_ndn = slap_empty_bv;
336 rc = ldap_chain_op( op, rs, lback->bi_op_bind, ref );
337 op->o_req_ndn = rndn;
343 int cleanup_attrs = 0;
345 if ( op->ora_e->e_attrs == NULL ) {
346 char textbuf[ SLAP_TEXT_BUFLEN ];
347 size_t textlen = sizeof( textbuf );
350 /* FIXME: op->o_bd is still set to the BackendDB
351 * structure of the database that tried to handle
352 * the operation and actually returned a referral
354 assert( SLAP_DBFLAGS( op->o_bd ) & SLAP_DBFLAG_GLOBAL_OVERLAY );
357 /* global overlay: create entry */
358 /* NOTE: this is a hack to use the chain overlay
359 * as global. I expect to be able to remove this
360 * soon by using slap_mods2entry() earlier in
361 * do_add(), adding the operational attrs later
363 rs->sr_err = slap_mods2entry( op->ora_modlist,
365 &rs->sr_text, textbuf, textlen );
366 if ( rs->sr_err != LDAP_SUCCESS ) {
367 send_ldap_result( op, rs );
372 rc = ldap_chain_op( op, rs, lback->bi_op_add, ref );
373 if ( cleanup_attrs ) {
374 attrs_free( op->ora_e->e_attrs );
375 op->ora_e->e_attrs = NULL;
379 case LDAP_REQ_DELETE:
380 rc = ldap_chain_op( op, rs, lback->bi_op_delete, ref );
382 case LDAP_REQ_MODRDN:
383 rc = ldap_chain_op( op, rs, lback->bi_op_modrdn, ref );
385 case LDAP_REQ_MODIFY:
386 rc = ldap_chain_op( op, rs, lback->bi_op_modify, ref );
388 case LDAP_REQ_COMPARE:
389 rc = ldap_chain_op( op, rs, lback->bi_op_compare, ref );
391 case LDAP_REQ_SEARCH:
392 if ( rs->sr_type == REP_SEARCHREF ) {
393 struct berval *curr = ref,
395 ondn = op->o_req_ndn;
397 rs->sr_type = REP_SEARCH;
399 sc2.sc_response = ldap_chain_cb_search_response;
403 op->o_bd->be_private = &li;
405 /* if we parse the URI then by no means
406 * we can cache stuff or reuse connections,
407 * because in back-ldap there's no caching
408 * based on the URI value, which is supposed
409 * to be set once for all (correct?) */
410 op->o_do_not_cache = 1;
412 /* copy the private info because we need to modify it */
413 for ( ; !BER_BVISNULL( &curr[0] ); curr++ ) {
417 /* parse reference and use
418 * proto://[host][:port]/ only */
419 rc = ldap_url_parse_ext( curr[0].bv_val, &srv );
420 if ( rc != LDAP_URL_SUCCESS ) {
422 rs->sr_err = LDAP_OTHER;
426 /* remove DN essentially because later on
427 * ldap_initialize() will parse the URL
428 * as a comma-separated URL list */
429 save_dn = srv->lud_dn;
431 srv->lud_scope = LDAP_SCOPE_DEFAULT;
432 li.url = ldap_url_desc2str( srv );
433 if ( li.url != NULL ) {
434 ber_str2bv_x( save_dn, 0, 1, &op->o_req_dn,
436 ber_dupbv_x( &op->o_req_ndn, &op->o_req_dn,
440 srv->lud_dn = save_dn;
441 ldap_free_urldesc( srv );
443 if ( li.url == NULL ) {
445 rs->sr_err = LDAP_OTHER;
450 /* FIXME: should we also copy filter and scope?
451 * according to RFC3296, no */
452 rc = lback->bi_op_search( op, rs );
454 ldap_memfree( li.url );
457 op->o_tmpfree( op->o_req_dn.bv_val,
459 op->o_tmpfree( op->o_req_ndn.bv_val,
462 if ( rc == LDAP_SUCCESS && rs->sr_err == LDAP_SUCCESS ) {
470 op->o_req_ndn = ondn;
471 rs->sr_type = REP_SEARCHREF;
474 if ( rc != LDAP_SUCCESS ) {
475 /* couldn't chase any of the referrals */
476 rc = SLAP_CB_CONTINUE;
480 rc = ldap_chain_op( op, rs, lback->bi_op_search, ref );
483 case LDAP_REQ_EXTENDED:
484 rc = ldap_chain_op( op, rs, lback->bi_extended, ref );
485 /* FIXME: ldap_back_extended() by design
486 * doesn't send result; frontend is expected
488 if ( rc != SLAPD_ABANDON ) {
489 send_ldap_extended( op, rs );
494 rc = SLAP_CB_CONTINUE;
498 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
499 if ( rc != LDAP_SUCCESS || sc2.sc_private == LDAP_CH_ERR ) {
500 if ( rs->sr_err == LDAP_CANNOT_CHAIN ) {
504 switch ( ( get_chainingBehavior( op ) & chain_mask ) >> chain_shift ) {
505 case LDAP_CHAINING_REQUIRED:
507 op->o_callback = NULL;
508 send_ldap_error( op, rs, LDAP_CANNOT_CHAIN, "operation cannot be completed without chaining" );
512 rc = SLAP_CB_CONTINUE;
514 rs->sr_type = sr_type;
519 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
521 if ( sc2.sc_private == LDAP_CH_NONE ) {
522 op->o_callback = NULL;
523 rc = rs->sr_err = slap_map_api2result( rs );
524 send_ldap_result( op, rs );
527 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
529 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
530 op->o_do_not_cache = cache;
531 op->o_bd->be_private = private;
540 ldap_chain_db_config(
548 slap_overinst *on = (slap_overinst *) be->bd_info;
549 void *private = be->be_private;
553 be->be_private = on->on_bi.bi_private;
554 if ( strncasecmp( argv[ 0 ], "chain-", STRLENOF( "chain-" ) ) == 0 ) {
556 argv[ 0 ] = &argv[ 0 ][ STRLENOF( "chain-" ) ];
558 rc = lback->bi_db_config( be, fname, lineno, argc, argv );
563 be->be_private = private;
572 slap_overinst *on = (slap_overinst *)be->bd_info;
576 if ( lback == NULL ) {
577 lback = backend_info( "ldap" );
579 if ( lback == NULL ) {
584 bd.be_private = NULL;
585 rc = lback->bi_db_init( &bd );
586 on->on_bi.bi_private = bd.be_private;
591 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
597 return overlay_register_control( be, LDAP_CONTROL_X_CHAINING_BEHAVIOR );
599 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
602 ldap_chain_db_destroy(
606 slap_overinst *on = (slap_overinst *) be->bd_info;
607 void *private = be->be_private;
610 be->be_private = on->on_bi.bi_private;
611 rc = lback->bi_db_destroy( be );
612 on->on_bi.bi_private = be->be_private;
613 be->be_private = private;
618 ldap_chain_connection_destroy(
623 slap_overinst *on = (slap_overinst *) be->bd_info;
624 void *private = be->be_private;
627 be->be_private = on->on_bi.bi_private;
628 rc = lback->bi_connection_destroy( be, conn );
629 on->on_bi.bi_private = be->be_private;
630 be->be_private = private;
634 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
636 ldap_chain_parse_ctrl(
646 if ( get_chaining( op ) != SLAP_CONTROL_NONE ) {
647 rs->sr_text = "Chaining behavior control specified multiple times";
648 return LDAP_PROTOCOL_ERROR;
651 if ( op->o_pagedresults != SLAP_CONTROL_NONE ) {
652 rs->sr_text = "Chaining behavior control specified with pagedResults control";
653 return LDAP_PROTOCOL_ERROR;
656 if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) {
657 mode = (SLAP_CH_RESOLVE_DEFAULT|SLAP_CH_CONTINUATION_DEFAULT);
662 /* Parse the control value
663 * ChainingBehavior ::= SEQUENCE {
664 * resolveBehavior Behavior OPTIONAL,
665 * continuationBehavior Behavior OPTIONAL }
667 * Behavior :: = ENUMERATED {
668 * chainingPreferred (0),
669 * chainingRequired (1),
670 * referralsPreferred (2),
671 * referralsRequired (3) }
674 ber = ber_init( &ctrl->ldctl_value );
676 rs->sr_text = "internal error";
680 tag = ber_scanf( ber, "{e" /* } */, &behavior );
681 /* FIXME: since the whole SEQUENCE is optional,
682 * should we accept no enumerations at all? */
683 if ( tag != LBER_ENUMERATED ) {
684 rs->sr_text = "Chaining behavior control: resolveBehavior decoding error";
685 return LDAP_PROTOCOL_ERROR;
688 switch ( behavior ) {
689 case LDAP_CHAINING_PREFERRED:
690 mode = SLAP_CH_RESOLVE_CHAINING_PREFERRED;
693 case LDAP_CHAINING_REQUIRED:
694 mode = SLAP_CH_RESOLVE_CHAINING_REQUIRED;
697 case LDAP_REFERRALS_PREFERRED:
698 mode = SLAP_CH_RESOLVE_REFERRALS_PREFERRED;
701 case LDAP_REFERRALS_REQUIRED:
702 mode = SLAP_CH_RESOLVE_REFERRALS_REQUIRED;
706 rs->sr_text = "Chaining behavior control: unknown resolveBehavior";
707 return LDAP_PROTOCOL_ERROR;
710 tag = ber_peek_tag( ber, &len );
711 if ( tag == LBER_ENUMERATED ) {
712 tag = ber_scanf( ber, "e", &behavior );
713 if ( tag == LBER_ERROR ) {
714 rs->sr_text = "Chaining behavior control: continuationBehavior decoding error";
715 return LDAP_PROTOCOL_ERROR;
719 if ( tag == LBER_DEFAULT ) {
720 mode |= SLAP_CH_CONTINUATION_DEFAULT;
723 switch ( behavior ) {
724 case LDAP_CHAINING_PREFERRED:
725 mode |= SLAP_CH_CONTINUATION_CHAINING_PREFERRED;
728 case LDAP_CHAINING_REQUIRED:
729 mode |= SLAP_CH_CONTINUATION_CHAINING_REQUIRED;
732 case LDAP_REFERRALS_PREFERRED:
733 mode |= SLAP_CH_CONTINUATION_REFERRALS_PREFERRED;
736 case LDAP_REFERRALS_REQUIRED:
737 mode |= SLAP_CH_CONTINUATION_REFERRALS_REQUIRED;
741 rs->sr_text = "Chaining behavior control: unknown continuationBehavior";
742 return LDAP_PROTOCOL_ERROR;
746 if ( ( ber_scanf( ber, /* { */ "}") ) == LBER_ERROR ) {
747 rs->sr_text = "Chaining behavior control: decoding error";
748 return LDAP_PROTOCOL_ERROR;
751 (void) ber_free( ber, 1 );
754 op->o_chaining = mode | ( ctrl->ldctl_iscritical
755 ? SLAP_CONTROL_CRITICAL
756 : SLAP_CONTROL_NONCRITICAL );
760 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
762 static slap_overinst ldapchain;
767 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
770 rc = register_supported_control( LDAP_CONTROL_X_CHAINING_BEHAVIOR,
771 /* SLAP_CTRL_GLOBAL| */ SLAP_CTRL_ACCESS|SLAP_CTRL_HIDE, NULL,
772 ldap_chain_parse_ctrl, &sc_chainingBehavior );
773 if ( rc != LDAP_SUCCESS ) {
774 fprintf( stderr, "Failed to register chaining behavior control: %d\n", rc );
777 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
779 ldapchain.on_bi.bi_type = "chain";
780 ldapchain.on_bi.bi_db_init = ldap_chain_db_init;
781 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
782 ldapchain.on_bi.bi_db_open = ldap_chain_db_open;
783 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
784 ldapchain.on_bi.bi_db_config = ldap_chain_db_config;
785 ldapchain.on_bi.bi_db_destroy = ldap_chain_db_destroy;
787 /* ... otherwise the underlying backend's function would be called,
788 * likely passing an invalid entry; on the contrary, the requested
789 * operational attributes should have been returned while chasing
790 * the referrals. This all in all is a bit messy, because part
791 * of the operational attributes are generated by the backend;
792 * part by the frontend; back-ldap should receive all the available
793 * ones from the remote server, but then, on its own, it strips those
794 * it assumes will be (re)generated by the frontend (e.g.
795 * subschemaSubentry.) */
796 ldapchain.on_bi.bi_operational = ldap_chain_operational;
798 ldapchain.on_bi.bi_connection_destroy = ldap_chain_connection_destroy;
800 ldapchain.on_response = ldap_chain_response;
802 return overlay_register( &ldapchain );