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