]> git.sur5r.net Git - openldap/blob - servers/slapd/back-ldap/chain.c
cleanup previous commit
[openldap] / servers / slapd / back-ldap / chain.c
1 /* chain.c - chain LDAP operations */
2 /* $OpenLDAP$ */
3 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
4  *
5  * Copyright 2003-2005 The OpenLDAP Foundation.
6  * Portions Copyright 2003 Howard Chu.
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted only as authorized by the OpenLDAP
11  * Public License.
12  *
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>.
16  */
17 /* ACKNOWLEDGEMENTS:
18  * This work was initially developed by the Howard Chu for inclusion
19  * in OpenLDAP Software.
20  */
21
22 #include "portable.h"
23
24 #include <stdio.h>
25
26 #include <ac/string.h>
27 #include <ac/socket.h>
28
29 #include "slap.h"
30 #include "back-ldap.h"
31
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
47
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 */
54
55 static int              sc_chainingBehavior;
56 static BackendInfo      *lback;
57
58 static int
59 ldap_chain_operational( Operation *op, SlapReply *rs )
60 {
61         /* Trap entries generated by back-ldap.
62          * 
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 ) {
71                 return 0;
72         }
73
74         return SLAP_CB_CONTINUE;
75 }
76
77 /*
78  * Search specific response that strips entryDN from entries
79  */
80 static int
81 ldap_chain_cb_search_response( Operation *op, SlapReply *rs )
82 {
83         assert( op->o_tag == LDAP_REQ_SEARCH );
84
85         if ( rs->sr_type == REP_SEARCH ) {
86                 Attribute       **ap = &rs->sr_entry->e_attrs;
87
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 )
93                         {
94                                 Attribute *a = *ap;
95
96                                 *ap = (*ap)->a_next;
97                                 attr_free( a );
98
99                                 /* there SHOULD be one only! */
100                                 break;
101                         }
102                 }
103                 
104                 return SLAP_CB_CONTINUE;
105
106         } else if ( rs->sr_type == REP_RESULT ) {
107                 /* back-ldap tried to send result */
108                 op->o_callback->sc_private = (void *)(1);
109         }
110
111         return 0;
112 }
113
114 /*
115  * Dummy response that simply traces if back-ldap tried to send 
116  * anything to the client
117  */
118 static int
119 ldap_chain_cb_response( Operation *op, SlapReply *rs )
120 {
121         if ( rs->sr_type == REP_RESULT ) {
122                 op->o_callback->sc_private = (void *)(1);
123
124         } else if ( op->o_tag == LDAP_REQ_SEARCH && rs->sr_type == REP_SEARCH )
125         {
126                 /* strip the entryDN attribute, but keep returning results */
127                 (void)ldap_chain_cb_search_response( op, rs );
128         }
129
130         return SLAP_CB_CONTINUE;
131 }
132
133 static int
134 ldap_chain_op(
135         Operation       *op,
136         SlapReply       *rs,
137         int             ( *op_f )( Operation *op, SlapReply *rs ), 
138         BerVarray       ref )
139 {
140         slap_overinst   *on = (slap_overinst *) op->o_bd->bd_info;
141         struct ldapinfo li, *lip = (struct ldapinfo *)on->on_bi.bi_private;
142
143         /* NOTE: returned if ref is empty... */
144         int             rc = LDAP_OTHER;
145
146         if ( lip->url != NULL ) {
147                 op->o_bd->be_private = on->on_bi.bi_private;
148                 return ( *op_f )( op, rs );
149         }
150
151         li = *lip;
152         op->o_bd->be_private = &li;
153
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;
160
161         for ( ; !BER_BVISNULL( ref ); ref++ ) {
162                 LDAPURLDesc     *srv;
163                 char            *save_dn;
164                         
165                 /* We're setting the URI of the first referral;
166                  * what if there are more?
167
168 Document: draft-ietf-ldapbis-protocol-27.txt
169
170 4.1.10. Referral 
171    ...
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. 
176
177                  * so we actually need to follow exactly one,
178                  * and we can assume any is fine.
179                  */
180         
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 ) {
185                         /* try next */
186                         rc = LDAP_OTHER;
187                         continue;
188                 }
189
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;
194                 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 );
199
200                 if ( li.url == NULL ) {
201                         /* try next */
202                         rc = LDAP_OTHER;
203                         continue;
204                 }
205
206                 rc = ( *op_f )( op, rs );
207
208                 ldap_memfree( li.url );
209                 li.url = NULL;
210                 
211                 if ( rc == LDAP_SUCCESS && rs->sr_err == LDAP_SUCCESS ) {
212                         break;
213                 }
214         }
215
216         return rc;
217 }
218
219 static int
220 ldap_chain_response( Operation *op, SlapReply *rs )
221 {
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,
225                         sc2 = { 0 };
226         int             rc = 0;
227         int             cache = op->o_do_not_cache;
228         BerVarray       ref;
229         struct berval   ndn = op->o_ndn;
230
231         struct ldapinfo li, *lip = (struct ldapinfo *)on->on_bi.bi_private;
232
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 */
239
240         if ( rs->sr_err != LDAP_REFERRAL && rs->sr_type != REP_SEARCHREF ) {
241                 return SLAP_CB_CONTINUE;
242         }
243
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;
250
251                 default:
252                         chain_mask = SLAP_CH_RESOLVE_MASK;
253                         chain_shift = SLAP_CH_RESOLVE_SHIFT;
254                         break;
255                 }
256
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;
262
263                 default:
264                         chain_mask = SLAP_CH_CONTINUATION_MASK;
265                         chain_shift = SLAP_CH_CONTINUATION_SHIFT;
266                         break;
267                 }
268         }
269 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
270
271         /*
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://)
277          *   e) what ssf
278          */
279
280         ref = rs->sr_ref;
281         rs->sr_ref = NULL;
282
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;
286
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).
291          *
292          * FIXME: the idassert-authcDN is one, will it be fine regardless
293          * of the URI we obtain from the referral?
294          */
295
296         switch ( op->o_tag ) {
297         case LDAP_REQ_BIND: {
298                 struct berval   rndn = op->o_req_ndn;
299                 Connection      *conn = op->o_conn;
300
301                 /* FIXME: can we really get a referral for binds? */
302                 op->o_req_ndn = slap_empty_bv;
303                 op->o_conn = NULL;
304                 rc = ldap_chain_op( op, rs, lback->bi_op_bind, ref );
305                 op->o_req_ndn = rndn;
306                 op->o_conn = conn;
307                 }
308                 break;
309         case LDAP_REQ_ADD:
310                 {
311                 int             cleanup_attrs = 0;
312
313                 if ( op->ora_e->e_attrs == NULL ) {
314                         char            textbuf[ SLAP_TEXT_BUFLEN ];
315                         size_t          textlen = sizeof( textbuf );
316
317                         /* global overlay: create entry */
318                         /* NOTE: this is a hack to use the chain overlay
319                          * as global.  I expect to be able to remove this
320                          * soon by using slap_mods2entry() earlier in
321                          * do_add(), adding the operational attrs later
322                          * if required. */
323                         rs->sr_err = slap_mods2entry( op->ora_modlist,
324                                         &op->ora_e, 0, 1,
325                                         &rs->sr_text, textbuf, textlen );
326                         if ( rs->sr_err != LDAP_SUCCESS ) {
327                                 send_ldap_result( op, rs );
328                                 rc = 1;
329                                 break;
330                         }
331                 }
332                 rc = ldap_chain_op( op, rs, lback->bi_op_add, ref );
333                 if ( cleanup_attrs ) {
334                         attrs_free( op->ora_e->e_attrs );
335                         op->ora_e->e_attrs = NULL;
336                 }
337                 break;
338                 }
339         case LDAP_REQ_DELETE:
340                 rc = ldap_chain_op( op, rs, lback->bi_op_delete, ref );
341                 break;
342         case LDAP_REQ_MODRDN:
343                 rc = ldap_chain_op( op, rs, lback->bi_op_modrdn, ref );
344                 break;
345         case LDAP_REQ_MODIFY:
346                 rc = ldap_chain_op( op, rs, lback->bi_op_modify, ref );
347                 break;
348         case LDAP_REQ_COMPARE:
349                 rc = ldap_chain_op( op, rs, lback->bi_op_compare, ref );
350                 break;
351         case LDAP_REQ_SEARCH:
352                 if ( rs->sr_type == REP_SEARCHREF ) {
353                         struct berval   *curr = ref,
354                                         odn = op->o_req_dn,
355                                         ondn = op->o_req_ndn;
356
357                         rs->sr_type = REP_SEARCH;
358
359                         sc2.sc_response = ldap_chain_cb_search_response;
360
361                         li = *lip;
362                         li.url = NULL;
363                         op->o_bd->be_private = &li;
364                         
365                         /* if we parse the URI then by no means 
366                          * we can cache stuff or reuse connections, 
367                          * because in back-ldap there's no caching
368                          * based on the URI value, which is supposed
369                          * to be set once for all (correct?) */
370                         op->o_do_not_cache = 1;
371
372                         /* copy the private info because we need to modify it */
373                         for ( ; !BER_BVISNULL( &curr[0] ); curr++ ) {
374                                 LDAPURLDesc     *srv;
375                                 char            *save_dn;
376
377                                 /* parse reference and use
378                                  * proto://[host][:port]/ only */
379                                 rc = ldap_url_parse_ext( curr[0].bv_val, &srv );
380                                 if ( rc != LDAP_URL_SUCCESS ) {
381                                         /* try next */
382                                         rs->sr_err = LDAP_OTHER;
383                                         continue;
384                                 }
385
386                                 /* remove DN essentially because later on 
387                                  * ldap_initialize() will parse the URL 
388                                  * as a comma-separated URL list */
389                                 save_dn = srv->lud_dn;
390                                 srv->lud_dn = "";
391                                 srv->lud_scope = LDAP_SCOPE_DEFAULT;
392                                 li.url = ldap_url_desc2str( srv );
393                                 if ( li.url != NULL ) {
394                                         ber_str2bv_x( save_dn, 0, 1, &op->o_req_dn,
395                                                         op->o_tmpmemctx );
396                                         ber_dupbv_x( &op->o_req_ndn, &op->o_req_dn,
397                                                         op->o_tmpmemctx );
398                                 }
399
400                                 srv->lud_dn = save_dn;
401                                 ldap_free_urldesc( srv );
402
403                                 if ( li.url == NULL ) {
404                                         /* try next */
405                                         rs->sr_err = LDAP_OTHER;
406                                         continue;
407                                 }
408
409
410                                 /* FIXME: should we also copy filter and scope?
411                                  * according to RFC3296, no */
412                                 rc = lback->bi_op_search( op, rs );
413
414                                 ldap_memfree( li.url );
415                                 li.url = NULL;
416
417                                 op->o_tmpfree( op->o_req_dn.bv_val,
418                                                 op->o_tmpmemctx );
419                                 op->o_tmpfree( op->o_req_ndn.bv_val,
420                                                 op->o_tmpmemctx );
421
422                                 if ( rc == LDAP_SUCCESS && rs->sr_err == LDAP_SUCCESS ) {
423                                         break;
424                                 }
425                         }
426
427                         op->o_req_dn = odn;
428                         op->o_req_ndn = ondn;
429                         rs->sr_type = REP_SEARCHREF;
430                         rs->sr_entry = NULL;
431
432                         if ( rc != LDAP_SUCCESS ) {
433                                 /* couldn't chase any of the referrals */
434                                 rc = SLAP_CB_CONTINUE;
435                         }
436                         
437                 } else {
438                         rc = ldap_chain_op( op, rs, lback->bi_op_search, ref );
439                 }
440                 break;
441         case LDAP_REQ_EXTENDED:
442                 rc = ldap_chain_op( op, rs, lback->bi_extended, ref );
443                 /* FIXME: ldap_back_extended() by design 
444                  * doesn't send result; frontend is expected
445                  * to send it... */
446                 if ( rc != SLAPD_ABANDON ) {
447                         send_ldap_extended( op, rs );
448                         rc = LDAP_SUCCESS;
449                 }
450                 break;
451         default:
452                 rc = SLAP_CB_CONTINUE;
453                 break;
454         }
455
456 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
457         if ( rc != LDAP_SUCCESS ) {
458                 switch ( ( get_chainingBehavior( op ) & chain_mask ) >> chain_shift ) {
459                 case LDAP_CHAINING_REQUIRED:
460                         op->o_callback = NULL;
461                         send_ldap_error( op, rs, LDAP_CANNOT_CHAIN, "operation cannot be completed without chaining" );
462                         break;
463
464                 default:
465                         rc = SLAP_CB_CONTINUE;
466                         rs->sr_err = sr_err;
467                         rs->sr_type = sr_type;
468                         break;
469                 }
470                 goto dont_chain;
471         }
472 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
473
474         if ( sc2.sc_private == NULL ) {
475                 op->o_callback = NULL;
476                 rc = rs->sr_err = slap_map_api2result( rs );
477                 send_ldap_result( op, rs );
478         }
479
480 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
481 dont_chain:;
482 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
483         op->o_do_not_cache = cache;
484         op->o_bd->be_private = private;
485         op->o_callback = sc;
486         op->o_ndn = ndn;
487         rs->sr_ref = ref;
488
489         return rc;
490 }
491
492 static int
493 ldap_chain_db_config(
494         BackendDB       *be,
495         const char      *fname,
496         int             lineno,
497         int             argc,
498         char    **argv
499 )
500 {
501         slap_overinst   *on = (slap_overinst *) be->bd_info;
502         void            *private = be->be_private;
503         char            *argv0 = NULL;
504         int             rc;
505
506         be->be_private = on->on_bi.bi_private;
507         if ( strncasecmp( argv[ 0 ], "chain-", sizeof( "chain-" ) - 1 ) == 0 ) {
508                 argv0 = argv[ 0 ];
509                 argv[ 0 ] = &argv[ 0 ][ sizeof( "chain-" ) - 1 ];
510         }
511         rc = lback->bi_db_config( be, fname, lineno, argc, argv );
512         if ( argv0 ) {
513                 argv[ 0 ] = argv0;
514         }
515         
516         be->be_private = private;
517         return rc;
518 }
519
520 static int
521 ldap_chain_db_init(
522         BackendDB *be
523 )
524 {
525         slap_overinst   *on = (slap_overinst *)be->bd_info;
526         int             rc;
527         BackendDB       bd = *be;
528
529         if ( lback == NULL ) {
530                 lback = backend_info( "ldap" );
531
532                 if ( lback == NULL ) {
533                         return -1;
534                 }
535         }
536
537         bd.be_private = NULL;
538         rc = lback->bi_db_init( &bd );
539         on->on_bi.bi_private = bd.be_private;
540
541         return rc;
542 }
543
544 static int
545 ldap_chain_db_destroy(
546         BackendDB *be
547 )
548 {
549         slap_overinst *on = (slap_overinst *) be->bd_info;
550         void *private = be->be_private;
551         int rc;
552
553         be->be_private = on->on_bi.bi_private;
554         rc = lback->bi_db_destroy( be );
555         on->on_bi.bi_private = be->be_private;
556         be->be_private = private;
557         return rc;
558 }
559
560 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
561 static int
562 ldap_chain_parse_ctrl(
563         Operation       *op,
564         SlapReply       *rs,
565         LDAPControl     *ctrl )
566 {
567         ber_tag_t       tag;
568         BerElement      *ber;
569         ber_int_t       mode,
570                         behavior;
571
572         if ( get_chaining( op ) != SLAP_CONTROL_NONE ) {
573                 rs->sr_text = "Chaining behavior control specified multiple times";
574                 return LDAP_PROTOCOL_ERROR;
575         }
576
577         if ( op->o_pagedresults != SLAP_CONTROL_NONE ) {
578                 rs->sr_text = "Chaining behavior control specified with pagedResults control";
579                 return LDAP_PROTOCOL_ERROR;
580         }
581
582         if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) {
583                 mode = (SLAP_CH_RESOLVE_DEFAULT|SLAP_CH_CONTINUATION_DEFAULT);
584
585         } else {
586                 ber_len_t       len;
587
588                 /* Parse the control value
589                  *      ChainingBehavior ::= SEQUENCE { 
590                  *           resolveBehavior         Behavior OPTIONAL, 
591                  *           continuationBehavior    Behavior OPTIONAL } 
592                  *                             
593                  *      Behavior :: = ENUMERATED { 
594                  *           chainingPreferred       (0), 
595                  *           chainingRequired        (1), 
596                  *           referralsPreferred      (2), 
597                  *           referralsRequired       (3) } 
598                  */
599
600                 ber = ber_init( &ctrl->ldctl_value );
601                 if( ber == NULL ) {
602                         rs->sr_text = "internal error";
603                         return LDAP_OTHER;
604                 }
605
606                 tag = ber_scanf( ber, "{e" /* } */, &behavior );
607                 /* FIXME: since the whole SEQUENCE is optional,
608                  * should we accept no enumerations at all? */
609                 if ( tag != LBER_ENUMERATED ) {
610                         rs->sr_text = "Chaining behavior control: resolveBehavior decoding error";
611                         return LDAP_PROTOCOL_ERROR;
612                 }
613
614                 switch ( behavior ) {
615                 case LDAP_CHAINING_PREFERRED:
616                         mode = SLAP_CH_RESOLVE_CHAINING_PREFERRED;
617                         break;
618
619                 case LDAP_CHAINING_REQUIRED:
620                         mode = SLAP_CH_RESOLVE_CHAINING_REQUIRED;
621                         break;
622
623                 case LDAP_REFERRALS_PREFERRED:
624                         mode = SLAP_CH_RESOLVE_REFERRALS_PREFERRED;
625                         break;
626
627                 case LDAP_REFERRALS_REQUIRED:
628                         mode = SLAP_CH_RESOLVE_REFERRALS_REQUIRED;
629                         break;
630
631                 default:
632                         rs->sr_text = "Chaining behavior control: unknown resolveBehavior";
633                         return LDAP_PROTOCOL_ERROR;
634                 }
635
636                 tag = ber_peek_tag( ber, &len );
637                 if ( tag == LBER_ENUMERATED ) {
638                         tag = ber_scanf( ber, "e", &behavior );
639                         if ( tag == LBER_ERROR ) {
640                                 rs->sr_text = "Chaining behavior control: continuationBehavior decoding error";
641                                 return LDAP_PROTOCOL_ERROR;
642                         }
643                 }
644
645                 if ( tag == LBER_DEFAULT ) {
646                         mode |= SLAP_CH_CONTINUATION_DEFAULT;
647
648                 } else {
649                         switch ( behavior ) {
650                         case LDAP_CHAINING_PREFERRED:
651                                 mode |= SLAP_CH_CONTINUATION_CHAINING_PREFERRED;
652                                 break;
653
654                         case LDAP_CHAINING_REQUIRED:
655                                 mode |= SLAP_CH_CONTINUATION_CHAINING_REQUIRED;
656                                 break;
657
658                         case LDAP_REFERRALS_PREFERRED:
659                                 mode |= SLAP_CH_CONTINUATION_REFERRALS_PREFERRED;
660                                 break;
661
662                         case LDAP_REFERRALS_REQUIRED:
663                                 mode |= SLAP_CH_CONTINUATION_REFERRALS_REQUIRED;
664                                 break;
665
666                         default:
667                                 rs->sr_text = "Chaining behavior control: unknown continuationBehavior";
668                                 return LDAP_PROTOCOL_ERROR;
669                         }
670                 }
671
672                 if ( ( ber_scanf( ber, /* { */ "}") ) == LBER_ERROR ) {
673                         rs->sr_text = "Chaining behavior control: decoding error";
674                         return LDAP_PROTOCOL_ERROR;
675                 }
676
677                 (void) ber_free( ber, 1 );
678         }
679
680         op->o_chaining = mode | ( ctrl->ldctl_iscritical
681                         ? SLAP_CONTROL_CRITICAL
682                         : SLAP_CONTROL_NONCRITICAL );
683
684         return LDAP_SUCCESS;
685 }
686 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
687
688 static slap_overinst ldapchain;
689
690 int
691 chain_init( void )
692 {
693 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
694         int     rc;
695
696         rc = register_supported_control( LDAP_CONTROL_X_CHAINING_BEHAVIOR,
697                         SLAP_CTRL_ACCESS|SLAP_CTRL_HIDE, NULL,
698                         ldap_chain_parse_ctrl, &sc_chainingBehavior );
699         if ( rc != LDAP_SUCCESS ) {
700                 fprintf( stderr, "Failed to register chaining behavior control: %d\n", rc );
701                 return rc;
702         }
703 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
704
705         ldapchain.on_bi.bi_type = "chain";
706         ldapchain.on_bi.bi_db_init = ldap_chain_db_init;
707         ldapchain.on_bi.bi_db_config = ldap_chain_db_config;
708         ldapchain.on_bi.bi_db_destroy = ldap_chain_db_destroy;
709         
710         /* ... otherwise the underlying backend's function would be called,
711          * likely passing an invalid entry; on the contrary, the requested
712          * operational attributes should have been returned while chasing
713          * the referrals.  This all in all is a bit messy, because part
714          * of the operational attributes are generated by they backend;
715          * part by the frontend; back-ldap should receive all the available
716          * ones from the remote server, but then, on it own, it strips those
717          * it assumes will be (re)generated by the frontend (e.g.
718          * subschemaSubentry.) */
719         ldapchain.on_bi.bi_operational = ldap_chain_operational;
720         
721         ldapchain.on_response = ldap_chain_response;
722
723         return overlay_register( &ldapchain );
724 }
725