2 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
4 * Copyright 1998-2004 The OpenLDAP Foundation.
5 * Portions Copyright 2000 Mark Adamson, Carnegie Mellon.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted only as authorized by the OpenLDAP
12 * A copy of this license is available in the file LICENSE in the
13 * top-level directory of the distribution or, alternatively, at
14 * <http://www.OpenLDAP.org/license.html>.
21 #include <ac/stdlib.h>
22 #include <ac/string.h>
32 #define SASLREGEX_REPLACE 10
34 #define LDAP_X_SCOPE_EXACT ((ber_int_t) 0x0010)
35 #define LDAP_X_SCOPE_REGEX ((ber_int_t) 0x0020)
36 #define LDAP_X_SCOPE_CHILDREN ((ber_int_t) 0x0030)
37 #define LDAP_X_SCOPE_SUBTREE ((ber_int_t) 0x0040)
38 #define LDAP_X_SCOPE_ONELEVEL ((ber_int_t) 0x0050)
39 #define LDAP_X_SCOPE_GROUP ((ber_int_t) 0x0060)
42 * IDs in DNauthzid form can now have a type specifier, that
43 * influences how they are used in related operations.
45 * syntax: dn[.{exact|regex}]:<val>
47 * dn.exact: the value must pass normalization and is used
49 * dn.regex: the value is treated as a regular expression
50 * in matching DN values in authz{To|From}
52 * dn: for backwards compatibility reasons, the value
53 * is treated as a regular expression, and thus
54 * it is not normalized nor validated; it is used
55 * in exact or regex comparisons based on the
58 * IDs in DNauthzid form can now have a type specifier, that
59 * influences how they are used in related operations.
61 * syntax: u[.mech[/realm]]:<val>
63 * where mech is a SIMPLE, AUTHZ, or a SASL mechanism name
64 * and realm is mechanism specific realm (separate to those
65 * which are representable as part of the principal).
68 typedef struct sasl_regexp {
69 char *sr_match; /* regexp match pattern */
70 char *sr_replace; /* regexp replace pattern */
71 regex_t sr_workspace; /* workspace for regexp engine */
72 int sr_offset[SASLREGEX_REPLACE+2]; /* offsets of $1,$2... in *replace */
75 static int nSaslRegexp = 0;
76 static SaslRegexp_t *SaslRegexp = NULL;
78 #ifdef SLAP_AUTH_REWRITE
80 struct rewrite_info *sasl_rwinfo = NULL;
81 #define AUTHID_CONTEXT "authid"
82 #endif /* SLAP_AUTH_REWRITE */
84 /* What SASL proxy authorization policies are allowed? */
85 #define SASL_AUTHZ_NONE 0x00
86 #define SASL_AUTHZ_FROM 0x01
87 #define SASL_AUTHZ_TO 0x02
88 #define SASL_AUTHZ_AND 0x10
90 static int authz_policy = SASL_AUTHZ_NONE;
93 int slap_sasl_match( Operation *opx, struct berval *rule,
94 struct berval *assertDN, struct berval *authc );
96 int slap_sasl_setpolicy( const char *arg )
98 int rc = LDAP_SUCCESS;
100 if ( strcasecmp( arg, "none" ) == 0 ) {
101 authz_policy = SASL_AUTHZ_NONE;
102 } else if ( strcasecmp( arg, "from" ) == 0 ) {
103 authz_policy = SASL_AUTHZ_FROM;
104 } else if ( strcasecmp( arg, "to" ) == 0 ) {
105 authz_policy = SASL_AUTHZ_TO;
106 } else if ( strcasecmp( arg, "both" ) == 0 || strcasecmp( arg, "any" ) == 0 ) {
107 authz_policy = SASL_AUTHZ_FROM | SASL_AUTHZ_TO;
108 } else if ( strcasecmp( arg, "all" ) == 0 ) {
109 authz_policy = SASL_AUTHZ_FROM | SASL_AUTHZ_TO | SASL_AUTHZ_AND;
116 int slap_parse_user( struct berval *id, struct berval *user,
117 struct berval *realm, struct berval *mech )
122 assert( !BER_BVISNULL( id ) );
129 if ( u != 'u' && u != 'U' ) {
130 /* called with something other than u: */
131 return LDAP_PROTOCOL_ERROR;
135 * u[.mech[/realm]]:user
138 user->bv_val = strchr( id->bv_val, ':' );
139 if ( BER_BVISNULL( user ) ) {
140 return LDAP_PROTOCOL_ERROR;
142 user->bv_val[ 0 ] = '\0';
144 user->bv_len = id->bv_len - ( user->bv_val - id->bv_val );
146 mech->bv_val = strchr( id->bv_val, '.' );
147 if ( !BER_BVISNULL( mech ) ) {
148 mech->bv_val[ 0 ] = '\0';
151 realm->bv_val = strchr( mech->bv_val, '/' );
153 if ( !BER_BVISNULL( realm ) ) {
154 realm->bv_val[ 0 ] = '\0';
156 mech->bv_len = realm->bv_val - mech->bv_val - 1;
157 realm->bv_len = user->bv_val - realm->bv_val - 1;
159 mech->bv_len = user->bv_val - mech->bv_val - 1;
166 if ( id->bv_val[ 1 ] != '\0' ) {
167 return LDAP_PROTOCOL_ERROR;
170 if ( !BER_BVISNULL( mech ) ) {
171 assert( mech->bv_val == id->bv_val + 2 );
173 AC_MEMCPY( mech->bv_val - 2, mech->bv_val, mech->bv_len + 1 );
177 if ( !BER_BVISNULL( realm ) ) {
178 assert( realm->bv_val >= id->bv_val + 2 );
180 AC_MEMCPY( realm->bv_val - 2, realm->bv_val, realm->bv_len + 1 );
184 /* leave "u:" before user */
187 user->bv_val[ 0 ] = u;
188 user->bv_val[ 1 ] = ':';
193 static int slap_parseURI( Operation *op, struct berval *uri,
194 struct berval *base, struct berval *nbase,
195 int *scope, Filter **filter, struct berval *fstr )
201 assert( uri != NULL && !BER_BVISNULL( uri ) );
209 LDAP_LOG( TRANSPORT, ENTRY,
210 "slap_parseURI: parsing %s\n", uri->bv_val, 0, 0 );
212 Debug( LDAP_DEBUG_TRACE,
213 "slap_parseURI: parsing %s\n", uri->bv_val, 0, 0 );
216 rc = LDAP_PROTOCOL_ERROR;
218 * dn[.<dnstyle>]:<dnpattern>
219 * <dnstyle> ::= {exact|regex|children|subtree|onelevel}
221 * <dnstyle> defaults to "exact"
222 * if <dnstyle> is not "regex", <dnpattern> must pass DN normalization
224 if ( !strncasecmp( uri->bv_val, "dn", STRLENOF( "dn" ) ) ) {
225 bv.bv_val = uri->bv_val + STRLENOF( "dn" );
227 if ( bv.bv_val[ 0 ] == '.' ) {
230 if ( !strncasecmp( bv.bv_val, "exact:", STRLENOF( "exact:" ) ) ) {
231 bv.bv_val += STRLENOF( "exact:" );
232 *scope = LDAP_X_SCOPE_EXACT;
234 } else if ( !strncasecmp( bv.bv_val, "regex:", STRLENOF( "regex:" ) ) ) {
235 bv.bv_val += STRLENOF( "regex:" );
236 *scope = LDAP_X_SCOPE_REGEX;
238 } else if ( !strncasecmp( bv.bv_val, "children:", STRLENOF( "children:" ) ) ) {
239 bv.bv_val += STRLENOF( "children:" );
240 *scope = LDAP_X_SCOPE_CHILDREN;
242 } else if ( !strncasecmp( bv.bv_val, "subtree:", STRLENOF( "subtree:" ) ) ) {
243 bv.bv_val += STRLENOF( "subtree:" );
244 *scope = LDAP_X_SCOPE_SUBTREE;
246 } else if ( !strncasecmp( bv.bv_val, "onelevel:", STRLENOF( "onelevel:" ) ) ) {
247 bv.bv_val += STRLENOF( "onelevel:" );
248 *scope = LDAP_X_SCOPE_ONELEVEL;
251 return LDAP_PROTOCOL_ERROR;
255 if ( bv.bv_val[ 0 ] != ':' ) {
256 return LDAP_PROTOCOL_ERROR;
258 *scope = LDAP_X_SCOPE_EXACT;
262 bv.bv_val += strspn( bv.bv_val, " " );
263 /* jump here in case no type specification was present
264 * and uri was not an URI... HEADS-UP: assuming EXACT */
265 is_dn: bv.bv_len = uri->bv_len - (bv.bv_val - uri->bv_val);
268 case LDAP_X_SCOPE_EXACT:
269 case LDAP_X_SCOPE_CHILDREN:
270 case LDAP_X_SCOPE_SUBTREE:
271 case LDAP_X_SCOPE_ONELEVEL:
272 rc = dnNormalize( 0, NULL, NULL, &bv, nbase, op->o_tmpmemctx );
273 if( rc != LDAP_SUCCESS ) {
278 case LDAP_X_SCOPE_REGEX:
279 ber_dupbv_x( nbase, &bv, op->o_tmpmemctx );
293 } else if ( ( uri->bv_val[ 0 ] == 'u' || uri->bv_val[ 0 ] == 'U' )
294 && ( uri->bv_val[ 1 ] == ':'
295 || uri->bv_val[ 1 ] == '/'
296 || uri->bv_val[ 1 ] == '.' ) )
298 Connection c = *op->o_conn;
299 char buf[ SLAP_LDAPDN_MAXLEN ];
305 if ( sizeof( buf ) <= uri->bv_len ) {
306 return LDAP_INVALID_SYNTAX;
309 id.bv_len = uri->bv_len;
311 strncpy( buf, uri->bv_val, sizeof( buf ) );
313 rc = slap_parse_user( &id, &user, &realm, &mech );
314 if ( rc != LDAP_SUCCESS ) {
318 if ( !BER_BVISNULL( &mech ) ) {
319 c.c_sasl_bind_mech = mech;
321 BER_BVSTR( &c.c_sasl_bind_mech, "AUTHZ" );
324 rc = slap_sasl_getdn( &c, op, &user,
325 realm.bv_val, nbase, SLAP_GETDN_AUTHZID );
327 if ( rc == LDAP_SUCCESS ) {
328 *scope = LDAP_X_SCOPE_EXACT;
334 * group[/<groupoc>[/<groupat>]]:<groupdn>
336 * groupoc defaults to "groupOfNames"
337 * groupat defaults to "member"
339 * <groupdn> must pass DN normalization
341 } else if ( strncasecmp( uri->bv_val, "group", STRLENOF( "group" ) ) == 0 )
343 struct berval group_dn = BER_BVNULL,
344 group_oc = BER_BVNULL,
345 member_at = BER_BVNULL;
348 bv.bv_val = uri->bv_val + STRLENOF( "group" );
349 group_dn.bv_val = strchr( bv.bv_val, ':' );
350 if ( group_dn.bv_val == NULL ) {
351 /* last chance: assume it's a(n exact) DN ... */
352 bv.bv_val = uri->bv_val;
353 *scope = LDAP_X_SCOPE_EXACT;
357 if ( bv.bv_val[ 0 ] == '/' ) {
358 group_oc.bv_val = &bv.bv_val[ 1 ];
360 member_at.bv_val = strchr( group_oc.bv_val, '/' );
361 if ( member_at.bv_val ) {
362 group_oc.bv_len = member_at.bv_val - group_oc.bv_val;
364 member_at.bv_len = group_dn.bv_val - member_at.bv_val;
367 group_oc.bv_len = group_dn.bv_val - group_oc.bv_val;
368 BER_BVSTR( &member_at, "member" );
372 BER_BVSTR( &group_oc, "groupOfNames" );
375 group_dn.bv_len = uri->bv_len - ( group_dn.bv_val - uri->bv_val );
377 rc = dnNormalize( 0, NULL, NULL, &group_dn, nbase, op->o_tmpmemctx );
378 if ( rc != LDAP_SUCCESS ) {
382 *scope = LDAP_X_SCOPE_GROUP;
384 /* FIXME: caller needs to add value of member attribute
385 * and close brackets twice */
386 fstr->bv_len = STRLENOF( "(&(objectClass=)(=" /* )) */ )
387 + group_oc.bv_len + member_at.bv_len;
388 fstr->bv_val = ch_malloc( fstr->bv_len + 1 );
390 tmp = lutil_strncopy( fstr->bv_val, "(&(objectClass=" /* )) */ ,
391 STRLENOF( "(&(objectClass=" /* )) */ ) );
392 tmp = lutil_strncopy( tmp, group_oc.bv_val, group_oc.bv_len );
393 tmp = lutil_strncopy( tmp, /* ( */ ")(" /* ) */ ,
394 STRLENOF( /* ( */ ")(" /* ) */ ) );
395 tmp = lutil_strncopy( tmp, member_at.bv_val, member_at.bv_len );
396 tmp = lutil_strncopy( tmp, "=", STRLENOF( "=" ) );
402 * ldap:///<base>??<scope>?<filter>
403 * <scope> ::= {base|one|subtree}
405 * <scope> defaults to "base"
406 * <base> must pass DN normalization
407 * <filter> must pass str2filter()
409 rc = ldap_url_parse( uri->bv_val, &ludp );
410 if ( rc == LDAP_URL_ERR_BADSCHEME ) {
412 * last chance: assume it's a(n exact) DN ...
414 * NOTE: must pass DN normalization
416 bv.bv_val = uri->bv_val;
417 *scope = LDAP_X_SCOPE_EXACT;
421 if ( rc != LDAP_URL_SUCCESS ) {
422 return LDAP_PROTOCOL_ERROR;
425 if (( ludp->lud_host && *ludp->lud_host )
426 || ludp->lud_attrs || ludp->lud_exts )
428 /* host part must be empty */
429 /* attrs and extensions parts must be empty */
430 rc = LDAP_PROTOCOL_ERROR;
435 *scope = ludp->lud_scope;
437 /* Grab the filter */
438 if ( ludp->lud_filter ) {
439 *filter = str2filter_x( op, ludp->lud_filter );
440 if ( *filter == NULL ) {
441 rc = LDAP_PROTOCOL_ERROR;
444 ber_str2bv( ludp->lud_filter, 0, 0, fstr );
447 /* Grab the searchbase */
448 ber_str2bv( ludp->lud_dn, 0, 0, base );
449 rc = dnNormalize( 0, NULL, NULL, base, nbase, op->o_tmpmemctx );
452 if( rc != LDAP_SUCCESS ) {
453 if( *filter ) filter_free_x( op, *filter );
457 /* Don't free these, return them to caller */
458 ludp->lud_filter = NULL;
462 ldap_free_urldesc( ludp );
466 static int slap_sasl_rx_off(char *rep, int *off)
471 /* Precompile replace pattern. Find the $<n> placeholders */
474 for ( c = rep; *c; c++ ) {
475 if ( *c == '\\' && c[1] ) {
480 if ( n == SASLREGEX_REPLACE ) {
482 LDAP_LOG( TRANSPORT, ERR,
483 "slap_sasl_rx_off: \"%s\" has too many $n "
484 "placeholders (max %d)\n", rep, SASLREGEX_REPLACE, 0 );
486 Debug( LDAP_DEBUG_ANY,
487 "SASL replace pattern %s has too many $n "
488 "placeholders (max %d)\n",
489 rep, SASLREGEX_REPLACE, 0 );
492 return( LDAP_OTHER );
499 /* Final placeholder, after the last $n */
503 return( LDAP_SUCCESS );
506 #ifdef SLAP_AUTH_REWRITE
507 int slap_sasl_rewrite_config(
517 /* init at first call */
518 if ( sasl_rwinfo == NULL ) {
519 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
522 /* strip "authid-" prefix for parsing */
524 argv[0] += STRLENOF( "authid-" );
525 rc = rewrite_parse( sasl_rwinfo, fname, lineno, argc, argv );
531 int slap_sasl_rewrite_destroy( void )
534 rewrite_info_delete( &sasl_rwinfo );
541 int slap_sasl_regexp_rewrite_config(
546 const char *context )
549 char *newreplace, *p;
550 char *argvRule[] = { "rewriteRule", NULL, NULL, "@", NULL };
552 /* init at first call */
553 if ( sasl_rwinfo == NULL ) {
554 char *argvEngine[] = { "rewriteEngine", "on", NULL };
555 char *argvContext[] = { "rewriteContext", NULL, NULL };
557 /* initialize rewrite engine */
558 sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
560 /* switch on rewrite engine */
561 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvEngine );
562 if (rc != LDAP_SUCCESS) {
566 /* create generic authid context */
567 argvContext[1] = AUTHID_CONTEXT;
568 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 2, argvContext );
569 if (rc != LDAP_SUCCESS) {
574 newreplace = ch_strdup( replace );
576 for (p = strchr( newreplace, '$' ); p; p = strchr( p + 1, '$' ) ) {
577 if ( isdigit( p[1] ) ) {
584 argvRule[1] = (char *)match;
585 argvRule[2] = newreplace;
586 rc = rewrite_parse( sasl_rwinfo, fname, lineno, 4, argvRule );
587 ch_free( newreplace );
591 #endif /* SLAP_AUTH_REWRITE */
593 int slap_sasl_regexp_config( const char *match, const char *replace )
595 #ifdef SLAP_AUTH_REWRITE
596 return slap_sasl_regexp_rewrite_config( "sasl-regexp", 0,
597 match, replace, AUTHID_CONTEXT );
598 #else /* ! SLAP_AUTH_REWRITE */
602 SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
603 (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
605 reg = &SaslRegexp[nSaslRegexp];
607 reg->sr_match = ch_strdup( match );
608 reg->sr_replace = ch_strdup( replace );
610 /* Precompile matching pattern */
611 rc = regcomp( ®->sr_workspace, reg->sr_match, REG_EXTENDED|REG_ICASE );
614 LDAP_LOG( TRANSPORT, ERR,
615 "slap_sasl_regexp_config: \"%s\" could not be compiled.\n",
616 reg->sr_match, 0, 0 );
618 Debug( LDAP_DEBUG_ANY,
619 "SASL match pattern %s could not be compiled by regexp engine\n",
620 reg->sr_match, 0, 0 );
623 return( LDAP_OTHER );
626 rc = slap_sasl_rx_off( reg->sr_replace, reg->sr_offset );
627 if ( rc != LDAP_SUCCESS ) return rc;
630 return( LDAP_SUCCESS );
631 #endif /* ! SLAP_AUTH_REWRITE */
634 /* Perform replacement on regexp matches */
635 static void slap_sasl_rx_exp(
639 const char *saslname,
643 int i, n, len, insert;
645 /* Get the total length of the final URI */
649 while( off[n] >= 0 ) {
650 /* Len of next section from replacement string (x,y,z above) */
651 len += off[n] - off[n-1] - 2;
655 /* Len of string from saslname that matched next $i (b,d above) */
656 i = rep[ off[n] + 1 ] - '0';
657 len += str[i].rm_eo - str[i].rm_so;
660 out->bv_val = slap_sl_malloc( len + 1, ctx );
663 /* Fill in URI with replace string, replacing $i as we go */
666 while( off[n] >= 0) {
667 /* Paste in next section from replacement string (x,y,z above) */
668 len = off[n] - off[n-1] - 2;
669 strncpy( out->bv_val+insert, rep + off[n-1] + 2, len);
674 /* Paste in string from saslname that matched next $i (b,d above) */
675 i = rep[ off[n] + 1 ] - '0';
676 len = str[i].rm_eo - str[i].rm_so;
677 strncpy( out->bv_val+insert, saslname + str[i].rm_so, len );
683 out->bv_val[insert] = '\0';
686 /* Take the passed in SASL name and attempt to convert it into an
687 LDAP URI to find the matching LDAP entry, using the pattern matching
688 strings given in the saslregexp config file directive(s) */
690 static int slap_authz_regexp( struct berval *in, struct berval *out,
691 int flags, void *ctx )
693 #ifdef SLAP_AUTH_REWRITE
694 const char *context = AUTHID_CONTEXT;
696 if ( sasl_rwinfo == NULL || BER_BVISNULL( in ) ) {
700 /* FIXME: if aware of authc/authz mapping,
701 * we could use different contexts ... */
702 switch ( rewrite_session( sasl_rwinfo, context, in->bv_val, NULL,
705 case REWRITE_REGEXEC_OK:
706 if ( !BER_BVISNULL( out ) ) {
707 char *val = out->bv_val;
708 ber_str2bv_x( val, 0, 1, out, ctx );
711 ber_dupbv_x( out, in, ctx );
714 LDAP_LOG( BACK_LDAP, DETAIL1,
715 "[rw] %s: \"%s\" -> \"%s\"\n",
716 context, in->bv_val, out->bv_val );
717 #else /* !NEW_LOGGING */
718 Debug( LDAP_DEBUG_ARGS,
719 "[rw] %s: \"%s\" -> \"%s\"\n",
720 context, in->bv_val, out->bv_val );
721 #endif /* !NEW_LOGGING */
724 case REWRITE_REGEXEC_UNWILLING:
725 case REWRITE_REGEXEC_ERR:
730 #else /* ! SLAP_AUTH_REWRITE */
731 char *saslname = in->bv_val;
733 regmatch_t sr_strings[SASLREGEX_REPLACE]; /* strings matching $1,$2 ... */
736 memset( out, 0, sizeof( *out ) );
739 LDAP_LOG( TRANSPORT, ENTRY,
740 "slap_authz_regexp: converting SASL name %s\n", saslname, 0, 0 );
742 Debug( LDAP_DEBUG_TRACE, "slap_authz_regexp: converting SASL name %s\n",
746 if (( saslname == NULL ) || ( nSaslRegexp == 0 )) {
750 /* Match the normalized SASL name to the saslregexp patterns */
751 for( reg = SaslRegexp,i=0; i<nSaslRegexp; i++,reg++ ) {
752 if ( regexec( ®->sr_workspace, saslname, SASLREGEX_REPLACE,
753 sr_strings, 0) == 0 )
757 if( i >= nSaslRegexp ) return( 0 );
760 * The match pattern may have been of the form "a(b.*)c(d.*)e" and the
761 * replace pattern of the form "x$1y$2z". The returned string needs
762 * to replace the $1,$2 with the strings that matched (b.*) and (d.*)
764 slap_sasl_rx_exp( reg->sr_replace, reg->sr_offset,
765 sr_strings, saslname, out, ctx );
768 LDAP_LOG( TRANSPORT, ENTRY,
769 "slap_authz_regexp: converted SASL name to %s\n",
770 BER_BVISEMPTY( out ) ? "" : out->bv_val, 0, 0 );
772 Debug( LDAP_DEBUG_TRACE,
773 "slap_authz_regexp: converted SASL name to %s\n",
774 BER_BVISEMPTY( out ) ? "" : out->bv_val, 0, 0 );
778 #endif /* ! SLAP_AUTH_REWRITE */
781 /* This callback actually does some work...*/
782 static int sasl_sc_sasl2dn( Operation *o, SlapReply *rs )
784 struct berval *ndn = o->o_callback->sc_private;
786 if (rs->sr_type != REP_SEARCH) return 0;
788 /* We only want to be called once */
789 if ( !BER_BVISNULL( ndn ) ) {
790 o->o_tmpfree(ndn->bv_val, o->o_tmpmemctx);
794 LDAP_LOG( TRANSPORT, DETAIL1,
795 "slap_sc_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
797 Debug( LDAP_DEBUG_TRACE,
798 "slap_sc_sasl2dn: search DN returned more than 1 entry\n", 0, 0, 0 );
803 ber_dupbv_x(ndn, &rs->sr_entry->e_nname, o->o_tmpmemctx);
808 typedef struct smatch_info {
813 static int sasl_sc_smatch( Operation *o, SlapReply *rs )
815 smatch_info *sm = o->o_callback->sc_private;
817 if ( rs->sr_type != REP_SEARCH ) {
818 if ( rs->sr_err != LDAP_SUCCESS ) {
824 if ( sm->match == 1 ) {
829 if (dn_match(sm->dn, &rs->sr_entry->e_nname)) {
840 slap_sasl_matches( Operation *op, BerVarray rules,
841 struct berval *assertDN, struct berval *authc )
843 int rc = LDAP_INAPPROPRIATE_AUTH;
845 if ( rules != NULL ) {
848 for( i = 0; !BER_BVISNULL( &rules[i] ); i++ ) {
849 rc = slap_sasl_match( op, &rules[i], assertDN, authc );
850 if ( rc == LDAP_SUCCESS ) break;
858 * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
859 * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
860 * the rule must be used as an internal search for entries. If that search
861 * returns the *assertDN entry, the match is successful.
863 * The assertDN should not have the dn: prefix
867 int slap_sasl_match( Operation *opx, struct berval *rule,
868 struct berval *assertDN, struct berval *authc )
873 slap_callback cb = { NULL, sasl_sc_smatch, NULL, NULL };
875 SlapReply rs = {REP_RESULT};
882 LDAP_LOG( TRANSPORT, ENTRY,
883 "slap_sasl_match: comparing DN %s to rule %s\n",
884 assertDN->bv_val, rule->bv_val,0 );
886 Debug( LDAP_DEBUG_TRACE,
887 "===>slap_sasl_match: comparing DN %s to rule %s\n",
888 assertDN->bv_val, rule->bv_val, 0 );
891 rc = slap_parseURI( opx, rule, &op.o_req_dn,
892 &op.o_req_ndn, &op.oq_search.rs_scope, &op.oq_search.rs_filter,
894 if( rc != LDAP_SUCCESS ) goto CONCLUDED;
896 /* Massive shortcut: search scope == base */
897 switch ( op.oq_search.rs_scope ) {
898 case LDAP_SCOPE_BASE:
899 case LDAP_X_SCOPE_EXACT:
901 if ( dn_match( &op.o_req_ndn, assertDN ) ) {
904 rc = LDAP_INAPPROPRIATE_AUTH;
908 case LDAP_X_SCOPE_CHILDREN:
909 case LDAP_X_SCOPE_SUBTREE:
910 case LDAP_X_SCOPE_ONELEVEL:
912 int d = assertDN->bv_len - op.o_req_ndn.bv_len;
914 rc = LDAP_INAPPROPRIATE_AUTH;
916 if ( d == 0 && op.oq_search.rs_scope == LDAP_X_SCOPE_SUBTREE ) {
919 } else if ( d > 0 ) {
922 /* leave room for at least one char of attributeType,
923 * one for '=' and one for ',' */
924 if ( d < STRLENOF( "x=,") ) {
928 bv.bv_len = op.o_req_ndn.bv_len;
929 bv.bv_val = assertDN->bv_val + d;
931 if ( bv.bv_val[ -1 ] == ',' && dn_match( &op.o_req_ndn, &bv ) ) {
932 switch ( op.oq_search.rs_scope ) {
933 case LDAP_X_SCOPE_SUBTREE:
934 case LDAP_X_SCOPE_CHILDREN:
938 case LDAP_X_SCOPE_ONELEVEL:
942 dnParent( assertDN, &pdn );
943 /* the common portion of the DN
944 * already matches, so only check
945 * if parent DN of assertedDN
946 * is all the pattern */
947 if ( pdn.bv_len == op.o_req_ndn.bv_len ) {
953 /* at present, impossible */
961 case LDAP_X_SCOPE_REGEX:
962 rc = regcomp(®, op.o_req_ndn.bv_val,
963 REG_EXTENDED|REG_ICASE|REG_NOSUB);
965 rc = regexec(®, assertDN->bv_val, 0, NULL, 0);
971 rc = LDAP_INAPPROPRIATE_AUTH;
975 case LDAP_X_SCOPE_GROUP: {
978 /* Now filterstr looks like "(&(objectClass=<group_oc>)(<member_at>="
979 * we need to append the <assertDN> so that the <group_dn> is searched
980 * with scope "base", and the filter ensures that <assertDN> is
981 * member of the group */
982 tmp = ch_realloc( op.ors_filterstr.bv_val,
983 op.ors_filterstr.bv_len + assertDN->bv_len + STRLENOF( /* (( */ "))" ) + 1 );
988 op.ors_filterstr.bv_val = tmp;
990 tmp = lutil_strcopy( &tmp[ op.ors_filterstr.bv_len ], assertDN->bv_val );
991 tmp = lutil_strcopy( tmp, /* (( */ "))" );
993 /* pass opx because str2filter_x may (and does) use o_tmpmfuncs */
994 op.ors_filter = str2filter_x( opx, op.ors_filterstr.bv_val );
995 if ( op.ors_filter == NULL ) {
996 rc = LDAP_PROTOCOL_ERROR;
999 op.ors_scope = LDAP_SCOPE_BASE;
1001 /* hijack match DN: use that of the group instead of the assertDN;
1002 * assertDN is now in the filter */
1003 sm.dn = &op.o_req_ndn;
1013 /* Must run an internal search. */
1014 if ( op.oq_search.rs_filter == NULL ) {
1015 rc = LDAP_FILTER_ERROR;
1020 LDAP_LOG( TRANSPORT, DETAIL1,
1021 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
1022 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
1024 Debug( LDAP_DEBUG_TRACE,
1025 "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
1026 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
1029 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
1030 if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
1031 rc = LDAP_INAPPROPRIATE_AUTH;
1035 op.o_tag = LDAP_REQ_SEARCH;
1036 op.o_protocol = LDAP_VERSION3;
1038 op.o_callback = &cb;
1039 op.o_time = slap_get_time();
1040 op.o_do_not_cache = 1;
1041 op.o_is_auth_check = 1;
1042 op.o_threadctx = opx->o_threadctx;
1043 op.o_tmpmemctx = opx->o_tmpmemctx;
1044 op.o_tmpmfuncs = opx->o_tmpmfuncs;
1046 op.o_pb = opx->o_pb;
1048 op.o_conn = opx->o_conn;
1049 op.o_connid = opx->o_connid;
1050 /* use req_ndn as req_dn instead of non-pretty base of uri */
1051 if( !BER_BVISNULL( &op.o_req_dn ) ) ch_free( op.o_req_dn.bv_val );
1052 ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1053 op.oq_search.rs_slimit = 1;
1054 op.oq_search.rs_tlimit = -1;
1055 op.o_sync_slog_size = -1;
1057 op.o_bd->be_search( &op, &rs );
1059 if (sm.match == 1) {
1062 rc = LDAP_INAPPROPRIATE_AUTH;
1066 if( !BER_BVISNULL( &op.o_req_dn ) ) slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1067 if( !BER_BVISNULL( &op.o_req_ndn ) ) slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1068 if( op.oq_search.rs_filter ) filter_free_x( opx, op.oq_search.rs_filter );
1069 if( !BER_BVISNULL( &op.ors_filterstr ) ) ch_free( op.ors_filterstr.bv_val );
1072 LDAP_LOG( TRANSPORT, ENTRY,
1073 "slap_sasl_match: comparison returned %d\n", rc, 0, 0 );
1075 Debug( LDAP_DEBUG_TRACE,
1076 "<===slap_sasl_match: comparison returned %d\n", rc, 0, 0);
1084 * This function answers the question, "Can this ID authorize to that ID?",
1085 * based on authorization rules. The rules are stored in the *searchDN, in the
1086 * attribute named by *attr. If any of those rules map to the *assertDN, the
1087 * authorization is approved.
1089 * The DNs should not have the dn: prefix
1092 slap_sasl_check_authz( Operation *op,
1093 struct berval *searchDN,
1094 struct berval *assertDN,
1095 AttributeDescription *ad,
1096 struct berval *authc )
1099 BerVarray vals = NULL;
1102 LDAP_LOG( TRANSPORT, ENTRY,
1103 "slap_sasl_check_authz: does %s match %s rule in %s?\n",
1104 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
1106 Debug( LDAP_DEBUG_TRACE,
1107 "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
1108 assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
1111 rc = backend_attribute( op, NULL, searchDN, ad, &vals );
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 );
1121 LDAP_LOG( TRANSPORT, RESULTS,
1122 "slap_sasl_check_authz: %s check returning %s\n",
1123 ad->ad_cname.bv_val, rc, 0 );
1125 Debug( LDAP_DEBUG_TRACE,
1126 "<==slap_sasl_check_authz: %s check returning %d\n",
1127 ad->ad_cname.bv_val, rc, 0);
1134 * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
1135 * return the LDAP DN to which it matches. The SASL regexp rules in the config
1136 * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
1137 * search with scope=base), just return the URI (or its searchbase). Otherwise
1138 * an internal search must be done, and if that search returns exactly one
1139 * entry, return the DN of that one entry.
1141 void slap_sasl2dn( Operation *opx,
1142 struct berval *saslname, struct berval *sasldn, int flags )
1145 slap_callback cb = { NULL, sasl_sc_sasl2dn, NULL, NULL };
1147 SlapReply rs = {REP_RESULT};
1148 struct berval regout = BER_BVNULL;
1151 LDAP_LOG( TRANSPORT, ENTRY,
1152 "slap_sasl2dn: converting SASL name %s to DN.\n",
1153 saslname->bv_val, 0, 0 );
1155 Debug( LDAP_DEBUG_TRACE, "==>slap_sasl2dn: "
1156 "converting SASL name %s to a DN\n",
1157 saslname->bv_val, 0,0 );
1160 sasldn->bv_val = NULL;
1162 cb.sc_private = sasldn;
1164 /* Convert the SASL name into a minimal URI */
1165 if( !slap_authz_regexp( saslname, ®out, flags, opx->o_tmpmemctx ) ) {
1169 rc = slap_parseURI( opx, ®out, &op.o_req_dn,
1170 &op.o_req_ndn, &op.oq_search.rs_scope, &op.oq_search.rs_filter,
1171 &op.ors_filterstr );
1172 if ( !BER_BVISNULL( ®out ) ) slap_sl_free( regout.bv_val, opx->o_tmpmemctx );
1173 if ( rc != LDAP_SUCCESS ) {
1177 /* Must do an internal search */
1178 op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
1180 /* Massive shortcut: search scope == base */
1181 switch ( op.oq_search.rs_scope ) {
1182 case LDAP_SCOPE_BASE:
1183 case LDAP_X_SCOPE_EXACT:
1184 *sasldn = op.o_req_ndn;
1185 BER_BVZERO( &op.o_req_ndn );
1186 /* intentionally continue to next case */
1188 case LDAP_X_SCOPE_REGEX:
1189 case LDAP_X_SCOPE_SUBTREE:
1190 case LDAP_X_SCOPE_CHILDREN:
1191 case LDAP_X_SCOPE_ONELEVEL:
1192 case LDAP_X_SCOPE_GROUP:
1193 /* correctly parsed, but illegal */
1196 case LDAP_SCOPE_ONELEVEL:
1197 case LDAP_SCOPE_SUBTREE:
1198 #ifdef LDAP_SCOPE_SUBORDINATE
1199 case LDAP_SCOPE_SUBORDINATE:
1205 /* catch unhandled cases (there shouldn't be) */
1210 LDAP_LOG( TRANSPORT, DETAIL1,
1211 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
1212 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
1214 Debug( LDAP_DEBUG_TRACE,
1215 "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
1216 op.o_req_ndn.bv_val, op.oq_search.rs_scope, 0 );
1219 if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
1223 op.o_conn = opx->o_conn;
1224 op.o_connid = opx->o_connid;
1225 op.o_tag = LDAP_REQ_SEARCH;
1226 op.o_protocol = LDAP_VERSION3;
1227 op.o_ndn = opx->o_conn->c_ndn;
1228 op.o_callback = &cb;
1229 op.o_time = slap_get_time();
1230 op.o_do_not_cache = 1;
1231 op.o_is_auth_check = 1;
1232 op.o_threadctx = opx->o_threadctx;
1233 op.o_tmpmemctx = opx->o_tmpmemctx;
1234 op.o_tmpmfuncs = opx->o_tmpmfuncs;
1236 op.o_pb = opx->o_pb;
1238 op.oq_search.rs_deref = LDAP_DEREF_NEVER;
1239 op.oq_search.rs_slimit = 1;
1240 op.oq_search.rs_tlimit = -1;
1241 op.oq_search.rs_attrsonly = 1;
1242 /* use req_ndn as req_dn instead of non-pretty base of uri */
1243 if( !BER_BVISNULL( &op.o_req_dn ) ) ch_free( op.o_req_dn.bv_val );
1244 ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
1246 op.o_bd->be_search( &op, &rs );
1249 if( !BER_BVISEMPTY( sasldn ) ) {
1250 opx->o_conn->c_authz_backend = op.o_bd;
1252 if( !BER_BVISNULL( &op.o_req_dn ) ) slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
1253 if( !BER_BVISNULL( &op.o_req_ndn ) ) slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
1254 if( op.oq_search.rs_filter ) filter_free_x( opx, op.oq_search.rs_filter );
1255 if( !BER_BVISNULL( &op.ors_filterstr ) ) ch_free( op.ors_filterstr.bv_val );
1258 LDAP_LOG( TRANSPORT, ENTRY,
1259 "slap_sasl2dn: Converted SASL name to %s\n",
1260 !BER_BVISEMPTY( sasldn ) ? sasldn->bv_val : "<nothing>", 0, 0 );
1262 Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
1263 !BER_BVISEMPTY( sasldn ) ? sasldn->bv_val : "<nothing>", 0, 0 );
1270 /* Check if a bind can SASL authorize to another identity.
1271 * The DNs should not have the dn: prefix
1274 int slap_sasl_authorized( Operation *op,
1275 struct berval *authcDN, struct berval *authzDN )
1277 int rc = LDAP_INAPPROPRIATE_AUTH;
1279 /* User binding as anonymous */
1280 if ( authzDN == NULL ) {
1286 LDAP_LOG( TRANSPORT, ENTRY,
1287 "slap_sasl_authorized: can %s become %s?\n",
1288 authcDN->bv_val, authzDN->bv_val, 0 );
1290 Debug( LDAP_DEBUG_TRACE,
1291 "==>slap_sasl_authorized: can %s become %s?\n",
1292 authcDN->bv_val, authzDN->bv_val, 0 );
1295 /* If person is authorizing to self, succeed */
1296 if ( dn_match( authcDN, authzDN ) ) {
1301 /* Allow the manager to authorize as any DN. */
1302 if( op->o_conn->c_authz_backend &&
1303 be_isroot_dn( op->o_conn->c_authz_backend, authcDN ))
1309 /* Check source rules */
1310 if( authz_policy & SASL_AUTHZ_TO ) {
1311 rc = slap_sasl_check_authz( op, authcDN, authzDN,
1312 slap_schema.si_ad_saslAuthzTo, authcDN );
1313 if( rc == LDAP_SUCCESS && !(authz_policy & SASL_AUTHZ_AND) ) {
1318 /* Check destination rules */
1319 if( authz_policy & SASL_AUTHZ_FROM ) {
1320 rc = slap_sasl_check_authz( op, authzDN, authcDN,
1321 slap_schema.si_ad_saslAuthzFrom, authcDN );
1322 if( rc == LDAP_SUCCESS ) {
1327 rc = LDAP_INAPPROPRIATE_AUTH;
1332 LDAP_LOG( TRANSPORT, RESULTS, "slap_sasl_authorized: return %d\n", rc,0,0 );
1334 Debug( LDAP_DEBUG_TRACE,
1335 "<== slap_sasl_authorized: return %d\n", rc, 0, 0 );