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 )
610 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
611 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
613 reg = &SaslRegexp[nSaslRegexp];
615 reg->sr_match = ch_strdup( match );
616 reg->sr_replace = ch_strdup( replace );
618 #ifdef SLAP_AUTH_REWRITE
619 rc = slap_sasl_regexp_rewrite_config( "sasl-regexp", 0,
620 match, replace, AUTHID_CONTEXT );
621 if ( rc == LDAP_SUCCESS ) nSaslRegexp++;
623 #else /* ! SLAP_AUTH_REWRITE */
625 /* Precompile matching pattern */
626 rc = regcomp( ®->sr_workspace, reg->sr_match, REG_EXTENDED|REG_ICASE );
628 Debug( LDAP_DEBUG_ANY,
629 "SASL match pattern %s could not be compiled by regexp engine\n",
630 reg->sr_match, 0, 0 );
632 return( LDAP_OTHER );
635 rc = slap_sasl_rx_off( reg->sr_replace, reg->sr_offset );
636 if ( rc != LDAP_SUCCESS ) return rc;
639 return( LDAP_SUCCESS );
640 #endif /* ! SLAP_AUTH_REWRITE */
643 void slap_sasl_regexp_unparse( BerVarray *out )
647 BerVarray bva = NULL;
651 if ( !nSaslRegexp ) return;
654 bva = ch_malloc( (nSaslRegexp+1) * sizeof(struct berval) );
655 BER_BVZERO(bva+nSaslRegexp);
656 for ( i=0; i<nSaslRegexp; i++ ) {
657 idx.bv_len = sprintf( idx.bv_val, "{%d}", i);
658 bva[i].bv_len = idx.bv_len + strlen( SaslRegexp[i].sr_match ) +
659 strlen( SaslRegexp[i].sr_replace ) + 5;
660 bva[i].bv_val = ch_malloc( bva[i].bv_len+1 );
661 ptr = lutil_strcopy( bva[i].bv_val, ibuf );
663 ptr = lutil_strcopy( ptr, SaslRegexp[i].sr_match );
664 ptr = lutil_strcopy( ptr, "\" \"" );
665 ptr = lutil_strcopy( ptr, SaslRegexp[i].sr_replace );
672 /* Perform replacement on regexp matches */
673 static void slap_sasl_rx_exp(
677 const char *saslname,
681 int i, n, len, insert;
683 /* Get the total length of the final URI */
687 while( off[n] >= 0 ) {
688 /* Len of next section from replacement string (x,y,z above) */
689 len += off[n] - off[n-1] - 2;
693 /* Len of string from saslname that matched next $i (b,d above) */
694 i = rep[ off[n] + 1 ] - '0';
695 len += str[i].rm_eo - str[i].rm_so;
698 out->bv_val = slap_sl_malloc( len + 1, ctx );
701 /* Fill in URI with replace string, replacing $i as we go */
704 while( off[n] >= 0) {
705 /* Paste in next section from replacement string (x,y,z above) */
706 len = off[n] - off[n-1] - 2;
707 strncpy( out->bv_val+insert, rep + off[n-1] + 2, len);
712 /* Paste in string from saslname that matched next $i (b,d above) */
713 i = rep[ off[n] + 1 ] - '0';
714 len = str[i].rm_eo - str[i].rm_so;
715 strncpy( out->bv_val+insert, saslname + str[i].rm_so, len );
721 out->bv_val[insert] = '\0';
724 /* Take the passed in SASL name and attempt to convert it into an
725 LDAP URI to find the matching LDAP entry, using the pattern matching
726 strings given in the saslregexp config file directive(s) */
728 static int slap_authz_regexp( struct berval *in, struct berval *out,
729 int flags, void *ctx )
731 #ifdef SLAP_AUTH_REWRITE
732 const char *context = AUTHID_CONTEXT;
734 if ( sasl_rwinfo == NULL || BER_BVISNULL( in ) ) {
738 /* FIXME: if aware of authc/authz mapping,
739 * we could use different contexts ... */
740 switch ( rewrite_session( sasl_rwinfo, context, in->bv_val, NULL,
743 case REWRITE_REGEXEC_OK:
744 if ( !BER_BVISNULL( out ) ) {
745 char *val = out->bv_val;
746 ber_str2bv_x( val, 0, 1, out, ctx );
747 if ( val != in->bv_val ) {
751 ber_dupbv_x( out, in, ctx );
753 Debug( LDAP_DEBUG_ARGS,
754 "[rw] %s: \"%s\" -> \"%s\"\n",
755 context, in->bv_val, out->bv_val );
758 case REWRITE_REGEXEC_UNWILLING:
759 case REWRITE_REGEXEC_ERR:
764 #else /* ! SLAP_AUTH_REWRITE */
765 char *saslname = in->bv_val;
767 regmatch_t sr_strings[SASLREGEX_REPLACE]; /* strings matching $1,$2 ... */
770 memset( out, 0, sizeof( *out ) );
772 Debug( LDAP_DEBUG_TRACE, "slap_authz_regexp: converting SASL name %s\n",
775 if (( saslname == NULL ) || ( nSaslRegexp == 0 )) {
779 /* Match the normalized SASL name to the saslregexp patterns */
780 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
781 if ( regexec( ®->sr_workspace, saslname, SASLREGEX_REPLACE,
782 sr_strings, 0) == 0 )
786 if( i >= nSaslRegexp ) return( 0 );
789 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
790 * replace pattern of the form "x$1y$2z". The returned string needs
791 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
793 slap_sasl_rx_exp( reg->sr_replace, reg->sr_offset,
794 sr_strings, saslname, out, ctx );
796 Debug( LDAP_DEBUG_TRACE,
797 "slap_authz_regexp: converted SASL name to %s\n",
798 BER_BVISEMPTY( out ) ? "" : out->bv_val, 0, 0 );
801 #endif /* ! SLAP_AUTH_REWRITE */
804 /* This callback actually does some work...*/
805 static int sasl_sc_sasl2dn( Operation *o, SlapReply *rs )
807 struct berval *ndn = o->o_callback->sc_private;
809 if (rs->sr_type != REP_SEARCH) return 0;
811 /* We only want to be called once */
812 if ( !BER_BVISNULL( ndn ) ) {
813 o->o_tmpfree(ndn->bv_val, o->o_tmpmemctx);
816 Debug( LDAP_DEBUG_TRACE,
817 "slap_sc_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
821 ber_dupbv_x(ndn, &rs->sr_entry->e_nname, o->o_tmpmemctx);
826 typedef struct smatch_info {
831 static int sasl_sc_smatch( Operation *o, SlapReply *rs )
833 smatch_info *sm = o->o_callback->sc_private;
835 if ( rs->sr_type != REP_SEARCH ) {
836 if ( rs->sr_err != LDAP_SUCCESS ) {
842 if ( sm->match == 1 ) {
847 if (dn_match(sm->dn, &rs->sr_entry->e_nname)) {
858 slap_sasl_matches( Operation *op, BerVarray rules,
859 struct berval *assertDN, struct berval *authc )
861 int rc = LDAP_INAPPROPRIATE_AUTH;
863 if ( rules != NULL ) {
866 for( i = 0; !BER_BVISNULL( &rules[i] ); i++ ) {
867 rc = slap_sasl_match( op, &rules[i], assertDN, authc );
868 if ( rc == LDAP_SUCCESS ) break;
876 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
877 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
878 * the rule must be used as an internal search for entries. If that search
879 * returns the *assertDN entry, the match is successful.
881 * The assertDN should not have the dn: prefix
885 int slap_sasl_match( Operation *opx, struct berval *rule,
886 struct berval *assertDN, struct berval *authc )
891 slap_callback cb = { NULL, sasl_sc_smatch, NULL, NULL };
893 SlapReply rs = {REP_RESULT};
894 struct berval base = BER_BVNULL;
900 Debug( LDAP_DEBUG_TRACE,
901 "===>slap_sasl_match: comparing DN %s to rule %s\n",
902 assertDN->bv_val, rule->bv_val, 0 );
904 rc = slap_parseURI( opx, rule, &base,
905 &op.o_req_ndn, &op.ors_scope, &op.ors_filter,
907 if( rc != LDAP_SUCCESS ) goto CONCLUDED;
909 switch ( op.ors_scope ) {
910 case LDAP_X_SCOPE_EXACT:
912 if ( dn_match( &op.o_req_ndn, assertDN ) ) {
915 rc = LDAP_INAPPROPRIATE_AUTH;
919 case LDAP_X_SCOPE_CHILDREN:
920 case LDAP_X_SCOPE_SUBTREE:
921 case LDAP_X_SCOPE_ONELEVEL:
923 int d = assertDN->bv_len - op.o_req_ndn.bv_len;
925 rc = LDAP_INAPPROPRIATE_AUTH;
927 if ( d == 0 && op.ors_scope == LDAP_X_SCOPE_SUBTREE ) {
930 } else if ( d > 0 ) {
933 /* leave room for at least one char of attributeType,
934 * one for '=' and one for ',' */
935 if ( d < STRLENOF( "x=,") ) {
939 bv.bv_len = op.o_req_ndn.bv_len;
940 bv.bv_val = assertDN->bv_val + d;
942 if ( bv.bv_val[ -1 ] == ',' && dn_match( &op.o_req_ndn, &bv ) ) {
943 switch ( op.ors_scope ) {
944 case LDAP_X_SCOPE_SUBTREE:
945 case LDAP_X_SCOPE_CHILDREN:
949 case LDAP_X_SCOPE_ONELEVEL:
953 dnParent( assertDN, &pdn );
954 /* the common portion of the DN
955 * already matches, so only check
956 * if parent DN of assertedDN
957 * is all the pattern */
958 if ( pdn.bv_len == op.o_req_ndn.bv_len ) {
964 /* at present, impossible */
972 case LDAP_X_SCOPE_REGEX:
973 rc = regcomp(®, op.o_req_ndn.bv_val,
974 REG_EXTENDED|REG_ICASE|REG_NOSUB);
976 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
982 rc = LDAP_INAPPROPRIATE_AUTH;
986 case LDAP_X_SCOPE_GROUP: {
989 /* Now filterstr looks like "(&(objectClass=<group_oc>)(<member_at>="
990 * we need to append the <assertDN> so that the <group_dn> is searched
991 * with scope "base", and the filter ensures that <assertDN> is
992 * member of the group */
993 tmp = ch_realloc( op.ors_filterstr.bv_val, op.ors_filterstr.bv_len +
994 assertDN->bv_len + STRLENOF( /*"(("*/ "))" ) + 1 );
999 op.ors_filterstr.bv_val = tmp;
1001 tmp = lutil_strcopy( &tmp[op.ors_filterstr.bv_len], assertDN->bv_val );
1002 tmp = lutil_strcopy( tmp, /*"(("*/ "))" );
1004 /* pass opx because str2filter_x may (and does) use o_tmpmfuncs */
1005 op.ors_filter = str2filter_x( opx, op.ors_filterstr.bv_val );
1006 if ( op.ors_filter == NULL ) {
1007 rc = LDAP_PROTOCOL_ERROR;
1010 op.ors_scope = LDAP_SCOPE_BASE;
1012 /* hijack match DN: use that of the group instead of the assertDN;
1013 * assertDN is now in the filter */
1014 sm.dn = &op.o_req_ndn;
1020 case LDAP_X_SCOPE_USERS:
1021 if ( !BER_BVISEMPTY( assertDN ) ) {
1024 rc = LDAP_INAPPROPRIATE_AUTH;
1032 /* Must run an internal search. */
1033 if ( op.ors_filter == NULL ) {
1034 rc = LDAP_FILTER_ERROR;
1038 Debug( LDAP_DEBUG_TRACE,
1039 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
1040 op.o_req_ndn.bv_val, op.ors_scope, 0 );
1042 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
1043 if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
1044 rc = LDAP_INAPPROPRIATE_AUTH;
1048 op.o_hdr = opx->o_hdr;
1049 op.o_tag = LDAP_REQ_SEARCH;
1051 op.o_callback = &cb;
1052 op.o_time = slap_get_time();
1053 op.o_do_not_cache = 1;
1054 op.o_is_auth_check = 1;
1055 /* use req_ndn as req_dn instead of non-pretty base of uri */
1056 if( !BER_BVISNULL( &base ) ) {
1057 ch_free( base.bv_val );
1058 /* just in case... */
1059 BER_BVZERO( &base );
1061 ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1063 op.ors_tlimit = SLAP_NO_LIMIT;
1064 op.ors_attrs = slap_anlist_no_attrs;
1065 op.ors_attrsonly = 1;
1067 op.o_bd->be_search( &op, &rs );
1069 if (sm.match == 1) {
1072 rc = LDAP_INAPPROPRIATE_AUTH;
1076 if( !BER_BVISNULL( &op.o_req_dn ) ) slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1077 if( !BER_BVISNULL( &op.o_req_ndn ) ) slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1078 if( op.ors_filter ) filter_free_x( opx, op.ors_filter );
1079 if( !BER_BVISNULL( &op.ors_filterstr ) ) ch_free( op.ors_filterstr.bv_val );
1081 Debug( LDAP_DEBUG_TRACE,
1082 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
1089 * This function answers the question, "Can this ID authorize to that ID?",
1090 * based on authorization rules. The rules are stored in the *searchDN, in the
1091 * attribute named by *attr. If any of those rules map to the *assertDN, the
1092 * authorization is approved.
1094 * The DNs should not have the dn: prefix
1097 slap_sasl_check_authz( Operation *op,
1098 struct berval *searchDN,
1099 struct berval *assertDN,
1100 AttributeDescription *ad,
1101 struct berval *authc )
1104 BerVarray vals = NULL;
1106 Debug( LDAP_DEBUG_TRACE,
1107 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
1108 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
1110 rc = backend_attribute( op, NULL, searchDN, ad, &vals, ACL_AUTH );
1111 if( rc != LDAP_SUCCESS ) goto COMPLETE;
1113 /* Check if the *assertDN matches any *vals */
1114 rc = slap_sasl_matches( op, vals, assertDN, authc );
1117 if( vals ) ber_bvarray_free_x( vals, op->o_tmpmemctx );
1119 Debug( LDAP_DEBUG_TRACE,
1120 "<==slap_sasl_check_authz: %s check returning %d\n",
1121 ad->ad_cname.bv_val, rc, 0);
1127 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
1128 * return the LDAP DN to which it matches. The SASL regexp rules in the config
1129 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
1130 * search with scope=base), just return the URI (or its searchbase). Otherwise
1131 * an internal search must be done, and if that search returns exactly one
1132 * entry, return the DN of that one entry.
1134 void slap_sasl2dn( Operation *opx,
1135 struct berval *saslname, struct berval *sasldn, int flags )
1138 slap_callback cb = { NULL, sasl_sc_sasl2dn, NULL, NULL };
1140 SlapReply rs = {REP_RESULT};
1141 struct berval regout = BER_BVNULL;
1142 struct berval base = BER_BVNULL;
1144 Debug( LDAP_DEBUG_TRACE, "==>slap_sasl2dn: "
1145 "converting SASL name %s to a DN\n",
1146 saslname->bv_val, 0,0 );
1148 BER_BVZERO( sasldn );
1149 cb.sc_private = sasldn;
1151 /* Convert the SASL name into a minimal URI */
1152 if( !slap_authz_regexp( saslname, ®out, flags, opx->o_tmpmemctx ) ) {
1156 rc = slap_parseURI( opx, ®out, &base,
1157 &op.o_req_ndn, &op.ors_scope, &op.ors_filter,
1158 &op.ors_filterstr );
1159 if ( !BER_BVISNULL( ®out ) ) slap_sl_free( regout.bv_val, opx->o_tmpmemctx );
1160 if ( rc != LDAP_SUCCESS ) {
1164 /* Must do an internal search */
1165 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
1167 switch ( op.ors_scope ) {
1168 case LDAP_X_SCOPE_EXACT:
1169 *sasldn = op.o_req_ndn;
1170 BER_BVZERO( &op.o_req_ndn );
1171 /* intentionally continue to next case */
1173 case LDAP_X_SCOPE_REGEX:
1174 case LDAP_X_SCOPE_SUBTREE:
1175 case LDAP_X_SCOPE_CHILDREN:
1176 case LDAP_X_SCOPE_ONELEVEL:
1177 case LDAP_X_SCOPE_GROUP:
1178 case LDAP_X_SCOPE_USERS:
1179 /* correctly parsed, but illegal */
1182 case LDAP_SCOPE_BASE:
1183 case LDAP_SCOPE_ONELEVEL:
1184 case LDAP_SCOPE_SUBTREE:
1185 #ifdef LDAP_SCOPE_SUBORDINATE
1186 case LDAP_SCOPE_SUBORDINATE:
1192 /* catch unhandled cases (there shouldn't be) */
1196 Debug( LDAP_DEBUG_TRACE,
1197 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
1198 op.o_req_ndn.bv_val, op.ors_scope, 0 );
1200 if ( ( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL) ) {
1204 /* Must run an internal search. */
1205 if ( op.ors_filter == NULL ) {
1206 rc = LDAP_FILTER_ERROR;
1210 op.o_hdr = opx->o_hdr;
1211 op.o_tag = LDAP_REQ_SEARCH;
1212 op.o_ndn = opx->o_conn->c_ndn;
1213 op.o_callback = &cb;
1214 op.o_time = slap_get_time();
1215 op.o_do_not_cache = 1;
1216 op.o_is_auth_check = 1;
1217 op.ors_deref = LDAP_DEREF_NEVER;
1219 op.ors_tlimit = SLAP_NO_LIMIT;
1220 op.ors_attrs = slap_anlist_no_attrs;
1221 op.ors_attrsonly = 1;
1222 /* use req_ndn as req_dn instead of non-pretty base of uri */
1223 if( !BER_BVISNULL( &base ) ) {
1224 ch_free( base.bv_val );
1225 /* just in case... */
1226 BER_BVZERO( &base );
1228 ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1230 op.o_bd->be_search( &op, &rs );
1233 if( !BER_BVISEMPTY( sasldn ) ) {
1234 opx->o_conn->c_authz_backend = op.o_bd;
1236 if( !BER_BVISNULL( &op.o_req_dn ) ) {
1237 slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1239 if( !BER_BVISNULL( &op.o_req_ndn ) ) {
1240 slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1242 if( op.ors_filter ) {
1243 filter_free_x( opx, op.ors_filter );
1245 if( !BER_BVISNULL( &op.ors_filterstr ) ) {
1246 ch_free( op.ors_filterstr.bv_val );
1249 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
1250 !BER_BVISEMPTY( sasldn ) ? sasldn->bv_val : "<nothing>", 0, 0 );
1256 /* Check if a bind can SASL authorize to another identity.
1257 * The DNs should not have the dn: prefix
1260 int slap_sasl_authorized( Operation *op,
1261 struct berval *authcDN, struct berval *authzDN )
1263 int rc = LDAP_INAPPROPRIATE_AUTH;
1265 /* User binding as anonymous */
1266 if ( authzDN == NULL ) {
1271 Debug( LDAP_DEBUG_TRACE,
1272 "==>slap_sasl_authorized: can %s become %s?\n",
1273 authcDN->bv_val, authzDN->bv_val, 0 );
1275 /* If person is authorizing to self, succeed */
1276 if ( dn_match( authcDN, authzDN ) ) {
1281 /* Allow the manager to authorize as any DN. */
1282 if( op->o_conn->c_authz_backend &&
1283 be_isroot_dn( op->o_conn->c_authz_backend, authcDN ))
1289 /* Check source rules */
1290 if( authz_policy & SASL_AUTHZ_TO ) {
1291 rc = slap_sasl_check_authz( op, authcDN, authzDN,
1292 slap_schema.si_ad_saslAuthzTo, authcDN );
1293 if( rc == LDAP_SUCCESS && !(authz_policy & SASL_AUTHZ_AND) ) {
1298 /* Check destination rules */
1299 if( authz_policy & SASL_AUTHZ_FROM ) {
1300 rc = slap_sasl_check_authz( op, authzDN, authcDN,
1301 slap_schema.si_ad_saslAuthzFrom, authcDN );
1302 if( rc == LDAP_SUCCESS ) {
1307 rc = LDAP_INAPPROPRIATE_AUTH;
1311 Debug( LDAP_DEBUG_TRACE,
1312 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );