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