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 ) );
209 Debug( LDAP_DEBUG_TRACE,
210 "slap_parseURI: parsing %s\n", uri->bv_val, 0, 0 );
212 rc = LDAP_PROTOCOL_ERROR;
214 * dn[.<dnstyle>]:<dnpattern>
215 * <dnstyle> ::= {exact|regex|children|subtree|onelevel}
217 * <dnstyle> defaults to "exact"
218 * if <dnstyle> is not "regex", <dnpattern> must pass DN normalization
220 if ( !strncasecmp( uri->bv_val, "dn", STRLENOF( "dn" ) ) ) {
221 bv.bv_val = uri->bv_val + STRLENOF( "dn" );
223 if ( bv.bv_val[ 0 ] == '.' ) {
226 if ( !strncasecmp( bv.bv_val, "exact:", STRLENOF( "exact:" ) ) ) {
227 bv.bv_val += STRLENOF( "exact:" );
228 *scope = LDAP_X_SCOPE_EXACT;
230 } else if ( !strncasecmp( bv.bv_val, "regex:", STRLENOF( "regex:" ) ) ) {
231 bv.bv_val += STRLENOF( "regex:" );
232 *scope = LDAP_X_SCOPE_REGEX;
234 } else if ( !strncasecmp( bv.bv_val, "children:", STRLENOF( "children:" ) ) ) {
235 bv.bv_val += STRLENOF( "children:" );
236 *scope = LDAP_X_SCOPE_CHILDREN;
238 } else if ( !strncasecmp( bv.bv_val, "subtree:", STRLENOF( "subtree:" ) ) ) {
239 bv.bv_val += STRLENOF( "subtree:" );
240 *scope = LDAP_X_SCOPE_SUBTREE;
242 } else if ( !strncasecmp( bv.bv_val, "onelevel:", STRLENOF( "onelevel:" ) ) ) {
243 bv.bv_val += STRLENOF( "onelevel:" );
244 *scope = LDAP_X_SCOPE_ONELEVEL;
247 return LDAP_PROTOCOL_ERROR;
251 if ( bv.bv_val[ 0 ] != ':' ) {
252 return LDAP_PROTOCOL_ERROR;
254 *scope = LDAP_X_SCOPE_EXACT;
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);
263 /* a single '*' means any DN without using regexes */
264 if ( ber_bvccmp( &bv, '*' ) ) {
265 *scope = LDAP_X_SCOPE_USERS;
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 ) {
279 case LDAP_X_SCOPE_REGEX:
280 ber_dupbv_x( nbase, &bv, op->o_tmpmemctx );
282 case LDAP_X_SCOPE_USERS:
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 ] == '.' ) )
301 Connection c = *op->o_conn;
302 char buf[ SLAP_LDAPDN_MAXLEN ];
308 if ( sizeof( buf ) <= uri->bv_len ) {
309 return LDAP_INVALID_SYNTAX;
312 id.bv_len = uri->bv_len;
314 strncpy( buf, uri->bv_val, sizeof( buf ) );
316 rc = slap_parse_user( &id, &user, &realm, &mech );
317 if ( rc != LDAP_SUCCESS ) {
321 if ( !BER_BVISNULL( &mech ) ) {
322 c.c_sasl_bind_mech = mech;
324 BER_BVSTR( &c.c_sasl_bind_mech, "AUTHZ" );
327 rc = slap_sasl_getdn( &c, op, &user,
328 realm.bv_val, nbase, SLAP_GETDN_AUTHZID );
330 if ( rc == LDAP_SUCCESS ) {
331 *scope = LDAP_X_SCOPE_EXACT;
337 * group[/<groupoc>[/<groupat>]]:<groupdn>
339 * groupoc defaults to "groupOfNames"
340 * groupat defaults to "member"
342 * <groupdn> must pass DN normalization
344 } else if ( strncasecmp( uri->bv_val, "group", STRLENOF( "group" ) ) == 0 )
346 struct berval group_dn = BER_BVNULL,
347 group_oc = BER_BVNULL,
348 member_at = BER_BVNULL;
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;
360 if ( bv.bv_val[ 0 ] == '/' ) {
361 group_oc.bv_val = &bv.bv_val[ 1 ];
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;
367 member_at.bv_len = group_dn.bv_val - member_at.bv_val;
370 group_oc.bv_len = group_dn.bv_val - group_oc.bv_val;
371 BER_BVSTR( &member_at, "member" );
375 BER_BVSTR( &group_oc, "groupOfNames" );
378 group_dn.bv_len = uri->bv_len - ( group_dn.bv_val - uri->bv_val );
380 rc = dnNormalize( 0, NULL, NULL, &group_dn, nbase, op->o_tmpmemctx );
381 if ( rc != LDAP_SUCCESS ) {
385 *scope = LDAP_X_SCOPE_GROUP;
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 );
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( "=" ) );
405 * ldap:///<base>??<scope>?<filter>
406 * <scope> ::= {base|one|subtree}
408 * <scope> defaults to "base"
409 * <base> must pass DN normalization
410 * <filter> must pass str2filter()
412 rc = ldap_url_parse( uri->bv_val, &ludp );
413 if ( rc == LDAP_URL_ERR_BADSCHEME ) {
415 * last chance: assume it's a(n exact) DN ...
417 * NOTE: must pass DN normalization
419 bv.bv_val = uri->bv_val;
420 *scope = LDAP_X_SCOPE_EXACT;
424 if ( rc != LDAP_URL_SUCCESS ) {
425 return LDAP_PROTOCOL_ERROR;
428 if (( ludp->lud_host && *ludp->lud_host )
429 || ludp->lud_attrs || ludp->lud_exts )
431 /* host part must be empty */
432 /* attrs and extensions parts must be empty */
433 rc = LDAP_PROTOCOL_ERROR;
438 *scope = ludp->lud_scope;
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;
447 ber_str2bv( ludp->lud_filter, 0, 0, fstr );
450 /* Grab the searchbase */
451 ber_str2bv( ludp->lud_dn, 0, 0, base );
452 rc = dnNormalize( 0, NULL, NULL, base, nbase, op->o_tmpmemctx );
455 if( rc != LDAP_SUCCESS ) {
456 if( *filter ) filter_free_x( op, *filter );
460 /* Don't free these, return them to caller */
461 ludp->lud_filter = NULL;
465 ldap_free_urldesc( ludp );
469 static int slap_sasl_rx_off(char *rep, int *off)
474 /* Precompile replace pattern. Find the $<n> placeholders */
477 for ( c = rep; *c; c++ ) {
478 if ( *c == '\\' && c[1] ) {
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 );
489 return( LDAP_OTHER );
496 /* Final placeholder, after the last $n */
500 return( LDAP_SUCCESS );
503 #ifdef SLAP_AUTH_REWRITE
504 int slap_sasl_rewrite_config(
514 /* init at first call */
515 if ( sasl_rwinfo == NULL ) {
516 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
519 /* strip "authid-" prefix for parsing */
521 argv[0] += STRLENOF( "authid-" );
522 rc = rewrite_parse( sasl_rwinfo, fname, lineno, argc, argv );
528 int slap_sasl_rewrite_destroy( void )
531 rewrite_info_delete( &sasl_rwinfo );
538 int slap_sasl_regexp_rewrite_config(
543 const char *context )
546 char *argvRule[] = { "rewriteRule", NULL, NULL, ":@", NULL };
548 /* init at first call */
549 if ( sasl_rwinfo == NULL ) {
550 char *argvEngine[] = { "rewriteEngine", "on", NULL };
551 char *argvContext[] = { "rewriteContext", NULL, NULL };
553 /* initialize rewrite engine */
554 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
556 /* switch on rewrite engine */
557 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvEngine );
558 if (rc != LDAP_SUCCESS) {
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) {
570 argvRule[1] = (char *)match;
571 argvRule[2] = (char *)replace;
572 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 4, argvRule );
576 #endif /* SLAP_AUTH_REWRITE */
578 int slap_sasl_regexp_config( const char *match, const char *replace )
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 */
587 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
588 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
590 reg = &SaslRegexp[nSaslRegexp];
592 reg->sr_match = ch_strdup( match );
593 reg->sr_replace = ch_strdup( replace );
595 /* Precompile matching pattern */
596 rc = regcomp( ®->sr_workspace, reg->sr_match, REG_EXTENDED|REG_ICASE );
598 Debug( LDAP_DEBUG_ANY,
599 "SASL match pattern %s could not be compiled by regexp engine\n",
600 reg->sr_match, 0, 0 );
602 return( LDAP_OTHER );
605 rc = slap_sasl_rx_off( reg->sr_replace, reg->sr_offset );
606 if ( rc != LDAP_SUCCESS ) return rc;
609 return( LDAP_SUCCESS );
610 #endif /* ! SLAP_AUTH_REWRITE */
613 /* Perform replacement on regexp matches */
614 static void slap_sasl_rx_exp(
618 const char *saslname,
622 int i, n, len, insert;
624 /* Get the total length of the final URI */
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;
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;
639 out->bv_val = slap_sl_malloc( len + 1, ctx );
642 /* Fill in URI with replace string, replacing $i as we go */
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);
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 );
662 out->bv_val[insert] = '\0';
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) */
669 static int slap_authz_regexp( struct berval *in, struct berval *out,
670 int flags, void *ctx )
672 #ifdef SLAP_AUTH_REWRITE
673 const char *context = AUTHID_CONTEXT;
675 if ( sasl_rwinfo == NULL || BER_BVISNULL( in ) ) {
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,
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 );
690 ber_dupbv_x( out, in, ctx );
692 Debug( LDAP_DEBUG_ARGS,
693 "[rw] %s: \"%s\" -> \"%s\"\n",
694 context, in->bv_val, out->bv_val );
697 case REWRITE_REGEXEC_UNWILLING:
698 case REWRITE_REGEXEC_ERR:
703 #else /* ! SLAP_AUTH_REWRITE */
704 char *saslname = in->bv_val;
706 regmatch_t sr_strings[SASLREGEX_REPLACE]; /* strings matching $1,$2 ... */
709 memset( out, 0, sizeof( *out ) );
711 Debug( LDAP_DEBUG_TRACE, "slap_authz_regexp: converting SASL name %s\n",
714 if (( saslname == NULL ) || ( nSaslRegexp == 0 )) {
718 /* Match the normalized SASL name to the saslregexp patterns */
719 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
720 if ( regexec( ®->sr_workspace, saslname, SASLREGEX_REPLACE,
721 sr_strings, 0) == 0 )
725 if( i >= nSaslRegexp ) return( 0 );
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.*)
732 slap_sasl_rx_exp( reg->sr_replace, reg->sr_offset,
733 sr_strings, saslname, out, ctx );
735 Debug( LDAP_DEBUG_TRACE,
736 "slap_authz_regexp: converted SASL name to %s\n",
737 BER_BVISEMPTY( out ) ? "" : out->bv_val, 0, 0 );
740 #endif /* ! SLAP_AUTH_REWRITE */
743 /* This callback actually does some work...*/
744 static int sasl_sc_sasl2dn( Operation *o, SlapReply *rs )
746 struct berval *ndn = o->o_callback->sc_private;
748 if (rs->sr_type != REP_SEARCH) return 0;
750 /* We only want to be called once */
751 if ( !BER_BVISNULL( ndn ) ) {
752 o->o_tmpfree(ndn->bv_val, o->o_tmpmemctx);
755 Debug( LDAP_DEBUG_TRACE,
756 "slap_sc_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
760 ber_dupbv_x(ndn, &rs->sr_entry->e_nname, o->o_tmpmemctx);
765 typedef struct smatch_info {
770 static int sasl_sc_smatch( Operation *o, SlapReply *rs )
772 smatch_info *sm = o->o_callback->sc_private;
774 if ( rs->sr_type != REP_SEARCH ) {
775 if ( rs->sr_err != LDAP_SUCCESS ) {
781 if ( sm->match == 1 ) {
786 if (dn_match(sm->dn, &rs->sr_entry->e_nname)) {
797 slap_sasl_matches( Operation *op, BerVarray rules,
798 struct berval *assertDN, struct berval *authc )
800 int rc = LDAP_INAPPROPRIATE_AUTH;
802 if ( rules != NULL ) {
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;
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.
820 * The assertDN should not have the dn: prefix
824 int slap_sasl_match( Operation *opx, struct berval *rule,
825 struct berval *assertDN, struct berval *authc )
830 slap_callback cb = { NULL, sasl_sc_smatch, NULL, NULL };
832 SlapReply rs = {REP_RESULT};
838 Debug( LDAP_DEBUG_TRACE,
839 "===>slap_sasl_match: comparing DN %s to rule %s\n",
840 assertDN->bv_val, rule->bv_val, 0 );
842 rc = slap_parseURI( opx, rule, &op.o_req_dn,
843 &op.o_req_ndn, &op.ors_scope, &op.ors_filter,
845 if( rc != LDAP_SUCCESS ) goto CONCLUDED;
847 switch ( op.ors_scope ) {
848 case LDAP_X_SCOPE_EXACT:
850 if ( dn_match( &op.o_req_ndn, assertDN ) ) {
853 rc = LDAP_INAPPROPRIATE_AUTH;
857 case LDAP_X_SCOPE_CHILDREN:
858 case LDAP_X_SCOPE_SUBTREE:
859 case LDAP_X_SCOPE_ONELEVEL:
861 int d = assertDN->bv_len - op.o_req_ndn.bv_len;
863 rc = LDAP_INAPPROPRIATE_AUTH;
865 if ( d == 0 && op.ors_scope == LDAP_X_SCOPE_SUBTREE ) {
868 } else if ( d > 0 ) {
871 /* leave room for at least one char of attributeType,
872 * one for '=' and one for ',' */
873 if ( d < STRLENOF( "x=,") ) {
877 bv.bv_len = op.o_req_ndn.bv_len;
878 bv.bv_val = assertDN->bv_val + d;
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:
887 case LDAP_X_SCOPE_ONELEVEL:
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 ) {
902 /* at present, impossible */
910 case LDAP_X_SCOPE_REGEX:
911 rc = regcomp(®, op.o_req_ndn.bv_val,
912 REG_EXTENDED|REG_ICASE|REG_NOSUB);
914 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
920 rc = LDAP_INAPPROPRIATE_AUTH;
924 case LDAP_X_SCOPE_GROUP: {
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 );
937 op.ors_filterstr.bv_val = tmp;
939 tmp = lutil_strcopy( &tmp[op.ors_filterstr.bv_len], assertDN->bv_val );
940 tmp = lutil_strcopy( tmp, /*"(("*/ "))" );
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;
948 op.ors_scope = LDAP_SCOPE_BASE;
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;
958 case LDAP_X_SCOPE_USERS:
959 if ( !BER_BVISEMPTY( assertDN ) ) {
962 rc = LDAP_INAPPROPRIATE_AUTH;
970 /* Must run an internal search. */
971 if ( op.ors_filter == NULL ) {
972 rc = LDAP_FILTER_ERROR;
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 );
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;
986 op.o_tag = LDAP_REQ_SEARCH;
987 op.o_protocol = LDAP_VERSION3;
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;
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 );
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;
1010 op.o_bd->be_search( &op, &rs );
1012 if (sm.match == 1) {
1015 rc = LDAP_INAPPROPRIATE_AUTH;
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 );
1024 Debug( LDAP_DEBUG_TRACE,
1025 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
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.
1037 * The DNs should not have the dn: prefix
1040 slap_sasl_check_authz( Operation *op,
1041 struct berval *searchDN,
1042 struct berval *assertDN,
1043 AttributeDescription *ad,
1044 struct berval *authc )
1047 BerVarray vals = NULL;
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);
1053 rc = backend_attribute( op, NULL, searchDN, ad, &vals, ACL_AUTH );
1054 if( rc != LDAP_SUCCESS ) goto COMPLETE;
1056 /* Check if the *assertDN matches any *vals */
1057 rc = slap_sasl_matches( op, vals, assertDN, authc );
1060 if( vals ) ber_bvarray_free_x( vals, op->o_tmpmemctx );
1062 Debug( LDAP_DEBUG_TRACE,
1063 "<==slap_sasl_check_authz: %s check returning %d\n",
1064 ad->ad_cname.bv_val, rc, 0);
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.
1077 void slap_sasl2dn( Operation *opx,
1078 struct berval *saslname, struct berval *sasldn, int flags )
1081 slap_callback cb = { NULL, sasl_sc_sasl2dn, NULL, NULL };
1083 SlapReply rs = {REP_RESULT};
1084 struct berval regout = BER_BVNULL;
1086 Debug( LDAP_DEBUG_TRACE, "==>slap_sasl2dn: "
1087 "converting SASL name %s to a DN\n",
1088 saslname->bv_val, 0,0 );
1090 sasldn->bv_val = NULL;
1092 cb.sc_private = sasldn;
1094 /* Convert the SASL name into a minimal URI */
1095 if( !slap_authz_regexp( saslname, ®out, flags, opx->o_tmpmemctx ) ) {
1099 rc = slap_parseURI( opx, ®out, &op.o_req_dn,
1100 &op.o_req_ndn, &op.ors_scope, &op.ors_filter,
1101 &op.ors_filterstr );
1102 if ( !BER_BVISNULL( ®out ) ) slap_sl_free( regout.bv_val, opx->o_tmpmemctx );
1103 if ( rc != LDAP_SUCCESS ) {
1107 /* Must do an internal search */
1108 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
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 */
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 */
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:
1135 /* catch unhandled cases (there shouldn't be) */
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 );
1143 if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
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;
1160 op.o_pb = opx->o_pb;
1162 op.ors_deref = LDAP_DEREF_NEVER;
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 );
1172 op.o_bd->be_search( &op, &rs );
1175 if( !BER_BVISEMPTY( sasldn ) ) {
1176 opx->o_conn->c_authz_backend = op.o_bd;
1178 if( !BER_BVISNULL( &op.o_req_dn ) ) {
1179 slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1181 if( !BER_BVISNULL( &op.o_req_ndn ) ) {
1182 slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1184 if( op.ors_filter ) {
1185 filter_free_x( opx, op.ors_filter );
1187 if( !BER_BVISNULL( &op.ors_filterstr ) ) {
1188 ch_free( op.ors_filterstr.bv_val );
1191 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
1192 !BER_BVISEMPTY( sasldn ) ? sasldn->bv_val : "<nothing>", 0, 0 );
1198 /* Check if a bind can SASL authorize to another identity.
1199 * The DNs should not have the dn: prefix
1202 int slap_sasl_authorized( Operation *op,
1203 struct berval *authcDN, struct berval *authzDN )
1205 int rc = LDAP_INAPPROPRIATE_AUTH;
1207 /* User binding as anonymous */
1208 if ( authzDN == NULL ) {
1213 Debug( LDAP_DEBUG_TRACE,
1214 "==>slap_sasl_authorized: can %s become %s?\n",
1215 authcDN->bv_val, authzDN->bv_val, 0 );
1217 /* If person is authorizing to self, succeed */
1218 if ( dn_match( authcDN, authzDN ) ) {
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 ))
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) ) {
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 ) {
1249 rc = LDAP_INAPPROPRIATE_AUTH;
1253 Debug( LDAP_DEBUG_TRACE,
1254 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );