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 )
135 assert( !BER_BVISNULL( id ) );
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 static int slap_sasl_rx_off(char *rep, int *off)
502 /* Precompile replace pattern. Find the $<n> placeholders */
505 for ( c = rep; *c; c++ ) {
506 if ( *c == '\\' && c[1] ) {
511 if ( n == SASLREGEX_REPLACE ) {
512 Debug( LDAP_DEBUG_ANY,
513 "SASL replace pattern %s has too many $n "
514 "placeholders (max %d)\n",
515 rep, SASLREGEX_REPLACE, 0 );
517 return( LDAP_OTHER );
524 /* Final placeholder, after the last $n */
528 return( LDAP_SUCCESS );
531 #ifdef SLAP_AUTH_REWRITE
532 int slap_sasl_rewrite_config(
542 /* init at first call */
543 if ( sasl_rwinfo == NULL ) {
544 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
547 /* strip "authid-" prefix for parsing */
549 argv[0] += STRLENOF( "authid-" );
550 rc = rewrite_parse( sasl_rwinfo, fname, lineno, argc, argv );
556 int slap_sasl_rewrite_destroy( void )
559 rewrite_info_delete( &sasl_rwinfo );
566 int slap_sasl_regexp_rewrite_config(
571 const char *context )
574 char *argvRule[] = { "rewriteRule", NULL, NULL, ":@", NULL };
576 /* init at first call */
577 if ( sasl_rwinfo == NULL ) {
578 char *argvEngine[] = { "rewriteEngine", "on", NULL };
579 char *argvContext[] = { "rewriteContext", NULL, NULL };
581 /* initialize rewrite engine */
582 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
584 /* switch on rewrite engine */
585 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvEngine );
586 if (rc != LDAP_SUCCESS) {
590 /* create generic authid context */
591 argvContext[1] = AUTHID_CONTEXT;
592 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvContext );
593 if (rc != LDAP_SUCCESS) {
598 argvRule[1] = (char *)match;
599 argvRule[2] = (char *)replace;
600 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 4, argvRule );
604 #endif /* SLAP_AUTH_REWRITE */
606 int slap_sasl_regexp_config( const char *match, const char *replace )
611 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
612 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
614 reg = &SaslRegexp[nSaslRegexp];
616 reg->sr_match = ch_strdup( match );
617 reg->sr_replace = ch_strdup( replace );
619 #ifdef SLAP_AUTH_REWRITE
620 rc = slap_sasl_regexp_rewrite_config( "sasl-regexp", 0,
621 match, replace, AUTHID_CONTEXT );
622 if ( rc == LDAP_SUCCESS ) nSaslRegexp++;
624 #else /* ! SLAP_AUTH_REWRITE */
626 /* Precompile matching pattern */
627 rc = regcomp( ®->sr_workspace, reg->sr_match, REG_EXTENDED|REG_ICASE );
629 Debug( LDAP_DEBUG_ANY,
630 "SASL match pattern %s could not be compiled by regexp engine\n",
631 reg->sr_match, 0, 0 );
633 return( LDAP_OTHER );
636 rc = slap_sasl_rx_off( reg->sr_replace, reg->sr_offset );
637 if ( rc != LDAP_SUCCESS ) return rc;
640 return( LDAP_SUCCESS );
641 #endif /* ! SLAP_AUTH_REWRITE */
644 void slap_sasl_regexp_unparse( BerVarray *out )
648 BerVarray bva = NULL;
652 if ( !nSaslRegexp ) return;
655 bva = ch_malloc( (nSaslRegexp+1) * sizeof(struct berval) );
656 BER_BVZERO(bva+nSaslRegexp);
657 for ( i=0; i<nSaslRegexp; i++ ) {
658 idx.bv_len = sprintf( idx.bv_val, "{%d}", i);
659 bva[i].bv_len = idx.bv_len + strlen( SaslRegexp[i].sr_match ) +
660 strlen( SaslRegexp[i].sr_replace ) + 5;
661 bva[i].bv_val = ch_malloc( bva[i].bv_len+1 );
662 ptr = lutil_strcopy( bva[i].bv_val, ibuf );
664 ptr = lutil_strcopy( ptr, SaslRegexp[i].sr_match );
665 ptr = lutil_strcopy( ptr, "\" \"" );
666 ptr = lutil_strcopy( ptr, SaslRegexp[i].sr_replace );
673 /* Perform replacement on regexp matches */
674 static void slap_sasl_rx_exp(
678 const char *saslname,
682 int i, n, len, insert;
684 /* Get the total length of the final URI */
688 while( off[n] >= 0 ) {
689 /* Len of next section from replacement string (x,y,z above) */
690 len += off[n] - off[n-1] - 2;
694 /* Len of string from saslname that matched next $i (b,d above) */
695 i = rep[ off[n] + 1 ] - '0';
696 len += str[i].rm_eo - str[i].rm_so;
699 out->bv_val = slap_sl_malloc( len + 1, ctx );
702 /* Fill in URI with replace string, replacing $i as we go */
705 while( off[n] >= 0) {
706 /* Paste in next section from replacement string (x,y,z above) */
707 len = off[n] - off[n-1] - 2;
708 strncpy( out->bv_val+insert, rep + off[n-1] + 2, len);
713 /* Paste in string from saslname that matched next $i (b,d above) */
714 i = rep[ off[n] + 1 ] - '0';
715 len = str[i].rm_eo - str[i].rm_so;
716 strncpy( out->bv_val+insert, saslname + str[i].rm_so, len );
722 out->bv_val[insert] = '\0';
725 /* Take the passed in SASL name and attempt to convert it into an
726 LDAP URI to find the matching LDAP entry, using the pattern matching
727 strings given in the saslregexp config file directive(s) */
729 static int slap_authz_regexp( struct berval *in, struct berval *out,
730 int flags, void *ctx )
732 #ifdef SLAP_AUTH_REWRITE
733 const char *context = AUTHID_CONTEXT;
735 if ( sasl_rwinfo == NULL || BER_BVISNULL( in ) ) {
739 /* FIXME: if aware of authc/authz mapping,
740 * we could use different contexts ... */
741 switch ( rewrite_session( sasl_rwinfo, context, in->bv_val, NULL,
744 case REWRITE_REGEXEC_OK:
745 if ( !BER_BVISNULL( out ) ) {
746 char *val = out->bv_val;
747 ber_str2bv_x( val, 0, 1, out, ctx );
748 if ( val != in->bv_val ) {
752 ber_dupbv_x( out, in, ctx );
754 Debug( LDAP_DEBUG_ARGS,
755 "[rw] %s: \"%s\" -> \"%s\"\n",
756 context, in->bv_val, out->bv_val );
759 case REWRITE_REGEXEC_UNWILLING:
760 case REWRITE_REGEXEC_ERR:
765 #else /* ! SLAP_AUTH_REWRITE */
766 char *saslname = in->bv_val;
768 regmatch_t sr_strings[SASLREGEX_REPLACE]; /* strings matching $1,$2 ... */
771 memset( out, 0, sizeof( *out ) );
773 Debug( LDAP_DEBUG_TRACE, "slap_authz_regexp: converting SASL name %s\n",
776 if (( saslname == NULL ) || ( nSaslRegexp == 0 )) {
780 /* Match the normalized SASL name to the saslregexp patterns */
781 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
782 if ( regexec( ®->sr_workspace, saslname, SASLREGEX_REPLACE,
783 sr_strings, 0) == 0 )
787 if( i >= nSaslRegexp ) return( 0 );
790 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
791 * replace pattern of the form "x$1y$2z". The returned string needs
792 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
794 slap_sasl_rx_exp( reg->sr_replace, reg->sr_offset,
795 sr_strings, saslname, out, ctx );
797 Debug( LDAP_DEBUG_TRACE,
798 "slap_authz_regexp: converted SASL name to %s\n",
799 BER_BVISEMPTY( out ) ? "" : out->bv_val, 0, 0 );
802 #endif /* ! SLAP_AUTH_REWRITE */
805 /* This callback actually does some work...*/
806 static int sasl_sc_sasl2dn( Operation *o, SlapReply *rs )
808 struct berval *ndn = o->o_callback->sc_private;
810 if (rs->sr_type != REP_SEARCH) return 0;
812 /* We only want to be called once */
813 if ( !BER_BVISNULL( ndn ) ) {
814 o->o_tmpfree(ndn->bv_val, o->o_tmpmemctx);
817 Debug( LDAP_DEBUG_TRACE,
818 "slap_sc_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
822 ber_dupbv_x(ndn, &rs->sr_entry->e_nname, o->o_tmpmemctx);
827 typedef struct smatch_info {
832 static int sasl_sc_smatch( Operation *o, SlapReply *rs )
834 smatch_info *sm = o->o_callback->sc_private;
836 if ( rs->sr_type != REP_SEARCH ) {
837 if ( rs->sr_err != LDAP_SUCCESS ) {
843 if ( sm->match == 1 ) {
848 if (dn_match(sm->dn, &rs->sr_entry->e_nname)) {
859 slap_sasl_matches( Operation *op, BerVarray rules,
860 struct berval *assertDN, struct berval *authc )
862 int rc = LDAP_INAPPROPRIATE_AUTH;
864 if ( rules != NULL ) {
867 for( i = 0; !BER_BVISNULL( &rules[i] ); i++ ) {
868 rc = slap_sasl_match( op, &rules[i], assertDN, authc );
869 if ( rc == LDAP_SUCCESS ) break;
877 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
878 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
879 * the rule must be used as an internal search for entries. If that search
880 * returns the *assertDN entry, the match is successful.
882 * The assertDN should not have the dn: prefix
886 int slap_sasl_match( Operation *opx, struct berval *rule,
887 struct berval *assertDN, struct berval *authc )
892 slap_callback cb = { NULL, sasl_sc_smatch, NULL, NULL };
894 SlapReply rs = {REP_RESULT};
895 struct berval base = BER_BVNULL;
901 Debug( LDAP_DEBUG_TRACE,
902 "===>slap_sasl_match: comparing DN %s to rule %s\n",
903 assertDN->bv_val, rule->bv_val, 0 );
905 rc = slap_parseURI( opx, rule, &base,
906 &op.o_req_ndn, &op.ors_scope, &op.ors_filter,
908 if( rc != LDAP_SUCCESS ) goto CONCLUDED;
910 switch ( op.ors_scope ) {
911 case LDAP_X_SCOPE_EXACT:
913 if ( dn_match( &op.o_req_ndn, assertDN ) ) {
916 rc = LDAP_INAPPROPRIATE_AUTH;
920 case LDAP_X_SCOPE_CHILDREN:
921 case LDAP_X_SCOPE_SUBTREE:
922 case LDAP_X_SCOPE_ONELEVEL:
924 int d = assertDN->bv_len - op.o_req_ndn.bv_len;
926 rc = LDAP_INAPPROPRIATE_AUTH;
928 if ( d == 0 && op.ors_scope == LDAP_X_SCOPE_SUBTREE ) {
931 } else if ( d > 0 ) {
934 /* leave room for at least one char of attributeType,
935 * one for '=' and one for ',' */
936 if ( d < STRLENOF( "x=,") ) {
940 bv.bv_len = op.o_req_ndn.bv_len;
941 bv.bv_val = assertDN->bv_val + d;
943 if ( bv.bv_val[ -1 ] == ',' && dn_match( &op.o_req_ndn, &bv ) ) {
944 switch ( op.ors_scope ) {
945 case LDAP_X_SCOPE_SUBTREE:
946 case LDAP_X_SCOPE_CHILDREN:
950 case LDAP_X_SCOPE_ONELEVEL:
954 dnParent( assertDN, &pdn );
955 /* the common portion of the DN
956 * already matches, so only check
957 * if parent DN of assertedDN
958 * is all the pattern */
959 if ( pdn.bv_len == op.o_req_ndn.bv_len ) {
965 /* at present, impossible */
973 case LDAP_X_SCOPE_REGEX:
974 rc = regcomp(®, op.o_req_ndn.bv_val,
975 REG_EXTENDED|REG_ICASE|REG_NOSUB);
977 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
983 rc = LDAP_INAPPROPRIATE_AUTH;
987 case LDAP_X_SCOPE_GROUP: {
990 /* Now filterstr looks like "(&(objectClass=<group_oc>)(<member_at>="
991 * we need to append the <assertDN> so that the <group_dn> is searched
992 * with scope "base", and the filter ensures that <assertDN> is
993 * member of the group */
994 tmp = ch_realloc( op.ors_filterstr.bv_val, op.ors_filterstr.bv_len +
995 assertDN->bv_len + STRLENOF( /*"(("*/ "))" ) + 1 );
1000 op.ors_filterstr.bv_val = tmp;
1002 tmp = lutil_strcopy( &tmp[op.ors_filterstr.bv_len], assertDN->bv_val );
1003 tmp = lutil_strcopy( tmp, /*"(("*/ "))" );
1005 /* pass opx because str2filter_x may (and does) use o_tmpmfuncs */
1006 op.ors_filter = str2filter_x( opx, op.ors_filterstr.bv_val );
1007 if ( op.ors_filter == NULL ) {
1008 rc = LDAP_PROTOCOL_ERROR;
1011 op.ors_scope = LDAP_SCOPE_BASE;
1013 /* hijack match DN: use that of the group instead of the assertDN;
1014 * assertDN is now in the filter */
1015 sm.dn = &op.o_req_ndn;
1021 case LDAP_X_SCOPE_USERS:
1022 if ( !BER_BVISEMPTY( assertDN ) ) {
1025 rc = LDAP_INAPPROPRIATE_AUTH;
1033 /* Must run an internal search. */
1034 if ( op.ors_filter == NULL ) {
1035 rc = LDAP_FILTER_ERROR;
1039 Debug( LDAP_DEBUG_TRACE,
1040 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
1041 op.o_req_ndn.bv_val, op.ors_scope, 0 );
1043 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
1044 if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
1045 rc = LDAP_INAPPROPRIATE_AUTH;
1049 op.o_hdr = opx->o_hdr;
1050 op.o_tag = LDAP_REQ_SEARCH;
1052 op.o_callback = &cb;
1053 op.o_time = slap_get_time();
1054 op.o_do_not_cache = 1;
1055 op.o_is_auth_check = 1;
1056 /* use req_ndn as req_dn instead of non-pretty base of uri */
1057 if( !BER_BVISNULL( &base ) ) {
1058 ch_free( base.bv_val );
1059 /* just in case... */
1060 BER_BVZERO( &base );
1062 ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1064 op.ors_tlimit = SLAP_NO_LIMIT;
1065 op.ors_attrs = slap_anlist_no_attrs;
1066 op.ors_attrsonly = 1;
1068 op.o_bd->be_search( &op, &rs );
1070 if (sm.match == 1) {
1073 rc = LDAP_INAPPROPRIATE_AUTH;
1077 if( !BER_BVISNULL( &op.o_req_dn ) ) slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1078 if( !BER_BVISNULL( &op.o_req_ndn ) ) slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1079 if( op.ors_filter ) filter_free_x( opx, op.ors_filter );
1080 if( !BER_BVISNULL( &op.ors_filterstr ) ) ch_free( op.ors_filterstr.bv_val );
1082 Debug( LDAP_DEBUG_TRACE,
1083 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
1090 * This function answers the question, "Can this ID authorize to that ID?",
1091 * based on authorization rules. The rules are stored in the *searchDN, in the
1092 * attribute named by *attr. If any of those rules map to the *assertDN, the
1093 * authorization is approved.
1095 * The DNs should not have the dn: prefix
1098 slap_sasl_check_authz( Operation *op,
1099 struct berval *searchDN,
1100 struct berval *assertDN,
1101 AttributeDescription *ad,
1102 struct berval *authc )
1105 BerVarray vals = NULL;
1107 Debug( LDAP_DEBUG_TRACE,
1108 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
1109 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
1111 rc = backend_attribute( op, NULL, searchDN, ad, &vals, ACL_AUTH );
1112 if( rc != LDAP_SUCCESS ) goto COMPLETE;
1114 /* Check if the *assertDN matches any *vals */
1115 rc = slap_sasl_matches( op, vals, assertDN, authc );
1118 if( vals ) ber_bvarray_free_x( vals, op->o_tmpmemctx );
1120 Debug( LDAP_DEBUG_TRACE,
1121 "<==slap_sasl_check_authz: %s check returning %d\n",
1122 ad->ad_cname.bv_val, rc, 0);
1128 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
1129 * return the LDAP DN to which it matches. The SASL regexp rules in the config
1130 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
1131 * search with scope=base), just return the URI (or its searchbase). Otherwise
1132 * an internal search must be done, and if that search returns exactly one
1133 * entry, return the DN of that one entry.
1135 void slap_sasl2dn( Operation *opx,
1136 struct berval *saslname, struct berval *sasldn, int flags )
1139 slap_callback cb = { NULL, sasl_sc_sasl2dn, NULL, NULL };
1141 SlapReply rs = {REP_RESULT};
1142 struct berval regout = BER_BVNULL;
1143 struct berval base = BER_BVNULL;
1145 Debug( LDAP_DEBUG_TRACE, "==>slap_sasl2dn: "
1146 "converting SASL name %s to a DN\n",
1147 saslname->bv_val, 0,0 );
1149 BER_BVZERO( sasldn );
1150 cb.sc_private = sasldn;
1152 /* Convert the SASL name into a minimal URI */
1153 if( !slap_authz_regexp( saslname, ®out, flags, opx->o_tmpmemctx ) ) {
1157 rc = slap_parseURI( opx, ®out, &base,
1158 &op.o_req_ndn, &op.ors_scope, &op.ors_filter,
1159 &op.ors_filterstr );
1160 if ( !BER_BVISNULL( ®out ) ) slap_sl_free( regout.bv_val, opx->o_tmpmemctx );
1161 if ( rc != LDAP_SUCCESS ) {
1165 /* Must do an internal search */
1166 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
1168 switch ( op.ors_scope ) {
1169 case LDAP_X_SCOPE_EXACT:
1170 *sasldn = op.o_req_ndn;
1171 BER_BVZERO( &op.o_req_ndn );
1172 /* intentionally continue to next case */
1174 case LDAP_X_SCOPE_REGEX:
1175 case LDAP_X_SCOPE_SUBTREE:
1176 case LDAP_X_SCOPE_CHILDREN:
1177 case LDAP_X_SCOPE_ONELEVEL:
1178 case LDAP_X_SCOPE_GROUP:
1179 case LDAP_X_SCOPE_USERS:
1180 /* correctly parsed, but illegal */
1183 case LDAP_SCOPE_BASE:
1184 case LDAP_SCOPE_ONELEVEL:
1185 case LDAP_SCOPE_SUBTREE:
1186 #ifdef LDAP_SCOPE_SUBORDINATE
1187 case LDAP_SCOPE_SUBORDINATE:
1193 /* catch unhandled cases (there shouldn't be) */
1197 Debug( LDAP_DEBUG_TRACE,
1198 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
1199 op.o_req_ndn.bv_val, op.ors_scope, 0 );
1201 if ( ( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL) ) {
1205 /* Must run an internal search. */
1206 if ( op.ors_filter == NULL ) {
1207 rc = LDAP_FILTER_ERROR;
1211 op.o_hdr = opx->o_hdr;
1212 op.o_tag = LDAP_REQ_SEARCH;
1213 op.o_ndn = opx->o_conn->c_ndn;
1214 op.o_callback = &cb;
1215 op.o_time = slap_get_time();
1216 op.o_do_not_cache = 1;
1217 op.o_is_auth_check = 1;
1218 op.ors_deref = LDAP_DEREF_NEVER;
1220 op.ors_tlimit = SLAP_NO_LIMIT;
1221 op.ors_attrs = slap_anlist_no_attrs;
1222 op.ors_attrsonly = 1;
1223 /* use req_ndn as req_dn instead of non-pretty base of uri */
1224 if( !BER_BVISNULL( &base ) ) {
1225 ch_free( base.bv_val );
1226 /* just in case... */
1227 BER_BVZERO( &base );
1229 ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1231 op.o_bd->be_search( &op, &rs );
1234 if( !BER_BVISEMPTY( sasldn ) ) {
1235 opx->o_conn->c_authz_backend = op.o_bd;
1237 if( !BER_BVISNULL( &op.o_req_dn ) ) {
1238 slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1240 if( !BER_BVISNULL( &op.o_req_ndn ) ) {
1241 slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1243 if( op.ors_filter ) {
1244 filter_free_x( opx, op.ors_filter );
1246 if( !BER_BVISNULL( &op.ors_filterstr ) ) {
1247 ch_free( op.ors_filterstr.bv_val );
1250 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
1251 !BER_BVISEMPTY( sasldn ) ? sasldn->bv_val : "<nothing>", 0, 0 );
1257 /* Check if a bind can SASL authorize to another identity.
1258 * The DNs should not have the dn: prefix
1261 int slap_sasl_authorized( Operation *op,
1262 struct berval *authcDN, struct berval *authzDN )
1264 int rc = LDAP_INAPPROPRIATE_AUTH;
1266 /* User binding as anonymous */
1267 if ( authzDN == NULL ) {
1272 Debug( LDAP_DEBUG_TRACE,
1273 "==>slap_sasl_authorized: can %s become %s?\n",
1274 authcDN->bv_val, authzDN->bv_val, 0 );
1276 /* If person is authorizing to self, succeed */
1277 if ( dn_match( authcDN, authzDN ) ) {
1282 /* Allow the manager to authorize as any DN. */
1283 if( op->o_conn->c_authz_backend &&
1284 be_isroot_dn( op->o_conn->c_authz_backend, authcDN ))
1290 /* Check source rules */
1291 if( authz_policy & SASL_AUTHZ_TO ) {
1292 rc = slap_sasl_check_authz( op, authcDN, authzDN,
1293 slap_schema.si_ad_saslAuthzTo, authcDN );
1294 if( rc == LDAP_SUCCESS && !(authz_policy & SASL_AUTHZ_AND) ) {
1299 /* Check destination rules */
1300 if( authz_policy & SASL_AUTHZ_FROM ) {
1301 rc = slap_sasl_check_authz( op, authzDN, authcDN,
1302 slap_schema.si_ad_saslAuthzFrom, authcDN );
1303 if( rc == LDAP_SUCCESS ) {
1308 rc = LDAP_INAPPROPRIATE_AUTH;
1312 Debug( LDAP_DEBUG_TRACE,
1313 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );