Skip to content
Snippets Groups Projects
aci.c 43.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • /* aci.c - routines to parse and check acl's */
    /* $OpenLDAP$ */
    /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
     *
    
    Kurt Zeilenga's avatar
    Kurt Zeilenga committed
     * Copyright 1998-2012 The OpenLDAP Foundation.
    
     * 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>.
     */
    /* Portions Copyright (c) 1995 Regents of the University of Michigan.
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms are permitted
     * provided that this notice is preserved and that due credit is given
     * to the University of Michigan at Ann Arbor. The name of the University
     * may not be used to endorse or promote products derived from this
     * software without specific prior written permission. This software
     * is provided ``as is'' without express or implied warranty.
     */
    
    #include "portable.h"
    
    #ifdef SLAPD_ACI_ENABLED
    
    #include <stdio.h>
    
    #include <ac/ctype.h>
    #include <ac/regex.h>
    #include <ac/socket.h>
    #include <ac/string.h>
    #include <ac/unistd.h>
    
    #include "slap.h"
    #include "lber_pvt.h"
    #include "lutil.h"
    
    
    /* use most appropriate size */
    #define ACI_BUF_SIZE 			1024
    
    /* move to "stable" when no longer experimental */
    #define SLAPD_ACI_SYNTAX		"1.3.6.1.4.1.4203.666.2.1"
    
    /* change this to "OpenLDAPset" */
    #define SLAPD_ACI_SET_ATTR		"template"
    
    Pierangelo Masarati's avatar
    Pierangelo Masarati committed
    typedef enum slap_aci_scope_t {
    	SLAP_ACI_SCOPE_ENTRY		= 0x1,
    	SLAP_ACI_SCOPE_CHILDREN		= 0x2,
    	SLAP_ACI_SCOPE_SUBTREE		= ( SLAP_ACI_SCOPE_ENTRY | SLAP_ACI_SCOPE_CHILDREN )
    } slap_aci_scope_t;
    
    
    enum {
    	ACI_BV_ENTRY,
    	ACI_BV_CHILDREN,
    	ACI_BV_ONELEVEL,
    	ACI_BV_SUBTREE,
    
    	ACI_BV_BR_ENTRY,
    
    	ACI_BV_BR_ALL,
    
    	ACI_BV_ACCESS_ID,
    	ACI_BV_PUBLIC,
    	ACI_BV_USERS,
    	ACI_BV_SELF,
    	ACI_BV_DNATTR,
    	ACI_BV_GROUP,
    	ACI_BV_ROLE,
    	ACI_BV_SET,
    	ACI_BV_SET_REF,
    
    	ACI_BV_GRANT,
    	ACI_BV_DENY,
    
    	ACI_BV_GROUP_CLASS,
    	ACI_BV_GROUP_ATTR,
    	ACI_BV_ROLE_CLASS,
    	ACI_BV_ROLE_ATTR,
    
    	ACI_BV_SET_ATTR,
    
    	ACI_BV_LAST
    };
    
    static const struct berval	aci_bv[] = {
    	/* scope */
    	BER_BVC("entry"),
    	BER_BVC("children"),
    	BER_BVC("onelevel"),
    	BER_BVC("subtree"),
    
    	/* */
    	BER_BVC("[entry]"),
    
    	BER_BVC("[all]"),
    
    	/* type */
    	BER_BVC("access-id"),
    	BER_BVC("public"),
    	BER_BVC("users"),
    	BER_BVC("self"),
    	BER_BVC("dnattr"),
    	BER_BVC("group"),
    	BER_BVC("role"),
    	BER_BVC("set"),
    	BER_BVC("set-ref"),
    
    	/* actions */
    	BER_BVC("grant"),
    	BER_BVC("deny"),
    
    	/* schema */
    	BER_BVC(SLAPD_GROUP_CLASS),
    	BER_BVC(SLAPD_GROUP_ATTR),
    	BER_BVC(SLAPD_ROLE_CLASS),
    	BER_BVC(SLAPD_ROLE_ATTR),
    
    	BER_BVC(SLAPD_ACI_SET_ATTR),
    
    	BER_BVNULL
    };
    
    
    static AttributeDescription	*slap_ad_aci;
    
    
    static int
    OpenLDAPaciValidate(
    	Syntax		*syntax,
    	struct berval	*val );
    
    static int
    OpenLDAPaciPretty(
    	Syntax		*syntax,
    	struct berval	*val,
    	struct berval	*out,
    	void		*ctx );
    
    static int
    OpenLDAPaciNormalize(
    	slap_mask_t	use,
    	Syntax		*syntax,
    	MatchingRule	*mr,
    	struct berval	*val,
    	struct berval	*out,
    	void		*ctx );
    
    #define	OpenLDAPaciMatch			octetStringMatch
    
    
    static int
    aci_list_map_rights(
    	struct berval	*list )
    {
    	struct berval	bv;
    	slap_access_t	mask;
    	int		i;
    
    	ACL_INIT( mask );
    	for ( i = 0; acl_get_part( list, i, ',', &bv ) >= 0; i++ ) {
    		if ( bv.bv_len <= 0 ) {
    			continue;
    		}
    
    		switch ( *bv.bv_val ) {
    
    		case 'x':
    			/* **** NOTE: draft-ietf-ldapext-aci-model-0.3.txt does not 
    			 * define any equivalent to the AUTH right, so I've just used
    			 * 'x' for now.
    			 */
    			ACL_PRIV_SET(mask, ACL_PRIV_AUTH);
    			break;
    		case 'd':
    			/* **** NOTE: draft-ietf-ldapext-aci-model-0.3.txt defines
    			 * the right 'd' to mean "delete"; we hijack it to mean
    			 * "disclose" for consistency wuith the rest of slapd.
    			 */
    			ACL_PRIV_SET(mask, ACL_PRIV_DISCLOSE);
    			break;
    
    		case 'c':
    			ACL_PRIV_SET(mask, ACL_PRIV_COMPARE);
    			break;
    		case 's':
    			/* **** NOTE: draft-ietf-ldapext-aci-model-0.3.txt defines
    			 * the right 's' to mean "set", but in the examples states
    			 * that the right 's' means "search".  The latter definition
    			 * is used here.
    			 */
    			ACL_PRIV_SET(mask, ACL_PRIV_SEARCH);
    			break;
    		case 'r':
    			ACL_PRIV_SET(mask, ACL_PRIV_READ);
    			break;
    		case 'w':
    			ACL_PRIV_SET(mask, ACL_PRIV_WRITE);
    			break;
    		default:
    			break;
    		}
    
    	}
    
    	return mask;
    }
    
    static int
    aci_list_has_attr(
    	struct berval		*list,
    	const struct berval	*attr,
    	struct berval		*val )
    {
    	struct berval	bv, left, right;
    	int		i;
    
    	for ( i = 0; acl_get_part( list, i, ',', &bv ) >= 0; i++ ) {
    		if ( acl_get_part(&bv, 0, '=', &left ) < 0
    			|| acl_get_part( &bv, 1, '=', &right ) < 0 )
    		{
    			if ( ber_bvstrcasecmp( attr, &bv ) == 0 ) {
    				return(1);
    			}
    
    		} else if ( val == NULL ) {
    			if ( ber_bvstrcasecmp( attr, &left ) == 0 ) {
    				return(1);
    			}
    
    		} else {
    			if ( ber_bvstrcasecmp( attr, &left ) == 0 ) {
    				/* FIXME: this is also totally undocumented! */
    				/* this is experimental code that implements a
    				 * simple (prefix) match of the attribute value.
    				 * the ACI draft does not provide for aci's that
    				 * apply to specific values, but it would be
    				 * nice to have.  If the <attr> part of an aci's
    				 * rights list is of the form <attr>=<value>,
    				 * that means the aci applies only to attrs with
    				 * the given value.  Furthermore, if the attr is
    				 * of the form <attr>=<value>*, then <value> is
    				 * treated as a prefix, and the aci applies to 
    				 * any value with that prefix.
    				 *
    				 * Ideally, this would allow r.e. matches.
    				 */
    				if ( acl_get_part( &right, 0, '*', &left ) < 0
    					|| right.bv_len <= left.bv_len )
    				{
    					if ( ber_bvstrcasecmp( val, &right ) == 0 ) {
    						return 1;
    					}
    
    				} else if ( val->bv_len >= left.bv_len ) {
    					if ( strncasecmp( val->bv_val, left.bv_val, left.bv_len ) == 0 ) {
    						return(1);
    					}
    				}
    			}
    		}
    	}
    
    	return 0;
    }
    
    static slap_access_t
    aci_list_get_attr_rights(
    	struct berval		*list,
    	const struct berval	*attr,
    	struct berval		*val )
    {
    	struct berval	bv;
    	slap_access_t	mask;
    	int		i;
    
    	/* loop through each rights/attr pair, skip first part (action) */
    	ACL_INIT(mask);
    	for ( i = 1; acl_get_part( list, i + 1, ';', &bv ) >= 0; i += 2 ) {
    		if ( aci_list_has_attr( &bv, attr, val ) == 0 ) {
    
    			Debug( LDAP_DEBUG_ACL,
    				"        <= aci_list_get_attr_rights "
    				"test %s for %s -> failed\n",
    				bv.bv_val, attr->bv_val, 0 );
    
    
    		Debug( LDAP_DEBUG_ACL,
    			"        <= aci_list_get_attr_rights "
    			"test %s for %s -> ok\n",
    			bv.bv_val, attr->bv_val, 0 );
    
    
    		if ( acl_get_part( list, i, ';', &bv ) < 0 ) {
    
    			Debug( LDAP_DEBUG_ACL,
    				"        <= aci_list_get_attr_rights "
    				"test no rights\n",
    				0, 0, 0 );
    
    			continue;
    		}
    
    		mask |= aci_list_map_rights( &bv );
    
    		Debug( LDAP_DEBUG_ACL,
    			"        <= aci_list_get_attr_rights "
    			"rights %s to mask 0x%x\n",
    			bv.bv_val, mask, 0 );
    
    	}
    
    	return mask;
    }
    
    static int
    aci_list_get_rights(
    
    	struct berval	*list,
    	struct berval	*attr,
    	struct berval	*val,
    	slap_access_t	*grant,
    	slap_access_t	*deny )
    
    	slap_access_t	*mask;
    	int		i, found;
    
    
    	if ( attr == NULL || BER_BVISEMPTY( attr ) ) {
    		attr = (struct berval *)&aci_bv[ ACI_BV_ENTRY ];
    
    	} else if ( acl_get_part( attr, 0, ';', &baseattr ) > 0 ) {
    		attr = &baseattr;
    	}
    
    	found = 0;
    	ACL_INIT(*grant);
    	ACL_INIT(*deny);
    	/* loop through each permissions clause */
    	for ( i = 0; acl_get_part( list, i, '$', &perm ) >= 0; i++ ) {
    		if ( acl_get_part( &perm, 0, ';', &actn ) < 0 ) {
    			continue;
    		}
    
    		if ( ber_bvstrcasecmp( &aci_bv[ ACI_BV_GRANT ], &actn ) == 0 ) {
    			mask = grant;
    
    		} else if ( ber_bvstrcasecmp( &aci_bv[ ACI_BV_DENY ], &actn ) == 0 ) {
    			mask = deny;
    
    		} else {
    			continue;
    		}
    
    		*mask |= aci_list_get_attr_rights( &perm, attr, val );
    		*mask |= aci_list_get_attr_rights( &perm, &aci_bv[ ACI_BV_BR_ALL ], NULL );
    
    Quanah Gibson-Mount's avatar
    Quanah Gibson-Mount committed
    
    		if ( *mask != ACL_PRIV_NONE ) { 
    			found = 1;
    		}
    
    	}
    
    	return found;
    }
    
    static int
    aci_group_member (
    	struct berval		*subj,
    	const struct berval	*defgrpoc,
    	const struct berval	*defgrpat,
    	Operation		*op,
    	Entry			*e,
    	int			nmatch,
    	regmatch_t		*matches
    )
    {
    	struct berval		subjdn;
    	struct berval		grpoc;
    	struct berval		grpat;
    	ObjectClass		*grp_oc = NULL;
    	AttributeDescription	*grp_ad = NULL;
    	const char		*text;
    	int			rc;
    
    
    	/* format of string is "{group|role}/objectClassValue/groupAttrName" */
    
    	if ( acl_get_part( subj, 0, '/', &subjdn ) < 0 ) {
    		return 0;
    	}
    
    	if ( acl_get_part( subj, 1, '/', &grpoc ) < 0 ) {
    		grpoc = *defgrpoc;
    	}
    
    	if ( acl_get_part( subj, 2, '/', &grpat ) < 0 ) {
    		grpat = *defgrpat;
    	}
    
    	rc = slap_bv2ad( &grpat, &grp_ad, &text );
    	if ( rc != LDAP_SUCCESS ) {
    		rc = 0;
    		goto done;
    	}
    	rc = 0;
    
    	grp_oc = oc_bvfind( &grpoc );
    
    	if ( grp_oc != NULL && grp_ad != NULL ) {
    		char		buf[ ACI_BUF_SIZE ];
    		struct berval	bv, ndn;
    
    Quanah Gibson-Mount's avatar
    Quanah Gibson-Mount committed
    		AclRegexMatches amatches = { 0 };
    
    		amatches.dn_count = nmatch;
    		AC_MEMCPY( amatches.dn_data, matches, sizeof( amatches.dn_data ) );
    
    
    		bv.bv_len = sizeof( buf ) - 1;
    		bv.bv_val = (char *)&buf;
    		if ( acl_string_expand( &bv, &subjdn,
    
    Quanah Gibson-Mount's avatar
    Quanah Gibson-Mount committed
    				&e->e_nname, NULL, &amatches ) )
    
    		{
    			rc = LDAP_OTHER;
    			goto done;
    		}
    
    		if ( dnNormalize( 0, NULL, NULL, &bv, &ndn, op->o_tmpmemctx ) == LDAP_SUCCESS )
    		{
    			rc = ( backend_group( op, e, &ndn, &op->o_ndn,
    				grp_oc, grp_ad ) == 0 );
    			slap_sl_free( ndn.bv_val, op->o_tmpmemctx );
    		}
    	}
    
    done:
    	return rc;
    }
    
    
    aci_mask(
    	Operation		*op,
    	Entry			*e,
    	AttributeDescription	*desc,
    	struct berval		*val,
    	struct berval		*aci,
    	int			nmatch,
    	regmatch_t		*matches,
    	slap_access_t		*grant,
    	slap_access_t		*deny,
    	slap_aci_scope_t	asserted_scope )
    {
    
    	struct berval		bv,
    				scope,
    				perms,
    				type,
    				opts,
    				sdn;
    
    Quanah Gibson-Mount's avatar
    Quanah Gibson-Mount committed
    
    	ACL_INIT( *grant );
    	ACL_INIT( *deny );
    
    
    	assert( !BER_BVISNULL( &desc->ad_cname ) );
    
    	/* parse an aci of the form:
    		oid # scope # action;rights;attr;rights;attr 
    			$ action;rights;attr;rights;attr # type # subject
    
    	   [NOTE: the following comment is very outdated,
    	   as the draft version it refers to (Ando, 2004-11-20)].
    
    	   See draft-ietf-ldapext-aci-model-04.txt section 9.1 for
    	   a full description of the format for this attribute.
    	   Differences: "this" in the draft is "self" here, and
    	   "self" and "public" is in the position of type.
    
    	   <scope> = {entry|children|subtree}
    	   <type> = {public|users|access-id|subtree|onelevel|children|
    	             self|dnattr|group|role|set|set-ref}
    
    	   This routine now supports scope={ENTRY,CHILDREN}
    	   with the semantics:
    	     - ENTRY applies to "entry" and "subtree";
    
    	     - CHILDREN applies to "children" and "subtree"
    
    	 */
    
    	/* check that the aci has all 5 components */
    	if ( acl_get_part( aci, 4, '#', NULL ) < 0 ) {
    		return 0;
    	}
    
    	/* check that the aci family is supported */
    	/* FIXME: the OID is ignored? */
    	if ( acl_get_part( aci, 0, '#', &bv ) < 0 ) {
    		return 0;
    	}
    
    	/* check that the scope matches */
    	if ( acl_get_part( aci, 1, '#', &scope ) < 0 ) {
    		return 0;
    	}
    
    	/* note: scope can be either ENTRY or CHILDREN;
    	 * they respectively match "entry" and "children" in bv
    	 * both match "subtree" */
    	switch ( asserted_scope ) {
    	case SLAP_ACI_SCOPE_ENTRY:
    
    		if ( ber_bvcmp( &scope, &aci_bv[ ACI_BV_ENTRY ] ) != 0
    
    				&& ber_bvstrcasecmp( &scope, &aci_bv[ ACI_BV_SUBTREE ] ) != 0 )
    		{
    			return 0;
    		}
    		break;
    
    	case SLAP_ACI_SCOPE_CHILDREN:
    
    		if ( ber_bvcmp( &scope, &aci_bv[ ACI_BV_CHILDREN ] ) != 0
    
    				&& ber_bvstrcasecmp( &scope, &aci_bv[ ACI_BV_SUBTREE ] ) != 0 )
    		{
    			return 0;
    		}
    		break;
    
    
    	case SLAP_ACI_SCOPE_SUBTREE:
    		/* TODO: add assertion? */
    
    		return 0;
    	}
    
    	/* get the list of permissions clauses, bail if empty */
    	if ( acl_get_part( aci, 2, '#', &perms ) <= 0 ) {
    
    		assert( 0 );
    
    		return 0;
    	}
    
    	/* check if any permissions allow desired access */
    	if ( aci_list_get_rights( &perms, &desc->ad_cname, val, grant, deny ) == 0 ) {
    		return 0;
    	}
    
    	/* see if we have a DN match */
    	if ( acl_get_part( aci, 3, '#', &type ) < 0 ) {
    
    		assert( 0 );
    
    		return 0;
    	}
    
    	/* see if we have a public (i.e. anonymous) access */
    
    	if ( ber_bvcmp( &aci_bv[ ACI_BV_PUBLIC ], &type ) == 0 ) {
    
    		return 1;
    	}
    	
    	/* otherwise require an identity */
    	if ( BER_BVISNULL( &op->o_ndn ) || BER_BVISEMPTY( &op->o_ndn ) ) {
    		return 0;
    	}
    
    	/* see if we have a users access */
    
    	if ( ber_bvcmp( &aci_bv[ ACI_BV_USERS ], &type ) == 0 ) {
    
    		return 1;
    	}
    	
    	/* NOTE: this may fail if a DN contains a valid '#' (unescaped);
    	 * just grab all the berval up to its end (ITS#3303).
    	 * NOTE: the problem could be solved by providing the DN with
    	 * the embedded '#' encoded as hexpairs: "cn=Foo#Bar" would 
    	 * become "cn=Foo\23Bar" and be safely used by aci_mask(). */
    #if 0
    	if ( acl_get_part( aci, 4, '#', &sdn ) < 0 ) {
    		return 0;
    	}
    #endif
    	sdn.bv_val = type.bv_val + type.bv_len + STRLENOF( "#" );
    	sdn.bv_len = aci->bv_len - ( sdn.bv_val - aci->bv_val );
    
    
    	/* get the type options, if any */
    	if ( acl_get_part( &type, 1, '/', &opts ) > 0 ) {
    		opts.bv_len = type.bv_len - ( opts.bv_val - type.bv_val );
    		type.bv_len = opts.bv_val - type.bv_val - 1;
    
    	} else {
    		BER_BVZERO( &opts );
    	}
    
    
    	if ( ber_bvcmp( &aci_bv[ ACI_BV_ACCESS_ID ], &type ) == 0 ) {
    		return dn_match( &op->o_ndn, &sdn );
    
    	} else if ( ber_bvcmp( &aci_bv[ ACI_BV_SUBTREE ], &type ) == 0 ) {
    		return dnIsSuffix( &op->o_ndn, &sdn );
    
    	} else if ( ber_bvcmp( &aci_bv[ ACI_BV_ONELEVEL ], &type ) == 0 ) {
    		struct berval pdn;
    
    		dnParent( &sdn, &pdn );
    
    		return dn_match( &op->o_ndn, &pdn );
    
    	} else if ( ber_bvcmp( &aci_bv[ ACI_BV_CHILDREN ], &type ) == 0 ) {
    		return ( !dn_match( &op->o_ndn, &sdn ) && dnIsSuffix( &op->o_ndn, &sdn ) );
    
    	} else if ( ber_bvcmp( &aci_bv[ ACI_BV_SELF ], &type ) == 0 ) {
    		return dn_match( &op->o_ndn, &e->e_nname );
    
    	} else if ( ber_bvcmp( &aci_bv[ ACI_BV_DNATTR ], &type ) == 0 ) {
    
    		Attribute		*at;
    		AttributeDescription	*ad = NULL;
    		const char		*text;
    
    		rc = slap_bv2ad( &sdn, &ad, &text );
    
    		assert( rc == LDAP_SUCCESS );
    
    
    		rc = 0;
    		for ( at = attrs_find( e->e_attrs, ad );
    				at != NULL;
    				at = attrs_find( at->a_next, ad ) )
    		{
    
    			if ( attr_valfind( at, 
    
    				SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH |
    					SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH,
    
    				&op->o_ndn, NULL, op->o_tmpmemctx ) == 0 )
    
    	} else if ( ber_bvcmp( &aci_bv[ ACI_BV_GROUP ], &type ) == 0 ) {
    
    		struct berval	oc,
    				at;
    
    		if ( BER_BVISNULL( &opts ) ) {
    			oc = aci_bv[ ACI_BV_GROUP_CLASS ];
    			at = aci_bv[ ACI_BV_GROUP_ATTR ];
    
    		} else {
    			if ( acl_get_part( &opts, 0, '/', &oc ) < 0 ) {
    				assert( 0 );
    			}
    
    			if ( acl_get_part( &opts, 1, '/', &at ) < 0 ) {
    				at = aci_bv[ ACI_BV_GROUP_ATTR ];
    			}
    		}
    
    		if ( aci_group_member( &sdn, &oc, &at, op, e, nmatch, matches ) )
    
    	} else if ( ber_bvcmp( &aci_bv[ ACI_BV_ROLE ], &type ) == 0 ) {
    
    		struct berval	oc,
    				at;
    
    		if ( BER_BVISNULL( &opts ) ) {
    			oc = aci_bv[ ACI_BV_ROLE_CLASS ];
    			at = aci_bv[ ACI_BV_ROLE_ATTR ];
    
    		} else {
    			if ( acl_get_part( &opts, 0, '/', &oc ) < 0 ) {
    				assert( 0 );
    			}
    
    			if ( acl_get_part( &opts, 1, '/', &at ) < 0 ) {
    				at = aci_bv[ ACI_BV_ROLE_ATTR ];
    			}
    		}
    
    		if ( aci_group_member( &sdn, &oc, &at, op, e, nmatch, matches ) )
    
    	} else if ( ber_bvcmp( &aci_bv[ ACI_BV_SET ], &type ) == 0 ) {
    
    		if ( acl_match_set( &sdn, op, e, NULL ) ) {
    
    	} else if ( ber_bvcmp( &aci_bv[ ACI_BV_SET_REF ], &type ) == 0 ) {
    
    		if ( acl_match_set( &sdn, op, e, (struct berval *)&aci_bv[ ACI_BV_SET_ATTR ] ) ) {
    
    
    	} else {
    		/* it passed normalization! */
    		assert( 0 );
    
    	/* OpenLDAP eXperimental Syntax */
    
    	static slap_syntax_defs_rec aci_syntax_def = {
    		"( 1.3.6.1.4.1.4203.666.2.1 DESC 'OpenLDAP Experimental ACI' )",
    			SLAP_SYNTAX_HIDE,
    
    			OpenLDAPaciValidate,
    			OpenLDAPaciPretty
    	};
    	static slap_mrule_defs_rec aci_mr_def = {
    		"( 1.3.6.1.4.1.4203.666.4.2 NAME 'OpenLDAPaciMatch' "
    			"SYNTAX 1.3.6.1.4.1.4203.666.2.1 )",
    			SLAP_MR_HIDE | SLAP_MR_EQUALITY, NULL,
    			NULL, OpenLDAPaciNormalize, OpenLDAPaciMatch,
    			NULL, NULL,
    			NULL
    	};
    	static struct {
    		char			*name;
    		char			*desc;
    		slap_mask_t		flags;
    		AttributeDescription	**ad;
    	}		aci_at = {
    		"OpenLDAPaci", "( 1.3.6.1.4.1.4203.666.1.5 "
    			"NAME 'OpenLDAPaci' "
    			"DESC 'OpenLDAP access control information (experimental)' "
    			"EQUALITY OpenLDAPaciMatch "
    			"SYNTAX 1.3.6.1.4.1.4203.666.2.1 "
    			"USAGE directoryOperation )",
    		SLAP_AT_HIDE,
    		&slap_ad_aci
    	};
    
    	int			rc;
    
    	/* ACI syntax */
    	rc = register_syntax( &aci_syntax_def );
    	if ( rc != 0 ) {
    		return rc;
    	}
    	
    	/* ACI equality rule */
    	rc = register_matching_rule( &aci_mr_def );
    	if ( rc != 0 ) {
    		return rc;
    	}
    
    	/* ACI attribute */
    
    	rc = register_at( aci_at.desc, aci_at.ad, 0 );
    
    	if ( rc != LDAP_SUCCESS ) {
    
    			"aci_init: at_register failed\n", 0, 0, 0 );
    
    		return rc;
    	}
    
    	/* install flags */
    
    	(*aci_at.ad)->ad_type->sat_flags |= aci_at.flags;
    
    dynacl_aci_parse(
    	const char *fname,
    	int lineno,
    	const char *opts,
    	slap_style_t sty,
    	const char *right,
    	void **privp )
    
    {
    	AttributeDescription	*ad = NULL;
    	const char		*text = NULL;
    
    	if ( sty != ACL_STYLE_REGEX && sty != ACL_STYLE_BASE ) {
    		fprintf( stderr, "%s: line %d: "
    			"inappropriate style \"%s\" in \"aci\" by clause\n",
    			fname, lineno, style_strings[sty] );
    		return -1;
    	}
    
    	if ( right != NULL && *right != '\0' ) {
    		if ( slap_str2ad( right, &ad, &text ) != LDAP_SUCCESS ) {
    			fprintf( stderr,
    				"%s: line %d: aci \"%s\": %s\n",
    				fname, lineno, right, text );
    			return -1;
    		}
    
    	} else {
    
    		ad = slap_ad_aci;
    
    	}
    
    	if ( !is_at_syntax( ad->ad_type, SLAPD_ACI_SYNTAX) ) {
    		fprintf( stderr, "%s: line %d: "
    			"aci \"%s\": inappropriate syntax: %s\n",
    			fname, lineno, right,
    			ad->ad_type->sat_syntax_oid );
    		return -1;
    	}
    
    	*privp = (void *)ad;
    
    	return 0;
    }
    
    static int
    dynacl_aci_unparse( void *priv, struct berval *bv )
    {
    	AttributeDescription	*ad = ( AttributeDescription * )priv;
    	char			*ptr;
    
    	assert( ad != NULL );
    
    	bv->bv_val = ch_malloc( STRLENOF(" aci=") + ad->ad_cname.bv_len + 1 );
    	ptr = lutil_strcopy( bv->bv_val, " aci=" );
    	ptr = lutil_strcopy( ptr, ad->ad_cname.bv_val );
    	bv->bv_len = ptr - bv->bv_val;
    
    	return 0;
    }
    
    static int
    dynacl_aci_mask(
    	void			*priv,
    	Operation		*op,
    	Entry			*e,
    	AttributeDescription	*desc,
    	struct berval		*val,
    	int			nmatch,
    	regmatch_t		*matches,
    	slap_access_t		*grantp,
    	slap_access_t		*denyp )
    {
    	AttributeDescription	*ad = ( AttributeDescription * )priv;
    	Attribute		*at;
    	slap_access_t		tgrant, tdeny, grant, deny;
    #ifdef LDAP_DEBUG
    	char			accessmaskbuf[ACCESSMASK_MAXLEN];
    	char			accessmaskbuf1[ACCESSMASK_MAXLEN];
    #endif /* LDAP_DEBUG */
    
    
    	if ( BER_BVISEMPTY( &e->e_nname ) ) {
    		/* no ACIs in the root DSE */
    		return -1;
    	}
    
    
    	/* start out with nothing granted, nothing denied */
    	ACL_INIT(tgrant);
    	ACL_INIT(tdeny);
    
    	/* get the aci attribute */
    	at = attr_find( e->e_attrs, ad );
    	if ( at != NULL ) {
    		int		i;
    
    		/* the aci is an multi-valued attribute.  The
    		 * rights are determined by OR'ing the individual
    		 * rights given by the acis.
    		 */
    		for ( i = 0; !BER_BVISNULL( &at->a_nvals[i] ); i++ ) {
    			if ( aci_mask( op, e, desc, val, &at->a_nvals[i],
    					nmatch, matches, &grant, &deny,
    					SLAP_ACI_SCOPE_ENTRY ) != 0 )
    			{
    				tgrant |= grant;
    				tdeny |= deny;
    			}
    		}
    		
    
    		Debug( LDAP_DEBUG_ACL, "        <= aci_mask grant %s deny %s\n",
    
    			  accessmask2str( tgrant, accessmaskbuf, 1 ), 
    			  accessmask2str( tdeny, accessmaskbuf1, 1 ), 0 );
    	}
    
    	/* If the entry level aci didn't contain anything valid for the 
    	 * current operation, climb up the tree and evaluate the
    	 * acis with scope set to subtree
    	 */
    	if ( tgrant == ACL_PRIV_NONE && tdeny == ACL_PRIV_NONE ) {
    		struct berval	parent_ndn;
    
    		dnParent( &e->e_nname, &parent_ndn );
    		while ( !BER_BVISEMPTY( &parent_ndn ) ){
    			int		i;
    			BerVarray	bvals = NULL;
    			int		ret, stop;
    
    
    			/* to solve the chicken'n'egg problem of accessing
    			 * the OpenLDAPaci attribute, the direct access
    			 * to the entry's attribute is unchecked; however,
    			 * further accesses to OpenLDAPaci values in the 
    			 * ancestors occur through backend_attribute(), i.e.
    			 * with the identity of the operation, requiring
    			 * further access checking.  For uniformity, this
    			 * makes further requests occur as the rootdn, if
    			 * any, i.e. searching for the OpenLDAPaci attribute
    			 * is considered an internal search.  If this is not
    			 * acceptable, then the same check needs be performed
    			 * when accessing the entry's attribute. */
    			struct berval	save_o_dn, save_o_ndn;
    	
    			if ( !BER_BVISNULL( &op->o_bd->be_rootndn ) ) {
    				save_o_dn = op->o_dn;
    				save_o_ndn = op->o_ndn;
    
    				op->o_dn = op->o_bd->be_rootdn;
    				op->o_ndn = op->o_bd->be_rootndn;
    			}
    
    			Debug( LDAP_DEBUG_ACL, "        checking ACI of \"%s\"\n", parent_ndn.bv_val, 0, 0 );
    			ret = backend_attribute( op, NULL, &parent_ndn, ad, &bvals, ACL_AUTH );
    
    			if ( !BER_BVISNULL( &op->o_bd->be_rootndn ) ) {
    				op->o_dn = save_o_dn;
    				op->o_ndn = save_o_ndn;
    			}
    
    
    			switch ( ret ) {
    			case LDAP_SUCCESS :
    				stop = 0;
    				if ( !bvals ) {
    					break;
    				}
    
    				for ( i = 0; !BER_BVISNULL( &bvals[i] ); i++ ) {
    					if ( aci_mask( op, e, desc, val,
    							&bvals[i],
    							nmatch, matches,
    							&grant, &deny,
    							SLAP_ACI_SCOPE_CHILDREN ) != 0 )
    					{
    						tgrant |= grant;
    						tdeny |= deny;
    						/* evaluation stops as soon as either a "deny" or a 
    						 * "grant" directive matches.
    						 */
    						if ( tgrant != ACL_PRIV_NONE || tdeny != ACL_PRIV_NONE ) {
    							stop = 1;
    						}
    					}
    					Debug( LDAP_DEBUG_ACL, "<= aci_mask grant %s deny %s\n", 
    						accessmask2str( tgrant, accessmaskbuf, 1 ),
    						accessmask2str( tdeny, accessmaskbuf1, 1 ), 0 );
    				}
    				break;
    
    			case LDAP_NO_SUCH_ATTRIBUTE:
    				/* just go on if the aci-Attribute is not present in
    				 * the current entry 
    				 */
    				Debug( LDAP_DEBUG_ACL, "no such attribute\n", 0, 0, 0 );
    				stop = 0;
    				break;
    
    			case LDAP_NO_SUCH_OBJECT:
    				/* We have reached the base object */
    				Debug( LDAP_DEBUG_ACL, "no such object\n", 0, 0, 0 );
    				stop = 1;
    				break;
    
    			default:
    				stop = 1;
    				break;
    			}
    
    			if ( stop ) {
    				break;
    			}
    			dnParent( &parent_ndn, &parent_ndn );
    		}
    	}
    
    	*grantp = tgrant;
    	*denyp = tdeny;
    
    	return 0;
    }
    
    /* need to register this at some point */
    static slap_dynacl_t	dynacl_aci = {
    	"aci",
    	dynacl_aci_parse,
    	dynacl_aci_unparse,
    	dynacl_aci_mask,
    	NULL,
    	NULL,
    	NULL
    };
    
    int
    dynacl_aci_init( void )
    {
    
    	int	rc;
    
    	rc = aci_init();
    
    	if ( rc == 0 ) {
    		rc = slap_dynacl_register( &dynacl_aci );
    	}
    	
    	return rc;
    
    }
    
    
    /* ACI syntax validation */
    
    /*
     * Matches given berval to array of bervals
     * Returns:
     *      >=0 if one if the array elements equals to this berval
     *       -1 if string was not found in array
     */
    static int 
    bv_getcaseidx(
    	struct berval *bv, 
    	const struct berval *arr[] )
    {
    	int i;