2 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
4 * Copyright 1998-2005 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>
31 #define SASLREGEX_REPLACE 10
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)
42 * IDs in DNauthzid form can now have a type specifier, that
43 * influences how they are used in related operations.
45 * syntax: dn[.{exact|regex}]:<val>
47 * dn.exact: the value must pass normalization and is used
49 * dn.regex: the value is treated as a regular expression
50 * in matching DN values in authz{To|From}
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
58 * IDs in DNauthzid form can now have a type specifier, that
59 * influences how they are used in related operations.
61 * syntax: u[.mech[/realm]]:<val>
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).
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 */
75 static int nSaslRegexp = 0;
76 static SaslRegexp_t *SaslRegexp = NULL;
78 #ifdef SLAP_AUTH_REWRITE
80 struct rewrite_info *sasl_rwinfo = NULL;
81 #define AUTHID_CONTEXT "authid"
82 #endif /* SLAP_AUTH_REWRITE */
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
90 static int authz_policy = SASL_AUTHZ_NONE;
93 int slap_sasl_match( Operation *opx, struct berval *rule,
94 struct berval *assertDN, struct berval *authc );
96 int slap_sasl_setpolicy( const char *arg )
98 int rc = LDAP_SUCCESS;
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;
116 int slap_parse_user( struct berval *id, struct berval *user,
117 struct berval *realm, struct berval *mech )
122 assert( !BER_BVISNULL( id ) );
129 if ( u != 'u' && u != 'U' ) {
130 /* called with something other than u: */
131 return LDAP_PROTOCOL_ERROR;
135 * u[.mech[/realm]]:user
138 user->bv_val = strchr( id->bv_val, ':' );
139 if ( BER_BVISNULL( user ) ) {
140 return LDAP_PROTOCOL_ERROR;
142 user->bv_val[ 0 ] = '\0';
144 user->bv_len = id->bv_len - ( user->bv_val - id->bv_val );
146 mech->bv_val = strchr( id->bv_val, '.' );
147 if ( !BER_BVISNULL( mech ) ) {
148 mech->bv_val[ 0 ] = '\0';
151 realm->bv_val = strchr( mech->bv_val, '/' );
153 if ( !BER_BVISNULL( realm ) ) {
154 realm->bv_val[ 0 ] = '\0';
156 mech->bv_len = realm->bv_val - mech->bv_val - 1;
157 realm->bv_len = user->bv_val - realm->bv_val - 1;
159 mech->bv_len = user->bv_val - mech->bv_val - 1;
166 if ( id->bv_val[ 1 ] != '\0' ) {
167 return LDAP_PROTOCOL_ERROR;
170 if ( !BER_BVISNULL( mech ) ) {
171 assert( mech->bv_val == id->bv_val + 2 );
173 AC_MEMCPY( mech->bv_val - 2, mech->bv_val, mech->bv_len + 1 );
177 if ( !BER_BVISNULL( realm ) ) {
178 assert( realm->bv_val >= id->bv_val + 2 );
180 AC_MEMCPY( realm->bv_val - 2, realm->bv_val, realm->bv_len + 1 );
184 /* leave "u:" before user */
187 user->bv_val[ 0 ] = u;
188 user->bv_val[ 1 ] = ':';
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 )
201 assert( uri != NULL && !BER_BVISNULL( uri ) );
208 Debug( LDAP_DEBUG_TRACE,
209 "slap_parseURI: parsing %s\n", uri->bv_val, 0, 0 );
211 rc = LDAP_PROTOCOL_ERROR;
213 * dn[.<dnstyle>]:<dnpattern>
214 * <dnstyle> ::= {exact|regex|children|subtree|onelevel}
216 * <dnstyle> defaults to "exact"
217 * if <dnstyle> is not "regex", <dnpattern> must pass DN normalization
219 if ( !strncasecmp( uri->bv_val, "dn", STRLENOF( "dn" ) ) ) {
220 bv.bv_val = uri->bv_val + STRLENOF( "dn" );
222 if ( bv.bv_val[ 0 ] == '.' ) {
225 if ( !strncasecmp( bv.bv_val, "exact:", STRLENOF( "exact:" ) ) ) {
226 bv.bv_val += STRLENOF( "exact:" );
227 *scope = LDAP_X_SCOPE_EXACT;
229 } else if ( !strncasecmp( bv.bv_val, "regex:", STRLENOF( "regex:" ) ) ) {
230 bv.bv_val += STRLENOF( "regex:" );
231 *scope = LDAP_X_SCOPE_REGEX;
233 } else if ( !strncasecmp( bv.bv_val, "children:", STRLENOF( "children:" ) ) ) {
234 bv.bv_val += STRLENOF( "children:" );
235 *scope = LDAP_X_SCOPE_CHILDREN;
237 } else if ( !strncasecmp( bv.bv_val, "subtree:", STRLENOF( "subtree:" ) ) ) {
238 bv.bv_val += STRLENOF( "subtree:" );
239 *scope = LDAP_X_SCOPE_SUBTREE;
241 } else if ( !strncasecmp( bv.bv_val, "onelevel:", STRLENOF( "onelevel:" ) ) ) {
242 bv.bv_val += STRLENOF( "onelevel:" );
243 *scope = LDAP_X_SCOPE_ONELEVEL;
246 return LDAP_PROTOCOL_ERROR;
250 if ( bv.bv_val[ 0 ] != ':' ) {
251 return LDAP_PROTOCOL_ERROR;
253 *scope = LDAP_X_SCOPE_EXACT;
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);
262 /* a single '*' means any DN without using regexes */
263 if ( ber_bvccmp( &bv, '*' ) ) {
264 *scope = LDAP_X_SCOPE_USERS;
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 ) {
278 case LDAP_X_SCOPE_REGEX:
279 ber_dupbv_x( nbase, &bv, op->o_tmpmemctx );
281 case LDAP_X_SCOPE_USERS:
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 ] == '.' ) )
300 Connection c = *op->o_conn;
301 char buf[ SLAP_LDAPDN_MAXLEN ];
307 if ( sizeof( buf ) <= uri->bv_len ) {
308 return LDAP_INVALID_SYNTAX;
311 id.bv_len = uri->bv_len;
313 strncpy( buf, uri->bv_val, sizeof( buf ) );
315 rc = slap_parse_user( &id, &user, &realm, &mech );
316 if ( rc != LDAP_SUCCESS ) {
320 if ( !BER_BVISNULL( &mech ) ) {
321 c.c_sasl_bind_mech = mech;
323 BER_BVSTR( &c.c_sasl_bind_mech, "AUTHZ" );
326 rc = slap_sasl_getdn( &c, op, &user,
327 realm.bv_val, nbase, SLAP_GETDN_AUTHZID );
329 if ( rc == LDAP_SUCCESS ) {
330 *scope = LDAP_X_SCOPE_EXACT;
336 * group[/<groupoc>[/<groupat>]]:<groupdn>
338 * groupoc defaults to "groupOfNames"
339 * groupat defaults to "member"
341 * <groupdn> must pass DN normalization
343 } else if ( strncasecmp( uri->bv_val, "group", STRLENOF( "group" ) ) == 0 )
345 struct berval group_dn = BER_BVNULL,
346 group_oc = BER_BVNULL,
347 member_at = BER_BVNULL;
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;
359 if ( bv.bv_val[ 0 ] == '/' ) {
360 group_oc.bv_val = &bv.bv_val[ 1 ];
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;
366 member_at.bv_len = group_dn.bv_val - member_at.bv_val;
369 group_oc.bv_len = group_dn.bv_val - group_oc.bv_val;
370 BER_BVSTR( &member_at, "member" );
374 BER_BVSTR( &group_oc, "groupOfNames" );
377 group_dn.bv_len = uri->bv_len - ( group_dn.bv_val - uri->bv_val );
379 rc = dnNormalize( 0, NULL, NULL, &group_dn, nbase, op->o_tmpmemctx );
380 if ( rc != LDAP_SUCCESS ) {
384 *scope = LDAP_X_SCOPE_GROUP;
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 );
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( "=" ) );
404 * ldap:///<base>??<scope>?<filter>
405 * <scope> ::= {base|one|subtree}
407 * <scope> defaults to "base"
408 * <base> must pass DN normalization
409 * <filter> must pass str2filter()
411 rc = ldap_url_parse( uri->bv_val, &ludp );
413 case LDAP_URL_SUCCESS:
414 /* FIXME: the check is pedantic, but I think it's necessary,
415 * because people tend to use things like ldaps:// which
416 * gives the idea SSL is being used. Maybe we could
417 * accept ldapi:// as well, but the point is that we use
418 * an URL as an easy means to define bits of a search with
421 if ( strcasecmp( ludp->lud_scheme, "ldap" ) != 0 ) {
425 return LDAP_PROTOCOL_ERROR;
429 case LDAP_URL_ERR_BADSCHEME:
431 * last chance: assume it's a(n exact) DN ...
433 * NOTE: must pass DN normalization
435 bv.bv_val = uri->bv_val;
436 *scope = LDAP_X_SCOPE_EXACT;
440 return LDAP_PROTOCOL_ERROR;
443 if ( ( ludp->lud_host && *ludp->lud_host )
444 || ludp->lud_attrs || ludp->lud_exts )
446 /* host part must be empty */
447 /* attrs and extensions parts must be empty */
448 rc = LDAP_PROTOCOL_ERROR;
453 *scope = ludp->lud_scope;
455 /* Grab the filter */
456 if ( ludp->lud_filter ) {
457 *filter = str2filter_x( op, ludp->lud_filter );
458 if ( *filter == NULL ) {
459 rc = LDAP_PROTOCOL_ERROR;
462 ber_str2bv( ludp->lud_filter, 0, 0, fstr );
465 /* Grab the searchbase */
466 ber_str2bv( ludp->lud_dn, 0, 0, base );
467 rc = dnNormalize( 0, NULL, NULL, base, nbase, op->o_tmpmemctx );
470 if( rc != LDAP_SUCCESS ) {
471 if( *filter ) filter_free_x( op, *filter );
475 /* Don't free these, return them to caller */
476 ludp->lud_filter = NULL;
480 ldap_free_urldesc( ludp );
484 static int slap_sasl_rx_off(char *rep, int *off)
489 /* Precompile replace pattern. Find the $<n> placeholders */
492 for ( c = rep; *c; c++ ) {
493 if ( *c == '\\' && c[1] ) {
498 if ( n == SASLREGEX_REPLACE ) {
499 Debug( LDAP_DEBUG_ANY,
500 "SASL replace pattern %s has too many $n "
501 "placeholders (max %d)\n",
502 rep, SASLREGEX_REPLACE, 0 );
504 return( LDAP_OTHER );
511 /* Final placeholder, after the last $n */
515 return( LDAP_SUCCESS );
518 #ifdef SLAP_AUTH_REWRITE
519 int slap_sasl_rewrite_config(
529 /* init at first call */
530 if ( sasl_rwinfo == NULL ) {
531 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
534 /* strip "authid-" prefix for parsing */
536 argv[0] += STRLENOF( "authid-" );
537 rc = rewrite_parse( sasl_rwinfo, fname, lineno, argc, argv );
543 int slap_sasl_rewrite_destroy( void )
546 rewrite_info_delete( &sasl_rwinfo );
553 int slap_sasl_regexp_rewrite_config(
558 const char *context )
561 char *argvRule[] = { "rewriteRule", NULL, NULL, ":@", NULL };
563 /* init at first call */
564 if ( sasl_rwinfo == NULL ) {
565 char *argvEngine[] = { "rewriteEngine", "on", NULL };
566 char *argvContext[] = { "rewriteContext", NULL, NULL };
568 /* initialize rewrite engine */
569 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
571 /* switch on rewrite engine */
572 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvEngine );
573 if (rc != LDAP_SUCCESS) {
577 /* create generic authid context */
578 argvContext[1] = AUTHID_CONTEXT;
579 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvContext );
580 if (rc != LDAP_SUCCESS) {
585 argvRule[1] = (char *)match;
586 argvRule[2] = (char *)replace;
587 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 4, argvRule );
591 #endif /* SLAP_AUTH_REWRITE */
593 int slap_sasl_regexp_config( const char *match, const char *replace )
595 #ifdef SLAP_AUTH_REWRITE
596 return slap_sasl_regexp_rewrite_config( "sasl-regexp", 0,
597 match, replace, AUTHID_CONTEXT );
598 #else /* ! SLAP_AUTH_REWRITE */
602 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
603 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
605 reg = &SaslRegexp[nSaslRegexp];
607 reg->sr_match = ch_strdup( match );
608 reg->sr_replace = ch_strdup( replace );
610 /* Precompile matching pattern */
611 rc = regcomp( ®->sr_workspace, reg->sr_match, REG_EXTENDED|REG_ICASE );
613 Debug( LDAP_DEBUG_ANY,
614 "SASL match pattern %s could not be compiled by regexp engine\n",
615 reg->sr_match, 0, 0 );
617 return( LDAP_OTHER );
620 rc = slap_sasl_rx_off( reg->sr_replace, reg->sr_offset );
621 if ( rc != LDAP_SUCCESS ) return rc;
624 return( LDAP_SUCCESS );
625 #endif /* ! SLAP_AUTH_REWRITE */
628 /* Perform replacement on regexp matches */
629 static void slap_sasl_rx_exp(
633 const char *saslname,
637 int i, n, len, insert;
639 /* Get the total length of the final URI */
643 while( off[n] >= 0 ) {
644 /* Len of next section from replacement string (x,y,z above) */
645 len += off[n] - off[n-1] - 2;
649 /* Len of string from saslname that matched next $i (b,d above) */
650 i = rep[ off[n] + 1 ] - '0';
651 len += str[i].rm_eo - str[i].rm_so;
654 out->bv_val = slap_sl_malloc( len + 1, ctx );
657 /* Fill in URI with replace string, replacing $i as we go */
660 while( off[n] >= 0) {
661 /* Paste in next section from replacement string (x,y,z above) */
662 len = off[n] - off[n-1] - 2;
663 strncpy( out->bv_val+insert, rep + off[n-1] + 2, len);
668 /* Paste in string from saslname that matched next $i (b,d above) */
669 i = rep[ off[n] + 1 ] - '0';
670 len = str[i].rm_eo - str[i].rm_so;
671 strncpy( out->bv_val+insert, saslname + str[i].rm_so, len );
677 out->bv_val[insert] = '\0';
680 /* Take the passed in SASL name and attempt to convert it into an
681 LDAP URI to find the matching LDAP entry, using the pattern matching
682 strings given in the saslregexp config file directive(s) */
684 static int slap_authz_regexp( struct berval *in, struct berval *out,
685 int flags, void *ctx )
687 #ifdef SLAP_AUTH_REWRITE
688 const char *context = AUTHID_CONTEXT;
690 if ( sasl_rwinfo == NULL || BER_BVISNULL( in ) ) {
694 /* FIXME: if aware of authc/authz mapping,
695 * we could use different contexts ... */
696 switch ( rewrite_session( sasl_rwinfo, context, in->bv_val, NULL,
699 case REWRITE_REGEXEC_OK:
700 if ( !BER_BVISNULL( out ) ) {
701 char *val = out->bv_val;
702 ber_str2bv_x( val, 0, 1, out, ctx );
703 if ( val != in->bv_val ) {
707 ber_dupbv_x( out, in, ctx );
709 Debug( LDAP_DEBUG_ARGS,
710 "[rw] %s: \"%s\" -> \"%s\"\n",
711 context, in->bv_val, out->bv_val );
714 case REWRITE_REGEXEC_UNWILLING:
715 case REWRITE_REGEXEC_ERR:
720 #else /* ! SLAP_AUTH_REWRITE */
721 char *saslname = in->bv_val;
723 regmatch_t sr_strings[SASLREGEX_REPLACE]; /* strings matching $1,$2 ... */
726 memset( out, 0, sizeof( *out ) );
728 Debug( LDAP_DEBUG_TRACE, "slap_authz_regexp: converting SASL name %s\n",
731 if (( saslname == NULL ) || ( nSaslRegexp == 0 )) {
735 /* Match the normalized SASL name to the saslregexp patterns */
736 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
737 if ( regexec( ®->sr_workspace, saslname, SASLREGEX_REPLACE,
738 sr_strings, 0) == 0 )
742 if( i >= nSaslRegexp ) return( 0 );
745 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
746 * replace pattern of the form "x$1y$2z". The returned string needs
747 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
749 slap_sasl_rx_exp( reg->sr_replace, reg->sr_offset,
750 sr_strings, saslname, out, ctx );
752 Debug( LDAP_DEBUG_TRACE,
753 "slap_authz_regexp: converted SASL name to %s\n",
754 BER_BVISEMPTY( out ) ? "" : out->bv_val, 0, 0 );
757 #endif /* ! SLAP_AUTH_REWRITE */
760 /* This callback actually does some work...*/
761 static int sasl_sc_sasl2dn( Operation *o, SlapReply *rs )
763 struct berval *ndn = o->o_callback->sc_private;
765 if (rs->sr_type != REP_SEARCH) return 0;
767 /* We only want to be called once */
768 if ( !BER_BVISNULL( ndn ) ) {
769 o->o_tmpfree(ndn->bv_val, o->o_tmpmemctx);
772 Debug( LDAP_DEBUG_TRACE,
773 "slap_sc_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
777 ber_dupbv_x(ndn, &rs->sr_entry->e_nname, o->o_tmpmemctx);
782 typedef struct smatch_info {
787 static int sasl_sc_smatch( Operation *o, SlapReply *rs )
789 smatch_info *sm = o->o_callback->sc_private;
791 if ( rs->sr_type != REP_SEARCH ) {
792 if ( rs->sr_err != LDAP_SUCCESS ) {
798 if ( sm->match == 1 ) {
803 if (dn_match(sm->dn, &rs->sr_entry->e_nname)) {
814 slap_sasl_matches( Operation *op, BerVarray rules,
815 struct berval *assertDN, struct berval *authc )
817 int rc = LDAP_INAPPROPRIATE_AUTH;
819 if ( rules != NULL ) {
822 for( i = 0; !BER_BVISNULL( &rules[i] ); i++ ) {
823 rc = slap_sasl_match( op, &rules[i], assertDN, authc );
824 if ( rc == LDAP_SUCCESS ) break;
832 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
833 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
834 * the rule must be used as an internal search for entries. If that search
835 * returns the *assertDN entry, the match is successful.
837 * The assertDN should not have the dn: prefix
841 int slap_sasl_match( Operation *opx, struct berval *rule,
842 struct berval *assertDN, struct berval *authc )
847 slap_callback cb = { NULL, sasl_sc_smatch, NULL, NULL };
849 SlapReply rs = {REP_RESULT};
850 struct berval base = BER_BVNULL;
856 Debug( LDAP_DEBUG_TRACE,
857 "===>slap_sasl_match: comparing DN %s to rule %s\n",
858 assertDN->bv_val, rule->bv_val, 0 );
860 rc = slap_parseURI( opx, rule, &base,
861 &op.o_req_ndn, &op.ors_scope, &op.ors_filter,
863 if( rc != LDAP_SUCCESS ) goto CONCLUDED;
865 switch ( op.ors_scope ) {
866 case LDAP_X_SCOPE_EXACT:
868 if ( dn_match( &op.o_req_ndn, assertDN ) ) {
871 rc = LDAP_INAPPROPRIATE_AUTH;
875 case LDAP_X_SCOPE_CHILDREN:
876 case LDAP_X_SCOPE_SUBTREE:
877 case LDAP_X_SCOPE_ONELEVEL:
879 int d = assertDN->bv_len - op.o_req_ndn.bv_len;
881 rc = LDAP_INAPPROPRIATE_AUTH;
883 if ( d == 0 && op.ors_scope == LDAP_X_SCOPE_SUBTREE ) {
886 } else if ( d > 0 ) {
889 /* leave room for at least one char of attributeType,
890 * one for '=' and one for ',' */
891 if ( d < STRLENOF( "x=,") ) {
895 bv.bv_len = op.o_req_ndn.bv_len;
896 bv.bv_val = assertDN->bv_val + d;
898 if ( bv.bv_val[ -1 ] == ',' && dn_match( &op.o_req_ndn, &bv ) ) {
899 switch ( op.ors_scope ) {
900 case LDAP_X_SCOPE_SUBTREE:
901 case LDAP_X_SCOPE_CHILDREN:
905 case LDAP_X_SCOPE_ONELEVEL:
909 dnParent( assertDN, &pdn );
910 /* the common portion of the DN
911 * already matches, so only check
912 * if parent DN of assertedDN
913 * is all the pattern */
914 if ( pdn.bv_len == op.o_req_ndn.bv_len ) {
920 /* at present, impossible */
928 case LDAP_X_SCOPE_REGEX:
929 rc = regcomp(®, op.o_req_ndn.bv_val,
930 REG_EXTENDED|REG_ICASE|REG_NOSUB);
932 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
938 rc = LDAP_INAPPROPRIATE_AUTH;
942 case LDAP_X_SCOPE_GROUP: {
945 /* Now filterstr looks like "(&(objectClass=<group_oc>)(<member_at>="
946 * we need to append the <assertDN> so that the <group_dn> is searched
947 * with scope "base", and the filter ensures that <assertDN> is
948 * member of the group */
949 tmp = ch_realloc( op.ors_filterstr.bv_val, op.ors_filterstr.bv_len +
950 assertDN->bv_len + STRLENOF( /*"(("*/ "))" ) + 1 );
955 op.ors_filterstr.bv_val = tmp;
957 tmp = lutil_strcopy( &tmp[op.ors_filterstr.bv_len], assertDN->bv_val );
958 tmp = lutil_strcopy( tmp, /*"(("*/ "))" );
960 /* pass opx because str2filter_x may (and does) use o_tmpmfuncs */
961 op.ors_filter = str2filter_x( opx, op.ors_filterstr.bv_val );
962 if ( op.ors_filter == NULL ) {
963 rc = LDAP_PROTOCOL_ERROR;
966 op.ors_scope = LDAP_SCOPE_BASE;
968 /* hijack match DN: use that of the group instead of the assertDN;
969 * assertDN is now in the filter */
970 sm.dn = &op.o_req_ndn;
976 case LDAP_X_SCOPE_USERS:
977 if ( !BER_BVISEMPTY( assertDN ) ) {
980 rc = LDAP_INAPPROPRIATE_AUTH;
988 /* Must run an internal search. */
989 if ( op.ors_filter == NULL ) {
990 rc = LDAP_FILTER_ERROR;
994 Debug( LDAP_DEBUG_TRACE,
995 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
996 op.o_req_ndn.bv_val, op.ors_scope, 0 );
998 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
999 if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
1000 rc = LDAP_INAPPROPRIATE_AUTH;
1004 op.o_hdr = opx->o_hdr;
1005 op.o_tag = LDAP_REQ_SEARCH;
1007 op.o_callback = &cb;
1008 op.o_time = slap_get_time();
1009 op.o_do_not_cache = 1;
1010 op.o_is_auth_check = 1;
1011 /* use req_ndn as req_dn instead of non-pretty base of uri */
1012 if( !BER_BVISNULL( &base ) ) {
1013 ch_free( base.bv_val );
1014 /* just in case... */
1015 BER_BVZERO( &base );
1017 ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1019 op.ors_tlimit = SLAP_NO_LIMIT;
1020 op.ors_attrs = slap_anlist_no_attrs;
1021 op.ors_attrsonly = 1;
1023 op.o_bd->be_search( &op, &rs );
1025 if (sm.match == 1) {
1028 rc = LDAP_INAPPROPRIATE_AUTH;
1032 if( !BER_BVISNULL( &op.o_req_dn ) ) slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1033 if( !BER_BVISNULL( &op.o_req_ndn ) ) slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1034 if( op.ors_filter ) filter_free_x( opx, op.ors_filter );
1035 if( !BER_BVISNULL( &op.ors_filterstr ) ) ch_free( op.ors_filterstr.bv_val );
1037 Debug( LDAP_DEBUG_TRACE,
1038 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
1045 * This function answers the question, "Can this ID authorize to that ID?",
1046 * based on authorization rules. The rules are stored in the *searchDN, in the
1047 * attribute named by *attr. If any of those rules map to the *assertDN, the
1048 * authorization is approved.
1050 * The DNs should not have the dn: prefix
1053 slap_sasl_check_authz( Operation *op,
1054 struct berval *searchDN,
1055 struct berval *assertDN,
1056 AttributeDescription *ad,
1057 struct berval *authc )
1060 BerVarray vals = NULL;
1062 Debug( LDAP_DEBUG_TRACE,
1063 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
1064 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
1066 rc = backend_attribute( op, NULL, searchDN, ad, &vals, ACL_AUTH );
1067 if( rc != LDAP_SUCCESS ) goto COMPLETE;
1069 /* Check if the *assertDN matches any *vals */
1070 rc = slap_sasl_matches( op, vals, assertDN, authc );
1073 if( vals ) ber_bvarray_free_x( vals, op->o_tmpmemctx );
1075 Debug( LDAP_DEBUG_TRACE,
1076 "<==slap_sasl_check_authz: %s check returning %d\n",
1077 ad->ad_cname.bv_val, rc, 0);
1083 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
1084 * return the LDAP DN to which it matches. The SASL regexp rules in the config
1085 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
1086 * search with scope=base), just return the URI (or its searchbase). Otherwise
1087 * an internal search must be done, and if that search returns exactly one
1088 * entry, return the DN of that one entry.
1090 void slap_sasl2dn( Operation *opx,
1091 struct berval *saslname, struct berval *sasldn, int flags )
1094 slap_callback cb = { NULL, sasl_sc_sasl2dn, NULL, NULL };
1096 SlapReply rs = {REP_RESULT};
1097 struct berval regout = BER_BVNULL;
1098 struct berval base = BER_BVNULL;
1100 Debug( LDAP_DEBUG_TRACE, "==>slap_sasl2dn: "
1101 "converting SASL name %s to a DN\n",
1102 saslname->bv_val, 0,0 );
1104 BER_BVZERO( sasldn );
1105 cb.sc_private = sasldn;
1107 /* Convert the SASL name into a minimal URI */
1108 if( !slap_authz_regexp( saslname, ®out, flags, opx->o_tmpmemctx ) ) {
1112 rc = slap_parseURI( opx, ®out, &base,
1113 &op.o_req_ndn, &op.ors_scope, &op.ors_filter,
1114 &op.ors_filterstr );
1115 if ( !BER_BVISNULL( ®out ) ) slap_sl_free( regout.bv_val, opx->o_tmpmemctx );
1116 if ( rc != LDAP_SUCCESS ) {
1120 /* Must do an internal search */
1121 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
1123 switch ( op.ors_scope ) {
1124 case LDAP_X_SCOPE_EXACT:
1125 *sasldn = op.o_req_ndn;
1126 BER_BVZERO( &op.o_req_ndn );
1127 /* intentionally continue to next case */
1129 case LDAP_X_SCOPE_REGEX:
1130 case LDAP_X_SCOPE_SUBTREE:
1131 case LDAP_X_SCOPE_CHILDREN:
1132 case LDAP_X_SCOPE_ONELEVEL:
1133 case LDAP_X_SCOPE_GROUP:
1134 case LDAP_X_SCOPE_USERS:
1135 /* correctly parsed, but illegal */
1138 case LDAP_SCOPE_BASE:
1139 case LDAP_SCOPE_ONELEVEL:
1140 case LDAP_SCOPE_SUBTREE:
1141 #ifdef LDAP_SCOPE_SUBORDINATE
1142 case LDAP_SCOPE_SUBORDINATE:
1148 /* catch unhandled cases (there shouldn't be) */
1152 Debug( LDAP_DEBUG_TRACE,
1153 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
1154 op.o_req_ndn.bv_val, op.ors_scope, 0 );
1156 if ( ( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL) ) {
1160 /* Must run an internal search. */
1161 if ( op.ors_filter == NULL ) {
1162 rc = LDAP_FILTER_ERROR;
1166 op.o_hdr = opx->o_hdr;
1167 op.o_tag = LDAP_REQ_SEARCH;
1168 op.o_ndn = opx->o_conn->c_ndn;
1169 op.o_callback = &cb;
1170 op.o_time = slap_get_time();
1171 op.o_do_not_cache = 1;
1172 op.o_is_auth_check = 1;
1173 op.ors_deref = LDAP_DEREF_NEVER;
1175 op.ors_tlimit = SLAP_NO_LIMIT;
1176 op.ors_attrs = slap_anlist_no_attrs;
1177 op.ors_attrsonly = 1;
1178 /* use req_ndn as req_dn instead of non-pretty base of uri */
1179 if( !BER_BVISNULL( &base ) ) {
1180 ch_free( base.bv_val );
1181 /* just in case... */
1182 BER_BVZERO( &base );
1184 ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1186 op.o_bd->be_search( &op, &rs );
1189 if( !BER_BVISEMPTY( sasldn ) ) {
1190 opx->o_conn->c_authz_backend = op.o_bd;
1192 if( !BER_BVISNULL( &op.o_req_dn ) ) {
1193 slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1195 if( !BER_BVISNULL( &op.o_req_ndn ) ) {
1196 slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1198 if( op.ors_filter ) {
1199 filter_free_x( opx, op.ors_filter );
1201 if( !BER_BVISNULL( &op.ors_filterstr ) ) {
1202 ch_free( op.ors_filterstr.bv_val );
1205 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
1206 !BER_BVISEMPTY( sasldn ) ? sasldn->bv_val : "<nothing>", 0, 0 );
1212 /* Check if a bind can SASL authorize to another identity.
1213 * The DNs should not have the dn: prefix
1216 int slap_sasl_authorized( Operation *op,
1217 struct berval *authcDN, struct berval *authzDN )
1219 int rc = LDAP_INAPPROPRIATE_AUTH;
1221 /* User binding as anonymous */
1222 if ( authzDN == NULL ) {
1227 Debug( LDAP_DEBUG_TRACE,
1228 "==>slap_sasl_authorized: can %s become %s?\n",
1229 authcDN->bv_val, authzDN->bv_val, 0 );
1231 /* If person is authorizing to self, succeed */
1232 if ( dn_match( authcDN, authzDN ) ) {
1237 /* Allow the manager to authorize as any DN. */
1238 if( op->o_conn->c_authz_backend &&
1239 be_isroot_dn( op->o_conn->c_authz_backend, authcDN ))
1245 /* Check source rules */
1246 if( authz_policy & SASL_AUTHZ_TO ) {
1247 rc = slap_sasl_check_authz( op, authcDN, authzDN,
1248 slap_schema.si_ad_saslAuthzTo, authcDN );
1249 if( rc == LDAP_SUCCESS && !(authz_policy & SASL_AUTHZ_AND) ) {
1254 /* Check destination rules */
1255 if( authz_policy & SASL_AUTHZ_FROM ) {
1256 rc = slap_sasl_check_authz( op, authzDN, authcDN,
1257 slap_schema.si_ad_saslAuthzFrom, authcDN );
1258 if( rc == LDAP_SUCCESS ) {
1263 rc = LDAP_INAPPROPRIATE_AUTH;
1267 Debug( LDAP_DEBUG_TRACE,
1268 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );