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>
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)
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;
92 int slap_sasl_setpolicy( const char *arg )
94 int rc = LDAP_SUCCESS;
96 if ( strcasecmp( arg, "none" ) == 0 ) {
97 authz_policy = SASL_AUTHZ_NONE;
98 } else if ( strcasecmp( arg, "from" ) == 0 ) {
99 authz_policy = SASL_AUTHZ_FROM;
100 } else if ( strcasecmp( arg, "to" ) == 0 ) {
101 authz_policy = SASL_AUTHZ_TO;
102 } else if ( strcasecmp( arg, "both" ) == 0 || strcasecmp( arg, "any" ) == 0 ) {
103 authz_policy = SASL_AUTHZ_FROM | SASL_AUTHZ_TO;
104 } else if ( strcasecmp( arg, "all" ) == 0 ) {
105 authz_policy = SASL_AUTHZ_FROM | SASL_AUTHZ_TO | SASL_AUTHZ_AND;
112 int slap_parse_user( struct berval *id, struct berval *user,
113 struct berval *realm, struct berval *mech )
118 assert( !BER_BVISNULL( id ) );
125 if ( u != 'u' && u != 'U' ) {
126 /* called with something other than u: */
127 return LDAP_PROTOCOL_ERROR;
131 * u[.mech[/realm]]:user
134 user->bv_val = strchr( id->bv_val, ':' );
135 if ( BER_BVISNULL( user ) ) {
136 return LDAP_PROTOCOL_ERROR;
138 user->bv_val[ 0 ] = '\0';
140 user->bv_len = id->bv_len - ( user->bv_val - id->bv_val );
142 mech->bv_val = strchr( id->bv_val, '.' );
143 if ( !BER_BVISNULL( mech ) ) {
144 mech->bv_val[ 0 ] = '\0';
147 realm->bv_val = strchr( mech->bv_val, '/' );
149 if ( !BER_BVISNULL( realm ) ) {
150 realm->bv_val[ 0 ] = '\0';
152 mech->bv_len = realm->bv_val - mech->bv_val - 1;
153 realm->bv_len = user->bv_val - realm->bv_val - 1;
155 mech->bv_len = user->bv_val - mech->bv_val - 1;
162 if ( id->bv_val[ 1 ] != '\0' ) {
163 return LDAP_PROTOCOL_ERROR;
166 if ( !BER_BVISNULL( mech ) ) {
167 assert( mech->bv_val == id->bv_val + 2 );
169 AC_MEMCPY( mech->bv_val - 2, mech->bv_val, mech->bv_len + 1 );
173 if ( !BER_BVISNULL( realm ) ) {
174 assert( realm->bv_val >= id->bv_val + 2 );
176 AC_MEMCPY( realm->bv_val - 2, realm->bv_val, realm->bv_len + 1 );
180 /* leave "u:" before user */
183 user->bv_val[ 0 ] = u;
184 user->bv_val[ 1 ] = ':';
189 static int slap_parseURI( Operation *op, struct berval *uri,
190 struct berval *base, struct berval *nbase,
191 int *scope, Filter **filter, struct berval *fstr )
197 assert( uri != NULL && !BER_BVISNULL( uri ) );
205 LDAP_LOG( TRANSPORT, ENTRY,
206 "slap_parseURI: parsing %s\n", uri->bv_val, 0, 0 );
208 Debug( LDAP_DEBUG_TRACE,
209 "slap_parseURI: parsing %s\n", uri->bv_val, 0, 0 );
212 rc = LDAP_PROTOCOL_ERROR;
213 if ( !strncasecmp( uri->bv_val, "dn", STRLENOF( "dn" ) ) ) {
214 bv.bv_val = uri->bv_val + STRLENOF( "dn" );
216 if ( bv.bv_val[ 0 ] == '.' ) {
219 if ( !strncasecmp( bv.bv_val, "exact:", STRLENOF( "exact:" ) ) ) {
220 bv.bv_val += STRLENOF( "exact:" );
221 *scope = LDAP_X_SCOPE_EXACT;
223 } else if ( !strncasecmp( bv.bv_val, "regex:", STRLENOF( "regex:" ) ) ) {
224 bv.bv_val += STRLENOF( "regex:" );
225 *scope = LDAP_X_SCOPE_REGEX;
227 } else if ( !strncasecmp( bv.bv_val, "children:", STRLENOF( "chldren:" ) ) ) {
228 bv.bv_val += STRLENOF( "children:" );
229 *scope = LDAP_X_SCOPE_CHILDREN;
231 } else if ( !strncasecmp( bv.bv_val, "subtree:", STRLENOF( "subtree:" ) ) ) {
232 bv.bv_val += STRLENOF( "subtree:" );
233 *scope = LDAP_X_SCOPE_SUBTREE;
235 } else if ( !strncasecmp( bv.bv_val, "onelevel:", STRLENOF( "onelevel:" ) ) ) {
236 bv.bv_val += STRLENOF( "onelevel:" );
237 *scope = LDAP_X_SCOPE_ONELEVEL;
240 return LDAP_PROTOCOL_ERROR;
244 if ( bv.bv_val[ 0 ] != ':' )
245 return LDAP_PROTOCOL_ERROR;
249 bv.bv_val += strspn( bv.bv_val, " " );
250 /* jump here in case no type specification was present
251 * and uir was not an URI... HEADS-UP: assuming EXACT */
252 is_dn: bv.bv_len = uri->bv_len - (bv.bv_val - uri->bv_val);
255 case LDAP_X_SCOPE_EXACT:
256 case LDAP_X_SCOPE_CHILDREN:
257 case LDAP_X_SCOPE_SUBTREE:
258 case LDAP_X_SCOPE_ONELEVEL:
259 rc = dnNormalize( 0, NULL, NULL, &bv, nbase, op->o_tmpmemctx );
260 if( rc != LDAP_SUCCESS ) {
265 case LDAP_X_SCOPE_REGEX:
266 ber_dupbv_x( nbase, &bv, op->o_tmpmemctx );
277 } else if ( ( uri->bv_val[ 0 ] == 'u' || uri->bv_val[ 0 ] == 'U' )
278 && ( uri->bv_val[ 1 ] == ':'
279 || uri->bv_val[ 1 ] == '/'
280 || uri->bv_val[ 1 ] == '.' ) )
282 Connection c = *op->o_conn;
283 char buf[ SLAP_LDAPDN_MAXLEN ];
289 if ( sizeof( buf ) <= uri->bv_len ) {
290 return LDAP_INVALID_SYNTAX;
293 id.bv_len = uri->bv_len;
295 strncpy( buf, uri->bv_val, sizeof( buf ) );
297 rc = slap_parse_user( &id, &user, &realm, &mech );
298 if ( rc != LDAP_SUCCESS ) {
302 if ( !BER_BVISNULL( &mech ) ) {
303 c.c_sasl_bind_mech = mech;
305 BER_BVSTR( &c.c_sasl_bind_mech, "AUTHZ" );
308 rc = slap_sasl_getdn( &c, op, &user,
309 realm.bv_val, nbase, SLAP_GETDN_AUTHZID );
311 if ( rc == LDAP_SUCCESS ) {
312 *scope = LDAP_X_SCOPE_EXACT;
317 } else if ( strncasecmp( uri->bv_val, "group", STRLENOF( "group" ) ) == 0 )
319 struct berval group_dn = BER_BVNULL,
320 group_oc = BER_BVNULL,
321 member_at = BER_BVNULL;
324 bv.bv_val = uri->bv_val + STRLENOF( "group" );
325 group_dn.bv_val = strchr( bv.bv_val, ':' );
326 if ( group_dn.bv_val == NULL ) {
327 /* last chance: assume it's a(n exact) DN ... */
328 bv.bv_val = uri->bv_val;
329 *scope = LDAP_X_SCOPE_EXACT;
333 if ( bv.bv_val[ 0 ] == '/' ) {
334 group_oc.bv_val = &bv.bv_val[ 1 ];
336 member_at.bv_val = strchr( group_oc.bv_val, '/' );
337 if ( member_at.bv_val ) {
338 group_oc.bv_len = member_at.bv_val - group_oc.bv_val;
340 member_at.bv_len = group_dn.bv_val - member_at.bv_val;
343 group_oc.bv_len = group_dn.bv_val - group_oc.bv_val;
344 BER_BVSTR( &member_at, "member" );
348 BER_BVSTR( &group_oc, "groupOfNames" );
351 group_dn.bv_len = uri->bv_len - ( group_dn.bv_val - uri->bv_val );
353 fstr->bv_len = STRLENOF( "(&(objectClass=)(=" ) + group_oc.bv_len + member_at.bv_len;
354 fstr->bv_val = ch_malloc( fstr->bv_len + 1 );
356 tmp = lutil_strncopy( fstr->bv_val, "(&(objectClass=", STRLENOF( "(&(objectClass=" ) );
357 tmp = lutil_strncopy( tmp, group_oc.bv_val, group_oc.bv_len );
358 tmp = lutil_strncopy( tmp, ")(", STRLENOF( ")(" ) );
359 tmp = lutil_strncopy( tmp, member_at.bv_val, member_at.bv_len );
360 tmp = lutil_strncopy( tmp, "=", STRLENOF( "=" ) );
362 rc = dnNormalize( 0, NULL, NULL, &group_dn, nbase, op->o_tmpmemctx );
363 if ( rc != LDAP_SUCCESS ) {
366 *scope = LDAP_X_SCOPE_GROUP;
371 rc = ldap_url_parse( uri->bv_val, &ludp );
372 if ( rc == LDAP_URL_ERR_BADSCHEME ) {
373 /* last chance: assume it's a(n exact) DN ... */
374 bv.bv_val = uri->bv_val;
375 *scope = LDAP_X_SCOPE_EXACT;
379 if ( rc != LDAP_URL_SUCCESS ) {
380 return LDAP_PROTOCOL_ERROR;
383 if (( ludp->lud_host && *ludp->lud_host )
384 || ludp->lud_attrs || ludp->lud_exts )
386 /* host part must be empty */
387 /* attrs and extensions parts must be empty */
388 rc = LDAP_PROTOCOL_ERROR;
393 *scope = ludp->lud_scope;
395 /* Grab the filter */
396 if ( ludp->lud_filter ) {
397 *filter = str2filter_x( op, ludp->lud_filter );
398 if ( *filter == NULL ) {
399 rc = LDAP_PROTOCOL_ERROR;
402 ber_str2bv( ludp->lud_filter, 0, 0, fstr );
405 /* Grab the searchbase */
406 ber_str2bv( ludp->lud_dn, 0, 0, base );
407 rc = dnNormalize( 0, NULL, NULL, base, nbase, op->o_tmpmemctx );
410 if( rc != LDAP_SUCCESS ) {
411 if( *filter ) filter_free_x( op, *filter );
415 /* Don't free these, return them to caller */
416 ludp->lud_filter = NULL;
420 ldap_free_urldesc( ludp );
424 static int slap_sasl_rx_off(char *rep, int *off)
429 /* Precompile replace pattern. Find the $<n> placeholders */
432 for ( c = rep; *c; c++ ) {
433 if ( *c == '\\' && c[1] ) {
438 if ( n == SASLREGEX_REPLACE ) {
440 LDAP_LOG( TRANSPORT, ERR,
441 "slap_sasl_rx_off: \"%s\" has too many $n "
442 "placeholders (max %d)\n", rep, SASLREGEX_REPLACE, 0 );
444 Debug( LDAP_DEBUG_ANY,
445 "SASL replace pattern %s has too many $n "
446 "placeholders (max %d)\n",
447 rep, SASLREGEX_REPLACE, 0 );
450 return( LDAP_OTHER );
457 /* Final placeholder, after the last $n */
461 return( LDAP_SUCCESS );
464 #ifdef SLAP_AUTH_REWRITE
465 int slap_sasl_rewrite_config(
475 /* init at first call */
476 if ( sasl_rwinfo == NULL ) {
477 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
480 /* strip "authid-" prefix for parsing */
482 argv[0] += STRLENOF( "authid-" );
483 rc = rewrite_parse( sasl_rwinfo, fname, lineno, argc, argv );
489 int slap_sasl_rewrite_destroy( void )
492 rewrite_info_delete( &sasl_rwinfo );
499 int slap_sasl_regexp_rewrite_config(
504 const char *context )
507 char *newreplace, *p;
508 char *argvRule[] = { "rewriteRule", NULL, NULL, "@", NULL };
510 /* init at first call */
511 if ( sasl_rwinfo == NULL ) {
512 char *argvEngine[] = { "rewriteEngine", "on", NULL };
513 char *argvContext[] = { "rewriteContext", NULL, NULL };
515 /* initialize rewrite engine */
516 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
518 /* switch on rewrite engine */
519 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvEngine );
520 if (rc != LDAP_SUCCESS) {
524 /* create generic authid context */
525 argvContext[1] = AUTHID_CONTEXT;
526 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvContext );
527 if (rc != LDAP_SUCCESS) {
532 newreplace = ch_strdup( replace );
534 for (p = strchr( newreplace, '$' ); p; p = strchr( p + 1, '$' ) ) {
535 if ( isdigit( p[1] ) ) {
542 argvRule[1] = (char *)match;
543 argvRule[2] = newreplace;
544 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 4, argvRule );
545 ch_free( newreplace );
549 #endif /* SLAP_AUTH_REWRITE */
551 int slap_sasl_regexp_config( const char *match, const char *replace )
553 #ifdef SLAP_AUTH_REWRITE
554 return slap_sasl_regexp_rewrite_config( "sasl-regexp", 0,
555 match, replace, AUTHID_CONTEXT );
556 #else /* ! SLAP_AUTH_REWRITE */
560 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
561 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
563 reg = &SaslRegexp[nSaslRegexp];
565 reg->sr_match = ch_strdup( match );
566 reg->sr_replace = ch_strdup( replace );
568 /* Precompile matching pattern */
569 rc = regcomp( ®->sr_workspace, reg->sr_match, REG_EXTENDED|REG_ICASE );
572 LDAP_LOG( TRANSPORT, ERR,
573 "slap_sasl_regexp_config: \"%s\" could not be compiled.\n",
574 reg->sr_match, 0, 0 );
576 Debug( LDAP_DEBUG_ANY,
577 "SASL match pattern %s could not be compiled by regexp engine\n",
578 reg->sr_match, 0, 0 );
581 return( LDAP_OTHER );
584 rc = slap_sasl_rx_off( reg->sr_replace, reg->sr_offset );
585 if ( rc != LDAP_SUCCESS ) return rc;
588 return( LDAP_SUCCESS );
589 #endif /* ! SLAP_AUTH_REWRITE */
592 /* Perform replacement on regexp matches */
593 static void slap_sasl_rx_exp(
597 const char *saslname,
601 int i, n, len, insert;
603 /* Get the total length of the final URI */
607 while( off[n] >= 0 ) {
608 /* Len of next section from replacement string (x,y,z above) */
609 len += off[n] - off[n-1] - 2;
613 /* Len of string from saslname that matched next $i (b,d above) */
614 i = rep[ off[n] + 1 ] - '0';
615 len += str[i].rm_eo - str[i].rm_so;
618 out->bv_val = slap_sl_malloc( len + 1, ctx );
621 /* Fill in URI with replace string, replacing $i as we go */
624 while( off[n] >= 0) {
625 /* Paste in next section from replacement string (x,y,z above) */
626 len = off[n] - off[n-1] - 2;
627 strncpy( out->bv_val+insert, rep + off[n-1] + 2, len);
632 /* Paste in string from saslname that matched next $i (b,d above) */
633 i = rep[ off[n] + 1 ] - '0';
634 len = str[i].rm_eo - str[i].rm_so;
635 strncpy( out->bv_val+insert, saslname + str[i].rm_so, len );
641 out->bv_val[insert] = '\0';
644 /* Take the passed in SASL name and attempt to convert it into an
645 LDAP URI to find the matching LDAP entry, using the pattern matching
646 strings given in the saslregexp config file directive(s) */
648 static int slap_sasl_regexp( struct berval *in, struct berval *out,
649 int flags, void *ctx )
651 #ifdef SLAP_AUTH_REWRITE
652 const char *context = AUTHID_CONTEXT;
654 if ( sasl_rwinfo == NULL || BER_BVISNULL( in ) ) {
658 /* FIXME: if aware of authc/authz mapping,
659 * we could use different contexts ... */
660 switch ( rewrite_session( sasl_rwinfo, context, in->bv_val, NULL,
663 case REWRITE_REGEXEC_OK:
664 if ( !BER_BVISNULL( out ) ) {
665 char *val = out->bv_val;
666 ber_str2bv_x( val, 0, 1, out, ctx );
669 ber_dupbv_x( out, in, ctx );
672 LDAP_LOG( BACK_LDAP, DETAIL1,
673 "[rw] %s: \"%s\" -> \"%s\"\n",
674 context, in->bv_val, out->bv_val );
675 #else /* !NEW_LOGGING */
676 Debug( LDAP_DEBUG_ARGS,
677 "[rw] %s: \"%s\" -> \"%s\"\n",
678 context, in->bv_val, out->bv_val );
679 #endif /* !NEW_LOGGING */
682 case REWRITE_REGEXEC_UNWILLING:
683 case REWRITE_REGEXEC_ERR:
688 #else /* ! SLAP_AUTH_REWRITE */
689 char *saslname = in->bv_val;
691 regmatch_t sr_strings[SASLREGEX_REPLACE]; /* strings matching $1,$2 ... */
694 memset( out, 0, sizeof( *out ) );
697 LDAP_LOG( TRANSPORT, ENTRY,
698 "slap_sasl_regexp: converting SASL name %s\n", saslname, 0, 0 );
700 Debug( LDAP_DEBUG_TRACE, "slap_sasl_regexp: converting SASL name %s\n",
704 if (( saslname == NULL ) || ( nSaslRegexp == 0 )) {
708 /* Match the normalized SASL name to the saslregexp patterns */
709 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
710 if ( regexec( ®->sr_workspace, saslname, SASLREGEX_REPLACE,
711 sr_strings, 0) == 0 )
715 if( i >= nSaslRegexp ) return( 0 );
718 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
719 * replace pattern of the form "x$1y$2z". The returned string needs
720 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
722 slap_sasl_rx_exp( reg->sr_replace, reg->sr_offset,
723 sr_strings, saslname, out, ctx );
726 LDAP_LOG( TRANSPORT, ENTRY,
727 "slap_sasl_regexp: converted SASL name to %s\n",
728 BER_BVISEMPTY( out ) ? "" : out->bv_val, 0, 0 );
730 Debug( LDAP_DEBUG_TRACE,
731 "slap_sasl_regexp: converted SASL name to %s\n",
732 BER_BVISEMPTY( out ) ? "" : out->bv_val, 0, 0 );
736 #endif /* ! SLAP_AUTH_REWRITE */
739 /* This callback actually does some work...*/
740 static int sasl_sc_sasl2dn( Operation *o, SlapReply *rs )
742 struct berval *ndn = o->o_callback->sc_private;
744 if (rs->sr_type != REP_SEARCH) return 0;
746 /* We only want to be called once */
747 if ( !BER_BVISNULL( ndn ) ) {
748 o->o_tmpfree(ndn->bv_val, o->o_tmpmemctx);
752 LDAP_LOG( TRANSPORT, DETAIL1,
753 "slap_sc_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
755 Debug( LDAP_DEBUG_TRACE,
756 "slap_sc_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
761 ber_dupbv_x(ndn, &rs->sr_entry->e_nname, o->o_tmpmemctx);
766 typedef struct smatch_info {
771 static int sasl_sc_smatch( Operation *o, SlapReply *rs )
773 smatch_info *sm = o->o_callback->sc_private;
775 if (rs->sr_type != REP_SEARCH) return 0;
777 if (dn_match(sm->dn, &rs->sr_entry->e_nname)) {
779 return -1; /* short-circuit the search */
786 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
787 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
788 * the rule must be used as an internal search for entries. If that search
789 * returns the *assertDN entry, the match is successful.
791 * The assertDN should not have the dn: prefix
795 int slap_sasl_match( Operation *opx, struct berval *rule,
796 struct berval *assertDN, struct berval *authc )
801 slap_callback cb = { NULL, sasl_sc_smatch, NULL, NULL };
803 SlapReply rs = {REP_RESULT};
810 LDAP_LOG( TRANSPORT, ENTRY,
811 "slap_sasl_match: comparing DN %s to rule %s\n",
812 assertDN->bv_val, rule->bv_val,0 );
814 Debug( LDAP_DEBUG_TRACE,
815 "===>slap_sasl_match: comparing DN %s to rule %s\n",
816 assertDN->bv_val, rule->bv_val, 0 );
819 rc = slap_parseURI( opx, rule, &op.o_req_dn,
820 &op.o_req_ndn, &op.oq_search.rs_scope, &op.oq_search.rs_filter,
822 if( rc != LDAP_SUCCESS ) goto CONCLUDED;
824 /* Massive shortcut: search scope == base */
825 switch ( op.oq_search.rs_scope ) {
826 case LDAP_SCOPE_BASE:
827 case LDAP_X_SCOPE_EXACT:
829 if ( dn_match( &op.o_req_ndn, assertDN ) ) {
832 rc = LDAP_INAPPROPRIATE_AUTH;
836 case LDAP_X_SCOPE_CHILDREN:
837 case LDAP_X_SCOPE_SUBTREE:
838 case LDAP_X_SCOPE_ONELEVEL:
840 int d = assertDN->bv_len - op.o_req_ndn.bv_len;
842 rc = LDAP_INAPPROPRIATE_AUTH;
844 if ( d == 0 && op.oq_search.rs_scope == LDAP_X_SCOPE_SUBTREE ) {
847 } else if ( d > 0 ) {
850 /* leave room for at least one char of attributeType,
851 * one for '=' and one for ',' */
852 if ( d < STRLENOF( "x=,") ) {
856 bv.bv_len = op.o_req_ndn.bv_len;
857 bv.bv_val = assertDN->bv_val + d;
859 if ( bv.bv_val[ -1 ] == ',' && dn_match( &op.o_req_ndn, &bv ) ) {
860 switch ( op.oq_search.rs_scope ) {
861 case LDAP_X_SCOPE_SUBTREE:
862 case LDAP_X_SCOPE_CHILDREN:
866 case LDAP_X_SCOPE_ONELEVEL:
870 dnParent( assertDN, &pdn );
871 /* the common portion of the DN
872 * already matches, so only check
873 * if parent DN of assertedDN
874 * is all the pattern */
875 if ( pdn.bv_len == op.o_req_ndn.bv_len ) {
881 /* at present, impossible */
889 case LDAP_X_SCOPE_REGEX:
890 rc = regcomp(®, op.o_req_ndn.bv_val,
891 REG_EXTENDED|REG_ICASE|REG_NOSUB);
893 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
899 rc = LDAP_INAPPROPRIATE_AUTH;
903 case LDAP_X_SCOPE_GROUP: {
906 /* Now filterstr looks like "(&(objectClass=<group_oc>)(<member_at>="
907 * we need to append the <assertDN> so that the <group_dn> is searched
908 * with scope "base", and the filter ensures that <assertDN> is
909 * member of the group */
910 tmp = ch_realloc( op.ors_filterstr.bv_val,
911 op.ors_filterstr.bv_len + assertDN->bv_len + STRLENOF( "))" ) + 1 );
916 op.ors_filterstr.bv_val = tmp;
918 tmp = lutil_strcopy( &tmp[ op.ors_filterstr.bv_len ], assertDN->bv_val );
919 tmp = lutil_strcopy( tmp, "))" );
921 /* pass opx because str2filter_x may (and does) use o_tmpmfuncs */
922 op.ors_filter = str2filter_x( opx, op.ors_filterstr.bv_val );
923 if ( op.ors_filter == NULL ) {
924 rc = LDAP_PROTOCOL_ERROR;
927 op.ors_scope = LDAP_SCOPE_BASE;
929 /* hijack match DN: use that of the group instead of the assertDN;
930 * assertDN is now in the filter */
931 sm.dn = &op.o_req_ndn;
941 /* Must run an internal search. */
942 if ( op.oq_search.rs_filter == NULL ) {
943 rc = LDAP_FILTER_ERROR;
948 LDAP_LOG( TRANSPORT, DETAIL1,
949 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
950 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
952 Debug( LDAP_DEBUG_TRACE,
953 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
954 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
957 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
958 if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
959 rc = LDAP_INAPPROPRIATE_AUTH;
963 op.o_tag = LDAP_REQ_SEARCH;
964 op.o_protocol = LDAP_VERSION3;
967 op.o_time = slap_get_time();
968 op.o_do_not_cache = 1;
969 op.o_is_auth_check = 1;
970 op.o_threadctx = opx->o_threadctx;
971 op.o_tmpmemctx = opx->o_tmpmemctx;
972 op.o_tmpmfuncs = opx->o_tmpmfuncs;
976 op.o_conn = opx->o_conn;
977 op.o_connid = opx->o_connid;
978 /* use req_ndn as req_dn instead of non-pretty base of uri */
979 if( !BER_BVISNULL( &op.o_req_dn ) ) ch_free( op.o_req_dn.bv_val );
980 ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
981 op.oq_search.rs_slimit = 1;
982 op.oq_search.rs_tlimit = -1;
984 op.o_bd->be_search( &op, &rs );
989 rc = LDAP_INAPPROPRIATE_AUTH;
993 if( !BER_BVISNULL( &op.o_req_dn ) ) slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
994 if( !BER_BVISNULL( &op.o_req_ndn ) ) slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
995 if( op.oq_search.rs_filter ) filter_free_x( opx, op.oq_search.rs_filter );
996 if( !BER_BVISNULL( &op.ors_filterstr ) ) ch_free( op.ors_filterstr.bv_val );
999 LDAP_LOG( TRANSPORT, ENTRY,
1000 "slap_sasl_match: comparison returned %d\n", rc, 0, 0 );
1002 Debug( LDAP_DEBUG_TRACE,
1003 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
1011 * This function answers the question, "Can this ID authorize to that ID?",
1012 * based on authorization rules. The rules are stored in the *searchDN, in the
1013 * attribute named by *attr. If any of those rules map to the *assertDN, the
1014 * authorization is approved.
1016 * The DNs should not have the dn: prefix
1019 slap_sasl_check_authz( Operation *op,
1020 struct berval *searchDN,
1021 struct berval *assertDN,
1022 AttributeDescription *ad,
1023 struct berval *authc )
1026 BerVarray vals=NULL;
1029 LDAP_LOG( TRANSPORT, ENTRY,
1030 "slap_sasl_check_authz: does %s match %s rule in %s?\n",
1031 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
1033 Debug( LDAP_DEBUG_TRACE,
1034 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
1035 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
1038 rc = backend_attribute( op, NULL,
1039 searchDN, ad, &vals );
1040 if( rc != LDAP_SUCCESS ) goto COMPLETE;
1042 /* Check if the *assertDN matches any **vals */
1043 if( vals != NULL ) {
1044 for( i=0; !BER_BVISNULL( &vals[i] ); i++ ) {
1045 rc = slap_sasl_match( op, &vals[i], assertDN, authc );
1046 if ( rc == LDAP_SUCCESS ) goto COMPLETE;
1049 rc = LDAP_INAPPROPRIATE_AUTH;
1052 if( vals ) ber_bvarray_free_x( vals, op->o_tmpmemctx );
1055 LDAP_LOG( TRANSPORT, RESULTS,
1056 "slap_sasl_check_authz: %s check returning %s\n",
1057 ad->ad_cname.bv_val, rc, 0 );
1059 Debug( LDAP_DEBUG_TRACE,
1060 "<==slap_sasl_check_authz: %s check returning %d\n",
1061 ad->ad_cname.bv_val, rc, 0);
1068 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
1069 * return the LDAP DN to which it matches. The SASL regexp rules in the config
1070 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
1071 * search with scope=base), just return the URI (or its searchbase). Otherwise
1072 * an internal search must be done, and if that search returns exactly one
1073 * entry, return the DN of that one entry.
1075 void slap_sasl2dn( Operation *opx,
1076 struct berval *saslname, struct berval *sasldn, int flags )
1079 slap_callback cb = { NULL, sasl_sc_sasl2dn, NULL, NULL };
1081 SlapReply rs = {REP_RESULT};
1082 struct berval regout = BER_BVNULL;
1085 LDAP_LOG( TRANSPORT, ENTRY,
1086 "slap_sasl2dn: converting SASL name %s to DN.\n",
1087 saslname->bv_val, 0, 0 );
1089 Debug( LDAP_DEBUG_TRACE, "==>slap_sasl2dn: "
1090 "converting SASL name %s to a DN\n",
1091 saslname->bv_val, 0,0 );
1094 sasldn->bv_val = NULL;
1096 cb.sc_private = sasldn;
1098 /* Convert the SASL name into a minimal URI */
1099 if( !slap_sasl_regexp( saslname, ®out, flags, opx->o_tmpmemctx ) ) {
1103 rc = slap_parseURI( opx, ®out, &op.o_req_dn,
1104 &op.o_req_ndn, &op.oq_search.rs_scope, &op.oq_search.rs_filter,
1105 &op.ors_filterstr );
1106 if ( !BER_BVISNULL( ®out ) ) slap_sl_free( regout.bv_val, opx->o_tmpmemctx );
1107 if ( rc != LDAP_SUCCESS ) {
1111 /* Must do an internal search */
1112 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
1114 /* Massive shortcut: search scope == base */
1115 switch ( op.oq_search.rs_scope ) {
1116 case LDAP_SCOPE_BASE:
1117 case LDAP_X_SCOPE_EXACT:
1118 *sasldn = op.o_req_ndn;
1119 BER_BVZERO( &op.o_req_ndn );
1120 /* intentionally continue to next case */
1122 case LDAP_X_SCOPE_REGEX:
1123 case LDAP_X_SCOPE_SUBTREE:
1124 case LDAP_X_SCOPE_CHILDREN:
1125 case LDAP_X_SCOPE_ONELEVEL:
1126 case LDAP_X_SCOPE_GROUP:
1127 /* correctly parsed, but illegal */
1130 case LDAP_SCOPE_ONELEVEL:
1131 case LDAP_SCOPE_SUBTREE:
1132 #ifdef LDAP_SCOPE_SUBORDINATE
1133 case LDAP_SCOPE_SUBORDINATE:
1139 /* catch unhandled cases (there shouldn't be) */
1144 LDAP_LOG( TRANSPORT, DETAIL1,
1145 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
1146 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
1148 Debug( LDAP_DEBUG_TRACE,
1149 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
1150 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
1153 if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
1157 op.o_conn = opx->o_conn;
1158 op.o_connid = opx->o_connid;
1159 op.o_tag = LDAP_REQ_SEARCH;
1160 op.o_protocol = LDAP_VERSION3;
1161 op.o_ndn = opx->o_conn->c_ndn;
1162 op.o_callback = &cb;
1163 op.o_time = slap_get_time();
1164 op.o_do_not_cache = 1;
1165 op.o_is_auth_check = 1;
1166 op.o_threadctx = opx->o_threadctx;
1167 op.o_tmpmemctx = opx->o_tmpmemctx;
1168 op.o_tmpmfuncs = opx->o_tmpmfuncs;
1170 op.o_pb = opx->o_pb;
1172 op.oq_search.rs_deref = LDAP_DEREF_NEVER;
1173 op.oq_search.rs_slimit = 1;
1174 op.oq_search.rs_tlimit = -1;
1175 op.oq_search.rs_attrsonly = 1;
1176 /* use req_ndn as req_dn instead of non-pretty base of uri */
1177 if( !BER_BVISNULL( &op.o_req_dn ) ) ch_free( op.o_req_dn.bv_val );
1178 ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1180 op.o_bd->be_search( &op, &rs );
1183 if( !BER_BVISEMPTY( sasldn ) ) {
1184 opx->o_conn->c_authz_backend = op.o_bd;
1186 if( !BER_BVISNULL( &op.o_req_dn ) ) slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1187 if( !BER_BVISNULL( &op.o_req_ndn ) ) slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1188 if( op.oq_search.rs_filter ) filter_free_x( opx, op.oq_search.rs_filter );
1189 if( !BER_BVISNULL( &op.ors_filterstr ) ) ch_free( op.ors_filterstr.bv_val );
1192 LDAP_LOG( TRANSPORT, ENTRY,
1193 "slap_sasl2dn: Converted SASL name to %s\n",
1194 !BER_BVISEMPTY( sasldn ) ? sasldn->bv_val : "<nothing>", 0, 0 );
1196 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
1197 !BER_BVISEMPTY( sasldn ) ? sasldn->bv_val : "<nothing>", 0, 0 );
1204 /* Check if a bind can SASL authorize to another identity.
1205 * The DNs should not have the dn: prefix
1208 int slap_sasl_authorized( Operation *op,
1209 struct berval *authcDN, struct berval *authzDN )
1211 int rc = LDAP_INAPPROPRIATE_AUTH;
1213 /* User binding as anonymous */
1214 if ( authzDN == NULL ) {
1220 LDAP_LOG( TRANSPORT, ENTRY,
1221 "slap_sasl_authorized: can %s become %s?\n",
1222 authcDN->bv_val, authzDN->bv_val, 0 );
1224 Debug( LDAP_DEBUG_TRACE,
1225 "==>slap_sasl_authorized: can %s become %s?\n",
1226 authcDN->bv_val, authzDN->bv_val, 0 );
1229 /* If person is authorizing to self, succeed */
1230 if ( dn_match( authcDN, authzDN ) ) {
1235 /* Allow the manager to authorize as any DN. */
1236 if( op->o_conn->c_authz_backend &&
1237 be_isroot_dn( op->o_conn->c_authz_backend, authcDN ))
1243 /* Check source rules */
1244 if( authz_policy & SASL_AUTHZ_TO ) {
1245 rc = slap_sasl_check_authz( op, authcDN, authzDN,
1246 slap_schema.si_ad_saslAuthzTo, authcDN );
1247 if( rc == LDAP_SUCCESS && !(authz_policy & SASL_AUTHZ_AND) ) {
1252 /* Check destination rules */
1253 if( authz_policy & SASL_AUTHZ_FROM ) {
1254 rc = slap_sasl_check_authz( op, authzDN, authcDN,
1255 slap_schema.si_ad_saslAuthzFrom, authcDN );
1256 if( rc == LDAP_SUCCESS ) {
1261 rc = LDAP_INAPPROPRIATE_AUTH;
1266 LDAP_LOG( TRANSPORT, RESULTS, "slap_sasl_authorized: return %d\n", rc,0,0 );
1268 Debug( LDAP_DEBUG_TRACE,
1269 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );