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 static int sc_chainingBehavior;
56 static BackendInfo *lback;
59 ldap_chain_operational( Operation *op, SlapReply *rs )
61 /* Trap entries generated by back-ldap.
63 * FIXME: we need a better way to recognize them; a cleaner
64 * solution would be to be able to intercept the response
65 * of be_operational(), so that we can divert only those
66 * calls that fail because operational attributes were
67 * requested for entries that do not belong to the underlying
68 * database. This fix is likely to intercept also entries
69 * generated by back-perl and so. */
70 if ( rs->sr_entry->e_private == NULL ) {
74 return SLAP_CB_CONTINUE;
78 * Search specific response that strips entryDN from entries
81 ldap_chain_cb_search_response( Operation *op, SlapReply *rs )
83 assert( op->o_tag == LDAP_REQ_SEARCH );
85 if ( rs->sr_type == REP_SEARCH ) {
86 Attribute **ap = &rs->sr_entry->e_attrs;
88 for ( ; *ap != NULL; ap = &(*ap)->a_next ) {
89 /* will be generated later by frontend
90 * (a cleaner solution would be that
91 * the frontend checks if it already exists */
92 if ( ad_cmp( (*ap)->a_desc, slap_schema.si_ad_entryDN ) == 0 )
99 /* there SHOULD be one only! */
104 return SLAP_CB_CONTINUE;
106 } else if ( rs->sr_type == REP_RESULT ) {
107 /* back-ldap tried to send result */
108 op->o_callback->sc_private = (void *)(1);
115 * Dummy response that simply traces if back-ldap tried to send
116 * anything to the client
119 ldap_chain_cb_response( Operation *op, SlapReply *rs )
121 if ( rs->sr_type == REP_RESULT ) {
122 op->o_callback->sc_private = (void *)(1);
124 } else if ( op->o_tag == LDAP_REQ_SEARCH && rs->sr_type == REP_SEARCH )
126 /* strip the entryDN attribute, but keep returning results */
127 (void)ldap_chain_cb_search_response( op, rs );
130 return SLAP_CB_CONTINUE;
137 int ( *op_f )( Operation *op, SlapReply *rs ),
140 slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
141 struct ldapinfo li, *lip = (struct ldapinfo *)on->on_bi.bi_private;
143 /* NOTE: returned if ref is empty... */
146 if ( lip->url != NULL ) {
147 op->o_bd->be_private = on->on_bi.bi_private;
148 return ( *op_f )( op, rs );
152 op->o_bd->be_private = &li;
154 /* if we parse the URI then by no means
155 * we can cache stuff or reuse connections,
156 * because in back-ldap there's no caching
157 * based on the URI value, which is supposed
158 * to be set once for all (correct?) */
159 op->o_do_not_cache = 1;
161 for ( ; !BER_BVISNULL( ref ); ref++ ) {
165 /* We're setting the URI of the first referral;
166 * what if there are more?
168 Document: draft-ietf-ldapbis-protocol-27.txt
172 If the client wishes to progress the operation, it MUST follow the
173 referral by contacting one of the supported services. If multiple
174 URIs are present, the client assumes that any supported URI may be
175 used to progress the operation.
177 * so we actually need to follow exactly one,
178 * and we can assume any is fine.
181 /* parse reference and use
182 * proto://[host][:port]/ only */
183 rc = ldap_url_parse_ext( ref->bv_val, &srv );
184 if ( rc != LDAP_URL_SUCCESS ) {
190 /* remove DN essentially because later on
191 * ldap_initialize() will parse the URL
192 * as a comma-separated URL list */
193 save_dn = srv->lud_dn;
195 srv->lud_scope = LDAP_SCOPE_DEFAULT;
196 li.url = ldap_url_desc2str( srv );
197 srv->lud_dn = save_dn;
198 ldap_free_urldesc( srv );
200 if ( li.url == NULL ) {
206 rc = ( *op_f )( op, rs );
208 ldap_memfree( li.url );
211 if ( rc == LDAP_SUCCESS && rs->sr_err == LDAP_SUCCESS ) {
220 ldap_chain_response( Operation *op, SlapReply *rs )
222 slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
223 void *private = op->o_bd->be_private;
224 slap_callback *sc = op->o_callback,
227 int cache = op->o_do_not_cache;
229 struct berval ndn = op->o_ndn;
231 struct ldapinfo li, *lip = (struct ldapinfo *)on->on_bi.bi_private;
233 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
234 int sr_err = rs->sr_err;
235 slap_reply_t sr_type = rs->sr_type;
236 slap_mask_t chain_mask = 0;
237 ber_len_t chain_shift = 0;
238 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
240 if ( rs->sr_err != LDAP_REFERRAL && rs->sr_type != REP_SEARCHREF ) {
241 return SLAP_CB_CONTINUE;
244 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
245 if ( rs->sr_err == LDAP_REFERRAL && get_chaining( op ) > SLAP_CONTROL_IGNORED ) {
246 switch ( get_resolveBehavior( op ) ) {
247 case SLAP_CH_RESOLVE_REFERRALS_PREFERRED:
248 case SLAP_CH_RESOLVE_REFERRALS_REQUIRED:
249 return SLAP_CB_CONTINUE;
252 chain_mask = SLAP_CH_RESOLVE_MASK;
253 chain_shift = SLAP_CH_RESOLVE_SHIFT;
257 } else if ( rs->sr_type == REP_SEARCHREF && get_chaining( op ) > SLAP_CONTROL_IGNORED ) {
258 switch ( get_continuationBehavior( op ) ) {
259 case SLAP_CH_CONTINUATION_REFERRALS_PREFERRED:
260 case SLAP_CH_CONTINUATION_REFERRALS_REQUIRED:
261 return SLAP_CB_CONTINUE;
264 chain_mask = SLAP_CH_CONTINUATION_MASK;
265 chain_shift = SLAP_CH_CONTINUATION_SHIFT;
269 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
272 * TODO: add checks on who/when chain operations; e.g.:
273 * a) what identities are authorized
274 * b) what request DN (e.g. only chain requests rooted at <DN>)
275 * c) what referral URIs
276 * d) what protocol scheme (e.g. only ldaps://)
283 /* we need this to know if back-ldap returned any result */
284 sc2.sc_response = ldap_chain_cb_response;
285 op->o_callback = &sc2;
287 /* Chaining can be performed by a privileged user on behalf
288 * of normal users, using the ProxyAuthz control, by exploiting
289 * the identity assertion feature of back-ldap; see idassert-*
290 * directives in slapd-ldap(5).
292 * FIXME: the idassert-authcDN is one, will it be fine regardless
293 * of the URI we obtain from the referral?
296 switch ( op->o_tag ) {
297 case LDAP_REQ_BIND: {
298 struct berval rndn = op->o_req_ndn;
299 Connection *conn = op->o_conn;
301 /* FIXME: can we really get a referral for binds? */
302 op->o_req_ndn = slap_empty_bv;
304 rc = ldap_chain_op( op, rs, lback->bi_op_bind, ref );
305 op->o_req_ndn = rndn;
311 int cleanup_attrs = 0;
313 if ( op->ora_e->e_attrs == NULL ) {
314 char textbuf[ SLAP_TEXT_BUFLEN ];
315 size_t textlen = sizeof( textbuf );
318 /* FIXME: op->o_bd is still set to the BackendDB
319 * structure of the database that tried to handle
320 * the operation and actually returned a referral
322 assert( SLAP_DBFLAGS( op->o_bd ) & SLAP_DBFLAG_GLOBAL_OVERLAY );
325 /* global overlay: create entry */
326 /* NOTE: this is a hack to use the chain overlay
327 * as global. I expect to be able to remove this
328 * soon by using slap_mods2entry() earlier in
329 * do_add(), adding the operational attrs later
331 rs->sr_err = slap_mods2entry( op->ora_modlist,
333 &rs->sr_text, textbuf, textlen );
334 if ( rs->sr_err != LDAP_SUCCESS ) {
335 send_ldap_result( op, rs );
340 rc = ldap_chain_op( op, rs, lback->bi_op_add, ref );
341 if ( cleanup_attrs ) {
342 attrs_free( op->ora_e->e_attrs );
343 op->ora_e->e_attrs = NULL;
347 case LDAP_REQ_DELETE:
348 rc = ldap_chain_op( op, rs, lback->bi_op_delete, ref );
350 case LDAP_REQ_MODRDN:
351 rc = ldap_chain_op( op, rs, lback->bi_op_modrdn, ref );
353 case LDAP_REQ_MODIFY:
354 rc = ldap_chain_op( op, rs, lback->bi_op_modify, ref );
356 case LDAP_REQ_COMPARE:
357 rc = ldap_chain_op( op, rs, lback->bi_op_compare, ref );
359 case LDAP_REQ_SEARCH:
360 if ( rs->sr_type == REP_SEARCHREF ) {
361 struct berval *curr = ref,
363 ondn = op->o_req_ndn;
365 rs->sr_type = REP_SEARCH;
367 sc2.sc_response = ldap_chain_cb_search_response;
371 op->o_bd->be_private = &li;
373 /* if we parse the URI then by no means
374 * we can cache stuff or reuse connections,
375 * because in back-ldap there's no caching
376 * based on the URI value, which is supposed
377 * to be set once for all (correct?) */
378 op->o_do_not_cache = 1;
380 /* copy the private info because we need to modify it */
381 for ( ; !BER_BVISNULL( &curr[0] ); curr++ ) {
385 /* parse reference and use
386 * proto://[host][:port]/ only */
387 rc = ldap_url_parse_ext( curr[0].bv_val, &srv );
388 if ( rc != LDAP_URL_SUCCESS ) {
390 rs->sr_err = LDAP_OTHER;
394 /* remove DN essentially because later on
395 * ldap_initialize() will parse the URL
396 * as a comma-separated URL list */
397 save_dn = srv->lud_dn;
399 srv->lud_scope = LDAP_SCOPE_DEFAULT;
400 li.url = ldap_url_desc2str( srv );
401 if ( li.url != NULL ) {
402 ber_str2bv_x( save_dn, 0, 1, &op->o_req_dn,
404 ber_dupbv_x( &op->o_req_ndn, &op->o_req_dn,
408 srv->lud_dn = save_dn;
409 ldap_free_urldesc( srv );
411 if ( li.url == NULL ) {
413 rs->sr_err = LDAP_OTHER;
418 /* FIXME: should we also copy filter and scope?
419 * according to RFC3296, no */
420 rc = lback->bi_op_search( op, rs );
422 ldap_memfree( li.url );
425 op->o_tmpfree( op->o_req_dn.bv_val,
427 op->o_tmpfree( op->o_req_ndn.bv_val,
430 if ( rc == LDAP_SUCCESS && rs->sr_err == LDAP_SUCCESS ) {
436 op->o_req_ndn = ondn;
437 rs->sr_type = REP_SEARCHREF;
440 if ( rc != LDAP_SUCCESS ) {
441 /* couldn't chase any of the referrals */
442 rc = SLAP_CB_CONTINUE;
446 rc = ldap_chain_op( op, rs, lback->bi_op_search, ref );
449 case LDAP_REQ_EXTENDED:
450 rc = ldap_chain_op( op, rs, lback->bi_extended, ref );
451 /* FIXME: ldap_back_extended() by design
452 * doesn't send result; frontend is expected
454 if ( rc != SLAPD_ABANDON ) {
455 send_ldap_extended( op, rs );
460 rc = SLAP_CB_CONTINUE;
464 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
465 if ( rc != LDAP_SUCCESS ) {
466 switch ( ( get_chainingBehavior( op ) & chain_mask ) >> chain_shift ) {
467 case LDAP_CHAINING_REQUIRED:
468 op->o_callback = NULL;
469 send_ldap_error( op, rs, LDAP_CANNOT_CHAIN, "operation cannot be completed without chaining" );
473 rc = SLAP_CB_CONTINUE;
475 rs->sr_type = sr_type;
480 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
482 if ( sc2.sc_private == NULL ) {
483 op->o_callback = NULL;
484 rc = rs->sr_err = slap_map_api2result( rs );
485 send_ldap_result( op, rs );
488 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
490 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
491 op->o_do_not_cache = cache;
492 op->o_bd->be_private = private;
501 ldap_chain_db_config(
509 slap_overinst *on = (slap_overinst *) be->bd_info;
510 void *private = be->be_private;
514 be->be_private = on->on_bi.bi_private;
515 if ( strncasecmp( argv[ 0 ], "chain-", STRLENOF( "chain-" ) ) == 0 ) {
517 argv[ 0 ] = &argv[ 0 ][ STRLENOF( "chain-" ) ];
519 rc = lback->bi_db_config( be, fname, lineno, argc, argv );
524 be->be_private = private;
533 slap_overinst *on = (slap_overinst *)be->bd_info;
537 if ( lback == NULL ) {
538 lback = backend_info( "ldap" );
540 if ( lback == NULL ) {
545 bd.be_private = NULL;
546 rc = lback->bi_db_init( &bd );
547 on->on_bi.bi_private = bd.be_private;
552 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
558 return overlay_register_control( be, LDAP_CONTROL_X_CHAINING_BEHAVIOR );
560 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
563 ldap_chain_db_destroy(
567 slap_overinst *on = (slap_overinst *) be->bd_info;
568 void *private = be->be_private;
571 be->be_private = on->on_bi.bi_private;
572 rc = lback->bi_db_destroy( be );
573 on->on_bi.bi_private = be->be_private;
574 be->be_private = private;
578 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
580 ldap_chain_parse_ctrl(
590 if ( get_chaining( op ) != SLAP_CONTROL_NONE ) {
591 rs->sr_text = "Chaining behavior control specified multiple times";
592 return LDAP_PROTOCOL_ERROR;
595 if ( op->o_pagedresults != SLAP_CONTROL_NONE ) {
596 rs->sr_text = "Chaining behavior control specified with pagedResults control";
597 return LDAP_PROTOCOL_ERROR;
600 if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) {
601 mode = (SLAP_CH_RESOLVE_DEFAULT|SLAP_CH_CONTINUATION_DEFAULT);
606 /* Parse the control value
607 * ChainingBehavior ::= SEQUENCE {
608 * resolveBehavior Behavior OPTIONAL,
609 * continuationBehavior Behavior OPTIONAL }
611 * Behavior :: = ENUMERATED {
612 * chainingPreferred (0),
613 * chainingRequired (1),
614 * referralsPreferred (2),
615 * referralsRequired (3) }
618 ber = ber_init( &ctrl->ldctl_value );
620 rs->sr_text = "internal error";
624 tag = ber_scanf( ber, "{e" /* } */, &behavior );
625 /* FIXME: since the whole SEQUENCE is optional,
626 * should we accept no enumerations at all? */
627 if ( tag != LBER_ENUMERATED ) {
628 rs->sr_text = "Chaining behavior control: resolveBehavior decoding error";
629 return LDAP_PROTOCOL_ERROR;
632 switch ( behavior ) {
633 case LDAP_CHAINING_PREFERRED:
634 mode = SLAP_CH_RESOLVE_CHAINING_PREFERRED;
637 case LDAP_CHAINING_REQUIRED:
638 mode = SLAP_CH_RESOLVE_CHAINING_REQUIRED;
641 case LDAP_REFERRALS_PREFERRED:
642 mode = SLAP_CH_RESOLVE_REFERRALS_PREFERRED;
645 case LDAP_REFERRALS_REQUIRED:
646 mode = SLAP_CH_RESOLVE_REFERRALS_REQUIRED;
650 rs->sr_text = "Chaining behavior control: unknown resolveBehavior";
651 return LDAP_PROTOCOL_ERROR;
654 tag = ber_peek_tag( ber, &len );
655 if ( tag == LBER_ENUMERATED ) {
656 tag = ber_scanf( ber, "e", &behavior );
657 if ( tag == LBER_ERROR ) {
658 rs->sr_text = "Chaining behavior control: continuationBehavior decoding error";
659 return LDAP_PROTOCOL_ERROR;
663 if ( tag == LBER_DEFAULT ) {
664 mode |= SLAP_CH_CONTINUATION_DEFAULT;
667 switch ( behavior ) {
668 case LDAP_CHAINING_PREFERRED:
669 mode |= SLAP_CH_CONTINUATION_CHAINING_PREFERRED;
672 case LDAP_CHAINING_REQUIRED:
673 mode |= SLAP_CH_CONTINUATION_CHAINING_REQUIRED;
676 case LDAP_REFERRALS_PREFERRED:
677 mode |= SLAP_CH_CONTINUATION_REFERRALS_PREFERRED;
680 case LDAP_REFERRALS_REQUIRED:
681 mode |= SLAP_CH_CONTINUATION_REFERRALS_REQUIRED;
685 rs->sr_text = "Chaining behavior control: unknown continuationBehavior";
686 return LDAP_PROTOCOL_ERROR;
690 if ( ( ber_scanf( ber, /* { */ "}") ) == LBER_ERROR ) {
691 rs->sr_text = "Chaining behavior control: decoding error";
692 return LDAP_PROTOCOL_ERROR;
695 (void) ber_free( ber, 1 );
698 op->o_chaining = mode | ( ctrl->ldctl_iscritical
699 ? SLAP_CONTROL_CRITICAL
700 : SLAP_CONTROL_NONCRITICAL );
704 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
706 static slap_overinst ldapchain;
711 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
714 rc = register_supported_control( LDAP_CONTROL_X_CHAINING_BEHAVIOR,
715 /* SLAP_CTRL_FRONTEND| */ SLAP_CTRL_ACCESS|SLAP_CTRL_HIDE, NULL,
716 ldap_chain_parse_ctrl, &sc_chainingBehavior );
717 if ( rc != LDAP_SUCCESS ) {
718 fprintf( stderr, "Failed to register chaining behavior control: %d\n", rc );
721 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
723 ldapchain.on_bi.bi_type = "chain";
724 ldapchain.on_bi.bi_db_init = ldap_chain_db_init;
725 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
726 ldapchain.on_bi.bi_db_open = ldap_chain_db_open;
727 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
728 ldapchain.on_bi.bi_db_config = ldap_chain_db_config;
729 ldapchain.on_bi.bi_db_destroy = ldap_chain_db_destroy;
731 /* ... otherwise the underlying backend's function would be called,
732 * likely passing an invalid entry; on the contrary, the requested
733 * operational attributes should have been returned while chasing
734 * the referrals. This all in all is a bit messy, because part
735 * of the operational attributes are generated by they backend;
736 * part by the frontend; back-ldap should receive all the available
737 * ones from the remote server, but then, on it own, it strips those
738 * it assumes will be (re)generated by the frontend (e.g.
739 * subschemaSubentry.) */
740 ldapchain.on_bi.bi_operational = ldap_chain_operational;
742 ldapchain.on_response = ldap_chain_response;
744 return overlay_register( &ldapchain );