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>.
24 #include <ac/stdlib.h>
25 #include <ac/string.h>
32 #define SASLREGEX_REPLACE 10
34 #define LDAP_X_SCOPE_EXACT ((ber_int_t) 0x0010)
35 #define LDAP_X_SCOPE_REGEX ((ber_int_t) 0x0020)
36 #define LDAP_X_SCOPE_CHILDREN ((ber_int_t) 0x0030)
37 #define LDAP_X_SCOPE_SUBTREE ((ber_int_t) 0x0040)
38 #define LDAP_X_SCOPE_ONELEVEL ((ber_int_t) 0x0050)
39 #define LDAP_X_SCOPE_GROUP ((ber_int_t) 0x0060)
40 #define LDAP_X_SCOPE_USERS ((ber_int_t) 0x0070)
43 * IDs in DNauthzid form can now have a type specifier, that
44 * influences how they are used in related operations.
46 * syntax: dn[.{exact|regex}]:<val>
48 * dn.exact: the value must pass normalization and is used
50 * dn.regex: the value is treated as a regular expression
51 * in matching DN values in authz{To|From}
53 * dn: for backwards compatibility reasons, the value
54 * is treated as a regular expression, and thus
55 * it is not normalized nor validated; it is used
56 * in exact or regex comparisons based on the
59 * IDs in DNauthzid form can now have a type specifier, that
60 * influences how they are used in related operations.
62 * syntax: u[.mech[/realm]]:<val>
64 * where mech is a SIMPLE, AUTHZ, or a SASL mechanism name
65 * and realm is mechanism specific realm (separate to those
66 * which are representable as part of the principal).
69 typedef struct sasl_regexp {
70 char *sr_match; /* regexp match pattern */
71 char *sr_replace; /* regexp replace pattern */
72 regex_t sr_workspace; /* workspace for regexp engine */
73 int sr_offset[SASLREGEX_REPLACE+2]; /* offsets of $1,$2... in *replace */
76 static int nSaslRegexp = 0;
77 static SaslRegexp_t *SaslRegexp = NULL;
79 #ifdef SLAP_AUTH_REWRITE
81 struct rewrite_info *sasl_rwinfo = NULL;
82 #define AUTHID_CONTEXT "authid"
83 #endif /* SLAP_AUTH_REWRITE */
85 /* What SASL proxy authorization policies are allowed? */
86 #define SASL_AUTHZ_NONE 0x00
87 #define SASL_AUTHZ_FROM 0x01
88 #define SASL_AUTHZ_TO 0x02
89 #define SASL_AUTHZ_AND 0x10
91 static const char *policy_txt[] = {
92 "none", "from", "to", "any"
95 static int authz_policy = SASL_AUTHZ_NONE;
98 slap_sasl_match( Operation *opx, struct berval *rule,
99 struct berval *assertDN, struct berval *authc );
101 int slap_sasl_setpolicy( const char *arg )
103 int rc = LDAP_SUCCESS;
105 if ( strcasecmp( arg, "none" ) == 0 ) {
106 authz_policy = SASL_AUTHZ_NONE;
107 } else if ( strcasecmp( arg, "from" ) == 0 ) {
108 authz_policy = SASL_AUTHZ_FROM;
109 } else if ( strcasecmp( arg, "to" ) == 0 ) {
110 authz_policy = SASL_AUTHZ_TO;
111 } else if ( strcasecmp( arg, "both" ) == 0 || strcasecmp( arg, "any" ) == 0 ) {
112 authz_policy = SASL_AUTHZ_FROM | SASL_AUTHZ_TO;
113 } else if ( strcasecmp( arg, "all" ) == 0 ) {
114 authz_policy = SASL_AUTHZ_FROM | SASL_AUTHZ_TO | SASL_AUTHZ_AND;
121 const char * slap_sasl_getpolicy()
123 if ( authz_policy == (SASL_AUTHZ_FROM | SASL_AUTHZ_TO | SASL_AUTHZ_AND) )
126 return policy_txt[authz_policy];
129 int slap_parse_user( struct berval *id, struct berval *user,
130 struct berval *realm, struct berval *mech )
134 assert( id != NULL );
135 assert( !BER_BVISNULL( id ) );
136 assert( user != NULL );
137 assert( realm != NULL );
138 assert( mech != NULL );
142 if ( u != 'u' && u != 'U' ) {
143 /* called with something other than u: */
144 return LDAP_PROTOCOL_ERROR;
148 * u[.mech[/realm]]:user
151 user->bv_val = strchr( id->bv_val, ':' );
152 if ( BER_BVISNULL( user ) ) {
153 return LDAP_PROTOCOL_ERROR;
155 user->bv_val[ 0 ] = '\0';
157 user->bv_len = id->bv_len - ( user->bv_val - id->bv_val );
159 mech->bv_val = strchr( id->bv_val, '.' );
160 if ( !BER_BVISNULL( mech ) ) {
161 mech->bv_val[ 0 ] = '\0';
164 realm->bv_val = strchr( mech->bv_val, '/' );
166 if ( !BER_BVISNULL( realm ) ) {
167 realm->bv_val[ 0 ] = '\0';
169 mech->bv_len = realm->bv_val - mech->bv_val - 1;
170 realm->bv_len = user->bv_val - realm->bv_val - 1;
172 mech->bv_len = user->bv_val - mech->bv_val - 1;
179 if ( id->bv_val[ 1 ] != '\0' ) {
180 return LDAP_PROTOCOL_ERROR;
183 if ( !BER_BVISNULL( mech ) ) {
184 assert( mech->bv_val == id->bv_val + 2 );
186 AC_MEMCPY( mech->bv_val - 2, mech->bv_val, mech->bv_len + 1 );
190 if ( !BER_BVISNULL( realm ) ) {
191 assert( realm->bv_val >= id->bv_val + 2 );
193 AC_MEMCPY( realm->bv_val - 2, realm->bv_val, realm->bv_len + 1 );
197 /* leave "u:" before user */
200 user->bv_val[ 0 ] = u;
201 user->bv_val[ 1 ] = ':';
206 static int slap_parseURI( Operation *op, struct berval *uri,
207 struct berval *base, struct berval *nbase,
208 int *scope, Filter **filter, struct berval *fstr )
214 assert( uri != NULL && !BER_BVISNULL( uri ) );
221 Debug( LDAP_DEBUG_TRACE,
222 "slap_parseURI: parsing %s\n", uri->bv_val, 0, 0 );
224 rc = LDAP_PROTOCOL_ERROR;
227 * dn[.<dnstyle>]:<dnpattern>
228 * <dnstyle> ::= {exact|regex|children|subtree|onelevel}
230 * <dnstyle> defaults to "exact"
231 * if <dnstyle> is not "regex", <dnpattern> must pass DN normalization
233 if ( !strncasecmp( uri->bv_val, "dn", STRLENOF( "dn" ) ) ) {
234 bv.bv_val = uri->bv_val + STRLENOF( "dn" );
236 if ( bv.bv_val[ 0 ] == '.' ) {
239 if ( !strncasecmp( bv.bv_val, "exact:", STRLENOF( "exact:" ) ) ) {
240 bv.bv_val += STRLENOF( "exact:" );
241 *scope = LDAP_X_SCOPE_EXACT;
243 } else if ( !strncasecmp( bv.bv_val, "regex:", STRLENOF( "regex:" ) ) ) {
244 bv.bv_val += STRLENOF( "regex:" );
245 *scope = LDAP_X_SCOPE_REGEX;
247 } else if ( !strncasecmp( bv.bv_val, "children:", STRLENOF( "children:" ) ) ) {
248 bv.bv_val += STRLENOF( "children:" );
249 *scope = LDAP_X_SCOPE_CHILDREN;
251 } else if ( !strncasecmp( bv.bv_val, "subtree:", STRLENOF( "subtree:" ) ) ) {
252 bv.bv_val += STRLENOF( "subtree:" );
253 *scope = LDAP_X_SCOPE_SUBTREE;
255 } else if ( !strncasecmp( bv.bv_val, "onelevel:", STRLENOF( "onelevel:" ) ) ) {
256 bv.bv_val += STRLENOF( "onelevel:" );
257 *scope = LDAP_X_SCOPE_ONELEVEL;
260 return LDAP_PROTOCOL_ERROR;
264 if ( bv.bv_val[ 0 ] != ':' ) {
265 return LDAP_PROTOCOL_ERROR;
267 *scope = LDAP_X_SCOPE_EXACT;
271 bv.bv_val += strspn( bv.bv_val, " " );
272 /* jump here in case no type specification was present
273 * and uri was not an URI... HEADS-UP: assuming EXACT */
274 is_dn: bv.bv_len = uri->bv_len - (bv.bv_val - uri->bv_val);
276 /* a single '*' means any DN without using regexes */
277 if ( ber_bvccmp( &bv, '*' ) ) {
278 *scope = LDAP_X_SCOPE_USERS;
282 case LDAP_X_SCOPE_EXACT:
283 case LDAP_X_SCOPE_CHILDREN:
284 case LDAP_X_SCOPE_SUBTREE:
285 case LDAP_X_SCOPE_ONELEVEL:
286 rc = dnNormalize( 0, NULL, NULL, &bv, nbase, op->o_tmpmemctx );
287 if( rc != LDAP_SUCCESS ) {
292 case LDAP_X_SCOPE_REGEX:
293 ber_dupbv_x( nbase, &bv, op->o_tmpmemctx );
295 case LDAP_X_SCOPE_USERS:
309 } else if ( ( uri->bv_val[ 0 ] == 'u' || uri->bv_val[ 0 ] == 'U' )
310 && ( uri->bv_val[ 1 ] == ':'
311 || uri->bv_val[ 1 ] == '/'
312 || uri->bv_val[ 1 ] == '.' ) )
314 Connection c = *op->o_conn;
315 char buf[ SLAP_LDAPDN_MAXLEN ];
321 if ( sizeof( buf ) <= uri->bv_len ) {
322 return LDAP_INVALID_SYNTAX;
325 id.bv_len = uri->bv_len;
327 strncpy( buf, uri->bv_val, sizeof( buf ) );
329 rc = slap_parse_user( &id, &user, &realm, &mech );
330 if ( rc != LDAP_SUCCESS ) {
334 if ( !BER_BVISNULL( &mech ) ) {
335 c.c_sasl_bind_mech = mech;
337 BER_BVSTR( &c.c_sasl_bind_mech, "AUTHZ" );
340 rc = slap_sasl_getdn( &c, op, &user,
341 realm.bv_val, nbase, SLAP_GETDN_AUTHZID );
343 if ( rc == LDAP_SUCCESS ) {
344 *scope = LDAP_X_SCOPE_EXACT;
350 * group[/<groupoc>[/<groupat>]]:<groupdn>
352 * groupoc defaults to "groupOfNames"
353 * groupat defaults to "member"
355 * <groupdn> must pass DN normalization
357 } else if ( strncasecmp( uri->bv_val, "group", STRLENOF( "group" ) ) == 0 )
359 struct berval group_dn = BER_BVNULL,
360 group_oc = BER_BVNULL,
361 member_at = BER_BVNULL;
364 bv.bv_val = uri->bv_val + STRLENOF( "group" );
365 group_dn.bv_val = strchr( bv.bv_val, ':' );
366 if ( group_dn.bv_val == NULL ) {
367 /* last chance: assume it's a(n exact) DN ... */
368 bv.bv_val = uri->bv_val;
369 *scope = LDAP_X_SCOPE_EXACT;
373 if ( bv.bv_val[ 0 ] == '/' ) {
374 group_oc.bv_val = &bv.bv_val[ 1 ];
376 member_at.bv_val = strchr( group_oc.bv_val, '/' );
377 if ( member_at.bv_val ) {
378 group_oc.bv_len = member_at.bv_val - group_oc.bv_val;
380 member_at.bv_len = group_dn.bv_val - member_at.bv_val;
383 group_oc.bv_len = group_dn.bv_val - group_oc.bv_val;
384 BER_BVSTR( &member_at, SLAPD_GROUP_ATTR );
388 BER_BVSTR( &group_oc, SLAPD_GROUP_CLASS );
391 group_dn.bv_len = uri->bv_len - ( group_dn.bv_val - uri->bv_val );
393 rc = dnNormalize( 0, NULL, NULL, &group_dn, nbase, op->o_tmpmemctx );
394 if ( rc != LDAP_SUCCESS ) {
398 *scope = LDAP_X_SCOPE_GROUP;
400 /* FIXME: caller needs to add value of member attribute
401 * and close brackets twice */
402 fstr->bv_len = STRLENOF( "(&(objectClass=)(=" /* )) */ )
403 + group_oc.bv_len + member_at.bv_len;
404 fstr->bv_val = ch_malloc( fstr->bv_len + 1 );
406 tmp = lutil_strncopy( fstr->bv_val, "(&(objectClass=" /* )) */ ,
407 STRLENOF( "(&(objectClass=" /* )) */ ) );
408 tmp = lutil_strncopy( tmp, group_oc.bv_val, group_oc.bv_len );
409 tmp = lutil_strncopy( tmp, /* ( */ ")(" /* ) */ ,
410 STRLENOF( /* ( */ ")(" /* ) */ ) );
411 tmp = lutil_strncopy( tmp, member_at.bv_val, member_at.bv_len );
412 tmp = lutil_strncopy( tmp, "=", STRLENOF( "=" ) );
418 * ldap:///<base>??<scope>?<filter>
419 * <scope> ::= {base|one|subtree}
421 * <scope> defaults to "base"
422 * <base> must pass DN normalization
423 * <filter> must pass str2filter()
425 rc = ldap_url_parse( uri->bv_val, &ludp );
427 case LDAP_URL_SUCCESS:
428 /* FIXME: the check is pedantic, but I think it's necessary,
429 * because people tend to use things like ldaps:// which
430 * gives the idea SSL is being used. Maybe we could
431 * accept ldapi:// as well, but the point is that we use
432 * an URL as an easy means to define bits of a search with
435 if ( strcasecmp( ludp->lud_scheme, "ldap" ) != 0 ) {
439 rc = LDAP_PROTOCOL_ERROR;
444 case LDAP_URL_ERR_BADSCHEME:
446 * last chance: assume it's a(n exact) DN ...
448 * NOTE: must pass DN normalization
450 ldap_free_urldesc( ludp );
451 bv.bv_val = uri->bv_val;
452 *scope = LDAP_X_SCOPE_EXACT;
456 rc = LDAP_PROTOCOL_ERROR;
460 if ( ( ludp->lud_host && *ludp->lud_host )
461 || ludp->lud_attrs || ludp->lud_exts )
463 /* host part must be empty */
464 /* attrs and extensions parts must be empty */
465 rc = LDAP_PROTOCOL_ERROR;
470 *scope = ludp->lud_scope;
472 /* Grab the filter */
473 if ( ludp->lud_filter ) {
474 *filter = str2filter_x( op, ludp->lud_filter );
475 if ( *filter == NULL ) {
476 rc = LDAP_PROTOCOL_ERROR;
479 ber_str2bv( ludp->lud_filter, 0, 0, fstr );
482 /* Grab the searchbase */
483 ber_str2bv( ludp->lud_dn, 0, 0, base );
484 rc = dnNormalize( 0, NULL, NULL, base, nbase, op->o_tmpmemctx );
487 if( rc != LDAP_SUCCESS ) {
488 if( *filter ) filter_free_x( op, *filter );
492 /* Don't free these, return them to caller */
493 ludp->lud_filter = NULL;
497 ldap_free_urldesc( ludp );
501 #ifndef SLAP_AUTH_REWRITE
502 static int slap_sasl_rx_off(char *rep, int *off)
507 /* Precompile replace pattern. Find the $<n> placeholders */
510 for ( c = rep; *c; c++ ) {
511 if ( *c == '\\' && c[1] ) {
516 if ( n == SASLREGEX_REPLACE ) {
517 Debug( LDAP_DEBUG_ANY,
518 "SASL replace pattern %s has too many $n "
519 "placeholders (max %d)\n",
520 rep, SASLREGEX_REPLACE, 0 );
522 return( LDAP_OTHER );
529 /* Final placeholder, after the last $n */
533 return( LDAP_SUCCESS );
535 #endif /* ! SLAP_AUTH_REWRITE */
537 #ifdef SLAP_AUTH_REWRITE
538 int slap_sasl_rewrite_config(
548 /* init at first call */
549 if ( sasl_rwinfo == NULL ) {
550 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
553 /* strip "authid-" prefix for parsing */
555 argv[0] += STRLENOF( "authid-" );
556 rc = rewrite_parse( sasl_rwinfo, fname, lineno, argc, argv );
562 int slap_sasl_rewrite_destroy( void )
565 rewrite_info_delete( &sasl_rwinfo );
572 int slap_sasl_regexp_rewrite_config(
577 const char *context )
580 char *argvRule[] = { "rewriteRule", NULL, NULL, ":@", NULL };
582 /* init at first call */
583 if ( sasl_rwinfo == NULL ) {
584 char *argvEngine[] = { "rewriteEngine", "on", NULL };
585 char *argvContext[] = { "rewriteContext", NULL, NULL };
587 /* initialize rewrite engine */
588 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
590 /* switch on rewrite engine */
591 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvEngine );
592 if (rc != LDAP_SUCCESS) {
596 /* create generic authid context */
597 argvContext[1] = AUTHID_CONTEXT;
598 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvContext );
599 if (rc != LDAP_SUCCESS) {
604 argvRule[1] = (char *)match;
605 argvRule[2] = (char *)replace;
606 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 4, argvRule );
610 #endif /* SLAP_AUTH_REWRITE */
612 int slap_sasl_regexp_config( const char *match, const char *replace )
617 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
618 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
620 reg = &SaslRegexp[nSaslRegexp];
622 reg->sr_match = ch_strdup( match );
623 reg->sr_replace = ch_strdup( replace );
625 #ifdef SLAP_AUTH_REWRITE
626 rc = slap_sasl_regexp_rewrite_config( "sasl-regexp", 0,
627 match, replace, AUTHID_CONTEXT );
628 if ( rc == LDAP_SUCCESS ) nSaslRegexp++;
630 #else /* ! SLAP_AUTH_REWRITE */
632 /* Precompile matching pattern */
633 rc = regcomp( ®->sr_workspace, reg->sr_match, REG_EXTENDED|REG_ICASE );
635 Debug( LDAP_DEBUG_ANY,
636 "SASL match pattern %s could not be compiled by regexp engine\n",
637 reg->sr_match, 0, 0 );
639 return( LDAP_OTHER );
642 rc = slap_sasl_rx_off( reg->sr_replace, reg->sr_offset );
643 if ( rc != LDAP_SUCCESS ) return rc;
646 return( LDAP_SUCCESS );
647 #endif /* ! SLAP_AUTH_REWRITE */
650 void slap_sasl_regexp_unparse( BerVarray *out )
653 BerVarray bva = NULL;
657 if ( !nSaslRegexp ) return;
660 bva = ch_malloc( (nSaslRegexp+1) * sizeof(struct berval) );
661 BER_BVZERO(bva+nSaslRegexp);
662 for ( i=0; i<nSaslRegexp; i++ ) {
663 idx.bv_len = sprintf( idx.bv_val, "{%d}", i);
664 bva[i].bv_len = idx.bv_len + strlen( SaslRegexp[i].sr_match ) +
665 strlen( SaslRegexp[i].sr_replace ) + 5;
666 bva[i].bv_val = ch_malloc( bva[i].bv_len+1 );
667 ptr = lutil_strcopy( bva[i].bv_val, ibuf );
669 ptr = lutil_strcopy( ptr, SaslRegexp[i].sr_match );
670 ptr = lutil_strcopy( ptr, "\" \"" );
671 ptr = lutil_strcopy( ptr, SaslRegexp[i].sr_replace );
678 #ifndef SLAP_AUTH_REWRITE
679 /* Perform replacement on regexp matches */
680 static void slap_sasl_rx_exp(
684 const char *saslname,
688 int i, n, len, insert;
690 /* Get the total length of the final URI */
694 while( off[n] >= 0 ) {
695 /* Len of next section from replacement string (x,y,z above) */
696 len += off[n] - off[n-1] - 2;
700 /* Len of string from saslname that matched next $i (b,d above) */
701 i = rep[ off[n] + 1 ] - '0';
702 len += str[i].rm_eo - str[i].rm_so;
705 out->bv_val = slap_sl_malloc( len + 1, ctx );
708 /* Fill in URI with replace string, replacing $i as we go */
711 while( off[n] >= 0) {
712 /* Paste in next section from replacement string (x,y,z above) */
713 len = off[n] - off[n-1] - 2;
714 strncpy( out->bv_val+insert, rep + off[n-1] + 2, len);
719 /* Paste in string from saslname that matched next $i (b,d above) */
720 i = rep[ off[n] + 1 ] - '0';
721 len = str[i].rm_eo - str[i].rm_so;
722 strncpy( out->bv_val+insert, saslname + str[i].rm_so, len );
728 out->bv_val[insert] = '\0';
730 #endif /* ! SLAP_AUTH_REWRITE */
732 /* Take the passed in SASL name and attempt to convert it into an
733 LDAP URI to find the matching LDAP entry, using the pattern matching
734 strings given in the saslregexp config file directive(s) */
736 static int slap_authz_regexp( struct berval *in, struct berval *out,
737 int flags, void *ctx )
739 #ifdef SLAP_AUTH_REWRITE
740 const char *context = AUTHID_CONTEXT;
742 if ( sasl_rwinfo == NULL || BER_BVISNULL( in ) ) {
746 /* FIXME: if aware of authc/authz mapping,
747 * we could use different contexts ... */
748 switch ( rewrite_session( sasl_rwinfo, context, in->bv_val, NULL,
751 case REWRITE_REGEXEC_OK:
752 if ( !BER_BVISNULL( out ) ) {
753 char *val = out->bv_val;
754 ber_str2bv_x( val, 0, 1, out, ctx );
755 if ( val != in->bv_val ) {
759 ber_dupbv_x( out, in, ctx );
761 Debug( LDAP_DEBUG_ARGS,
762 "[rw] %s: \"%s\" -> \"%s\"\n",
763 context, in->bv_val, out->bv_val );
766 case REWRITE_REGEXEC_UNWILLING:
767 case REWRITE_REGEXEC_ERR:
772 #else /* ! SLAP_AUTH_REWRITE */
773 char *saslname = in->bv_val;
775 regmatch_t sr_strings[SASLREGEX_REPLACE]; /* strings matching $1,$2 ... */
778 memset( out, 0, sizeof( *out ) );
780 Debug( LDAP_DEBUG_TRACE, "slap_authz_regexp: converting SASL name %s\n",
783 if (( saslname == NULL ) || ( nSaslRegexp == 0 )) {
787 /* Match the normalized SASL name to the saslregexp patterns */
788 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
789 if ( regexec( ®->sr_workspace, saslname, SASLREGEX_REPLACE,
790 sr_strings, 0) == 0 )
794 if( i >= nSaslRegexp ) return( 0 );
797 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
798 * replace pattern of the form "x$1y$2z". The returned string needs
799 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
801 slap_sasl_rx_exp( reg->sr_replace, reg->sr_offset,
802 sr_strings, saslname, out, ctx );
804 Debug( LDAP_DEBUG_TRACE,
805 "slap_authz_regexp: converted SASL name to %s\n",
806 BER_BVISEMPTY( out ) ? "" : out->bv_val, 0, 0 );
809 #endif /* ! SLAP_AUTH_REWRITE */
812 /* This callback actually does some work...*/
813 static int sasl_sc_sasl2dn( Operation *o, SlapReply *rs )
815 struct berval *ndn = o->o_callback->sc_private;
817 if (rs->sr_type != REP_SEARCH) return 0;
819 /* We only want to be called once */
820 if ( !BER_BVISNULL( ndn ) ) {
821 o->o_tmpfree(ndn->bv_val, o->o_tmpmemctx);
824 Debug( LDAP_DEBUG_TRACE,
825 "slap_sc_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
829 ber_dupbv_x(ndn, &rs->sr_entry->e_nname, o->o_tmpmemctx);
834 typedef struct smatch_info {
839 static int sasl_sc_smatch( Operation *o, SlapReply *rs )
841 smatch_info *sm = o->o_callback->sc_private;
843 if ( rs->sr_type != REP_SEARCH ) {
844 if ( rs->sr_err != LDAP_SUCCESS ) {
850 if ( sm->match == 1 ) {
855 if (dn_match(sm->dn, &rs->sr_entry->e_nname)) {
866 slap_sasl_matches( Operation *op, BerVarray rules,
867 struct berval *assertDN, struct berval *authc )
869 int rc = LDAP_INAPPROPRIATE_AUTH;
871 if ( rules != NULL ) {
874 for( i = 0; !BER_BVISNULL( &rules[i] ); i++ ) {
875 rc = slap_sasl_match( op, &rules[i], assertDN, authc );
876 if ( rc == LDAP_SUCCESS ) break;
884 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
885 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
886 * the rule must be used as an internal search for entries. If that search
887 * returns the *assertDN entry, the match is successful.
889 * The assertDN should not have the dn: prefix
893 slap_sasl_match( Operation *opx, struct berval *rule,
894 struct berval *assertDN, struct berval *authc )
899 slap_callback cb = { NULL, sasl_sc_smatch, NULL, NULL };
901 SlapReply rs = {REP_RESULT};
902 struct berval base = BER_BVNULL;
908 Debug( LDAP_DEBUG_TRACE,
909 "===>slap_sasl_match: comparing DN %s to rule %s\n",
910 assertDN->bv_val, rule->bv_val, 0 );
912 rc = slap_parseURI( opx, rule, &base,
913 &op.o_req_ndn, &op.ors_scope, &op.ors_filter,
915 if( rc != LDAP_SUCCESS ) goto CONCLUDED;
917 switch ( op.ors_scope ) {
918 case LDAP_X_SCOPE_EXACT:
920 if ( dn_match( &op.o_req_ndn, assertDN ) ) {
923 rc = LDAP_INAPPROPRIATE_AUTH;
927 case LDAP_X_SCOPE_CHILDREN:
928 case LDAP_X_SCOPE_SUBTREE:
929 case LDAP_X_SCOPE_ONELEVEL:
931 int d = assertDN->bv_len - op.o_req_ndn.bv_len;
933 rc = LDAP_INAPPROPRIATE_AUTH;
935 if ( d == 0 && op.ors_scope == LDAP_X_SCOPE_SUBTREE ) {
938 } else if ( d > 0 ) {
941 /* leave room for at least one char of attributeType,
942 * one for '=' and one for ',' */
943 if ( d < STRLENOF( "x=,") ) {
947 bv.bv_len = op.o_req_ndn.bv_len;
948 bv.bv_val = assertDN->bv_val + d;
950 if ( bv.bv_val[ -1 ] == ',' && dn_match( &op.o_req_ndn, &bv ) ) {
951 switch ( op.ors_scope ) {
952 case LDAP_X_SCOPE_SUBTREE:
953 case LDAP_X_SCOPE_CHILDREN:
957 case LDAP_X_SCOPE_ONELEVEL:
961 dnParent( assertDN, &pdn );
962 /* the common portion of the DN
963 * already matches, so only check
964 * if parent DN of assertedDN
965 * is all the pattern */
966 if ( pdn.bv_len == op.o_req_ndn.bv_len ) {
972 /* at present, impossible */
980 case LDAP_X_SCOPE_REGEX:
981 rc = regcomp(®, op.o_req_ndn.bv_val,
982 REG_EXTENDED|REG_ICASE|REG_NOSUB);
984 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
990 rc = LDAP_INAPPROPRIATE_AUTH;
994 case LDAP_X_SCOPE_GROUP: {
997 /* Now filterstr looks like "(&(objectClass=<group_oc>)(<member_at>="
998 * we need to append the <assertDN> so that the <group_dn> is searched
999 * with scope "base", and the filter ensures that <assertDN> is
1000 * member of the group */
1001 tmp = ch_realloc( op.ors_filterstr.bv_val, op.ors_filterstr.bv_len +
1002 assertDN->bv_len + STRLENOF( /*"(("*/ "))" ) + 1 );
1003 if ( tmp == NULL ) {
1004 rc = LDAP_NO_MEMORY;
1007 op.ors_filterstr.bv_val = tmp;
1009 tmp = lutil_strcopy( &tmp[op.ors_filterstr.bv_len], assertDN->bv_val );
1010 tmp = lutil_strcopy( tmp, /*"(("*/ "))" );
1012 /* pass opx because str2filter_x may (and does) use o_tmpmfuncs */
1013 op.ors_filter = str2filter_x( opx, op.ors_filterstr.bv_val );
1014 if ( op.ors_filter == NULL ) {
1015 rc = LDAP_PROTOCOL_ERROR;
1018 op.ors_scope = LDAP_SCOPE_BASE;
1020 /* hijack match DN: use that of the group instead of the assertDN;
1021 * assertDN is now in the filter */
1022 sm.dn = &op.o_req_ndn;
1028 case LDAP_X_SCOPE_USERS:
1029 if ( !BER_BVISEMPTY( assertDN ) ) {
1032 rc = LDAP_INAPPROPRIATE_AUTH;
1040 /* Must run an internal search. */
1041 if ( op.ors_filter == NULL ) {
1042 rc = LDAP_FILTER_ERROR;
1046 Debug( LDAP_DEBUG_TRACE,
1047 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
1048 op.o_req_ndn.bv_val, op.ors_scope, 0 );
1050 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
1051 if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
1052 rc = LDAP_INAPPROPRIATE_AUTH;
1056 op.o_hdr = opx->o_hdr;
1057 op.o_tag = LDAP_REQ_SEARCH;
1059 op.o_callback = &cb;
1060 op.o_time = slap_get_time();
1061 op.o_do_not_cache = 1;
1062 op.o_is_auth_check = 1;
1063 /* use req_ndn as req_dn instead of non-pretty base of uri */
1064 if( !BER_BVISNULL( &base ) ) {
1065 ch_free( base.bv_val );
1066 /* just in case... */
1067 BER_BVZERO( &base );
1069 ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1070 op.ors_deref = LDAP_DEREF_NEVER;
1072 op.ors_tlimit = SLAP_NO_LIMIT;
1073 op.ors_attrs = slap_anlist_no_attrs;
1074 op.ors_attrsonly = 1;
1076 op.o_bd->be_search( &op, &rs );
1078 if (sm.match == 1) {
1081 rc = LDAP_INAPPROPRIATE_AUTH;
1085 if( !BER_BVISNULL( &op.o_req_dn ) ) slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1086 if( !BER_BVISNULL( &op.o_req_ndn ) ) slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1087 if( op.ors_filter ) filter_free_x( opx, op.ors_filter );
1088 if( !BER_BVISNULL( &op.ors_filterstr ) ) ch_free( op.ors_filterstr.bv_val );
1090 Debug( LDAP_DEBUG_TRACE,
1091 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
1098 * This function answers the question, "Can this ID authorize to that ID?",
1099 * based on authorization rules. The rules are stored in the *searchDN, in the
1100 * attribute named by *attr. If any of those rules map to the *assertDN, the
1101 * authorization is approved.
1103 * The DNs should not have the dn: prefix
1106 slap_sasl_check_authz( Operation *op,
1107 struct berval *searchDN,
1108 struct berval *assertDN,
1109 AttributeDescription *ad,
1110 struct berval *authc )
1113 BerVarray vals = NULL;
1115 Debug( LDAP_DEBUG_TRACE,
1116 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
1117 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
1119 rc = backend_attribute( op, NULL, searchDN, ad, &vals, ACL_AUTH );
1120 if( rc != LDAP_SUCCESS ) goto COMPLETE;
1122 /* Check if the *assertDN matches any *vals */
1123 rc = slap_sasl_matches( op, vals, assertDN, authc );
1126 if( vals ) ber_bvarray_free_x( vals, op->o_tmpmemctx );
1128 Debug( LDAP_DEBUG_TRACE,
1129 "<==slap_sasl_check_authz: %s check returning %d\n",
1130 ad->ad_cname.bv_val, rc, 0);
1136 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
1137 * return the LDAP DN to which it matches. The SASL regexp rules in the config
1138 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
1139 * search with scope=base), just return the URI (or its searchbase). Otherwise
1140 * an internal search must be done, and if that search returns exactly one
1141 * entry, return the DN of that one entry.
1146 struct berval *saslname,
1147 struct berval *sasldn,
1151 slap_callback cb = { NULL, sasl_sc_sasl2dn, NULL, NULL };
1153 SlapReply rs = {REP_RESULT};
1154 struct berval regout = BER_BVNULL;
1155 struct berval base = BER_BVNULL;
1157 Debug( LDAP_DEBUG_TRACE, "==>slap_sasl2dn: "
1158 "converting SASL name %s to a DN\n",
1159 saslname->bv_val, 0,0 );
1161 BER_BVZERO( sasldn );
1162 cb.sc_private = sasldn;
1164 /* Convert the SASL name into a minimal URI */
1165 if( !slap_authz_regexp( saslname, ®out, flags, opx->o_tmpmemctx ) ) {
1169 rc = slap_parseURI( opx, ®out, &base,
1170 &op.o_req_ndn, &op.ors_scope, &op.ors_filter,
1171 &op.ors_filterstr );
1172 if ( !BER_BVISNULL( ®out ) ) slap_sl_free( regout.bv_val, opx->o_tmpmemctx );
1173 if ( rc != LDAP_SUCCESS ) {
1177 /* Must do an internal search */
1178 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
1180 switch ( op.ors_scope ) {
1181 case LDAP_X_SCOPE_EXACT:
1182 *sasldn = op.o_req_ndn;
1183 BER_BVZERO( &op.o_req_ndn );
1184 /* intentionally continue to next case */
1186 case LDAP_X_SCOPE_REGEX:
1187 case LDAP_X_SCOPE_SUBTREE:
1188 case LDAP_X_SCOPE_CHILDREN:
1189 case LDAP_X_SCOPE_ONELEVEL:
1190 case LDAP_X_SCOPE_GROUP:
1191 case LDAP_X_SCOPE_USERS:
1192 /* correctly parsed, but illegal */
1195 case LDAP_SCOPE_BASE:
1196 case LDAP_SCOPE_ONELEVEL:
1197 case LDAP_SCOPE_SUBTREE:
1198 #ifdef LDAP_SCOPE_SUBORDINATE
1199 case LDAP_SCOPE_SUBORDINATE:
1205 /* catch unhandled cases (there shouldn't be) */
1209 Debug( LDAP_DEBUG_TRACE,
1210 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
1211 op.o_req_ndn.bv_val, op.ors_scope, 0 );
1213 if ( ( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL) ) {
1217 /* Must run an internal search. */
1218 if ( op.ors_filter == NULL ) {
1219 rc = LDAP_FILTER_ERROR;
1223 op.o_hdr = opx->o_hdr;
1224 op.o_tag = LDAP_REQ_SEARCH;
1225 op.o_ndn = opx->o_conn->c_ndn;
1226 op.o_callback = &cb;
1227 op.o_time = slap_get_time();
1228 op.o_do_not_cache = 1;
1229 op.o_is_auth_check = 1;
1230 op.ors_deref = LDAP_DEREF_NEVER;
1232 op.ors_tlimit = SLAP_NO_LIMIT;
1233 op.ors_attrs = slap_anlist_no_attrs;
1234 op.ors_attrsonly = 1;
1235 /* use req_ndn as req_dn instead of non-pretty base of uri */
1236 if( !BER_BVISNULL( &base ) ) {
1237 ch_free( base.bv_val );
1238 /* just in case... */
1239 BER_BVZERO( &base );
1241 ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1243 op.o_bd->be_search( &op, &rs );
1246 if( !BER_BVISEMPTY( sasldn ) ) {
1247 opx->o_conn->c_authz_backend = op.o_bd;
1249 if( !BER_BVISNULL( &op.o_req_dn ) ) {
1250 slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1252 if( !BER_BVISNULL( &op.o_req_ndn ) ) {
1253 slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1255 if( op.ors_filter ) {
1256 filter_free_x( opx, op.ors_filter );
1258 if( !BER_BVISNULL( &op.ors_filterstr ) ) {
1259 ch_free( op.ors_filterstr.bv_val );
1262 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
1263 !BER_BVISEMPTY( sasldn ) ? sasldn->bv_val : "<nothing>", 0, 0 );
1269 /* Check if a bind can SASL authorize to another identity.
1270 * The DNs should not have the dn: prefix
1273 int slap_sasl_authorized( Operation *op,
1274 struct berval *authcDN, struct berval *authzDN )
1276 int rc = LDAP_INAPPROPRIATE_AUTH;
1278 /* User binding as anonymous */
1279 if ( authzDN == NULL ) {
1284 Debug( LDAP_DEBUG_TRACE,
1285 "==>slap_sasl_authorized: can %s become %s?\n",
1286 authcDN->bv_val, authzDN->bv_val, 0 );
1288 /* If person is authorizing to self, succeed */
1289 if ( dn_match( authcDN, authzDN ) ) {
1294 /* Allow the manager to authorize as any DN. */
1295 if( op->o_conn->c_authz_backend &&
1296 be_isroot_dn( op->o_conn->c_authz_backend, authcDN ))
1302 /* Check source rules */
1303 if( authz_policy & SASL_AUTHZ_TO ) {
1304 rc = slap_sasl_check_authz( op, authcDN, authzDN,
1305 slap_schema.si_ad_saslAuthzTo, authcDN );
1306 if( rc == LDAP_SUCCESS && !(authz_policy & SASL_AUTHZ_AND) ) {
1311 /* Check destination rules */
1312 if( authz_policy & SASL_AUTHZ_FROM ) {
1313 rc = slap_sasl_check_authz( op, authzDN, authcDN,
1314 slap_schema.si_ad_saslAuthzFrom, authcDN );
1315 if( rc == LDAP_SUCCESS ) {
1320 rc = LDAP_INAPPROPRIATE_AUTH;
1324 Debug( LDAP_DEBUG_TRACE,
1325 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );