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 );
412 if ( rc == LDAP_URL_ERR_BADSCHEME ) {
414 * last chance: assume it's a(n exact) DN ...
416 * NOTE: must pass DN normalization
418 bv.bv_val = uri->bv_val;
419 *scope = LDAP_X_SCOPE_EXACT;
423 if ( rc != LDAP_URL_SUCCESS ) {
424 return LDAP_PROTOCOL_ERROR;
427 if (( ludp->lud_host && *ludp->lud_host )
428 || ludp->lud_attrs || ludp->lud_exts )
430 /* host part must be empty */
431 /* attrs and extensions parts must be empty */
432 rc = LDAP_PROTOCOL_ERROR;
437 *scope = ludp->lud_scope;
439 /* Grab the filter */
440 if ( ludp->lud_filter ) {
441 *filter = str2filter_x( op, ludp->lud_filter );
442 if ( *filter == NULL ) {
443 rc = LDAP_PROTOCOL_ERROR;
446 ber_str2bv( ludp->lud_filter, 0, 0, fstr );
449 /* Grab the searchbase */
450 ber_str2bv( ludp->lud_dn, 0, 0, base );
451 rc = dnNormalize( 0, NULL, NULL, base, nbase, op->o_tmpmemctx );
454 if( rc != LDAP_SUCCESS ) {
455 if( *filter ) filter_free_x( op, *filter );
459 /* Don't free these, return them to caller */
460 ludp->lud_filter = NULL;
464 ldap_free_urldesc( ludp );
468 static int slap_sasl_rx_off(char *rep, int *off)
473 /* Precompile replace pattern. Find the $<n> placeholders */
476 for ( c = rep; *c; c++ ) {
477 if ( *c == '\\' && c[1] ) {
482 if ( n == SASLREGEX_REPLACE ) {
483 Debug( LDAP_DEBUG_ANY,
484 "SASL replace pattern %s has too many $n "
485 "placeholders (max %d)\n",
486 rep, SASLREGEX_REPLACE, 0 );
488 return( LDAP_OTHER );
495 /* Final placeholder, after the last $n */
499 return( LDAP_SUCCESS );
502 #ifdef SLAP_AUTH_REWRITE
503 int slap_sasl_rewrite_config(
513 /* init at first call */
514 if ( sasl_rwinfo == NULL ) {
515 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
518 /* strip "authid-" prefix for parsing */
520 argv[0] += STRLENOF( "authid-" );
521 rc = rewrite_parse( sasl_rwinfo, fname, lineno, argc, argv );
527 int slap_sasl_rewrite_destroy( void )
530 rewrite_info_delete( &sasl_rwinfo );
537 int slap_sasl_regexp_rewrite_config(
542 const char *context )
545 char *argvRule[] = { "rewriteRule", NULL, NULL, ":@", NULL };
547 /* init at first call */
548 if ( sasl_rwinfo == NULL ) {
549 char *argvEngine[] = { "rewriteEngine", "on", NULL };
550 char *argvContext[] = { "rewriteContext", NULL, NULL };
552 /* initialize rewrite engine */
553 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
555 /* switch on rewrite engine */
556 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvEngine );
557 if (rc != LDAP_SUCCESS) {
561 /* create generic authid context */
562 argvContext[1] = AUTHID_CONTEXT;
563 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvContext );
564 if (rc != LDAP_SUCCESS) {
569 argvRule[1] = (char *)match;
570 argvRule[2] = (char *)replace;
571 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 4, argvRule );
575 #endif /* SLAP_AUTH_REWRITE */
577 int slap_sasl_regexp_config( const char *match, const char *replace )
579 #ifdef SLAP_AUTH_REWRITE
580 return slap_sasl_regexp_rewrite_config( "sasl-regexp", 0,
581 match, replace, AUTHID_CONTEXT );
582 #else /* ! SLAP_AUTH_REWRITE */
586 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
587 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
589 reg = &SaslRegexp[nSaslRegexp];
591 reg->sr_match = ch_strdup( match );
592 reg->sr_replace = ch_strdup( replace );
594 /* Precompile matching pattern */
595 rc = regcomp( ®->sr_workspace, reg->sr_match, REG_EXTENDED|REG_ICASE );
597 Debug( LDAP_DEBUG_ANY,
598 "SASL match pattern %s could not be compiled by regexp engine\n",
599 reg->sr_match, 0, 0 );
601 return( LDAP_OTHER );
604 rc = slap_sasl_rx_off( reg->sr_replace, reg->sr_offset );
605 if ( rc != LDAP_SUCCESS ) return rc;
608 return( LDAP_SUCCESS );
609 #endif /* ! SLAP_AUTH_REWRITE */
612 /* Perform replacement on regexp matches */
613 static void slap_sasl_rx_exp(
617 const char *saslname,
621 int i, n, len, insert;
623 /* Get the total length of the final URI */
627 while( off[n] >= 0 ) {
628 /* Len of next section from replacement string (x,y,z above) */
629 len += off[n] - off[n-1] - 2;
633 /* Len of string from saslname that matched next $i (b,d above) */
634 i = rep[ off[n] + 1 ] - '0';
635 len += str[i].rm_eo - str[i].rm_so;
638 out->bv_val = slap_sl_malloc( len + 1, ctx );
641 /* Fill in URI with replace string, replacing $i as we go */
644 while( off[n] >= 0) {
645 /* Paste in next section from replacement string (x,y,z above) */
646 len = off[n] - off[n-1] - 2;
647 strncpy( out->bv_val+insert, rep + off[n-1] + 2, len);
652 /* Paste in string from saslname that matched next $i (b,d above) */
653 i = rep[ off[n] + 1 ] - '0';
654 len = str[i].rm_eo - str[i].rm_so;
655 strncpy( out->bv_val+insert, saslname + str[i].rm_so, len );
661 out->bv_val[insert] = '\0';
664 /* Take the passed in SASL name and attempt to convert it into an
665 LDAP URI to find the matching LDAP entry, using the pattern matching
666 strings given in the saslregexp config file directive(s) */
668 static int slap_authz_regexp( struct berval *in, struct berval *out,
669 int flags, void *ctx )
671 #ifdef SLAP_AUTH_REWRITE
672 const char *context = AUTHID_CONTEXT;
674 if ( sasl_rwinfo == NULL || BER_BVISNULL( in ) ) {
678 /* FIXME: if aware of authc/authz mapping,
679 * we could use different contexts ... */
680 switch ( rewrite_session( sasl_rwinfo, context, in->bv_val, NULL,
683 case REWRITE_REGEXEC_OK:
684 if ( !BER_BVISNULL( out ) ) {
685 char *val = out->bv_val;
686 ber_str2bv_x( val, 0, 1, out, ctx );
689 ber_dupbv_x( out, in, ctx );
691 Debug( LDAP_DEBUG_ARGS,
692 "[rw] %s: \"%s\" -> \"%s\"\n",
693 context, in->bv_val, out->bv_val );
696 case REWRITE_REGEXEC_UNWILLING:
697 case REWRITE_REGEXEC_ERR:
702 #else /* ! SLAP_AUTH_REWRITE */
703 char *saslname = in->bv_val;
705 regmatch_t sr_strings[SASLREGEX_REPLACE]; /* strings matching $1,$2 ... */
708 memset( out, 0, sizeof( *out ) );
710 Debug( LDAP_DEBUG_TRACE, "slap_authz_regexp: converting SASL name %s\n",
713 if (( saslname == NULL ) || ( nSaslRegexp == 0 )) {
717 /* Match the normalized SASL name to the saslregexp patterns */
718 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
719 if ( regexec( ®->sr_workspace, saslname, SASLREGEX_REPLACE,
720 sr_strings, 0) == 0 )
724 if( i >= nSaslRegexp ) return( 0 );
727 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
728 * replace pattern of the form "x$1y$2z". The returned string needs
729 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
731 slap_sasl_rx_exp( reg->sr_replace, reg->sr_offset,
732 sr_strings, saslname, out, ctx );
734 Debug( LDAP_DEBUG_TRACE,
735 "slap_authz_regexp: converted SASL name to %s\n",
736 BER_BVISEMPTY( out ) ? "" : out->bv_val, 0, 0 );
739 #endif /* ! SLAP_AUTH_REWRITE */
742 /* This callback actually does some work...*/
743 static int sasl_sc_sasl2dn( Operation *o, SlapReply *rs )
745 struct berval *ndn = o->o_callback->sc_private;
747 if (rs->sr_type != REP_SEARCH) return 0;
749 /* We only want to be called once */
750 if ( !BER_BVISNULL( ndn ) ) {
751 o->o_tmpfree(ndn->bv_val, o->o_tmpmemctx);
754 Debug( LDAP_DEBUG_TRACE,
755 "slap_sc_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
759 ber_dupbv_x(ndn, &rs->sr_entry->e_nname, o->o_tmpmemctx);
764 typedef struct smatch_info {
769 static int sasl_sc_smatch( Operation *o, SlapReply *rs )
771 smatch_info *sm = o->o_callback->sc_private;
773 if ( rs->sr_type != REP_SEARCH ) {
774 if ( rs->sr_err != LDAP_SUCCESS ) {
780 if ( sm->match == 1 ) {
785 if (dn_match(sm->dn, &rs->sr_entry->e_nname)) {
796 slap_sasl_matches( Operation *op, BerVarray rules,
797 struct berval *assertDN, struct berval *authc )
799 int rc = LDAP_INAPPROPRIATE_AUTH;
801 if ( rules != NULL ) {
804 for( i = 0; !BER_BVISNULL( &rules[i] ); i++ ) {
805 rc = slap_sasl_match( op, &rules[i], assertDN, authc );
806 if ( rc == LDAP_SUCCESS ) break;
814 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
815 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
816 * the rule must be used as an internal search for entries. If that search
817 * returns the *assertDN entry, the match is successful.
819 * The assertDN should not have the dn: prefix
823 int slap_sasl_match( Operation *opx, struct berval *rule,
824 struct berval *assertDN, struct berval *authc )
829 slap_callback cb = { NULL, sasl_sc_smatch, NULL, NULL };
831 SlapReply rs = {REP_RESULT};
837 Debug( LDAP_DEBUG_TRACE,
838 "===>slap_sasl_match: comparing DN %s to rule %s\n",
839 assertDN->bv_val, rule->bv_val, 0 );
841 rc = slap_parseURI( opx, rule, &op.o_req_dn,
842 &op.o_req_ndn, &op.ors_scope, &op.ors_filter,
844 if( rc != LDAP_SUCCESS ) goto CONCLUDED;
846 switch ( op.ors_scope ) {
847 case LDAP_X_SCOPE_EXACT:
849 if ( dn_match( &op.o_req_ndn, assertDN ) ) {
852 rc = LDAP_INAPPROPRIATE_AUTH;
856 case LDAP_X_SCOPE_CHILDREN:
857 case LDAP_X_SCOPE_SUBTREE:
858 case LDAP_X_SCOPE_ONELEVEL:
860 int d = assertDN->bv_len - op.o_req_ndn.bv_len;
862 rc = LDAP_INAPPROPRIATE_AUTH;
864 if ( d == 0 && op.ors_scope == LDAP_X_SCOPE_SUBTREE ) {
867 } else if ( d > 0 ) {
870 /* leave room for at least one char of attributeType,
871 * one for '=' and one for ',' */
872 if ( d < STRLENOF( "x=,") ) {
876 bv.bv_len = op.o_req_ndn.bv_len;
877 bv.bv_val = assertDN->bv_val + d;
879 if ( bv.bv_val[ -1 ] == ',' && dn_match( &op.o_req_ndn, &bv ) ) {
880 switch ( op.ors_scope ) {
881 case LDAP_X_SCOPE_SUBTREE:
882 case LDAP_X_SCOPE_CHILDREN:
886 case LDAP_X_SCOPE_ONELEVEL:
890 dnParent( assertDN, &pdn );
891 /* the common portion of the DN
892 * already matches, so only check
893 * if parent DN of assertedDN
894 * is all the pattern */
895 if ( pdn.bv_len == op.o_req_ndn.bv_len ) {
901 /* at present, impossible */
909 case LDAP_X_SCOPE_REGEX:
910 rc = regcomp(®, op.o_req_ndn.bv_val,
911 REG_EXTENDED|REG_ICASE|REG_NOSUB);
913 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
919 rc = LDAP_INAPPROPRIATE_AUTH;
923 case LDAP_X_SCOPE_GROUP: {
926 /* Now filterstr looks like "(&(objectClass=<group_oc>)(<member_at>="
927 * we need to append the <assertDN> so that the <group_dn> is searched
928 * with scope "base", and the filter ensures that <assertDN> is
929 * member of the group */
930 tmp = ch_realloc( op.ors_filterstr.bv_val, op.ors_filterstr.bv_len +
931 assertDN->bv_len + STRLENOF( /*"(("*/ "))" ) + 1 );
936 op.ors_filterstr.bv_val = tmp;
938 tmp = lutil_strcopy( &tmp[op.ors_filterstr.bv_len], assertDN->bv_val );
939 tmp = lutil_strcopy( tmp, /*"(("*/ "))" );
941 /* pass opx because str2filter_x may (and does) use o_tmpmfuncs */
942 op.ors_filter = str2filter_x( opx, op.ors_filterstr.bv_val );
943 if ( op.ors_filter == NULL ) {
944 rc = LDAP_PROTOCOL_ERROR;
947 op.ors_scope = LDAP_SCOPE_BASE;
949 /* hijack match DN: use that of the group instead of the assertDN;
950 * assertDN is now in the filter */
951 sm.dn = &op.o_req_ndn;
957 case LDAP_X_SCOPE_USERS:
958 if ( !BER_BVISEMPTY( assertDN ) ) {
961 rc = LDAP_INAPPROPRIATE_AUTH;
969 /* Must run an internal search. */
970 if ( op.ors_filter == NULL ) {
971 rc = LDAP_FILTER_ERROR;
975 Debug( LDAP_DEBUG_TRACE,
976 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
977 op.o_req_ndn.bv_val, op.ors_scope, 0 );
979 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
980 if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
981 rc = LDAP_INAPPROPRIATE_AUTH;
985 op.o_tag = LDAP_REQ_SEARCH;
986 op.o_protocol = LDAP_VERSION3;
989 op.o_time = slap_get_time();
990 op.o_do_not_cache = 1;
991 op.o_is_auth_check = 1;
992 op.o_threadctx = opx->o_threadctx;
993 op.o_tmpmemctx = opx->o_tmpmemctx;
994 op.o_tmpmfuncs = opx->o_tmpmfuncs;
998 op.o_conn = opx->o_conn;
999 op.o_connid = opx->o_connid;
1000 /* use req_ndn as req_dn instead of non-pretty base of uri */
1001 if( !BER_BVISNULL( &op.o_req_dn ) ) ch_free( op.o_req_dn.bv_val );
1002 ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1004 op.ors_tlimit = SLAP_NO_LIMIT;
1005 op.ors_attrs = slap_anlist_no_attrs;
1006 op.ors_attrsonly = 1;
1007 op.o_sync_slog_size = -1;
1009 op.o_bd->be_search( &op, &rs );
1011 if (sm.match == 1) {
1014 rc = LDAP_INAPPROPRIATE_AUTH;
1018 if( !BER_BVISNULL( &op.o_req_dn ) ) slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1019 if( !BER_BVISNULL( &op.o_req_ndn ) ) slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1020 if( op.ors_filter ) filter_free_x( opx, op.ors_filter );
1021 if( !BER_BVISNULL( &op.ors_filterstr ) ) ch_free( op.ors_filterstr.bv_val );
1023 Debug( LDAP_DEBUG_TRACE,
1024 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
1031 * This function answers the question, "Can this ID authorize to that ID?",
1032 * based on authorization rules. The rules are stored in the *searchDN, in the
1033 * attribute named by *attr. If any of those rules map to the *assertDN, the
1034 * authorization is approved.
1036 * The DNs should not have the dn: prefix
1039 slap_sasl_check_authz( Operation *op,
1040 struct berval *searchDN,
1041 struct berval *assertDN,
1042 AttributeDescription *ad,
1043 struct berval *authc )
1046 BerVarray vals = NULL;
1048 Debug( LDAP_DEBUG_TRACE,
1049 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
1050 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
1052 rc = backend_attribute( op, NULL, searchDN, ad, &vals, ACL_AUTH );
1053 if( rc != LDAP_SUCCESS ) goto COMPLETE;
1055 /* Check if the *assertDN matches any *vals */
1056 rc = slap_sasl_matches( op, vals, assertDN, authc );
1059 if( vals ) ber_bvarray_free_x( vals, op->o_tmpmemctx );
1061 Debug( LDAP_DEBUG_TRACE,
1062 "<==slap_sasl_check_authz: %s check returning %d\n",
1063 ad->ad_cname.bv_val, rc, 0);
1069 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
1070 * return the LDAP DN to which it matches. The SASL regexp rules in the config
1071 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
1072 * search with scope=base), just return the URI (or its searchbase). Otherwise
1073 * an internal search must be done, and if that search returns exactly one
1074 * entry, return the DN of that one entry.
1076 void slap_sasl2dn( Operation *opx,
1077 struct berval *saslname, struct berval *sasldn, int flags )
1080 slap_callback cb = { NULL, sasl_sc_sasl2dn, NULL, NULL };
1082 SlapReply rs = {REP_RESULT};
1083 struct berval regout = BER_BVNULL;
1085 Debug( LDAP_DEBUG_TRACE, "==>slap_sasl2dn: "
1086 "converting SASL name %s to a DN\n",
1087 saslname->bv_val, 0,0 );
1089 sasldn->bv_val = NULL;
1091 cb.sc_private = sasldn;
1093 /* Convert the SASL name into a minimal URI */
1094 if( !slap_authz_regexp( saslname, ®out, flags, opx->o_tmpmemctx ) ) {
1098 rc = slap_parseURI( opx, ®out, &op.o_req_dn,
1099 &op.o_req_ndn, &op.ors_scope, &op.ors_filter,
1100 &op.ors_filterstr );
1101 if ( !BER_BVISNULL( ®out ) ) slap_sl_free( regout.bv_val, opx->o_tmpmemctx );
1102 if ( rc != LDAP_SUCCESS ) {
1106 /* Must do an internal search */
1107 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
1109 switch ( op.ors_scope ) {
1110 case LDAP_X_SCOPE_EXACT:
1111 *sasldn = op.o_req_ndn;
1112 BER_BVZERO( &op.o_req_ndn );
1113 /* intentionally continue to next case */
1115 case LDAP_X_SCOPE_REGEX:
1116 case LDAP_X_SCOPE_SUBTREE:
1117 case LDAP_X_SCOPE_CHILDREN:
1118 case LDAP_X_SCOPE_ONELEVEL:
1119 case LDAP_X_SCOPE_GROUP:
1120 case LDAP_X_SCOPE_USERS:
1121 /* correctly parsed, but illegal */
1124 case LDAP_SCOPE_BASE:
1125 case LDAP_SCOPE_ONELEVEL:
1126 case LDAP_SCOPE_SUBTREE:
1127 #ifdef LDAP_SCOPE_SUBORDINATE
1128 case LDAP_SCOPE_SUBORDINATE:
1134 /* catch unhandled cases (there shouldn't be) */
1138 Debug( LDAP_DEBUG_TRACE,
1139 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
1140 op.o_req_ndn.bv_val, op.ors_scope, 0 );
1142 if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
1146 op.o_conn = opx->o_conn;
1147 op.o_connid = opx->o_connid;
1148 op.o_tag = LDAP_REQ_SEARCH;
1149 op.o_protocol = LDAP_VERSION3;
1150 op.o_ndn = opx->o_conn->c_ndn;
1151 op.o_callback = &cb;
1152 op.o_time = slap_get_time();
1153 op.o_do_not_cache = 1;
1154 op.o_is_auth_check = 1;
1155 op.o_threadctx = opx->o_threadctx;
1156 op.o_tmpmemctx = opx->o_tmpmemctx;
1157 op.o_tmpmfuncs = opx->o_tmpmfuncs;
1159 op.o_pb = opx->o_pb;
1161 op.ors_deref = LDAP_DEREF_NEVER;
1163 op.ors_tlimit = SLAP_NO_LIMIT;
1164 op.ors_attrs = slap_anlist_no_attrs;
1165 op.ors_attrsonly = 1;
1166 op.o_sync_slog_size = -1;
1167 /* use req_ndn as req_dn instead of non-pretty base of uri */
1168 if( !BER_BVISNULL( &op.o_req_dn ) ) ch_free( op.o_req_dn.bv_val );
1169 ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1171 op.o_bd->be_search( &op, &rs );
1174 if( !BER_BVISEMPTY( sasldn ) ) {
1175 opx->o_conn->c_authz_backend = op.o_bd;
1177 if( !BER_BVISNULL( &op.o_req_dn ) ) {
1178 slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1180 if( !BER_BVISNULL( &op.o_req_ndn ) ) {
1181 slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1183 if( op.ors_filter ) {
1184 filter_free_x( opx, op.ors_filter );
1186 if( !BER_BVISNULL( &op.ors_filterstr ) ) {
1187 ch_free( op.ors_filterstr.bv_val );
1190 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
1191 !BER_BVISEMPTY( sasldn ) ? sasldn->bv_val : "<nothing>", 0, 0 );
1197 /* Check if a bind can SASL authorize to another identity.
1198 * The DNs should not have the dn: prefix
1201 int slap_sasl_authorized( Operation *op,
1202 struct berval *authcDN, struct berval *authzDN )
1204 int rc = LDAP_INAPPROPRIATE_AUTH;
1206 /* User binding as anonymous */
1207 if ( authzDN == NULL ) {
1212 Debug( LDAP_DEBUG_TRACE,
1213 "==>slap_sasl_authorized: can %s become %s?\n",
1214 authcDN->bv_val, authzDN->bv_val, 0 );
1216 /* If person is authorizing to self, succeed */
1217 if ( dn_match( authcDN, authzDN ) ) {
1222 /* Allow the manager to authorize as any DN. */
1223 if( op->o_conn->c_authz_backend &&
1224 be_isroot_dn( op->o_conn->c_authz_backend, authcDN ))
1230 /* Check source rules */
1231 if( authz_policy & SASL_AUTHZ_TO ) {
1232 rc = slap_sasl_check_authz( op, authcDN, authzDN,
1233 slap_schema.si_ad_saslAuthzTo, authcDN );
1234 if( rc == LDAP_SUCCESS && !(authz_policy & SASL_AUTHZ_AND) ) {
1239 /* Check destination rules */
1240 if( authz_policy & SASL_AUTHZ_FROM ) {
1241 rc = slap_sasl_check_authz( op, authzDN, authcDN,
1242 slap_schema.si_ad_saslAuthzFrom, authcDN );
1243 if( rc == LDAP_SUCCESS ) {
1248 rc = LDAP_INAPPROPRIATE_AUTH;
1252 Debug( LDAP_DEBUG_TRACE,
1253 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );