]> git.sur5r.net Git - openldap/blob - servers/slapd/saslauthz.c
Happy New Year!
[openldap] / servers / slapd / saslauthz.c
1 /* $OpenLDAP$ */
2 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
3  *
4  * Copyright 1998-2005 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                         if ( val != in->bv_val ) {
704                                 free( val );
705                         }
706                 } else {
707                         ber_dupbv_x( out, in, ctx );
708                 }
709                 Debug( LDAP_DEBUG_ARGS,
710                         "[rw] %s: \"%s\" -> \"%s\"\n",
711                         context, in->bv_val, out->bv_val );             
712                 return 1;
713                 
714         case REWRITE_REGEXEC_UNWILLING:
715         case REWRITE_REGEXEC_ERR:
716         default:
717                 return 0;
718         }
719
720 #else /* ! SLAP_AUTH_REWRITE */
721         char *saslname = in->bv_val;
722         SaslRegexp_t *reg;
723         regmatch_t sr_strings[SASLREGEX_REPLACE];       /* strings matching $1,$2 ... */
724         int i;
725
726         memset( out, 0, sizeof( *out ) );
727
728         Debug( LDAP_DEBUG_TRACE, "slap_authz_regexp: converting SASL name %s\n",
729            saslname, 0, 0 );
730
731         if (( saslname == NULL ) || ( nSaslRegexp == 0 )) {
732                 return( 0 );
733         }
734
735         /* Match the normalized SASL name to the saslregexp patterns */
736         for( reg = SaslRegexp,i=0;  i<nSaslRegexp;  i++,reg++ ) {
737                 if ( regexec( &reg->sr_workspace, saslname, SASLREGEX_REPLACE,
738                   sr_strings, 0)  == 0 )
739                         break;
740         }
741
742         if( i >= nSaslRegexp ) return( 0 );
743
744         /*
745          * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
746          * replace pattern of the form "x$1y$2z". The returned string needs
747          * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
748          */
749         slap_sasl_rx_exp( reg->sr_replace, reg->sr_offset,
750                 sr_strings, saslname, out, ctx );
751
752         Debug( LDAP_DEBUG_TRACE,
753                 "slap_authz_regexp: converted SASL name to %s\n",
754                 BER_BVISEMPTY( out ) ? "" : out->bv_val, 0, 0 );
755
756         return( 1 );
757 #endif /* ! SLAP_AUTH_REWRITE */
758 }
759
760 /* This callback actually does some work...*/
761 static int sasl_sc_sasl2dn( Operation *o, SlapReply *rs )
762 {
763         struct berval *ndn = o->o_callback->sc_private;
764
765         if (rs->sr_type != REP_SEARCH) return 0;
766
767         /* We only want to be called once */
768         if ( !BER_BVISNULL( ndn ) ) {
769                 o->o_tmpfree(ndn->bv_val, o->o_tmpmemctx);
770                 BER_BVZERO( ndn );
771
772                 Debug( LDAP_DEBUG_TRACE,
773                         "slap_sc_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
774                 return -1;
775         }
776
777         ber_dupbv_x(ndn, &rs->sr_entry->e_nname, o->o_tmpmemctx);
778         return 0;
779 }
780
781
782 typedef struct smatch_info {
783         struct berval *dn;
784         int match;
785 } smatch_info;
786
787 static int sasl_sc_smatch( Operation *o, SlapReply *rs )
788 {
789         smatch_info *sm = o->o_callback->sc_private;
790
791         if ( rs->sr_type != REP_SEARCH ) {
792                 if ( rs->sr_err != LDAP_SUCCESS ) {
793                         sm->match = -1;
794                 }
795                 return 0;
796         }
797
798         if ( sm->match == 1 ) {
799                 sm->match = -1;
800                 return 0;
801         }
802
803         if (dn_match(sm->dn, &rs->sr_entry->e_nname)) {
804                 sm->match = 1;
805
806         } else {
807                 sm->match = -1;
808         }
809
810         return 0;
811 }
812
813 int
814 slap_sasl_matches( Operation *op, BerVarray rules,
815                 struct berval *assertDN, struct berval *authc )
816 {
817         int     rc = LDAP_INAPPROPRIATE_AUTH;
818
819         if ( rules != NULL ) {
820                 int     i;
821
822                 for( i = 0; !BER_BVISNULL( &rules[i] ); i++ ) {
823                         rc = slap_sasl_match( op, &rules[i], assertDN, authc );
824                         if ( rc == LDAP_SUCCESS ) break;
825                 }
826         }
827         
828         return rc;
829 }
830
831 /*
832  * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
833  * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
834  * the rule must be used as an internal search for entries. If that search
835  * returns the *assertDN entry, the match is successful.
836  *
837  * The assertDN should not have the dn: prefix
838  */
839
840 static
841 int slap_sasl_match( Operation *opx, struct berval *rule,
842         struct berval *assertDN, struct berval *authc )
843 {
844         int rc; 
845         regex_t reg;
846         smatch_info sm;
847         slap_callback cb = { NULL, sasl_sc_smatch, NULL, NULL };
848         Operation op = {0};
849         SlapReply rs = {REP_RESULT};
850         struct berval base = BER_BVNULL;
851
852         sm.dn = assertDN;
853         sm.match = 0;
854         cb.sc_private = &sm;
855
856         Debug( LDAP_DEBUG_TRACE,
857            "===>slap_sasl_match: comparing DN %s to rule %s\n",
858                 assertDN->bv_val, rule->bv_val, 0 );
859
860         rc = slap_parseURI( opx, rule, &base,
861                 &op.o_req_ndn, &op.ors_scope, &op.ors_filter,
862                 &op.ors_filterstr );
863         if( rc != LDAP_SUCCESS ) goto CONCLUDED;
864
865         switch ( op.ors_scope ) {
866         case LDAP_X_SCOPE_EXACT:
867 exact_match:
868                 if ( dn_match( &op.o_req_ndn, assertDN ) ) {
869                         rc = LDAP_SUCCESS;
870                 } else {
871                         rc = LDAP_INAPPROPRIATE_AUTH;
872                 }
873                 goto CONCLUDED;
874
875         case LDAP_X_SCOPE_CHILDREN:
876         case LDAP_X_SCOPE_SUBTREE:
877         case LDAP_X_SCOPE_ONELEVEL:
878         {
879                 int     d = assertDN->bv_len - op.o_req_ndn.bv_len;
880
881                 rc = LDAP_INAPPROPRIATE_AUTH;
882
883                 if ( d == 0 && op.ors_scope == LDAP_X_SCOPE_SUBTREE ) {
884                         goto exact_match;
885
886                 } else if ( d > 0 ) {
887                         struct berval bv;
888
889                         /* leave room for at least one char of attributeType,
890                          * one for '=' and one for ',' */
891                         if ( d < STRLENOF( "x=,") ) {
892                                 goto CONCLUDED;
893                         }
894
895                         bv.bv_len = op.o_req_ndn.bv_len;
896                         bv.bv_val = assertDN->bv_val + d;
897
898                         if ( bv.bv_val[ -1 ] == ',' && dn_match( &op.o_req_ndn, &bv ) ) {
899                                 switch ( op.ors_scope ) {
900                                 case LDAP_X_SCOPE_SUBTREE:
901                                 case LDAP_X_SCOPE_CHILDREN:
902                                         rc = LDAP_SUCCESS;
903                                         break;
904
905                                 case LDAP_X_SCOPE_ONELEVEL:
906                                 {
907                                         struct berval   pdn;
908
909                                         dnParent( assertDN, &pdn );
910                                         /* the common portion of the DN
911                                          * already matches, so only check
912                                          * if parent DN of assertedDN 
913                                          * is all the pattern */
914                                         if ( pdn.bv_len == op.o_req_ndn.bv_len ) {
915                                                 rc = LDAP_SUCCESS;
916                                         }
917                                         break;
918                                 }
919                                 default:
920                                         /* at present, impossible */
921                                         assert( 0 );
922                                 }
923                         }
924                 }
925                 goto CONCLUDED;
926         }
927
928         case LDAP_X_SCOPE_REGEX:
929                 rc = regcomp(&reg, op.o_req_ndn.bv_val,
930                         REG_EXTENDED|REG_ICASE|REG_NOSUB);
931                 if ( rc == 0 ) {
932                         rc = regexec(&reg, assertDN->bv_val, 0, NULL, 0);
933                         regfree( &reg );
934                 }
935                 if ( rc == 0 ) {
936                         rc = LDAP_SUCCESS;
937                 } else {
938                         rc = LDAP_INAPPROPRIATE_AUTH;
939                 }
940                 goto CONCLUDED;
941
942         case LDAP_X_SCOPE_GROUP: {
943                 char    *tmp;
944
945                 /* Now filterstr looks like "(&(objectClass=<group_oc>)(<member_at>="
946                  * we need to append the <assertDN> so that the <group_dn> is searched
947                  * with scope "base", and the filter ensures that <assertDN> is
948                  * member of the group */
949                 tmp = ch_realloc( op.ors_filterstr.bv_val, op.ors_filterstr.bv_len +
950                         assertDN->bv_len + STRLENOF( /*"(("*/ "))" ) + 1 );
951                 if ( tmp == NULL ) {
952                         rc = LDAP_NO_MEMORY;
953                         goto CONCLUDED;
954                 }
955                 op.ors_filterstr.bv_val = tmp;
956                 
957                 tmp = lutil_strcopy( &tmp[op.ors_filterstr.bv_len], assertDN->bv_val );
958                 tmp = lutil_strcopy( tmp, /*"(("*/ "))" );
959
960                 /* pass opx because str2filter_x may (and does) use o_tmpmfuncs */
961                 op.ors_filter = str2filter_x( opx, op.ors_filterstr.bv_val );
962                 if ( op.ors_filter == NULL ) {
963                         rc = LDAP_PROTOCOL_ERROR;
964                         goto CONCLUDED;
965                 }
966                 op.ors_scope = LDAP_SCOPE_BASE;
967
968                 /* hijack match DN: use that of the group instead of the assertDN;
969                  * assertDN is now in the filter */
970                 sm.dn = &op.o_req_ndn;
971
972                 /* do the search */
973                 break;
974                 }
975
976         case LDAP_X_SCOPE_USERS:
977                 if ( !BER_BVISEMPTY( assertDN ) ) {
978                         rc = LDAP_SUCCESS;
979                 } else {
980                         rc = LDAP_INAPPROPRIATE_AUTH;
981                 }
982                 goto CONCLUDED;
983
984         default:
985                 break;
986         }
987
988         /* Must run an internal search. */
989         if ( op.ors_filter == NULL ) {
990                 rc = LDAP_FILTER_ERROR;
991                 goto CONCLUDED;
992         }
993
994         Debug( LDAP_DEBUG_TRACE,
995            "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
996            op.o_req_ndn.bv_val, op.ors_scope, 0 );
997
998         op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
999         if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
1000                 rc = LDAP_INAPPROPRIATE_AUTH;
1001                 goto CONCLUDED;
1002         }
1003
1004         op.o_hdr = opx->o_hdr;
1005         op.o_tag = LDAP_REQ_SEARCH;
1006         op.o_ndn = *authc;
1007         op.o_callback = &cb;
1008         op.o_time = slap_get_time();
1009         op.o_do_not_cache = 1;
1010         op.o_is_auth_check = 1;
1011         /* use req_ndn as req_dn instead of non-pretty base of uri */
1012         if( !BER_BVISNULL( &base ) ) {
1013                 ch_free( base.bv_val );
1014                 /* just in case... */
1015                 BER_BVZERO( &base );
1016         }
1017         ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1018         op.ors_slimit = 1;
1019         op.ors_tlimit = SLAP_NO_LIMIT;
1020         op.ors_attrs = slap_anlist_no_attrs;
1021         op.ors_attrsonly = 1;
1022
1023         op.o_bd->be_search( &op, &rs );
1024
1025         if (sm.match == 1) {
1026                 rc = LDAP_SUCCESS;
1027         } else {
1028                 rc = LDAP_INAPPROPRIATE_AUTH;
1029         }
1030
1031 CONCLUDED:
1032         if( !BER_BVISNULL( &op.o_req_dn ) ) slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1033         if( !BER_BVISNULL( &op.o_req_ndn ) ) slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1034         if( op.ors_filter ) filter_free_x( opx, op.ors_filter );
1035         if( !BER_BVISNULL( &op.ors_filterstr ) ) ch_free( op.ors_filterstr.bv_val );
1036
1037         Debug( LDAP_DEBUG_TRACE,
1038            "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
1039
1040         return( rc );
1041 }
1042
1043
1044 /*
1045  * This function answers the question, "Can this ID authorize to that ID?",
1046  * based on authorization rules. The rules are stored in the *searchDN, in the
1047  * attribute named by *attr. If any of those rules map to the *assertDN, the
1048  * authorization is approved.
1049  *
1050  * The DNs should not have the dn: prefix
1051  */
1052 static int
1053 slap_sasl_check_authz( Operation *op,
1054         struct berval *searchDN,
1055         struct berval *assertDN,
1056         AttributeDescription *ad,
1057         struct berval *authc )
1058 {
1059         int i, rc;
1060         BerVarray vals = NULL;
1061
1062         Debug( LDAP_DEBUG_TRACE,
1063            "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
1064            assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
1065
1066         rc = backend_attribute( op, NULL, searchDN, ad, &vals, ACL_AUTH );
1067         if( rc != LDAP_SUCCESS ) goto COMPLETE;
1068
1069         /* Check if the *assertDN matches any *vals */
1070         rc = slap_sasl_matches( op, vals, assertDN, authc );
1071
1072 COMPLETE:
1073         if( vals ) ber_bvarray_free_x( vals, op->o_tmpmemctx );
1074
1075         Debug( LDAP_DEBUG_TRACE,
1076            "<==slap_sasl_check_authz: %s check returning %d\n",
1077                 ad->ad_cname.bv_val, rc, 0);
1078
1079         return( rc );
1080 }
1081
1082 /*
1083  * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
1084  * return the LDAP DN to which it matches. The SASL regexp rules in the config
1085  * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
1086  * search with scope=base), just return the URI (or its searchbase). Otherwise
1087  * an internal search must be done, and if that search returns exactly one
1088  * entry, return the DN of that one entry.
1089  */
1090 void slap_sasl2dn( Operation *opx,
1091         struct berval *saslname, struct berval *sasldn, int flags )
1092 {
1093         int rc;
1094         slap_callback cb = { NULL, sasl_sc_sasl2dn, NULL, NULL };
1095         Operation op = {0};
1096         SlapReply rs = {REP_RESULT};
1097         struct berval regout = BER_BVNULL;
1098         struct berval base = BER_BVNULL;
1099
1100         Debug( LDAP_DEBUG_TRACE, "==>slap_sasl2dn: "
1101                 "converting SASL name %s to a DN\n",
1102                 saslname->bv_val, 0,0 );
1103
1104         sasldn->bv_val = NULL;
1105         sasldn->bv_len = 0;
1106         cb.sc_private = sasldn;
1107
1108         /* Convert the SASL name into a minimal URI */
1109         if( !slap_authz_regexp( saslname, &regout, flags, opx->o_tmpmemctx ) ) {
1110                 goto FINISHED;
1111         }
1112
1113         rc = slap_parseURI( opx, &regout, &base,
1114                 &op.o_req_ndn, &op.ors_scope, &op.ors_filter,
1115                 &op.ors_filterstr );
1116         if ( !BER_BVISNULL( &regout ) ) slap_sl_free( regout.bv_val, opx->o_tmpmemctx );
1117         if ( rc != LDAP_SUCCESS ) {
1118                 goto FINISHED;
1119         }
1120
1121         /* Must do an internal search */
1122         op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
1123
1124         switch ( op.ors_scope ) {
1125         case LDAP_X_SCOPE_EXACT:
1126                 *sasldn = op.o_req_ndn;
1127                 BER_BVZERO( &op.o_req_ndn );
1128                 /* intentionally continue to next case */
1129
1130         case LDAP_X_SCOPE_REGEX:
1131         case LDAP_X_SCOPE_SUBTREE:
1132         case LDAP_X_SCOPE_CHILDREN:
1133         case LDAP_X_SCOPE_ONELEVEL:
1134         case LDAP_X_SCOPE_GROUP:
1135         case LDAP_X_SCOPE_USERS:
1136                 /* correctly parsed, but illegal */
1137                 goto FINISHED;
1138
1139         case LDAP_SCOPE_BASE:
1140         case LDAP_SCOPE_ONELEVEL:
1141         case LDAP_SCOPE_SUBTREE:
1142 #ifdef LDAP_SCOPE_SUBORDINATE
1143         case LDAP_SCOPE_SUBORDINATE:
1144 #endif
1145                 /* do a search */
1146                 break;
1147
1148         default:
1149                 /* catch unhandled cases (there shouldn't be) */
1150                 assert( 0 );
1151         }
1152
1153         Debug( LDAP_DEBUG_TRACE,
1154                 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
1155                 op.o_req_ndn.bv_val, op.ors_scope, 0 );
1156
1157         if ( ( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL) ) {
1158                 goto FINISHED;
1159         }
1160
1161         /* Must run an internal search. */
1162         if ( op.ors_filter == NULL ) {
1163                 rc = LDAP_FILTER_ERROR;
1164                 goto FINISHED;
1165         }
1166
1167         op.o_hdr = opx->o_hdr;
1168         op.o_tag = LDAP_REQ_SEARCH;
1169         op.o_ndn = opx->o_conn->c_ndn;
1170         op.o_callback = &cb;
1171         op.o_time = slap_get_time();
1172         op.o_do_not_cache = 1;
1173         op.o_is_auth_check = 1;
1174         op.ors_deref = LDAP_DEREF_NEVER;
1175         op.ors_slimit = 1;
1176         op.ors_tlimit = SLAP_NO_LIMIT;
1177         op.ors_attrs = slap_anlist_no_attrs;
1178         op.ors_attrsonly = 1;
1179         /* use req_ndn as req_dn instead of non-pretty base of uri */
1180         if( !BER_BVISNULL( &base ) ) {
1181                 ch_free( base.bv_val );
1182                 /* just in case... */
1183                 BER_BVZERO( &base );
1184         }
1185         ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1186
1187         op.o_bd->be_search( &op, &rs );
1188         
1189 FINISHED:
1190         if( !BER_BVISEMPTY( sasldn ) ) {
1191                 opx->o_conn->c_authz_backend = op.o_bd;
1192         }
1193         if( !BER_BVISNULL( &op.o_req_dn ) ) {
1194                 slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1195         }
1196         if( !BER_BVISNULL( &op.o_req_ndn ) ) {
1197                 slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1198         }
1199         if( op.ors_filter ) {
1200                 filter_free_x( opx, op.ors_filter );
1201         }
1202         if( !BER_BVISNULL( &op.ors_filterstr ) ) {
1203                 ch_free( op.ors_filterstr.bv_val );
1204         }
1205
1206         Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
1207                 !BER_BVISEMPTY( sasldn ) ? sasldn->bv_val : "<nothing>", 0, 0 );
1208
1209         return;
1210 }
1211
1212
1213 /* Check if a bind can SASL authorize to another identity.
1214  * The DNs should not have the dn: prefix
1215  */
1216
1217 int slap_sasl_authorized( Operation *op,
1218         struct berval *authcDN, struct berval *authzDN )
1219 {
1220         int rc = LDAP_INAPPROPRIATE_AUTH;
1221
1222         /* User binding as anonymous */
1223         if ( authzDN == NULL ) {
1224                 rc = LDAP_SUCCESS;
1225                 goto DONE;
1226         }
1227
1228         Debug( LDAP_DEBUG_TRACE,
1229            "==>slap_sasl_authorized: can %s become %s?\n",
1230                 authcDN->bv_val, authzDN->bv_val, 0 );
1231
1232         /* If person is authorizing to self, succeed */
1233         if ( dn_match( authcDN, authzDN ) ) {
1234                 rc = LDAP_SUCCESS;
1235                 goto DONE;
1236         }
1237
1238         /* Allow the manager to authorize as any DN. */
1239         if( op->o_conn->c_authz_backend &&
1240                 be_isroot_dn( op->o_conn->c_authz_backend, authcDN ))
1241         {
1242                 rc = LDAP_SUCCESS;
1243                 goto DONE;
1244         }
1245
1246         /* Check source rules */
1247         if( authz_policy & SASL_AUTHZ_TO ) {
1248                 rc = slap_sasl_check_authz( op, authcDN, authzDN,
1249                         slap_schema.si_ad_saslAuthzTo, authcDN );
1250                 if( rc == LDAP_SUCCESS && !(authz_policy & SASL_AUTHZ_AND) ) {
1251                         goto DONE;
1252                 }
1253         }
1254
1255         /* Check destination rules */
1256         if( authz_policy & SASL_AUTHZ_FROM ) {
1257                 rc = slap_sasl_check_authz( op, authzDN, authcDN,
1258                         slap_schema.si_ad_saslAuthzFrom, authcDN );
1259                 if( rc == LDAP_SUCCESS ) {
1260                         goto DONE;
1261                 }
1262         }
1263
1264         rc = LDAP_INAPPROPRIATE_AUTH;
1265
1266 DONE:
1267
1268         Debug( LDAP_DEBUG_TRACE,
1269                 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );
1270
1271         return( rc );
1272 }