]> git.sur5r.net Git - openldap/blob - servers/slapd/saslauthz.c
clarify why we don't accept scheme other than ldap:// in authz-regexps
[openldap] / servers / slapd / saslauthz.c
1 /* $OpenLDAP$ */
2 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
3  *
4  * Copyright 1998-2004 The OpenLDAP Foundation.
5  * Portions Copyright 2000 Mark Adamson, Carnegie Mellon.
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted only as authorized by the OpenLDAP
10  * Public License.
11  *
12  * A copy of this license is available in the file LICENSE in the
13  * top-level directory of the distribution or, alternatively, at
14  * <http://www.OpenLDAP.org/license.html>.
15  */
16
17 #include "portable.h"
18
19 #include <stdio.h>
20
21 #include <ac/stdlib.h>
22 #include <ac/string.h>
23 #include <ac/ctype.h>
24
25 #include "slap.h"
26
27 #include <limits.h>
28
29 #include "lutil.h"
30
31 #define SASLREGEX_REPLACE 10
32
33 #define LDAP_X_SCOPE_EXACT      ((ber_int_t) 0x0010)
34 #define LDAP_X_SCOPE_REGEX      ((ber_int_t) 0x0020)
35 #define LDAP_X_SCOPE_CHILDREN   ((ber_int_t) 0x0030)
36 #define LDAP_X_SCOPE_SUBTREE    ((ber_int_t) 0x0040)
37 #define LDAP_X_SCOPE_ONELEVEL   ((ber_int_t) 0x0050)
38 #define LDAP_X_SCOPE_GROUP      ((ber_int_t) 0x0060)
39 #define LDAP_X_SCOPE_USERS      ((ber_int_t) 0x0070)
40
41 /*
42  * IDs in DNauthzid form can now have a type specifier, that
43  * influences how they are used in related operations.
44  *
45  * syntax: dn[.{exact|regex}]:<val>
46  *
47  * dn.exact:    the value must pass normalization and is used 
48  *              in exact DN match.
49  * dn.regex:    the value is treated as a regular expression 
50  *              in matching DN values in authz{To|From}
51  *              attributes.
52  * dn:          for backwards compatibility reasons, the value 
53  *              is treated as a regular expression, and thus 
54  *              it is not normalized nor validated; it is used
55  *              in exact or regex comparisons based on the 
56  *              context.
57  *
58  * IDs in DNauthzid form can now have a type specifier, that
59  * influences how they are used in related operations.
60  *
61  * syntax: u[.mech[/realm]]:<val>
62  * 
63  * where mech is a SIMPLE, AUTHZ, or a SASL mechanism name
64  * and realm is mechanism specific realm (separate to those
65  * which are representable as part of the principal).
66  */
67
68 typedef struct sasl_regexp {
69   char *sr_match;                                               /* regexp match pattern */
70   char *sr_replace;                                     /* regexp replace pattern */
71   regex_t sr_workspace;                                 /* workspace for regexp engine */
72   int sr_offset[SASLREGEX_REPLACE+2];   /* offsets of $1,$2... in *replace */
73 } SaslRegexp_t;
74
75 static int nSaslRegexp = 0;
76 static SaslRegexp_t *SaslRegexp = NULL;
77
78 #ifdef SLAP_AUTH_REWRITE
79 #include "rewrite.h"
80 struct rewrite_info     *sasl_rwinfo = NULL;
81 #define AUTHID_CONTEXT  "authid"
82 #endif /* SLAP_AUTH_REWRITE */
83
84 /* What SASL proxy authorization policies are allowed? */
85 #define SASL_AUTHZ_NONE 0x00
86 #define SASL_AUTHZ_FROM 0x01
87 #define SASL_AUTHZ_TO   0x02
88 #define SASL_AUTHZ_AND  0x10
89
90 static int authz_policy = SASL_AUTHZ_NONE;
91
92 static
93 int slap_sasl_match( Operation *opx, struct berval *rule,
94         struct berval *assertDN, struct berval *authc );
95
96 int slap_sasl_setpolicy( const char *arg )
97 {
98         int rc = LDAP_SUCCESS;
99
100         if ( strcasecmp( arg, "none" ) == 0 ) {
101                 authz_policy = SASL_AUTHZ_NONE;
102         } else if ( strcasecmp( arg, "from" ) == 0 ) {
103                 authz_policy = SASL_AUTHZ_FROM;
104         } else if ( strcasecmp( arg, "to" ) == 0 ) {
105                 authz_policy = SASL_AUTHZ_TO;
106         } else if ( strcasecmp( arg, "both" ) == 0 || strcasecmp( arg, "any" ) == 0 ) {
107                 authz_policy = SASL_AUTHZ_FROM | SASL_AUTHZ_TO;
108         } else if ( strcasecmp( arg, "all" ) == 0 ) {
109                 authz_policy = SASL_AUTHZ_FROM | SASL_AUTHZ_TO | SASL_AUTHZ_AND;
110         } else {
111                 rc = LDAP_OTHER;
112         }
113         return rc;
114 }
115
116 int slap_parse_user( struct berval *id, struct berval *user,
117                 struct berval *realm, struct berval *mech )
118 {
119         char    u;
120         
121         assert( id );
122         assert( !BER_BVISNULL( id ) );
123         assert( user );
124         assert( realm );
125         assert( mech );
126
127         u = id->bv_val[ 0 ];
128         
129         if ( u != 'u' && u != 'U' ) {
130                 /* called with something other than u: */
131                 return LDAP_PROTOCOL_ERROR;
132         }
133
134         /* uauthzid form:
135          *              u[.mech[/realm]]:user
136          */
137         
138         user->bv_val = strchr( id->bv_val, ':' );
139         if ( BER_BVISNULL( user ) ) {
140                 return LDAP_PROTOCOL_ERROR;
141         }
142         user->bv_val[ 0 ] = '\0';
143         user->bv_val++;
144         user->bv_len = id->bv_len - ( user->bv_val - id->bv_val );
145
146         mech->bv_val = strchr( id->bv_val, '.' );
147         if ( !BER_BVISNULL( mech ) ) {
148                 mech->bv_val[ 0 ] = '\0';
149                 mech->bv_val++;
150
151                 realm->bv_val = strchr( mech->bv_val, '/' );
152
153                 if ( !BER_BVISNULL( realm ) ) {
154                         realm->bv_val[ 0 ] = '\0';
155                         realm->bv_val++;
156                         mech->bv_len = realm->bv_val - mech->bv_val - 1;
157                         realm->bv_len = user->bv_val - realm->bv_val - 1;
158                 } else {
159                         mech->bv_len = user->bv_val - mech->bv_val - 1;
160                 }
161
162         } else {
163                 BER_BVZERO( realm );
164         }
165
166         if ( id->bv_val[ 1 ] != '\0' ) {
167                 return LDAP_PROTOCOL_ERROR;
168         }
169
170         if ( !BER_BVISNULL( mech ) ) {
171                 assert( mech->bv_val == id->bv_val + 2 );
172
173                 AC_MEMCPY( mech->bv_val - 2, mech->bv_val, mech->bv_len + 1 );
174                 mech->bv_val -= 2;
175         }
176
177         if ( !BER_BVISNULL( realm ) ) {
178                 assert( realm->bv_val >= id->bv_val + 2 );
179
180                 AC_MEMCPY( realm->bv_val - 2, realm->bv_val, realm->bv_len + 1 );
181                 realm->bv_val -= 2;
182         }
183
184         /* leave "u:" before user */
185         user->bv_val -= 2;
186         user->bv_len += 2;
187         user->bv_val[ 0 ] = u;
188         user->bv_val[ 1 ] = ':';
189
190         return LDAP_SUCCESS;
191 }
192
193 static int slap_parseURI( Operation *op, struct berval *uri,
194         struct berval *base, struct berval *nbase,
195         int *scope, Filter **filter, struct berval *fstr )
196 {
197         struct berval bv;
198         int rc;
199         LDAPURLDesc *ludp;
200
201         assert( uri != NULL && !BER_BVISNULL( uri ) );
202         BER_BVZERO( base );
203         BER_BVZERO( nbase );
204         BER_BVZERO( fstr );
205         *scope = -1;
206         *filter = NULL;
207
208         Debug( LDAP_DEBUG_TRACE,
209                 "slap_parseURI: parsing %s\n", uri->bv_val, 0, 0 );
210
211         rc = LDAP_PROTOCOL_ERROR;
212         /*
213          * dn[.<dnstyle>]:<dnpattern>
214          * <dnstyle> ::= {exact|regex|children|subtree|onelevel}
215          *
216          * <dnstyle> defaults to "exact"
217          * if <dnstyle> is not "regex", <dnpattern> must pass DN normalization
218          */
219         if ( !strncasecmp( uri->bv_val, "dn", STRLENOF( "dn" ) ) ) {
220                 bv.bv_val = uri->bv_val + STRLENOF( "dn" );
221
222                 if ( bv.bv_val[ 0 ] == '.' ) {
223                         bv.bv_val++;
224
225                         if ( !strncasecmp( bv.bv_val, "exact:", STRLENOF( "exact:" ) ) ) {
226                                 bv.bv_val += STRLENOF( "exact:" );
227                                 *scope = LDAP_X_SCOPE_EXACT;
228
229                         } else if ( !strncasecmp( bv.bv_val, "regex:", STRLENOF( "regex:" ) ) ) {
230                                 bv.bv_val += STRLENOF( "regex:" );
231                                 *scope = LDAP_X_SCOPE_REGEX;
232
233                         } else if ( !strncasecmp( bv.bv_val, "children:", STRLENOF( "children:" ) ) ) {
234                                 bv.bv_val += STRLENOF( "children:" );
235                                 *scope = LDAP_X_SCOPE_CHILDREN;
236
237                         } else if ( !strncasecmp( bv.bv_val, "subtree:", STRLENOF( "subtree:" ) ) ) {
238                                 bv.bv_val += STRLENOF( "subtree:" );
239                                 *scope = LDAP_X_SCOPE_SUBTREE;
240
241                         } else if ( !strncasecmp( bv.bv_val, "onelevel:", STRLENOF( "onelevel:" ) ) ) {
242                                 bv.bv_val += STRLENOF( "onelevel:" );
243                                 *scope = LDAP_X_SCOPE_ONELEVEL;
244
245                         } else {
246                                 return LDAP_PROTOCOL_ERROR;
247                         }
248
249                 } else {
250                         if ( bv.bv_val[ 0 ] != ':' ) {
251                                 return LDAP_PROTOCOL_ERROR;
252                         }
253                         *scope = LDAP_X_SCOPE_EXACT;
254                         bv.bv_val++;
255                 }
256
257                 bv.bv_val += strspn( bv.bv_val, " " );
258                 /* jump here in case no type specification was present
259                  * and uri was not an URI... HEADS-UP: assuming EXACT */
260 is_dn:          bv.bv_len = uri->bv_len - (bv.bv_val - uri->bv_val);
261
262                 /* a single '*' means any DN without using regexes */
263                 if ( ber_bvccmp( &bv, '*' ) ) {
264                         *scope = LDAP_X_SCOPE_USERS;
265                 }
266
267                 switch ( *scope ) {
268                 case LDAP_X_SCOPE_EXACT:
269                 case LDAP_X_SCOPE_CHILDREN:
270                 case LDAP_X_SCOPE_SUBTREE:
271                 case LDAP_X_SCOPE_ONELEVEL:
272                         rc = dnNormalize( 0, NULL, NULL, &bv, nbase, op->o_tmpmemctx );
273                         if( rc != LDAP_SUCCESS ) {
274                                 *scope = -1;
275                         }
276                         break;
277
278                 case LDAP_X_SCOPE_REGEX:
279                         ber_dupbv_x( nbase, &bv, op->o_tmpmemctx );
280
281                 case LDAP_X_SCOPE_USERS:
282                         rc = LDAP_SUCCESS;
283                         break;
284
285                 default:
286                         *scope = -1;
287                         break;
288                 }
289
290                 return rc;
291
292         /*
293          * u:<uid>
294          */
295         } else if ( ( uri->bv_val[ 0 ] == 'u' || uri->bv_val[ 0 ] == 'U' )
296                         && ( uri->bv_val[ 1 ] == ':' 
297                                 || uri->bv_val[ 1 ] == '/' 
298                                 || uri->bv_val[ 1 ] == '.' ) )
299         {
300                 Connection      c = *op->o_conn;
301                 char            buf[ SLAP_LDAPDN_MAXLEN ];
302                 struct berval   id,
303                                 user = BER_BVNULL,
304                                 realm = BER_BVNULL,
305                                 mech = BER_BVNULL;
306
307                 if ( sizeof( buf ) <= uri->bv_len ) {
308                         return LDAP_INVALID_SYNTAX;
309                 }
310
311                 id.bv_len = uri->bv_len;
312                 id.bv_val = buf;
313                 strncpy( buf, uri->bv_val, sizeof( buf ) );
314
315                 rc = slap_parse_user( &id, &user, &realm, &mech );
316                 if ( rc != LDAP_SUCCESS ) {
317                         return rc;
318                 }
319
320                 if ( !BER_BVISNULL( &mech ) ) {
321                         c.c_sasl_bind_mech = mech;
322                 } else {
323                         BER_BVSTR( &c.c_sasl_bind_mech, "AUTHZ" );
324                 }
325                 
326                 rc = slap_sasl_getdn( &c, op, &user,
327                                 realm.bv_val, nbase, SLAP_GETDN_AUTHZID );
328
329                 if ( rc == LDAP_SUCCESS ) {
330                         *scope = LDAP_X_SCOPE_EXACT;
331                 }
332
333                 return rc;
334
335         /*
336          * group[/<groupoc>[/<groupat>]]:<groupdn>
337          *
338          * groupoc defaults to "groupOfNames"
339          * groupat defaults to "member"
340          * 
341          * <groupdn> must pass DN normalization
342          */
343         } else if ( strncasecmp( uri->bv_val, "group", STRLENOF( "group" ) ) == 0 )
344         {
345                 struct berval   group_dn = BER_BVNULL,
346                                 group_oc = BER_BVNULL,
347                                 member_at = BER_BVNULL;
348                 char            *tmp;
349
350                 bv.bv_val = uri->bv_val + STRLENOF( "group" );
351                 group_dn.bv_val = strchr( bv.bv_val, ':' );
352                 if ( group_dn.bv_val == NULL ) {
353                         /* last chance: assume it's a(n exact) DN ... */
354                         bv.bv_val = uri->bv_val;
355                         *scope = LDAP_X_SCOPE_EXACT;
356                         goto is_dn;
357                 }
358                 
359                 if ( bv.bv_val[ 0 ] == '/' ) {
360                         group_oc.bv_val = &bv.bv_val[ 1 ];
361
362                         member_at.bv_val = strchr( group_oc.bv_val, '/' );
363                         if ( member_at.bv_val ) {
364                                 group_oc.bv_len = member_at.bv_val - group_oc.bv_val;
365                                 member_at.bv_val++;
366                                 member_at.bv_len = group_dn.bv_val - member_at.bv_val;
367
368                         } else {
369                                 group_oc.bv_len = group_dn.bv_val - group_oc.bv_val;
370                                 BER_BVSTR( &member_at, "member" );
371                         }
372
373                 } else {
374                         BER_BVSTR( &group_oc, "groupOfNames" );
375                 }
376                 group_dn.bv_val++;
377                 group_dn.bv_len = uri->bv_len - ( group_dn.bv_val - uri->bv_val );
378
379                 rc = dnNormalize( 0, NULL, NULL, &group_dn, nbase, op->o_tmpmemctx );
380                 if ( rc != LDAP_SUCCESS ) {
381                         *scope = -1;
382                         return rc;
383                 }
384                 *scope = LDAP_X_SCOPE_GROUP;
385
386                 /* FIXME: caller needs to add value of member attribute
387                  * and close brackets twice */
388                 fstr->bv_len = STRLENOF( "(&(objectClass=)(=" /* )) */ )
389                         + group_oc.bv_len + member_at.bv_len;
390                 fstr->bv_val = ch_malloc( fstr->bv_len + 1 );
391
392                 tmp = lutil_strncopy( fstr->bv_val, "(&(objectClass=" /* )) */ ,
393                                 STRLENOF( "(&(objectClass=" /* )) */ ) );
394                 tmp = lutil_strncopy( tmp, group_oc.bv_val, group_oc.bv_len );
395                 tmp = lutil_strncopy( tmp, /* ( */ ")(" /* ) */ ,
396                                 STRLENOF( /* ( */ ")(" /* ) */ ) );
397                 tmp = lutil_strncopy( tmp, member_at.bv_val, member_at.bv_len );
398                 tmp = lutil_strncopy( tmp, "=", STRLENOF( "=" ) );
399
400                 return rc;
401         }
402
403         /*
404          * ldap:///<base>??<scope>?<filter>
405          * <scope> ::= {base|one|subtree}
406          *
407          * <scope> defaults to "base"
408          * <base> must pass DN normalization
409          * <filter> must pass str2filter()
410          */
411         rc = ldap_url_parse( uri->bv_val, &ludp );
412         switch ( rc ) {
413         case LDAP_URL_SUCCESS:
414                 /* FIXME: the check is pedantic, but I think it's necessary,
415                  * because people tend to use things like ldaps:// which
416                  * gives the idea SSL is being used.  Maybe we could
417                  * accept ldapi:// as well, but the point is that we use
418                  * an URL as an easy means to define bits of a search with
419                  * little parsing.
420                  */
421                 if ( strcasecmp( ludp->lud_scheme, "ldap" ) != 0 ) {
422                         /*
423                          * must be ldap:///
424                          */
425                         return LDAP_PROTOCOL_ERROR;
426                 }
427                 break;
428
429         case LDAP_URL_ERR_BADSCHEME:
430                 /*
431                  * last chance: assume it's a(n exact) DN ...
432                  *
433                  * NOTE: must pass DN normalization
434                  */
435                 bv.bv_val = uri->bv_val;
436                 *scope = LDAP_X_SCOPE_EXACT;
437                 goto is_dn;
438
439         default:
440                 return LDAP_PROTOCOL_ERROR;
441         }
442
443         if ( ( ludp->lud_host && *ludp->lud_host )
444                 || ludp->lud_attrs || ludp->lud_exts )
445         {
446                 /* host part must be empty */
447                 /* attrs and extensions parts must be empty */
448                 rc = LDAP_PROTOCOL_ERROR;
449                 goto done;
450         }
451
452         /* Grab the scope */
453         *scope = ludp->lud_scope;
454
455         /* Grab the filter */
456         if ( ludp->lud_filter ) {
457                 *filter = str2filter_x( op, ludp->lud_filter );
458                 if ( *filter == NULL ) {
459                         rc = LDAP_PROTOCOL_ERROR;
460                         goto done;
461                 }
462                 ber_str2bv( ludp->lud_filter, 0, 0, fstr );
463         }
464
465         /* Grab the searchbase */
466         ber_str2bv( ludp->lud_dn, 0, 0, base );
467         rc = dnNormalize( 0, NULL, NULL, base, nbase, op->o_tmpmemctx );
468
469 done:
470         if( rc != LDAP_SUCCESS ) {
471                 if( *filter ) filter_free_x( op, *filter );
472                 BER_BVZERO( base );
473                 BER_BVZERO( fstr );
474         } else {
475                 /* Don't free these, return them to caller */
476                 ludp->lud_filter = NULL;
477                 ludp->lud_dn = NULL;
478         }
479
480         ldap_free_urldesc( ludp );
481         return( rc );
482 }
483
484 static int slap_sasl_rx_off(char *rep, int *off)
485 {
486         const char *c;
487         int n;
488
489         /* Precompile replace pattern. Find the $<n> placeholders */
490         off[0] = -2;
491         n = 1;
492         for ( c = rep;   *c;  c++ ) {
493                 if ( *c == '\\' && c[1] ) {
494                         c++;
495                         continue;
496                 }
497                 if ( *c == '$' ) {
498                         if ( n == SASLREGEX_REPLACE ) {
499                                 Debug( LDAP_DEBUG_ANY,
500                                         "SASL replace pattern %s has too many $n "
501                                                 "placeholders (max %d)\n",
502                                         rep, SASLREGEX_REPLACE, 0 );
503
504                                 return( LDAP_OTHER );
505                         }
506                         off[n] = c - rep;
507                         n++;
508                 }
509         }
510
511         /* Final placeholder, after the last $n */
512         off[n] = c - rep;
513         n++;
514         off[n] = -1;
515         return( LDAP_SUCCESS );
516 }
517
518 #ifdef SLAP_AUTH_REWRITE
519 int slap_sasl_rewrite_config( 
520                 const char      *fname,
521                 int             lineno,
522                 int             argc,
523                 char            **argv
524 )
525 {
526         int     rc;
527         char    *savearg0;
528
529         /* init at first call */
530         if ( sasl_rwinfo == NULL ) {
531                 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
532         }
533
534         /* strip "authid-" prefix for parsing */
535         savearg0 = argv[0];
536         argv[0] += STRLENOF( "authid-" );
537         rc = rewrite_parse( sasl_rwinfo, fname, lineno, argc, argv );
538         argv[0] = savearg0;
539
540         return rc;
541 }
542
543 int slap_sasl_rewrite_destroy( void )
544 {
545         if ( sasl_rwinfo ) {
546                 rewrite_info_delete( &sasl_rwinfo );
547                 sasl_rwinfo = NULL;
548         }
549
550         return 0;
551 }
552
553 int slap_sasl_regexp_rewrite_config(
554                 const char      *fname,
555                 int             lineno,
556                 const char      *match,
557                 const char      *replace,
558                 const char      *context )
559 {
560         int     rc;
561         char    *argvRule[] = { "rewriteRule", NULL, NULL, ":@", NULL };
562
563         /* init at first call */
564         if ( sasl_rwinfo == NULL ) {
565                 char *argvEngine[] = { "rewriteEngine", "on", NULL };
566                 char *argvContext[] = { "rewriteContext", NULL, NULL };
567
568                 /* initialize rewrite engine */
569                 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
570
571                 /* switch on rewrite engine */
572                 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvEngine );
573                 if (rc != LDAP_SUCCESS) {
574                         return rc;
575                 }
576
577                 /* create generic authid context */
578                 argvContext[1] = AUTHID_CONTEXT;
579                 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvContext );
580                 if (rc != LDAP_SUCCESS) {
581                         return rc;
582                 }
583         }
584
585         argvRule[1] = (char *)match;
586         argvRule[2] = (char *)replace;
587         rc = rewrite_parse( sasl_rwinfo, fname, lineno, 4, argvRule );
588
589         return rc;
590 }
591 #endif /* SLAP_AUTH_REWRITE */
592
593 int slap_sasl_regexp_config( const char *match, const char *replace )
594 {
595 #ifdef SLAP_AUTH_REWRITE
596         return slap_sasl_regexp_rewrite_config( "sasl-regexp", 0,
597                         match, replace, AUTHID_CONTEXT );
598 #else /* ! SLAP_AUTH_REWRITE */
599         int rc;
600         SaslRegexp_t *reg;
601
602         SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
603           (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
604
605         reg = &SaslRegexp[nSaslRegexp];
606
607         reg->sr_match = ch_strdup( match );
608         reg->sr_replace = ch_strdup( replace );
609
610         /* Precompile matching pattern */
611         rc = regcomp( &reg->sr_workspace, reg->sr_match, REG_EXTENDED|REG_ICASE );
612         if ( rc ) {
613                 Debug( LDAP_DEBUG_ANY,
614                 "SASL match pattern %s could not be compiled by regexp engine\n",
615                 reg->sr_match, 0, 0 );
616
617                 return( LDAP_OTHER );
618         }
619
620         rc = slap_sasl_rx_off( reg->sr_replace, reg->sr_offset );
621         if ( rc != LDAP_SUCCESS ) return rc;
622
623         nSaslRegexp++;
624         return( LDAP_SUCCESS );
625 #endif /* ! SLAP_AUTH_REWRITE */
626 }
627
628 /* Perform replacement on regexp matches */
629 static void slap_sasl_rx_exp(
630         const char *rep,
631         const int *off,
632         regmatch_t *str,
633         const char *saslname,
634         struct berval *out,
635         void *ctx )
636 {
637         int i, n, len, insert;
638
639         /* Get the total length of the final URI */
640
641         n=1;
642         len = 0;
643         while( off[n] >= 0 ) {
644                 /* Len of next section from replacement string (x,y,z above) */
645                 len += off[n] - off[n-1] - 2;
646                 if( off[n+1] < 0)
647                         break;
648
649                 /* Len of string from saslname that matched next $i  (b,d above) */
650                 i = rep[ off[n] + 1 ]   - '0';
651                 len += str[i].rm_eo - str[i].rm_so;
652                 n++;
653         }
654         out->bv_val = slap_sl_malloc( len + 1, ctx );
655         out->bv_len = len;
656
657         /* Fill in URI with replace string, replacing $i as we go */
658         n=1;
659         insert = 0;
660         while( off[n] >= 0) {
661                 /* Paste in next section from replacement string (x,y,z above) */
662                 len = off[n] - off[n-1] - 2;
663                 strncpy( out->bv_val+insert, rep + off[n-1] + 2, len);
664                 insert += len;
665                 if( off[n+1] < 0)
666                         break;
667
668                 /* Paste in string from saslname that matched next $i  (b,d above) */
669                 i = rep[ off[n] + 1 ]   - '0';
670                 len = str[i].rm_eo - str[i].rm_so;
671                 strncpy( out->bv_val+insert, saslname + str[i].rm_so, len );
672                 insert += len;
673
674                 n++;
675         }
676
677         out->bv_val[insert] = '\0';
678 }
679
680 /* Take the passed in SASL name and attempt to convert it into an
681    LDAP URI to find the matching LDAP entry, using the pattern matching
682    strings given in the saslregexp config file directive(s) */
683
684 static int slap_authz_regexp( struct berval *in, struct berval *out,
685                 int flags, void *ctx )
686 {
687 #ifdef SLAP_AUTH_REWRITE
688         const char      *context = AUTHID_CONTEXT;
689
690         if ( sasl_rwinfo == NULL || BER_BVISNULL( in ) ) {
691                 return 0;
692         }
693
694         /* FIXME: if aware of authc/authz mapping, 
695          * we could use different contexts ... */
696         switch ( rewrite_session( sasl_rwinfo, context, in->bv_val, NULL, 
697                                 &out->bv_val ) )
698         {
699         case REWRITE_REGEXEC_OK:
700                 if ( !BER_BVISNULL( out ) ) {
701                         char *val = out->bv_val;
702                         ber_str2bv_x( val, 0, 1, out, ctx );
703                         free( val );
704                 } else {
705                         ber_dupbv_x( out, in, ctx );
706                 }
707                 Debug( LDAP_DEBUG_ARGS,
708                         "[rw] %s: \"%s\" -> \"%s\"\n",
709                         context, in->bv_val, out->bv_val );             
710                 return 1;
711                 
712         case REWRITE_REGEXEC_UNWILLING:
713         case REWRITE_REGEXEC_ERR:
714         default:
715                 return 0;
716         }
717
718 #else /* ! SLAP_AUTH_REWRITE */
719         char *saslname = in->bv_val;
720         SaslRegexp_t *reg;
721         regmatch_t sr_strings[SASLREGEX_REPLACE];       /* strings matching $1,$2 ... */
722         int i;
723
724         memset( out, 0, sizeof( *out ) );
725
726         Debug( LDAP_DEBUG_TRACE, "slap_authz_regexp: converting SASL name %s\n",
727            saslname, 0, 0 );
728
729         if (( saslname == NULL ) || ( nSaslRegexp == 0 )) {
730                 return( 0 );
731         }
732
733         /* Match the normalized SASL name to the saslregexp patterns */
734         for( reg = SaslRegexp,i=0;  i<nSaslRegexp;  i++,reg++ ) {
735                 if ( regexec( &reg->sr_workspace, saslname, SASLREGEX_REPLACE,
736                   sr_strings, 0)  == 0 )
737                         break;
738         }
739
740         if( i >= nSaslRegexp ) return( 0 );
741
742         /*
743          * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
744          * replace pattern of the form "x$1y$2z". The returned string needs
745          * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
746          */
747         slap_sasl_rx_exp( reg->sr_replace, reg->sr_offset,
748                 sr_strings, saslname, out, ctx );
749
750         Debug( LDAP_DEBUG_TRACE,
751                 "slap_authz_regexp: converted SASL name to %s\n",
752                 BER_BVISEMPTY( out ) ? "" : out->bv_val, 0, 0 );
753
754         return( 1 );
755 #endif /* ! SLAP_AUTH_REWRITE */
756 }
757
758 /* This callback actually does some work...*/
759 static int sasl_sc_sasl2dn( Operation *o, SlapReply *rs )
760 {
761         struct berval *ndn = o->o_callback->sc_private;
762
763         if (rs->sr_type != REP_SEARCH) return 0;
764
765         /* We only want to be called once */
766         if ( !BER_BVISNULL( ndn ) ) {
767                 o->o_tmpfree(ndn->bv_val, o->o_tmpmemctx);
768                 BER_BVZERO( ndn );
769
770                 Debug( LDAP_DEBUG_TRACE,
771                         "slap_sc_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
772                 return -1;
773         }
774
775         ber_dupbv_x(ndn, &rs->sr_entry->e_nname, o->o_tmpmemctx);
776         return 0;
777 }
778
779
780 typedef struct smatch_info {
781         struct berval *dn;
782         int match;
783 } smatch_info;
784
785 static int sasl_sc_smatch( Operation *o, SlapReply *rs )
786 {
787         smatch_info *sm = o->o_callback->sc_private;
788
789         if ( rs->sr_type != REP_SEARCH ) {
790                 if ( rs->sr_err != LDAP_SUCCESS ) {
791                         sm->match = -1;
792                 }
793                 return 0;
794         }
795
796         if ( sm->match == 1 ) {
797                 sm->match = -1;
798                 return 0;
799         }
800
801         if (dn_match(sm->dn, &rs->sr_entry->e_nname)) {
802                 sm->match = 1;
803
804         } else {
805                 sm->match = -1;
806         }
807
808         return 0;
809 }
810
811 int
812 slap_sasl_matches( Operation *op, BerVarray rules,
813                 struct berval *assertDN, struct berval *authc )
814 {
815         int     rc = LDAP_INAPPROPRIATE_AUTH;
816
817         if ( rules != NULL ) {
818                 int     i;
819
820                 for( i = 0; !BER_BVISNULL( &rules[i] ); i++ ) {
821                         rc = slap_sasl_match( op, &rules[i], assertDN, authc );
822                         if ( rc == LDAP_SUCCESS ) break;
823                 }
824         }
825         
826         return rc;
827 }
828
829 /*
830  * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
831  * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
832  * the rule must be used as an internal search for entries. If that search
833  * returns the *assertDN entry, the match is successful.
834  *
835  * The assertDN should not have the dn: prefix
836  */
837
838 static
839 int slap_sasl_match( Operation *opx, struct berval *rule,
840         struct berval *assertDN, struct berval *authc )
841 {
842         int rc; 
843         regex_t reg;
844         smatch_info sm;
845         slap_callback cb = { NULL, sasl_sc_smatch, NULL, NULL };
846         Operation op = {0};
847         SlapReply rs = {REP_RESULT};
848         struct berval base = BER_BVNULL;
849
850         sm.dn = assertDN;
851         sm.match = 0;
852         cb.sc_private = &sm;
853
854         Debug( LDAP_DEBUG_TRACE,
855            "===>slap_sasl_match: comparing DN %s to rule %s\n",
856                 assertDN->bv_val, rule->bv_val, 0 );
857
858         rc = slap_parseURI( opx, rule, &base,
859                 &op.o_req_ndn, &op.ors_scope, &op.ors_filter,
860                 &op.ors_filterstr );
861         if( rc != LDAP_SUCCESS ) goto CONCLUDED;
862
863         switch ( op.ors_scope ) {
864         case LDAP_X_SCOPE_EXACT:
865 exact_match:
866                 if ( dn_match( &op.o_req_ndn, assertDN ) ) {
867                         rc = LDAP_SUCCESS;
868                 } else {
869                         rc = LDAP_INAPPROPRIATE_AUTH;
870                 }
871                 goto CONCLUDED;
872
873         case LDAP_X_SCOPE_CHILDREN:
874         case LDAP_X_SCOPE_SUBTREE:
875         case LDAP_X_SCOPE_ONELEVEL:
876         {
877                 int     d = assertDN->bv_len - op.o_req_ndn.bv_len;
878
879                 rc = LDAP_INAPPROPRIATE_AUTH;
880
881                 if ( d == 0 && op.ors_scope == LDAP_X_SCOPE_SUBTREE ) {
882                         goto exact_match;
883
884                 } else if ( d > 0 ) {
885                         struct berval bv;
886
887                         /* leave room for at least one char of attributeType,
888                          * one for '=' and one for ',' */
889                         if ( d < STRLENOF( "x=,") ) {
890                                 goto CONCLUDED;
891                         }
892
893                         bv.bv_len = op.o_req_ndn.bv_len;
894                         bv.bv_val = assertDN->bv_val + d;
895
896                         if ( bv.bv_val[ -1 ] == ',' && dn_match( &op.o_req_ndn, &bv ) ) {
897                                 switch ( op.ors_scope ) {
898                                 case LDAP_X_SCOPE_SUBTREE:
899                                 case LDAP_X_SCOPE_CHILDREN:
900                                         rc = LDAP_SUCCESS;
901                                         break;
902
903                                 case LDAP_X_SCOPE_ONELEVEL:
904                                 {
905                                         struct berval   pdn;
906
907                                         dnParent( assertDN, &pdn );
908                                         /* the common portion of the DN
909                                          * already matches, so only check
910                                          * if parent DN of assertedDN 
911                                          * is all the pattern */
912                                         if ( pdn.bv_len == op.o_req_ndn.bv_len ) {
913                                                 rc = LDAP_SUCCESS;
914                                         }
915                                         break;
916                                 }
917                                 default:
918                                         /* at present, impossible */
919                                         assert( 0 );
920                                 }
921                         }
922                 }
923                 goto CONCLUDED;
924         }
925
926         case LDAP_X_SCOPE_REGEX:
927                 rc = regcomp(&reg, op.o_req_ndn.bv_val,
928                         REG_EXTENDED|REG_ICASE|REG_NOSUB);
929                 if ( rc == 0 ) {
930                         rc = regexec(&reg, assertDN->bv_val, 0, NULL, 0);
931                         regfree( &reg );
932                 }
933                 if ( rc == 0 ) {
934                         rc = LDAP_SUCCESS;
935                 } else {
936                         rc = LDAP_INAPPROPRIATE_AUTH;
937                 }
938                 goto CONCLUDED;
939
940         case LDAP_X_SCOPE_GROUP: {
941                 char    *tmp;
942
943                 /* Now filterstr looks like "(&(objectClass=<group_oc>)(<member_at>="
944                  * we need to append the <assertDN> so that the <group_dn> is searched
945                  * with scope "base", and the filter ensures that <assertDN> is
946                  * member of the group */
947                 tmp = ch_realloc( op.ors_filterstr.bv_val, op.ors_filterstr.bv_len +
948                         assertDN->bv_len + STRLENOF( /*"(("*/ "))" ) + 1 );
949                 if ( tmp == NULL ) {
950                         rc = LDAP_NO_MEMORY;
951                         goto CONCLUDED;
952                 }
953                 op.ors_filterstr.bv_val = tmp;
954                 
955                 tmp = lutil_strcopy( &tmp[op.ors_filterstr.bv_len], assertDN->bv_val );
956                 tmp = lutil_strcopy( tmp, /*"(("*/ "))" );
957
958                 /* pass opx because str2filter_x may (and does) use o_tmpmfuncs */
959                 op.ors_filter = str2filter_x( opx, op.ors_filterstr.bv_val );
960                 if ( op.ors_filter == NULL ) {
961                         rc = LDAP_PROTOCOL_ERROR;
962                         goto CONCLUDED;
963                 }
964                 op.ors_scope = LDAP_SCOPE_BASE;
965
966                 /* hijack match DN: use that of the group instead of the assertDN;
967                  * assertDN is now in the filter */
968                 sm.dn = &op.o_req_ndn;
969
970                 /* do the search */
971                 break;
972                 }
973
974         case LDAP_X_SCOPE_USERS:
975                 if ( !BER_BVISEMPTY( assertDN ) ) {
976                         rc = LDAP_SUCCESS;
977                 } else {
978                         rc = LDAP_INAPPROPRIATE_AUTH;
979                 }
980                 goto CONCLUDED;
981
982         default:
983                 break;
984         }
985
986         /* Must run an internal search. */
987         if ( op.ors_filter == NULL ) {
988                 rc = LDAP_FILTER_ERROR;
989                 goto CONCLUDED;
990         }
991
992         Debug( LDAP_DEBUG_TRACE,
993            "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
994            op.o_req_ndn.bv_val, op.ors_scope, 0 );
995
996         op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
997         if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
998                 rc = LDAP_INAPPROPRIATE_AUTH;
999                 goto CONCLUDED;
1000         }
1001
1002         op.o_hdr = opx->o_hdr;
1003         op.o_tag = LDAP_REQ_SEARCH;
1004         op.o_ndn = *authc;
1005         op.o_callback = &cb;
1006         op.o_time = slap_get_time();
1007         op.o_do_not_cache = 1;
1008         op.o_is_auth_check = 1;
1009         /* use req_ndn as req_dn instead of non-pretty base of uri */
1010         if( !BER_BVISNULL( &base ) ) {
1011                 ch_free( base.bv_val );
1012                 /* just in case... */
1013                 BER_BVZERO( &base );
1014         }
1015         ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1016         op.ors_slimit = 1;
1017         op.ors_tlimit = SLAP_NO_LIMIT;
1018         op.ors_attrs = slap_anlist_no_attrs;
1019         op.ors_attrsonly = 1;
1020
1021         op.o_bd->be_search( &op, &rs );
1022
1023         if (sm.match == 1) {
1024                 rc = LDAP_SUCCESS;
1025         } else {
1026                 rc = LDAP_INAPPROPRIATE_AUTH;
1027         }
1028
1029 CONCLUDED:
1030         if( !BER_BVISNULL( &op.o_req_dn ) ) slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1031         if( !BER_BVISNULL( &op.o_req_ndn ) ) slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1032         if( op.ors_filter ) filter_free_x( opx, op.ors_filter );
1033         if( !BER_BVISNULL( &op.ors_filterstr ) ) ch_free( op.ors_filterstr.bv_val );
1034
1035         Debug( LDAP_DEBUG_TRACE,
1036            "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
1037
1038         return( rc );
1039 }
1040
1041
1042 /*
1043  * This function answers the question, "Can this ID authorize to that ID?",
1044  * based on authorization rules. The rules are stored in the *searchDN, in the
1045  * attribute named by *attr. If any of those rules map to the *assertDN, the
1046  * authorization is approved.
1047  *
1048  * The DNs should not have the dn: prefix
1049  */
1050 static int
1051 slap_sasl_check_authz( Operation *op,
1052         struct berval *searchDN,
1053         struct berval *assertDN,
1054         AttributeDescription *ad,
1055         struct berval *authc )
1056 {
1057         int i, rc;
1058         BerVarray vals = NULL;
1059
1060         Debug( LDAP_DEBUG_TRACE,
1061            "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
1062            assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
1063
1064         rc = backend_attribute( op, NULL, searchDN, ad, &vals, ACL_AUTH );
1065         if( rc != LDAP_SUCCESS ) goto COMPLETE;
1066
1067         /* Check if the *assertDN matches any *vals */
1068         rc = slap_sasl_matches( op, vals, assertDN, authc );
1069
1070 COMPLETE:
1071         if( vals ) ber_bvarray_free_x( vals, op->o_tmpmemctx );
1072
1073         Debug( LDAP_DEBUG_TRACE,
1074            "<==slap_sasl_check_authz: %s check returning %d\n",
1075                 ad->ad_cname.bv_val, rc, 0);
1076
1077         return( rc );
1078 }
1079
1080 /*
1081  * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
1082  * return the LDAP DN to which it matches. The SASL regexp rules in the config
1083  * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
1084  * search with scope=base), just return the URI (or its searchbase). Otherwise
1085  * an internal search must be done, and if that search returns exactly one
1086  * entry, return the DN of that one entry.
1087  */
1088 void slap_sasl2dn( Operation *opx,
1089         struct berval *saslname, struct berval *sasldn, int flags )
1090 {
1091         int rc;
1092         slap_callback cb = { NULL, sasl_sc_sasl2dn, NULL, NULL };
1093         Operation op = {0};
1094         SlapReply rs = {REP_RESULT};
1095         struct berval regout = BER_BVNULL;
1096         struct berval base = BER_BVNULL;
1097
1098         Debug( LDAP_DEBUG_TRACE, "==>slap_sasl2dn: "
1099                 "converting SASL name %s to a DN\n",
1100                 saslname->bv_val, 0,0 );
1101
1102         sasldn->bv_val = NULL;
1103         sasldn->bv_len = 0;
1104         cb.sc_private = sasldn;
1105
1106         /* Convert the SASL name into a minimal URI */
1107         if( !slap_authz_regexp( saslname, &regout, flags, opx->o_tmpmemctx ) ) {
1108                 goto FINISHED;
1109         }
1110
1111         rc = slap_parseURI( opx, &regout, &base,
1112                 &op.o_req_ndn, &op.ors_scope, &op.ors_filter,
1113                 &op.ors_filterstr );
1114         if ( !BER_BVISNULL( &regout ) ) slap_sl_free( regout.bv_val, opx->o_tmpmemctx );
1115         if ( rc != LDAP_SUCCESS ) {
1116                 goto FINISHED;
1117         }
1118
1119         /* Must do an internal search */
1120         op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
1121
1122         switch ( op.ors_scope ) {
1123         case LDAP_X_SCOPE_EXACT:
1124                 *sasldn = op.o_req_ndn;
1125                 BER_BVZERO( &op.o_req_ndn );
1126                 /* intentionally continue to next case */
1127
1128         case LDAP_X_SCOPE_REGEX:
1129         case LDAP_X_SCOPE_SUBTREE:
1130         case LDAP_X_SCOPE_CHILDREN:
1131         case LDAP_X_SCOPE_ONELEVEL:
1132         case LDAP_X_SCOPE_GROUP:
1133         case LDAP_X_SCOPE_USERS:
1134                 /* correctly parsed, but illegal */
1135                 goto FINISHED;
1136
1137         case LDAP_SCOPE_BASE:
1138         case LDAP_SCOPE_ONELEVEL:
1139         case LDAP_SCOPE_SUBTREE:
1140 #ifdef LDAP_SCOPE_SUBORDINATE
1141         case LDAP_SCOPE_SUBORDINATE:
1142 #endif
1143                 /* do a search */
1144                 break;
1145
1146         default:
1147                 /* catch unhandled cases (there shouldn't be) */
1148                 assert( 0 );
1149         }
1150
1151         Debug( LDAP_DEBUG_TRACE,
1152                 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
1153                 op.o_req_ndn.bv_val, op.ors_scope, 0 );
1154
1155         if ( ( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL) ) {
1156                 goto FINISHED;
1157         }
1158
1159         /* Must run an internal search. */
1160         if ( op.ors_filter == NULL ) {
1161                 rc = LDAP_FILTER_ERROR;
1162                 goto FINISHED;
1163         }
1164
1165         op.o_hdr = opx->o_hdr;
1166         op.o_tag = LDAP_REQ_SEARCH;
1167         op.o_ndn = opx->o_conn->c_ndn;
1168         op.o_callback = &cb;
1169         op.o_time = slap_get_time();
1170         op.o_do_not_cache = 1;
1171         op.o_is_auth_check = 1;
1172         op.ors_deref = LDAP_DEREF_NEVER;
1173         op.ors_slimit = 1;
1174         op.ors_tlimit = SLAP_NO_LIMIT;
1175         op.ors_attrs = slap_anlist_no_attrs;
1176         op.ors_attrsonly = 1;
1177         /* use req_ndn as req_dn instead of non-pretty base of uri */
1178         if( !BER_BVISNULL( &base ) ) {
1179                 ch_free( base.bv_val );
1180                 /* just in case... */
1181                 BER_BVZERO( &base );
1182         }
1183         ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1184
1185         op.o_bd->be_search( &op, &rs );
1186         
1187 FINISHED:
1188         if( !BER_BVISEMPTY( sasldn ) ) {
1189                 opx->o_conn->c_authz_backend = op.o_bd;
1190         }
1191         if( !BER_BVISNULL( &op.o_req_dn ) ) {
1192                 slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1193         }
1194         if( !BER_BVISNULL( &op.o_req_ndn ) ) {
1195                 slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1196         }
1197         if( op.ors_filter ) {
1198                 filter_free_x( opx, op.ors_filter );
1199         }
1200         if( !BER_BVISNULL( &op.ors_filterstr ) ) {
1201                 ch_free( op.ors_filterstr.bv_val );
1202         }
1203
1204         Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
1205                 !BER_BVISEMPTY( sasldn ) ? sasldn->bv_val : "<nothing>", 0, 0 );
1206
1207         return;
1208 }
1209
1210
1211 /* Check if a bind can SASL authorize to another identity.
1212  * The DNs should not have the dn: prefix
1213  */
1214
1215 int slap_sasl_authorized( Operation *op,
1216         struct berval *authcDN, struct berval *authzDN )
1217 {
1218         int rc = LDAP_INAPPROPRIATE_AUTH;
1219
1220         /* User binding as anonymous */
1221         if ( authzDN == NULL ) {
1222                 rc = LDAP_SUCCESS;
1223                 goto DONE;
1224         }
1225
1226         Debug( LDAP_DEBUG_TRACE,
1227            "==>slap_sasl_authorized: can %s become %s?\n",
1228                 authcDN->bv_val, authzDN->bv_val, 0 );
1229
1230         /* If person is authorizing to self, succeed */
1231         if ( dn_match( authcDN, authzDN ) ) {
1232                 rc = LDAP_SUCCESS;
1233                 goto DONE;
1234         }
1235
1236         /* Allow the manager to authorize as any DN. */
1237         if( op->o_conn->c_authz_backend &&
1238                 be_isroot_dn( op->o_conn->c_authz_backend, authcDN ))
1239         {
1240                 rc = LDAP_SUCCESS;
1241                 goto DONE;
1242         }
1243
1244         /* Check source rules */
1245         if( authz_policy & SASL_AUTHZ_TO ) {
1246                 rc = slap_sasl_check_authz( op, authcDN, authzDN,
1247                         slap_schema.si_ad_saslAuthzTo, authcDN );
1248                 if( rc == LDAP_SUCCESS && !(authz_policy & SASL_AUTHZ_AND) ) {
1249                         goto DONE;
1250                 }
1251         }
1252
1253         /* Check destination rules */
1254         if( authz_policy & SASL_AUTHZ_FROM ) {
1255                 rc = slap_sasl_check_authz( op, authzDN, authcDN,
1256                         slap_schema.si_ad_saslAuthzFrom, authcDN );
1257                 if( rc == LDAP_SUCCESS ) {
1258                         goto DONE;
1259                 }
1260         }
1261
1262         rc = LDAP_INAPPROPRIATE_AUTH;
1263
1264 DONE:
1265
1266         Debug( LDAP_DEBUG_TRACE,
1267                 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );
1268
1269         return( rc );
1270 }