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 const char *policy_txt[] = {
91 "none", "from", "to", "any"
94 static int authz_policy = SASL_AUTHZ_NONE;
97 int slap_sasl_match( Operation *opx, struct berval *rule,
98 struct berval *assertDN, struct berval *authc );
100 int slap_sasl_setpolicy( const char *arg )
102 int rc = LDAP_SUCCESS;
104 if ( strcasecmp( arg, "none" ) == 0 ) {
105 authz_policy = SASL_AUTHZ_NONE;
106 } else if ( strcasecmp( arg, "from" ) == 0 ) {
107 authz_policy = SASL_AUTHZ_FROM;
108 } else if ( strcasecmp( arg, "to" ) == 0 ) {
109 authz_policy = SASL_AUTHZ_TO;
110 } else if ( strcasecmp( arg, "both" ) == 0 || strcasecmp( arg, "any" ) == 0 ) {
111 authz_policy = SASL_AUTHZ_FROM | SASL_AUTHZ_TO;
112 } else if ( strcasecmp( arg, "all" ) == 0 ) {
113 authz_policy = SASL_AUTHZ_FROM | SASL_AUTHZ_TO | SASL_AUTHZ_AND;
120 const char * slap_sasl_getpolicy()
122 if ( authz_policy == (SASL_AUTHZ_FROM | SASL_AUTHZ_TO | SASL_AUTHZ_AND) )
125 return policy_txt[authz_policy];
128 int slap_parse_user( struct berval *id, struct berval *user,
129 struct berval *realm, struct berval *mech )
134 assert( !BER_BVISNULL( id ) );
141 if ( u != 'u' && u != 'U' ) {
142 /* called with something other than u: */
143 return LDAP_PROTOCOL_ERROR;
147 * u[.mech[/realm]]:user
150 user->bv_val = strchr( id->bv_val, ':' );
151 if ( BER_BVISNULL( user ) ) {
152 return LDAP_PROTOCOL_ERROR;
154 user->bv_val[ 0 ] = '\0';
156 user->bv_len = id->bv_len - ( user->bv_val - id->bv_val );
158 mech->bv_val = strchr( id->bv_val, '.' );
159 if ( !BER_BVISNULL( mech ) ) {
160 mech->bv_val[ 0 ] = '\0';
163 realm->bv_val = strchr( mech->bv_val, '/' );
165 if ( !BER_BVISNULL( realm ) ) {
166 realm->bv_val[ 0 ] = '\0';
168 mech->bv_len = realm->bv_val - mech->bv_val - 1;
169 realm->bv_len = user->bv_val - realm->bv_val - 1;
171 mech->bv_len = user->bv_val - mech->bv_val - 1;
178 if ( id->bv_val[ 1 ] != '\0' ) {
179 return LDAP_PROTOCOL_ERROR;
182 if ( !BER_BVISNULL( mech ) ) {
183 assert( mech->bv_val == id->bv_val + 2 );
185 AC_MEMCPY( mech->bv_val - 2, mech->bv_val, mech->bv_len + 1 );
189 if ( !BER_BVISNULL( realm ) ) {
190 assert( realm->bv_val >= id->bv_val + 2 );
192 AC_MEMCPY( realm->bv_val - 2, realm->bv_val, realm->bv_len + 1 );
196 /* leave "u:" before user */
199 user->bv_val[ 0 ] = u;
200 user->bv_val[ 1 ] = ':';
205 static int slap_parseURI( Operation *op, struct berval *uri,
206 struct berval *base, struct berval *nbase,
207 int *scope, Filter **filter, struct berval *fstr )
213 assert( uri != NULL && !BER_BVISNULL( uri ) );
220 Debug( LDAP_DEBUG_TRACE,
221 "slap_parseURI: parsing %s\n", uri->bv_val, 0, 0 );
223 rc = LDAP_PROTOCOL_ERROR;
225 * dn[.<dnstyle>]:<dnpattern>
226 * <dnstyle> ::= {exact|regex|children|subtree|onelevel}
228 * <dnstyle> defaults to "exact"
229 * if <dnstyle> is not "regex", <dnpattern> must pass DN normalization
231 if ( !strncasecmp( uri->bv_val, "dn", STRLENOF( "dn" ) ) ) {
232 bv.bv_val = uri->bv_val + STRLENOF( "dn" );
234 if ( bv.bv_val[ 0 ] == '.' ) {
237 if ( !strncasecmp( bv.bv_val, "exact:", STRLENOF( "exact:" ) ) ) {
238 bv.bv_val += STRLENOF( "exact:" );
239 *scope = LDAP_X_SCOPE_EXACT;
241 } else if ( !strncasecmp( bv.bv_val, "regex:", STRLENOF( "regex:" ) ) ) {
242 bv.bv_val += STRLENOF( "regex:" );
243 *scope = LDAP_X_SCOPE_REGEX;
245 } else if ( !strncasecmp( bv.bv_val, "children:", STRLENOF( "children:" ) ) ) {
246 bv.bv_val += STRLENOF( "children:" );
247 *scope = LDAP_X_SCOPE_CHILDREN;
249 } else if ( !strncasecmp( bv.bv_val, "subtree:", STRLENOF( "subtree:" ) ) ) {
250 bv.bv_val += STRLENOF( "subtree:" );
251 *scope = LDAP_X_SCOPE_SUBTREE;
253 } else if ( !strncasecmp( bv.bv_val, "onelevel:", STRLENOF( "onelevel:" ) ) ) {
254 bv.bv_val += STRLENOF( "onelevel:" );
255 *scope = LDAP_X_SCOPE_ONELEVEL;
258 return LDAP_PROTOCOL_ERROR;
262 if ( bv.bv_val[ 0 ] != ':' ) {
263 return LDAP_PROTOCOL_ERROR;
265 *scope = LDAP_X_SCOPE_EXACT;
269 bv.bv_val += strspn( bv.bv_val, " " );
270 /* jump here in case no type specification was present
271 * and uri was not an URI... HEADS-UP: assuming EXACT */
272 is_dn: bv.bv_len = uri->bv_len - (bv.bv_val - uri->bv_val);
274 /* a single '*' means any DN without using regexes */
275 if ( ber_bvccmp( &bv, '*' ) ) {
276 *scope = LDAP_X_SCOPE_USERS;
280 case LDAP_X_SCOPE_EXACT:
281 case LDAP_X_SCOPE_CHILDREN:
282 case LDAP_X_SCOPE_SUBTREE:
283 case LDAP_X_SCOPE_ONELEVEL:
284 rc = dnNormalize( 0, NULL, NULL, &bv, nbase, op->o_tmpmemctx );
285 if( rc != LDAP_SUCCESS ) {
290 case LDAP_X_SCOPE_REGEX:
291 ber_dupbv_x( nbase, &bv, op->o_tmpmemctx );
293 case LDAP_X_SCOPE_USERS:
307 } else if ( ( uri->bv_val[ 0 ] == 'u' || uri->bv_val[ 0 ] == 'U' )
308 && ( uri->bv_val[ 1 ] == ':'
309 || uri->bv_val[ 1 ] == '/'
310 || uri->bv_val[ 1 ] == '.' ) )
312 Connection c = *op->o_conn;
313 char buf[ SLAP_LDAPDN_MAXLEN ];
319 if ( sizeof( buf ) <= uri->bv_len ) {
320 return LDAP_INVALID_SYNTAX;
323 id.bv_len = uri->bv_len;
325 strncpy( buf, uri->bv_val, sizeof( buf ) );
327 rc = slap_parse_user( &id, &user, &realm, &mech );
328 if ( rc != LDAP_SUCCESS ) {
332 if ( !BER_BVISNULL( &mech ) ) {
333 c.c_sasl_bind_mech = mech;
335 BER_BVSTR( &c.c_sasl_bind_mech, "AUTHZ" );
338 rc = slap_sasl_getdn( &c, op, &user,
339 realm.bv_val, nbase, SLAP_GETDN_AUTHZID );
341 if ( rc == LDAP_SUCCESS ) {
342 *scope = LDAP_X_SCOPE_EXACT;
348 * group[/<groupoc>[/<groupat>]]:<groupdn>
350 * groupoc defaults to "groupOfNames"
351 * groupat defaults to "member"
353 * <groupdn> must pass DN normalization
355 } else if ( strncasecmp( uri->bv_val, "group", STRLENOF( "group" ) ) == 0 )
357 struct berval group_dn = BER_BVNULL,
358 group_oc = BER_BVNULL,
359 member_at = BER_BVNULL;
362 bv.bv_val = uri->bv_val + STRLENOF( "group" );
363 group_dn.bv_val = strchr( bv.bv_val, ':' );
364 if ( group_dn.bv_val == NULL ) {
365 /* last chance: assume it's a(n exact) DN ... */
366 bv.bv_val = uri->bv_val;
367 *scope = LDAP_X_SCOPE_EXACT;
371 if ( bv.bv_val[ 0 ] == '/' ) {
372 group_oc.bv_val = &bv.bv_val[ 1 ];
374 member_at.bv_val = strchr( group_oc.bv_val, '/' );
375 if ( member_at.bv_val ) {
376 group_oc.bv_len = member_at.bv_val - group_oc.bv_val;
378 member_at.bv_len = group_dn.bv_val - member_at.bv_val;
381 group_oc.bv_len = group_dn.bv_val - group_oc.bv_val;
382 BER_BVSTR( &member_at, "member" );
386 BER_BVSTR( &group_oc, "groupOfNames" );
389 group_dn.bv_len = uri->bv_len - ( group_dn.bv_val - uri->bv_val );
391 rc = dnNormalize( 0, NULL, NULL, &group_dn, nbase, op->o_tmpmemctx );
392 if ( rc != LDAP_SUCCESS ) {
396 *scope = LDAP_X_SCOPE_GROUP;
398 /* FIXME: caller needs to add value of member attribute
399 * and close brackets twice */
400 fstr->bv_len = STRLENOF( "(&(objectClass=)(=" /* )) */ )
401 + group_oc.bv_len + member_at.bv_len;
402 fstr->bv_val = ch_malloc( fstr->bv_len + 1 );
404 tmp = lutil_strncopy( fstr->bv_val, "(&(objectClass=" /* )) */ ,
405 STRLENOF( "(&(objectClass=" /* )) */ ) );
406 tmp = lutil_strncopy( tmp, group_oc.bv_val, group_oc.bv_len );
407 tmp = lutil_strncopy( tmp, /* ( */ ")(" /* ) */ ,
408 STRLENOF( /* ( */ ")(" /* ) */ ) );
409 tmp = lutil_strncopy( tmp, member_at.bv_val, member_at.bv_len );
410 tmp = lutil_strncopy( tmp, "=", STRLENOF( "=" ) );
416 * ldap:///<base>??<scope>?<filter>
417 * <scope> ::= {base|one|subtree}
419 * <scope> defaults to "base"
420 * <base> must pass DN normalization
421 * <filter> must pass str2filter()
423 rc = ldap_url_parse( uri->bv_val, &ludp );
425 case LDAP_URL_SUCCESS:
426 /* FIXME: the check is pedantic, but I think it's necessary,
427 * because people tend to use things like ldaps:// which
428 * gives the idea SSL is being used. Maybe we could
429 * accept ldapi:// as well, but the point is that we use
430 * an URL as an easy means to define bits of a search with
433 if ( strcasecmp( ludp->lud_scheme, "ldap" ) != 0 ) {
437 return LDAP_PROTOCOL_ERROR;
441 case LDAP_URL_ERR_BADSCHEME:
443 * last chance: assume it's a(n exact) DN ...
445 * NOTE: must pass DN normalization
447 bv.bv_val = uri->bv_val;
448 *scope = LDAP_X_SCOPE_EXACT;
452 return LDAP_PROTOCOL_ERROR;
455 if ( ( ludp->lud_host && *ludp->lud_host )
456 || ludp->lud_attrs || ludp->lud_exts )
458 /* host part must be empty */
459 /* attrs and extensions parts must be empty */
460 rc = LDAP_PROTOCOL_ERROR;
465 *scope = ludp->lud_scope;
467 /* Grab the filter */
468 if ( ludp->lud_filter ) {
469 *filter = str2filter_x( op, ludp->lud_filter );
470 if ( *filter == NULL ) {
471 rc = LDAP_PROTOCOL_ERROR;
474 ber_str2bv( ludp->lud_filter, 0, 0, fstr );
477 /* Grab the searchbase */
478 ber_str2bv( ludp->lud_dn, 0, 0, base );
479 rc = dnNormalize( 0, NULL, NULL, base, nbase, op->o_tmpmemctx );
482 if( rc != LDAP_SUCCESS ) {
483 if( *filter ) filter_free_x( op, *filter );
487 /* Don't free these, return them to caller */
488 ludp->lud_filter = NULL;
492 ldap_free_urldesc( ludp );
496 static int slap_sasl_rx_off(char *rep, int *off)
501 /* Precompile replace pattern. Find the $<n> placeholders */
504 for ( c = rep; *c; c++ ) {
505 if ( *c == '\\' && c[1] ) {
510 if ( n == SASLREGEX_REPLACE ) {
511 Debug( LDAP_DEBUG_ANY,
512 "SASL replace pattern %s has too many $n "
513 "placeholders (max %d)\n",
514 rep, SASLREGEX_REPLACE, 0 );
516 return( LDAP_OTHER );
523 /* Final placeholder, after the last $n */
527 return( LDAP_SUCCESS );
530 #ifdef SLAP_AUTH_REWRITE
531 int slap_sasl_rewrite_config(
541 /* init at first call */
542 if ( sasl_rwinfo == NULL ) {
543 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
546 /* strip "authid-" prefix for parsing */
548 argv[0] += STRLENOF( "authid-" );
549 rc = rewrite_parse( sasl_rwinfo, fname, lineno, argc, argv );
555 int slap_sasl_rewrite_destroy( void )
558 rewrite_info_delete( &sasl_rwinfo );
565 int slap_sasl_regexp_rewrite_config(
570 const char *context )
573 char *argvRule[] = { "rewriteRule", NULL, NULL, ":@", NULL };
575 /* init at first call */
576 if ( sasl_rwinfo == NULL ) {
577 char *argvEngine[] = { "rewriteEngine", "on", NULL };
578 char *argvContext[] = { "rewriteContext", NULL, NULL };
580 /* initialize rewrite engine */
581 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
583 /* switch on rewrite engine */
584 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvEngine );
585 if (rc != LDAP_SUCCESS) {
589 /* create generic authid context */
590 argvContext[1] = AUTHID_CONTEXT;
591 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvContext );
592 if (rc != LDAP_SUCCESS) {
597 argvRule[1] = (char *)match;
598 argvRule[2] = (char *)replace;
599 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 4, argvRule );
603 #endif /* SLAP_AUTH_REWRITE */
605 int slap_sasl_regexp_config( const char *match, const char *replace )
607 #ifdef SLAP_AUTH_REWRITE
608 return slap_sasl_regexp_rewrite_config( "sasl-regexp", 0,
609 match, replace, AUTHID_CONTEXT );
610 #else /* ! SLAP_AUTH_REWRITE */
614 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
615 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
617 reg = &SaslRegexp[nSaslRegexp];
619 reg->sr_match = ch_strdup( match );
620 reg->sr_replace = ch_strdup( replace );
622 /* Precompile matching pattern */
623 rc = regcomp( ®->sr_workspace, reg->sr_match, REG_EXTENDED|REG_ICASE );
625 Debug( LDAP_DEBUG_ANY,
626 "SASL match pattern %s could not be compiled by regexp engine\n",
627 reg->sr_match, 0, 0 );
629 return( LDAP_OTHER );
632 rc = slap_sasl_rx_off( reg->sr_replace, reg->sr_offset );
633 if ( rc != LDAP_SUCCESS ) return rc;
636 return( LDAP_SUCCESS );
637 #endif /* ! SLAP_AUTH_REWRITE */
640 /* Perform replacement on regexp matches */
641 static void slap_sasl_rx_exp(
645 const char *saslname,
649 int i, n, len, insert;
651 /* Get the total length of the final URI */
655 while( off[n] >= 0 ) {
656 /* Len of next section from replacement string (x,y,z above) */
657 len += off[n] - off[n-1] - 2;
661 /* Len of 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;
666 out->bv_val = slap_sl_malloc( len + 1, ctx );
669 /* Fill in URI with replace string, replacing $i as we go */
672 while( off[n] >= 0) {
673 /* Paste in next section from replacement string (x,y,z above) */
674 len = off[n] - off[n-1] - 2;
675 strncpy( out->bv_val+insert, rep + off[n-1] + 2, len);
680 /* Paste in string from saslname that matched next $i (b,d above) */
681 i = rep[ off[n] + 1 ] - '0';
682 len = str[i].rm_eo - str[i].rm_so;
683 strncpy( out->bv_val+insert, saslname + str[i].rm_so, len );
689 out->bv_val[insert] = '\0';
692 /* Take the passed in SASL name and attempt to convert it into an
693 LDAP URI to find the matching LDAP entry, using the pattern matching
694 strings given in the saslregexp config file directive(s) */
696 static int slap_authz_regexp( struct berval *in, struct berval *out,
697 int flags, void *ctx )
699 #ifdef SLAP_AUTH_REWRITE
700 const char *context = AUTHID_CONTEXT;
702 if ( sasl_rwinfo == NULL || BER_BVISNULL( in ) ) {
706 /* FIXME: if aware of authc/authz mapping,
707 * we could use different contexts ... */
708 switch ( rewrite_session( sasl_rwinfo, context, in->bv_val, NULL,
711 case REWRITE_REGEXEC_OK:
712 if ( !BER_BVISNULL( out ) ) {
713 char *val = out->bv_val;
714 ber_str2bv_x( val, 0, 1, out, ctx );
715 if ( val != in->bv_val ) {
719 ber_dupbv_x( out, in, ctx );
721 Debug( LDAP_DEBUG_ARGS,
722 "[rw] %s: \"%s\" -> \"%s\"\n",
723 context, in->bv_val, out->bv_val );
726 case REWRITE_REGEXEC_UNWILLING:
727 case REWRITE_REGEXEC_ERR:
732 #else /* ! SLAP_AUTH_REWRITE */
733 char *saslname = in->bv_val;
735 regmatch_t sr_strings[SASLREGEX_REPLACE]; /* strings matching $1,$2 ... */
738 memset( out, 0, sizeof( *out ) );
740 Debug( LDAP_DEBUG_TRACE, "slap_authz_regexp: converting SASL name %s\n",
743 if (( saslname == NULL ) || ( nSaslRegexp == 0 )) {
747 /* Match the normalized SASL name to the saslregexp patterns */
748 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
749 if ( regexec( ®->sr_workspace, saslname, SASLREGEX_REPLACE,
750 sr_strings, 0) == 0 )
754 if( i >= nSaslRegexp ) return( 0 );
757 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
758 * replace pattern of the form "x$1y$2z". The returned string needs
759 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
761 slap_sasl_rx_exp( reg->sr_replace, reg->sr_offset,
762 sr_strings, saslname, out, ctx );
764 Debug( LDAP_DEBUG_TRACE,
765 "slap_authz_regexp: converted SASL name to %s\n",
766 BER_BVISEMPTY( out ) ? "" : out->bv_val, 0, 0 );
769 #endif /* ! SLAP_AUTH_REWRITE */
772 /* This callback actually does some work...*/
773 static int sasl_sc_sasl2dn( Operation *o, SlapReply *rs )
775 struct berval *ndn = o->o_callback->sc_private;
777 if (rs->sr_type != REP_SEARCH) return 0;
779 /* We only want to be called once */
780 if ( !BER_BVISNULL( ndn ) ) {
781 o->o_tmpfree(ndn->bv_val, o->o_tmpmemctx);
784 Debug( LDAP_DEBUG_TRACE,
785 "slap_sc_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
789 ber_dupbv_x(ndn, &rs->sr_entry->e_nname, o->o_tmpmemctx);
794 typedef struct smatch_info {
799 static int sasl_sc_smatch( Operation *o, SlapReply *rs )
801 smatch_info *sm = o->o_callback->sc_private;
803 if ( rs->sr_type != REP_SEARCH ) {
804 if ( rs->sr_err != LDAP_SUCCESS ) {
810 if ( sm->match == 1 ) {
815 if (dn_match(sm->dn, &rs->sr_entry->e_nname)) {
826 slap_sasl_matches( Operation *op, BerVarray rules,
827 struct berval *assertDN, struct berval *authc )
829 int rc = LDAP_INAPPROPRIATE_AUTH;
831 if ( rules != NULL ) {
834 for( i = 0; !BER_BVISNULL( &rules[i] ); i++ ) {
835 rc = slap_sasl_match( op, &rules[i], assertDN, authc );
836 if ( rc == LDAP_SUCCESS ) break;
844 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
845 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
846 * the rule must be used as an internal search for entries. If that search
847 * returns the *assertDN entry, the match is successful.
849 * The assertDN should not have the dn: prefix
853 int slap_sasl_match( Operation *opx, struct berval *rule,
854 struct berval *assertDN, struct berval *authc )
859 slap_callback cb = { NULL, sasl_sc_smatch, NULL, NULL };
861 SlapReply rs = {REP_RESULT};
862 struct berval base = BER_BVNULL;
868 Debug( LDAP_DEBUG_TRACE,
869 "===>slap_sasl_match: comparing DN %s to rule %s\n",
870 assertDN->bv_val, rule->bv_val, 0 );
872 rc = slap_parseURI( opx, rule, &base,
873 &op.o_req_ndn, &op.ors_scope, &op.ors_filter,
875 if( rc != LDAP_SUCCESS ) goto CONCLUDED;
877 switch ( op.ors_scope ) {
878 case LDAP_X_SCOPE_EXACT:
880 if ( dn_match( &op.o_req_ndn, assertDN ) ) {
883 rc = LDAP_INAPPROPRIATE_AUTH;
887 case LDAP_X_SCOPE_CHILDREN:
888 case LDAP_X_SCOPE_SUBTREE:
889 case LDAP_X_SCOPE_ONELEVEL:
891 int d = assertDN->bv_len - op.o_req_ndn.bv_len;
893 rc = LDAP_INAPPROPRIATE_AUTH;
895 if ( d == 0 && op.ors_scope == LDAP_X_SCOPE_SUBTREE ) {
898 } else if ( d > 0 ) {
901 /* leave room for at least one char of attributeType,
902 * one for '=' and one for ',' */
903 if ( d < STRLENOF( "x=,") ) {
907 bv.bv_len = op.o_req_ndn.bv_len;
908 bv.bv_val = assertDN->bv_val + d;
910 if ( bv.bv_val[ -1 ] == ',' && dn_match( &op.o_req_ndn, &bv ) ) {
911 switch ( op.ors_scope ) {
912 case LDAP_X_SCOPE_SUBTREE:
913 case LDAP_X_SCOPE_CHILDREN:
917 case LDAP_X_SCOPE_ONELEVEL:
921 dnParent( assertDN, &pdn );
922 /* the common portion of the DN
923 * already matches, so only check
924 * if parent DN of assertedDN
925 * is all the pattern */
926 if ( pdn.bv_len == op.o_req_ndn.bv_len ) {
932 /* at present, impossible */
940 case LDAP_X_SCOPE_REGEX:
941 rc = regcomp(®, op.o_req_ndn.bv_val,
942 REG_EXTENDED|REG_ICASE|REG_NOSUB);
944 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
950 rc = LDAP_INAPPROPRIATE_AUTH;
954 case LDAP_X_SCOPE_GROUP: {
957 /* Now filterstr looks like "(&(objectClass=<group_oc>)(<member_at>="
958 * we need to append the <assertDN> so that the <group_dn> is searched
959 * with scope "base", and the filter ensures that <assertDN> is
960 * member of the group */
961 tmp = ch_realloc( op.ors_filterstr.bv_val, op.ors_filterstr.bv_len +
962 assertDN->bv_len + STRLENOF( /*"(("*/ "))" ) + 1 );
967 op.ors_filterstr.bv_val = tmp;
969 tmp = lutil_strcopy( &tmp[op.ors_filterstr.bv_len], assertDN->bv_val );
970 tmp = lutil_strcopy( tmp, /*"(("*/ "))" );
972 /* pass opx because str2filter_x may (and does) use o_tmpmfuncs */
973 op.ors_filter = str2filter_x( opx, op.ors_filterstr.bv_val );
974 if ( op.ors_filter == NULL ) {
975 rc = LDAP_PROTOCOL_ERROR;
978 op.ors_scope = LDAP_SCOPE_BASE;
980 /* hijack match DN: use that of the group instead of the assertDN;
981 * assertDN is now in the filter */
982 sm.dn = &op.o_req_ndn;
988 case LDAP_X_SCOPE_USERS:
989 if ( !BER_BVISEMPTY( assertDN ) ) {
992 rc = LDAP_INAPPROPRIATE_AUTH;
1000 /* Must run an internal search. */
1001 if ( op.ors_filter == NULL ) {
1002 rc = LDAP_FILTER_ERROR;
1006 Debug( LDAP_DEBUG_TRACE,
1007 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
1008 op.o_req_ndn.bv_val, op.ors_scope, 0 );
1010 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
1011 if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
1012 rc = LDAP_INAPPROPRIATE_AUTH;
1016 op.o_hdr = opx->o_hdr;
1017 op.o_tag = LDAP_REQ_SEARCH;
1019 op.o_callback = &cb;
1020 op.o_time = slap_get_time();
1021 op.o_do_not_cache = 1;
1022 op.o_is_auth_check = 1;
1023 /* use req_ndn as req_dn instead of non-pretty base of uri */
1024 if( !BER_BVISNULL( &base ) ) {
1025 ch_free( base.bv_val );
1026 /* just in case... */
1027 BER_BVZERO( &base );
1029 ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1031 op.ors_tlimit = SLAP_NO_LIMIT;
1032 op.ors_attrs = slap_anlist_no_attrs;
1033 op.ors_attrsonly = 1;
1035 op.o_bd->be_search( &op, &rs );
1037 if (sm.match == 1) {
1040 rc = LDAP_INAPPROPRIATE_AUTH;
1044 if( !BER_BVISNULL( &op.o_req_dn ) ) slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1045 if( !BER_BVISNULL( &op.o_req_ndn ) ) slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1046 if( op.ors_filter ) filter_free_x( opx, op.ors_filter );
1047 if( !BER_BVISNULL( &op.ors_filterstr ) ) ch_free( op.ors_filterstr.bv_val );
1049 Debug( LDAP_DEBUG_TRACE,
1050 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
1057 * This function answers the question, "Can this ID authorize to that ID?",
1058 * based on authorization rules. The rules are stored in the *searchDN, in the
1059 * attribute named by *attr. If any of those rules map to the *assertDN, the
1060 * authorization is approved.
1062 * The DNs should not have the dn: prefix
1065 slap_sasl_check_authz( Operation *op,
1066 struct berval *searchDN,
1067 struct berval *assertDN,
1068 AttributeDescription *ad,
1069 struct berval *authc )
1072 BerVarray vals = NULL;
1074 Debug( LDAP_DEBUG_TRACE,
1075 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
1076 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
1078 rc = backend_attribute( op, NULL, searchDN, ad, &vals, ACL_AUTH );
1079 if( rc != LDAP_SUCCESS ) goto COMPLETE;
1081 /* Check if the *assertDN matches any *vals */
1082 rc = slap_sasl_matches( op, vals, assertDN, authc );
1085 if( vals ) ber_bvarray_free_x( vals, op->o_tmpmemctx );
1087 Debug( LDAP_DEBUG_TRACE,
1088 "<==slap_sasl_check_authz: %s check returning %d\n",
1089 ad->ad_cname.bv_val, rc, 0);
1095 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
1096 * return the LDAP DN to which it matches. The SASL regexp rules in the config
1097 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
1098 * search with scope=base), just return the URI (or its searchbase). Otherwise
1099 * an internal search must be done, and if that search returns exactly one
1100 * entry, return the DN of that one entry.
1102 void slap_sasl2dn( Operation *opx,
1103 struct berval *saslname, struct berval *sasldn, int flags )
1106 slap_callback cb = { NULL, sasl_sc_sasl2dn, NULL, NULL };
1108 SlapReply rs = {REP_RESULT};
1109 struct berval regout = BER_BVNULL;
1110 struct berval base = BER_BVNULL;
1112 Debug( LDAP_DEBUG_TRACE, "==>slap_sasl2dn: "
1113 "converting SASL name %s to a DN\n",
1114 saslname->bv_val, 0,0 );
1116 BER_BVZERO( sasldn );
1117 cb.sc_private = sasldn;
1119 /* Convert the SASL name into a minimal URI */
1120 if( !slap_authz_regexp( saslname, ®out, flags, opx->o_tmpmemctx ) ) {
1124 rc = slap_parseURI( opx, ®out, &base,
1125 &op.o_req_ndn, &op.ors_scope, &op.ors_filter,
1126 &op.ors_filterstr );
1127 if ( !BER_BVISNULL( ®out ) ) slap_sl_free( regout.bv_val, opx->o_tmpmemctx );
1128 if ( rc != LDAP_SUCCESS ) {
1132 /* Must do an internal search */
1133 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
1135 switch ( op.ors_scope ) {
1136 case LDAP_X_SCOPE_EXACT:
1137 *sasldn = op.o_req_ndn;
1138 BER_BVZERO( &op.o_req_ndn );
1139 /* intentionally continue to next case */
1141 case LDAP_X_SCOPE_REGEX:
1142 case LDAP_X_SCOPE_SUBTREE:
1143 case LDAP_X_SCOPE_CHILDREN:
1144 case LDAP_X_SCOPE_ONELEVEL:
1145 case LDAP_X_SCOPE_GROUP:
1146 case LDAP_X_SCOPE_USERS:
1147 /* correctly parsed, but illegal */
1150 case LDAP_SCOPE_BASE:
1151 case LDAP_SCOPE_ONELEVEL:
1152 case LDAP_SCOPE_SUBTREE:
1153 #ifdef LDAP_SCOPE_SUBORDINATE
1154 case LDAP_SCOPE_SUBORDINATE:
1160 /* catch unhandled cases (there shouldn't be) */
1164 Debug( LDAP_DEBUG_TRACE,
1165 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
1166 op.o_req_ndn.bv_val, op.ors_scope, 0 );
1168 if ( ( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL) ) {
1172 /* Must run an internal search. */
1173 if ( op.ors_filter == NULL ) {
1174 rc = LDAP_FILTER_ERROR;
1178 op.o_hdr = opx->o_hdr;
1179 op.o_tag = LDAP_REQ_SEARCH;
1180 op.o_ndn = opx->o_conn->c_ndn;
1181 op.o_callback = &cb;
1182 op.o_time = slap_get_time();
1183 op.o_do_not_cache = 1;
1184 op.o_is_auth_check = 1;
1185 op.ors_deref = LDAP_DEREF_NEVER;
1187 op.ors_tlimit = SLAP_NO_LIMIT;
1188 op.ors_attrs = slap_anlist_no_attrs;
1189 op.ors_attrsonly = 1;
1190 /* use req_ndn as req_dn instead of non-pretty base of uri */
1191 if( !BER_BVISNULL( &base ) ) {
1192 ch_free( base.bv_val );
1193 /* just in case... */
1194 BER_BVZERO( &base );
1196 ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1198 op.o_bd->be_search( &op, &rs );
1201 if( !BER_BVISEMPTY( sasldn ) ) {
1202 opx->o_conn->c_authz_backend = op.o_bd;
1204 if( !BER_BVISNULL( &op.o_req_dn ) ) {
1205 slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1207 if( !BER_BVISNULL( &op.o_req_ndn ) ) {
1208 slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1210 if( op.ors_filter ) {
1211 filter_free_x( opx, op.ors_filter );
1213 if( !BER_BVISNULL( &op.ors_filterstr ) ) {
1214 ch_free( op.ors_filterstr.bv_val );
1217 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
1218 !BER_BVISEMPTY( sasldn ) ? sasldn->bv_val : "<nothing>", 0, 0 );
1224 /* Check if a bind can SASL authorize to another identity.
1225 * The DNs should not have the dn: prefix
1228 int slap_sasl_authorized( Operation *op,
1229 struct berval *authcDN, struct berval *authzDN )
1231 int rc = LDAP_INAPPROPRIATE_AUTH;
1233 /* User binding as anonymous */
1234 if ( authzDN == NULL ) {
1239 Debug( LDAP_DEBUG_TRACE,
1240 "==>slap_sasl_authorized: can %s become %s?\n",
1241 authcDN->bv_val, authzDN->bv_val, 0 );
1243 /* If person is authorizing to self, succeed */
1244 if ( dn_match( authcDN, authzDN ) ) {
1249 /* Allow the manager to authorize as any DN. */
1250 if( op->o_conn->c_authz_backend &&
1251 be_isroot_dn( op->o_conn->c_authz_backend, authcDN ))
1257 /* Check source rules */
1258 if( authz_policy & SASL_AUTHZ_TO ) {
1259 rc = slap_sasl_check_authz( op, authcDN, authzDN,
1260 slap_schema.si_ad_saslAuthzTo, authcDN );
1261 if( rc == LDAP_SUCCESS && !(authz_policy & SASL_AUTHZ_AND) ) {
1266 /* Check destination rules */
1267 if( authz_policy & SASL_AUTHZ_FROM ) {
1268 rc = slap_sasl_check_authz( op, authzDN, authcDN,
1269 slap_schema.si_ad_saslAuthzFrom, authcDN );
1270 if( rc == LDAP_SUCCESS ) {
1275 rc = LDAP_INAPPROPRIATE_AUTH;
1279 Debug( LDAP_DEBUG_TRACE,
1280 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );