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 rc = dnNormalize( 0, NULL, NULL, &group_dn, nbase, op->o_tmpmemctx );
372 if ( rc != LDAP_SUCCESS ) {
376 *scope = LDAP_X_SCOPE_GROUP;
378 /* FIXME: caller needs to add value of member attribute
379 * and close brackets twice */
380 fstr->bv_len = STRLENOF( "(&(objectClass=)(=" /* )) */ )
381 + group_oc.bv_len + member_at.bv_len;
382 fstr->bv_val = ch_malloc( fstr->bv_len + 1 );
384 tmp = lutil_strncopy( fstr->bv_val, "(&(objectClass=" /* )) */ ,
385 STRLENOF( "(&(objectClass=" /* )) */ ) );
386 tmp = lutil_strncopy( tmp, group_oc.bv_val, group_oc.bv_len );
387 tmp = lutil_strncopy( tmp, /* ( */ ")(" /* ) */ ,
388 STRLENOF( /* ( */ ")(" /* ) */ ) );
389 tmp = lutil_strncopy( tmp, member_at.bv_val, member_at.bv_len );
390 tmp = lutil_strncopy( tmp, "=", STRLENOF( "=" ) );
396 * ldap:///<base>??<scope>?<filter>
397 * <scope> ::= {base|one|subtree}
399 * <scope> defaults to "base"
400 * <base> must pass DN normalization
401 * <filter> must pass str2filter()
403 rc = ldap_url_parse( uri->bv_val, &ludp );
404 if ( rc == LDAP_URL_ERR_BADSCHEME ) {
406 * last chance: assume it's a(n exact) DN ...
408 * NOTE: must pass DN normalization
410 bv.bv_val = uri->bv_val;
411 *scope = LDAP_X_SCOPE_EXACT;
415 if ( rc != LDAP_URL_SUCCESS ) {
416 return LDAP_PROTOCOL_ERROR;
419 if (( ludp->lud_host && *ludp->lud_host )
420 || ludp->lud_attrs || ludp->lud_exts )
422 /* host part must be empty */
423 /* attrs and extensions parts must be empty */
424 rc = LDAP_PROTOCOL_ERROR;
429 *scope = ludp->lud_scope;
431 /* Grab the filter */
432 if ( ludp->lud_filter ) {
433 *filter = str2filter_x( op, ludp->lud_filter );
434 if ( *filter == NULL ) {
435 rc = LDAP_PROTOCOL_ERROR;
438 ber_str2bv( ludp->lud_filter, 0, 0, fstr );
441 /* Grab the searchbase */
442 ber_str2bv( ludp->lud_dn, 0, 0, base );
443 rc = dnNormalize( 0, NULL, NULL, base, nbase, op->o_tmpmemctx );
446 if( rc != LDAP_SUCCESS ) {
447 if( *filter ) filter_free_x( op, *filter );
451 /* Don't free these, return them to caller */
452 ludp->lud_filter = NULL;
456 ldap_free_urldesc( ludp );
460 static int slap_sasl_rx_off(char *rep, int *off)
465 /* Precompile replace pattern. Find the $<n> placeholders */
468 for ( c = rep; *c; c++ ) {
469 if ( *c == '\\' && c[1] ) {
474 if ( n == SASLREGEX_REPLACE ) {
476 LDAP_LOG( TRANSPORT, ERR,
477 "slap_sasl_rx_off: \"%s\" has too many $n "
478 "placeholders (max %d)\n", rep, SASLREGEX_REPLACE, 0 );
480 Debug( LDAP_DEBUG_ANY,
481 "SASL replace pattern %s has too many $n "
482 "placeholders (max %d)\n",
483 rep, SASLREGEX_REPLACE, 0 );
486 return( LDAP_OTHER );
493 /* Final placeholder, after the last $n */
497 return( LDAP_SUCCESS );
500 #ifdef SLAP_AUTH_REWRITE
501 int slap_sasl_rewrite_config(
511 /* init at first call */
512 if ( sasl_rwinfo == NULL ) {
513 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
516 /* strip "authid-" prefix for parsing */
518 argv[0] += STRLENOF( "authid-" );
519 rc = rewrite_parse( sasl_rwinfo, fname, lineno, argc, argv );
525 int slap_sasl_rewrite_destroy( void )
528 rewrite_info_delete( &sasl_rwinfo );
535 int slap_sasl_regexp_rewrite_config(
540 const char *context )
543 char *newreplace, *p;
544 char *argvRule[] = { "rewriteRule", NULL, NULL, "@", NULL };
546 /* init at first call */
547 if ( sasl_rwinfo == NULL ) {
548 char *argvEngine[] = { "rewriteEngine", "on", NULL };
549 char *argvContext[] = { "rewriteContext", NULL, NULL };
551 /* initialize rewrite engine */
552 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
554 /* switch on rewrite engine */
555 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvEngine );
556 if (rc != LDAP_SUCCESS) {
560 /* create generic authid context */
561 argvContext[1] = AUTHID_CONTEXT;
562 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvContext );
563 if (rc != LDAP_SUCCESS) {
568 newreplace = ch_strdup( replace );
570 for (p = strchr( newreplace, '$' ); p; p = strchr( p + 1, '$' ) ) {
571 if ( isdigit( p[1] ) ) {
578 argvRule[1] = (char *)match;
579 argvRule[2] = newreplace;
580 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 4, argvRule );
581 ch_free( newreplace );
585 #endif /* SLAP_AUTH_REWRITE */
587 int slap_sasl_regexp_config( const char *match, const char *replace )
589 #ifdef SLAP_AUTH_REWRITE
590 return slap_sasl_regexp_rewrite_config( "sasl-regexp", 0,
591 match, replace, AUTHID_CONTEXT );
592 #else /* ! SLAP_AUTH_REWRITE */
596 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
597 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
599 reg = &SaslRegexp[nSaslRegexp];
601 reg->sr_match = ch_strdup( match );
602 reg->sr_replace = ch_strdup( replace );
604 /* Precompile matching pattern */
605 rc = regcomp( ®->sr_workspace, reg->sr_match, REG_EXTENDED|REG_ICASE );
608 LDAP_LOG( TRANSPORT, ERR,
609 "slap_sasl_regexp_config: \"%s\" could not be compiled.\n",
610 reg->sr_match, 0, 0 );
612 Debug( LDAP_DEBUG_ANY,
613 "SASL match pattern %s could not be compiled by regexp engine\n",
614 reg->sr_match, 0, 0 );
617 return( LDAP_OTHER );
620 rc = slap_sasl_rx_off( reg->sr_replace, reg->sr_offset );
621 if ( rc != LDAP_SUCCESS ) return rc;
624 return( LDAP_SUCCESS );
625 #endif /* ! SLAP_AUTH_REWRITE */
628 /* Perform replacement on regexp matches */
629 static void slap_sasl_rx_exp(
633 const char *saslname,
637 int i, n, len, insert;
639 /* Get the total length of the final URI */
643 while( off[n] >= 0 ) {
644 /* Len of next section from replacement string (x,y,z above) */
645 len += off[n] - off[n-1] - 2;
649 /* Len of string from saslname that matched next $i (b,d above) */
650 i = rep[ off[n] + 1 ] - '0';
651 len += str[i].rm_eo - str[i].rm_so;
654 out->bv_val = slap_sl_malloc( len + 1, ctx );
657 /* Fill in URI with replace string, replacing $i as we go */
660 while( off[n] >= 0) {
661 /* Paste in next section from replacement string (x,y,z above) */
662 len = off[n] - off[n-1] - 2;
663 strncpy( out->bv_val+insert, rep + off[n-1] + 2, len);
668 /* Paste in string from saslname that matched next $i (b,d above) */
669 i = rep[ off[n] + 1 ] - '0';
670 len = str[i].rm_eo - str[i].rm_so;
671 strncpy( out->bv_val+insert, saslname + str[i].rm_so, len );
677 out->bv_val[insert] = '\0';
680 /* Take the passed in SASL name and attempt to convert it into an
681 LDAP URI to find the matching LDAP entry, using the pattern matching
682 strings given in the saslregexp config file directive(s) */
684 static int slap_authz_regexp( struct berval *in, struct berval *out,
685 int flags, void *ctx )
687 #ifdef SLAP_AUTH_REWRITE
688 const char *context = AUTHID_CONTEXT;
690 if ( sasl_rwinfo == NULL || BER_BVISNULL( in ) ) {
694 /* FIXME: if aware of authc/authz mapping,
695 * we could use different contexts ... */
696 switch ( rewrite_session( sasl_rwinfo, context, in->bv_val, NULL,
699 case REWRITE_REGEXEC_OK:
700 if ( !BER_BVISNULL( out ) ) {
701 char *val = out->bv_val;
702 ber_str2bv_x( val, 0, 1, out, ctx );
705 ber_dupbv_x( out, in, ctx );
708 LDAP_LOG( BACK_LDAP, DETAIL1,
709 "[rw] %s: \"%s\" -> \"%s\"\n",
710 context, in->bv_val, out->bv_val );
711 #else /* !NEW_LOGGING */
712 Debug( LDAP_DEBUG_ARGS,
713 "[rw] %s: \"%s\" -> \"%s\"\n",
714 context, in->bv_val, out->bv_val );
715 #endif /* !NEW_LOGGING */
718 case REWRITE_REGEXEC_UNWILLING:
719 case REWRITE_REGEXEC_ERR:
724 #else /* ! SLAP_AUTH_REWRITE */
725 char *saslname = in->bv_val;
727 regmatch_t sr_strings[SASLREGEX_REPLACE]; /* strings matching $1,$2 ... */
730 memset( out, 0, sizeof( *out ) );
733 LDAP_LOG( TRANSPORT, ENTRY,
734 "slap_authz_regexp: converting SASL name %s\n", saslname, 0, 0 );
736 Debug( LDAP_DEBUG_TRACE, "slap_authz_regexp: converting SASL name %s\n",
740 if (( saslname == NULL ) || ( nSaslRegexp == 0 )) {
744 /* Match the normalized SASL name to the saslregexp patterns */
745 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
746 if ( regexec( ®->sr_workspace, saslname, SASLREGEX_REPLACE,
747 sr_strings, 0) == 0 )
751 if( i >= nSaslRegexp ) return( 0 );
754 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
755 * replace pattern of the form "x$1y$2z". The returned string needs
756 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
758 slap_sasl_rx_exp( reg->sr_replace, reg->sr_offset,
759 sr_strings, saslname, out, ctx );
762 LDAP_LOG( TRANSPORT, ENTRY,
763 "slap_authz_regexp: converted SASL name to %s\n",
764 BER_BVISEMPTY( out ) ? "" : out->bv_val, 0, 0 );
766 Debug( LDAP_DEBUG_TRACE,
767 "slap_authz_regexp: converted SASL name to %s\n",
768 BER_BVISEMPTY( out ) ? "" : out->bv_val, 0, 0 );
772 #endif /* ! SLAP_AUTH_REWRITE */
775 /* This callback actually does some work...*/
776 static int sasl_sc_sasl2dn( Operation *o, SlapReply *rs )
778 struct berval *ndn = o->o_callback->sc_private;
780 if (rs->sr_type != REP_SEARCH) return 0;
782 /* We only want to be called once */
783 if ( !BER_BVISNULL( ndn ) ) {
784 o->o_tmpfree(ndn->bv_val, o->o_tmpmemctx);
788 LDAP_LOG( TRANSPORT, DETAIL1,
789 "slap_sc_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
791 Debug( LDAP_DEBUG_TRACE,
792 "slap_sc_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
797 ber_dupbv_x(ndn, &rs->sr_entry->e_nname, o->o_tmpmemctx);
802 typedef struct smatch_info {
807 static int sasl_sc_smatch( Operation *o, SlapReply *rs )
809 smatch_info *sm = o->o_callback->sc_private;
811 if ( rs->sr_type != REP_SEARCH ) {
812 if ( rs->sr_err != LDAP_SUCCESS ) {
818 if ( sm->match == 1 ) {
823 if (dn_match(sm->dn, &rs->sr_entry->e_nname)) {
834 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
835 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
836 * the rule must be used as an internal search for entries. If that search
837 * returns the *assertDN entry, the match is successful.
839 * The assertDN should not have the dn: prefix
843 int slap_sasl_match( Operation *opx, struct berval *rule,
844 struct berval *assertDN, struct berval *authc )
849 slap_callback cb = { NULL, sasl_sc_smatch, NULL, NULL };
851 SlapReply rs = {REP_RESULT};
858 LDAP_LOG( TRANSPORT, ENTRY,
859 "slap_sasl_match: comparing DN %s to rule %s\n",
860 assertDN->bv_val, rule->bv_val,0 );
862 Debug( LDAP_DEBUG_TRACE,
863 "===>slap_sasl_match: comparing DN %s to rule %s\n",
864 assertDN->bv_val, rule->bv_val, 0 );
867 rc = slap_parseURI( opx, rule, &op.o_req_dn,
868 &op.o_req_ndn, &op.oq_search.rs_scope, &op.oq_search.rs_filter,
870 if( rc != LDAP_SUCCESS ) goto CONCLUDED;
872 /* Massive shortcut: search scope == base */
873 switch ( op.oq_search.rs_scope ) {
874 case LDAP_SCOPE_BASE:
875 case LDAP_X_SCOPE_EXACT:
877 if ( dn_match( &op.o_req_ndn, assertDN ) ) {
880 rc = LDAP_INAPPROPRIATE_AUTH;
884 case LDAP_X_SCOPE_CHILDREN:
885 case LDAP_X_SCOPE_SUBTREE:
886 case LDAP_X_SCOPE_ONELEVEL:
888 int d = assertDN->bv_len - op.o_req_ndn.bv_len;
890 rc = LDAP_INAPPROPRIATE_AUTH;
892 if ( d == 0 && op.oq_search.rs_scope == LDAP_X_SCOPE_SUBTREE ) {
895 } else if ( d > 0 ) {
898 /* leave room for at least one char of attributeType,
899 * one for '=' and one for ',' */
900 if ( d < STRLENOF( "x=,") ) {
904 bv.bv_len = op.o_req_ndn.bv_len;
905 bv.bv_val = assertDN->bv_val + d;
907 if ( bv.bv_val[ -1 ] == ',' && dn_match( &op.o_req_ndn, &bv ) ) {
908 switch ( op.oq_search.rs_scope ) {
909 case LDAP_X_SCOPE_SUBTREE:
910 case LDAP_X_SCOPE_CHILDREN:
914 case LDAP_X_SCOPE_ONELEVEL:
918 dnParent( assertDN, &pdn );
919 /* the common portion of the DN
920 * already matches, so only check
921 * if parent DN of assertedDN
922 * is all the pattern */
923 if ( pdn.bv_len == op.o_req_ndn.bv_len ) {
929 /* at present, impossible */
937 case LDAP_X_SCOPE_REGEX:
938 rc = regcomp(®, op.o_req_ndn.bv_val,
939 REG_EXTENDED|REG_ICASE|REG_NOSUB);
941 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
947 rc = LDAP_INAPPROPRIATE_AUTH;
951 case LDAP_X_SCOPE_GROUP: {
954 /* Now filterstr looks like "(&(objectClass=<group_oc>)(<member_at>="
955 * we need to append the <assertDN> so that the <group_dn> is searched
956 * with scope "base", and the filter ensures that <assertDN> is
957 * member of the group */
958 tmp = ch_realloc( op.ors_filterstr.bv_val,
959 op.ors_filterstr.bv_len + assertDN->bv_len + STRLENOF( /* (( */ "))" ) + 1 );
964 op.ors_filterstr.bv_val = tmp;
966 tmp = lutil_strcopy( &tmp[ op.ors_filterstr.bv_len ], assertDN->bv_val );
967 tmp = lutil_strcopy( tmp, /* (( */ "))" );
969 /* pass opx because str2filter_x may (and does) use o_tmpmfuncs */
970 op.ors_filter = str2filter_x( opx, op.ors_filterstr.bv_val );
971 if ( op.ors_filter == NULL ) {
972 rc = LDAP_PROTOCOL_ERROR;
975 op.ors_scope = LDAP_SCOPE_BASE;
977 /* hijack match DN: use that of the group instead of the assertDN;
978 * assertDN is now in the filter */
979 sm.dn = &op.o_req_ndn;
989 /* Must run an internal search. */
990 if ( op.oq_search.rs_filter == NULL ) {
991 rc = LDAP_FILTER_ERROR;
996 LDAP_LOG( TRANSPORT, DETAIL1,
997 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
998 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
1000 Debug( LDAP_DEBUG_TRACE,
1001 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
1002 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
1005 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
1006 if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
1007 rc = LDAP_INAPPROPRIATE_AUTH;
1011 op.o_tag = LDAP_REQ_SEARCH;
1012 op.o_protocol = LDAP_VERSION3;
1014 op.o_callback = &cb;
1015 op.o_time = slap_get_time();
1016 op.o_do_not_cache = 1;
1017 op.o_is_auth_check = 1;
1018 op.o_threadctx = opx->o_threadctx;
1019 op.o_tmpmemctx = opx->o_tmpmemctx;
1020 op.o_tmpmfuncs = opx->o_tmpmfuncs;
1022 op.o_pb = opx->o_pb;
1024 op.o_conn = opx->o_conn;
1025 op.o_connid = opx->o_connid;
1026 /* use req_ndn as req_dn instead of non-pretty base of uri */
1027 if( !BER_BVISNULL( &op.o_req_dn ) ) ch_free( op.o_req_dn.bv_val );
1028 ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1029 op.oq_search.rs_slimit = 1;
1030 op.oq_search.rs_tlimit = -1;
1031 op.o_sync_slog_size = -1;
1033 op.o_bd->be_search( &op, &rs );
1035 if (sm.match == 1) {
1038 rc = LDAP_INAPPROPRIATE_AUTH;
1042 if( !BER_BVISNULL( &op.o_req_dn ) ) slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1043 if( !BER_BVISNULL( &op.o_req_ndn ) ) slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1044 if( op.oq_search.rs_filter ) filter_free_x( opx, op.oq_search.rs_filter );
1045 if( !BER_BVISNULL( &op.ors_filterstr ) ) ch_free( op.ors_filterstr.bv_val );
1048 LDAP_LOG( TRANSPORT, ENTRY,
1049 "slap_sasl_match: comparison returned %d\n", rc, 0, 0 );
1051 Debug( LDAP_DEBUG_TRACE,
1052 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
1060 * This function answers the question, "Can this ID authorize to that ID?",
1061 * based on authorization rules. The rules are stored in the *searchDN, in the
1062 * attribute named by *attr. If any of those rules map to the *assertDN, the
1063 * authorization is approved.
1065 * The DNs should not have the dn: prefix
1068 slap_sasl_check_authz( Operation *op,
1069 struct berval *searchDN,
1070 struct berval *assertDN,
1071 AttributeDescription *ad,
1072 struct berval *authc )
1075 BerVarray vals=NULL;
1078 LDAP_LOG( TRANSPORT, ENTRY,
1079 "slap_sasl_check_authz: does %s match %s rule in %s?\n",
1080 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
1082 Debug( LDAP_DEBUG_TRACE,
1083 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
1084 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
1087 rc = backend_attribute( op, NULL,
1088 searchDN, ad, &vals );
1089 if( rc != LDAP_SUCCESS ) goto COMPLETE;
1091 /* Check if the *assertDN matches any **vals */
1092 if( vals != NULL ) {
1093 for( i=0; !BER_BVISNULL( &vals[i] ); i++ ) {
1094 rc = slap_sasl_match( op, &vals[i], assertDN, authc );
1095 if ( rc == LDAP_SUCCESS ) goto COMPLETE;
1098 rc = LDAP_INAPPROPRIATE_AUTH;
1101 if( vals ) ber_bvarray_free_x( vals, op->o_tmpmemctx );
1104 LDAP_LOG( TRANSPORT, RESULTS,
1105 "slap_sasl_check_authz: %s check returning %s\n",
1106 ad->ad_cname.bv_val, rc, 0 );
1108 Debug( LDAP_DEBUG_TRACE,
1109 "<==slap_sasl_check_authz: %s check returning %d\n",
1110 ad->ad_cname.bv_val, rc, 0);
1117 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
1118 * return the LDAP DN to which it matches. The SASL regexp rules in the config
1119 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
1120 * search with scope=base), just return the URI (or its searchbase). Otherwise
1121 * an internal search must be done, and if that search returns exactly one
1122 * entry, return the DN of that one entry.
1124 void slap_sasl2dn( Operation *opx,
1125 struct berval *saslname, struct berval *sasldn, int flags )
1128 slap_callback cb = { NULL, sasl_sc_sasl2dn, NULL, NULL };
1130 SlapReply rs = {REP_RESULT};
1131 struct berval regout = BER_BVNULL;
1134 LDAP_LOG( TRANSPORT, ENTRY,
1135 "slap_sasl2dn: converting SASL name %s to DN.\n",
1136 saslname->bv_val, 0, 0 );
1138 Debug( LDAP_DEBUG_TRACE, "==>slap_sasl2dn: "
1139 "converting SASL name %s to a DN\n",
1140 saslname->bv_val, 0,0 );
1143 sasldn->bv_val = NULL;
1145 cb.sc_private = sasldn;
1147 /* Convert the SASL name into a minimal URI */
1148 if( !slap_authz_regexp( saslname, ®out, flags, opx->o_tmpmemctx ) ) {
1152 rc = slap_parseURI( opx, ®out, &op.o_req_dn,
1153 &op.o_req_ndn, &op.oq_search.rs_scope, &op.oq_search.rs_filter,
1154 &op.ors_filterstr );
1155 if ( !BER_BVISNULL( ®out ) ) slap_sl_free( regout.bv_val, opx->o_tmpmemctx );
1156 if ( rc != LDAP_SUCCESS ) {
1160 /* Must do an internal search */
1161 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
1163 /* Massive shortcut: search scope == base */
1164 switch ( op.oq_search.rs_scope ) {
1165 case LDAP_SCOPE_BASE:
1166 case LDAP_X_SCOPE_EXACT:
1167 *sasldn = op.o_req_ndn;
1168 BER_BVZERO( &op.o_req_ndn );
1169 /* intentionally continue to next case */
1171 case LDAP_X_SCOPE_REGEX:
1172 case LDAP_X_SCOPE_SUBTREE:
1173 case LDAP_X_SCOPE_CHILDREN:
1174 case LDAP_X_SCOPE_ONELEVEL:
1175 case LDAP_X_SCOPE_GROUP:
1176 /* correctly parsed, but illegal */
1179 case LDAP_SCOPE_ONELEVEL:
1180 case LDAP_SCOPE_SUBTREE:
1181 #ifdef LDAP_SCOPE_SUBORDINATE
1182 case LDAP_SCOPE_SUBORDINATE:
1188 /* catch unhandled cases (there shouldn't be) */
1193 LDAP_LOG( TRANSPORT, DETAIL1,
1194 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
1195 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
1197 Debug( LDAP_DEBUG_TRACE,
1198 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
1199 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
1202 if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
1206 op.o_conn = opx->o_conn;
1207 op.o_connid = opx->o_connid;
1208 op.o_tag = LDAP_REQ_SEARCH;
1209 op.o_protocol = LDAP_VERSION3;
1210 op.o_ndn = opx->o_conn->c_ndn;
1211 op.o_callback = &cb;
1212 op.o_time = slap_get_time();
1213 op.o_do_not_cache = 1;
1214 op.o_is_auth_check = 1;
1215 op.o_threadctx = opx->o_threadctx;
1216 op.o_tmpmemctx = opx->o_tmpmemctx;
1217 op.o_tmpmfuncs = opx->o_tmpmfuncs;
1219 op.o_pb = opx->o_pb;
1221 op.oq_search.rs_deref = LDAP_DEREF_NEVER;
1222 op.oq_search.rs_slimit = 1;
1223 op.oq_search.rs_tlimit = -1;
1224 op.oq_search.rs_attrsonly = 1;
1225 /* use req_ndn as req_dn instead of non-pretty base of uri */
1226 if( !BER_BVISNULL( &op.o_req_dn ) ) ch_free( op.o_req_dn.bv_val );
1227 ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1229 op.o_bd->be_search( &op, &rs );
1232 if( !BER_BVISEMPTY( sasldn ) ) {
1233 opx->o_conn->c_authz_backend = op.o_bd;
1235 if( !BER_BVISNULL( &op.o_req_dn ) ) slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1236 if( !BER_BVISNULL( &op.o_req_ndn ) ) slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1237 if( op.oq_search.rs_filter ) filter_free_x( opx, op.oq_search.rs_filter );
1238 if( !BER_BVISNULL( &op.ors_filterstr ) ) ch_free( op.ors_filterstr.bv_val );
1241 LDAP_LOG( TRANSPORT, ENTRY,
1242 "slap_sasl2dn: Converted SASL name to %s\n",
1243 !BER_BVISEMPTY( sasldn ) ? sasldn->bv_val : "<nothing>", 0, 0 );
1245 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
1246 !BER_BVISEMPTY( sasldn ) ? sasldn->bv_val : "<nothing>", 0, 0 );
1253 /* Check if a bind can SASL authorize to another identity.
1254 * The DNs should not have the dn: prefix
1257 int slap_sasl_authorized( Operation *op,
1258 struct berval *authcDN, struct berval *authzDN )
1260 int rc = LDAP_INAPPROPRIATE_AUTH;
1262 /* User binding as anonymous */
1263 if ( authzDN == NULL ) {
1269 LDAP_LOG( TRANSPORT, ENTRY,
1270 "slap_sasl_authorized: can %s become %s?\n",
1271 authcDN->bv_val, authzDN->bv_val, 0 );
1273 Debug( LDAP_DEBUG_TRACE,
1274 "==>slap_sasl_authorized: can %s become %s?\n",
1275 authcDN->bv_val, authzDN->bv_val, 0 );
1278 /* If person is authorizing to self, succeed */
1279 if ( dn_match( authcDN, authzDN ) ) {
1284 /* Allow the manager to authorize as any DN. */
1285 if( op->o_conn->c_authz_backend &&
1286 be_isroot_dn( op->o_conn->c_authz_backend, authcDN ))
1292 /* Check source rules */
1293 if( authz_policy & SASL_AUTHZ_TO ) {
1294 rc = slap_sasl_check_authz( op, authcDN, authzDN,
1295 slap_schema.si_ad_saslAuthzTo, authcDN );
1296 if( rc == LDAP_SUCCESS && !(authz_policy & SASL_AUTHZ_AND) ) {
1301 /* Check destination rules */
1302 if( authz_policy & SASL_AUTHZ_FROM ) {
1303 rc = slap_sasl_check_authz( op, authzDN, authcDN,
1304 slap_schema.si_ad_saslAuthzFrom, authcDN );
1305 if( rc == LDAP_SUCCESS ) {
1310 rc = LDAP_INAPPROPRIATE_AUTH;
1315 LDAP_LOG( TRANSPORT, RESULTS, "slap_sasl_authorized: return %d\n", rc,0,0 );
1317 Debug( LDAP_DEBUG_TRACE,
1318 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );