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 );
317 assert( SLAP_DBFLAGS( op->o_bd ) & SLAP_DBFLAG_GLOBAL_OVERLAY );
319 /* global overlay: create entry */
320 /* NOTE: this is a hack to use the chain overlay
321 * as global. I expect to be able to remove this
322 * soon by using slap_mods2entry() earlier in
323 * do_add(), adding the operational attrs later
325 rs->sr_err = slap_mods2entry( op->ora_modlist,
327 &rs->sr_text, textbuf, textlen );
328 if ( rs->sr_err != LDAP_SUCCESS ) {
329 send_ldap_result( op, rs );
334 rc = ldap_chain_op( op, rs, lback->bi_op_add, ref );
335 if ( cleanup_attrs ) {
336 attrs_free( op->ora_e->e_attrs );
337 op->ora_e->e_attrs = NULL;
341 case LDAP_REQ_DELETE:
342 rc = ldap_chain_op( op, rs, lback->bi_op_delete, ref );
344 case LDAP_REQ_MODRDN:
345 rc = ldap_chain_op( op, rs, lback->bi_op_modrdn, ref );
347 case LDAP_REQ_MODIFY:
348 rc = ldap_chain_op( op, rs, lback->bi_op_modify, ref );
350 case LDAP_REQ_COMPARE:
351 rc = ldap_chain_op( op, rs, lback->bi_op_compare, ref );
353 case LDAP_REQ_SEARCH:
354 if ( rs->sr_type == REP_SEARCHREF ) {
355 struct berval *curr = ref,
357 ondn = op->o_req_ndn;
359 rs->sr_type = REP_SEARCH;
361 sc2.sc_response = ldap_chain_cb_search_response;
365 op->o_bd->be_private = &li;
367 /* if we parse the URI then by no means
368 * we can cache stuff or reuse connections,
369 * because in back-ldap there's no caching
370 * based on the URI value, which is supposed
371 * to be set once for all (correct?) */
372 op->o_do_not_cache = 1;
374 /* copy the private info because we need to modify it */
375 for ( ; !BER_BVISNULL( &curr[0] ); curr++ ) {
379 /* parse reference and use
380 * proto://[host][:port]/ only */
381 rc = ldap_url_parse_ext( curr[0].bv_val, &srv );
382 if ( rc != LDAP_URL_SUCCESS ) {
384 rs->sr_err = LDAP_OTHER;
388 /* remove DN essentially because later on
389 * ldap_initialize() will parse the URL
390 * as a comma-separated URL list */
391 save_dn = srv->lud_dn;
393 srv->lud_scope = LDAP_SCOPE_DEFAULT;
394 li.url = ldap_url_desc2str( srv );
395 if ( li.url != NULL ) {
396 ber_str2bv_x( save_dn, 0, 1, &op->o_req_dn,
398 ber_dupbv_x( &op->o_req_ndn, &op->o_req_dn,
402 srv->lud_dn = save_dn;
403 ldap_free_urldesc( srv );
405 if ( li.url == NULL ) {
407 rs->sr_err = LDAP_OTHER;
412 /* FIXME: should we also copy filter and scope?
413 * according to RFC3296, no */
414 rc = lback->bi_op_search( op, rs );
416 ldap_memfree( li.url );
419 op->o_tmpfree( op->o_req_dn.bv_val,
421 op->o_tmpfree( op->o_req_ndn.bv_val,
424 if ( rc == LDAP_SUCCESS && rs->sr_err == LDAP_SUCCESS ) {
430 op->o_req_ndn = ondn;
431 rs->sr_type = REP_SEARCHREF;
434 if ( rc != LDAP_SUCCESS ) {
435 /* couldn't chase any of the referrals */
436 rc = SLAP_CB_CONTINUE;
440 rc = ldap_chain_op( op, rs, lback->bi_op_search, ref );
443 case LDAP_REQ_EXTENDED:
444 rc = ldap_chain_op( op, rs, lback->bi_extended, ref );
445 /* FIXME: ldap_back_extended() by design
446 * doesn't send result; frontend is expected
448 if ( rc != SLAPD_ABANDON ) {
449 send_ldap_extended( op, rs );
454 rc = SLAP_CB_CONTINUE;
458 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
459 if ( rc != LDAP_SUCCESS ) {
460 switch ( ( get_chainingBehavior( op ) & chain_mask ) >> chain_shift ) {
461 case LDAP_CHAINING_REQUIRED:
462 op->o_callback = NULL;
463 send_ldap_error( op, rs, LDAP_CANNOT_CHAIN, "operation cannot be completed without chaining" );
467 rc = SLAP_CB_CONTINUE;
469 rs->sr_type = sr_type;
474 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
476 if ( sc2.sc_private == NULL ) {
477 op->o_callback = NULL;
478 rc = rs->sr_err = slap_map_api2result( rs );
479 send_ldap_result( op, rs );
482 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
484 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
485 op->o_do_not_cache = cache;
486 op->o_bd->be_private = private;
495 ldap_chain_db_config(
503 slap_overinst *on = (slap_overinst *) be->bd_info;
504 void *private = be->be_private;
508 be->be_private = on->on_bi.bi_private;
509 if ( strncasecmp( argv[ 0 ], "chain-", STRLENOF( "chain-" ) ) == 0 ) {
511 argv[ 0 ] = &argv[ 0 ][ STRLENOF( "chain-" ) ];
513 rc = lback->bi_db_config( be, fname, lineno, argc, argv );
518 be->be_private = private;
527 slap_overinst *on = (slap_overinst *)be->bd_info;
531 if ( lback == NULL ) {
532 lback = backend_info( "ldap" );
534 if ( lback == NULL ) {
539 bd.be_private = NULL;
540 rc = lback->bi_db_init( &bd );
541 on->on_bi.bi_private = bd.be_private;
546 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
552 return overlay_register_control( be, LDAP_CONTROL_X_CHAINING_BEHAVIOR );
554 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
557 ldap_chain_db_destroy(
561 slap_overinst *on = (slap_overinst *) be->bd_info;
562 void *private = be->be_private;
565 be->be_private = on->on_bi.bi_private;
566 rc = lback->bi_db_destroy( be );
567 on->on_bi.bi_private = be->be_private;
568 be->be_private = private;
572 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
574 ldap_chain_parse_ctrl(
584 if ( get_chaining( op ) != SLAP_CONTROL_NONE ) {
585 rs->sr_text = "Chaining behavior control specified multiple times";
586 return LDAP_PROTOCOL_ERROR;
589 if ( op->o_pagedresults != SLAP_CONTROL_NONE ) {
590 rs->sr_text = "Chaining behavior control specified with pagedResults control";
591 return LDAP_PROTOCOL_ERROR;
594 if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) {
595 mode = (SLAP_CH_RESOLVE_DEFAULT|SLAP_CH_CONTINUATION_DEFAULT);
600 /* Parse the control value
601 * ChainingBehavior ::= SEQUENCE {
602 * resolveBehavior Behavior OPTIONAL,
603 * continuationBehavior Behavior OPTIONAL }
605 * Behavior :: = ENUMERATED {
606 * chainingPreferred (0),
607 * chainingRequired (1),
608 * referralsPreferred (2),
609 * referralsRequired (3) }
612 ber = ber_init( &ctrl->ldctl_value );
614 rs->sr_text = "internal error";
618 tag = ber_scanf( ber, "{e" /* } */, &behavior );
619 /* FIXME: since the whole SEQUENCE is optional,
620 * should we accept no enumerations at all? */
621 if ( tag != LBER_ENUMERATED ) {
622 rs->sr_text = "Chaining behavior control: resolveBehavior decoding error";
623 return LDAP_PROTOCOL_ERROR;
626 switch ( behavior ) {
627 case LDAP_CHAINING_PREFERRED:
628 mode = SLAP_CH_RESOLVE_CHAINING_PREFERRED;
631 case LDAP_CHAINING_REQUIRED:
632 mode = SLAP_CH_RESOLVE_CHAINING_REQUIRED;
635 case LDAP_REFERRALS_PREFERRED:
636 mode = SLAP_CH_RESOLVE_REFERRALS_PREFERRED;
639 case LDAP_REFERRALS_REQUIRED:
640 mode = SLAP_CH_RESOLVE_REFERRALS_REQUIRED;
644 rs->sr_text = "Chaining behavior control: unknown resolveBehavior";
645 return LDAP_PROTOCOL_ERROR;
648 tag = ber_peek_tag( ber, &len );
649 if ( tag == LBER_ENUMERATED ) {
650 tag = ber_scanf( ber, "e", &behavior );
651 if ( tag == LBER_ERROR ) {
652 rs->sr_text = "Chaining behavior control: continuationBehavior decoding error";
653 return LDAP_PROTOCOL_ERROR;
657 if ( tag == LBER_DEFAULT ) {
658 mode |= SLAP_CH_CONTINUATION_DEFAULT;
661 switch ( behavior ) {
662 case LDAP_CHAINING_PREFERRED:
663 mode |= SLAP_CH_CONTINUATION_CHAINING_PREFERRED;
666 case LDAP_CHAINING_REQUIRED:
667 mode |= SLAP_CH_CONTINUATION_CHAINING_REQUIRED;
670 case LDAP_REFERRALS_PREFERRED:
671 mode |= SLAP_CH_CONTINUATION_REFERRALS_PREFERRED;
674 case LDAP_REFERRALS_REQUIRED:
675 mode |= SLAP_CH_CONTINUATION_REFERRALS_REQUIRED;
679 rs->sr_text = "Chaining behavior control: unknown continuationBehavior";
680 return LDAP_PROTOCOL_ERROR;
684 if ( ( ber_scanf( ber, /* { */ "}") ) == LBER_ERROR ) {
685 rs->sr_text = "Chaining behavior control: decoding error";
686 return LDAP_PROTOCOL_ERROR;
689 (void) ber_free( ber, 1 );
692 op->o_chaining = mode | ( ctrl->ldctl_iscritical
693 ? SLAP_CONTROL_CRITICAL
694 : SLAP_CONTROL_NONCRITICAL );
698 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
700 static slap_overinst ldapchain;
705 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
708 rc = register_supported_control( LDAP_CONTROL_X_CHAINING_BEHAVIOR,
709 /* SLAP_CTRL_FRONTEND| */ SLAP_CTRL_ACCESS|SLAP_CTRL_HIDE, NULL,
710 ldap_chain_parse_ctrl, &sc_chainingBehavior );
711 if ( rc != LDAP_SUCCESS ) {
712 fprintf( stderr, "Failed to register chaining behavior control: %d\n", rc );
715 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
717 ldapchain.on_bi.bi_type = "chain";
718 ldapchain.on_bi.bi_db_init = ldap_chain_db_init;
719 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
720 ldapchain.on_bi.bi_db_open = ldap_chain_db_open;
721 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
722 ldapchain.on_bi.bi_db_config = ldap_chain_db_config;
723 ldapchain.on_bi.bi_db_destroy = ldap_chain_db_destroy;
725 /* ... otherwise the underlying backend's function would be called,
726 * likely passing an invalid entry; on the contrary, the requested
727 * operational attributes should have been returned while chasing
728 * the referrals. This all in all is a bit messy, because part
729 * of the operational attributes are generated by they backend;
730 * part by the frontend; back-ldap should receive all the available
731 * ones from the remote server, but then, on it own, it strips those
732 * it assumes will be (re)generated by the frontend (e.g.
733 * subschemaSubentry.) */
734 ldapchain.on_bi.bi_operational = ldap_chain_operational;
736 ldapchain.on_response = ldap_chain_response;
738 return overlay_register( &ldapchain );