Skip to content
Snippets Groups Projects
saslauthz.c 50 KiB
Newer Older
  • Learn to ignore specific revisions
  • /* $OpenLDAP$ */
    
    Kurt Zeilenga's avatar
    Kurt Zeilenga committed
    /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
     *
    
    Kurt Zeilenga's avatar
    Kurt Zeilenga committed
     * Copyright 1998-2014 The OpenLDAP Foundation.
    
     * Portions Copyright 2000 Mark Adamson, Carnegie Mellon.
    
    Kurt Zeilenga's avatar
    Kurt Zeilenga committed
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without
     * modification, are permitted only as authorized by the OpenLDAP
     * Public License.
     *
     * A copy of this license is available in the file LICENSE in the
     * top-level directory of the distribution or, alternatively, at
     * <http://www.OpenLDAP.org/license.html>.
    
    Kurt Zeilenga's avatar
    Kurt Zeilenga committed
     */
    
    
    #include "portable.h"
    
    #include <stdio.h>
    
    #ifdef HAVE_LIMITS_H
    #include <limits.h>
    #endif
    
    #include <ac/stdlib.h>
    
    Kurt Zeilenga's avatar
    Kurt Zeilenga committed
    #include <ac/string.h>
    
    Kurt Zeilenga's avatar
    Kurt Zeilenga committed
    #include "slap.h"
    
    
    Pierangelo Masarati's avatar
    Pierangelo Masarati committed
    #include "lutil.h"
    
    #define SASLREGEX_REPLACE 10
    
    
    #define LDAP_X_SCOPE_EXACT	((ber_int_t) 0x0010)
    #define LDAP_X_SCOPE_REGEX	((ber_int_t) 0x0020)
    
    #define LDAP_X_SCOPE_CHILDREN	((ber_int_t) 0x0030)
    #define LDAP_X_SCOPE_SUBTREE	((ber_int_t) 0x0040)
    
    #define LDAP_X_SCOPE_ONELEVEL	((ber_int_t) 0x0050)
    
    Pierangelo Masarati's avatar
    Pierangelo Masarati committed
    #define LDAP_X_SCOPE_GROUP	((ber_int_t) 0x0060)
    
    Kurt Zeilenga's avatar
    Kurt Zeilenga committed
     * IDs in DNauthzid form can now have a type specifier, that
     * influences how they are used in related operations.
    
    Kurt Zeilenga's avatar
    Kurt Zeilenga committed
     * syntax: dn[.{exact|regex}]:<val>
    
     *
     * dn.exact:	the value must pass normalization and is used 
     *		in exact DN match.
     * dn.regex:	the value is treated as a regular expression 
    
    Kurt Zeilenga's avatar
    Kurt Zeilenga committed
     *		in matching DN values in authz{To|From}
    
     *		attributes.
     * dn:		for backwards compatibility reasons, the value 
     *		is treated as a regular expression, and thus 
     *		it is not normalized nor validated; it is used
     *		in exact or regex comparisons based on the 
     *		context.
    
    Kurt Zeilenga's avatar
    Kurt Zeilenga committed
     *
     * IDs in DNauthzid form can now have a type specifier, that
     * influences how they are used in related operations.
     *
     * syntax: u[.mech[/realm]]:<val>
     * 
     * where mech is a SIMPLE, AUTHZ, or a SASL mechanism name
     * and realm is mechanism specific realm (separate to those
     * which are representable as part of the principal).
    
    typedef struct sasl_regexp {
    
    Kurt Zeilenga's avatar
    Kurt Zeilenga committed
      char *sr_match;						/* regexp match pattern */
      char *sr_replace; 					/* regexp replace pattern */
      regex_t sr_workspace;					/* workspace for regexp engine */
      int sr_offset[SASLREGEX_REPLACE+2];	/* offsets of $1,$2... in *replace */
    
    } SaslRegexp_t;
    
    static int nSaslRegexp = 0;
    static SaslRegexp_t *SaslRegexp = NULL;
    
    
    #ifdef SLAP_AUTH_REWRITE
    
    #include "rewrite.h"
    struct rewrite_info	*sasl_rwinfo = NULL;
    #define AUTHID_CONTEXT	"authid"
    
    #endif /* SLAP_AUTH_REWRITE */
    
    /* What SASL proxy authorization policies are allowed? */
    
    #define	SASL_AUTHZ_NONE	0x00
    #define	SASL_AUTHZ_FROM	0x01
    #define	SASL_AUTHZ_TO	0x02
    #define SASL_AUTHZ_AND	0x10
    
    static const char *policy_txt[] = {
    	"none", "from", "to", "any"
    };
    
    
    static int authz_policy = SASL_AUTHZ_NONE;
    
    
    static int
    slap_sasl_match( Operation *opx, struct berval *rule,
    
    	struct berval *assertDN, struct berval *authc );
    
    
    int slap_sasl_setpolicy( const char *arg )
    {
    	int rc = LDAP_SUCCESS;
    
    
    Kurt Zeilenga's avatar
    Kurt Zeilenga committed
    	if ( strcasecmp( arg, "none" ) == 0 ) {
    
    		authz_policy = SASL_AUTHZ_NONE;
    
    Kurt Zeilenga's avatar
    Kurt Zeilenga committed
    	} else if ( strcasecmp( arg, "from" ) == 0 ) {
    
    		authz_policy = SASL_AUTHZ_FROM;
    
    Kurt Zeilenga's avatar
    Kurt Zeilenga committed
    	} else if ( strcasecmp( arg, "to" ) == 0 ) {
    
    		authz_policy = SASL_AUTHZ_TO;
    
    	} else if ( strcasecmp( arg, "both" ) == 0 || strcasecmp( arg, "any" ) == 0 ) {
    
    		authz_policy = SASL_AUTHZ_FROM | SASL_AUTHZ_TO;
    
    	} else if ( strcasecmp( arg, "all" ) == 0 ) {
    		authz_policy = SASL_AUTHZ_FROM | SASL_AUTHZ_TO | SASL_AUTHZ_AND;
    
    Kurt Zeilenga's avatar
    Kurt Zeilenga committed
    	} else {
    
    Kurt Zeilenga's avatar
    Kurt Zeilenga committed
    	}
    
    const char * slap_sasl_getpolicy()
    {
    	if ( authz_policy == (SASL_AUTHZ_FROM | SASL_AUTHZ_TO | SASL_AUTHZ_AND) )
    		return "all";
    	else
    		return policy_txt[authz_policy];
    }
    
    
    int slap_parse_user( struct berval *id, struct berval *user,
    		struct berval *realm, struct berval *mech )
    {
    	char	u;
    	
    
    	assert( user != NULL );
    	assert( realm != NULL );
    	assert( mech != NULL );
    
    
    	u = id->bv_val[ 0 ];
    	
    
    	if ( u != 'u' && u != 'U' ) {
    		/* called with something other than u: */
    		return LDAP_PROTOCOL_ERROR;
    	}
    
    	/* uauthzid form:
    	 *		u[.mech[/realm]]:user
    	 */
    	
    
    	user->bv_val = ber_bvchr( id, ':' );
    
    		return LDAP_PROTOCOL_ERROR;
    	}
    	user->bv_val[ 0 ] = '\0';
    	user->bv_val++;
    	user->bv_len = id->bv_len - ( user->bv_val - id->bv_val );
    
    
    	mech->bv_val = ber_bvchr( id, '.' );
    
    		mech->bv_val[ 0 ] = '\0';
    		mech->bv_val++;
    
    		mech->bv_len = user->bv_val - mech->bv_val - 1;
    
    		realm->bv_val = ber_bvchr( mech, '/' );
    
    			mech->bv_len = realm->bv_val - mech->bv_val - 1;
    
    			realm->bv_len = user->bv_val - realm->bv_val - 1;
    
    	}
    
    	if ( id->bv_val[ 1 ] != '\0' ) {
    		return LDAP_PROTOCOL_ERROR;
    	}
    
    
    		assert( mech->bv_val == id->bv_val + 2 );
    
    
    		AC_MEMCPY( mech->bv_val - 2, mech->bv_val, mech->bv_len + 1 );
    
    		mech->bv_val -= 2;
    	}
    
    
    		assert( realm->bv_val >= id->bv_val + 2 );
    
    
    		AC_MEMCPY( realm->bv_val - 2, realm->bv_val, realm->bv_len + 1 );
    
    		realm->bv_val -= 2;
    	}
    
    
    	/* leave "u:" before user */
    	user->bv_val -= 2;
    	user->bv_len += 2;
    	user->bv_val[ 0 ] = u;
    	user->bv_val[ 1 ] = ':';
    
    
    	return LDAP_SUCCESS;
    }
    
    
    	struct berval	bv;
    	int		rc = LDAP_INVALID_SYNTAX;
    	LDAPURLDesc	*ludp = NULL;
    	int		scope = -1;
    
    	/*
    	 * 1) <DN>
    	 * 2) dn[.{exact|children|subtree|onelevel}]:{*|<DN>}
    	 * 3) dn.regex:<pattern>
    	 * 4) u[.mech[/realm]]:<ID>
    	 * 5) group[/<groupClass>[/<memberAttr>]]:<DN>
    	 * 6) <URL>
    	 */
    
    	assert( in != NULL );
    	assert( !BER_BVISNULL( in ) );
    
    	Debug( LDAP_DEBUG_TRACE,
    		"authzValidate: parsing %s\n", in->bv_val, 0, 0 );
    
    	/*
    	 * 2) dn[.{exact|children|subtree|onelevel}]:{*|<DN>}
    	 * 3) dn.regex:<pattern>
    	 *
    	 * <DN> must pass DN normalization
    	 */
    	if ( !strncasecmp( in->bv_val, "dn", STRLENOF( "dn" ) ) ) {
    		bv.bv_val = in->bv_val + STRLENOF( "dn" );
    
    		if ( bv.bv_val[ 0 ] == '.' ) {
    			bv.bv_val++;
    
    			if ( !strncasecmp( bv.bv_val, "exact:", STRLENOF( "exact:" ) ) ) {
    				bv.bv_val += STRLENOF( "exact:" );
    				scope = LDAP_X_SCOPE_EXACT;
    
    			} else if ( !strncasecmp( bv.bv_val, "regex:", STRLENOF( "regex:" ) ) ) {
    				bv.bv_val += STRLENOF( "regex:" );
    				scope = LDAP_X_SCOPE_REGEX;
    
    			} else if ( !strncasecmp( bv.bv_val, "children:", STRLENOF( "children:" ) ) ) {
    				bv.bv_val += STRLENOF( "children:" );
    				scope = LDAP_X_SCOPE_CHILDREN;
    
    			} else if ( !strncasecmp( bv.bv_val, "subtree:", STRLENOF( "subtree:" ) ) ) {
    				bv.bv_val += STRLENOF( "subtree:" );
    				scope = LDAP_X_SCOPE_SUBTREE;
    
    			} else if ( !strncasecmp( bv.bv_val, "onelevel:", STRLENOF( "onelevel:" ) ) ) {
    				bv.bv_val += STRLENOF( "onelevel:" );
    				scope = LDAP_X_SCOPE_ONELEVEL;
    
    			} else {
    				return LDAP_INVALID_SYNTAX;
    			}
    
    		} else {
    			if ( bv.bv_val[ 0 ] != ':' ) {
    				return LDAP_INVALID_SYNTAX;
    			}
    			scope = LDAP_X_SCOPE_EXACT;
    			bv.bv_val++;
    		}
    
    		bv.bv_val += strspn( bv.bv_val, " " );
    		/* jump here in case no type specification was present
    		 * and uri was not an URI... HEADS-UP: assuming EXACT */
    is_dn:		bv.bv_len = in->bv_len - ( bv.bv_val - in->bv_val );
    
    		/* a single '*' means any DN without using regexes */
    		if ( ber_bvccmp( &bv, '*' ) ) {
    			/* LDAP_X_SCOPE_USERS */
    			return LDAP_SUCCESS;
    		}
    
    		switch ( scope ) {
    		case LDAP_X_SCOPE_EXACT:
    		case LDAP_X_SCOPE_CHILDREN:
    		case LDAP_X_SCOPE_SUBTREE:
    		case LDAP_X_SCOPE_ONELEVEL:
    			return dnValidate( NULL, &bv );
    
    		case LDAP_X_SCOPE_REGEX:
    			return LDAP_SUCCESS;
    		}
    
    		return rc;
    
    	/*
    	 * 4) u[.mech[/realm]]:<ID>
    	 */
    	} else if ( ( in->bv_val[ 0 ] == 'u' || in->bv_val[ 0 ] == 'U' )
    			&& ( in->bv_val[ 1 ] == ':' 
    				|| in->bv_val[ 1 ] == '/' 
    				|| in->bv_val[ 1 ] == '.' ) )
    	{
    		char		buf[ SLAP_LDAPDN_MAXLEN ];
    		struct berval	id,
    				user = BER_BVNULL,
    				realm = BER_BVNULL,
    				mech = BER_BVNULL;
    
    		if ( sizeof( buf ) <= in->bv_len ) {
    			return LDAP_INVALID_SYNTAX;
    		}
    
    		id.bv_len = in->bv_len;
    		id.bv_val = buf;
    		strncpy( buf, in->bv_val, sizeof( buf ) );
    
    		rc = slap_parse_user( &id, &user, &realm, &mech );
    		if ( rc != LDAP_SUCCESS ) {
    			return LDAP_INVALID_SYNTAX;
    		}
    
    		return rc;
    
    	/*
    	 * 5) group[/groupClass[/memberAttr]]:<DN>
    	 *
    	 * <groupClass> defaults to "groupOfNames"
    	 * <memberAttr> defaults to "member"
    	 * 
    	 * <DN> must pass DN normalization
    	 */
    	} else if ( strncasecmp( in->bv_val, "group", STRLENOF( "group" ) ) == 0 )
    	{
    		struct berval	group_dn = BER_BVNULL,
    				group_oc = BER_BVNULL,
    				member_at = BER_BVNULL;
    
    		bv.bv_val = in->bv_val + STRLENOF( "group" );
    
    		bv.bv_len = in->bv_len - STRLENOF( "group" );
    		group_dn.bv_val = ber_bvchr( &bv, ':' );
    
    		if ( group_dn.bv_val == NULL ) {
    			/* last chance: assume it's a(n exact) DN ... */
    			bv.bv_val = in->bv_val;
    			scope = LDAP_X_SCOPE_EXACT;
    			goto is_dn;
    		}
    		
    		/*
    		 * FIXME: we assume that "member" and "groupOfNames"
    		 * are present in schema...
    		 */
    		if ( bv.bv_val[ 0 ] == '/' ) {
    			group_oc.bv_val = &bv.bv_val[ 1 ];
    
    			group_oc.bv_len = group_dn.bv_val - group_oc.bv_val;
    
    			member_at.bv_val = ber_bvchr( &group_oc, '/' );
    
    			if ( member_at.bv_val ) {
    				AttributeDescription	*ad = NULL;
    				const char		*text = NULL;
    
    				group_oc.bv_len = member_at.bv_val - group_oc.bv_val;
    				member_at.bv_val++;
    				member_at.bv_len = group_dn.bv_val - member_at.bv_val;
    				rc = slap_bv2ad( &member_at, &ad, &text );
    				if ( rc != LDAP_SUCCESS ) {
    					return rc;
    				}
    
    			if ( oc_bvfind( &group_oc ) == NULL ) {
    				return LDAP_INVALID_SYNTAX;
    
    			}
    		}
    
    		group_dn.bv_val++;
    		group_dn.bv_len = in->bv_len - ( group_dn.bv_val - in->bv_val );
    
    		rc = dnValidate( NULL, &group_dn );
    		if ( rc != LDAP_SUCCESS ) {
    			return rc;
    		}
    
    		return rc;
    	}
    
    	/*
    	 * ldap:///<base>??<scope>?<filter>
    	 * <scope> ::= {base|one|subtree}
    	 *
    	 * <scope> defaults to "base"
    	 * <base> must pass DN normalization
    	 * <filter> must pass str2filter()
    	 */
    	rc = ldap_url_parse( in->bv_val, &ludp );
    	switch ( rc ) {
    	case LDAP_URL_SUCCESS:
    		/* FIXME: the check is pedantic, but I think it's necessary,
    		 * because people tend to use things like ldaps:// which
    		 * gives the idea SSL is being used.  Maybe we could
    		 * accept ldapi:// as well, but the point is that we use
    		 * an URL as an easy means to define bits of a search with
    		 * little parsing.
    		 */
    		if ( strcasecmp( ludp->lud_scheme, "ldap" ) != 0 ) {
    			/*
    			 * must be ldap:///
    			 */
    			rc = LDAP_INVALID_SYNTAX;
    			goto done;
    		}
    		break;
    
    	case LDAP_URL_ERR_BADSCHEME:
    		/*
    		 * last chance: assume it's a(n exact) DN ...
    		 *
    		 * NOTE: must pass DN normalization
    		 */
    		ldap_free_urldesc( ludp );
    		bv.bv_val = in->bv_val;
    		scope = LDAP_X_SCOPE_EXACT;
    		goto is_dn;
    
    	default:
    		rc = LDAP_INVALID_SYNTAX;
    		goto done;
    	}
    
    	if ( ( ludp->lud_host && *ludp->lud_host )
    		|| ludp->lud_attrs || ludp->lud_exts )
    	{
    		/* host part must be empty */
    		/* attrs and extensions parts must be empty */
    		rc = LDAP_INVALID_SYNTAX;
    		goto done;
    	}
    
    	/* Grab the filter */
    	if ( ludp->lud_filter ) {
    		Filter	*f = str2filter( ludp->lud_filter );
    		if ( f == NULL ) {
    			rc = LDAP_INVALID_SYNTAX;
    			goto done;
    		}
    		filter_free( f );
    	}
    
    	/* Grab the searchbase */
    
    	ber_str2bv( ludp->lud_dn, 0, 0, &bv );
    	rc = dnValidate( NULL, &bv );
    
    done:
    	ldap_free_urldesc( ludp );
    	return( rc );
    }
    
    static int
    authzPrettyNormal(
    	struct berval	*val,
    	struct berval	*normalized,
    	void		*ctx,
    	int		normalize )
    {
    	struct berval	bv;
    	int		rc = LDAP_INVALID_SYNTAX;
    	LDAPURLDesc	*ludp = NULL;
    	char		*lud_dn = NULL,
    			*lud_filter = NULL;
    	int		scope = -1;
    
    	/*
    	 * 1) <DN>
    	 * 2) dn[.{exact|children|subtree|onelevel}]:{*|<DN>}
    	 * 3) dn.regex:<pattern>
    	 * 4) u[.mech[/realm]]:<ID>
    	 * 5) group[/<groupClass>[/<memberAttr>]]:<DN>
    	 * 6) <URL>
    	 */
    
    	assert( val != NULL );
    	assert( !BER_BVISNULL( val ) );
    
    	/*
    	 * 2) dn[.{exact|children|subtree|onelevel}]:{*|<DN>}
    	 * 3) dn.regex:<pattern>
    	 *
    	 * <DN> must pass DN normalization
    	 */
    	if ( !strncasecmp( val->bv_val, "dn", STRLENOF( "dn" ) ) ) {
    		struct berval	out = BER_BVNULL,
    				prefix = BER_BVNULL;
    		char		*ptr;
    
    		bv.bv_val = val->bv_val + STRLENOF( "dn" );
    
    		if ( bv.bv_val[ 0 ] == '.' ) {
    			bv.bv_val++;
    
    			if ( !strncasecmp( bv.bv_val, "exact:", STRLENOF( "exact:" ) ) ) {
    				bv.bv_val += STRLENOF( "exact:" );
    				scope = LDAP_X_SCOPE_EXACT;
    
    			} else if ( !strncasecmp( bv.bv_val, "regex:", STRLENOF( "regex:" ) ) ) {
    				bv.bv_val += STRLENOF( "regex:" );
    				scope = LDAP_X_SCOPE_REGEX;
    
    			} else if ( !strncasecmp( bv.bv_val, "children:", STRLENOF( "children:" ) ) ) {
    				bv.bv_val += STRLENOF( "children:" );
    				scope = LDAP_X_SCOPE_CHILDREN;
    
    			} else if ( !strncasecmp( bv.bv_val, "subtree:", STRLENOF( "subtree:" ) ) ) {
    				bv.bv_val += STRLENOF( "subtree:" );
    				scope = LDAP_X_SCOPE_SUBTREE;
    
    			} else if ( !strncasecmp( bv.bv_val, "onelevel:", STRLENOF( "onelevel:" ) ) ) {
    				bv.bv_val += STRLENOF( "onelevel:" );
    				scope = LDAP_X_SCOPE_ONELEVEL;
    
    			} else {
    				return LDAP_INVALID_SYNTAX;
    			}
    
    		} else {
    			if ( bv.bv_val[ 0 ] != ':' ) {
    				return LDAP_INVALID_SYNTAX;
    			}
    			scope = LDAP_X_SCOPE_EXACT;
    			bv.bv_val++;
    		}
    
    		bv.bv_val += strspn( bv.bv_val, " " );
    		/* jump here in case no type specification was present
    		 * and uri was not an URI... HEADS-UP: assuming EXACT */
    is_dn:		bv.bv_len = val->bv_len - ( bv.bv_val - val->bv_val );
    
    		/* a single '*' means any DN without using regexes */
    		if ( ber_bvccmp( &bv, '*' ) ) {
    			ber_str2bv_x( "dn:*", STRLENOF( "dn:*" ), 1, normalized, ctx );
    			return LDAP_SUCCESS;
    		}
    
    		switch ( scope ) {
    		case LDAP_X_SCOPE_EXACT:
    		case LDAP_X_SCOPE_CHILDREN:
    		case LDAP_X_SCOPE_SUBTREE:
    		case LDAP_X_SCOPE_ONELEVEL:
    			if ( normalize ) {
    				rc = dnNormalize( 0, NULL, NULL, &bv, &out, ctx );
    			} else {
    				rc = dnPretty( NULL, &bv, &out, ctx );
    			}
    			if( rc != LDAP_SUCCESS ) {
    				return LDAP_INVALID_SYNTAX;
    			}
    			break;
    
    		case LDAP_X_SCOPE_REGEX:
    			normalized->bv_len = STRLENOF( "dn.regex:" ) + bv.bv_len;
    			normalized->bv_val = ber_memalloc_x( normalized->bv_len + 1, ctx );
    			ptr = lutil_strcopy( normalized->bv_val, "dn.regex:" );
    			ptr = lutil_strncopy( ptr, bv.bv_val, bv.bv_len );
    			ptr[ 0 ] = '\0';
    			return LDAP_SUCCESS;
    
    		default:
    			return LDAP_INVALID_SYNTAX;
    		}
    
    		/* prepare prefix */
    		switch ( scope ) {
    		case LDAP_X_SCOPE_EXACT:
    			BER_BVSTR( &prefix, "dn:" );
    			break;
    
    		case LDAP_X_SCOPE_CHILDREN:
    			BER_BVSTR( &prefix, "dn.children:" );
    			break;
    
    		case LDAP_X_SCOPE_SUBTREE:
    			BER_BVSTR( &prefix, "dn.subtree:" );
    			break;
    
    		case LDAP_X_SCOPE_ONELEVEL:
    			BER_BVSTR( &prefix, "dn.onelevel:" );
    			break;
    
    		default:
    			assert( 0 );
    			break;
    		}
    
    		normalized->bv_len = prefix.bv_len + out.bv_len;
    		normalized->bv_val = ber_memalloc_x( normalized->bv_len + 1, ctx );
    		
    		ptr = lutil_strcopy( normalized->bv_val, prefix.bv_val );
    		ptr = lutil_strncopy( ptr, out.bv_val, out.bv_len );
    		ptr[ 0 ] = '\0';
    		ber_memfree_x( out.bv_val, ctx );
    
    		return LDAP_SUCCESS;
    
    	/*
    	 * 4) u[.mech[/realm]]:<ID>
    	 */
    	} else if ( ( val->bv_val[ 0 ] == 'u' || val->bv_val[ 0 ] == 'U' )
    			&& ( val->bv_val[ 1 ] == ':' 
    				|| val->bv_val[ 1 ] == '/' 
    				|| val->bv_val[ 1 ] == '.' ) )
    	{
    		char		buf[ SLAP_LDAPDN_MAXLEN ];
    		struct berval	id,
    				user = BER_BVNULL,
    				realm = BER_BVNULL,
    				mech = BER_BVNULL;
    
    		if ( sizeof( buf ) <= val->bv_len ) {
    			return LDAP_INVALID_SYNTAX;
    		}
    
    		id.bv_len = val->bv_len;
    		id.bv_val = buf;
    		strncpy( buf, val->bv_val, sizeof( buf ) );
    
    		rc = slap_parse_user( &id, &user, &realm, &mech );
    		if ( rc != LDAP_SUCCESS ) {
    			return LDAP_INVALID_SYNTAX;
    		}
    
    		ber_dupbv_x( normalized, val, ctx );
    
    		return rc;
    
    	/*
    	 * 5) group[/groupClass[/memberAttr]]:<DN>
    	 *
    	 * <groupClass> defaults to "groupOfNames"
    	 * <memberAttr> defaults to "member"
    	 * 
    	 * <DN> must pass DN normalization
    	 */
    	} else if ( strncasecmp( val->bv_val, "group", STRLENOF( "group" ) ) == 0 )
    	{
    		struct berval	group_dn = BER_BVNULL,
    				group_oc = BER_BVNULL,
    				member_at = BER_BVNULL,
    				out = BER_BVNULL;
    		char		*ptr;
    
    		bv.bv_val = val->bv_val + STRLENOF( "group" );
    
    		bv.bv_len = val->bv_len - STRLENOF( "group" );
    		group_dn.bv_val = ber_bvchr( &bv, ':' );
    
    		if ( group_dn.bv_val == NULL ) {
    			/* last chance: assume it's a(n exact) DN ... */
    			bv.bv_val = val->bv_val;
    			scope = LDAP_X_SCOPE_EXACT;
    			goto is_dn;
    		}
    
    		/*
    		 * FIXME: we assume that "member" and "groupOfNames"
    		 * are present in schema...
    		 */
    		if ( bv.bv_val[ 0 ] == '/' ) {
    
    			ObjectClass		*oc = NULL;
    
    
    			group_oc.bv_len = group_dn.bv_val - group_oc.bv_val;
    
    			member_at.bv_val = ber_bvchr( &group_oc, '/' );
    
    			if ( member_at.bv_val ) {
    				AttributeDescription	*ad = NULL;
    				const char		*text = NULL;
    
    				group_oc.bv_len = member_at.bv_val - group_oc.bv_val;
    				member_at.bv_val++;
    				member_at.bv_len = group_dn.bv_val - member_at.bv_val;
    				rc = slap_bv2ad( &member_at, &ad, &text );
    				if ( rc != LDAP_SUCCESS ) {
    					return rc;
    				}
    
    				member_at = ad->ad_cname;
    
    
    			oc = oc_bvfind( &group_oc );
    			if ( oc == NULL ) {
    				return LDAP_INVALID_SYNTAX;
    
    
    			group_oc = oc->soc_cname;
    
    		}
    
    		group_dn.bv_val++;
    		group_dn.bv_len = val->bv_len - ( group_dn.bv_val - val->bv_val );
    
    		if ( normalize ) {
    			rc = dnNormalize( 0, NULL, NULL, &group_dn, &out, ctx );
    		} else {
    			rc = dnPretty( NULL, &group_dn, &out, ctx );
    		}
    		if ( rc != LDAP_SUCCESS ) {
    			return rc;
    		}
    
    		normalized->bv_len = STRLENOF( "group" ":" ) + out.bv_len;
    		if ( !BER_BVISNULL( &group_oc ) ) {
    			normalized->bv_len += STRLENOF( "/" ) + group_oc.bv_len;
    			if ( !BER_BVISNULL( &member_at ) ) {
    				normalized->bv_len += STRLENOF( "/" ) + member_at.bv_len;
    			}
    		}
    
    		normalized->bv_val = ber_memalloc_x( normalized->bv_len + 1, ctx );
    		ptr = lutil_strcopy( normalized->bv_val, "group" );
    		if ( !BER_BVISNULL( &group_oc ) ) {
    			ptr[ 0 ] = '/';
    			ptr++;
    			ptr = lutil_strncopy( ptr, group_oc.bv_val, group_oc.bv_len );
    			if ( !BER_BVISNULL( &member_at ) ) {
    				ptr[ 0 ] = '/';
    				ptr++;
    				ptr = lutil_strncopy( ptr, member_at.bv_val, member_at.bv_len );
    			}
    		}
    		ptr[ 0 ] = ':';
    		ptr++;
    		ptr = lutil_strncopy( ptr, out.bv_val, out.bv_len );
    		ptr[ 0 ] = '\0';
    		ber_memfree_x( out.bv_val, ctx );
    
    		return rc;
    	}
    
    	/*
    	 * ldap:///<base>??<scope>?<filter>
    	 * <scope> ::= {base|one|subtree}
    	 *
    	 * <scope> defaults to "base"
    	 * <base> must pass DN normalization
    	 * <filter> must pass str2filter()
    	 */
    	rc = ldap_url_parse( val->bv_val, &ludp );
    	switch ( rc ) {
    	case LDAP_URL_SUCCESS:
    		/* FIXME: the check is pedantic, but I think it's necessary,
    		 * because people tend to use things like ldaps:// which
    		 * gives the idea SSL is being used.  Maybe we could
    		 * accept ldapi:// as well, but the point is that we use
    		 * an URL as an easy means to define bits of a search with
    		 * little parsing.
    		 */
    		if ( strcasecmp( ludp->lud_scheme, "ldap" ) != 0 ) {
    			/*
    			 * must be ldap:///
    			 */
    			rc = LDAP_INVALID_SYNTAX;
    			goto done;
    		}
    
    		AC_MEMCPY( ludp->lud_scheme, "ldap", STRLENOF( "ldap" ) );
    		break;
    
    	case LDAP_URL_ERR_BADSCHEME:
    		/*
    		 * last chance: assume it's a(n exact) DN ...
    		 *
    		 * NOTE: must pass DN normalization
    		 */
    		ldap_free_urldesc( ludp );
    		bv.bv_val = val->bv_val;
    		scope = LDAP_X_SCOPE_EXACT;
    		goto is_dn;
    
    	default:
    		rc = LDAP_INVALID_SYNTAX;
    		goto done;
    	}
    
    	if ( ( ludp->lud_host && *ludp->lud_host )
    		|| ludp->lud_attrs || ludp->lud_exts )
    	{
    		/* host part must be empty */
    		/* attrs and extensions parts must be empty */
    		rc = LDAP_INVALID_SYNTAX;
    		goto done;
    	}
    
    	/* Grab the filter */
    	if ( ludp->lud_filter ) {
    		struct berval	filterstr;
    		Filter		*f;
    
    		lud_filter = ludp->lud_filter;
    
    		f = str2filter( lud_filter );
    		if ( f == NULL ) {
    			rc = LDAP_INVALID_SYNTAX;
    			goto done;
    		}
    		filter2bv( f, &filterstr );
    		filter_free( f );
    		if ( BER_BVISNULL( &filterstr ) ) {
    			rc = LDAP_INVALID_SYNTAX;
    			goto done;
    		}
    
    		ludp->lud_filter = filterstr.bv_val;
    	}
    
    	/* Grab the searchbase */
    
    	if ( ludp->lud_dn ) {
    		struct berval	out = BER_BVNULL;
    
    		lud_dn = ludp->lud_dn;
    
    		ber_str2bv( lud_dn, 0, 0, &bv );
    		if ( normalize ) {
    			rc = dnNormalize( 0, NULL, NULL, &bv, &out, ctx );
    		} else {
    			rc = dnPretty( NULL, &bv, &out, ctx );
    		}
    
    		if ( rc != LDAP_SUCCESS ) {
    			goto done;
    		}
    
    		ludp->lud_dn = out.bv_val;
    	}
    
    	ludp->lud_port = 0;
    	normalized->bv_val = ldap_url_desc2str( ludp );
    	if ( normalized->bv_val ) {
    		normalized->bv_len = strlen( normalized->bv_val );
    
    	} else {
    		rc = LDAP_INVALID_SYNTAX;
    	}
    
    done:
    	if ( lud_filter ) {
    		if ( ludp->lud_filter != lud_filter ) {
    			ber_memfree( ludp->lud_filter );
    		}
    		ludp->lud_filter = lud_filter;
    	}
    
    	if ( lud_dn ) {
    		if ( ludp->lud_dn != lud_dn ) {
    			ber_memfree( ludp->lud_dn );
    		}
    		ludp->lud_dn = lud_dn;
    	}
    
    	ldap_free_urldesc( ludp );
    
    	return( rc );
    }
    
    int
    authzNormalize(
    	slap_mask_t	usage,
    	Syntax		*syntax,
    	MatchingRule	*mr,
    	struct berval	*val,
    	struct berval	*normalized,
    	void		*ctx )
    {
    	int		rc;
    
    	Debug( LDAP_DEBUG_TRACE, ">>> authzNormalize: <%s>\n",
    		val->bv_val, 0, 0 );
    
    	rc = authzPrettyNormal( val, normalized, ctx, 1 );
    
    	Debug( LDAP_DEBUG_TRACE, "<<< authzNormalize: <%s> (%d)\n",
    		normalized->bv_val, rc, 0 );
    
    	return rc;
    }
    
    int
    authzPretty(
    	Syntax *syntax,
    	struct berval *val,
    	struct berval *out,
    	void *ctx)
    {
    	int		rc;
    
    	Debug( LDAP_DEBUG_TRACE, ">>> authzPretty: <%s>\n",
    		val->bv_val, 0, 0 );
    
    	rc = authzPrettyNormal( val, out, ctx, 0 );
    
    	Debug( LDAP_DEBUG_TRACE, "<<< authzPretty: <%s> (%d)\n",
    		out->bv_val, rc, 0 );
    
    	return rc;
    }
    
    
    static int
    slap_parseURI(
    	Operation	*op,
    	struct berval	*uri,
    	struct berval	*base,
    	struct berval	*nbase,
    	int		*scope,
    	Filter		**filter,
    	struct berval	*fstr,
    	int		normalize )
    {
    	struct berval	bv;
    	int		rc;
    	LDAPURLDesc	*ludp;
    
    	struct berval	idx;
    
    	assert( uri != NULL && !BER_BVISNULL( uri ) );
    	BER_BVZERO( base );
    	BER_BVZERO( nbase );
    	BER_BVZERO( fstr );
    
    	*scope = -1;
    	*filter = NULL;
    
    
    	Debug( LDAP_DEBUG_TRACE,
    		"slap_parseURI: parsing %s\n", uri->bv_val, 0, 0 );
    
    	rc = LDAP_PROTOCOL_ERROR;
    
    		ptr = ber_bvchr( &idx, '}' ) + 1;
    
    
    		assert( ptr != (void *)1 );
    
    		idx.bv_len -= ptr - idx.bv_val;
    		idx.bv_val = ptr;
    		uri = &idx;
    	}
    
    
    	/*
    	 * dn[.<dnstyle>]:<dnpattern>
    	 * <dnstyle> ::= {exact|regex|children|subtree|onelevel}
    	 *
    	 * <dnstyle> defaults to "exact"
    	 * if <dnstyle> is not "regex", <dnpattern> must pass DN normalization
    	 */
    
    	if ( !strncasecmp( uri->bv_val, "dn", STRLENOF( "dn" ) ) ) {
    		bv.bv_val = uri->bv_val + STRLENOF( "dn" );
    
    			if ( !strncasecmp( bv.bv_val, "exact:", STRLENOF( "exact:" ) ) ) {
    				bv.bv_val += STRLENOF( "exact:" );
    
    			} else if ( !strncasecmp( bv.bv_val, "regex:", STRLENOF( "regex:" ) ) ) {
    				bv.bv_val += STRLENOF( "regex:" );
    
    			} else if ( !strncasecmp( bv.bv_val, "children:", STRLENOF( "children:" ) ) ) {
    
    			} else if ( !strncasecmp( bv.bv_val, "subtree:", STRLENOF( "subtree:" ) ) ) {
    				bv.bv_val += STRLENOF( "subtree:" );
    
    			} else if ( !strncasecmp( bv.bv_val, "onelevel:", STRLENOF( "onelevel:" ) ) ) {
    				bv.bv_val += STRLENOF( "onelevel:" );
    
    Pierangelo Masarati's avatar
    Pierangelo Masarati committed
    			if ( bv.bv_val[ 0 ] != ':' ) {
    
    				return LDAP_PROTOCOL_ERROR;
    
    Pierangelo Masarati's avatar
    Pierangelo Masarati committed
    			}
    
    
    		bv.bv_val += strspn( bv.bv_val, " " );
    
    		/* jump here in case no type specification was present
    
    		 * and uri was not an URI... HEADS-UP: assuming EXACT */
    
    is_dn:		bv.bv_len = uri->bv_len - (bv.bv_val - uri->bv_val);
    
    		/* a single '*' means any DN without using regexes */