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;
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 ) );
209 LDAP_LOG( TRANSPORT, ENTRY,
210 "slap_parseURI: parsing %s\n", uri->bv_val, 0, 0 );
212 Debug( LDAP_DEBUG_TRACE,
213 "slap_parseURI: parsing %s\n", uri->bv_val, 0, 0 );
216 rc = LDAP_PROTOCOL_ERROR;
218 * dn[.<dnstyle>]:<dnpattern>
219 * <dnstyle> ::= {exact|regex|children|subtree|onelevel}
221 * <dnstyle> defaults to "exact"
222 * if <dnstyle> is not "regex", <dnpattern> must pass DN normalization
224 if ( !strncasecmp( uri->bv_val, "dn", STRLENOF( "dn" ) ) ) {
225 bv.bv_val = uri->bv_val + STRLENOF( "dn" );
227 if ( bv.bv_val[ 0 ] == '.' ) {
230 if ( !strncasecmp( bv.bv_val, "exact:", STRLENOF( "exact:" ) ) ) {
231 bv.bv_val += STRLENOF( "exact:" );
232 *scope = LDAP_X_SCOPE_EXACT;
234 } else if ( !strncasecmp( bv.bv_val, "regex:", STRLENOF( "regex:" ) ) ) {
235 bv.bv_val += STRLENOF( "regex:" );
236 *scope = LDAP_X_SCOPE_REGEX;
238 } else if ( !strncasecmp( bv.bv_val, "children:", STRLENOF( "children:" ) ) ) {
239 bv.bv_val += STRLENOF( "children:" );
240 *scope = LDAP_X_SCOPE_CHILDREN;
242 } else if ( !strncasecmp( bv.bv_val, "subtree:", STRLENOF( "subtree:" ) ) ) {
243 bv.bv_val += STRLENOF( "subtree:" );
244 *scope = LDAP_X_SCOPE_SUBTREE;
246 } else if ( !strncasecmp( bv.bv_val, "onelevel:", STRLENOF( "onelevel:" ) ) ) {
247 bv.bv_val += STRLENOF( "onelevel:" );
248 *scope = LDAP_X_SCOPE_ONELEVEL;
251 return LDAP_PROTOCOL_ERROR;
255 if ( bv.bv_val[ 0 ] != ':' )
256 return LDAP_PROTOCOL_ERROR;
257 *scope = LDAP_X_SCOPE_EXACT;
261 bv.bv_val += strspn( bv.bv_val, " " );
262 /* jump here in case no type specification was present
263 * and uri was not an URI... HEADS-UP: assuming EXACT */
264 is_dn: bv.bv_len = uri->bv_len - (bv.bv_val - uri->bv_val);
267 case LDAP_X_SCOPE_EXACT:
268 case LDAP_X_SCOPE_CHILDREN:
269 case LDAP_X_SCOPE_SUBTREE:
270 case LDAP_X_SCOPE_ONELEVEL:
271 rc = dnNormalize( 0, NULL, NULL, &bv, nbase, op->o_tmpmemctx );
272 if( rc != LDAP_SUCCESS ) {
277 case LDAP_X_SCOPE_REGEX:
278 ber_dupbv_x( nbase, &bv, op->o_tmpmemctx );
292 } else if ( ( uri->bv_val[ 0 ] == 'u' || uri->bv_val[ 0 ] == 'U' )
293 && ( uri->bv_val[ 1 ] == ':'
294 || uri->bv_val[ 1 ] == '/'
295 || uri->bv_val[ 1 ] == '.' ) )
297 Connection c = *op->o_conn;
298 char buf[ SLAP_LDAPDN_MAXLEN ];
304 if ( sizeof( buf ) <= uri->bv_len ) {
305 return LDAP_INVALID_SYNTAX;
308 id.bv_len = uri->bv_len;
310 strncpy( buf, uri->bv_val, sizeof( buf ) );
312 rc = slap_parse_user( &id, &user, &realm, &mech );
313 if ( rc != LDAP_SUCCESS ) {
317 if ( !BER_BVISNULL( &mech ) ) {
318 c.c_sasl_bind_mech = mech;
320 BER_BVSTR( &c.c_sasl_bind_mech, "AUTHZ" );
323 rc = slap_sasl_getdn( &c, op, &user,
324 realm.bv_val, nbase, SLAP_GETDN_AUTHZID );
326 if ( rc == LDAP_SUCCESS ) {
327 *scope = LDAP_X_SCOPE_EXACT;
333 * group[/<groupoc>[/<groupat>]]:<groupdn>
335 * groupoc defaults to "groupOfNames"
336 * groupat defaults to "member"
338 * <groupdn> must pass DN normalization
340 } else if ( strncasecmp( uri->bv_val, "group", STRLENOF( "group" ) ) == 0 )
342 struct berval group_dn = BER_BVNULL,
343 group_oc = BER_BVNULL,
344 member_at = BER_BVNULL;
347 bv.bv_val = uri->bv_val + STRLENOF( "group" );
348 group_dn.bv_val = strchr( bv.bv_val, ':' );
349 if ( group_dn.bv_val == NULL ) {
350 /* last chance: assume it's a(n exact) DN ... */
351 bv.bv_val = uri->bv_val;
352 *scope = LDAP_X_SCOPE_EXACT;
356 if ( bv.bv_val[ 0 ] == '/' ) {
357 group_oc.bv_val = &bv.bv_val[ 1 ];
359 member_at.bv_val = strchr( group_oc.bv_val, '/' );
360 if ( member_at.bv_val ) {
361 group_oc.bv_len = member_at.bv_val - group_oc.bv_val;
363 member_at.bv_len = group_dn.bv_val - member_at.bv_val;
366 group_oc.bv_len = group_dn.bv_val - group_oc.bv_val;
367 BER_BVSTR( &member_at, "member" );
371 BER_BVSTR( &group_oc, "groupOfNames" );
374 group_dn.bv_len = uri->bv_len - ( group_dn.bv_val - uri->bv_val );
376 rc = dnNormalize( 0, NULL, NULL, &group_dn, nbase, op->o_tmpmemctx );
377 if ( rc != LDAP_SUCCESS ) {
381 *scope = LDAP_X_SCOPE_GROUP;
383 /* FIXME: caller needs to add value of member attribute
384 * and close brackets twice */
385 fstr->bv_len = STRLENOF( "(&(objectClass=)(=" /* )) */ )
386 + group_oc.bv_len + member_at.bv_len;
387 fstr->bv_val = ch_malloc( fstr->bv_len + 1 );
389 tmp = lutil_strncopy( fstr->bv_val, "(&(objectClass=" /* )) */ ,
390 STRLENOF( "(&(objectClass=" /* )) */ ) );
391 tmp = lutil_strncopy( tmp, group_oc.bv_val, group_oc.bv_len );
392 tmp = lutil_strncopy( tmp, /* ( */ ")(" /* ) */ ,
393 STRLENOF( /* ( */ ")(" /* ) */ ) );
394 tmp = lutil_strncopy( tmp, member_at.bv_val, member_at.bv_len );
395 tmp = lutil_strncopy( tmp, "=", STRLENOF( "=" ) );
401 * ldap:///<base>??<scope>?<filter>
402 * <scope> ::= {base|one|subtree}
404 * <scope> defaults to "base"
405 * <base> must pass DN normalization
406 * <filter> must pass str2filter()
408 rc = ldap_url_parse( uri->bv_val, &ludp );
409 if ( rc == LDAP_URL_ERR_BADSCHEME ) {
411 * last chance: assume it's a(n exact) DN ...
413 * NOTE: must pass DN normalization
415 bv.bv_val = uri->bv_val;
416 *scope = LDAP_X_SCOPE_EXACT;
420 if ( rc != LDAP_URL_SUCCESS ) {
421 return LDAP_PROTOCOL_ERROR;
424 if (( ludp->lud_host && *ludp->lud_host )
425 || ludp->lud_attrs || ludp->lud_exts )
427 /* host part must be empty */
428 /* attrs and extensions parts must be empty */
429 rc = LDAP_PROTOCOL_ERROR;
434 *scope = ludp->lud_scope;
436 /* Grab the filter */
437 if ( ludp->lud_filter ) {
438 *filter = str2filter_x( op, ludp->lud_filter );
439 if ( *filter == NULL ) {
440 rc = LDAP_PROTOCOL_ERROR;
443 ber_str2bv( ludp->lud_filter, 0, 0, fstr );
446 /* Grab the searchbase */
447 ber_str2bv( ludp->lud_dn, 0, 0, base );
448 rc = dnNormalize( 0, NULL, NULL, base, nbase, op->o_tmpmemctx );
451 if( rc != LDAP_SUCCESS ) {
452 if( *filter ) filter_free_x( op, *filter );
456 /* Don't free these, return them to caller */
457 ludp->lud_filter = NULL;
461 ldap_free_urldesc( ludp );
465 static int slap_sasl_rx_off(char *rep, int *off)
470 /* Precompile replace pattern. Find the $<n> placeholders */
473 for ( c = rep; *c; c++ ) {
474 if ( *c == '\\' && c[1] ) {
479 if ( n == SASLREGEX_REPLACE ) {
481 LDAP_LOG( TRANSPORT, ERR,
482 "slap_sasl_rx_off: \"%s\" has too many $n "
483 "placeholders (max %d)\n", rep, SASLREGEX_REPLACE, 0 );
485 Debug( LDAP_DEBUG_ANY,
486 "SASL replace pattern %s has too many $n "
487 "placeholders (max %d)\n",
488 rep, SASLREGEX_REPLACE, 0 );
491 return( LDAP_OTHER );
498 /* Final placeholder, after the last $n */
502 return( LDAP_SUCCESS );
505 #ifdef SLAP_AUTH_REWRITE
506 int slap_sasl_rewrite_config(
516 /* init at first call */
517 if ( sasl_rwinfo == NULL ) {
518 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
521 /* strip "authid-" prefix for parsing */
523 argv[0] += STRLENOF( "authid-" );
524 rc = rewrite_parse( sasl_rwinfo, fname, lineno, argc, argv );
530 int slap_sasl_rewrite_destroy( void )
533 rewrite_info_delete( &sasl_rwinfo );
540 int slap_sasl_regexp_rewrite_config(
545 const char *context )
548 char *newreplace, *p;
549 char *argvRule[] = { "rewriteRule", NULL, NULL, "@", NULL };
551 /* init at first call */
552 if ( sasl_rwinfo == NULL ) {
553 char *argvEngine[] = { "rewriteEngine", "on", NULL };
554 char *argvContext[] = { "rewriteContext", NULL, NULL };
556 /* initialize rewrite engine */
557 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
559 /* switch on rewrite engine */
560 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvEngine );
561 if (rc != LDAP_SUCCESS) {
565 /* create generic authid context */
566 argvContext[1] = AUTHID_CONTEXT;
567 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvContext );
568 if (rc != LDAP_SUCCESS) {
573 newreplace = ch_strdup( replace );
575 for (p = strchr( newreplace, '$' ); p; p = strchr( p + 1, '$' ) ) {
576 if ( isdigit( p[1] ) ) {
583 argvRule[1] = (char *)match;
584 argvRule[2] = newreplace;
585 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 4, argvRule );
586 ch_free( newreplace );
590 #endif /* SLAP_AUTH_REWRITE */
592 int slap_sasl_regexp_config( const char *match, const char *replace )
594 #ifdef SLAP_AUTH_REWRITE
595 return slap_sasl_regexp_rewrite_config( "sasl-regexp", 0,
596 match, replace, AUTHID_CONTEXT );
597 #else /* ! SLAP_AUTH_REWRITE */
601 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
602 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
604 reg = &SaslRegexp[nSaslRegexp];
606 reg->sr_match = ch_strdup( match );
607 reg->sr_replace = ch_strdup( replace );
609 /* Precompile matching pattern */
610 rc = regcomp( ®->sr_workspace, reg->sr_match, REG_EXTENDED|REG_ICASE );
613 LDAP_LOG( TRANSPORT, ERR,
614 "slap_sasl_regexp_config: \"%s\" could not be compiled.\n",
615 reg->sr_match, 0, 0 );
617 Debug( LDAP_DEBUG_ANY,
618 "SASL match pattern %s could not be compiled by regexp engine\n",
619 reg->sr_match, 0, 0 );
622 return( LDAP_OTHER );
625 rc = slap_sasl_rx_off( reg->sr_replace, reg->sr_offset );
626 if ( rc != LDAP_SUCCESS ) return rc;
629 return( LDAP_SUCCESS );
630 #endif /* ! SLAP_AUTH_REWRITE */
633 /* Perform replacement on regexp matches */
634 static void slap_sasl_rx_exp(
638 const char *saslname,
642 int i, n, len, insert;
644 /* Get the total length of the final URI */
648 while( off[n] >= 0 ) {
649 /* Len of next section from replacement string (x,y,z above) */
650 len += off[n] - off[n-1] - 2;
654 /* Len of string from saslname that matched next $i (b,d above) */
655 i = rep[ off[n] + 1 ] - '0';
656 len += str[i].rm_eo - str[i].rm_so;
659 out->bv_val = slap_sl_malloc( len + 1, ctx );
662 /* Fill in URI with replace string, replacing $i as we go */
665 while( off[n] >= 0) {
666 /* Paste in next section from replacement string (x,y,z above) */
667 len = off[n] - off[n-1] - 2;
668 strncpy( out->bv_val+insert, rep + off[n-1] + 2, len);
673 /* Paste in string from saslname that matched next $i (b,d above) */
674 i = rep[ off[n] + 1 ] - '0';
675 len = str[i].rm_eo - str[i].rm_so;
676 strncpy( out->bv_val+insert, saslname + str[i].rm_so, len );
682 out->bv_val[insert] = '\0';
685 /* Take the passed in SASL name and attempt to convert it into an
686 LDAP URI to find the matching LDAP entry, using the pattern matching
687 strings given in the saslregexp config file directive(s) */
689 static int slap_authz_regexp( struct berval *in, struct berval *out,
690 int flags, void *ctx )
692 #ifdef SLAP_AUTH_REWRITE
693 const char *context = AUTHID_CONTEXT;
695 if ( sasl_rwinfo == NULL || BER_BVISNULL( in ) ) {
699 /* FIXME: if aware of authc/authz mapping,
700 * we could use different contexts ... */
701 switch ( rewrite_session( sasl_rwinfo, context, in->bv_val, NULL,
704 case REWRITE_REGEXEC_OK:
705 if ( !BER_BVISNULL( out ) ) {
706 char *val = out->bv_val;
707 ber_str2bv_x( val, 0, 1, out, ctx );
710 ber_dupbv_x( out, in, ctx );
713 LDAP_LOG( BACK_LDAP, DETAIL1,
714 "[rw] %s: \"%s\" -> \"%s\"\n",
715 context, in->bv_val, out->bv_val );
716 #else /* !NEW_LOGGING */
717 Debug( LDAP_DEBUG_ARGS,
718 "[rw] %s: \"%s\" -> \"%s\"\n",
719 context, in->bv_val, out->bv_val );
720 #endif /* !NEW_LOGGING */
723 case REWRITE_REGEXEC_UNWILLING:
724 case REWRITE_REGEXEC_ERR:
729 #else /* ! SLAP_AUTH_REWRITE */
730 char *saslname = in->bv_val;
732 regmatch_t sr_strings[SASLREGEX_REPLACE]; /* strings matching $1,$2 ... */
735 memset( out, 0, sizeof( *out ) );
738 LDAP_LOG( TRANSPORT, ENTRY,
739 "slap_authz_regexp: converting SASL name %s\n", saslname, 0, 0 );
741 Debug( LDAP_DEBUG_TRACE, "slap_authz_regexp: converting SASL name %s\n",
745 if (( saslname == NULL ) || ( nSaslRegexp == 0 )) {
749 /* Match the normalized SASL name to the saslregexp patterns */
750 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
751 if ( regexec( ®->sr_workspace, saslname, SASLREGEX_REPLACE,
752 sr_strings, 0) == 0 )
756 if( i >= nSaslRegexp ) return( 0 );
759 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
760 * replace pattern of the form "x$1y$2z". The returned string needs
761 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
763 slap_sasl_rx_exp( reg->sr_replace, reg->sr_offset,
764 sr_strings, saslname, out, ctx );
767 LDAP_LOG( TRANSPORT, ENTRY,
768 "slap_authz_regexp: converted SASL name to %s\n",
769 BER_BVISEMPTY( out ) ? "" : out->bv_val, 0, 0 );
771 Debug( LDAP_DEBUG_TRACE,
772 "slap_authz_regexp: converted SASL name to %s\n",
773 BER_BVISEMPTY( out ) ? "" : out->bv_val, 0, 0 );
777 #endif /* ! SLAP_AUTH_REWRITE */
780 /* This callback actually does some work...*/
781 static int sasl_sc_sasl2dn( Operation *o, SlapReply *rs )
783 struct berval *ndn = o->o_callback->sc_private;
785 if (rs->sr_type != REP_SEARCH) return 0;
787 /* We only want to be called once */
788 if ( !BER_BVISNULL( ndn ) ) {
789 o->o_tmpfree(ndn->bv_val, o->o_tmpmemctx);
793 LDAP_LOG( TRANSPORT, DETAIL1,
794 "slap_sc_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
796 Debug( LDAP_DEBUG_TRACE,
797 "slap_sc_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
802 ber_dupbv_x(ndn, &rs->sr_entry->e_nname, o->o_tmpmemctx);
807 typedef struct smatch_info {
812 static int sasl_sc_smatch( Operation *o, SlapReply *rs )
814 smatch_info *sm = o->o_callback->sc_private;
816 if ( rs->sr_type != REP_SEARCH ) {
817 if ( rs->sr_err != LDAP_SUCCESS ) {
823 if ( sm->match == 1 ) {
828 if (dn_match(sm->dn, &rs->sr_entry->e_nname)) {
839 slap_sasl_matches( Operation *op, BerVarray rules,
840 struct berval *assertDN, struct berval *authc )
842 int rc = LDAP_INAPPROPRIATE_AUTH;
844 if ( rules != NULL ) {
847 for( i = 0; !BER_BVISNULL( &rules[i] ); i++ ) {
848 rc = slap_sasl_match( op, &rules[i], assertDN, authc );
849 if ( rc == LDAP_SUCCESS ) break;
857 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
858 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
859 * the rule must be used as an internal search for entries. If that search
860 * returns the *assertDN entry, the match is successful.
862 * The assertDN should not have the dn: prefix
866 int slap_sasl_match( Operation *opx, struct berval *rule,
867 struct berval *assertDN, struct berval *authc )
872 slap_callback cb = { NULL, sasl_sc_smatch, NULL, NULL };
874 SlapReply rs = {REP_RESULT};
881 LDAP_LOG( TRANSPORT, ENTRY,
882 "slap_sasl_match: comparing DN %s to rule %s\n",
883 assertDN->bv_val, rule->bv_val,0 );
885 Debug( LDAP_DEBUG_TRACE,
886 "===>slap_sasl_match: comparing DN %s to rule %s\n",
887 assertDN->bv_val, rule->bv_val, 0 );
890 rc = slap_parseURI( opx, rule, &op.o_req_dn,
891 &op.o_req_ndn, &op.oq_search.rs_scope, &op.oq_search.rs_filter,
893 if( rc != LDAP_SUCCESS ) goto CONCLUDED;
895 /* Massive shortcut: search scope == base */
896 switch ( op.oq_search.rs_scope ) {
897 case LDAP_SCOPE_BASE:
898 case LDAP_X_SCOPE_EXACT:
900 if ( dn_match( &op.o_req_ndn, assertDN ) ) {
903 rc = LDAP_INAPPROPRIATE_AUTH;
907 case LDAP_X_SCOPE_CHILDREN:
908 case LDAP_X_SCOPE_SUBTREE:
909 case LDAP_X_SCOPE_ONELEVEL:
911 int d = assertDN->bv_len - op.o_req_ndn.bv_len;
913 rc = LDAP_INAPPROPRIATE_AUTH;
915 if ( d == 0 && op.oq_search.rs_scope == LDAP_X_SCOPE_SUBTREE ) {
918 } else if ( d > 0 ) {
921 /* leave room for at least one char of attributeType,
922 * one for '=' and one for ',' */
923 if ( d < STRLENOF( "x=,") ) {
927 bv.bv_len = op.o_req_ndn.bv_len;
928 bv.bv_val = assertDN->bv_val + d;
930 if ( bv.bv_val[ -1 ] == ',' && dn_match( &op.o_req_ndn, &bv ) ) {
931 switch ( op.oq_search.rs_scope ) {
932 case LDAP_X_SCOPE_SUBTREE:
933 case LDAP_X_SCOPE_CHILDREN:
937 case LDAP_X_SCOPE_ONELEVEL:
941 dnParent( assertDN, &pdn );
942 /* the common portion of the DN
943 * already matches, so only check
944 * if parent DN of assertedDN
945 * is all the pattern */
946 if ( pdn.bv_len == op.o_req_ndn.bv_len ) {
952 /* at present, impossible */
960 case LDAP_X_SCOPE_REGEX:
961 rc = regcomp(®, op.o_req_ndn.bv_val,
962 REG_EXTENDED|REG_ICASE|REG_NOSUB);
964 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
970 rc = LDAP_INAPPROPRIATE_AUTH;
974 case LDAP_X_SCOPE_GROUP: {
977 /* Now filterstr looks like "(&(objectClass=<group_oc>)(<member_at>="
978 * we need to append the <assertDN> so that the <group_dn> is searched
979 * with scope "base", and the filter ensures that <assertDN> is
980 * member of the group */
981 tmp = ch_realloc( op.ors_filterstr.bv_val,
982 op.ors_filterstr.bv_len + assertDN->bv_len + STRLENOF( /* (( */ "))" ) + 1 );
987 op.ors_filterstr.bv_val = tmp;
989 tmp = lutil_strcopy( &tmp[ op.ors_filterstr.bv_len ], assertDN->bv_val );
990 tmp = lutil_strcopy( tmp, /* (( */ "))" );
992 /* pass opx because str2filter_x may (and does) use o_tmpmfuncs */
993 op.ors_filter = str2filter_x( opx, op.ors_filterstr.bv_val );
994 if ( op.ors_filter == NULL ) {
995 rc = LDAP_PROTOCOL_ERROR;
998 op.ors_scope = LDAP_SCOPE_BASE;
1000 /* hijack match DN: use that of the group instead of the assertDN;
1001 * assertDN is now in the filter */
1002 sm.dn = &op.o_req_ndn;
1012 /* Must run an internal search. */
1013 if ( op.oq_search.rs_filter == NULL ) {
1014 rc = LDAP_FILTER_ERROR;
1019 LDAP_LOG( TRANSPORT, DETAIL1,
1020 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
1021 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
1023 Debug( LDAP_DEBUG_TRACE,
1024 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
1025 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
1028 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
1029 if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
1030 rc = LDAP_INAPPROPRIATE_AUTH;
1034 op.o_tag = LDAP_REQ_SEARCH;
1035 op.o_protocol = LDAP_VERSION3;
1037 op.o_callback = &cb;
1038 op.o_time = slap_get_time();
1039 op.o_do_not_cache = 1;
1040 op.o_is_auth_check = 1;
1041 op.o_threadctx = opx->o_threadctx;
1042 op.o_tmpmemctx = opx->o_tmpmemctx;
1043 op.o_tmpmfuncs = opx->o_tmpmfuncs;
1045 op.o_pb = opx->o_pb;
1047 op.o_conn = opx->o_conn;
1048 op.o_connid = opx->o_connid;
1049 /* use req_ndn as req_dn instead of non-pretty base of uri */
1050 if( !BER_BVISNULL( &op.o_req_dn ) ) ch_free( op.o_req_dn.bv_val );
1051 ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1052 op.oq_search.rs_slimit = 1;
1053 op.oq_search.rs_tlimit = -1;
1054 op.o_sync_slog_size = -1;
1056 op.o_bd->be_search( &op, &rs );
1058 if (sm.match == 1) {
1061 rc = LDAP_INAPPROPRIATE_AUTH;
1065 if( !BER_BVISNULL( &op.o_req_dn ) ) slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1066 if( !BER_BVISNULL( &op.o_req_ndn ) ) slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1067 if( op.oq_search.rs_filter ) filter_free_x( opx, op.oq_search.rs_filter );
1068 if( !BER_BVISNULL( &op.ors_filterstr ) ) ch_free( op.ors_filterstr.bv_val );
1071 LDAP_LOG( TRANSPORT, ENTRY,
1072 "slap_sasl_match: comparison returned %d\n", rc, 0, 0 );
1074 Debug( LDAP_DEBUG_TRACE,
1075 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
1083 * This function answers the question, "Can this ID authorize to that ID?",
1084 * based on authorization rules. The rules are stored in the *searchDN, in the
1085 * attribute named by *attr. If any of those rules map to the *assertDN, the
1086 * authorization is approved.
1088 * The DNs should not have the dn: prefix
1091 slap_sasl_check_authz( Operation *op,
1092 struct berval *searchDN,
1093 struct berval *assertDN,
1094 AttributeDescription *ad,
1095 struct berval *authc )
1098 BerVarray vals = NULL;
1101 LDAP_LOG( TRANSPORT, ENTRY,
1102 "slap_sasl_check_authz: does %s match %s rule in %s?\n",
1103 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
1105 Debug( LDAP_DEBUG_TRACE,
1106 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
1107 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
1110 rc = backend_attribute( op, NULL, searchDN, ad, &vals );
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 );
1120 LDAP_LOG( TRANSPORT, RESULTS,
1121 "slap_sasl_check_authz: %s check returning %s\n",
1122 ad->ad_cname.bv_val, rc, 0 );
1124 Debug( LDAP_DEBUG_TRACE,
1125 "<==slap_sasl_check_authz: %s check returning %d\n",
1126 ad->ad_cname.bv_val, rc, 0);
1133 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
1134 * return the LDAP DN to which it matches. The SASL regexp rules in the config
1135 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
1136 * search with scope=base), just return the URI (or its searchbase). Otherwise
1137 * an internal search must be done, and if that search returns exactly one
1138 * entry, return the DN of that one entry.
1140 void slap_sasl2dn( Operation *opx,
1141 struct berval *saslname, struct berval *sasldn, int flags )
1144 slap_callback cb = { NULL, sasl_sc_sasl2dn, NULL, NULL };
1146 SlapReply rs = {REP_RESULT};
1147 struct berval regout = BER_BVNULL;
1150 LDAP_LOG( TRANSPORT, ENTRY,
1151 "slap_sasl2dn: converting SASL name %s to DN.\n",
1152 saslname->bv_val, 0, 0 );
1154 Debug( LDAP_DEBUG_TRACE, "==>slap_sasl2dn: "
1155 "converting SASL name %s to a DN\n",
1156 saslname->bv_val, 0,0 );
1159 sasldn->bv_val = NULL;
1161 cb.sc_private = sasldn;
1163 /* Convert the SASL name into a minimal URI */
1164 if( !slap_authz_regexp( saslname, ®out, flags, opx->o_tmpmemctx ) ) {
1168 rc = slap_parseURI( opx, ®out, &op.o_req_dn,
1169 &op.o_req_ndn, &op.oq_search.rs_scope, &op.oq_search.rs_filter,
1170 &op.ors_filterstr );
1171 if ( !BER_BVISNULL( ®out ) ) slap_sl_free( regout.bv_val, opx->o_tmpmemctx );
1172 if ( rc != LDAP_SUCCESS ) {
1176 /* Must do an internal search */
1177 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
1179 /* Massive shortcut: search scope == base */
1180 switch ( op.oq_search.rs_scope ) {
1181 case LDAP_SCOPE_BASE:
1182 case LDAP_X_SCOPE_EXACT:
1183 *sasldn = op.o_req_ndn;
1184 BER_BVZERO( &op.o_req_ndn );
1185 /* intentionally continue to next case */
1187 case LDAP_X_SCOPE_REGEX:
1188 case LDAP_X_SCOPE_SUBTREE:
1189 case LDAP_X_SCOPE_CHILDREN:
1190 case LDAP_X_SCOPE_ONELEVEL:
1191 case LDAP_X_SCOPE_GROUP:
1192 /* correctly parsed, but illegal */
1195 case LDAP_SCOPE_ONELEVEL:
1196 case LDAP_SCOPE_SUBTREE:
1197 #ifdef LDAP_SCOPE_SUBORDINATE
1198 case LDAP_SCOPE_SUBORDINATE:
1204 /* catch unhandled cases (there shouldn't be) */
1209 LDAP_LOG( TRANSPORT, DETAIL1,
1210 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
1211 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
1213 Debug( LDAP_DEBUG_TRACE,
1214 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
1215 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
1218 if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
1222 op.o_conn = opx->o_conn;
1223 op.o_connid = opx->o_connid;
1224 op.o_tag = LDAP_REQ_SEARCH;
1225 op.o_protocol = LDAP_VERSION3;
1226 op.o_ndn = opx->o_conn->c_ndn;
1227 op.o_callback = &cb;
1228 op.o_time = slap_get_time();
1229 op.o_do_not_cache = 1;
1230 op.o_is_auth_check = 1;
1231 op.o_threadctx = opx->o_threadctx;
1232 op.o_tmpmemctx = opx->o_tmpmemctx;
1233 op.o_tmpmfuncs = opx->o_tmpmfuncs;
1235 op.o_pb = opx->o_pb;
1237 op.oq_search.rs_deref = LDAP_DEREF_NEVER;
1238 op.oq_search.rs_slimit = 1;
1239 op.oq_search.rs_tlimit = -1;
1240 op.oq_search.rs_attrsonly = 1;
1241 /* use req_ndn as req_dn instead of non-pretty base of uri */
1242 if( !BER_BVISNULL( &op.o_req_dn ) ) ch_free( op.o_req_dn.bv_val );
1243 ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1245 op.o_bd->be_search( &op, &rs );
1248 if( !BER_BVISEMPTY( sasldn ) ) {
1249 opx->o_conn->c_authz_backend = op.o_bd;
1251 if( !BER_BVISNULL( &op.o_req_dn ) ) slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1252 if( !BER_BVISNULL( &op.o_req_ndn ) ) slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1253 if( op.oq_search.rs_filter ) filter_free_x( opx, op.oq_search.rs_filter );
1254 if( !BER_BVISNULL( &op.ors_filterstr ) ) ch_free( op.ors_filterstr.bv_val );
1257 LDAP_LOG( TRANSPORT, ENTRY,
1258 "slap_sasl2dn: Converted SASL name to %s\n",
1259 !BER_BVISEMPTY( sasldn ) ? sasldn->bv_val : "<nothing>", 0, 0 );
1261 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
1262 !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 ) {
1285 LDAP_LOG( TRANSPORT, ENTRY,
1286 "slap_sasl_authorized: can %s become %s?\n",
1287 authcDN->bv_val, authzDN->bv_val, 0 );
1289 Debug( LDAP_DEBUG_TRACE,
1290 "==>slap_sasl_authorized: can %s become %s?\n",
1291 authcDN->bv_val, authzDN->bv_val, 0 );
1294 /* If person is authorizing to self, succeed */
1295 if ( dn_match( authcDN, authzDN ) ) {
1300 /* Allow the manager to authorize as any DN. */
1301 if( op->o_conn->c_authz_backend &&
1302 be_isroot_dn( op->o_conn->c_authz_backend, authcDN ))
1308 /* Check source rules */
1309 if( authz_policy & SASL_AUTHZ_TO ) {
1310 rc = slap_sasl_check_authz( op, authcDN, authzDN,
1311 slap_schema.si_ad_saslAuthzTo, authcDN );
1312 if( rc == LDAP_SUCCESS && !(authz_policy & SASL_AUTHZ_AND) ) {
1317 /* Check destination rules */
1318 if( authz_policy & SASL_AUTHZ_FROM ) {
1319 rc = slap_sasl_check_authz( op, authzDN, authcDN,
1320 slap_schema.si_ad_saslAuthzFrom, authcDN );
1321 if( rc == LDAP_SUCCESS ) {
1326 rc = LDAP_INAPPROPRIATE_AUTH;
1331 LDAP_LOG( TRANSPORT, RESULTS, "slap_sasl_authorized: return %d\n", rc,0,0 );
1333 Debug( LDAP_DEBUG_TRACE,
1334 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );