]> git.sur5r.net Git - openldap/blob - servers/slapd/back-ldap/chain.c
a772b5209c70e5884989b60997bfc94d2b7cdce8
[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 #if 0
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
321                          * ... */
322                         assert( SLAP_DBFLAGS( op->o_bd ) & SLAP_DBFLAG_GLOBAL_OVERLAY );
323 #endif
324
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
330                          * if required. */
331                         rs->sr_err = slap_mods2entry( op->ora_modlist,
332                                         &op->ora_e, 0, 1,
333                                         &rs->sr_text, textbuf, textlen );
334                         if ( rs->sr_err != LDAP_SUCCESS ) {
335                                 send_ldap_result( op, rs );
336                                 rc = 1;
337                                 break;
338                         }
339                 }
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;
344                 }
345                 break;
346                 }
347         case LDAP_REQ_DELETE:
348                 rc = ldap_chain_op( op, rs, lback->bi_op_delete, ref );
349                 break;
350         case LDAP_REQ_MODRDN:
351                 rc = ldap_chain_op( op, rs, lback->bi_op_modrdn, ref );
352                 break;
353         case LDAP_REQ_MODIFY:
354                 rc = ldap_chain_op( op, rs, lback->bi_op_modify, ref );
355                 break;
356         case LDAP_REQ_COMPARE:
357                 rc = ldap_chain_op( op, rs, lback->bi_op_compare, ref );
358                 break;
359         case LDAP_REQ_SEARCH:
360                 if ( rs->sr_type == REP_SEARCHREF ) {
361                         struct berval   *curr = ref,
362                                         odn = op->o_req_dn,
363                                         ondn = op->o_req_ndn;
364
365                         rs->sr_type = REP_SEARCH;
366
367                         sc2.sc_response = ldap_chain_cb_search_response;
368
369                         li = *lip;
370                         li.url = NULL;
371                         op->o_bd->be_private = &li;
372                         
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;
379
380                         /* copy the private info because we need to modify it */
381                         for ( ; !BER_BVISNULL( &curr[0] ); curr++ ) {
382                                 LDAPURLDesc     *srv;
383                                 char            *save_dn;
384
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 ) {
389                                         /* try next */
390                                         rs->sr_err = LDAP_OTHER;
391                                         continue;
392                                 }
393
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;
398                                 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,
403                                                         op->o_tmpmemctx );
404                                         ber_dupbv_x( &op->o_req_ndn, &op->o_req_dn,
405                                                         op->o_tmpmemctx );
406                                 }
407
408                                 srv->lud_dn = save_dn;
409                                 ldap_free_urldesc( srv );
410
411                                 if ( li.url == NULL ) {
412                                         /* try next */
413                                         rs->sr_err = LDAP_OTHER;
414                                         continue;
415                                 }
416
417
418                                 /* FIXME: should we also copy filter and scope?
419                                  * according to RFC3296, no */
420                                 rc = lback->bi_op_search( op, rs );
421
422                                 ldap_memfree( li.url );
423                                 li.url = NULL;
424
425                                 op->o_tmpfree( op->o_req_dn.bv_val,
426                                                 op->o_tmpmemctx );
427                                 op->o_tmpfree( op->o_req_ndn.bv_val,
428                                                 op->o_tmpmemctx );
429
430                                 if ( rc == LDAP_SUCCESS && rs->sr_err == LDAP_SUCCESS ) {
431                                         break;
432                                 }
433                         }
434
435                         op->o_req_dn = odn;
436                         op->o_req_ndn = ondn;
437                         rs->sr_type = REP_SEARCHREF;
438                         rs->sr_entry = NULL;
439
440                         if ( rc != LDAP_SUCCESS ) {
441                                 /* couldn't chase any of the referrals */
442                                 rc = SLAP_CB_CONTINUE;
443                         }
444                         
445                 } else {
446                         rc = ldap_chain_op( op, rs, lback->bi_op_search, ref );
447                 }
448                 break;
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
453                  * to send it... */
454                 if ( rc != SLAPD_ABANDON ) {
455                         send_ldap_extended( op, rs );
456                         rc = LDAP_SUCCESS;
457                 }
458                 break;
459         default:
460                 rc = SLAP_CB_CONTINUE;
461                 break;
462         }
463
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" );
470                         break;
471
472                 default:
473                         rc = SLAP_CB_CONTINUE;
474                         rs->sr_err = sr_err;
475                         rs->sr_type = sr_type;
476                         break;
477                 }
478                 goto dont_chain;
479         }
480 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
481
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 );
486         }
487
488 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
489 dont_chain:;
490 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
491         op->o_do_not_cache = cache;
492         op->o_bd->be_private = private;
493         op->o_callback = sc;
494         op->o_ndn = ndn;
495         rs->sr_ref = ref;
496
497         return rc;
498 }
499
500 static int
501 ldap_chain_db_config(
502         BackendDB       *be,
503         const char      *fname,
504         int             lineno,
505         int             argc,
506         char    **argv
507 )
508 {
509         slap_overinst   *on = (slap_overinst *) be->bd_info;
510         void            *private = be->be_private;
511         char            *argv0 = NULL;
512         int             rc;
513
514         be->be_private = on->on_bi.bi_private;
515         if ( strncasecmp( argv[ 0 ], "chain-", STRLENOF( "chain-" ) ) == 0 ) {
516                 argv0 = argv[ 0 ];
517                 argv[ 0 ] = &argv[ 0 ][ STRLENOF( "chain-" ) ];
518         }
519         rc = lback->bi_db_config( be, fname, lineno, argc, argv );
520         if ( argv0 ) {
521                 argv[ 0 ] = argv0;
522         }
523         
524         be->be_private = private;
525         return rc;
526 }
527
528 static int
529 ldap_chain_db_init(
530         BackendDB *be
531 )
532 {
533         slap_overinst   *on = (slap_overinst *)be->bd_info;
534         int             rc;
535         BackendDB       bd = *be;
536
537         if ( lback == NULL ) {
538                 lback = backend_info( "ldap" );
539
540                 if ( lback == NULL ) {
541                         return -1;
542                 }
543         }
544
545         bd.be_private = NULL;
546         rc = lback->bi_db_init( &bd );
547         on->on_bi.bi_private = bd.be_private;
548
549         return rc;
550 }
551
552 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
553 static int
554 ldap_chain_db_open(
555         BackendDB *be
556 )
557 {
558         return overlay_register_control( be, LDAP_CONTROL_X_CHAINING_BEHAVIOR );
559 }
560 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
561
562 static int
563 ldap_chain_db_destroy(
564         BackendDB *be
565 )
566 {
567         slap_overinst *on = (slap_overinst *) be->bd_info;
568         void *private = be->be_private;
569         int rc;
570
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;
575         return rc;
576 }
577
578 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
579 static int
580 ldap_chain_parse_ctrl(
581         Operation       *op,
582         SlapReply       *rs,
583         LDAPControl     *ctrl )
584 {
585         ber_tag_t       tag;
586         BerElement      *ber;
587         ber_int_t       mode,
588                         behavior;
589
590         if ( get_chaining( op ) != SLAP_CONTROL_NONE ) {
591                 rs->sr_text = "Chaining behavior control specified multiple times";
592                 return LDAP_PROTOCOL_ERROR;
593         }
594
595         if ( op->o_pagedresults != SLAP_CONTROL_NONE ) {
596                 rs->sr_text = "Chaining behavior control specified with pagedResults control";
597                 return LDAP_PROTOCOL_ERROR;
598         }
599
600         if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) {
601                 mode = (SLAP_CH_RESOLVE_DEFAULT|SLAP_CH_CONTINUATION_DEFAULT);
602
603         } else {
604                 ber_len_t       len;
605
606                 /* Parse the control value
607                  *      ChainingBehavior ::= SEQUENCE { 
608                  *           resolveBehavior         Behavior OPTIONAL, 
609                  *           continuationBehavior    Behavior OPTIONAL } 
610                  *                             
611                  *      Behavior :: = ENUMERATED { 
612                  *           chainingPreferred       (0), 
613                  *           chainingRequired        (1), 
614                  *           referralsPreferred      (2), 
615                  *           referralsRequired       (3) } 
616                  */
617
618                 ber = ber_init( &ctrl->ldctl_value );
619                 if( ber == NULL ) {
620                         rs->sr_text = "internal error";
621                         return LDAP_OTHER;
622                 }
623
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;
630                 }
631
632                 switch ( behavior ) {
633                 case LDAP_CHAINING_PREFERRED:
634                         mode = SLAP_CH_RESOLVE_CHAINING_PREFERRED;
635                         break;
636
637                 case LDAP_CHAINING_REQUIRED:
638                         mode = SLAP_CH_RESOLVE_CHAINING_REQUIRED;
639                         break;
640
641                 case LDAP_REFERRALS_PREFERRED:
642                         mode = SLAP_CH_RESOLVE_REFERRALS_PREFERRED;
643                         break;
644
645                 case LDAP_REFERRALS_REQUIRED:
646                         mode = SLAP_CH_RESOLVE_REFERRALS_REQUIRED;
647                         break;
648
649                 default:
650                         rs->sr_text = "Chaining behavior control: unknown resolveBehavior";
651                         return LDAP_PROTOCOL_ERROR;
652                 }
653
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;
660                         }
661                 }
662
663                 if ( tag == LBER_DEFAULT ) {
664                         mode |= SLAP_CH_CONTINUATION_DEFAULT;
665
666                 } else {
667                         switch ( behavior ) {
668                         case LDAP_CHAINING_PREFERRED:
669                                 mode |= SLAP_CH_CONTINUATION_CHAINING_PREFERRED;
670                                 break;
671
672                         case LDAP_CHAINING_REQUIRED:
673                                 mode |= SLAP_CH_CONTINUATION_CHAINING_REQUIRED;
674                                 break;
675
676                         case LDAP_REFERRALS_PREFERRED:
677                                 mode |= SLAP_CH_CONTINUATION_REFERRALS_PREFERRED;
678                                 break;
679
680                         case LDAP_REFERRALS_REQUIRED:
681                                 mode |= SLAP_CH_CONTINUATION_REFERRALS_REQUIRED;
682                                 break;
683
684                         default:
685                                 rs->sr_text = "Chaining behavior control: unknown continuationBehavior";
686                                 return LDAP_PROTOCOL_ERROR;
687                         }
688                 }
689
690                 if ( ( ber_scanf( ber, /* { */ "}") ) == LBER_ERROR ) {
691                         rs->sr_text = "Chaining behavior control: decoding error";
692                         return LDAP_PROTOCOL_ERROR;
693                 }
694
695                 (void) ber_free( ber, 1 );
696         }
697
698         op->o_chaining = mode | ( ctrl->ldctl_iscritical
699                         ? SLAP_CONTROL_CRITICAL
700                         : SLAP_CONTROL_NONCRITICAL );
701
702         return LDAP_SUCCESS;
703 }
704 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
705
706 static slap_overinst ldapchain;
707
708 int
709 chain_init( void )
710 {
711 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
712         int     rc;
713
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 );
719                 return rc;
720         }
721 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
722
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;
730         
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;
741         
742         ldapchain.on_response = ldap_chain_response;
743
744         return overlay_register( &ldapchain );
745 }
746