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;
214 * dn[.<dnstyle>]:<dnpattern>
215 * <dnstyle> ::= {exact|regex|children|subtree|onelevel}
217 * <dnstyle> defaults to "exact"
218 * if <dnstyle> is not "regex", <dnpattern> must pass DN normalization
220 if ( !strncasecmp( uri->bv_val, "dn", STRLENOF( "dn" ) ) ) {
221 bv.bv_val = uri->bv_val + STRLENOF( "dn" );
223 if ( bv.bv_val[ 0 ] == '.' ) {
226 if ( !strncasecmp( bv.bv_val, "exact:", STRLENOF( "exact:" ) ) ) {
227 bv.bv_val += STRLENOF( "exact:" );
228 *scope = LDAP_X_SCOPE_EXACT;
230 } else if ( !strncasecmp( bv.bv_val, "regex:", STRLENOF( "regex:" ) ) ) {
231 bv.bv_val += STRLENOF( "regex:" );
232 *scope = LDAP_X_SCOPE_REGEX;
234 } else if ( !strncasecmp( bv.bv_val, "children:", STRLENOF( "children:" ) ) ) {
235 bv.bv_val += STRLENOF( "children:" );
236 *scope = LDAP_X_SCOPE_CHILDREN;
238 } else if ( !strncasecmp( bv.bv_val, "subtree:", STRLENOF( "subtree:" ) ) ) {
239 bv.bv_val += STRLENOF( "subtree:" );
240 *scope = LDAP_X_SCOPE_SUBTREE;
242 } else if ( !strncasecmp( bv.bv_val, "onelevel:", STRLENOF( "onelevel:" ) ) ) {
243 bv.bv_val += STRLENOF( "onelevel:" );
244 *scope = LDAP_X_SCOPE_ONELEVEL;
247 return LDAP_PROTOCOL_ERROR;
251 if ( bv.bv_val[ 0 ] != ':' )
252 return LDAP_PROTOCOL_ERROR;
256 bv.bv_val += strspn( bv.bv_val, " " );
257 /* jump here in case no type specification was present
258 * and uri was not an URI... HEADS-UP: assuming EXACT */
259 is_dn: bv.bv_len = uri->bv_len - (bv.bv_val - uri->bv_val);
262 case LDAP_X_SCOPE_EXACT:
263 case LDAP_X_SCOPE_CHILDREN:
264 case LDAP_X_SCOPE_SUBTREE:
265 case LDAP_X_SCOPE_ONELEVEL:
266 rc = dnNormalize( 0, NULL, NULL, &bv, nbase, op->o_tmpmemctx );
267 if( rc != LDAP_SUCCESS ) {
272 case LDAP_X_SCOPE_REGEX:
273 ber_dupbv_x( nbase, &bv, op->o_tmpmemctx );
287 } else if ( ( uri->bv_val[ 0 ] == 'u' || uri->bv_val[ 0 ] == 'U' )
288 && ( uri->bv_val[ 1 ] == ':'
289 || uri->bv_val[ 1 ] == '/'
290 || uri->bv_val[ 1 ] == '.' ) )
292 Connection c = *op->o_conn;
293 char buf[ SLAP_LDAPDN_MAXLEN ];
299 if ( sizeof( buf ) <= uri->bv_len ) {
300 return LDAP_INVALID_SYNTAX;
303 id.bv_len = uri->bv_len;
305 strncpy( buf, uri->bv_val, sizeof( buf ) );
307 rc = slap_parse_user( &id, &user, &realm, &mech );
308 if ( rc != LDAP_SUCCESS ) {
312 if ( !BER_BVISNULL( &mech ) ) {
313 c.c_sasl_bind_mech = mech;
315 BER_BVSTR( &c.c_sasl_bind_mech, "AUTHZ" );
318 rc = slap_sasl_getdn( &c, op, &user,
319 realm.bv_val, nbase, SLAP_GETDN_AUTHZID );
321 if ( rc == LDAP_SUCCESS ) {
322 *scope = LDAP_X_SCOPE_EXACT;
328 * group[/<groupoc>[/<groupat>]]:<groupdn>
330 * groupoc defaults to "groupOfNames"
331 * groupat defaults to "member"
333 * <groupdn> must pass DN normalization
335 } else if ( strncasecmp( uri->bv_val, "group", STRLENOF( "group" ) ) == 0 )
337 struct berval group_dn = BER_BVNULL,
338 group_oc = BER_BVNULL,
339 member_at = BER_BVNULL;
342 bv.bv_val = uri->bv_val + STRLENOF( "group" );
343 group_dn.bv_val = strchr( bv.bv_val, ':' );
344 if ( group_dn.bv_val == NULL ) {
345 /* last chance: assume it's a(n exact) DN ... */
346 bv.bv_val = uri->bv_val;
347 *scope = LDAP_X_SCOPE_EXACT;
351 if ( bv.bv_val[ 0 ] == '/' ) {
352 group_oc.bv_val = &bv.bv_val[ 1 ];
354 member_at.bv_val = strchr( group_oc.bv_val, '/' );
355 if ( member_at.bv_val ) {
356 group_oc.bv_len = member_at.bv_val - group_oc.bv_val;
358 member_at.bv_len = group_dn.bv_val - member_at.bv_val;
361 group_oc.bv_len = group_dn.bv_val - group_oc.bv_val;
362 BER_BVSTR( &member_at, "member" );
366 BER_BVSTR( &group_oc, "groupOfNames" );
369 group_dn.bv_len = uri->bv_len - ( group_dn.bv_val - uri->bv_val );
371 fstr->bv_len = STRLENOF( "(&(objectClass=)(=" ) + group_oc.bv_len + member_at.bv_len;
372 fstr->bv_val = ch_malloc( fstr->bv_len + 1 );
374 tmp = lutil_strncopy( fstr->bv_val, "(&(objectClass=", STRLENOF( "(&(objectClass=" ) );
375 tmp = lutil_strncopy( tmp, group_oc.bv_val, group_oc.bv_len );
376 tmp = lutil_strncopy( tmp, ")(", STRLENOF( ")(" ) );
377 tmp = lutil_strncopy( tmp, member_at.bv_val, member_at.bv_len );
378 tmp = lutil_strncopy( tmp, "=", STRLENOF( "=" ) );
380 rc = dnNormalize( 0, NULL, NULL, &group_dn, nbase, op->o_tmpmemctx );
381 if ( rc != LDAP_SUCCESS ) {
384 *scope = LDAP_X_SCOPE_GROUP;
390 * ldap:///<base>??<scope>?<filter>
391 * <scope> ::= {base|one|subtree}
393 * <scope> defaults to "base"
394 * <base> must pass DN normalization
395 * <filter> must pass str2filter()
397 rc = ldap_url_parse( uri->bv_val, &ludp );
398 if ( rc == LDAP_URL_ERR_BADSCHEME ) {
400 * last chance: assume it's a(n exact) DN ...
402 * NOTE: must pass DN normalization
404 bv.bv_val = uri->bv_val;
405 *scope = LDAP_X_SCOPE_EXACT;
409 if ( rc != LDAP_URL_SUCCESS ) {
410 return LDAP_PROTOCOL_ERROR;
413 if (( ludp->lud_host && *ludp->lud_host )
414 || ludp->lud_attrs || ludp->lud_exts )
416 /* host part must be empty */
417 /* attrs and extensions parts must be empty */
418 rc = LDAP_PROTOCOL_ERROR;
423 *scope = ludp->lud_scope;
425 /* Grab the filter */
426 if ( ludp->lud_filter ) {
427 *filter = str2filter_x( op, ludp->lud_filter );
428 if ( *filter == NULL ) {
429 rc = LDAP_PROTOCOL_ERROR;
432 ber_str2bv( ludp->lud_filter, 0, 0, fstr );
435 /* Grab the searchbase */
436 ber_str2bv( ludp->lud_dn, 0, 0, base );
437 rc = dnNormalize( 0, NULL, NULL, base, nbase, op->o_tmpmemctx );
440 if( rc != LDAP_SUCCESS ) {
441 if( *filter ) filter_free_x( op, *filter );
445 /* Don't free these, return them to caller */
446 ludp->lud_filter = NULL;
450 ldap_free_urldesc( ludp );
454 static int slap_sasl_rx_off(char *rep, int *off)
459 /* Precompile replace pattern. Find the $<n> placeholders */
462 for ( c = rep; *c; c++ ) {
463 if ( *c == '\\' && c[1] ) {
468 if ( n == SASLREGEX_REPLACE ) {
470 LDAP_LOG( TRANSPORT, ERR,
471 "slap_sasl_rx_off: \"%s\" has too many $n "
472 "placeholders (max %d)\n", rep, SASLREGEX_REPLACE, 0 );
474 Debug( LDAP_DEBUG_ANY,
475 "SASL replace pattern %s has too many $n "
476 "placeholders (max %d)\n",
477 rep, SASLREGEX_REPLACE, 0 );
480 return( LDAP_OTHER );
487 /* Final placeholder, after the last $n */
491 return( LDAP_SUCCESS );
494 #ifdef SLAP_AUTH_REWRITE
495 int slap_sasl_rewrite_config(
505 /* init at first call */
506 if ( sasl_rwinfo == NULL ) {
507 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
510 /* strip "authid-" prefix for parsing */
512 argv[0] += STRLENOF( "authid-" );
513 rc = rewrite_parse( sasl_rwinfo, fname, lineno, argc, argv );
519 int slap_sasl_rewrite_destroy( void )
522 rewrite_info_delete( &sasl_rwinfo );
529 int slap_sasl_regexp_rewrite_config(
534 const char *context )
537 char *newreplace, *p;
538 char *argvRule[] = { "rewriteRule", NULL, NULL, "@", NULL };
540 /* init at first call */
541 if ( sasl_rwinfo == NULL ) {
542 char *argvEngine[] = { "rewriteEngine", "on", NULL };
543 char *argvContext[] = { "rewriteContext", NULL, NULL };
545 /* initialize rewrite engine */
546 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
548 /* switch on rewrite engine */
549 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvEngine );
550 if (rc != LDAP_SUCCESS) {
554 /* create generic authid context */
555 argvContext[1] = AUTHID_CONTEXT;
556 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvContext );
557 if (rc != LDAP_SUCCESS) {
562 newreplace = ch_strdup( replace );
564 for (p = strchr( newreplace, '$' ); p; p = strchr( p + 1, '$' ) ) {
565 if ( isdigit( p[1] ) ) {
572 argvRule[1] = (char *)match;
573 argvRule[2] = newreplace;
574 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 4, argvRule );
575 ch_free( newreplace );
579 #endif /* SLAP_AUTH_REWRITE */
581 int slap_sasl_regexp_config( const char *match, const char *replace )
583 #ifdef SLAP_AUTH_REWRITE
584 return slap_sasl_regexp_rewrite_config( "sasl-regexp", 0,
585 match, replace, AUTHID_CONTEXT );
586 #else /* ! SLAP_AUTH_REWRITE */
590 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
591 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
593 reg = &SaslRegexp[nSaslRegexp];
595 reg->sr_match = ch_strdup( match );
596 reg->sr_replace = ch_strdup( replace );
598 /* Precompile matching pattern */
599 rc = regcomp( ®->sr_workspace, reg->sr_match, REG_EXTENDED|REG_ICASE );
602 LDAP_LOG( TRANSPORT, ERR,
603 "slap_sasl_regexp_config: \"%s\" could not be compiled.\n",
604 reg->sr_match, 0, 0 );
606 Debug( LDAP_DEBUG_ANY,
607 "SASL match pattern %s could not be compiled by regexp engine\n",
608 reg->sr_match, 0, 0 );
611 return( LDAP_OTHER );
614 rc = slap_sasl_rx_off( reg->sr_replace, reg->sr_offset );
615 if ( rc != LDAP_SUCCESS ) return rc;
618 return( LDAP_SUCCESS );
619 #endif /* ! SLAP_AUTH_REWRITE */
622 /* Perform replacement on regexp matches */
623 static void slap_sasl_rx_exp(
627 const char *saslname,
631 int i, n, len, insert;
633 /* Get the total length of the final URI */
637 while( off[n] >= 0 ) {
638 /* Len of next section from replacement string (x,y,z above) */
639 len += off[n] - off[n-1] - 2;
643 /* Len of string from saslname that matched next $i (b,d above) */
644 i = rep[ off[n] + 1 ] - '0';
645 len += str[i].rm_eo - str[i].rm_so;
648 out->bv_val = slap_sl_malloc( len + 1, ctx );
651 /* Fill in URI with replace string, replacing $i as we go */
654 while( off[n] >= 0) {
655 /* Paste in next section from replacement string (x,y,z above) */
656 len = off[n] - off[n-1] - 2;
657 strncpy( out->bv_val+insert, rep + off[n-1] + 2, len);
662 /* Paste in string from saslname that matched next $i (b,d above) */
663 i = rep[ off[n] + 1 ] - '0';
664 len = str[i].rm_eo - str[i].rm_so;
665 strncpy( out->bv_val+insert, saslname + str[i].rm_so, len );
671 out->bv_val[insert] = '\0';
674 /* Take the passed in SASL name and attempt to convert it into an
675 LDAP URI to find the matching LDAP entry, using the pattern matching
676 strings given in the saslregexp config file directive(s) */
678 static int slap_sasl_regexp( struct berval *in, struct berval *out,
679 int flags, void *ctx )
681 #ifdef SLAP_AUTH_REWRITE
682 const char *context = AUTHID_CONTEXT;
684 if ( sasl_rwinfo == NULL || BER_BVISNULL( in ) ) {
688 /* FIXME: if aware of authc/authz mapping,
689 * we could use different contexts ... */
690 switch ( rewrite_session( sasl_rwinfo, context, in->bv_val, NULL,
693 case REWRITE_REGEXEC_OK:
694 if ( !BER_BVISNULL( out ) ) {
695 char *val = out->bv_val;
696 ber_str2bv_x( val, 0, 1, out, ctx );
699 ber_dupbv_x( out, in, ctx );
702 LDAP_LOG( BACK_LDAP, DETAIL1,
703 "[rw] %s: \"%s\" -> \"%s\"\n",
704 context, in->bv_val, out->bv_val );
705 #else /* !NEW_LOGGING */
706 Debug( LDAP_DEBUG_ARGS,
707 "[rw] %s: \"%s\" -> \"%s\"\n",
708 context, in->bv_val, out->bv_val );
709 #endif /* !NEW_LOGGING */
712 case REWRITE_REGEXEC_UNWILLING:
713 case REWRITE_REGEXEC_ERR:
718 #else /* ! SLAP_AUTH_REWRITE */
719 char *saslname = in->bv_val;
721 regmatch_t sr_strings[SASLREGEX_REPLACE]; /* strings matching $1,$2 ... */
724 memset( out, 0, sizeof( *out ) );
727 LDAP_LOG( TRANSPORT, ENTRY,
728 "slap_sasl_regexp: converting SASL name %s\n", saslname, 0, 0 );
730 Debug( LDAP_DEBUG_TRACE, "slap_sasl_regexp: converting SASL name %s\n",
734 if (( saslname == NULL ) || ( nSaslRegexp == 0 )) {
738 /* Match the normalized SASL name to the saslregexp patterns */
739 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
740 if ( regexec( ®->sr_workspace, saslname, SASLREGEX_REPLACE,
741 sr_strings, 0) == 0 )
745 if( i >= nSaslRegexp ) return( 0 );
748 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
749 * replace pattern of the form "x$1y$2z". The returned string needs
750 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
752 slap_sasl_rx_exp( reg->sr_replace, reg->sr_offset,
753 sr_strings, saslname, out, ctx );
756 LDAP_LOG( TRANSPORT, ENTRY,
757 "slap_sasl_regexp: converted SASL name to %s\n",
758 BER_BVISEMPTY( out ) ? "" : out->bv_val, 0, 0 );
760 Debug( LDAP_DEBUG_TRACE,
761 "slap_sasl_regexp: converted SASL name to %s\n",
762 BER_BVISEMPTY( out ) ? "" : out->bv_val, 0, 0 );
766 #endif /* ! SLAP_AUTH_REWRITE */
769 /* This callback actually does some work...*/
770 static int sasl_sc_sasl2dn( Operation *o, SlapReply *rs )
772 struct berval *ndn = o->o_callback->sc_private;
774 if (rs->sr_type != REP_SEARCH) return 0;
776 /* We only want to be called once */
777 if ( !BER_BVISNULL( ndn ) ) {
778 o->o_tmpfree(ndn->bv_val, o->o_tmpmemctx);
782 LDAP_LOG( TRANSPORT, DETAIL1,
783 "slap_sc_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
785 Debug( LDAP_DEBUG_TRACE,
786 "slap_sc_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
791 ber_dupbv_x(ndn, &rs->sr_entry->e_nname, o->o_tmpmemctx);
796 typedef struct smatch_info {
801 static int sasl_sc_smatch( Operation *o, SlapReply *rs )
803 smatch_info *sm = o->o_callback->sc_private;
805 if ( rs->sr_type != REP_SEARCH ) {
806 if ( rs->sr_err != LDAP_SUCCESS ) {
812 if ( sm->match == 1 ) {
817 if (dn_match(sm->dn, &rs->sr_entry->e_nname)) {
828 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
829 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
830 * the rule must be used as an internal search for entries. If that search
831 * returns the *assertDN entry, the match is successful.
833 * The assertDN should not have the dn: prefix
837 int slap_sasl_match( Operation *opx, struct berval *rule,
838 struct berval *assertDN, struct berval *authc )
843 slap_callback cb = { NULL, sasl_sc_smatch, NULL, NULL };
845 SlapReply rs = {REP_RESULT};
852 LDAP_LOG( TRANSPORT, ENTRY,
853 "slap_sasl_match: comparing DN %s to rule %s\n",
854 assertDN->bv_val, rule->bv_val,0 );
856 Debug( LDAP_DEBUG_TRACE,
857 "===>slap_sasl_match: comparing DN %s to rule %s\n",
858 assertDN->bv_val, rule->bv_val, 0 );
861 rc = slap_parseURI( opx, rule, &op.o_req_dn,
862 &op.o_req_ndn, &op.oq_search.rs_scope, &op.oq_search.rs_filter,
864 if( rc != LDAP_SUCCESS ) goto CONCLUDED;
866 /* Massive shortcut: search scope == base */
867 switch ( op.oq_search.rs_scope ) {
868 case LDAP_SCOPE_BASE:
869 case LDAP_X_SCOPE_EXACT:
871 if ( dn_match( &op.o_req_ndn, assertDN ) ) {
874 rc = LDAP_INAPPROPRIATE_AUTH;
878 case LDAP_X_SCOPE_CHILDREN:
879 case LDAP_X_SCOPE_SUBTREE:
880 case LDAP_X_SCOPE_ONELEVEL:
882 int d = assertDN->bv_len - op.o_req_ndn.bv_len;
884 rc = LDAP_INAPPROPRIATE_AUTH;
886 if ( d == 0 && op.oq_search.rs_scope == LDAP_X_SCOPE_SUBTREE ) {
889 } else if ( d > 0 ) {
892 /* leave room for at least one char of attributeType,
893 * one for '=' and one for ',' */
894 if ( d < STRLENOF( "x=,") ) {
898 bv.bv_len = op.o_req_ndn.bv_len;
899 bv.bv_val = assertDN->bv_val + d;
901 if ( bv.bv_val[ -1 ] == ',' && dn_match( &op.o_req_ndn, &bv ) ) {
902 switch ( op.oq_search.rs_scope ) {
903 case LDAP_X_SCOPE_SUBTREE:
904 case LDAP_X_SCOPE_CHILDREN:
908 case LDAP_X_SCOPE_ONELEVEL:
912 dnParent( assertDN, &pdn );
913 /* the common portion of the DN
914 * already matches, so only check
915 * if parent DN of assertedDN
916 * is all the pattern */
917 if ( pdn.bv_len == op.o_req_ndn.bv_len ) {
923 /* at present, impossible */
931 case LDAP_X_SCOPE_REGEX:
932 rc = regcomp(®, op.o_req_ndn.bv_val,
933 REG_EXTENDED|REG_ICASE|REG_NOSUB);
935 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
941 rc = LDAP_INAPPROPRIATE_AUTH;
945 case LDAP_X_SCOPE_GROUP: {
948 /* Now filterstr looks like "(&(objectClass=<group_oc>)(<member_at>="
949 * we need to append the <assertDN> so that the <group_dn> is searched
950 * with scope "base", and the filter ensures that <assertDN> is
951 * member of the group */
952 tmp = ch_realloc( op.ors_filterstr.bv_val,
953 op.ors_filterstr.bv_len + assertDN->bv_len + STRLENOF( "))" ) + 1 );
958 op.ors_filterstr.bv_val = tmp;
960 tmp = lutil_strcopy( &tmp[ op.ors_filterstr.bv_len ], assertDN->bv_val );
961 tmp = lutil_strcopy( tmp, "))" );
963 /* pass opx because str2filter_x may (and does) use o_tmpmfuncs */
964 op.ors_filter = str2filter_x( opx, op.ors_filterstr.bv_val );
965 if ( op.ors_filter == NULL ) {
966 rc = LDAP_PROTOCOL_ERROR;
969 op.ors_scope = LDAP_SCOPE_BASE;
971 /* hijack match DN: use that of the group instead of the assertDN;
972 * assertDN is now in the filter */
973 sm.dn = &op.o_req_ndn;
983 /* Must run an internal search. */
984 if ( op.oq_search.rs_filter == NULL ) {
985 rc = LDAP_FILTER_ERROR;
990 LDAP_LOG( TRANSPORT, DETAIL1,
991 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
992 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
994 Debug( LDAP_DEBUG_TRACE,
995 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
996 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
999 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
1000 if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
1001 rc = LDAP_INAPPROPRIATE_AUTH;
1005 op.o_tag = LDAP_REQ_SEARCH;
1006 op.o_protocol = LDAP_VERSION3;
1008 op.o_callback = &cb;
1009 op.o_time = slap_get_time();
1010 op.o_do_not_cache = 1;
1011 op.o_is_auth_check = 1;
1012 op.o_threadctx = opx->o_threadctx;
1013 op.o_tmpmemctx = opx->o_tmpmemctx;
1014 op.o_tmpmfuncs = opx->o_tmpmfuncs;
1016 op.o_pb = opx->o_pb;
1018 op.o_conn = opx->o_conn;
1019 op.o_connid = opx->o_connid;
1020 /* use req_ndn as req_dn instead of non-pretty base of uri */
1021 if( !BER_BVISNULL( &op.o_req_dn ) ) ch_free( op.o_req_dn.bv_val );
1022 ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1023 op.oq_search.rs_slimit = 1;
1024 op.oq_search.rs_tlimit = -1;
1025 op.o_sync_slog_size = -1;
1027 op.o_bd->be_search( &op, &rs );
1029 if (sm.match == 1) {
1032 rc = LDAP_INAPPROPRIATE_AUTH;
1036 if( !BER_BVISNULL( &op.o_req_dn ) ) slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1037 if( !BER_BVISNULL( &op.o_req_ndn ) ) slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1038 if( op.oq_search.rs_filter ) filter_free_x( opx, op.oq_search.rs_filter );
1039 if( !BER_BVISNULL( &op.ors_filterstr ) ) ch_free( op.ors_filterstr.bv_val );
1042 LDAP_LOG( TRANSPORT, ENTRY,
1043 "slap_sasl_match: comparison returned %d\n", rc, 0, 0 );
1045 Debug( LDAP_DEBUG_TRACE,
1046 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
1054 * This function answers the question, "Can this ID authorize to that ID?",
1055 * based on authorization rules. The rules are stored in the *searchDN, in the
1056 * attribute named by *attr. If any of those rules map to the *assertDN, the
1057 * authorization is approved.
1059 * The DNs should not have the dn: prefix
1062 slap_sasl_check_authz( Operation *op,
1063 struct berval *searchDN,
1064 struct berval *assertDN,
1065 AttributeDescription *ad,
1066 struct berval *authc )
1069 BerVarray vals=NULL;
1072 LDAP_LOG( TRANSPORT, ENTRY,
1073 "slap_sasl_check_authz: does %s match %s rule in %s?\n",
1074 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
1076 Debug( LDAP_DEBUG_TRACE,
1077 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
1078 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
1081 rc = backend_attribute( op, NULL,
1082 searchDN, ad, &vals );
1083 if( rc != LDAP_SUCCESS ) goto COMPLETE;
1085 /* Check if the *assertDN matches any **vals */
1086 if( vals != NULL ) {
1087 for( i=0; !BER_BVISNULL( &vals[i] ); i++ ) {
1088 rc = slap_sasl_match( op, &vals[i], assertDN, authc );
1089 if ( rc == LDAP_SUCCESS ) goto COMPLETE;
1092 rc = LDAP_INAPPROPRIATE_AUTH;
1095 if( vals ) ber_bvarray_free_x( vals, op->o_tmpmemctx );
1098 LDAP_LOG( TRANSPORT, RESULTS,
1099 "slap_sasl_check_authz: %s check returning %s\n",
1100 ad->ad_cname.bv_val, rc, 0 );
1102 Debug( LDAP_DEBUG_TRACE,
1103 "<==slap_sasl_check_authz: %s check returning %d\n",
1104 ad->ad_cname.bv_val, rc, 0);
1111 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
1112 * return the LDAP DN to which it matches. The SASL regexp rules in the config
1113 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
1114 * search with scope=base), just return the URI (or its searchbase). Otherwise
1115 * an internal search must be done, and if that search returns exactly one
1116 * entry, return the DN of that one entry.
1118 void slap_sasl2dn( Operation *opx,
1119 struct berval *saslname, struct berval *sasldn, int flags )
1122 slap_callback cb = { NULL, sasl_sc_sasl2dn, NULL, NULL };
1124 SlapReply rs = {REP_RESULT};
1125 struct berval regout = BER_BVNULL;
1128 LDAP_LOG( TRANSPORT, ENTRY,
1129 "slap_sasl2dn: converting SASL name %s to DN.\n",
1130 saslname->bv_val, 0, 0 );
1132 Debug( LDAP_DEBUG_TRACE, "==>slap_sasl2dn: "
1133 "converting SASL name %s to a DN\n",
1134 saslname->bv_val, 0,0 );
1137 sasldn->bv_val = NULL;
1139 cb.sc_private = sasldn;
1141 /* Convert the SASL name into a minimal URI */
1142 if( !slap_sasl_regexp( saslname, ®out, flags, opx->o_tmpmemctx ) ) {
1146 rc = slap_parseURI( opx, ®out, &op.o_req_dn,
1147 &op.o_req_ndn, &op.oq_search.rs_scope, &op.oq_search.rs_filter,
1148 &op.ors_filterstr );
1149 if ( !BER_BVISNULL( ®out ) ) slap_sl_free( regout.bv_val, opx->o_tmpmemctx );
1150 if ( rc != LDAP_SUCCESS ) {
1154 /* Must do an internal search */
1155 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
1157 /* Massive shortcut: search scope == base */
1158 switch ( op.oq_search.rs_scope ) {
1159 case LDAP_SCOPE_BASE:
1160 case LDAP_X_SCOPE_EXACT:
1161 *sasldn = op.o_req_ndn;
1162 BER_BVZERO( &op.o_req_ndn );
1163 /* intentionally continue to next case */
1165 case LDAP_X_SCOPE_REGEX:
1166 case LDAP_X_SCOPE_SUBTREE:
1167 case LDAP_X_SCOPE_CHILDREN:
1168 case LDAP_X_SCOPE_ONELEVEL:
1169 case LDAP_X_SCOPE_GROUP:
1170 /* correctly parsed, but illegal */
1173 case LDAP_SCOPE_ONELEVEL:
1174 case LDAP_SCOPE_SUBTREE:
1175 #ifdef LDAP_SCOPE_SUBORDINATE
1176 case LDAP_SCOPE_SUBORDINATE:
1182 /* catch unhandled cases (there shouldn't be) */
1187 LDAP_LOG( TRANSPORT, DETAIL1,
1188 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
1189 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
1191 Debug( LDAP_DEBUG_TRACE,
1192 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
1193 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
1196 if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
1200 op.o_conn = opx->o_conn;
1201 op.o_connid = opx->o_connid;
1202 op.o_tag = LDAP_REQ_SEARCH;
1203 op.o_protocol = LDAP_VERSION3;
1204 op.o_ndn = opx->o_conn->c_ndn;
1205 op.o_callback = &cb;
1206 op.o_time = slap_get_time();
1207 op.o_do_not_cache = 1;
1208 op.o_is_auth_check = 1;
1209 op.o_threadctx = opx->o_threadctx;
1210 op.o_tmpmemctx = opx->o_tmpmemctx;
1211 op.o_tmpmfuncs = opx->o_tmpmfuncs;
1213 op.o_pb = opx->o_pb;
1215 op.oq_search.rs_deref = LDAP_DEREF_NEVER;
1216 op.oq_search.rs_slimit = 1;
1217 op.oq_search.rs_tlimit = -1;
1218 op.oq_search.rs_attrsonly = 1;
1219 /* use req_ndn as req_dn instead of non-pretty base of uri */
1220 if( !BER_BVISNULL( &op.o_req_dn ) ) ch_free( op.o_req_dn.bv_val );
1221 ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1223 op.o_bd->be_search( &op, &rs );
1226 if( !BER_BVISEMPTY( sasldn ) ) {
1227 opx->o_conn->c_authz_backend = op.o_bd;
1229 if( !BER_BVISNULL( &op.o_req_dn ) ) slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1230 if( !BER_BVISNULL( &op.o_req_ndn ) ) slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1231 if( op.oq_search.rs_filter ) filter_free_x( opx, op.oq_search.rs_filter );
1232 if( !BER_BVISNULL( &op.ors_filterstr ) ) ch_free( op.ors_filterstr.bv_val );
1235 LDAP_LOG( TRANSPORT, ENTRY,
1236 "slap_sasl2dn: Converted SASL name to %s\n",
1237 !BER_BVISEMPTY( sasldn ) ? sasldn->bv_val : "<nothing>", 0, 0 );
1239 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
1240 !BER_BVISEMPTY( sasldn ) ? sasldn->bv_val : "<nothing>", 0, 0 );
1247 /* Check if a bind can SASL authorize to another identity.
1248 * The DNs should not have the dn: prefix
1251 int slap_sasl_authorized( Operation *op,
1252 struct berval *authcDN, struct berval *authzDN )
1254 int rc = LDAP_INAPPROPRIATE_AUTH;
1256 /* User binding as anonymous */
1257 if ( authzDN == NULL ) {
1263 LDAP_LOG( TRANSPORT, ENTRY,
1264 "slap_sasl_authorized: can %s become %s?\n",
1265 authcDN->bv_val, authzDN->bv_val, 0 );
1267 Debug( LDAP_DEBUG_TRACE,
1268 "==>slap_sasl_authorized: can %s become %s?\n",
1269 authcDN->bv_val, authzDN->bv_val, 0 );
1272 /* If person is authorizing to self, succeed */
1273 if ( dn_match( authcDN, authzDN ) ) {
1278 /* Allow the manager to authorize as any DN. */
1279 if( op->o_conn->c_authz_backend &&
1280 be_isroot_dn( op->o_conn->c_authz_backend, authcDN ))
1286 /* Check source rules */
1287 if( authz_policy & SASL_AUTHZ_TO ) {
1288 rc = slap_sasl_check_authz( op, authcDN, authzDN,
1289 slap_schema.si_ad_saslAuthzTo, authcDN );
1290 if( rc == LDAP_SUCCESS && !(authz_policy & SASL_AUTHZ_AND) ) {
1295 /* Check destination rules */
1296 if( authz_policy & SASL_AUTHZ_FROM ) {
1297 rc = slap_sasl_check_authz( op, authzDN, authcDN,
1298 slap_schema.si_ad_saslAuthzFrom, authcDN );
1299 if( rc == LDAP_SUCCESS ) {
1304 rc = LDAP_INAPPROPRIATE_AUTH;
1309 LDAP_LOG( TRANSPORT, RESULTS, "slap_sasl_authorized: return %d\n", rc,0,0 );
1311 Debug( LDAP_DEBUG_TRACE,
1312 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );