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>
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 if ( strcasecmp( ludp->lud_scheme, "ldap" ) != 0 ) {
418 return LDAP_PROTOCOL_ERROR;
422 case LDAP_URL_ERR_BADSCHEME:
424 * last chance: assume it's a(n exact) DN ...
426 * NOTE: must pass DN normalization
428 bv.bv_val = uri->bv_val;
429 *scope = LDAP_X_SCOPE_EXACT;
433 return LDAP_PROTOCOL_ERROR;
436 if ( ( ludp->lud_host && *ludp->lud_host )
437 || ludp->lud_attrs || ludp->lud_exts )
439 /* host part must be empty */
440 /* attrs and extensions parts must be empty */
441 rc = LDAP_PROTOCOL_ERROR;
446 *scope = ludp->lud_scope;
448 /* Grab the filter */
449 if ( ludp->lud_filter ) {
450 *filter = str2filter_x( op, ludp->lud_filter );
451 if ( *filter == NULL ) {
452 rc = LDAP_PROTOCOL_ERROR;
455 ber_str2bv( ludp->lud_filter, 0, 0, fstr );
458 /* Grab the searchbase */
459 ber_str2bv( ludp->lud_dn, 0, 0, base );
460 rc = dnNormalize( 0, NULL, NULL, base, nbase, op->o_tmpmemctx );
463 if( rc != LDAP_SUCCESS ) {
464 if( *filter ) filter_free_x( op, *filter );
468 /* Don't free these, return them to caller */
469 ludp->lud_filter = NULL;
473 ldap_free_urldesc( ludp );
477 static int slap_sasl_rx_off(char *rep, int *off)
482 /* Precompile replace pattern. Find the $<n> placeholders */
485 for ( c = rep; *c; c++ ) {
486 if ( *c == '\\' && c[1] ) {
491 if ( n == SASLREGEX_REPLACE ) {
492 Debug( LDAP_DEBUG_ANY,
493 "SASL replace pattern %s has too many $n "
494 "placeholders (max %d)\n",
495 rep, SASLREGEX_REPLACE, 0 );
497 return( LDAP_OTHER );
504 /* Final placeholder, after the last $n */
508 return( LDAP_SUCCESS );
511 #ifdef SLAP_AUTH_REWRITE
512 int slap_sasl_rewrite_config(
522 /* init at first call */
523 if ( sasl_rwinfo == NULL ) {
524 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
527 /* strip "authid-" prefix for parsing */
529 argv[0] += STRLENOF( "authid-" );
530 rc = rewrite_parse( sasl_rwinfo, fname, lineno, argc, argv );
536 int slap_sasl_rewrite_destroy( void )
539 rewrite_info_delete( &sasl_rwinfo );
546 int slap_sasl_regexp_rewrite_config(
551 const char *context )
554 char *argvRule[] = { "rewriteRule", NULL, NULL, ":@", NULL };
556 /* init at first call */
557 if ( sasl_rwinfo == NULL ) {
558 char *argvEngine[] = { "rewriteEngine", "on", NULL };
559 char *argvContext[] = { "rewriteContext", NULL, NULL };
561 /* initialize rewrite engine */
562 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
564 /* switch on rewrite engine */
565 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvEngine );
566 if (rc != LDAP_SUCCESS) {
570 /* create generic authid context */
571 argvContext[1] = AUTHID_CONTEXT;
572 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvContext );
573 if (rc != LDAP_SUCCESS) {
578 argvRule[1] = (char *)match;
579 argvRule[2] = (char *)replace;
580 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 4, argvRule );
584 #endif /* SLAP_AUTH_REWRITE */
586 int slap_sasl_regexp_config( const char *match, const char *replace )
588 #ifdef SLAP_AUTH_REWRITE
589 return slap_sasl_regexp_rewrite_config( "sasl-regexp", 0,
590 match, replace, AUTHID_CONTEXT );
591 #else /* ! SLAP_AUTH_REWRITE */
595 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
596 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
598 reg = &SaslRegexp[nSaslRegexp];
600 reg->sr_match = ch_strdup( match );
601 reg->sr_replace = ch_strdup( replace );
603 /* Precompile matching pattern */
604 rc = regcomp( ®->sr_workspace, reg->sr_match, REG_EXTENDED|REG_ICASE );
606 Debug( LDAP_DEBUG_ANY,
607 "SASL match pattern %s could not be compiled by regexp engine\n",
608 reg->sr_match, 0, 0 );
610 return( LDAP_OTHER );
613 rc = slap_sasl_rx_off( reg->sr_replace, reg->sr_offset );
614 if ( rc != LDAP_SUCCESS ) return rc;
617 return( LDAP_SUCCESS );
618 #endif /* ! SLAP_AUTH_REWRITE */
621 /* Perform replacement on regexp matches */
622 static void slap_sasl_rx_exp(
626 const char *saslname,
630 int i, n, len, insert;
632 /* Get the total length of the final URI */
636 while( off[n] >= 0 ) {
637 /* Len of next section from replacement string (x,y,z above) */
638 len += off[n] - off[n-1] - 2;
642 /* Len of string from saslname that matched next $i (b,d above) */
643 i = rep[ off[n] + 1 ] - '0';
644 len += str[i].rm_eo - str[i].rm_so;
647 out->bv_val = slap_sl_malloc( len + 1, ctx );
650 /* Fill in URI with replace string, replacing $i as we go */
653 while( off[n] >= 0) {
654 /* Paste in next section from replacement string (x,y,z above) */
655 len = off[n] - off[n-1] - 2;
656 strncpy( out->bv_val+insert, rep + off[n-1] + 2, len);
661 /* Paste in string from saslname that matched next $i (b,d above) */
662 i = rep[ off[n] + 1 ] - '0';
663 len = str[i].rm_eo - str[i].rm_so;
664 strncpy( out->bv_val+insert, saslname + str[i].rm_so, len );
670 out->bv_val[insert] = '\0';
673 /* Take the passed in SASL name and attempt to convert it into an
674 LDAP URI to find the matching LDAP entry, using the pattern matching
675 strings given in the saslregexp config file directive(s) */
677 static int slap_authz_regexp( struct berval *in, struct berval *out,
678 int flags, void *ctx )
680 #ifdef SLAP_AUTH_REWRITE
681 const char *context = AUTHID_CONTEXT;
683 if ( sasl_rwinfo == NULL || BER_BVISNULL( in ) ) {
687 /* FIXME: if aware of authc/authz mapping,
688 * we could use different contexts ... */
689 switch ( rewrite_session( sasl_rwinfo, context, in->bv_val, NULL,
692 case REWRITE_REGEXEC_OK:
693 if ( !BER_BVISNULL( out ) ) {
694 char *val = out->bv_val;
695 ber_str2bv_x( val, 0, 1, out, ctx );
698 ber_dupbv_x( out, in, ctx );
700 Debug( LDAP_DEBUG_ARGS,
701 "[rw] %s: \"%s\" -> \"%s\"\n",
702 context, in->bv_val, out->bv_val );
705 case REWRITE_REGEXEC_UNWILLING:
706 case REWRITE_REGEXEC_ERR:
711 #else /* ! SLAP_AUTH_REWRITE */
712 char *saslname = in->bv_val;
714 regmatch_t sr_strings[SASLREGEX_REPLACE]; /* strings matching $1,$2 ... */
717 memset( out, 0, sizeof( *out ) );
719 Debug( LDAP_DEBUG_TRACE, "slap_authz_regexp: converting SASL name %s\n",
722 if (( saslname == NULL ) || ( nSaslRegexp == 0 )) {
726 /* Match the normalized SASL name to the saslregexp patterns */
727 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
728 if ( regexec( ®->sr_workspace, saslname, SASLREGEX_REPLACE,
729 sr_strings, 0) == 0 )
733 if( i >= nSaslRegexp ) return( 0 );
736 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
737 * replace pattern of the form "x$1y$2z". The returned string needs
738 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
740 slap_sasl_rx_exp( reg->sr_replace, reg->sr_offset,
741 sr_strings, saslname, out, ctx );
743 Debug( LDAP_DEBUG_TRACE,
744 "slap_authz_regexp: converted SASL name to %s\n",
745 BER_BVISEMPTY( out ) ? "" : out->bv_val, 0, 0 );
748 #endif /* ! SLAP_AUTH_REWRITE */
751 /* This callback actually does some work...*/
752 static int sasl_sc_sasl2dn( Operation *o, SlapReply *rs )
754 struct berval *ndn = o->o_callback->sc_private;
756 if (rs->sr_type != REP_SEARCH) return 0;
758 /* We only want to be called once */
759 if ( !BER_BVISNULL( ndn ) ) {
760 o->o_tmpfree(ndn->bv_val, o->o_tmpmemctx);
763 Debug( LDAP_DEBUG_TRACE,
764 "slap_sc_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
768 ber_dupbv_x(ndn, &rs->sr_entry->e_nname, o->o_tmpmemctx);
773 typedef struct smatch_info {
778 static int sasl_sc_smatch( Operation *o, SlapReply *rs )
780 smatch_info *sm = o->o_callback->sc_private;
782 if ( rs->sr_type != REP_SEARCH ) {
783 if ( rs->sr_err != LDAP_SUCCESS ) {
789 if ( sm->match == 1 ) {
794 if (dn_match(sm->dn, &rs->sr_entry->e_nname)) {
805 slap_sasl_matches( Operation *op, BerVarray rules,
806 struct berval *assertDN, struct berval *authc )
808 int rc = LDAP_INAPPROPRIATE_AUTH;
810 if ( rules != NULL ) {
813 for( i = 0; !BER_BVISNULL( &rules[i] ); i++ ) {
814 rc = slap_sasl_match( op, &rules[i], assertDN, authc );
815 if ( rc == LDAP_SUCCESS ) break;
823 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
824 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
825 * the rule must be used as an internal search for entries. If that search
826 * returns the *assertDN entry, the match is successful.
828 * The assertDN should not have the dn: prefix
832 int slap_sasl_match( Operation *opx, struct berval *rule,
833 struct berval *assertDN, struct berval *authc )
838 slap_callback cb = { NULL, sasl_sc_smatch, NULL, NULL };
840 SlapReply rs = {REP_RESULT};
841 struct berval base = BER_BVNULL;
847 Debug( LDAP_DEBUG_TRACE,
848 "===>slap_sasl_match: comparing DN %s to rule %s\n",
849 assertDN->bv_val, rule->bv_val, 0 );
851 rc = slap_parseURI( opx, rule, &base,
852 &op.o_req_ndn, &op.ors_scope, &op.ors_filter,
854 if( rc != LDAP_SUCCESS ) goto CONCLUDED;
856 switch ( op.ors_scope ) {
857 case LDAP_X_SCOPE_EXACT:
859 if ( dn_match( &op.o_req_ndn, assertDN ) ) {
862 rc = LDAP_INAPPROPRIATE_AUTH;
866 case LDAP_X_SCOPE_CHILDREN:
867 case LDAP_X_SCOPE_SUBTREE:
868 case LDAP_X_SCOPE_ONELEVEL:
870 int d = assertDN->bv_len - op.o_req_ndn.bv_len;
872 rc = LDAP_INAPPROPRIATE_AUTH;
874 if ( d == 0 && op.ors_scope == LDAP_X_SCOPE_SUBTREE ) {
877 } else if ( d > 0 ) {
880 /* leave room for at least one char of attributeType,
881 * one for '=' and one for ',' */
882 if ( d < STRLENOF( "x=,") ) {
886 bv.bv_len = op.o_req_ndn.bv_len;
887 bv.bv_val = assertDN->bv_val + d;
889 if ( bv.bv_val[ -1 ] == ',' && dn_match( &op.o_req_ndn, &bv ) ) {
890 switch ( op.ors_scope ) {
891 case LDAP_X_SCOPE_SUBTREE:
892 case LDAP_X_SCOPE_CHILDREN:
896 case LDAP_X_SCOPE_ONELEVEL:
900 dnParent( assertDN, &pdn );
901 /* the common portion of the DN
902 * already matches, so only check
903 * if parent DN of assertedDN
904 * is all the pattern */
905 if ( pdn.bv_len == op.o_req_ndn.bv_len ) {
911 /* at present, impossible */
919 case LDAP_X_SCOPE_REGEX:
920 rc = regcomp(®, op.o_req_ndn.bv_val,
921 REG_EXTENDED|REG_ICASE|REG_NOSUB);
923 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
929 rc = LDAP_INAPPROPRIATE_AUTH;
933 case LDAP_X_SCOPE_GROUP: {
936 /* Now filterstr looks like "(&(objectClass=<group_oc>)(<member_at>="
937 * we need to append the <assertDN> so that the <group_dn> is searched
938 * with scope "base", and the filter ensures that <assertDN> is
939 * member of the group */
940 tmp = ch_realloc( op.ors_filterstr.bv_val, op.ors_filterstr.bv_len +
941 assertDN->bv_len + STRLENOF( /*"(("*/ "))" ) + 1 );
946 op.ors_filterstr.bv_val = tmp;
948 tmp = lutil_strcopy( &tmp[op.ors_filterstr.bv_len], assertDN->bv_val );
949 tmp = lutil_strcopy( tmp, /*"(("*/ "))" );
951 /* pass opx because str2filter_x may (and does) use o_tmpmfuncs */
952 op.ors_filter = str2filter_x( opx, op.ors_filterstr.bv_val );
953 if ( op.ors_filter == NULL ) {
954 rc = LDAP_PROTOCOL_ERROR;
957 op.ors_scope = LDAP_SCOPE_BASE;
959 /* hijack match DN: use that of the group instead of the assertDN;
960 * assertDN is now in the filter */
961 sm.dn = &op.o_req_ndn;
967 case LDAP_X_SCOPE_USERS:
968 if ( !BER_BVISEMPTY( assertDN ) ) {
971 rc = LDAP_INAPPROPRIATE_AUTH;
979 /* Must run an internal search. */
980 if ( op.ors_filter == NULL ) {
981 rc = LDAP_FILTER_ERROR;
985 Debug( LDAP_DEBUG_TRACE,
986 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
987 op.o_req_ndn.bv_val, op.ors_scope, 0 );
989 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
990 if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
991 rc = LDAP_INAPPROPRIATE_AUTH;
995 op.o_hdr = opx->o_hdr;
996 op.o_tag = LDAP_REQ_SEARCH;
999 op.o_time = slap_get_time();
1000 op.o_do_not_cache = 1;
1001 op.o_is_auth_check = 1;
1002 /* use req_ndn as req_dn instead of non-pretty base of uri */
1003 if( !BER_BVISNULL( &base ) ) {
1004 ch_free( base.bv_val );
1005 /* just in case... */
1006 BER_BVZERO( &base );
1008 ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1010 op.ors_tlimit = SLAP_NO_LIMIT;
1011 op.ors_attrs = slap_anlist_no_attrs;
1012 op.ors_attrsonly = 1;
1014 op.o_bd->be_search( &op, &rs );
1016 if (sm.match == 1) {
1019 rc = LDAP_INAPPROPRIATE_AUTH;
1023 if( !BER_BVISNULL( &op.o_req_dn ) ) slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1024 if( !BER_BVISNULL( &op.o_req_ndn ) ) slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1025 if( op.ors_filter ) filter_free_x( opx, op.ors_filter );
1026 if( !BER_BVISNULL( &op.ors_filterstr ) ) ch_free( op.ors_filterstr.bv_val );
1028 Debug( LDAP_DEBUG_TRACE,
1029 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
1036 * This function answers the question, "Can this ID authorize to that ID?",
1037 * based on authorization rules. The rules are stored in the *searchDN, in the
1038 * attribute named by *attr. If any of those rules map to the *assertDN, the
1039 * authorization is approved.
1041 * The DNs should not have the dn: prefix
1044 slap_sasl_check_authz( Operation *op,
1045 struct berval *searchDN,
1046 struct berval *assertDN,
1047 AttributeDescription *ad,
1048 struct berval *authc )
1051 BerVarray vals = NULL;
1053 Debug( LDAP_DEBUG_TRACE,
1054 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
1055 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
1057 rc = backend_attribute( op, NULL, searchDN, ad, &vals, ACL_AUTH );
1058 if( rc != LDAP_SUCCESS ) goto COMPLETE;
1060 /* Check if the *assertDN matches any *vals */
1061 rc = slap_sasl_matches( op, vals, assertDN, authc );
1064 if( vals ) ber_bvarray_free_x( vals, op->o_tmpmemctx );
1066 Debug( LDAP_DEBUG_TRACE,
1067 "<==slap_sasl_check_authz: %s check returning %d\n",
1068 ad->ad_cname.bv_val, rc, 0);
1074 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
1075 * return the LDAP DN to which it matches. The SASL regexp rules in the config
1076 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
1077 * search with scope=base), just return the URI (or its searchbase). Otherwise
1078 * an internal search must be done, and if that search returns exactly one
1079 * entry, return the DN of that one entry.
1081 void slap_sasl2dn( Operation *opx,
1082 struct berval *saslname, struct berval *sasldn, int flags )
1085 slap_callback cb = { NULL, sasl_sc_sasl2dn, NULL, NULL };
1087 SlapReply rs = {REP_RESULT};
1088 struct berval regout = BER_BVNULL;
1089 struct berval base = BER_BVNULL;
1091 Debug( LDAP_DEBUG_TRACE, "==>slap_sasl2dn: "
1092 "converting SASL name %s to a DN\n",
1093 saslname->bv_val, 0,0 );
1095 sasldn->bv_val = NULL;
1097 cb.sc_private = sasldn;
1099 /* Convert the SASL name into a minimal URI */
1100 if( !slap_authz_regexp( saslname, ®out, flags, opx->o_tmpmemctx ) ) {
1104 rc = slap_parseURI( opx, ®out, &base,
1105 &op.o_req_ndn, &op.ors_scope, &op.ors_filter,
1106 &op.ors_filterstr );
1107 if ( !BER_BVISNULL( ®out ) ) slap_sl_free( regout.bv_val, opx->o_tmpmemctx );
1108 if ( rc != LDAP_SUCCESS ) {
1112 /* Must do an internal search */
1113 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
1115 switch ( op.ors_scope ) {
1116 case LDAP_X_SCOPE_EXACT:
1117 *sasldn = op.o_req_ndn;
1118 BER_BVZERO( &op.o_req_ndn );
1119 /* intentionally continue to next case */
1121 case LDAP_X_SCOPE_REGEX:
1122 case LDAP_X_SCOPE_SUBTREE:
1123 case LDAP_X_SCOPE_CHILDREN:
1124 case LDAP_X_SCOPE_ONELEVEL:
1125 case LDAP_X_SCOPE_GROUP:
1126 case LDAP_X_SCOPE_USERS:
1127 /* correctly parsed, but illegal */
1130 case LDAP_SCOPE_BASE:
1131 case LDAP_SCOPE_ONELEVEL:
1132 case LDAP_SCOPE_SUBTREE:
1133 #ifdef LDAP_SCOPE_SUBORDINATE
1134 case LDAP_SCOPE_SUBORDINATE:
1140 /* catch unhandled cases (there shouldn't be) */
1144 Debug( LDAP_DEBUG_TRACE,
1145 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
1146 op.o_req_ndn.bv_val, op.ors_scope, 0 );
1148 if ( ( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL) ) {
1152 /* Must run an internal search. */
1153 if ( op.ors_filter == NULL ) {
1154 rc = LDAP_FILTER_ERROR;
1158 op.o_hdr = opx->o_hdr;
1159 op.o_tag = LDAP_REQ_SEARCH;
1160 op.o_ndn = opx->o_conn->c_ndn;
1161 op.o_callback = &cb;
1162 op.o_time = slap_get_time();
1163 op.o_do_not_cache = 1;
1164 op.o_is_auth_check = 1;
1165 op.ors_deref = LDAP_DEREF_NEVER;
1167 op.ors_tlimit = SLAP_NO_LIMIT;
1168 op.ors_attrs = slap_anlist_no_attrs;
1169 op.ors_attrsonly = 1;
1170 /* use req_ndn as req_dn instead of non-pretty base of uri */
1171 if( !BER_BVISNULL( &base ) ) {
1172 ch_free( base.bv_val );
1173 /* just in case... */
1174 BER_BVZERO( &base );
1176 ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1178 op.o_bd->be_search( &op, &rs );
1181 if( !BER_BVISEMPTY( sasldn ) ) {
1182 opx->o_conn->c_authz_backend = op.o_bd;
1184 if( !BER_BVISNULL( &op.o_req_dn ) ) {
1185 slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1187 if( !BER_BVISNULL( &op.o_req_ndn ) ) {
1188 slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1190 if( op.ors_filter ) {
1191 filter_free_x( opx, op.ors_filter );
1193 if( !BER_BVISNULL( &op.ors_filterstr ) ) {
1194 ch_free( op.ors_filterstr.bv_val );
1197 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
1198 !BER_BVISEMPTY( sasldn ) ? sasldn->bv_val : "<nothing>", 0, 0 );
1204 /* Check if a bind can SASL authorize to another identity.
1205 * The DNs should not have the dn: prefix
1208 int slap_sasl_authorized( Operation *op,
1209 struct berval *authcDN, struct berval *authzDN )
1211 int rc = LDAP_INAPPROPRIATE_AUTH;
1213 /* User binding as anonymous */
1214 if ( authzDN == NULL ) {
1219 Debug( LDAP_DEBUG_TRACE,
1220 "==>slap_sasl_authorized: can %s become %s?\n",
1221 authcDN->bv_val, authzDN->bv_val, 0 );
1223 /* If person is authorizing to self, succeed */
1224 if ( dn_match( authcDN, authzDN ) ) {
1229 /* Allow the manager to authorize as any DN. */
1230 if( op->o_conn->c_authz_backend &&
1231 be_isroot_dn( op->o_conn->c_authz_backend, authcDN ))
1237 /* Check source rules */
1238 if( authz_policy & SASL_AUTHZ_TO ) {
1239 rc = slap_sasl_check_authz( op, authcDN, authzDN,
1240 slap_schema.si_ad_saslAuthzTo, authcDN );
1241 if( rc == LDAP_SUCCESS && !(authz_policy & SASL_AUTHZ_AND) ) {
1246 /* Check destination rules */
1247 if( authz_policy & SASL_AUTHZ_FROM ) {
1248 rc = slap_sasl_check_authz( op, authzDN, authcDN,
1249 slap_schema.si_ad_saslAuthzFrom, authcDN );
1250 if( rc == LDAP_SUCCESS ) {
1255 rc = LDAP_INAPPROPRIATE_AUTH;
1259 Debug( LDAP_DEBUG_TRACE,
1260 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );