2 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
4 * Copyright 1998-2004 The OpenLDAP Foundation.
5 * Portions Copyright 2000 Mark Adamson, Carnegie Mellon.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted only as authorized by the OpenLDAP
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>.
21 #include <ac/stdlib.h>
22 #include <ac/string.h>
32 #define SASLREGEX_REPLACE 10
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)
43 * IDs in DNauthzid form can now have a type specifier, that
44 * influences how they are used in related operations.
46 * syntax: dn[.{exact|regex}]:<val>
48 * dn.exact: the value must pass normalization and is used
50 * dn.regex: the value is treated as a regular expression
51 * in matching DN values in authz{To|From}
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
59 * IDs in DNauthzid form can now have a type specifier, that
60 * influences how they are used in related operations.
62 * syntax: u[.mech[/realm]]:<val>
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).
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 */
76 static int nSaslRegexp = 0;
77 static SaslRegexp_t *SaslRegexp = NULL;
79 #ifdef SLAP_AUTH_REWRITE
81 struct rewrite_info *sasl_rwinfo = NULL;
82 #define AUTHID_CONTEXT "authid"
83 #endif /* SLAP_AUTH_REWRITE */
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
91 static int authz_policy = SASL_AUTHZ_NONE;
94 int slap_sasl_match( Operation *opx, struct berval *rule,
95 struct berval *assertDN, struct berval *authc );
97 int slap_sasl_setpolicy( const char *arg )
99 int rc = LDAP_SUCCESS;
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;
117 int slap_parse_user( struct berval *id, struct berval *user,
118 struct berval *realm, struct berval *mech )
123 assert( !BER_BVISNULL( id ) );
130 if ( u != 'u' && u != 'U' ) {
131 /* called with something other than u: */
132 return LDAP_PROTOCOL_ERROR;
136 * u[.mech[/realm]]:user
139 user->bv_val = strchr( id->bv_val, ':' );
140 if ( BER_BVISNULL( user ) ) {
141 return LDAP_PROTOCOL_ERROR;
143 user->bv_val[ 0 ] = '\0';
145 user->bv_len = id->bv_len - ( user->bv_val - id->bv_val );
147 mech->bv_val = strchr( id->bv_val, '.' );
148 if ( !BER_BVISNULL( mech ) ) {
149 mech->bv_val[ 0 ] = '\0';
152 realm->bv_val = strchr( mech->bv_val, '/' );
154 if ( !BER_BVISNULL( realm ) ) {
155 realm->bv_val[ 0 ] = '\0';
157 mech->bv_len = realm->bv_val - mech->bv_val - 1;
158 realm->bv_len = user->bv_val - realm->bv_val - 1;
160 mech->bv_len = user->bv_val - mech->bv_val - 1;
167 if ( id->bv_val[ 1 ] != '\0' ) {
168 return LDAP_PROTOCOL_ERROR;
171 if ( !BER_BVISNULL( mech ) ) {
172 assert( mech->bv_val == id->bv_val + 2 );
174 AC_MEMCPY( mech->bv_val - 2, mech->bv_val, mech->bv_len + 1 );
178 if ( !BER_BVISNULL( realm ) ) {
179 assert( realm->bv_val >= id->bv_val + 2 );
181 AC_MEMCPY( realm->bv_val - 2, realm->bv_val, realm->bv_len + 1 );
185 /* leave "u:" before user */
188 user->bv_val[ 0 ] = u;
189 user->bv_val[ 1 ] = ':';
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 )
202 assert( uri != NULL && !BER_BVISNULL( uri ) );
210 LDAP_LOG( TRANSPORT, ENTRY,
211 "slap_parseURI: parsing %s\n", uri->bv_val, 0, 0 );
213 Debug( LDAP_DEBUG_TRACE,
214 "slap_parseURI: parsing %s\n", uri->bv_val, 0, 0 );
217 rc = LDAP_PROTOCOL_ERROR;
219 * dn[.<dnstyle>]:<dnpattern>
220 * <dnstyle> ::= {exact|regex|children|subtree|onelevel}
222 * <dnstyle> defaults to "exact"
223 * if <dnstyle> is not "regex", <dnpattern> must pass DN normalization
225 if ( !strncasecmp( uri->bv_val, "dn", STRLENOF( "dn" ) ) ) {
226 bv.bv_val = uri->bv_val + STRLENOF( "dn" );
228 if ( bv.bv_val[ 0 ] == '.' ) {
231 if ( !strncasecmp( bv.bv_val, "exact:", STRLENOF( "exact:" ) ) ) {
232 bv.bv_val += STRLENOF( "exact:" );
233 *scope = LDAP_X_SCOPE_EXACT;
235 } else if ( !strncasecmp( bv.bv_val, "regex:", STRLENOF( "regex:" ) ) ) {
236 bv.bv_val += STRLENOF( "regex:" );
237 *scope = LDAP_X_SCOPE_REGEX;
239 } else if ( !strncasecmp( bv.bv_val, "children:", STRLENOF( "children:" ) ) ) {
240 bv.bv_val += STRLENOF( "children:" );
241 *scope = LDAP_X_SCOPE_CHILDREN;
243 } else if ( !strncasecmp( bv.bv_val, "subtree:", STRLENOF( "subtree:" ) ) ) {
244 bv.bv_val += STRLENOF( "subtree:" );
245 *scope = LDAP_X_SCOPE_SUBTREE;
247 } else if ( !strncasecmp( bv.bv_val, "onelevel:", STRLENOF( "onelevel:" ) ) ) {
248 bv.bv_val += STRLENOF( "onelevel:" );
249 *scope = LDAP_X_SCOPE_ONELEVEL;
252 return LDAP_PROTOCOL_ERROR;
256 if ( bv.bv_val[ 0 ] != ':' ) {
257 return LDAP_PROTOCOL_ERROR;
259 *scope = LDAP_X_SCOPE_EXACT;
263 bv.bv_val += strspn( bv.bv_val, " " );
264 /* jump here in case no type specification was present
265 * and uri was not an URI... HEADS-UP: assuming EXACT */
266 is_dn: bv.bv_len = uri->bv_len - (bv.bv_val - uri->bv_val);
268 /* a single '*' means any DN without using regexes */
269 if ( ber_bvccmp( &bv, '*' ) ) {
270 *scope = LDAP_X_SCOPE_USERS;
274 case LDAP_X_SCOPE_EXACT:
275 case LDAP_X_SCOPE_CHILDREN:
276 case LDAP_X_SCOPE_SUBTREE:
277 case LDAP_X_SCOPE_ONELEVEL:
278 rc = dnNormalize( 0, NULL, NULL, &bv, nbase, op->o_tmpmemctx );
279 if( rc != LDAP_SUCCESS ) {
284 case LDAP_X_SCOPE_REGEX:
285 ber_dupbv_x( nbase, &bv, op->o_tmpmemctx );
287 case LDAP_X_SCOPE_USERS:
301 } else if ( ( uri->bv_val[ 0 ] == 'u' || uri->bv_val[ 0 ] == 'U' )
302 && ( uri->bv_val[ 1 ] == ':'
303 || uri->bv_val[ 1 ] == '/'
304 || uri->bv_val[ 1 ] == '.' ) )
306 Connection c = *op->o_conn;
307 char buf[ SLAP_LDAPDN_MAXLEN ];
313 if ( sizeof( buf ) <= uri->bv_len ) {
314 return LDAP_INVALID_SYNTAX;
317 id.bv_len = uri->bv_len;
319 strncpy( buf, uri->bv_val, sizeof( buf ) );
321 rc = slap_parse_user( &id, &user, &realm, &mech );
322 if ( rc != LDAP_SUCCESS ) {
326 if ( !BER_BVISNULL( &mech ) ) {
327 c.c_sasl_bind_mech = mech;
329 BER_BVSTR( &c.c_sasl_bind_mech, "AUTHZ" );
332 rc = slap_sasl_getdn( &c, op, &user,
333 realm.bv_val, nbase, SLAP_GETDN_AUTHZID );
335 if ( rc == LDAP_SUCCESS ) {
336 *scope = LDAP_X_SCOPE_EXACT;
342 * group[/<groupoc>[/<groupat>]]:<groupdn>
344 * groupoc defaults to "groupOfNames"
345 * groupat defaults to "member"
347 * <groupdn> must pass DN normalization
349 } else if ( strncasecmp( uri->bv_val, "group", STRLENOF( "group" ) ) == 0 )
351 struct berval group_dn = BER_BVNULL,
352 group_oc = BER_BVNULL,
353 member_at = BER_BVNULL;
356 bv.bv_val = uri->bv_val + STRLENOF( "group" );
357 group_dn.bv_val = strchr( bv.bv_val, ':' );
358 if ( group_dn.bv_val == NULL ) {
359 /* last chance: assume it's a(n exact) DN ... */
360 bv.bv_val = uri->bv_val;
361 *scope = LDAP_X_SCOPE_EXACT;
365 if ( bv.bv_val[ 0 ] == '/' ) {
366 group_oc.bv_val = &bv.bv_val[ 1 ];
368 member_at.bv_val = strchr( group_oc.bv_val, '/' );
369 if ( member_at.bv_val ) {
370 group_oc.bv_len = member_at.bv_val - group_oc.bv_val;
372 member_at.bv_len = group_dn.bv_val - member_at.bv_val;
375 group_oc.bv_len = group_dn.bv_val - group_oc.bv_val;
376 BER_BVSTR( &member_at, "member" );
380 BER_BVSTR( &group_oc, "groupOfNames" );
383 group_dn.bv_len = uri->bv_len - ( group_dn.bv_val - uri->bv_val );
385 rc = dnNormalize( 0, NULL, NULL, &group_dn, nbase, op->o_tmpmemctx );
386 if ( rc != LDAP_SUCCESS ) {
390 *scope = LDAP_X_SCOPE_GROUP;
392 /* FIXME: caller needs to add value of member attribute
393 * and close brackets twice */
394 fstr->bv_len = STRLENOF( "(&(objectClass=)(=" /* )) */ )
395 + group_oc.bv_len + member_at.bv_len;
396 fstr->bv_val = ch_malloc( fstr->bv_len + 1 );
398 tmp = lutil_strncopy( fstr->bv_val, "(&(objectClass=" /* )) */ ,
399 STRLENOF( "(&(objectClass=" /* )) */ ) );
400 tmp = lutil_strncopy( tmp, group_oc.bv_val, group_oc.bv_len );
401 tmp = lutil_strncopy( tmp, /* ( */ ")(" /* ) */ ,
402 STRLENOF( /* ( */ ")(" /* ) */ ) );
403 tmp = lutil_strncopy( tmp, member_at.bv_val, member_at.bv_len );
404 tmp = lutil_strncopy( tmp, "=", STRLENOF( "=" ) );
410 * ldap:///<base>??<scope>?<filter>
411 * <scope> ::= {base|one|subtree}
413 * <scope> defaults to "base"
414 * <base> must pass DN normalization
415 * <filter> must pass str2filter()
417 rc = ldap_url_parse( uri->bv_val, &ludp );
418 if ( rc == LDAP_URL_ERR_BADSCHEME ) {
420 * last chance: assume it's a(n exact) DN ...
422 * NOTE: must pass DN normalization
424 bv.bv_val = uri->bv_val;
425 *scope = LDAP_X_SCOPE_EXACT;
429 if ( rc != LDAP_URL_SUCCESS ) {
430 return LDAP_PROTOCOL_ERROR;
433 if (( ludp->lud_host && *ludp->lud_host )
434 || ludp->lud_attrs || ludp->lud_exts )
436 /* host part must be empty */
437 /* attrs and extensions parts must be empty */
438 rc = LDAP_PROTOCOL_ERROR;
443 *scope = ludp->lud_scope;
445 /* Grab the filter */
446 if ( ludp->lud_filter ) {
447 *filter = str2filter_x( op, ludp->lud_filter );
448 if ( *filter == NULL ) {
449 rc = LDAP_PROTOCOL_ERROR;
452 ber_str2bv( ludp->lud_filter, 0, 0, fstr );
455 /* Grab the searchbase */
456 ber_str2bv( ludp->lud_dn, 0, 0, base );
457 rc = dnNormalize( 0, NULL, NULL, base, nbase, op->o_tmpmemctx );
460 if( rc != LDAP_SUCCESS ) {
461 if( *filter ) filter_free_x( op, *filter );
465 /* Don't free these, return them to caller */
466 ludp->lud_filter = NULL;
470 ldap_free_urldesc( ludp );
474 static int slap_sasl_rx_off(char *rep, int *off)
479 /* Precompile replace pattern. Find the $<n> placeholders */
482 for ( c = rep; *c; c++ ) {
483 if ( *c == '\\' && c[1] ) {
488 if ( n == SASLREGEX_REPLACE ) {
490 LDAP_LOG( TRANSPORT, ERR,
491 "slap_sasl_rx_off: \"%s\" has too many $n "
492 "placeholders (max %d)\n", rep, SASLREGEX_REPLACE, 0 );
494 Debug( LDAP_DEBUG_ANY,
495 "SASL replace pattern %s has too many $n "
496 "placeholders (max %d)\n",
497 rep, SASLREGEX_REPLACE, 0 );
500 return( LDAP_OTHER );
507 /* Final placeholder, after the last $n */
511 return( LDAP_SUCCESS );
514 #ifdef SLAP_AUTH_REWRITE
515 int slap_sasl_rewrite_config(
525 /* init at first call */
526 if ( sasl_rwinfo == NULL ) {
527 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
530 /* strip "authid-" prefix for parsing */
532 argv[0] += STRLENOF( "authid-" );
533 rc = rewrite_parse( sasl_rwinfo, fname, lineno, argc, argv );
539 int slap_sasl_rewrite_destroy( void )
542 rewrite_info_delete( &sasl_rwinfo );
549 int slap_sasl_regexp_rewrite_config(
554 const char *context )
557 char *argvRule[] = { "rewriteRule", NULL, NULL, ":@", NULL };
559 /* init at first call */
560 if ( sasl_rwinfo == NULL ) {
561 char *argvEngine[] = { "rewriteEngine", "on", NULL };
562 char *argvContext[] = { "rewriteContext", NULL, NULL };
564 /* initialize rewrite engine */
565 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
567 /* switch on rewrite engine */
568 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvEngine );
569 if (rc != LDAP_SUCCESS) {
573 /* create generic authid context */
574 argvContext[1] = AUTHID_CONTEXT;
575 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvContext );
576 if (rc != LDAP_SUCCESS) {
581 argvRule[1] = (char *)match;
582 argvRule[2] = (char *)replace;
583 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 4, argvRule );
587 #endif /* SLAP_AUTH_REWRITE */
589 int slap_sasl_regexp_config( const char *match, const char *replace )
591 #ifdef SLAP_AUTH_REWRITE
592 return slap_sasl_regexp_rewrite_config( "sasl-regexp", 0,
593 match, replace, AUTHID_CONTEXT );
594 #else /* ! SLAP_AUTH_REWRITE */
598 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
599 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
601 reg = &SaslRegexp[nSaslRegexp];
603 reg->sr_match = ch_strdup( match );
604 reg->sr_replace = ch_strdup( replace );
606 /* Precompile matching pattern */
607 rc = regcomp( ®->sr_workspace, reg->sr_match, REG_EXTENDED|REG_ICASE );
610 LDAP_LOG( TRANSPORT, ERR,
611 "slap_sasl_regexp_config: \"%s\" could not be compiled.\n",
612 reg->sr_match, 0, 0 );
614 Debug( LDAP_DEBUG_ANY,
615 "SASL match pattern %s could not be compiled by regexp engine\n",
616 reg->sr_match, 0, 0 );
619 return( LDAP_OTHER );
622 rc = slap_sasl_rx_off( reg->sr_replace, reg->sr_offset );
623 if ( rc != LDAP_SUCCESS ) return rc;
626 return( LDAP_SUCCESS );
627 #endif /* ! SLAP_AUTH_REWRITE */
630 /* Perform replacement on regexp matches */
631 static void slap_sasl_rx_exp(
635 const char *saslname,
639 int i, n, len, insert;
641 /* Get the total length of the final URI */
645 while( off[n] >= 0 ) {
646 /* Len of next section from replacement string (x,y,z above) */
647 len += off[n] - off[n-1] - 2;
651 /* Len of string from saslname that matched next $i (b,d above) */
652 i = rep[ off[n] + 1 ] - '0';
653 len += str[i].rm_eo - str[i].rm_so;
656 out->bv_val = slap_sl_malloc( len + 1, ctx );
659 /* Fill in URI with replace string, replacing $i as we go */
662 while( off[n] >= 0) {
663 /* Paste in next section from replacement string (x,y,z above) */
664 len = off[n] - off[n-1] - 2;
665 strncpy( out->bv_val+insert, rep + off[n-1] + 2, len);
670 /* Paste in string from saslname that matched next $i (b,d above) */
671 i = rep[ off[n] + 1 ] - '0';
672 len = str[i].rm_eo - str[i].rm_so;
673 strncpy( out->bv_val+insert, saslname + str[i].rm_so, len );
679 out->bv_val[insert] = '\0';
682 /* Take the passed in SASL name and attempt to convert it into an
683 LDAP URI to find the matching LDAP entry, using the pattern matching
684 strings given in the saslregexp config file directive(s) */
686 static int slap_authz_regexp( struct berval *in, struct berval *out,
687 int flags, void *ctx )
689 #ifdef SLAP_AUTH_REWRITE
690 const char *context = AUTHID_CONTEXT;
692 if ( sasl_rwinfo == NULL || BER_BVISNULL( in ) ) {
696 /* FIXME: if aware of authc/authz mapping,
697 * we could use different contexts ... */
698 switch ( rewrite_session( sasl_rwinfo, context, in->bv_val, NULL,
701 case REWRITE_REGEXEC_OK:
702 if ( !BER_BVISNULL( out ) ) {
703 char *val = out->bv_val;
704 ber_str2bv_x( val, 0, 1, out, ctx );
707 ber_dupbv_x( out, in, ctx );
710 LDAP_LOG( BACK_LDAP, DETAIL1,
711 "[rw] %s: \"%s\" -> \"%s\"\n",
712 context, in->bv_val, out->bv_val );
713 #else /* !NEW_LOGGING */
714 Debug( LDAP_DEBUG_ARGS,
715 "[rw] %s: \"%s\" -> \"%s\"\n",
716 context, in->bv_val, out->bv_val );
717 #endif /* !NEW_LOGGING */
720 case REWRITE_REGEXEC_UNWILLING:
721 case REWRITE_REGEXEC_ERR:
726 #else /* ! SLAP_AUTH_REWRITE */
727 char *saslname = in->bv_val;
729 regmatch_t sr_strings[SASLREGEX_REPLACE]; /* strings matching $1,$2 ... */
732 memset( out, 0, sizeof( *out ) );
735 LDAP_LOG( TRANSPORT, ENTRY,
736 "slap_authz_regexp: converting SASL name %s\n", saslname, 0, 0 );
738 Debug( LDAP_DEBUG_TRACE, "slap_authz_regexp: converting SASL name %s\n",
742 if (( saslname == NULL ) || ( nSaslRegexp == 0 )) {
746 /* Match the normalized SASL name to the saslregexp patterns */
747 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
748 if ( regexec( ®->sr_workspace, saslname, SASLREGEX_REPLACE,
749 sr_strings, 0) == 0 )
753 if( i >= nSaslRegexp ) return( 0 );
756 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
757 * replace pattern of the form "x$1y$2z". The returned string needs
758 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
760 slap_sasl_rx_exp( reg->sr_replace, reg->sr_offset,
761 sr_strings, saslname, out, ctx );
764 LDAP_LOG( TRANSPORT, ENTRY,
765 "slap_authz_regexp: converted SASL name to %s\n",
766 BER_BVISEMPTY( out ) ? "" : out->bv_val, 0, 0 );
768 Debug( LDAP_DEBUG_TRACE,
769 "slap_authz_regexp: converted SASL name to %s\n",
770 BER_BVISEMPTY( out ) ? "" : out->bv_val, 0, 0 );
774 #endif /* ! SLAP_AUTH_REWRITE */
777 /* This callback actually does some work...*/
778 static int sasl_sc_sasl2dn( Operation *o, SlapReply *rs )
780 struct berval *ndn = o->o_callback->sc_private;
782 if (rs->sr_type != REP_SEARCH) return 0;
784 /* We only want to be called once */
785 if ( !BER_BVISNULL( ndn ) ) {
786 o->o_tmpfree(ndn->bv_val, o->o_tmpmemctx);
790 LDAP_LOG( TRANSPORT, DETAIL1,
791 "slap_sc_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
793 Debug( LDAP_DEBUG_TRACE,
794 "slap_sc_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
799 ber_dupbv_x(ndn, &rs->sr_entry->e_nname, o->o_tmpmemctx);
804 typedef struct smatch_info {
809 static int sasl_sc_smatch( Operation *o, SlapReply *rs )
811 smatch_info *sm = o->o_callback->sc_private;
813 if ( rs->sr_type != REP_SEARCH ) {
814 if ( rs->sr_err != LDAP_SUCCESS ) {
820 if ( sm->match == 1 ) {
825 if (dn_match(sm->dn, &rs->sr_entry->e_nname)) {
836 slap_sasl_matches( Operation *op, BerVarray rules,
837 struct berval *assertDN, struct berval *authc )
839 int rc = LDAP_INAPPROPRIATE_AUTH;
841 if ( rules != NULL ) {
844 for( i = 0; !BER_BVISNULL( &rules[i] ); i++ ) {
845 rc = slap_sasl_match( op, &rules[i], assertDN, authc );
846 if ( rc == LDAP_SUCCESS ) break;
854 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
855 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
856 * the rule must be used as an internal search for entries. If that search
857 * returns the *assertDN entry, the match is successful.
859 * The assertDN should not have the dn: prefix
863 int slap_sasl_match( Operation *opx, struct berval *rule,
864 struct berval *assertDN, struct berval *authc )
869 slap_callback cb = { NULL, sasl_sc_smatch, NULL, NULL };
871 SlapReply rs = {REP_RESULT};
878 LDAP_LOG( TRANSPORT, ENTRY,
879 "slap_sasl_match: comparing DN %s to rule %s\n",
880 assertDN->bv_val, rule->bv_val,0 );
882 Debug( LDAP_DEBUG_TRACE,
883 "===>slap_sasl_match: comparing DN %s to rule %s\n",
884 assertDN->bv_val, rule->bv_val, 0 );
887 rc = slap_parseURI( opx, rule, &op.o_req_dn,
888 &op.o_req_ndn, &op.ors_scope, &op.ors_filter,
890 if( rc != LDAP_SUCCESS ) goto CONCLUDED;
892 switch ( op.ors_scope ) {
893 case LDAP_X_SCOPE_EXACT:
895 if ( dn_match( &op.o_req_ndn, assertDN ) ) {
898 rc = LDAP_INAPPROPRIATE_AUTH;
902 case LDAP_X_SCOPE_CHILDREN:
903 case LDAP_X_SCOPE_SUBTREE:
904 case LDAP_X_SCOPE_ONELEVEL:
906 int d = assertDN->bv_len - op.o_req_ndn.bv_len;
908 rc = LDAP_INAPPROPRIATE_AUTH;
910 if ( d == 0 && op.ors_scope == LDAP_X_SCOPE_SUBTREE ) {
913 } else if ( d > 0 ) {
916 /* leave room for at least one char of attributeType,
917 * one for '=' and one for ',' */
918 if ( d < STRLENOF( "x=,") ) {
922 bv.bv_len = op.o_req_ndn.bv_len;
923 bv.bv_val = assertDN->bv_val + d;
925 if ( bv.bv_val[ -1 ] == ',' && dn_match( &op.o_req_ndn, &bv ) ) {
926 switch ( op.ors_scope ) {
927 case LDAP_X_SCOPE_SUBTREE:
928 case LDAP_X_SCOPE_CHILDREN:
932 case LDAP_X_SCOPE_ONELEVEL:
936 dnParent( assertDN, &pdn );
937 /* the common portion of the DN
938 * already matches, so only check
939 * if parent DN of assertedDN
940 * is all the pattern */
941 if ( pdn.bv_len == op.o_req_ndn.bv_len ) {
947 /* at present, impossible */
955 case LDAP_X_SCOPE_REGEX:
956 rc = regcomp(®, op.o_req_ndn.bv_val,
957 REG_EXTENDED|REG_ICASE|REG_NOSUB);
959 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
965 rc = LDAP_INAPPROPRIATE_AUTH;
969 case LDAP_X_SCOPE_GROUP: {
972 /* Now filterstr looks like "(&(objectClass=<group_oc>)(<member_at>="
973 * we need to append the <assertDN> so that the <group_dn> is searched
974 * with scope "base", and the filter ensures that <assertDN> is
975 * member of the group */
976 tmp = ch_realloc( op.ors_filterstr.bv_val, op.ors_filterstr.bv_len +
977 assertDN->bv_len + STRLENOF( /*"(("*/ "))" ) + 1 );
982 op.ors_filterstr.bv_val = tmp;
984 tmp = lutil_strcopy( &tmp[op.ors_filterstr.bv_len], assertDN->bv_val );
985 tmp = lutil_strcopy( tmp, /*"(("*/ "))" );
987 /* pass opx because str2filter_x may (and does) use o_tmpmfuncs */
988 op.ors_filter = str2filter_x( opx, op.ors_filterstr.bv_val );
989 if ( op.ors_filter == NULL ) {
990 rc = LDAP_PROTOCOL_ERROR;
993 op.ors_scope = LDAP_SCOPE_BASE;
995 /* hijack match DN: use that of the group instead of the assertDN;
996 * assertDN is now in the filter */
997 sm.dn = &op.o_req_ndn;
1003 case LDAP_X_SCOPE_USERS:
1004 if ( !BER_BVISEMPTY( assertDN ) ) {
1007 rc = LDAP_INAPPROPRIATE_AUTH;
1015 /* Must run an internal search. */
1016 if ( op.ors_filter == NULL ) {
1017 rc = LDAP_FILTER_ERROR;
1022 LDAP_LOG( TRANSPORT, DETAIL1,
1023 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
1024 op.o_req_ndn.bv_val, op.ors_scope, 0 );
1026 Debug( LDAP_DEBUG_TRACE,
1027 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
1028 op.o_req_ndn.bv_val, op.ors_scope, 0 );
1031 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
1032 if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
1033 rc = LDAP_INAPPROPRIATE_AUTH;
1037 op.o_tag = LDAP_REQ_SEARCH;
1038 op.o_protocol = LDAP_VERSION3;
1040 op.o_callback = &cb;
1041 op.o_time = slap_get_time();
1042 op.o_do_not_cache = 1;
1043 op.o_is_auth_check = 1;
1044 op.o_threadctx = opx->o_threadctx;
1045 op.o_tmpmemctx = opx->o_tmpmemctx;
1046 op.o_tmpmfuncs = opx->o_tmpmfuncs;
1048 op.o_pb = opx->o_pb;
1050 op.o_conn = opx->o_conn;
1051 op.o_connid = opx->o_connid;
1052 /* use req_ndn as req_dn instead of non-pretty base of uri */
1053 if( !BER_BVISNULL( &op.o_req_dn ) ) ch_free( op.o_req_dn.bv_val );
1054 ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1056 op.ors_tlimit = SLAP_NO_LIMIT;
1057 op.ors_attrs = slap_anlist_no_attrs;
1058 op.ors_attrsonly = 1;
1059 op.o_sync_slog_size = -1;
1061 op.o_bd->be_search( &op, &rs );
1063 if (sm.match == 1) {
1066 rc = LDAP_INAPPROPRIATE_AUTH;
1070 if( !BER_BVISNULL( &op.o_req_dn ) ) slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1071 if( !BER_BVISNULL( &op.o_req_ndn ) ) slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1072 if( op.ors_filter ) filter_free_x( opx, op.ors_filter );
1073 if( !BER_BVISNULL( &op.ors_filterstr ) ) ch_free( op.ors_filterstr.bv_val );
1076 LDAP_LOG( TRANSPORT, ENTRY,
1077 "slap_sasl_match: comparison returned %d\n", rc, 0, 0 );
1079 Debug( LDAP_DEBUG_TRACE,
1080 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
1088 * This function answers the question, "Can this ID authorize to that ID?",
1089 * based on authorization rules. The rules are stored in the *searchDN, in the
1090 * attribute named by *attr. If any of those rules map to the *assertDN, the
1091 * authorization is approved.
1093 * The DNs should not have the dn: prefix
1096 slap_sasl_check_authz( Operation *op,
1097 struct berval *searchDN,
1098 struct berval *assertDN,
1099 AttributeDescription *ad,
1100 struct berval *authc )
1103 BerVarray vals = NULL;
1106 LDAP_LOG( TRANSPORT, ENTRY,
1107 "slap_sasl_check_authz: does %s match %s rule in %s?\n",
1108 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
1110 Debug( LDAP_DEBUG_TRACE,
1111 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
1112 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
1115 rc = backend_attribute( op, NULL, searchDN, ad, &vals, ACL_AUTH );
1116 if( rc != LDAP_SUCCESS ) goto COMPLETE;
1118 /* Check if the *assertDN matches any *vals */
1119 rc = slap_sasl_matches( op, vals, assertDN, authc );
1122 if( vals ) ber_bvarray_free_x( vals, op->o_tmpmemctx );
1125 LDAP_LOG( TRANSPORT, RESULTS,
1126 "slap_sasl_check_authz: %s check returning %d\n",
1127 ad->ad_cname.bv_val, rc, 0 );
1129 Debug( LDAP_DEBUG_TRACE,
1130 "<==slap_sasl_check_authz: %s check returning %d\n",
1131 ad->ad_cname.bv_val, rc, 0);
1138 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
1139 * return the LDAP DN to which it matches. The SASL regexp rules in the config
1140 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
1141 * search with scope=base), just return the URI (or its searchbase). Otherwise
1142 * an internal search must be done, and if that search returns exactly one
1143 * entry, return the DN of that one entry.
1145 void slap_sasl2dn( Operation *opx,
1146 struct berval *saslname, struct berval *sasldn, int flags )
1149 slap_callback cb = { NULL, sasl_sc_sasl2dn, NULL, NULL };
1151 SlapReply rs = {REP_RESULT};
1152 struct berval regout = BER_BVNULL;
1155 LDAP_LOG( TRANSPORT, ENTRY,
1156 "slap_sasl2dn: converting SASL name %s to DN.\n",
1157 saslname->bv_val, 0, 0 );
1159 Debug( LDAP_DEBUG_TRACE, "==>slap_sasl2dn: "
1160 "converting SASL name %s to a DN\n",
1161 saslname->bv_val, 0,0 );
1164 sasldn->bv_val = NULL;
1166 cb.sc_private = sasldn;
1168 /* Convert the SASL name into a minimal URI */
1169 if( !slap_authz_regexp( saslname, ®out, flags, opx->o_tmpmemctx ) ) {
1173 rc = slap_parseURI( opx, ®out, &op.o_req_dn,
1174 &op.o_req_ndn, &op.ors_scope, &op.ors_filter,
1175 &op.ors_filterstr );
1176 if ( !BER_BVISNULL( ®out ) ) slap_sl_free( regout.bv_val, opx->o_tmpmemctx );
1177 if ( rc != LDAP_SUCCESS ) {
1181 /* Must do an internal search */
1182 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
1184 switch ( op.ors_scope ) {
1185 case LDAP_X_SCOPE_EXACT:
1186 *sasldn = op.o_req_ndn;
1187 BER_BVZERO( &op.o_req_ndn );
1188 /* intentionally continue to next case */
1190 case LDAP_X_SCOPE_REGEX:
1191 case LDAP_X_SCOPE_SUBTREE:
1192 case LDAP_X_SCOPE_CHILDREN:
1193 case LDAP_X_SCOPE_ONELEVEL:
1194 case LDAP_X_SCOPE_GROUP:
1195 case LDAP_X_SCOPE_USERS:
1196 /* correctly parsed, but illegal */
1199 case LDAP_SCOPE_BASE:
1200 case LDAP_SCOPE_ONELEVEL:
1201 case LDAP_SCOPE_SUBTREE:
1202 #ifdef LDAP_SCOPE_SUBORDINATE
1203 case LDAP_SCOPE_SUBORDINATE:
1209 /* catch unhandled cases (there shouldn't be) */
1214 LDAP_LOG( TRANSPORT, DETAIL1,
1215 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
1216 op.o_req_ndn.bv_val, op.ors_scope, 0 );
1218 Debug( LDAP_DEBUG_TRACE,
1219 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
1220 op.o_req_ndn.bv_val, op.ors_scope, 0 );
1223 if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
1227 op.o_conn = opx->o_conn;
1228 op.o_connid = opx->o_connid;
1229 op.o_tag = LDAP_REQ_SEARCH;
1230 op.o_protocol = LDAP_VERSION3;
1231 op.o_ndn = opx->o_conn->c_ndn;
1232 op.o_callback = &cb;
1233 op.o_time = slap_get_time();
1234 op.o_do_not_cache = 1;
1235 op.o_is_auth_check = 1;
1236 op.o_threadctx = opx->o_threadctx;
1237 op.o_tmpmemctx = opx->o_tmpmemctx;
1238 op.o_tmpmfuncs = opx->o_tmpmfuncs;
1240 op.o_pb = opx->o_pb;
1242 op.ors_deref = LDAP_DEREF_NEVER;
1244 op.ors_tlimit = SLAP_NO_LIMIT;
1245 op.ors_attrs = slap_anlist_no_attrs;
1246 op.ors_attrsonly = 1;
1247 op.o_sync_slog_size = -1;
1248 /* use req_ndn as req_dn instead of non-pretty base of uri */
1249 if( !BER_BVISNULL( &op.o_req_dn ) ) ch_free( op.o_req_dn.bv_val );
1250 ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1252 op.o_bd->be_search( &op, &rs );
1255 if( !BER_BVISEMPTY( sasldn ) ) {
1256 opx->o_conn->c_authz_backend = op.o_bd;
1258 if( !BER_BVISNULL( &op.o_req_dn ) ) {
1259 slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1261 if( !BER_BVISNULL( &op.o_req_ndn ) ) {
1262 slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1264 if( op.ors_filter ) {
1265 filter_free_x( opx, op.ors_filter );
1267 if( !BER_BVISNULL( &op.ors_filterstr ) ) {
1268 ch_free( op.ors_filterstr.bv_val );
1272 LDAP_LOG( TRANSPORT, ENTRY,
1273 "slap_sasl2dn: Converted SASL name to %s\n",
1274 !BER_BVISEMPTY( sasldn ) ? sasldn->bv_val : "<nothing>", 0, 0 );
1276 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
1277 !BER_BVISEMPTY( sasldn ) ? sasldn->bv_val : "<nothing>", 0, 0 );
1284 /* Check if a bind can SASL authorize to another identity.
1285 * The DNs should not have the dn: prefix
1288 int slap_sasl_authorized( Operation *op,
1289 struct berval *authcDN, struct berval *authzDN )
1291 int rc = LDAP_INAPPROPRIATE_AUTH;
1293 /* User binding as anonymous */
1294 if ( authzDN == NULL ) {
1300 LDAP_LOG( TRANSPORT, ENTRY,
1301 "slap_sasl_authorized: can %s become %s?\n",
1302 authcDN->bv_val, authzDN->bv_val, 0 );
1304 Debug( LDAP_DEBUG_TRACE,
1305 "==>slap_sasl_authorized: can %s become %s?\n",
1306 authcDN->bv_val, authzDN->bv_val, 0 );
1309 /* If person is authorizing to self, succeed */
1310 if ( dn_match( authcDN, authzDN ) ) {
1315 /* Allow the manager to authorize as any DN. */
1316 if( op->o_conn->c_authz_backend &&
1317 be_isroot_dn( op->o_conn->c_authz_backend, authcDN ))
1323 /* Check source rules */
1324 if( authz_policy & SASL_AUTHZ_TO ) {
1325 rc = slap_sasl_check_authz( op, authcDN, authzDN,
1326 slap_schema.si_ad_saslAuthzTo, authcDN );
1327 if( rc == LDAP_SUCCESS && !(authz_policy & SASL_AUTHZ_AND) ) {
1332 /* Check destination rules */
1333 if( authz_policy & SASL_AUTHZ_FROM ) {
1334 rc = slap_sasl_check_authz( op, authzDN, authcDN,
1335 slap_schema.si_ad_saslAuthzFrom, authcDN );
1336 if( rc == LDAP_SUCCESS ) {
1341 rc = LDAP_INAPPROPRIATE_AUTH;
1346 LDAP_LOG( TRANSPORT, RESULTS, "slap_sasl_authorized: return %d\n", rc,0,0 );
1348 Debug( LDAP_DEBUG_TRACE,
1349 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );