2 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
4 * Copyright 1998-2005 The OpenLDAP Foundation.
5 * Portions Copyright 2000 Mark Adamson, Carnegie Mellon.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted only as authorized by the OpenLDAP
12 * A copy of this license is available in the file LICENSE in the
13 * top-level directory of the distribution or, alternatively, at
14 * <http://www.OpenLDAP.org/license.html>.
24 #include <ac/stdlib.h>
25 #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)
40 #define LDAP_X_SCOPE_USERS ((ber_int_t) 0x0070)
43 * IDs in DNauthzid form can now have a type specifier, that
44 * influences how they are used in related operations.
46 * syntax: dn[.{exact|regex}]:<val>
48 * dn.exact: the value must pass normalization and is used
50 * dn.regex: the value is treated as a regular expression
51 * in matching DN values in authz{To|From}
53 * dn: for backwards compatibility reasons, the value
54 * is treated as a regular expression, and thus
55 * it is not normalized nor validated; it is used
56 * in exact or regex comparisons based on the
59 * IDs in DNauthzid form can now have a type specifier, that
60 * influences how they are used in related operations.
62 * syntax: u[.mech[/realm]]:<val>
64 * where mech is a SIMPLE, AUTHZ, or a SASL mechanism name
65 * and realm is mechanism specific realm (separate to those
66 * which are representable as part of the principal).
69 typedef struct sasl_regexp {
70 char *sr_match; /* regexp match pattern */
71 char *sr_replace; /* regexp replace pattern */
72 regex_t sr_workspace; /* workspace for regexp engine */
73 int sr_offset[SASLREGEX_REPLACE+2]; /* offsets of $1,$2... in *replace */
76 static int nSaslRegexp = 0;
77 static SaslRegexp_t *SaslRegexp = NULL;
79 #ifdef SLAP_AUTH_REWRITE
81 struct rewrite_info *sasl_rwinfo = NULL;
82 #define AUTHID_CONTEXT "authid"
83 #endif /* SLAP_AUTH_REWRITE */
85 /* What SASL proxy authorization policies are allowed? */
86 #define SASL_AUTHZ_NONE 0x00
87 #define SASL_AUTHZ_FROM 0x01
88 #define SASL_AUTHZ_TO 0x02
89 #define SASL_AUTHZ_AND 0x10
91 static const char *policy_txt[] = {
92 "none", "from", "to", "any"
95 static int authz_policy = SASL_AUTHZ_NONE;
98 int slap_sasl_match( Operation *opx, struct berval *rule,
99 struct berval *assertDN, struct berval *authc );
101 int slap_sasl_setpolicy( const char *arg )
103 int rc = LDAP_SUCCESS;
105 if ( strcasecmp( arg, "none" ) == 0 ) {
106 authz_policy = SASL_AUTHZ_NONE;
107 } else if ( strcasecmp( arg, "from" ) == 0 ) {
108 authz_policy = SASL_AUTHZ_FROM;
109 } else if ( strcasecmp( arg, "to" ) == 0 ) {
110 authz_policy = SASL_AUTHZ_TO;
111 } else if ( strcasecmp( arg, "both" ) == 0 || strcasecmp( arg, "any" ) == 0 ) {
112 authz_policy = SASL_AUTHZ_FROM | SASL_AUTHZ_TO;
113 } else if ( strcasecmp( arg, "all" ) == 0 ) {
114 authz_policy = SASL_AUTHZ_FROM | SASL_AUTHZ_TO | SASL_AUTHZ_AND;
121 const char * slap_sasl_getpolicy()
123 if ( authz_policy == (SASL_AUTHZ_FROM | SASL_AUTHZ_TO | SASL_AUTHZ_AND) )
126 return policy_txt[authz_policy];
129 int slap_parse_user( struct berval *id, struct berval *user,
130 struct berval *realm, struct berval *mech )
134 assert( id != NULL );
135 assert( !BER_BVISNULL( id ) );
136 assert( user != NULL );
137 assert( realm != NULL );
138 assert( mech != NULL );
142 if ( u != 'u' && u != 'U' ) {
143 /* called with something other than u: */
144 return LDAP_PROTOCOL_ERROR;
148 * u[.mech[/realm]]:user
151 user->bv_val = strchr( id->bv_val, ':' );
152 if ( BER_BVISNULL( user ) ) {
153 return LDAP_PROTOCOL_ERROR;
155 user->bv_val[ 0 ] = '\0';
157 user->bv_len = id->bv_len - ( user->bv_val - id->bv_val );
159 mech->bv_val = strchr( id->bv_val, '.' );
160 if ( !BER_BVISNULL( mech ) ) {
161 mech->bv_val[ 0 ] = '\0';
164 realm->bv_val = strchr( mech->bv_val, '/' );
166 if ( !BER_BVISNULL( realm ) ) {
167 realm->bv_val[ 0 ] = '\0';
169 mech->bv_len = realm->bv_val - mech->bv_val - 1;
170 realm->bv_len = user->bv_val - realm->bv_val - 1;
172 mech->bv_len = user->bv_val - mech->bv_val - 1;
179 if ( id->bv_val[ 1 ] != '\0' ) {
180 return LDAP_PROTOCOL_ERROR;
183 if ( !BER_BVISNULL( mech ) ) {
184 assert( mech->bv_val == id->bv_val + 2 );
186 AC_MEMCPY( mech->bv_val - 2, mech->bv_val, mech->bv_len + 1 );
190 if ( !BER_BVISNULL( realm ) ) {
191 assert( realm->bv_val >= id->bv_val + 2 );
193 AC_MEMCPY( realm->bv_val - 2, realm->bv_val, realm->bv_len + 1 );
197 /* leave "u:" before user */
200 user->bv_val[ 0 ] = u;
201 user->bv_val[ 1 ] = ':';
206 static int slap_parseURI( Operation *op, struct berval *uri,
207 struct berval *base, struct berval *nbase,
208 int *scope, Filter **filter, struct berval *fstr )
214 assert( uri != NULL && !BER_BVISNULL( uri ) );
221 Debug( LDAP_DEBUG_TRACE,
222 "slap_parseURI: parsing %s\n", uri->bv_val, 0, 0 );
224 rc = LDAP_PROTOCOL_ERROR;
226 * dn[.<dnstyle>]:<dnpattern>
227 * <dnstyle> ::= {exact|regex|children|subtree|onelevel}
229 * <dnstyle> defaults to "exact"
230 * if <dnstyle> is not "regex", <dnpattern> must pass DN normalization
232 if ( !strncasecmp( uri->bv_val, "dn", STRLENOF( "dn" ) ) ) {
233 bv.bv_val = uri->bv_val + STRLENOF( "dn" );
235 if ( bv.bv_val[ 0 ] == '.' ) {
238 if ( !strncasecmp( bv.bv_val, "exact:", STRLENOF( "exact:" ) ) ) {
239 bv.bv_val += STRLENOF( "exact:" );
240 *scope = LDAP_X_SCOPE_EXACT;
242 } else if ( !strncasecmp( bv.bv_val, "regex:", STRLENOF( "regex:" ) ) ) {
243 bv.bv_val += STRLENOF( "regex:" );
244 *scope = LDAP_X_SCOPE_REGEX;
246 } else if ( !strncasecmp( bv.bv_val, "children:", STRLENOF( "children:" ) ) ) {
247 bv.bv_val += STRLENOF( "children:" );
248 *scope = LDAP_X_SCOPE_CHILDREN;
250 } else if ( !strncasecmp( bv.bv_val, "subtree:", STRLENOF( "subtree:" ) ) ) {
251 bv.bv_val += STRLENOF( "subtree:" );
252 *scope = LDAP_X_SCOPE_SUBTREE;
254 } else if ( !strncasecmp( bv.bv_val, "onelevel:", STRLENOF( "onelevel:" ) ) ) {
255 bv.bv_val += STRLENOF( "onelevel:" );
256 *scope = LDAP_X_SCOPE_ONELEVEL;
259 return LDAP_PROTOCOL_ERROR;
263 if ( bv.bv_val[ 0 ] != ':' ) {
264 return LDAP_PROTOCOL_ERROR;
266 *scope = LDAP_X_SCOPE_EXACT;
270 bv.bv_val += strspn( bv.bv_val, " " );
271 /* jump here in case no type specification was present
272 * and uri was not an URI... HEADS-UP: assuming EXACT */
273 is_dn: bv.bv_len = uri->bv_len - (bv.bv_val - uri->bv_val);
275 /* a single '*' means any DN without using regexes */
276 if ( ber_bvccmp( &bv, '*' ) ) {
277 *scope = LDAP_X_SCOPE_USERS;
281 case LDAP_X_SCOPE_EXACT:
282 case LDAP_X_SCOPE_CHILDREN:
283 case LDAP_X_SCOPE_SUBTREE:
284 case LDAP_X_SCOPE_ONELEVEL:
285 rc = dnNormalize( 0, NULL, NULL, &bv, nbase, op->o_tmpmemctx );
286 if( rc != LDAP_SUCCESS ) {
291 case LDAP_X_SCOPE_REGEX:
292 ber_dupbv_x( nbase, &bv, op->o_tmpmemctx );
294 case LDAP_X_SCOPE_USERS:
308 } else if ( ( uri->bv_val[ 0 ] == 'u' || uri->bv_val[ 0 ] == 'U' )
309 && ( uri->bv_val[ 1 ] == ':'
310 || uri->bv_val[ 1 ] == '/'
311 || uri->bv_val[ 1 ] == '.' ) )
313 Connection c = *op->o_conn;
314 char buf[ SLAP_LDAPDN_MAXLEN ];
320 if ( sizeof( buf ) <= uri->bv_len ) {
321 return LDAP_INVALID_SYNTAX;
324 id.bv_len = uri->bv_len;
326 strncpy( buf, uri->bv_val, sizeof( buf ) );
328 rc = slap_parse_user( &id, &user, &realm, &mech );
329 if ( rc != LDAP_SUCCESS ) {
333 if ( !BER_BVISNULL( &mech ) ) {
334 c.c_sasl_bind_mech = mech;
336 BER_BVSTR( &c.c_sasl_bind_mech, "AUTHZ" );
339 rc = slap_sasl_getdn( &c, op, &user,
340 realm.bv_val, nbase, SLAP_GETDN_AUTHZID );
342 if ( rc == LDAP_SUCCESS ) {
343 *scope = LDAP_X_SCOPE_EXACT;
349 * group[/<groupoc>[/<groupat>]]:<groupdn>
351 * groupoc defaults to "groupOfNames"
352 * groupat defaults to "member"
354 * <groupdn> must pass DN normalization
356 } else if ( strncasecmp( uri->bv_val, "group", STRLENOF( "group" ) ) == 0 )
358 struct berval group_dn = BER_BVNULL,
359 group_oc = BER_BVNULL,
360 member_at = BER_BVNULL;
363 bv.bv_val = uri->bv_val + STRLENOF( "group" );
364 group_dn.bv_val = strchr( bv.bv_val, ':' );
365 if ( group_dn.bv_val == NULL ) {
366 /* last chance: assume it's a(n exact) DN ... */
367 bv.bv_val = uri->bv_val;
368 *scope = LDAP_X_SCOPE_EXACT;
372 if ( bv.bv_val[ 0 ] == '/' ) {
373 group_oc.bv_val = &bv.bv_val[ 1 ];
375 member_at.bv_val = strchr( group_oc.bv_val, '/' );
376 if ( member_at.bv_val ) {
377 group_oc.bv_len = member_at.bv_val - group_oc.bv_val;
379 member_at.bv_len = group_dn.bv_val - member_at.bv_val;
382 group_oc.bv_len = group_dn.bv_val - group_oc.bv_val;
383 BER_BVSTR( &member_at, "member" );
387 BER_BVSTR( &group_oc, "groupOfNames" );
390 group_dn.bv_len = uri->bv_len - ( group_dn.bv_val - uri->bv_val );
392 rc = dnNormalize( 0, NULL, NULL, &group_dn, nbase, op->o_tmpmemctx );
393 if ( rc != LDAP_SUCCESS ) {
397 *scope = LDAP_X_SCOPE_GROUP;
399 /* FIXME: caller needs to add value of member attribute
400 * and close brackets twice */
401 fstr->bv_len = STRLENOF( "(&(objectClass=)(=" /* )) */ )
402 + group_oc.bv_len + member_at.bv_len;
403 fstr->bv_val = ch_malloc( fstr->bv_len + 1 );
405 tmp = lutil_strncopy( fstr->bv_val, "(&(objectClass=" /* )) */ ,
406 STRLENOF( "(&(objectClass=" /* )) */ ) );
407 tmp = lutil_strncopy( tmp, group_oc.bv_val, group_oc.bv_len );
408 tmp = lutil_strncopy( tmp, /* ( */ ")(" /* ) */ ,
409 STRLENOF( /* ( */ ")(" /* ) */ ) );
410 tmp = lutil_strncopy( tmp, member_at.bv_val, member_at.bv_len );
411 tmp = lutil_strncopy( tmp, "=", STRLENOF( "=" ) );
417 * ldap:///<base>??<scope>?<filter>
418 * <scope> ::= {base|one|subtree}
420 * <scope> defaults to "base"
421 * <base> must pass DN normalization
422 * <filter> must pass str2filter()
424 rc = ldap_url_parse( uri->bv_val, &ludp );
426 case LDAP_URL_SUCCESS:
427 /* FIXME: the check is pedantic, but I think it's necessary,
428 * because people tend to use things like ldaps:// which
429 * gives the idea SSL is being used. Maybe we could
430 * accept ldapi:// as well, but the point is that we use
431 * an URL as an easy means to define bits of a search with
434 if ( strcasecmp( ludp->lud_scheme, "ldap" ) != 0 ) {
438 return LDAP_PROTOCOL_ERROR;
442 case LDAP_URL_ERR_BADSCHEME:
444 * last chance: assume it's a(n exact) DN ...
446 * NOTE: must pass DN normalization
448 bv.bv_val = uri->bv_val;
449 *scope = LDAP_X_SCOPE_EXACT;
453 return LDAP_PROTOCOL_ERROR;
456 if ( ( ludp->lud_host && *ludp->lud_host )
457 || ludp->lud_attrs || ludp->lud_exts )
459 /* host part must be empty */
460 /* attrs and extensions parts must be empty */
461 rc = LDAP_PROTOCOL_ERROR;
466 *scope = ludp->lud_scope;
468 /* Grab the filter */
469 if ( ludp->lud_filter ) {
470 *filter = str2filter_x( op, ludp->lud_filter );
471 if ( *filter == NULL ) {
472 rc = LDAP_PROTOCOL_ERROR;
475 ber_str2bv( ludp->lud_filter, 0, 0, fstr );
478 /* Grab the searchbase */
479 ber_str2bv( ludp->lud_dn, 0, 0, base );
480 rc = dnNormalize( 0, NULL, NULL, base, nbase, op->o_tmpmemctx );
483 if( rc != LDAP_SUCCESS ) {
484 if( *filter ) filter_free_x( op, *filter );
488 /* Don't free these, return them to caller */
489 ludp->lud_filter = NULL;
493 ldap_free_urldesc( ludp );
497 #ifndef SLAP_AUTH_REWRITE
498 static int slap_sasl_rx_off(char *rep, int *off)
503 /* Precompile replace pattern. Find the $<n> placeholders */
506 for ( c = rep; *c; c++ ) {
507 if ( *c == '\\' && c[1] ) {
512 if ( n == SASLREGEX_REPLACE ) {
513 Debug( LDAP_DEBUG_ANY,
514 "SASL replace pattern %s has too many $n "
515 "placeholders (max %d)\n",
516 rep, SASLREGEX_REPLACE, 0 );
518 return( LDAP_OTHER );
525 /* Final placeholder, after the last $n */
529 return( LDAP_SUCCESS );
531 #endif /* ! SLAP_AUTH_REWRITE */
533 #ifdef SLAP_AUTH_REWRITE
534 int slap_sasl_rewrite_config(
544 /* init at first call */
545 if ( sasl_rwinfo == NULL ) {
546 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
549 /* strip "authid-" prefix for parsing */
551 argv[0] += STRLENOF( "authid-" );
552 rc = rewrite_parse( sasl_rwinfo, fname, lineno, argc, argv );
558 int slap_sasl_rewrite_destroy( void )
561 rewrite_info_delete( &sasl_rwinfo );
568 int slap_sasl_regexp_rewrite_config(
573 const char *context )
576 char *argvRule[] = { "rewriteRule", NULL, NULL, ":@", NULL };
578 /* init at first call */
579 if ( sasl_rwinfo == NULL ) {
580 char *argvEngine[] = { "rewriteEngine", "on", NULL };
581 char *argvContext[] = { "rewriteContext", NULL, NULL };
583 /* initialize rewrite engine */
584 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
586 /* switch on rewrite engine */
587 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvEngine );
588 if (rc != LDAP_SUCCESS) {
592 /* create generic authid context */
593 argvContext[1] = AUTHID_CONTEXT;
594 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvContext );
595 if (rc != LDAP_SUCCESS) {
600 argvRule[1] = (char *)match;
601 argvRule[2] = (char *)replace;
602 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 4, argvRule );
606 #endif /* SLAP_AUTH_REWRITE */
608 int slap_sasl_regexp_config( const char *match, const char *replace )
613 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
614 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
616 reg = &SaslRegexp[nSaslRegexp];
618 reg->sr_match = ch_strdup( match );
619 reg->sr_replace = ch_strdup( replace );
621 #ifdef SLAP_AUTH_REWRITE
622 rc = slap_sasl_regexp_rewrite_config( "sasl-regexp", 0,
623 match, replace, AUTHID_CONTEXT );
624 if ( rc == LDAP_SUCCESS ) nSaslRegexp++;
626 #else /* ! SLAP_AUTH_REWRITE */
628 /* Precompile matching pattern */
629 rc = regcomp( ®->sr_workspace, reg->sr_match, REG_EXTENDED|REG_ICASE );
631 Debug( LDAP_DEBUG_ANY,
632 "SASL match pattern %s could not be compiled by regexp engine\n",
633 reg->sr_match, 0, 0 );
635 return( LDAP_OTHER );
638 rc = slap_sasl_rx_off( reg->sr_replace, reg->sr_offset );
639 if ( rc != LDAP_SUCCESS ) return rc;
642 return( LDAP_SUCCESS );
643 #endif /* ! SLAP_AUTH_REWRITE */
646 void slap_sasl_regexp_unparse( BerVarray *out )
649 BerVarray bva = NULL;
653 if ( !nSaslRegexp ) return;
656 bva = ch_malloc( (nSaslRegexp+1) * sizeof(struct berval) );
657 BER_BVZERO(bva+nSaslRegexp);
658 for ( i=0; i<nSaslRegexp; i++ ) {
659 idx.bv_len = sprintf( idx.bv_val, "{%d}", i);
660 bva[i].bv_len = idx.bv_len + strlen( SaslRegexp[i].sr_match ) +
661 strlen( SaslRegexp[i].sr_replace ) + 5;
662 bva[i].bv_val = ch_malloc( bva[i].bv_len+1 );
663 ptr = lutil_strcopy( bva[i].bv_val, ibuf );
665 ptr = lutil_strcopy( ptr, SaslRegexp[i].sr_match );
666 ptr = lutil_strcopy( ptr, "\" \"" );
667 ptr = lutil_strcopy( ptr, SaslRegexp[i].sr_replace );
674 #ifndef SLAP_AUTH_REWRITE
675 /* Perform replacement on regexp matches */
676 static void slap_sasl_rx_exp(
680 const char *saslname,
684 int i, n, len, insert;
686 /* Get the total length of the final URI */
690 while( off[n] >= 0 ) {
691 /* Len of next section from replacement string (x,y,z above) */
692 len += off[n] - off[n-1] - 2;
696 /* Len of string from saslname that matched next $i (b,d above) */
697 i = rep[ off[n] + 1 ] - '0';
698 len += str[i].rm_eo - str[i].rm_so;
701 out->bv_val = slap_sl_malloc( len + 1, ctx );
704 /* Fill in URI with replace string, replacing $i as we go */
707 while( off[n] >= 0) {
708 /* Paste in next section from replacement string (x,y,z above) */
709 len = off[n] - off[n-1] - 2;
710 strncpy( out->bv_val+insert, rep + off[n-1] + 2, len);
715 /* Paste in string from saslname that matched next $i (b,d above) */
716 i = rep[ off[n] + 1 ] - '0';
717 len = str[i].rm_eo - str[i].rm_so;
718 strncpy( out->bv_val+insert, saslname + str[i].rm_so, len );
724 out->bv_val[insert] = '\0';
726 #endif /* ! SLAP_AUTH_REWRITE */
728 /* Take the passed in SASL name and attempt to convert it into an
729 LDAP URI to find the matching LDAP entry, using the pattern matching
730 strings given in the saslregexp config file directive(s) */
732 static int slap_authz_regexp( struct berval *in, struct berval *out,
733 int flags, void *ctx )
735 #ifdef SLAP_AUTH_REWRITE
736 const char *context = AUTHID_CONTEXT;
738 if ( sasl_rwinfo == NULL || BER_BVISNULL( in ) ) {
742 /* FIXME: if aware of authc/authz mapping,
743 * we could use different contexts ... */
744 switch ( rewrite_session( sasl_rwinfo, context, in->bv_val, NULL,
747 case REWRITE_REGEXEC_OK:
748 if ( !BER_BVISNULL( out ) ) {
749 char *val = out->bv_val;
750 ber_str2bv_x( val, 0, 1, out, ctx );
751 if ( val != in->bv_val ) {
755 ber_dupbv_x( out, in, ctx );
757 Debug( LDAP_DEBUG_ARGS,
758 "[rw] %s: \"%s\" -> \"%s\"\n",
759 context, in->bv_val, out->bv_val );
762 case REWRITE_REGEXEC_UNWILLING:
763 case REWRITE_REGEXEC_ERR:
768 #else /* ! SLAP_AUTH_REWRITE */
769 char *saslname = in->bv_val;
771 regmatch_t sr_strings[SASLREGEX_REPLACE]; /* strings matching $1,$2 ... */
774 memset( out, 0, sizeof( *out ) );
776 Debug( LDAP_DEBUG_TRACE, "slap_authz_regexp: converting SASL name %s\n",
779 if (( saslname == NULL ) || ( nSaslRegexp == 0 )) {
783 /* Match the normalized SASL name to the saslregexp patterns */
784 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
785 if ( regexec( ®->sr_workspace, saslname, SASLREGEX_REPLACE,
786 sr_strings, 0) == 0 )
790 if( i >= nSaslRegexp ) return( 0 );
793 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
794 * replace pattern of the form "x$1y$2z". The returned string needs
795 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
797 slap_sasl_rx_exp( reg->sr_replace, reg->sr_offset,
798 sr_strings, saslname, out, ctx );
800 Debug( LDAP_DEBUG_TRACE,
801 "slap_authz_regexp: converted SASL name to %s\n",
802 BER_BVISEMPTY( out ) ? "" : out->bv_val, 0, 0 );
805 #endif /* ! SLAP_AUTH_REWRITE */
808 /* This callback actually does some work...*/
809 static int sasl_sc_sasl2dn( Operation *o, SlapReply *rs )
811 struct berval *ndn = o->o_callback->sc_private;
813 if (rs->sr_type != REP_SEARCH) return 0;
815 /* We only want to be called once */
816 if ( !BER_BVISNULL( ndn ) ) {
817 o->o_tmpfree(ndn->bv_val, o->o_tmpmemctx);
820 Debug( LDAP_DEBUG_TRACE,
821 "slap_sc_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
825 ber_dupbv_x(ndn, &rs->sr_entry->e_nname, o->o_tmpmemctx);
830 typedef struct smatch_info {
835 static int sasl_sc_smatch( Operation *o, SlapReply *rs )
837 smatch_info *sm = o->o_callback->sc_private;
839 if ( rs->sr_type != REP_SEARCH ) {
840 if ( rs->sr_err != LDAP_SUCCESS ) {
846 if ( sm->match == 1 ) {
851 if (dn_match(sm->dn, &rs->sr_entry->e_nname)) {
862 slap_sasl_matches( Operation *op, BerVarray rules,
863 struct berval *assertDN, struct berval *authc )
865 int rc = LDAP_INAPPROPRIATE_AUTH;
867 if ( rules != NULL ) {
870 for( i = 0; !BER_BVISNULL( &rules[i] ); i++ ) {
871 rc = slap_sasl_match( op, &rules[i], assertDN, authc );
872 if ( rc == LDAP_SUCCESS ) break;
880 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
881 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
882 * the rule must be used as an internal search for entries. If that search
883 * returns the *assertDN entry, the match is successful.
885 * The assertDN should not have the dn: prefix
889 int slap_sasl_match( Operation *opx, struct berval *rule,
890 struct berval *assertDN, struct berval *authc )
895 slap_callback cb = { NULL, sasl_sc_smatch, NULL, NULL };
897 SlapReply rs = {REP_RESULT};
898 struct berval base = BER_BVNULL;
904 Debug( LDAP_DEBUG_TRACE,
905 "===>slap_sasl_match: comparing DN %s to rule %s\n",
906 assertDN->bv_val, rule->bv_val, 0 );
908 rc = slap_parseURI( opx, rule, &base,
909 &op.o_req_ndn, &op.ors_scope, &op.ors_filter,
911 if( rc != LDAP_SUCCESS ) goto CONCLUDED;
913 switch ( op.ors_scope ) {
914 case LDAP_X_SCOPE_EXACT:
916 if ( dn_match( &op.o_req_ndn, assertDN ) ) {
919 rc = LDAP_INAPPROPRIATE_AUTH;
923 case LDAP_X_SCOPE_CHILDREN:
924 case LDAP_X_SCOPE_SUBTREE:
925 case LDAP_X_SCOPE_ONELEVEL:
927 int d = assertDN->bv_len - op.o_req_ndn.bv_len;
929 rc = LDAP_INAPPROPRIATE_AUTH;
931 if ( d == 0 && op.ors_scope == LDAP_X_SCOPE_SUBTREE ) {
934 } else if ( d > 0 ) {
937 /* leave room for at least one char of attributeType,
938 * one for '=' and one for ',' */
939 if ( d < STRLENOF( "x=,") ) {
943 bv.bv_len = op.o_req_ndn.bv_len;
944 bv.bv_val = assertDN->bv_val + d;
946 if ( bv.bv_val[ -1 ] == ',' && dn_match( &op.o_req_ndn, &bv ) ) {
947 switch ( op.ors_scope ) {
948 case LDAP_X_SCOPE_SUBTREE:
949 case LDAP_X_SCOPE_CHILDREN:
953 case LDAP_X_SCOPE_ONELEVEL:
957 dnParent( assertDN, &pdn );
958 /* the common portion of the DN
959 * already matches, so only check
960 * if parent DN of assertedDN
961 * is all the pattern */
962 if ( pdn.bv_len == op.o_req_ndn.bv_len ) {
968 /* at present, impossible */
976 case LDAP_X_SCOPE_REGEX:
977 rc = regcomp(®, op.o_req_ndn.bv_val,
978 REG_EXTENDED|REG_ICASE|REG_NOSUB);
980 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
986 rc = LDAP_INAPPROPRIATE_AUTH;
990 case LDAP_X_SCOPE_GROUP: {
993 /* Now filterstr looks like "(&(objectClass=<group_oc>)(<member_at>="
994 * we need to append the <assertDN> so that the <group_dn> is searched
995 * with scope "base", and the filter ensures that <assertDN> is
996 * member of the group */
997 tmp = ch_realloc( op.ors_filterstr.bv_val, op.ors_filterstr.bv_len +
998 assertDN->bv_len + STRLENOF( /*"(("*/ "))" ) + 1 );
1000 rc = LDAP_NO_MEMORY;
1003 op.ors_filterstr.bv_val = tmp;
1005 tmp = lutil_strcopy( &tmp[op.ors_filterstr.bv_len], assertDN->bv_val );
1006 tmp = lutil_strcopy( tmp, /*"(("*/ "))" );
1008 /* pass opx because str2filter_x may (and does) use o_tmpmfuncs */
1009 op.ors_filter = str2filter_x( opx, op.ors_filterstr.bv_val );
1010 if ( op.ors_filter == NULL ) {
1011 rc = LDAP_PROTOCOL_ERROR;
1014 op.ors_scope = LDAP_SCOPE_BASE;
1016 /* hijack match DN: use that of the group instead of the assertDN;
1017 * assertDN is now in the filter */
1018 sm.dn = &op.o_req_ndn;
1024 case LDAP_X_SCOPE_USERS:
1025 if ( !BER_BVISEMPTY( assertDN ) ) {
1028 rc = LDAP_INAPPROPRIATE_AUTH;
1036 /* Must run an internal search. */
1037 if ( op.ors_filter == NULL ) {
1038 rc = LDAP_FILTER_ERROR;
1042 Debug( LDAP_DEBUG_TRACE,
1043 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
1044 op.o_req_ndn.bv_val, op.ors_scope, 0 );
1046 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
1047 if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
1048 rc = LDAP_INAPPROPRIATE_AUTH;
1052 op.o_hdr = opx->o_hdr;
1053 op.o_tag = LDAP_REQ_SEARCH;
1055 op.o_callback = &cb;
1056 op.o_time = slap_get_time();
1057 op.o_do_not_cache = 1;
1058 op.o_is_auth_check = 1;
1059 /* use req_ndn as req_dn instead of non-pretty base of uri */
1060 if( !BER_BVISNULL( &base ) ) {
1061 ch_free( base.bv_val );
1062 /* just in case... */
1063 BER_BVZERO( &base );
1065 ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1066 op.ors_deref = LDAP_DEREF_NEVER;
1068 op.ors_tlimit = SLAP_NO_LIMIT;
1069 op.ors_attrs = slap_anlist_no_attrs;
1070 op.ors_attrsonly = 1;
1072 op.o_bd->be_search( &op, &rs );
1074 if (sm.match == 1) {
1077 rc = LDAP_INAPPROPRIATE_AUTH;
1081 if( !BER_BVISNULL( &op.o_req_dn ) ) slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1082 if( !BER_BVISNULL( &op.o_req_ndn ) ) slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1083 if( op.ors_filter ) filter_free_x( opx, op.ors_filter );
1084 if( !BER_BVISNULL( &op.ors_filterstr ) ) ch_free( op.ors_filterstr.bv_val );
1086 Debug( LDAP_DEBUG_TRACE,
1087 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
1094 * This function answers the question, "Can this ID authorize to that ID?",
1095 * based on authorization rules. The rules are stored in the *searchDN, in the
1096 * attribute named by *attr. If any of those rules map to the *assertDN, the
1097 * authorization is approved.
1099 * The DNs should not have the dn: prefix
1102 slap_sasl_check_authz( Operation *op,
1103 struct berval *searchDN,
1104 struct berval *assertDN,
1105 AttributeDescription *ad,
1106 struct berval *authc )
1109 BerVarray vals = NULL;
1111 Debug( LDAP_DEBUG_TRACE,
1112 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
1113 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
1115 rc = backend_attribute( op, NULL, searchDN, ad, &vals, ACL_AUTH );
1116 if( rc != LDAP_SUCCESS ) goto COMPLETE;
1118 /* Check if the *assertDN matches any *vals */
1119 rc = slap_sasl_matches( op, vals, assertDN, authc );
1122 if( vals ) ber_bvarray_free_x( vals, op->o_tmpmemctx );
1124 Debug( LDAP_DEBUG_TRACE,
1125 "<==slap_sasl_check_authz: %s check returning %d\n",
1126 ad->ad_cname.bv_val, rc, 0);
1132 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
1133 * return the LDAP DN to which it matches. The SASL regexp rules in the config
1134 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
1135 * search with scope=base), just return the URI (or its searchbase). Otherwise
1136 * an internal search must be done, and if that search returns exactly one
1137 * entry, return the DN of that one entry.
1139 void slap_sasl2dn( Operation *opx,
1140 struct berval *saslname, struct berval *sasldn, int flags )
1143 slap_callback cb = { NULL, sasl_sc_sasl2dn, NULL, NULL };
1145 SlapReply rs = {REP_RESULT};
1146 struct berval regout = BER_BVNULL;
1147 struct berval base = BER_BVNULL;
1149 Debug( LDAP_DEBUG_TRACE, "==>slap_sasl2dn: "
1150 "converting SASL name %s to a DN\n",
1151 saslname->bv_val, 0,0 );
1153 BER_BVZERO( sasldn );
1154 cb.sc_private = sasldn;
1156 /* Convert the SASL name into a minimal URI */
1157 if( !slap_authz_regexp( saslname, ®out, flags, opx->o_tmpmemctx ) ) {
1161 rc = slap_parseURI( opx, ®out, &base,
1162 &op.o_req_ndn, &op.ors_scope, &op.ors_filter,
1163 &op.ors_filterstr );
1164 if ( !BER_BVISNULL( ®out ) ) slap_sl_free( regout.bv_val, opx->o_tmpmemctx );
1165 if ( rc != LDAP_SUCCESS ) {
1169 /* Must do an internal search */
1170 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
1172 switch ( op.ors_scope ) {
1173 case LDAP_X_SCOPE_EXACT:
1174 *sasldn = op.o_req_ndn;
1175 BER_BVZERO( &op.o_req_ndn );
1176 /* intentionally continue to next case */
1178 case LDAP_X_SCOPE_REGEX:
1179 case LDAP_X_SCOPE_SUBTREE:
1180 case LDAP_X_SCOPE_CHILDREN:
1181 case LDAP_X_SCOPE_ONELEVEL:
1182 case LDAP_X_SCOPE_GROUP:
1183 case LDAP_X_SCOPE_USERS:
1184 /* correctly parsed, but illegal */
1187 case LDAP_SCOPE_BASE:
1188 case LDAP_SCOPE_ONELEVEL:
1189 case LDAP_SCOPE_SUBTREE:
1190 #ifdef LDAP_SCOPE_SUBORDINATE
1191 case LDAP_SCOPE_SUBORDINATE:
1197 /* catch unhandled cases (there shouldn't be) */
1201 Debug( LDAP_DEBUG_TRACE,
1202 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
1203 op.o_req_ndn.bv_val, op.ors_scope, 0 );
1205 if ( ( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL) ) {
1209 /* Must run an internal search. */
1210 if ( op.ors_filter == NULL ) {
1211 rc = LDAP_FILTER_ERROR;
1215 op.o_hdr = opx->o_hdr;
1216 op.o_tag = LDAP_REQ_SEARCH;
1217 op.o_ndn = opx->o_conn->c_ndn;
1218 op.o_callback = &cb;
1219 op.o_time = slap_get_time();
1220 op.o_do_not_cache = 1;
1221 op.o_is_auth_check = 1;
1222 op.ors_deref = LDAP_DEREF_NEVER;
1224 op.ors_tlimit = SLAP_NO_LIMIT;
1225 op.ors_attrs = slap_anlist_no_attrs;
1226 op.ors_attrsonly = 1;
1227 /* use req_ndn as req_dn instead of non-pretty base of uri */
1228 if( !BER_BVISNULL( &base ) ) {
1229 ch_free( base.bv_val );
1230 /* just in case... */
1231 BER_BVZERO( &base );
1233 ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1235 op.o_bd->be_search( &op, &rs );
1238 if( !BER_BVISEMPTY( sasldn ) ) {
1239 opx->o_conn->c_authz_backend = op.o_bd;
1241 if( !BER_BVISNULL( &op.o_req_dn ) ) {
1242 slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1244 if( !BER_BVISNULL( &op.o_req_ndn ) ) {
1245 slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1247 if( op.ors_filter ) {
1248 filter_free_x( opx, op.ors_filter );
1250 if( !BER_BVISNULL( &op.ors_filterstr ) ) {
1251 ch_free( op.ors_filterstr.bv_val );
1254 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
1255 !BER_BVISEMPTY( sasldn ) ? sasldn->bv_val : "<nothing>", 0, 0 );
1261 /* Check if a bind can SASL authorize to another identity.
1262 * The DNs should not have the dn: prefix
1265 int slap_sasl_authorized( Operation *op,
1266 struct berval *authcDN, struct berval *authzDN )
1268 int rc = LDAP_INAPPROPRIATE_AUTH;
1270 /* User binding as anonymous */
1271 if ( authzDN == NULL ) {
1276 Debug( LDAP_DEBUG_TRACE,
1277 "==>slap_sasl_authorized: can %s become %s?\n",
1278 authcDN->bv_val, authzDN->bv_val, 0 );
1280 /* If person is authorizing to self, succeed */
1281 if ( dn_match( authcDN, authzDN ) ) {
1286 /* Allow the manager to authorize as any DN. */
1287 if( op->o_conn->c_authz_backend &&
1288 be_isroot_dn( op->o_conn->c_authz_backend, authcDN ))
1294 /* Check source rules */
1295 if( authz_policy & SASL_AUTHZ_TO ) {
1296 rc = slap_sasl_check_authz( op, authcDN, authzDN,
1297 slap_schema.si_ad_saslAuthzTo, authcDN );
1298 if( rc == LDAP_SUCCESS && !(authz_policy & SASL_AUTHZ_AND) ) {
1303 /* Check destination rules */
1304 if( authz_policy & SASL_AUTHZ_FROM ) {
1305 rc = slap_sasl_check_authz( op, authzDN, authcDN,
1306 slap_schema.si_ad_saslAuthzFrom, authcDN );
1307 if( rc == LDAP_SUCCESS ) {
1312 rc = LDAP_INAPPROPRIATE_AUTH;
1316 Debug( LDAP_DEBUG_TRACE,
1317 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );