aci.c   [plain text]


/* aci.c - routines to parse and check acl's */
/* $OpenLDAP: pkg/ldap/servers/slapd/aci.c,v 1.14.2.6 2008/02/11 23:26:43 kurt Exp $ */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
 *
 * Copyright 1998-2008 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"

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_CHILDREN,
	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("[children]"),
	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 );
			continue;
		}

		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 )
{
	struct berval	perm, actn, baseattr;
	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;
		}

		found = 1;
		*mask |= aci_list_get_attr_rights( &perm, attr, val );
		*mask |= aci_list_get_attr_rights( &perm, &aci_bv[ ACI_BV_BR_ALL ], NULL );
	}

	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;

		bv.bv_len = sizeof( buf ) - 1;
		bv.bv_val = (char *)&buf;
		if ( acl_string_expand( &bv, &subjdn,
				e->e_ndn, nmatch, matches ) )
		{
			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;
}

static int
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;
	int			rc;
		

	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 )
			{
				rc = 1;
				break;
			}
		}

		return rc;

	} 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 ) )
		{
			return 1;
		}

	} 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 ) )
		{
			return 1;
		}

	} else if ( ber_bvcmp( &aci_bv[ ACI_BV_SET ], &type ) == 0 ) {
		if ( acl_match_set( &sdn, op, e, NULL ) ) {
			return 1;
		}

	} 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 ] ) ) {
			return 1;
		}

	} else {
		/* it passed normalization! */
		assert( 0 );
	}

	return 0;
}

static int
aci_init( void )
{
	/* 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,
			NULL,
			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 ) {
		Debug( LDAP_DEBUG_ANY,
			"aci_init: at_register failed\n", 0, 0, 0 );
		return rc;
	}

	/* install flags */
	(*aci_at.ad)->ad_type->sat_flags |= aci_at.flags;

	return rc;
}

static int
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;

	if ( BER_BVISEMPTY( bv ) ) {
		return -1;
	}

	for ( i = 0; arr[ i ] != NULL ; i++ ) {
		if ( ber_bvstrcasecmp( bv, arr[ i ] ) == 0 ) {
 			return i;
		}
	}

  	return -1;
}


/* Returns what have left in input berval after current sub */
static void
bv_get_tail(
	struct berval *val,
	struct berval *sub,
	struct berval *tail )
{
	int		head_len;

	tail->bv_val = sub->bv_val + sub->bv_len;
	head_len = (unsigned long) tail->bv_val - (unsigned long) val->bv_val;
  	tail->bv_len = val->bv_len - head_len;
}


/*
 * aci is accepted in following form:
 *    oid#scope#rights#type#subject
 * Where:
 *    oid       := numeric OID (currently ignored)
 *    scope     := entry|children|subtree
 *    rights    := right[[$right]...]
 *    right     := (grant|deny);action
 *    action    := perms;attrs[[;perms;attrs]...]
 *    perms     := perm[[,perm]...]
 *    perm      := c|s|r|w|x
 *    attrs     := attribute[[,attribute]..]|"[all]"
 *    attribute := attributeType|attributeType=attributeValue|attributeType=attributeValuePrefix*
 *    type      := public|users|self|dnattr|group|role|set|set-ref|
 *                 access_id|subtree|onelevel|children
 */
static int 
OpenLDAPaciValidatePerms(
	struct berval *perms ) 
{
	int		i;

	for ( i = 0; i < perms->bv_len; ) {
		switch ( perms->bv_val[ i ] ) {
		case 'x':
		case 'd':
		case 'c':
		case 's':
		case 'r':
		case 'w':
			break;

		default:
		        Debug( LDAP_DEBUG_ACL, "aciValidatePerms: perms needs to be one of x,d,c,s,r,w in '%s'\n", perms->bv_val, 0, 0 );
			return LDAP_INVALID_SYNTAX;
		}

		if ( ++i == perms->bv_len ) {
			return LDAP_SUCCESS;
		}

		while ( i < perms->bv_len && perms->bv_val[ i ] == ' ' )
			i++;

		assert( i != perms->bv_len );

		if ( perms->bv_val[ i ] != ',' ) {
		        Debug( LDAP_DEBUG_ACL, "aciValidatePerms: missing comma in '%s'\n", perms->bv_val, 0, 0 );
			return LDAP_INVALID_SYNTAX;
		}

		do {
			i++;
		} while ( perms->bv_val[ i ] == ' ' );
	}

	return LDAP_SUCCESS;
}

static const struct berval *ACIgrantdeny[] = {
	&aci_bv[ ACI_BV_GRANT ],
	&aci_bv[ ACI_BV_DENY ],
	NULL
};

static int 
OpenLDAPaciValidateRight(
	struct berval *action )
{
	struct berval	bv = BER_BVNULL;
	int		i;

	/* grant|deny */
	if ( acl_get_part( action, 0, ';', &bv ) < 0 ||
		bv_getcaseidx( &bv, ACIgrantdeny ) == -1 )
	{
		Debug( LDAP_DEBUG_ACL, "aciValidateRight: '%s' must be either 'grant' or 'deny'\n", bv.bv_val, 0, 0 );
		return LDAP_INVALID_SYNTAX;
	}

	for ( i = 0; acl_get_part( action, i + 1, ';', &bv ) >= 0; i++ ) {
		if ( i & 1 ) {
			/* perms */
			if ( OpenLDAPaciValidatePerms( &bv ) != LDAP_SUCCESS )
			{
				return LDAP_INVALID_SYNTAX;
			}

		} else {
			/* attr */
			AttributeDescription	*ad;
			const char		*text;
			struct berval		attr, left, right;
			int			j;

			/* could be "[all]" or an attribute description */
			if ( ber_bvstrcasecmp( &bv, &aci_bv[ ACI_BV_BR_ALL ] ) == 0 ) {
				continue;
			}


			for ( j = 0; acl_get_part( &bv, j, ',', &attr ) >= 0; j++ ) 
			{
				ad = NULL;
				text = NULL;
				if ( acl_get_part( &attr, 0, '=', &left ) < 0
					|| acl_get_part( &attr, 1, '=', &right ) < 0 ) 
				{
					if ( slap_bv2ad( &attr, &ad, &text ) != LDAP_SUCCESS ) 
					{
						Debug( LDAP_DEBUG_ACL, "aciValidateRight: unknown attribute: '%s'\n", attr.bv_val, 0, 0 );
						return LDAP_INVALID_SYNTAX;
					}
				} else {
					if ( slap_bv2ad( &left, &ad, &text ) != LDAP_SUCCESS ) 
					{
						Debug( LDAP_DEBUG_ACL, "aciValidateRight: unknown attribute: '%s'\n", left.bv_val, 0, 0 );
						return LDAP_INVALID_SYNTAX;
					}
				}
			}
		}
	}
	
	/* "perms;attr" go in pairs */
	if ( i > 0 && ( i & 1 ) == 0 ) {
		return LDAP_SUCCESS;

	} else {
		Debug( LDAP_DEBUG_ACL, "aciValidateRight: perms:attr need to be pairs in '%s'\n", action->bv_val, 0, 0 );
		return LDAP_INVALID_SYNTAX;
	}

	return LDAP_SUCCESS;
}

static int
OpenLDAPaciNormalizeRight(
	struct berval	*action,
	struct berval	*naction,
	void		*ctx )
{
	struct berval	grantdeny,
			perms = BER_BVNULL,
			bv = BER_BVNULL;
	int		idx,
			i;

	/* grant|deny */
	if ( acl_get_part( action, 0, ';', &grantdeny ) < 0 ) {
	        Debug( LDAP_DEBUG_ACL, "aciNormalizeRight: missing ';' in '%s'\n", action->bv_val, 0, 0 );
		return LDAP_INVALID_SYNTAX;
	}
	idx = bv_getcaseidx( &grantdeny, ACIgrantdeny );
	if ( idx == -1 ) {
	        Debug( LDAP_DEBUG_ACL, "aciNormalizeRight: '%s' must be grant or deny\n", grantdeny.bv_val, 0, 0 );
		return LDAP_INVALID_SYNTAX;
	}

	ber_dupbv_x( naction, (struct berval *)ACIgrantdeny[ idx ], ctx );

	for ( i = 1; acl_get_part( action, i, ';', &bv ) >= 0; i++ ) {
		struct berval	nattrs = BER_BVNULL;
		int		freenattrs = 1;
		if ( i & 1 ) {
			/* perms */
			if ( OpenLDAPaciValidatePerms( &bv ) != LDAP_SUCCESS )
			{
				return LDAP_INVALID_SYNTAX;
			}
			perms = bv;

		} else {
			/* attr */
			char		*ptr;

			/* could be "[all]" or an attribute description */
			if ( ber_bvstrcasecmp( &bv, &aci_bv[ ACI_BV_BR_ALL ] ) == 0 ) {
				nattrs = aci_bv[ ACI_BV_BR_ALL ];
				freenattrs = 0;

			} else {
				AttributeDescription	*ad = NULL;
				AttributeDescription	adstatic= { 0 };
				const char		*text = NULL;
				struct berval		attr, left, right;
				int			j;
				int			len;

				for ( j = 0; acl_get_part( &bv, j, ',', &attr ) >= 0; j++ ) 
				{
					ad = NULL;
					text = NULL;
					/* openldap 2.1 aci compabitibility [entry] -> entry */
					if ( ber_bvstrcasecmp( &attr, &aci_bv[ ACI_BV_BR_ENTRY ] ) == 0 ) {
						ad = &adstatic;
						adstatic.ad_cname = aci_bv[ ACI_BV_ENTRY ];

					/* openldap 2.1 aci compabitibility [children] -> children */
					} else if ( ber_bvstrcasecmp( &attr, &aci_bv[ ACI_BV_BR_CHILDREN ] ) == 0 ) {
						ad = &adstatic;
						adstatic.ad_cname = aci_bv[ ACI_BV_CHILDREN ];

					/* openldap 2.1 aci compabitibility [all] -> only [all] */
					} else if ( ber_bvstrcasecmp( &attr, &aci_bv[ ACI_BV_BR_ALL ] ) == 0 ) {
						ber_memfree_x( nattrs.bv_val, ctx );
						nattrs = aci_bv[ ACI_BV_BR_ALL ];
						freenattrs = 0;
						break;

					} else if ( acl_get_part( &attr, 0, '=', &left ) < 0
				     		|| acl_get_part( &attr, 1, '=', &right ) < 0 ) 
					{
						if ( slap_bv2ad( &attr, &ad, &text ) != LDAP_SUCCESS ) 
						{
							ber_memfree_x( nattrs.bv_val, ctx );
							Debug( LDAP_DEBUG_ACL, "aciNormalizeRight: unknown attribute: '%s'\n", attr.bv_val, 0, 0 );
							return LDAP_INVALID_SYNTAX;
						}

					} else {
						if ( slap_bv2ad( &left, &ad, &text ) != LDAP_SUCCESS ) 
						{
							ber_memfree_x( nattrs.bv_val, ctx );
							Debug( LDAP_DEBUG_ACL, "aciNormalizeRight: unknown attribute: '%s'\n", left.bv_val, 0, 0 );
							return LDAP_INVALID_SYNTAX;
						}
					}
					
				
					len = nattrs.bv_len + ( !BER_BVISEMPTY( &nattrs ) ? STRLENOF( "," ) : 0 )
				      		+ ad->ad_cname.bv_len;
					nattrs.bv_val = ber_memrealloc_x( nattrs.bv_val, len + 1, ctx );
	                        	ptr = &nattrs.bv_val[ nattrs.bv_len ];
					if ( !BER_BVISEMPTY( &nattrs ) ) {
						*ptr++ = ',';
					}
					ptr = lutil_strncopy( ptr, ad->ad_cname.bv_val, ad->ad_cname.bv_len );
                                	ptr[ 0 ] = '\0';
                                	nattrs.bv_len = len;
				}

			}

			naction->bv_val = ber_memrealloc_x( naction->bv_val,
				naction->bv_len + STRLENOF( ";" )
				+ perms.bv_len + STRLENOF( ";" )
				+ nattrs.bv_len + 1,
				ctx );

			ptr = &naction->bv_val[ naction->bv_len ];
			ptr[ 0 ] = ';';
			ptr++;
			ptr = lutil_strncopy( ptr, perms.bv_val, perms.bv_len );
			ptr[ 0 ] = ';';
			ptr++;
			ptr = lutil_strncopy( ptr, nattrs.bv_val, nattrs.bv_len );
			ptr[ 0 ] = '\0';
			naction->bv_len += STRLENOF( ";" ) + perms.bv_len
				+ STRLENOF( ";" ) + nattrs.bv_len;
			if ( freenattrs ) {
				ber_memfree_x( nattrs.bv_val, ctx );
			}
		}
	}
	
	/* perms;attr go in pairs */
	if ( i > 1 && ( i & 1 ) ) {
		return LDAP_SUCCESS;

	} else {
		Debug( LDAP_DEBUG_ACL, "aciNormalizeRight: perms:attr need to be pairs in '%s'\n", action->bv_val, 0, 0 );
		return LDAP_INVALID_SYNTAX;
	}
}

static int 
OpenLDAPaciValidateRights(
	struct berval *actions )

{
	struct berval	bv = BER_BVNULL;
	int		i;

	for ( i = 0; acl_get_part( actions, i, '$', &bv ) >= 0; i++ ) {
		if ( OpenLDAPaciValidateRight( &bv ) != LDAP_SUCCESS ) {
			return LDAP_INVALID_SYNTAX;
		}
	}

	return LDAP_SUCCESS;
}

static int 
OpenLDAPaciNormalizeRights(
	struct berval	*actions,
	struct berval	*nactions,
	void		*ctx )

{
	struct berval	bv = BER_BVNULL;
	int		i;

	BER_BVZERO( nactions );
	for ( i = 0; acl_get_part( actions, i, '$', &bv ) >= 0; i++ ) {
		int		rc;
		struct berval	nbv;

		rc = OpenLDAPaciNormalizeRight( &bv, &nbv, ctx );
		if ( rc != LDAP_SUCCESS ) {
			ber_memfree_x( nactions->bv_val, ctx );
			BER_BVZERO( nactions );
			return LDAP_INVALID_SYNTAX;
		}

		if ( i == 0 ) {
			*nactions = nbv;

		} else {
			nactions->bv_val = ber_memrealloc_x( nactions->bv_val,
				nactions->bv_len + STRLENOF( "$" )
				+ nbv.bv_len + 1,
				ctx );
			nactions->bv_val[ nactions->bv_len ] = '$';
			AC_MEMCPY( &nactions->bv_val[ nactions->bv_len + 1 ],
				nbv.bv_val, nbv.bv_len + 1 );
			ber_memfree_x( nbv.bv_val, ctx );
			nactions->bv_len += STRLENOF( "$" ) + nbv.bv_len;
		}
		BER_BVZERO( &nbv );
	}

	return LDAP_SUCCESS;
}

static const struct berval *OpenLDAPaciscopes[] = {
	&aci_bv[ ACI_BV_ENTRY ],
	&aci_bv[ ACI_BV_CHILDREN ],
	&aci_bv[ ACI_BV_SUBTREE ],

	NULL
};

static const struct berval *OpenLDAPacitypes[] = {
	/* DN-valued */
	&aci_bv[ ACI_BV_GROUP ], 
	&aci_bv[ ACI_BV_ROLE ],

/* set to one past the last DN-valued type with options (/) */
#define	LAST_OPTIONAL	2

	&aci_bv[ ACI_BV_ACCESS_ID ],
	&aci_bv[ ACI_BV_SUBTREE ],
	&aci_bv[ ACI_BV_ONELEVEL ],
	&aci_bv[ ACI_BV_CHILDREN ],

/* set to one past the last DN-valued type */
#define LAST_DNVALUED	6

	/* non DN-valued */
	&aci_bv[ ACI_BV_DNATTR ],
	&aci_bv[ ACI_BV_PUBLIC ],
	&aci_bv[ ACI_BV_USERS ],
	&aci_bv[ ACI_BV_SELF ],
	&aci_bv[ ACI_BV_SET ],
	&aci_bv[ ACI_BV_SET_REF ],

	NULL
};

static int
OpenLDAPaciValidate(
	Syntax		*syntax,
	struct berval	*val )
{
	struct berval	oid = BER_BVNULL,
			scope = BER_BVNULL,
			rights = BER_BVNULL,
			type = BER_BVNULL,
			subject = BER_BVNULL;
	int		idx;
	int		rc;
	
	if ( BER_BVISEMPTY( val ) ) {
		Debug( LDAP_DEBUG_ACL, "aciValidatet: value is empty\n", 0, 0, 0 );
		return LDAP_INVALID_SYNTAX;
	}

	/* oid */
	if ( acl_get_part( val, 0, '#', &oid ) < 0 || 
		numericoidValidate( NULL, &oid ) != LDAP_SUCCESS )
	{
		/* NOTE: the numericoidValidate() is rather pedantic;
		 * I'd replace it with X-ORDERED VALUES so that
		 * it's guaranteed values are maintained and used
		 * in the desired order */
		Debug( LDAP_DEBUG_ACL, "aciValidate: invalid oid '%s'\n", oid.bv_val, 0, 0 );
		return LDAP_INVALID_SYNTAX;
	}

	/* scope */
	if ( acl_get_part( val, 1, '#', &scope ) < 0 || 
		bv_getcaseidx( &scope, OpenLDAPaciscopes ) == -1 )
	{
		Debug( LDAP_DEBUG_ACL, "aciValidate: invalid scope '%s'\n", scope.bv_val, 0, 0 );
		return LDAP_INVALID_SYNTAX;
	}

	/* rights */
	if ( acl_get_part( val, 2, '#', &rights ) < 0 ||
		OpenLDAPaciValidateRights( &rights ) != LDAP_SUCCESS ) 
	{
		return LDAP_INVALID_SYNTAX;
	}

	/* type */
	if ( acl_get_part( val, 3, '#', &type ) < 0 ) {
		Debug( LDAP_DEBUG_ACL, "aciValidate: missing type in '%s'\n", val->bv_val, 0, 0 );
		return LDAP_INVALID_SYNTAX;
	}
	idx = bv_getcaseidx( &type, OpenLDAPacitypes );
	if ( idx == -1 ) {
		struct berval	isgr;

		if ( acl_get_part( &type, 0, '/', &isgr ) < 0 ) {
			Debug( LDAP_DEBUG_ACL, "aciValidate: invalid type '%s'\n", type.bv_val, 0, 0 );
			return LDAP_INVALID_SYNTAX;
		}

		idx = bv_getcaseidx( &isgr, OpenLDAPacitypes );
		if ( idx == -1 || idx >= LAST_OPTIONAL ) {
			Debug( LDAP_DEBUG_ACL, "aciValidate: invalid type '%s'\n", isgr.bv_val, 0, 0 );
			return LDAP_INVALID_SYNTAX;
		}
	}

	/* subject */
	bv_get_tail( val, &type, &subject );
	if ( subject.bv_val[ 0 ] != '#' ) {
		Debug( LDAP_DEBUG_ACL, "aciValidate: missing subject in '%s'\n", val->bv_val, 0, 0 );
		return LDAP_INVALID_SYNTAX;
	}

	if ( idx >= LAST_DNVALUED ) {
		if ( OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_DNATTR ] ) {
			AttributeDescription	*ad = NULL;
			const char		*text = NULL;

			rc = slap_bv2ad( &subject, &ad, &text );
			if ( rc != LDAP_SUCCESS ) {
				Debug( LDAP_DEBUG_ACL, "aciValidate: unknown dn attribute '%s'\n", subject.bv_val, 0, 0 );
				return LDAP_INVALID_SYNTAX;
			}

			if ( ad->ad_type->sat_syntax != slap_schema.si_syn_distinguishedName ) {
				/* FIXME: allow nameAndOptionalUID? */
				Debug( LDAP_DEBUG_ACL, "aciValidate: wrong syntax for dn attribute '%s'\n", subject.bv_val, 0, 0 );
				return LDAP_INVALID_SYNTAX;
			}
		}

		/* not a DN */
		return LDAP_SUCCESS;

	} else if ( OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_GROUP ]
			|| OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_ROLE ] )
	{
		/* do {group|role}/oc/at check */
		struct berval	ocbv = BER_BVNULL,
				atbv = BER_BVNULL;

		ocbv.bv_val = ber_bvchr( &type, '/' );
		if ( ocbv.bv_val != NULL ) {
			ocbv.bv_val++;
			ocbv.bv_len = type.bv_len
					- ( ocbv.bv_val - type.bv_val );

			atbv.bv_val = ber_bvchr( &ocbv, '/' );
			if ( atbv.bv_val != NULL ) {
				AttributeDescription	*ad = NULL;
				const char		*text = NULL;
				int			rc;

				atbv.bv_val++;
				atbv.bv_len = type.bv_len
					- ( atbv.bv_val - type.bv_val );
				ocbv.bv_len = atbv.bv_val - ocbv.bv_val - 1;

				rc = slap_bv2ad( &atbv, &ad, &text );
				if ( rc != LDAP_SUCCESS ) {
				        Debug( LDAP_DEBUG_ACL, "aciValidate: unknown group attribute '%s'\n", atbv.bv_val, 0, 0 );
					return LDAP_INVALID_SYNTAX;
				}
			}

			if ( oc_bvfind( &ocbv ) == NULL ) {
			        Debug( LDAP_DEBUG_ACL, "aciValidate: unknown group '%s'\n", ocbv.bv_val, 0, 0 );
				return LDAP_INVALID_SYNTAX;
			}
		}
	}

	if ( BER_BVISEMPTY( &subject ) ) {
		/* empty DN invalid */
	        Debug( LDAP_DEBUG_ACL, "aciValidate: missing dn in '%s'\n", val->bv_val, 0, 0 );
		return LDAP_INVALID_SYNTAX;
	}

	subject.bv_val++;
	subject.bv_len--;

	/* FIXME: pass DN syntax? */
	rc = dnValidate( NULL, &subject );
	if ( rc != LDAP_SUCCESS ) {
	        Debug( LDAP_DEBUG_ACL, "aciValidate: invalid dn '%s'\n", subject.bv_val, 0, 0 );
	}
	return rc;
}

static int
OpenLDAPaciPrettyNormal(
	struct berval	*val,
	struct berval	*out,
	void		*ctx,
	int		normalize )
{
	struct berval	oid = BER_BVNULL,
			scope = BER_BVNULL,
			rights = BER_BVNULL,
			nrights = BER_BVNULL,
			type = BER_BVNULL,
			ntype = BER_BVNULL,
			subject = BER_BVNULL,
			nsubject = BER_BVNULL;
	int		idx,
			rc = LDAP_SUCCESS,
			freesubject = 0,
			freetype = 0;
	char		*ptr;

	BER_BVZERO( out );

	if ( BER_BVISEMPTY( val ) ) {
		Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: value is empty\n", 0, 0, 0 );
		return LDAP_INVALID_SYNTAX;
	}

	/* oid: if valid, it's already normalized */
	if ( acl_get_part( val, 0, '#', &oid ) < 0 || 
		numericoidValidate( NULL, &oid ) != LDAP_SUCCESS )
	{
		Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid oid '%s'\n", oid.bv_val, 0, 0 );
		return LDAP_INVALID_SYNTAX;
	}

	/* scope: normalize by replacing with OpenLDAPaciscopes */
	if ( acl_get_part( val, 1, '#', &scope ) < 0 ) {
		Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: missing scope in '%s'\n", val->bv_val, 0, 0 );
		return LDAP_INVALID_SYNTAX;
	}
	idx = bv_getcaseidx( &scope, OpenLDAPaciscopes );
	if ( idx == -1 ) {
		Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid scope '%s'\n", scope.bv_val, 0, 0 );
		return LDAP_INVALID_SYNTAX;
	}
	scope = *OpenLDAPaciscopes[ idx ];

	/* rights */
	if ( acl_get_part( val, 2, '#', &rights ) < 0 ) {
		Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: missing rights in '%s'\n", val->bv_val, 0, 0 );
		return LDAP_INVALID_SYNTAX;
	}
	if ( OpenLDAPaciNormalizeRights( &rights, &nrights, ctx )
		!= LDAP_SUCCESS ) 
	{
		return LDAP_INVALID_SYNTAX;
	}

	/* type */
	if ( acl_get_part( val, 3, '#', &type ) < 0 ) {
		Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: missing type in '%s'\n", val->bv_val, 0, 0 );
		rc = LDAP_INVALID_SYNTAX;
		goto cleanup;
	}
	idx = bv_getcaseidx( &type, OpenLDAPacitypes );
	if ( idx == -1 ) {
		struct berval	isgr;

		if ( acl_get_part( &type, 0, '/', &isgr ) < 0 ) {
		        Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid type '%s'\n", type.bv_val, 0, 0 );
			rc = LDAP_INVALID_SYNTAX;
			goto cleanup;
		}

		idx = bv_getcaseidx( &isgr, OpenLDAPacitypes );
		if ( idx == -1 || idx >= LAST_OPTIONAL ) {
		        Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid type '%s'\n", isgr.bv_val, 0, 0 );
			rc = LDAP_INVALID_SYNTAX;
			goto cleanup;
		}
	}
	ntype = *OpenLDAPacitypes[ idx ];

	/* subject */
	bv_get_tail( val, &type, &subject );

	if ( BER_BVISEMPTY( &subject ) || subject.bv_val[ 0 ] != '#' ) {
	        Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: missing subject in '%s'\n", val->bv_val, 0, 0 );
		rc = LDAP_INVALID_SYNTAX;
		goto cleanup;
	}

	subject.bv_val++;
	subject.bv_len--;

	if ( idx < LAST_DNVALUED ) {
		/* FIXME: pass DN syntax? */
		if ( normalize ) {
			rc = dnNormalize( 0, NULL, NULL,
				&subject, &nsubject, ctx );
		} else {
			rc = dnPretty( NULL, &subject, &nsubject, ctx );
		}

		if ( rc == LDAP_SUCCESS ) {
			freesubject = 1;

		} else {
	                Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid subject dn '%s'\n", subject.bv_val, 0, 0 );
			goto cleanup;
		}

		if ( OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_GROUP ]
			|| OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_ROLE ] )
		{
			/* do {group|role}/oc/at check */
			struct berval	ocbv = BER_BVNULL,
					atbv = BER_BVNULL;

			ocbv.bv_val = ber_bvchr( &type, '/' );
			if ( ocbv.bv_val != NULL ) {
				ObjectClass		*oc = NULL;
				AttributeDescription	*ad = NULL;
				const char		*text = NULL;
				int			rc;
				struct berval		bv;

				bv.bv_len = ntype.bv_len;

				ocbv.bv_val++;
				ocbv.bv_len = type.bv_len - ( ocbv.bv_val - type.bv_val );

				atbv.bv_val = ber_bvchr( &ocbv, '/' );
				if ( atbv.bv_val != NULL ) {
					atbv.bv_val++;
					atbv.bv_len = type.bv_len
						- ( atbv.bv_val - type.bv_val );
					ocbv.bv_len = atbv.bv_val - ocbv.bv_val - 1;
	
					rc = slap_bv2ad( &atbv, &ad, &text );
					if ( rc != LDAP_SUCCESS ) {
	                                        Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: unknown group attribute '%s'\n", atbv.bv_val, 0, 0 );
						rc = LDAP_INVALID_SYNTAX;
						goto cleanup;
					}

					bv.bv_len += STRLENOF( "/" ) + ad->ad_cname.bv_len;
				}

				oc = oc_bvfind( &ocbv );
				if ( oc == NULL ) {
                                        Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid group '%s'\n", ocbv.bv_val, 0, 0 );
					rc = LDAP_INVALID_SYNTAX;
					goto cleanup;
				}

				bv.bv_len += STRLENOF( "/" ) + oc->soc_cname.bv_len;
				bv.bv_val = ber_memalloc_x( bv.bv_len + 1, ctx );

				ptr = bv.bv_val;
				ptr = lutil_strncopy( ptr, ntype.bv_val, ntype.bv_len );
				ptr[ 0 ] = '/';
				ptr++;
				ptr = lutil_strncopy( ptr,
					oc->soc_cname.bv_val,
					oc->soc_cname.bv_len );
				if ( ad != NULL ) {
					ptr[ 0 ] = '/';
					ptr++;
					ptr = lutil_strncopy( ptr,
						ad->ad_cname.bv_val,
						ad->ad_cname.bv_len );
				}
				ptr[ 0 ] = '\0';

				ntype = bv;
				freetype = 1;
			}
		}

	} else if ( OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_DNATTR ] ) {
		AttributeDescription	*ad = NULL;
		const char		*text = NULL;
		int			rc;

		rc = slap_bv2ad( &subject, &ad, &text );
		if ( rc != LDAP_SUCCESS ) {
                        Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: unknown dn attribute '%s'\n", subject.bv_val, 0, 0 );
			rc = LDAP_INVALID_SYNTAX;
			goto cleanup;
		}

		if ( ad->ad_type->sat_syntax != slap_schema.si_syn_distinguishedName ) {
			/* FIXME: allow nameAndOptionalUID? */
                        Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: wrong syntax for dn attribute '%s'\n", subject.bv_val, 0, 0 );
			rc = LDAP_INVALID_SYNTAX;
			goto cleanup;
		}

		nsubject = ad->ad_cname;
	}


	out->bv_len = 
		oid.bv_len + STRLENOF( "#" )
		+ scope.bv_len + STRLENOF( "#" )
		+ nrights.bv_len + STRLENOF( "#" )
		+ ntype.bv_len + STRLENOF( "#" )
		+ nsubject.bv_len;

	out->bv_val = ber_memalloc_x( out->bv_len + 1, ctx );
	ptr = lutil_strncopy( out->bv_val, oid.bv_val, oid.bv_len );
	ptr[ 0 ] = '#';
	ptr++;
	ptr = lutil_strncopy( ptr, scope.bv_val, scope.bv_len );
	ptr[ 0 ] = '#';
	ptr++;
	ptr = lutil_strncopy( ptr, nrights.bv_val, nrights.bv_len );
	ptr[ 0 ] = '#';
	ptr++;
	ptr = lutil_strncopy( ptr, ntype.bv_val, ntype.bv_len );
	ptr[ 0 ] = '#';
	ptr++;
	if ( !BER_BVISNULL( &nsubject ) ) {
		ptr = lutil_strncopy( ptr, nsubject.bv_val, nsubject.bv_len );
	}
	ptr[ 0 ] = '\0';

cleanup:;
	if ( freesubject ) {
		ber_memfree_x( nsubject.bv_val, ctx );
	}

	if ( freetype ) {
		ber_memfree_x( ntype.bv_val, ctx );
	}

	if ( !BER_BVISNULL( &nrights ) ) {
		ber_memfree_x( nrights.bv_val, ctx );
	}

	return rc;
}

static int
OpenLDAPaciPretty(
	Syntax		*syntax,
	struct berval	*val,
	struct berval	*out,
	void		*ctx )
{
	return OpenLDAPaciPrettyNormal( val, out, ctx, 0 );
}

static int
OpenLDAPaciNormalize(
	slap_mask_t	use,
	Syntax		*syntax,
	MatchingRule	*mr,
	struct berval	*val,
	struct berval	*out,
	void		*ctx )
{
	return OpenLDAPaciPrettyNormal( val, out, ctx, 1 );
}

#if SLAPD_ACI_ENABLED == SLAPD_MOD_DYNAMIC
/*
 * FIXME: need config and Makefile.am code to ease building
 * as dynamic module
 */
int
init_module( int argc, char *argv[] )
{
	return dynacl_aci_init();
}
#endif /* SLAPD_ACI_ENABLED == SLAPD_MOD_DYNAMIC */

#endif /* SLAPD_ACI_ENABLED */