]> git.sur5r.net Git - openldap/blob - servers/slapd/back-ldap/chain.c
make referrals chasing optional (default is to chase them)
[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 #define LDAP_CH_NONE                    ((void *)(0))
56 #define LDAP_CH_RES                     ((void *)(1))
57 #define LDAP_CH_ERR                     ((void *)(2))
58
59 static int              sc_chainingBehavior;
60 static BackendInfo      *lback;
61
62 static int
63 ldap_chain_operational( Operation *op, SlapReply *rs )
64 {
65         /* Trap entries generated by back-ldap.
66          * 
67          * FIXME: we need a better way to recognize them; a cleaner
68          * solution would be to be able to intercept the response
69          * of be_operational(), so that we can divert only those
70          * calls that fail because operational attributes were
71          * requested for entries that do not belong to the underlying
72          * database.  This fix is likely to intercept also entries
73          * generated by back-perl and so. */
74         if ( rs->sr_entry->e_private == NULL ) {
75                 return 0;
76         }
77
78         return SLAP_CB_CONTINUE;
79 }
80
81 /*
82  * Search specific response that strips entryDN from entries
83  */
84 static int
85 ldap_chain_cb_search_response( Operation *op, SlapReply *rs )
86 {
87         assert( op->o_tag == LDAP_REQ_SEARCH );
88
89         /* if in error, don't proceed any further */
90         if ( op->o_callback->sc_private == LDAP_CH_ERR ) {
91                 return 0;
92         }
93
94         if ( rs->sr_type == REP_SEARCH ) {
95                 Attribute       **ap = &rs->sr_entry->e_attrs;
96
97                 for ( ; *ap != NULL; ap = &(*ap)->a_next ) {
98                         /* will be generated later by frontend
99                          * (a cleaner solution would be that
100                          * the frontend checks if it already exists */
101                         if ( ad_cmp( (*ap)->a_desc, slap_schema.si_ad_entryDN ) == 0 )
102                         {
103                                 Attribute *a = *ap;
104
105                                 *ap = (*ap)->a_next;
106                                 attr_free( a );
107
108                                 /* there SHOULD be one only! */
109                                 break;
110                         }
111                 }
112                 
113                 return SLAP_CB_CONTINUE;
114
115         } else if ( rs->sr_type == REP_SEARCHREF ) {
116                 /* if we get it here, it means the library was unable
117                  * to chase the referral... */
118
119 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
120                 if ( get_chaining( op ) > SLAP_CONTROL_IGNORED ) {
121                         switch ( get_continuationBehavior( op ) ) {
122                         case SLAP_CH_RESOLVE_CHAINING_REQUIRED:
123                                 op->o_callback->sc_private = LDAP_CH_ERR;
124                                 return -1;
125
126                         default:
127                                 break;
128                         }
129                 }
130 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
131                 return SLAP_CB_CONTINUE;
132
133         } else if ( rs->sr_type == REP_RESULT ) {
134                 /* back-ldap tried to send result */
135                 op->o_callback->sc_private = LDAP_CH_RES;
136         }
137
138         return 0;
139 }
140
141 /*
142  * Dummy response that simply traces if back-ldap tried to send 
143  * anything to the client
144  */
145 static int
146 ldap_chain_cb_response( Operation *op, SlapReply *rs )
147 {
148         /* if in error, don't proceed any further */
149         if ( op->o_callback->sc_private == LDAP_CH_ERR ) {
150                 return 0;
151         }
152
153         if ( rs->sr_type == REP_RESULT ) {
154                 op->o_callback->sc_private = LDAP_CH_RES;
155
156         } else if ( op->o_tag == LDAP_REQ_SEARCH && rs->sr_type == REP_SEARCH )
157         {
158                 /* strip the entryDN attribute, but keep returning results */
159                 (void)ldap_chain_cb_search_response( op, rs );
160         }
161
162         return SLAP_CB_CONTINUE;
163 }
164
165 static int
166 ldap_chain_op(
167         Operation       *op,
168         SlapReply       *rs,
169         int             ( *op_f )( Operation *op, SlapReply *rs ), 
170         BerVarray       ref )
171 {
172         slap_overinst   *on = (slap_overinst *) op->o_bd->bd_info;
173         struct ldapinfo li, *lip = (struct ldapinfo *)on->on_bi.bi_private;
174
175         /* NOTE: returned if ref is empty... */
176         int             rc = LDAP_OTHER;
177
178         if ( lip->url != NULL ) {
179                 op->o_bd->be_private = on->on_bi.bi_private;
180                 return ( *op_f )( op, rs );
181         }
182
183         li = *lip;
184         op->o_bd->be_private = &li;
185
186         /* if we parse the URI then by no means 
187          * we can cache stuff or reuse connections, 
188          * because in back-ldap there's no caching
189          * based on the URI value, which is supposed
190          * to be set once for all (correct?) */
191         op->o_do_not_cache = 1;
192
193         for ( ; !BER_BVISNULL( ref ); ref++ ) {
194                 LDAPURLDesc     *srv;
195                 char            *save_dn;
196                         
197                 /* We're setting the URI of the first referral;
198                  * what if there are more?
199
200 Document: draft-ietf-ldapbis-protocol-27.txt
201
202 4.1.10. Referral 
203    ...
204    If the client wishes to progress the operation, it MUST follow the 
205    referral by contacting one of the supported services. If multiple 
206    URIs are present, the client assumes that any supported URI may be 
207    used to progress the operation. 
208
209                  * so we actually need to follow exactly one,
210                  * and we can assume any is fine.
211                  */
212         
213                 /* parse reference and use 
214                  * proto://[host][:port]/ only */
215                 rc = ldap_url_parse_ext( ref->bv_val, &srv );
216                 if ( rc != LDAP_URL_SUCCESS ) {
217                         /* try next */
218                         rc = LDAP_OTHER;
219                         continue;
220                 }
221
222                 /* remove DN essentially because later on 
223                  * ldap_initialize() will parse the URL 
224                  * as a comma-separated URL list */
225                 save_dn = srv->lud_dn;
226                 srv->lud_dn = "";
227                 srv->lud_scope = LDAP_SCOPE_DEFAULT;
228                 li.url = ldap_url_desc2str( srv );
229                 srv->lud_dn = save_dn;
230                 ldap_free_urldesc( srv );
231
232                 if ( li.url == NULL ) {
233                         /* try next */
234                         rc = LDAP_OTHER;
235                         continue;
236                 }
237
238                 rc = ( *op_f )( op, rs );
239
240                 ldap_memfree( li.url );
241                 li.url = NULL;
242                 
243                 if ( rc == LDAP_SUCCESS && rs->sr_err == LDAP_SUCCESS ) {
244                         break;
245                 }
246         }
247
248         return rc;
249 }
250
251 static int
252 ldap_chain_response( Operation *op, SlapReply *rs )
253 {
254         slap_overinst   *on = (slap_overinst *) op->o_bd->bd_info;
255         void            *private = op->o_bd->be_private;
256         slap_callback   *sc = op->o_callback,
257                         sc2 = { 0 };
258         int             rc = 0;
259         int             cache = op->o_do_not_cache;
260         BerVarray       ref;
261         struct berval   ndn = op->o_ndn;
262
263         struct ldapinfo li, *lip = (struct ldapinfo *)on->on_bi.bi_private;
264
265 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
266         int             sr_err = rs->sr_err;
267         slap_reply_t    sr_type = rs->sr_type;
268         slap_mask_t     chain_mask = 0;
269         ber_len_t       chain_shift = 0;
270 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
271
272         if ( rs->sr_err != LDAP_REFERRAL && rs->sr_type != REP_SEARCHREF ) {
273                 return SLAP_CB_CONTINUE;
274         }
275
276 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
277         if ( rs->sr_err == LDAP_REFERRAL && get_chaining( op ) > SLAP_CONTROL_IGNORED ) {
278                 switch ( get_resolveBehavior( op ) ) {
279                 case SLAP_CH_RESOLVE_REFERRALS_PREFERRED:
280                 case SLAP_CH_RESOLVE_REFERRALS_REQUIRED:
281                         return SLAP_CB_CONTINUE;
282
283                 default:
284                         chain_mask = SLAP_CH_RESOLVE_MASK;
285                         chain_shift = SLAP_CH_RESOLVE_SHIFT;
286                         break;
287                 }
288
289         } else if ( rs->sr_type == REP_SEARCHREF && get_chaining( op ) > SLAP_CONTROL_IGNORED ) {
290                 switch ( get_continuationBehavior( op ) ) {
291                 case SLAP_CH_CONTINUATION_REFERRALS_PREFERRED:
292                 case SLAP_CH_CONTINUATION_REFERRALS_REQUIRED:
293                         return SLAP_CB_CONTINUE;
294
295                 default:
296                         chain_mask = SLAP_CH_CONTINUATION_MASK;
297                         chain_shift = SLAP_CH_CONTINUATION_SHIFT;
298                         break;
299                 }
300         }
301 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
302
303         /*
304          * TODO: add checks on who/when chain operations; e.g.:
305          *   a) what identities are authorized
306          *   b) what request DN (e.g. only chain requests rooted at <DN>)
307          *   c) what referral URIs
308          *   d) what protocol scheme (e.g. only ldaps://)
309          *   e) what ssf
310          */
311
312         ref = rs->sr_ref;
313         rs->sr_ref = NULL;
314
315         /* we need this to know if back-ldap returned any result */
316         sc2.sc_response = ldap_chain_cb_response;
317         op->o_callback = &sc2;
318
319         /* Chaining can be performed by a privileged user on behalf
320          * of normal users, using the ProxyAuthz control, by exploiting
321          * the identity assertion feature of back-ldap; see idassert-*
322          * directives in slapd-ldap(5).
323          *
324          * FIXME: the idassert-authcDN is one, will it be fine regardless
325          * of the URI we obtain from the referral?
326          */
327
328         switch ( op->o_tag ) {
329         case LDAP_REQ_BIND: {
330                 struct berval   rndn = op->o_req_ndn;
331                 Connection      *conn = op->o_conn;
332
333                 /* FIXME: can we really get a referral for binds? */
334                 op->o_req_ndn = slap_empty_bv;
335                 op->o_conn = NULL;
336                 rc = ldap_chain_op( op, rs, lback->bi_op_bind, ref );
337                 op->o_req_ndn = rndn;
338                 op->o_conn = conn;
339                 }
340                 break;
341         case LDAP_REQ_ADD:
342                 {
343                 int             cleanup_attrs = 0;
344
345                 if ( op->ora_e->e_attrs == NULL ) {
346                         char            textbuf[ SLAP_TEXT_BUFLEN ];
347                         size_t          textlen = sizeof( textbuf );
348
349 #if 0
350                         /* FIXME: op->o_bd is still set to the BackendDB 
351                          * structure of the database that tried to handle
352                          * the operation and actually returned a referral
353                          * ... */
354                         assert( SLAP_DBFLAGS( op->o_bd ) & SLAP_DBFLAG_GLOBAL_OVERLAY );
355 #endif
356
357                         /* global overlay: create entry */
358                         /* NOTE: this is a hack to use the chain overlay
359                          * as global.  I expect to be able to remove this
360                          * soon by using slap_mods2entry() earlier in
361                          * do_add(), adding the operational attrs later
362                          * if required. */
363                         rs->sr_err = slap_mods2entry( op->ora_modlist,
364                                         &op->ora_e, 0, 1,
365                                         &rs->sr_text, textbuf, textlen );
366                         if ( rs->sr_err != LDAP_SUCCESS ) {
367                                 send_ldap_result( op, rs );
368                                 rc = 1;
369                                 break;
370                         }
371                 }
372                 rc = ldap_chain_op( op, rs, lback->bi_op_add, ref );
373                 if ( cleanup_attrs ) {
374                         attrs_free( op->ora_e->e_attrs );
375                         op->ora_e->e_attrs = NULL;
376                 }
377                 break;
378                 }
379         case LDAP_REQ_DELETE:
380                 rc = ldap_chain_op( op, rs, lback->bi_op_delete, ref );
381                 break;
382         case LDAP_REQ_MODRDN:
383                 rc = ldap_chain_op( op, rs, lback->bi_op_modrdn, ref );
384                 break;
385         case LDAP_REQ_MODIFY:
386                 rc = ldap_chain_op( op, rs, lback->bi_op_modify, ref );
387                 break;
388         case LDAP_REQ_COMPARE:
389                 rc = ldap_chain_op( op, rs, lback->bi_op_compare, ref );
390                 break;
391         case LDAP_REQ_SEARCH:
392                 if ( rs->sr_type == REP_SEARCHREF ) {
393                         struct berval   *curr = ref,
394                                         odn = op->o_req_dn,
395                                         ondn = op->o_req_ndn;
396
397                         rs->sr_type = REP_SEARCH;
398
399                         sc2.sc_response = ldap_chain_cb_search_response;
400
401                         li = *lip;
402                         li.url = NULL;
403                         op->o_bd->be_private = &li;
404                         
405                         /* if we parse the URI then by no means 
406                          * we can cache stuff or reuse connections, 
407                          * because in back-ldap there's no caching
408                          * based on the URI value, which is supposed
409                          * to be set once for all (correct?) */
410                         op->o_do_not_cache = 1;
411
412                         /* copy the private info because we need to modify it */
413                         for ( ; !BER_BVISNULL( &curr[0] ); curr++ ) {
414                                 LDAPURLDesc     *srv;
415                                 char            *save_dn;
416
417                                 /* parse reference and use
418                                  * proto://[host][:port]/ only */
419                                 rc = ldap_url_parse_ext( curr[0].bv_val, &srv );
420                                 if ( rc != LDAP_URL_SUCCESS ) {
421                                         /* try next */
422                                         rs->sr_err = LDAP_OTHER;
423                                         continue;
424                                 }
425
426                                 /* remove DN essentially because later on 
427                                  * ldap_initialize() will parse the URL 
428                                  * as a comma-separated URL list */
429                                 save_dn = srv->lud_dn;
430                                 srv->lud_dn = "";
431                                 srv->lud_scope = LDAP_SCOPE_DEFAULT;
432                                 li.url = ldap_url_desc2str( srv );
433                                 if ( li.url != NULL ) {
434                                         ber_str2bv_x( save_dn, 0, 1, &op->o_req_dn,
435                                                         op->o_tmpmemctx );
436                                         ber_dupbv_x( &op->o_req_ndn, &op->o_req_dn,
437                                                         op->o_tmpmemctx );
438                                 }
439
440                                 srv->lud_dn = save_dn;
441                                 ldap_free_urldesc( srv );
442
443                                 if ( li.url == NULL ) {
444                                         /* try next */
445                                         rs->sr_err = LDAP_OTHER;
446                                         continue;
447                                 }
448
449
450                                 /* FIXME: should we also copy filter and scope?
451                                  * according to RFC3296, no */
452                                 rc = lback->bi_op_search( op, rs );
453
454                                 ldap_memfree( li.url );
455                                 li.url = NULL;
456
457                                 op->o_tmpfree( op->o_req_dn.bv_val,
458                                                 op->o_tmpmemctx );
459                                 op->o_tmpfree( op->o_req_ndn.bv_val,
460                                                 op->o_tmpmemctx );
461
462                                 if ( rc == LDAP_SUCCESS && rs->sr_err == LDAP_SUCCESS ) {
463                                         break;
464                                 }
465
466                                 rc = rs->sr_err;
467                         }
468
469                         op->o_req_dn = odn;
470                         op->o_req_ndn = ondn;
471                         rs->sr_type = REP_SEARCHREF;
472                         rs->sr_entry = NULL;
473
474                         if ( rc != LDAP_SUCCESS ) {
475                                 /* couldn't chase any of the referrals */
476                                 rc = SLAP_CB_CONTINUE;
477                         }
478                         
479                 } else {
480                         rc = ldap_chain_op( op, rs, lback->bi_op_search, ref );
481                 }
482                 break;
483         case LDAP_REQ_EXTENDED:
484                 rc = ldap_chain_op( op, rs, lback->bi_extended, ref );
485                 /* FIXME: ldap_back_extended() by design 
486                  * doesn't send result; frontend is expected
487                  * to send it... */
488                 if ( rc != SLAPD_ABANDON ) {
489                         send_ldap_extended( op, rs );
490                         rc = LDAP_SUCCESS;
491                 }
492                 break;
493         default:
494                 rc = SLAP_CB_CONTINUE;
495                 break;
496         }
497
498 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
499         if ( rc != LDAP_SUCCESS || sc2.sc_private == LDAP_CH_ERR ) {
500                 if ( rs->sr_err == LDAP_CANNOT_CHAIN ) {
501                         goto cannot_chain;
502                 }
503
504                 switch ( ( get_chainingBehavior( op ) & chain_mask ) >> chain_shift ) {
505                 case LDAP_CHAINING_REQUIRED:
506 cannot_chain:;
507                         op->o_callback = NULL;
508                         send_ldap_error( op, rs, LDAP_CANNOT_CHAIN, "operation cannot be completed without chaining" );
509                         break;
510
511                 default:
512                         rc = SLAP_CB_CONTINUE;
513                         rs->sr_err = sr_err;
514                         rs->sr_type = sr_type;
515                         break;
516                 }
517                 goto dont_chain;
518         }
519 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
520
521         if ( sc2.sc_private == LDAP_CH_NONE ) {
522                 op->o_callback = NULL;
523                 rc = rs->sr_err = slap_map_api2result( rs );
524                 send_ldap_result( op, rs );
525         }
526
527 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
528 dont_chain:;
529 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
530         op->o_do_not_cache = cache;
531         op->o_bd->be_private = private;
532         op->o_callback = sc;
533         op->o_ndn = ndn;
534         rs->sr_ref = ref;
535
536         return rc;
537 }
538
539 static int
540 ldap_chain_db_config(
541         BackendDB       *be,
542         const char      *fname,
543         int             lineno,
544         int             argc,
545         char    **argv
546 )
547 {
548         slap_overinst   *on = (slap_overinst *) be->bd_info;
549         void            *private = be->be_private;
550         char            *argv0 = NULL;
551         int             rc;
552
553         be->be_private = on->on_bi.bi_private;
554         if ( strncasecmp( argv[ 0 ], "chain-", STRLENOF( "chain-" ) ) == 0 ) {
555                 argv0 = argv[ 0 ];
556                 argv[ 0 ] = &argv[ 0 ][ STRLENOF( "chain-" ) ];
557         }
558         rc = lback->bi_db_config( be, fname, lineno, argc, argv );
559         if ( argv0 ) {
560                 argv[ 0 ] = argv0;
561         }
562         
563         be->be_private = private;
564         return rc;
565 }
566
567 static int
568 ldap_chain_db_init(
569         BackendDB *be
570 )
571 {
572         slap_overinst   *on = (slap_overinst *)be->bd_info;
573         int             rc;
574         BackendDB       bd = *be;
575
576         if ( lback == NULL ) {
577                 lback = backend_info( "ldap" );
578
579                 if ( lback == NULL ) {
580                         return -1;
581                 }
582         }
583
584         bd.be_private = NULL;
585         rc = lback->bi_db_init( &bd );
586         on->on_bi.bi_private = bd.be_private;
587
588         return rc;
589 }
590
591 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
592 static int
593 ldap_chain_db_open(
594         BackendDB *be
595 )
596 {
597         return overlay_register_control( be, LDAP_CONTROL_X_CHAINING_BEHAVIOR );
598 }
599 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
600
601 static int
602 ldap_chain_db_destroy(
603         BackendDB *be
604 )
605 {
606         slap_overinst *on = (slap_overinst *) be->bd_info;
607         void *private = be->be_private;
608         int rc;
609
610         be->be_private = on->on_bi.bi_private;
611         rc = lback->bi_db_destroy( be );
612         on->on_bi.bi_private = be->be_private;
613         be->be_private = private;
614         return rc;
615 }
616
617 static int
618 ldap_chain_connection_destroy(
619         BackendDB *be,
620         Connection *conn
621 )
622 {
623         slap_overinst *on = (slap_overinst *) be->bd_info;
624         void *private = be->be_private;
625         int rc;
626
627         be->be_private = on->on_bi.bi_private;
628         rc = lback->bi_connection_destroy( be, conn );
629         on->on_bi.bi_private = be->be_private;
630         be->be_private = private;
631         return rc;
632 }
633
634 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
635 static int
636 ldap_chain_parse_ctrl(
637         Operation       *op,
638         SlapReply       *rs,
639         LDAPControl     *ctrl )
640 {
641         ber_tag_t       tag;
642         BerElement      *ber;
643         ber_int_t       mode,
644                         behavior;
645
646         if ( get_chaining( op ) != SLAP_CONTROL_NONE ) {
647                 rs->sr_text = "Chaining behavior control specified multiple times";
648                 return LDAP_PROTOCOL_ERROR;
649         }
650
651         if ( op->o_pagedresults != SLAP_CONTROL_NONE ) {
652                 rs->sr_text = "Chaining behavior control specified with pagedResults control";
653                 return LDAP_PROTOCOL_ERROR;
654         }
655
656         if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) {
657                 mode = (SLAP_CH_RESOLVE_DEFAULT|SLAP_CH_CONTINUATION_DEFAULT);
658
659         } else {
660                 ber_len_t       len;
661
662                 /* Parse the control value
663                  *      ChainingBehavior ::= SEQUENCE { 
664                  *           resolveBehavior         Behavior OPTIONAL, 
665                  *           continuationBehavior    Behavior OPTIONAL } 
666                  *                             
667                  *      Behavior :: = ENUMERATED { 
668                  *           chainingPreferred       (0), 
669                  *           chainingRequired        (1), 
670                  *           referralsPreferred      (2), 
671                  *           referralsRequired       (3) } 
672                  */
673
674                 ber = ber_init( &ctrl->ldctl_value );
675                 if( ber == NULL ) {
676                         rs->sr_text = "internal error";
677                         return LDAP_OTHER;
678                 }
679
680                 tag = ber_scanf( ber, "{e" /* } */, &behavior );
681                 /* FIXME: since the whole SEQUENCE is optional,
682                  * should we accept no enumerations at all? */
683                 if ( tag != LBER_ENUMERATED ) {
684                         rs->sr_text = "Chaining behavior control: resolveBehavior decoding error";
685                         return LDAP_PROTOCOL_ERROR;
686                 }
687
688                 switch ( behavior ) {
689                 case LDAP_CHAINING_PREFERRED:
690                         mode = SLAP_CH_RESOLVE_CHAINING_PREFERRED;
691                         break;
692
693                 case LDAP_CHAINING_REQUIRED:
694                         mode = SLAP_CH_RESOLVE_CHAINING_REQUIRED;
695                         break;
696
697                 case LDAP_REFERRALS_PREFERRED:
698                         mode = SLAP_CH_RESOLVE_REFERRALS_PREFERRED;
699                         break;
700
701                 case LDAP_REFERRALS_REQUIRED:
702                         mode = SLAP_CH_RESOLVE_REFERRALS_REQUIRED;
703                         break;
704
705                 default:
706                         rs->sr_text = "Chaining behavior control: unknown resolveBehavior";
707                         return LDAP_PROTOCOL_ERROR;
708                 }
709
710                 tag = ber_peek_tag( ber, &len );
711                 if ( tag == LBER_ENUMERATED ) {
712                         tag = ber_scanf( ber, "e", &behavior );
713                         if ( tag == LBER_ERROR ) {
714                                 rs->sr_text = "Chaining behavior control: continuationBehavior decoding error";
715                                 return LDAP_PROTOCOL_ERROR;
716                         }
717                 }
718
719                 if ( tag == LBER_DEFAULT ) {
720                         mode |= SLAP_CH_CONTINUATION_DEFAULT;
721
722                 } else {
723                         switch ( behavior ) {
724                         case LDAP_CHAINING_PREFERRED:
725                                 mode |= SLAP_CH_CONTINUATION_CHAINING_PREFERRED;
726                                 break;
727
728                         case LDAP_CHAINING_REQUIRED:
729                                 mode |= SLAP_CH_CONTINUATION_CHAINING_REQUIRED;
730                                 break;
731
732                         case LDAP_REFERRALS_PREFERRED:
733                                 mode |= SLAP_CH_CONTINUATION_REFERRALS_PREFERRED;
734                                 break;
735
736                         case LDAP_REFERRALS_REQUIRED:
737                                 mode |= SLAP_CH_CONTINUATION_REFERRALS_REQUIRED;
738                                 break;
739
740                         default:
741                                 rs->sr_text = "Chaining behavior control: unknown continuationBehavior";
742                                 return LDAP_PROTOCOL_ERROR;
743                         }
744                 }
745
746                 if ( ( ber_scanf( ber, /* { */ "}") ) == LBER_ERROR ) {
747                         rs->sr_text = "Chaining behavior control: decoding error";
748                         return LDAP_PROTOCOL_ERROR;
749                 }
750
751                 (void) ber_free( ber, 1 );
752         }
753
754         op->o_chaining = mode | ( ctrl->ldctl_iscritical
755                         ? SLAP_CONTROL_CRITICAL
756                         : SLAP_CONTROL_NONCRITICAL );
757
758         return LDAP_SUCCESS;
759 }
760 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
761
762 static slap_overinst ldapchain;
763
764 int
765 chain_init( void )
766 {
767 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
768         int     rc;
769
770         rc = register_supported_control( LDAP_CONTROL_X_CHAINING_BEHAVIOR,
771                         /* SLAP_CTRL_GLOBAL| */ SLAP_CTRL_ACCESS|SLAP_CTRL_HIDE, NULL,
772                         ldap_chain_parse_ctrl, &sc_chainingBehavior );
773         if ( rc != LDAP_SUCCESS ) {
774                 fprintf( stderr, "Failed to register chaining behavior control: %d\n", rc );
775                 return rc;
776         }
777 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
778
779         ldapchain.on_bi.bi_type = "chain";
780         ldapchain.on_bi.bi_db_init = ldap_chain_db_init;
781 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
782         ldapchain.on_bi.bi_db_open = ldap_chain_db_open;
783 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
784         ldapchain.on_bi.bi_db_config = ldap_chain_db_config;
785         ldapchain.on_bi.bi_db_destroy = ldap_chain_db_destroy;
786
787         /* ... otherwise the underlying backend's function would be called,
788          * likely passing an invalid entry; on the contrary, the requested
789          * operational attributes should have been returned while chasing
790          * the referrals.  This all in all is a bit messy, because part
791          * of the operational attributes are generated by the backend;
792          * part by the frontend; back-ldap should receive all the available
793          * ones from the remote server, but then, on its own, it strips those
794          * it assumes will be (re)generated by the frontend (e.g.
795          * subschemaSubentry.) */
796         ldapchain.on_bi.bi_operational = ldap_chain_operational;
797         
798         ldapchain.on_bi.bi_connection_destroy = ldap_chain_connection_destroy;
799
800         ldapchain.on_response = ldap_chain_response;
801
802         return overlay_register( &ldapchain );
803 }
804