]> git.sur5r.net Git - openldap/blob - servers/slapd/back-ldap/chain.c
d481abb7a486707060f8b820c0896d517adeec95
[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 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
618 static int
619 ldap_chain_parse_ctrl(
620         Operation       *op,
621         SlapReply       *rs,
622         LDAPControl     *ctrl )
623 {
624         ber_tag_t       tag;
625         BerElement      *ber;
626         ber_int_t       mode,
627                         behavior;
628
629         if ( get_chaining( op ) != SLAP_CONTROL_NONE ) {
630                 rs->sr_text = "Chaining behavior control specified multiple times";
631                 return LDAP_PROTOCOL_ERROR;
632         }
633
634         if ( op->o_pagedresults != SLAP_CONTROL_NONE ) {
635                 rs->sr_text = "Chaining behavior control specified with pagedResults control";
636                 return LDAP_PROTOCOL_ERROR;
637         }
638
639         if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) {
640                 mode = (SLAP_CH_RESOLVE_DEFAULT|SLAP_CH_CONTINUATION_DEFAULT);
641
642         } else {
643                 ber_len_t       len;
644
645                 /* Parse the control value
646                  *      ChainingBehavior ::= SEQUENCE { 
647                  *           resolveBehavior         Behavior OPTIONAL, 
648                  *           continuationBehavior    Behavior OPTIONAL } 
649                  *                             
650                  *      Behavior :: = ENUMERATED { 
651                  *           chainingPreferred       (0), 
652                  *           chainingRequired        (1), 
653                  *           referralsPreferred      (2), 
654                  *           referralsRequired       (3) } 
655                  */
656
657                 ber = ber_init( &ctrl->ldctl_value );
658                 if( ber == NULL ) {
659                         rs->sr_text = "internal error";
660                         return LDAP_OTHER;
661                 }
662
663                 tag = ber_scanf( ber, "{e" /* } */, &behavior );
664                 /* FIXME: since the whole SEQUENCE is optional,
665                  * should we accept no enumerations at all? */
666                 if ( tag != LBER_ENUMERATED ) {
667                         rs->sr_text = "Chaining behavior control: resolveBehavior decoding error";
668                         return LDAP_PROTOCOL_ERROR;
669                 }
670
671                 switch ( behavior ) {
672                 case LDAP_CHAINING_PREFERRED:
673                         mode = SLAP_CH_RESOLVE_CHAINING_PREFERRED;
674                         break;
675
676                 case LDAP_CHAINING_REQUIRED:
677                         mode = SLAP_CH_RESOLVE_CHAINING_REQUIRED;
678                         break;
679
680                 case LDAP_REFERRALS_PREFERRED:
681                         mode = SLAP_CH_RESOLVE_REFERRALS_PREFERRED;
682                         break;
683
684                 case LDAP_REFERRALS_REQUIRED:
685                         mode = SLAP_CH_RESOLVE_REFERRALS_REQUIRED;
686                         break;
687
688                 default:
689                         rs->sr_text = "Chaining behavior control: unknown resolveBehavior";
690                         return LDAP_PROTOCOL_ERROR;
691                 }
692
693                 tag = ber_peek_tag( ber, &len );
694                 if ( tag == LBER_ENUMERATED ) {
695                         tag = ber_scanf( ber, "e", &behavior );
696                         if ( tag == LBER_ERROR ) {
697                                 rs->sr_text = "Chaining behavior control: continuationBehavior decoding error";
698                                 return LDAP_PROTOCOL_ERROR;
699                         }
700                 }
701
702                 if ( tag == LBER_DEFAULT ) {
703                         mode |= SLAP_CH_CONTINUATION_DEFAULT;
704
705                 } else {
706                         switch ( behavior ) {
707                         case LDAP_CHAINING_PREFERRED:
708                                 mode |= SLAP_CH_CONTINUATION_CHAINING_PREFERRED;
709                                 break;
710
711                         case LDAP_CHAINING_REQUIRED:
712                                 mode |= SLAP_CH_CONTINUATION_CHAINING_REQUIRED;
713                                 break;
714
715                         case LDAP_REFERRALS_PREFERRED:
716                                 mode |= SLAP_CH_CONTINUATION_REFERRALS_PREFERRED;
717                                 break;
718
719                         case LDAP_REFERRALS_REQUIRED:
720                                 mode |= SLAP_CH_CONTINUATION_REFERRALS_REQUIRED;
721                                 break;
722
723                         default:
724                                 rs->sr_text = "Chaining behavior control: unknown continuationBehavior";
725                                 return LDAP_PROTOCOL_ERROR;
726                         }
727                 }
728
729                 if ( ( ber_scanf( ber, /* { */ "}") ) == LBER_ERROR ) {
730                         rs->sr_text = "Chaining behavior control: decoding error";
731                         return LDAP_PROTOCOL_ERROR;
732                 }
733
734                 (void) ber_free( ber, 1 );
735         }
736
737         op->o_chaining = mode | ( ctrl->ldctl_iscritical
738                         ? SLAP_CONTROL_CRITICAL
739                         : SLAP_CONTROL_NONCRITICAL );
740
741         return LDAP_SUCCESS;
742 }
743 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
744
745 static slap_overinst ldapchain;
746
747 int
748 chain_init( void )
749 {
750 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
751         int     rc;
752
753         rc = register_supported_control( LDAP_CONTROL_X_CHAINING_BEHAVIOR,
754                         /* SLAP_CTRL_GLOBAL| */ SLAP_CTRL_ACCESS|SLAP_CTRL_HIDE, NULL,
755                         ldap_chain_parse_ctrl, &sc_chainingBehavior );
756         if ( rc != LDAP_SUCCESS ) {
757                 fprintf( stderr, "Failed to register chaining behavior control: %d\n", rc );
758                 return rc;
759         }
760 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
761
762         ldapchain.on_bi.bi_type = "chain";
763         ldapchain.on_bi.bi_db_init = ldap_chain_db_init;
764 #ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
765         ldapchain.on_bi.bi_db_open = ldap_chain_db_open;
766 #endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
767         ldapchain.on_bi.bi_db_config = ldap_chain_db_config;
768         ldapchain.on_bi.bi_db_destroy = ldap_chain_db_destroy;
769         
770         /* ... otherwise the underlying backend's function would be called,
771          * likely passing an invalid entry; on the contrary, the requested
772          * operational attributes should have been returned while chasing
773          * the referrals.  This all in all is a bit messy, because part
774          * of the operational attributes are generated by they backend;
775          * part by the frontend; back-ldap should receive all the available
776          * ones from the remote server, but then, on it own, it strips those
777          * it assumes will be (re)generated by the frontend (e.g.
778          * subschemaSubentry.) */
779         ldapchain.on_bi.bi_operational = ldap_chain_operational;
780         
781         ldapchain.on_response = ldap_chain_response;
782
783         return overlay_register( &ldapchain );
784 }
785