From ec49990d51658542eff1461366806cbfb58eb63f Mon Sep 17 00:00:00 2001 From: Pierangelo Masarati Date: Wed, 17 Aug 2005 17:14:57 +0000 Subject: [PATCH] ACIs almost entirely factored out of slapd Added OpenLDAPaciSyntax based on ITS#3877 by Nikita Shulga aci_mask() doesn't exploit the normalized value yet (next step) The case #define SLAPD_ACI_ENABLED / #undef SLAP_DYNACL should be removed --- servers/slapd/Makefile.in | 2 + servers/slapd/aci.c | 1502 +++++++++++++++++++++++++++++++++++ servers/slapd/acl.c | 886 ++------------------- servers/slapd/proto-slap.h | 44 +- servers/slapd/schema_init.c | 10 +- servers/slapd/slap.h | 49 +- 6 files changed, 1685 insertions(+), 808 deletions(-) create mode 100644 servers/slapd/aci.c diff --git a/servers/slapd/Makefile.in b/servers/slapd/Makefile.in index 2ae075d17b..f774b1322c 100644 --- a/servers/slapd/Makefile.in +++ b/servers/slapd/Makefile.in @@ -38,6 +38,7 @@ SRCS = main.c globals.c bconfig.c config.c daemon.c \ backover.c ctxcsn.c ldapsync.c frontend.c \ slapadd.c slapcat.c slapcommon.c slapdn.c slapindex.c \ slappasswd.c slaptest.c slapauth.c slapacl.c component.c \ + aci.c \ $(@PLAT@_SRCS) OBJS = main.o globals.o bconfig.o config.o daemon.o \ @@ -55,6 +56,7 @@ OBJS = main.o globals.o bconfig.o config.o daemon.o \ backover.o ctxcsn.o ldapsync.o frontend.o \ slapadd.o slapcat.o slapcommon.o slapdn.o slapindex.o \ slappasswd.o slaptest.o slapauth.o slapacl.o component.o \ + aci.o \ $(@PLAT@_OBJS) LDAP_INCDIR= ../../include -I$(srcdir) -I$(srcdir)/slapi -I. diff --git a/servers/slapd/aci.c b/servers/slapd/aci.c new file mode 100644 index 0000000000..3d587cd9e6 --- /dev/null +++ b/servers/slapd/aci.c @@ -0,0 +1,1502 @@ +/* aci.c - routines to parse and check acl's */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 1998-2005 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 + * . + */ +/* 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 + +#include +#include +#include +#include +#include + +#include "slap.h" +#include "lber_pvt.h" +#include "lutil.h" + +#define ACI_BUF_SIZE 1024 /* use most appropriate size */ + +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 '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; + 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; + 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 part of an aci's + * rights list is of the form =, + * that means the aci applies only to attrs with + * the given value. Furthermore, if the attr is + * of the form =*, then 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 ) { + continue; + } + + if ( acl_get_part( list, i, ';', &bv ) < 0 ) { + continue; + } + + mask |= aci_list_map_rights( &bv ); + } + + return mask; +} + +static int +aci_list_get_rights( + struct berval *list, + const struct berval *attr, + struct berval *val, + slap_access_t *grant, + slap_access_t *deny ) +{ + struct berval perm, actn; + slap_access_t *mask; + int i, found; + + if ( attr == NULL || BER_BVISEMPTY( attr ) + || ber_bvstrcasecmp( attr, &aci_bv[ ACI_BV_ENTRY ] ) == 0 ) + { + attr = &aci_bv[ ACI_BV_BR_ENTRY ]; + } + + 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/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; +} + +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, 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. + + = {entry|children|subtree} + = {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 aplies 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: + /* TODO: use ber_bvcmp */ + if ( ber_bvstrcasecmp( &scope, &aci_bv[ ACI_BV_ENTRY ] ) != 0 + && ber_bvstrcasecmp( &scope, &aci_bv[ ACI_BV_SUBTREE ] ) != 0 ) + { + return 0; + } + break; + + case SLAP_ACI_SCOPE_CHILDREN: + /* TODO: use ber_bvcmp */ + if ( ber_bvstrcasecmp( &scope, &aci_bv[ ACI_BV_CHILDREN ] ) != 0 + && ber_bvstrcasecmp( &scope, &aci_bv[ ACI_BV_SUBTREE ] ) != 0 ) + { + return 0; + } + break; + + default: + /* TODO: add assertion */ + return 0; + } + + /* get the list of permissions clauses, bail if empty */ + if ( acl_get_part( aci, 2, '#', &perms ) <= 0 ) { + /* TODO: add assertion */ + 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 ) { + /* TODO: add assertion */ + return 0; + } + + /* see if we have a public (i.e. anonymous) access */ + /* TODO: use ber_bvcmp */ + if ( ber_bvstrcasecmp( &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 */ + /* TODO: use ber_bvcmp */ + if ( ber_bvstrcasecmp( &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 ); + + /* TODO: use ber_bvcmp */ + if ( ber_bvstrcasecmp( &aci_bv[ ACI_BV_ACCESS_ID ], &type ) == 0 ) { + struct berval ndn; + + /* TODO: don't normalize */ + rc = dnNormalize( 0, NULL, NULL, &sdn, &ndn, op->o_tmpmemctx ); + if ( rc != LDAP_SUCCESS ) { + return 0; + } + + if ( dn_match( &op->o_ndn, &ndn ) ) { + rc = 1; + } + slap_sl_free( ndn.bv_val, op->o_tmpmemctx ); + + return rc; + + /* TODO: use ber_bvcmp */ + } else if ( ber_bvstrcasecmp( &aci_bv[ ACI_BV_SUBTREE ], &type ) == 0 ) { + struct berval ndn; + + /* TODO: don't normalize */ + rc = dnNormalize( 0, NULL, NULL, &sdn, &ndn, op->o_tmpmemctx ); + if ( rc != LDAP_SUCCESS ) { + return 0; + } + + if ( dnIsSuffix( &op->o_ndn, &ndn ) ) { + rc = 1; + } + slap_sl_free( ndn.bv_val, op->o_tmpmemctx ); + + return rc; + + /* TODO: use ber_bvcmp */ + } else if ( ber_bvstrcasecmp( &aci_bv[ ACI_BV_ONELEVEL ], &type ) == 0 ) { + struct berval ndn, pndn; + + /* TODO: don't normalize */ + rc = dnNormalize( 0, NULL, NULL, &sdn, &ndn, op->o_tmpmemctx ); + if ( rc != LDAP_SUCCESS ) { + return 0; + } + + dnParent( &ndn, &pndn ); + + if ( dn_match( &op->o_ndn, &pndn ) ) { + rc = 1; + } + slap_sl_free( ndn.bv_val, op->o_tmpmemctx ); + + return rc; + + /* TODO: use ber_bvcmp */ + } else if ( ber_bvstrcasecmp( &aci_bv[ ACI_BV_CHILDREN ], &type ) == 0 ) { + struct berval ndn; + + /* TODO: don't normalize */ + rc = dnNormalize( 0, NULL, NULL, &sdn, &ndn, op->o_tmpmemctx ); + if ( rc != LDAP_SUCCESS ) { + return 0; + } + + if ( !dn_match( &op->o_ndn, &ndn ) + && dnIsSuffix( &op->o_ndn, &ndn ) ) + { + rc = 1; + } + slap_sl_free( ndn.bv_val, op->o_tmpmemctx ); + + return rc; + + /* TODO: use ber_bvcmp */ + } else if ( ber_bvstrcasecmp( &aci_bv[ ACI_BV_SELF ], &type ) == 0 ) { + if ( dn_match( &op->o_ndn, &e->e_nname ) ) { + return 1; + } + + /* TODO: use ber_bvcmp */ + } else if ( ber_bvstrcasecmp( &aci_bv[ ACI_BV_DNATTR ], &type ) == 0 ) { + Attribute *at; + AttributeDescription *ad = NULL; + const char *text; + + rc = slap_bv2ad( &sdn, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + /* TODO: add assertion */ + return 0; + } + + rc = 0; + for ( at = attrs_find( e->e_attrs, ad ); + at != NULL; + at = attrs_find( at->a_next, ad ) ) + { + if ( value_find_ex( ad, + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH | + SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH, + at->a_nvals, + &op->o_ndn, op->o_tmpmemctx ) == 0 ) + { + rc = 1; + break; + } + } + + return rc; + + /* TODO: use ber_bvcmp */ + } else if ( ber_bvstrcasecmp( &aci_bv[ ACI_BV_GROUP ], &type ) == 0 ) { + if ( aci_group_member( &sdn, &aci_bv[ ACI_BV_GROUP_CLASS ], + &aci_bv[ ACI_BV_GROUP_ATTR ], op, e, nmatch, matches ) ) + { + return 1; + } + + /* TODO: use ber_bvcmp */ + } else if ( ber_bvstrcasecmp( &aci_bv[ ACI_BV_ROLE ], &type ) == 0 ) { + if ( aci_group_member( &sdn, &aci_bv[ ACI_BV_ROLE_CLASS ], + &aci_bv[ ACI_BV_ROLE_ATTR ], op, e, nmatch, matches ) ) + { + return 1; + } + + /* TODO: use ber_bvcmp */ + } else if ( ber_bvstrcasecmp( &aci_bv[ ACI_BV_SET ], &type ) == 0 ) { + if ( acl_match_set( &sdn, op, e, 0 ) ) { + return 1; + } + + /* TODO: use ber_bvcmp */ + } else if ( ber_bvstrcasecmp( &aci_bv[ ACI_BV_SET_REF ], &type ) == 0 ) { + if ( acl_match_set( &sdn, op, e, 1 ) ) { + return 1; + } + } + + return 0; +} + +#ifdef SLAP_DYNACL +/* + * FIXME: there is a silly dependence that makes it difficult + * to move ACIs in a run-time loadable module under the "dynacl" + * umbrella, because sets share some helpers with ACIs. + */ +static int +dynacl_aci_parse( const char *fname, int lineno, 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_schema.si_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 */ + + /* 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; + +#if 1 + /* 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. */ + Operation op2 = *op; + + if ( !BER_BVISNULL( &op->o_bd->be_rootndn ) ) { + op2.o_dn = op->o_bd->be_rootdn; + op2.o_ndn = op->o_bd->be_rootndn; + } +#endif + + dnParent( &e->e_nname, &parent_ndn ); + while ( !BER_BVISEMPTY( &parent_ndn ) ){ + int i; + BerVarray bvals = NULL; + int ret, stop; + + Debug( LDAP_DEBUG_ACL, "checking ACI of \"%s\"\n", parent_ndn.bv_val, 0, 0 ); + ret = backend_attribute( &op2, NULL, &parent_ndn, ad, &bvals, ACL_AUTH ); + + 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 ) +{ + return slap_dynacl_register( &dynacl_aci ); +} + +#endif /* SLAP_DYNACL */ + +/* 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 + * scope := entry|children + * rights := right[[$right]...] + * right := (grant|deny);action + * action := perms;attr[[;perms;attr]...] + * perms := perm[[,perm]...] + * perm := c|s|r|w|x + * attr := attributeType|[all] + * 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 'c': + case 's': + case 'r': + case 'w': + case 'x': + break; + + default: + 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 ] != ',' ) { + 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 ) + { + 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 = NULL; + const char *text = NULL; + + /* could be "[all]" or an attribute description */ + if ( ber_bvstrcasecmp( &bv, &aci_bv[ ACI_BV_BR_ALL ] ) == 0 ) { + continue; + } + + if ( slap_bv2ad( &bv, &ad, &text ) != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + } + } + + /* "perms;attr" go in pairs */ + if ( i > 0 && ( i & 1 ) == 0 ) { + return LDAP_SUCCESS; + + } else { + 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 ) { + return LDAP_INVALID_SYNTAX; + } + idx = bv_getcaseidx( &grantdeny, ACIgrantdeny ); + if ( idx == -1 ) { + return LDAP_INVALID_SYNTAX; + } + + ber_dupbv_x( naction, (struct berval *)ACIgrantdeny[ idx ], ctx ); + + for ( i = 1; acl_get_part( action, i, ';', &bv ) >= 0; i++ ) { + 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 ) { + bv = aci_bv[ ACI_BV_BR_ALL ]; + + } else { + AttributeDescription *ad = NULL; + const char *text = NULL; + int rc; + + rc = slap_bv2ad( &bv, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + return LDAP_INVALID_SYNTAX; + } + + bv = ad->ad_cname; + } + + naction->bv_val = ber_memrealloc_x( naction->bv_val, + naction->bv_len + STRLENOF( ";" ) + + perms.bv_len + STRLENOF( ";" ) + + bv.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, bv.bv_val, bv.bv_len ); + ptr[ 0 ] = '\0'; + naction->bv_len += STRLENOF( ";" ) + perms.bv_len + + STRLENOF( ";" ) + bv.bv_len; + } + } + + /* perms;attr go in pairs */ + if ( i > 1 && ( i & 1 ) ) { + return LDAP_SUCCESS; + + } else { + 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 ], + + 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 +}; + +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; + + if ( BER_BVISEMPTY( val ) ) { + 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 */ + return LDAP_INVALID_SYNTAX; + } + + /* scope */ + if ( acl_get_part( val, 1, '#', &scope ) < 0 || + bv_getcaseidx( &scope, OpenLDAPaciscopes ) == -1 ) + { + 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 ) { + return LDAP_INVALID_SYNTAX; + } + idx = bv_getcaseidx( &type, OpenLDAPacitypes ); + if ( idx == -1 ) { + struct berval isgr; + + if ( acl_get_part( &type, 0, '/', &isgr ) < 0 ) { + return LDAP_INVALID_SYNTAX; + } + + idx = bv_getcaseidx( &isgr, OpenLDAPacitypes ); + if ( idx == -1 || idx >= LAST_OPTIONAL ) { + return LDAP_INVALID_SYNTAX; + } + } + + /* subject */ + bv_get_tail( val, &type, &subject ); + if ( subject.bv_val[ 0 ] != '#' ) { + return LDAP_INVALID_SYNTAX; + } + + if ( idx >= LAST_DNVALUED ) { + 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 ) { + return LDAP_INVALID_SYNTAX; + } + + if ( ad->ad_type->sat_syntax != slap_schema.si_syn_distinguishedName ) { + /* FIXME: allow nameAndOptionalUID? */ + 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 = strchr( type.bv_val, '/' ); + if ( ocbv.bv_val != NULL ) { + ocbv.bv_val++; + + atbv.bv_val = strchr( ocbv.bv_val, '/' ); + 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 ) { + return LDAP_INVALID_SYNTAX; + } + + } else { + ocbv.bv_len = type.bv_len + - ( ocbv.bv_val - type.bv_val ); + } + + if ( oc_bvfind( &ocbv ) == NULL ) { + return LDAP_INVALID_SYNTAX; + } + } + } + + if ( BER_BVISEMPTY( &subject ) ) { + /* empty DN invalid */ + return LDAP_INVALID_SYNTAX; + } + + subject.bv_val++; + subject.bv_len--; + + /* FIXME: pass DN syntax? */ + return dnValidate( NULL, &subject ); +} + +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, + freesubject = 0, + freetype = 0; + char *ptr; + + if ( BER_BVISEMPTY( val ) ) { + return LDAP_INVALID_SYNTAX; + } + + /* oid: if valid, it's already normalized */ + if ( acl_get_part( val, 0, '#', &oid ) < 0 || + numericoidValidate( NULL, &oid ) != LDAP_SUCCESS ) + { + return LDAP_INVALID_SYNTAX; + } + + /* scope: normalize by replacing with OpenLDAPaciscopes */ + if ( acl_get_part( val, 1, '#', &scope ) < 0 ) { + return LDAP_INVALID_SYNTAX; + } + idx = bv_getcaseidx( &scope, OpenLDAPaciscopes ); + if ( idx == -1 ) { + return LDAP_INVALID_SYNTAX; + } + scope = *OpenLDAPaciscopes[ idx ]; + + /* rights */ + if ( acl_get_part( val, 2, '#', &rights ) < 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 ) { + 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 ) { + rc = LDAP_INVALID_SYNTAX; + goto cleanup; + } + + idx = bv_getcaseidx( &isgr, OpenLDAPacitypes ); + if ( idx == -1 || idx >= LAST_OPTIONAL ) { + rc = LDAP_INVALID_SYNTAX; + goto cleanup; + } + } + ntype = *OpenLDAPacitypes[ idx ]; + + /* subject */ + bv_get_tail( val, &type, &subject ); + + if ( BER_BVISEMPTY( &subject ) || subject.bv_val[ 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 { + 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 = strchr( type.bv_val, '/' ); + 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++; + + atbv.bv_val = strchr( ocbv.bv_val, '/' ); + 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 ) { + rc = LDAP_INVALID_SYNTAX; + goto cleanup; + } + + bv.bv_len += STRLENOF( "/" ) + ad->ad_cname.bv_len; + + } else { + ocbv.bv_len = type.bv_len + - ( ocbv.bv_val - type.bv_val ); + } + + if ( oc_bvfind( &ocbv ) == NULL ) { + 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 ) { + rc = LDAP_INVALID_SYNTAX; + goto cleanup; + } + + if ( ad->ad_type->sat_syntax != slap_schema.si_syn_distinguishedName ) { + /* FIXME: allow nameAndOptionalUID? */ + rc = LDAP_INVALID_SYNTAX; + goto cleanup; + } + + nsubject = ad->ad_cname; + } + + + out->bv_len = + oid.bv_len + STRLENOF( "#" ) + + scope.bv_len + STRLENOF( "#" ) + + rights.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; +} + +int +OpenLDAPaciPretty( + Syntax *syntax, + struct berval *val, + struct berval *out, + void *ctx ) +{ + return OpenLDAPaciPrettyNormal( val, out, ctx, 0 ); +} + +int +OpenLDAPaciNormalize( + slap_mask_t use, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *out, + void *ctx ) +{ + return OpenLDAPaciPrettyNormal( val, out, ctx, 1 ); +} + +#endif /* SLAPD_ACI_ENABLED */ + diff --git a/servers/slapd/acl.c b/servers/slapd/acl.c index 265b4cb4a6..2f27cf5b5f 100644 --- a/servers/slapd/acl.c +++ b/servers/slapd/acl.c @@ -42,47 +42,43 @@ /* * speed up compares */ -static struct berval - aci_bv_entry = BER_BVC("entry"), - aci_bv_children = BER_BVC("children"), - aci_bv_onelevel = BER_BVC("onelevel"), - aci_bv_subtree = BER_BVC("subtree"), - aci_bv_br_entry = BER_BVC("[entry]"), - aci_bv_br_all = BER_BVC("[all]"), - aci_bv_access_id = BER_BVC("access-id"), +const struct berval aci_bv[] = { + BER_BVC("entry"), + BER_BVC("children"), + BER_BVC("onelevel"), + BER_BVC("subtree"), + BER_BVC("[entry]"), + BER_BVC("[all]"), + BER_BVC("access-id"), #if 0 - aci_bv_anonymous = BER_BVC("anonymous"), + BER_BVC("anonymous"), #endif - aci_bv_public = BER_BVC("public"), - aci_bv_users = BER_BVC("users"), - aci_bv_self = BER_BVC("self"), - aci_bv_dnattr = BER_BVC("dnattr"), - aci_bv_group = BER_BVC("group"), - aci_bv_role = BER_BVC("role"), - aci_bv_set = BER_BVC("set"), - aci_bv_set_ref = BER_BVC("set-ref"), - aci_bv_grant = BER_BVC("grant"), - aci_bv_deny = BER_BVC("deny"), - - aci_bv_ip_eq = BER_BVC("IP="), + 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"), + BER_BVC("grant"), + BER_BVC("deny"), + + BER_BVC("IP="), #ifdef LDAP_PF_LOCAL - aci_bv_path_eq = BER_BVC("PATH="), + BER_BVC("PATH="), #if 0 - aci_bv_dirsep = BER_BVC(LDAP_DIRSEP), + BER_BVC(LDAP_DIRSEP), #endif #endif /* LDAP_PF_LOCAL */ - aci_bv_group_class = BER_BVC(SLAPD_GROUP_CLASS), - aci_bv_group_attr = BER_BVC(SLAPD_GROUP_ATTR), - aci_bv_role_class = BER_BVC(SLAPD_ROLE_CLASS), - aci_bv_role_attr = BER_BVC(SLAPD_ROLE_ATTR), - aci_bv_set_attr = BER_BVC(SLAPD_ACI_SET_ATTR); - -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; + 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) +}; static AccessControl * slap_acl_get( AccessControl *ac, int *count, @@ -102,35 +98,17 @@ static slap_control_t slap_acl_mask( int count, AccessControlState *state ); -#ifdef SLAPD_ACI_ENABLED -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 scope); -#endif /* SLAPD_ACI_ENABLED */ - static int regex_matches( struct berval *pat, char *str, char *buf, int nmatch, regmatch_t *matches); -static int string_expand( - struct berval *newbuf, struct berval *pattern, - char *match, int nmatch, regmatch_t *matches); -typedef struct AciSetCookie { +typedef struct AclSetCookie { Operation *op; Entry *e; -} AciSetCookie; +} AclSetCookie; -SLAP_SET_GATHER aci_set_gather; -SLAP_SET_GATHER aci_set_gather2; -static int aci_match_set ( struct berval *subj, Operation *op, - Entry *e, int setref ); +SLAP_SET_GATHER acl_set_gather; +SLAP_SET_GATHER acl_set_gather2; /* * access_allowed - check whether op->o_ndn is allowed the requested access @@ -1148,7 +1126,7 @@ acl_mask_dn( return 1; } - if ( string_expand( &bv, &b->a_pat, + if ( acl_string_expand( &bv, &b->a_pat, e->e_nname.bv_val, tmp_nmatch, tmp_matchesp ) ) { @@ -1507,7 +1485,7 @@ slap_acl_mask( bv.bv_len = sizeof( buf ) - 1; bv.bv_val = buf; - if ( string_expand( &bv, &b->a_sockurl_pat, + if ( acl_string_expand( &bv, &b->a_sockurl_pat, e->e_ndn, nmatch, matches ) ) { continue; @@ -1552,7 +1530,7 @@ slap_acl_mask( bv.bv_len = sizeof(buf) - 1; bv.bv_val = buf; - if ( string_expand(&bv, &b->a_domain_pat, + if ( acl_string_expand(&bv, &b->a_domain_pat, e->e_ndn, nmatch, matches) ) { continue; @@ -1609,7 +1587,7 @@ slap_acl_mask( bv.bv_len = sizeof( buf ) - 1; bv.bv_val = buf; - if ( string_expand( &bv, &b->a_peername_pat, + if ( acl_string_expand( &bv, &b->a_peername_pat, e->e_ndn, nmatch, matches ) ) { continue; @@ -1628,11 +1606,12 @@ slap_acl_mask( int port_number = -1; if ( strncasecmp( op->o_conn->c_peer_name.bv_val, - aci_bv_ip_eq.bv_val, aci_bv_ip_eq.bv_len ) != 0 ) + aci_bv[ ACI_BV_IP_EQ ].bv_val, + aci_bv[ ACI_BV_IP_EQ ].bv_len ) != 0 ) continue; - ip.bv_val = op->o_conn->c_peer_name.bv_val + aci_bv_ip_eq.bv_len; - ip.bv_len = op->o_conn->c_peer_name.bv_len - aci_bv_ip_eq.bv_len; + ip.bv_val = op->o_conn->c_peer_name.bv_val + aci_bv[ ACI_BV_IP_EQ ].bv_len; + ip.bv_len = op->o_conn->c_peer_name.bv_len - aci_bv[ ACI_BV_IP_EQ ].bv_len; port = strrchr( ip.bv_val, ':' ); if ( port ) { @@ -1671,11 +1650,14 @@ slap_acl_mask( struct berval path; if ( strncmp( op->o_conn->c_peer_name.bv_val, - aci_bv_path_eq.bv_val, aci_bv_path_eq.bv_len ) != 0 ) + aci_bv[ ACI_BV_PATH_EQ ].bv_val, + aci_bv[ ACI_BV_PATH_EQ ].bv_len ) != 0 ) continue; - path.bv_val = op->o_conn->c_peer_name.bv_val + aci_bv_path_eq.bv_len; - path.bv_len = op->o_conn->c_peer_name.bv_len - aci_bv_path_eq.bv_len; + path.bv_val = op->o_conn->c_peer_name.bv_val + + aci_bv[ ACI_BV_PATH_EQ ].bv_len; + path.bv_len = op->o_conn->c_peer_name.bv_len + - aci_bv[ ACI_BV_PATH_EQ ].bv_len; if ( ber_bvcmp( &b->a_peername_pat, &path ) != 0 ) continue; @@ -1710,7 +1692,7 @@ slap_acl_mask( bv.bv_len = sizeof( buf ) - 1; bv.bv_val = buf; - if ( string_expand( &bv, &b->a_sockname_pat, + if ( acl_string_expand( &bv, &b->a_sockname_pat, e->e_ndn, nmatch, matches ) ) { continue; @@ -1818,7 +1800,7 @@ slap_acl_mask( continue; } - if ( string_expand( &bv, &b->a_group_pat, + if ( acl_string_expand( &bv, &b->a_group_pat, e->e_nname.bv_val, tmp_nmatch, tmp_matchesp ) ) { @@ -1903,7 +1885,7 @@ slap_acl_mask( continue; } - if ( string_expand( &bv, &b->a_set_pat, + if ( acl_string_expand( &bv, &b->a_set_pat, e->e_nname.bv_val, tmp_nmatch, tmp_matchesp ) ) { @@ -1914,7 +1896,7 @@ slap_acl_mask( bv = b->a_set_pat; } - if ( aci_match_set( &bv, op, e, 0 ) == 0 ) { + if ( acl_match_set( &bv, op, e, 0 ) == 0 ) { continue; } } @@ -2389,8 +2371,8 @@ done: return( ret ); } -static int -aci_get_part( +int +acl_get_part( struct berval *list, int ix, char sep, @@ -2432,15 +2414,15 @@ aci_get_part( return bv->bv_len; } -typedef struct aci_set_gather_t { +typedef struct acl_set_gather_t { SetCookie *cookie; BerVarray bvals; -} aci_set_gather_t; +} acl_set_gather_t; static int -aci_set_cb_gather( Operation *op, SlapReply *rs ) +acl_set_cb_gather( Operation *op, SlapReply *rs ) { - aci_set_gather_t *p = (aci_set_gather_t *)op->o_callback->sc_private; + acl_set_gather_t *p = (acl_set_gather_t *)op->o_callback->sc_private; if ( rs->sr_type == REP_SEARCH ) { BerValue bvals[ 2 ]; @@ -2483,17 +2465,17 @@ aci_set_cb_gather( Operation *op, SlapReply *rs ) } BerVarray -aci_set_gather( SetCookie *cookie, struct berval *name, AttributeDescription *desc ) +acl_set_gather( SetCookie *cookie, struct berval *name, AttributeDescription *desc ) { - AciSetCookie *cp = (AciSetCookie *)cookie; + AclSetCookie *cp = (AclSetCookie *)cookie; int rc = 0; LDAPURLDesc *ludp = NULL; Operation op2 = { 0 }; SlapReply rs = {REP_RESULT}; AttributeName anlist[ 2 ], *anlistp = NULL; int nattrs = 0; - slap_callback cb = { NULL, aci_set_cb_gather, NULL, NULL }; - aci_set_gather_t p = { 0 }; + slap_callback cb = { NULL, acl_set_cb_gather, NULL, NULL }; + acl_set_gather_t p = { 0 }; const char *text = NULL; static struct berval defaultFilter_bv = BER_BVC( "(objectClass=*)" ); @@ -2502,7 +2484,7 @@ aci_set_gather( SetCookie *cookie, struct berval *name, AttributeDescription *de * also return the syntax or some "comparison cookie". */ if ( strncasecmp( name->bv_val, "ldap:///", STRLENOF( "ldap:///" ) ) != 0 ) { - return aci_set_gather2( cookie, name, desc ); + return acl_set_gather2( cookie, name, desc ); } rc = ldap_url_parse( name->bv_val, &ludp ); @@ -2623,9 +2605,9 @@ url_done:; } BerVarray -aci_set_gather2( SetCookie *cookie, struct berval *name, AttributeDescription *desc ) +acl_set_gather2( SetCookie *cookie, struct berval *name, AttributeDescription *desc ) { - AciSetCookie *cp = (AciSetCookie *)cookie; + AclSetCookie *cp = (AclSetCookie *)cookie; BerVarray bvals = NULL; struct berval ndn; int rc = 0; @@ -2656,17 +2638,16 @@ aci_set_gather2( SetCookie *cookie, struct berval *name, AttributeDescription *d return bvals; } -static int -aci_match_set ( +int +acl_match_set ( struct berval *subj, Operation *op, Entry *e, - int setref -) + int setref ) { struct berval set = BER_BVNULL; int rc = 0; - AciSetCookie cookie; + AclSetCookie cookie; if ( setref == 0 ) { ber_dupbv_x( &set, subj, op->o_tmpmemctx ); @@ -2679,12 +2660,12 @@ aci_match_set ( AttributeDescription *desc = NULL; /* format of string is "entry/setAttrName" */ - if ( aci_get_part( subj, 0, '/', &subjdn ) < 0 ) { + if ( acl_get_part( subj, 0, '/', &subjdn ) < 0 ) { return 0; } - if ( aci_get_part( subj, 1, '/', &setat ) < 0 ) { - setat = aci_bv_set_attr; + if ( acl_get_part( subj, 1, '/', &setat ) < 0 ) { + setat = aci_bv[ ACI_BV_SET_ATTR ]; } /* @@ -2714,7 +2695,7 @@ aci_match_set ( if ( !BER_BVISNULL( &set ) ) { cookie.op = op; cookie.e = e; - rc = ( slap_set_filter( aci_set_gather, (SetCookie *)&cookie, &set, + rc = ( slap_set_filter( acl_set_gather, (SetCookie *)&cookie, &set, &op->o_ndn, &e->e_nname, NULL ) > 0 ); slap_sl_free( set.bv_val, op->o_tmpmemctx ); } @@ -2722,698 +2703,6 @@ aci_match_set ( return(rc); } -#ifdef SLAPD_ACI_ENABLED -static int -aci_list_map_rights( - struct berval *list ) -{ - struct berval bv; - slap_access_t mask; - int i; - - ACL_INIT(mask); - for (i = 0; aci_get_part(list, i, ',', &bv) >= 0; i++) { - if (bv.bv_len <= 0) - continue; - switch (*bv.bv_val) { - 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; - 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; - 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; aci_get_part(list, i, ',', &bv) >= 0; i++) { - if (aci_get_part(&bv, 0, '=', &left) < 0 - || aci_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) { - /* 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 part of an aci's - * rights list is of the form =, - * that means the aci applies only to attrs with - * the given value. Furthermore, if the attr is - * of the form =*, then is - * treated as a prefix, and the aci applies to - * any value with that prefix. - * - * Ideally, this would allow r.e. matches. - */ - if (aci_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; aci_get_part(list, i + 1, ';', &bv) >= 0; i += 2) { - if (aci_list_has_attr(&bv, attr, val) == 0) - continue; - if (aci_get_part(list, i, ';', &bv) < 0) - continue; - mask |= aci_list_map_rights(&bv); - } - return(mask); -} - -static int -aci_list_get_rights( - struct berval *list, - const struct berval *attr, - struct berval *val, - slap_access_t *grant, - slap_access_t *deny ) -{ - struct berval perm, actn; - slap_access_t *mask; - int i, found; - - if (attr == NULL || attr->bv_len == 0 - || ber_bvstrcasecmp( attr, &aci_bv_entry ) == 0) { - attr = &aci_bv_br_entry; - } - - found = 0; - ACL_INIT(*grant); - ACL_INIT(*deny); - /* loop through each permissions clause */ - for (i = 0; aci_get_part(list, i, '$', &perm) >= 0; i++) { - if (aci_get_part(&perm, 0, ';', &actn) < 0) - continue; - if (ber_bvstrcasecmp( &aci_bv_grant, &actn ) == 0) { - mask = grant; - } else if (ber_bvstrcasecmp( &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_br_all, NULL); - } - return(found); -} - -static int -aci_group_member ( - struct berval *subj, - struct berval *defgrpoc, - 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/objectClassValue/groupAttrName" */ - if (aci_get_part(subj, 0, '/', &subjdn) < 0) { - return(0); - } - - if (aci_get_part(subj, 1, '/', &grpoc) < 0) { - grpoc = *defgrpoc; - } - - if (aci_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[ACL_BUF_SIZE]; - struct berval bv, ndn; - bv.bv_len = sizeof( buf ) - 1; - bv.bv_val = (char *)&buf; - if ( 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, 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. - - = {entry|children|subtree} - = {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 aplies to "children" and "subtree" - */ - - /* check that the aci has all 5 components */ - if ( aci_get_part( aci, 4, '#', NULL ) < 0 ) { - return 0; - } - - /* check that the aci family is supported */ - if ( aci_get_part( aci, 0, '#', &bv ) < 0 ) { - return 0; - } - - /* check that the scope matches */ - if ( aci_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_bvstrcasecmp( &scope, &aci_bv_entry ) != 0 - && ber_bvstrcasecmp( &scope, &aci_bv_subtree ) != 0 ) - { - return 0; - } - break; - - case SLAP_ACI_SCOPE_CHILDREN: - if ( ber_bvstrcasecmp( &scope, &aci_bv_children ) != 0 - && ber_bvstrcasecmp( &scope, &aci_bv_subtree ) != 0 ) - { - return 0; - } - break; - - default: - return 0; - } - - /* get the list of permissions clauses, bail if empty */ - if ( aci_get_part( aci, 2, '#', &perms ) <= 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 ( aci_get_part( aci, 3, '#', &type ) < 0 ) { - return 0; - } - - /* see if we have a public (i.e. anonymous) access */ - if ( ber_bvstrcasecmp( &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_bvstrcasecmp( &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 ( aci_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 ); - - if ( ber_bvstrcasecmp( &aci_bv_access_id, &type ) == 0 ) { - struct berval ndn; - - rc = dnNormalize( 0, NULL, NULL, &sdn, &ndn, op->o_tmpmemctx ); - if ( rc != LDAP_SUCCESS ) { - return 0; - } - - if ( dn_match( &op->o_ndn, &ndn ) ) { - rc = 1; - } - slap_sl_free( ndn.bv_val, op->o_tmpmemctx ); - - return rc; - - } else if ( ber_bvstrcasecmp( &aci_bv_subtree, &type ) == 0 ) { - struct berval ndn; - - rc = dnNormalize( 0, NULL, NULL, &sdn, &ndn, op->o_tmpmemctx ); - if ( rc != LDAP_SUCCESS ) { - return 0; - } - - if ( dnIsSuffix( &op->o_ndn, &ndn ) ) { - rc = 1; - } - slap_sl_free( ndn.bv_val, op->o_tmpmemctx ); - - return rc; - - } else if ( ber_bvstrcasecmp( &aci_bv_onelevel, &type ) == 0 ) { - struct berval ndn, pndn; - - rc = dnNormalize( 0, NULL, NULL, &sdn, &ndn, op->o_tmpmemctx ); - if ( rc != LDAP_SUCCESS ) { - return 0; - } - - dnParent( &ndn, &pndn ); - - if ( dn_match( &op->o_ndn, &pndn ) ) { - rc = 1; - } - slap_sl_free( ndn.bv_val, op->o_tmpmemctx ); - - return rc; - - } else if ( ber_bvstrcasecmp( &aci_bv_children, &type ) == 0 ) { - struct berval ndn; - - rc = dnNormalize( 0, NULL, NULL, &sdn, &ndn, op->o_tmpmemctx ); - if ( rc != LDAP_SUCCESS ) { - return 0; - } - - if ( !dn_match( &op->o_ndn, &ndn ) - && dnIsSuffix( &op->o_ndn, &ndn ) ) - { - rc = 1; - } - slap_sl_free( ndn.bv_val, op->o_tmpmemctx ); - - return rc; - - } else if ( ber_bvstrcasecmp( &aci_bv_self, &type ) == 0 ) { - if ( dn_match( &op->o_ndn, &e->e_nname ) ) { - return 1; - } - - } else if ( ber_bvstrcasecmp( &aci_bv_dnattr, &type ) == 0 ) { - Attribute *at; - AttributeDescription *ad = NULL; - const char *text; - - rc = slap_bv2ad( &sdn, &ad, &text ); - - if( rc != LDAP_SUCCESS ) { - return 0; - } - - rc = 0; - - for ( at = attrs_find( e->e_attrs, ad ); - at != NULL; - at = attrs_find( at->a_next, ad ) ) - { - if ( value_find_ex( ad, - SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH | - SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH, - at->a_nvals, - &op->o_ndn, op->o_tmpmemctx ) == 0 ) - { - rc = 1; - break; - } - } - - return rc; - - } else if ( ber_bvstrcasecmp( &aci_bv_group, &type ) == 0 ) { - if ( aci_group_member( &sdn, &aci_bv_group_class, - &aci_bv_group_attr, op, e, nmatch, matches ) ) - { - return 1; - } - - } else if ( ber_bvstrcasecmp( &aci_bv_role, &type ) == 0 ) { - if ( aci_group_member( &sdn, &aci_bv_role_class, - &aci_bv_role_attr, op, e, nmatch, matches ) ) - { - return 1; - } - - } else if ( ber_bvstrcasecmp( &aci_bv_set, &type ) == 0 ) { - if ( aci_match_set( &sdn, op, e, 0 ) ) { - return 1; - } - - } else if ( ber_bvstrcasecmp( &aci_bv_set_ref, &type ) == 0 ) { - if ( aci_match_set( &sdn, op, e, 1 ) ) { - return 1; - } - } - - return 0; -} - -#ifdef SLAP_DYNACL -/* - * FIXME: there is a silly dependence that makes it difficult - * to move ACIs in a run-time loadable module under the "dynacl" - * umbrella, because sets share some helpers with ACIs. - */ -static int -dynacl_aci_parse( const char *fname, int lineno, 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_schema.si_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 */ - - /* 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; - -#if 1 - /* 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. */ - Operation op2 = *op; - - if ( !BER_BVISNULL( &op->o_bd->be_rootndn ) ) { - op2.o_dn = op->o_bd->be_rootdn; - op2.o_ndn = op->o_bd->be_rootndn; - } -#endif - - dnParent( &e->e_nname, &parent_ndn ); - while ( !BER_BVISEMPTY( &parent_ndn ) ){ - int i; - BerVarray bvals = NULL; - int ret, stop; - - Debug( LDAP_DEBUG_ACL, "checking ACI of \"%s\"\n", parent_ndn.bv_val, 0, 0 ); - ret = backend_attribute( &op2, NULL, &parent_ndn, ad, &bvals, ACL_AUTH ); - - 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 -}; - -#endif /* SLAP_DYNACL */ - -#endif /* SLAPD_ACI_ENABLED */ - #ifdef SLAP_DYNACL /* @@ -3475,27 +2764,22 @@ int acl_init( void ) { #ifdef SLAP_DYNACL - int i, rc; - slap_dynacl_t *known_dynacl[] = { -#ifdef SLAPD_ACI_ENABLED - &dynacl_aci, -#endif /* SLAPD_ACI_ENABLED */ - NULL - }; + int rc; - for ( i = 0; known_dynacl[ i ]; i++ ) { - rc = slap_dynacl_register( known_dynacl[ i ] ); - if ( rc ) { - return rc; - } +#ifdef SLAPD_ACI_ENABLED + rc = dynacl_aci_init(); + if ( rc != 0 ) { + return rc; } +#endif /* SLAPD_ACI_ENABLED */ + #endif /* SLAP_DYNACL */ return 0; } -static int -string_expand( +int +acl_string_expand( struct berval *bv, struct berval *pat, char *match, @@ -3579,8 +2863,8 @@ string_expand( *dp = '\0'; bv->bv_len = size; - Debug( LDAP_DEBUG_TRACE, "=> string_expand: pattern: %.*s\n", (int)pat->bv_len, pat->bv_val, 0 ); - Debug( LDAP_DEBUG_TRACE, "=> string_expand: expanded: %s\n", bv->bv_val, 0, 0 ); + Debug( LDAP_DEBUG_TRACE, "=> acl_string_expand: pattern: %.*s\n", (int)pat->bv_len, pat->bv_val, 0 ); + Debug( LDAP_DEBUG_TRACE, "=> acl_string_expand: expanded: %s\n", bv->bv_val, 0, 0 ); return 0; } @@ -3606,7 +2890,7 @@ regex_matches( str = ""; }; - string_expand( &bv, pat, buf, nmatch, matches ); + acl_string_expand( &bv, pat, buf, nmatch, matches ); rc = regcomp( &re, newbuf, REG_EXTENDED|REG_ICASE ); if ( rc ) { char error[ACL_BUF_SIZE]; diff --git a/servers/slapd/proto-slap.h b/servers/slapd/proto-slap.h index 06ff2b2ea7..08b9668d32 100644 --- a/servers/slapd/proto-slap.h +++ b/servers/slapd/proto-slap.h @@ -31,6 +31,30 @@ LDAP_BEGIN_DECL +/* + * aci.c + */ +#ifdef SLAPD_ACI_ENABLED +LDAP_SLAPD_F (int) aci_mask LDAP_P(( + 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 scope)); +LDAP_SLAPD_F (int) OpenLDAPaciValidate LDAP_P(( + Syntax *syn, struct berval *in )); +LDAP_SLAPD_F (int) OpenLDAPaciPretty LDAP_P(( + Syntax *syn, struct berval *val, struct berval *out, void *ctx )); +LDAP_SLAPD_F (slap_mr_normalize_func) OpenLDAPaciNormalize; +#ifdef SLAP_DYNACL +LDAP_SLAPD_F (int) dynacl_aci_init LDAP_P(( void )); +#endif /* SLAP_DYNACL */ +#endif /* SLAPD_ACI_ENABLED */ + /* * acl.c */ @@ -71,6 +95,22 @@ LDAP_SLAPD_F (slap_dynacl_t *) slap_dynacl_get LDAP_P(( const char *name )); #endif /* SLAP_DYNACL */ LDAP_SLAPD_F (int) acl_init LDAP_P(( void )); +LDAP_SLAPD_V (const struct berval) aci_bv[]; + +LDAP_SLAPD_F (int) acl_get_part LDAP_P(( + struct berval *list, + int ix, + char sep, + struct berval *bv )); +LDAP_SLAPD_F (int) acl_match_set LDAP_P(( + struct berval *subj, + Operation *op, + Entry *e, + int setref )); +LDAP_SLAPD_F (int) acl_string_expand LDAP_P(( + struct berval *newbuf, struct berval *pattern, + char *match, int nmatch, regmatch_t *matches )); + /* * aclparse.c */ @@ -1407,7 +1447,9 @@ LDAP_SLAPD_F (void) schema_destroy LDAP_P(( void )); LDAP_SLAPD_F( slap_mr_indexer_func ) octetStringIndexer; LDAP_SLAPD_F( slap_mr_filter_func ) octetStringFilter; - +LDAP_SLAPD_F( int ) numericoidValidate LDAP_P(( + struct slap_syntax *syntax, + struct berval *in )); /* * schema_prep.c diff --git a/servers/slapd/schema_init.c b/servers/slapd/schema_init.c index 344acbe974..183d1875bf 100644 --- a/servers/slapd/schema_init.c +++ b/servers/slapd/schema_init.c @@ -50,7 +50,7 @@ #define HASH_Update(c,buf,len) lutil_HASHUpdate(c,buf,len) #define HASH_Final(d,c) lutil_HASHFinal(d,c) -#define OpenLDAPaciMatch NULL +#define OpenLDAPaciMatch octetStringMatch /* approx matching rules */ #define directoryStringApproxMatchOID "1.3.6.1.4.1.4203.666.4.4" @@ -1861,7 +1861,7 @@ telephoneNumberNormalize( return LDAP_SUCCESS; } -static int +int numericoidValidate( Syntax *syntax, struct berval *in ) @@ -3427,8 +3427,8 @@ static slap_syntax_defs_rec syntax_defs[] = { /* OpenLDAP Experimental Syntaxes */ {"( 1.3.6.1.4.1.4203.666.2.1 DESC 'OpenLDAP Experimental ACI' )", SLAP_SYNTAX_HIDE, - UTF8StringValidate /* THIS WILL CHANGE FOR NEW ACI SYNTAX */, - NULL}, + OpenLDAPaciValidate, + OpenLDAPaciPretty}, #endif #ifdef SLAPD_AUTHPASSWD @@ -3851,7 +3851,7 @@ static slap_mrule_defs_rec mrule_defs[] = { {"( 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, NULL, OpenLDAPaciMatch, + NULL, OpenLDAPaciNormalize, OpenLDAPaciMatch, NULL, NULL, NULL}, #endif diff --git a/servers/slapd/slap.h b/servers/slapd/slap.h index 130648a663..0d9fc9ce9d 100644 --- a/servers/slapd/slap.h +++ b/servers/slapd/slap.h @@ -914,7 +914,7 @@ struct slap_internal_schema { #endif AttributeDescription *si_ad_description; AttributeDescription *si_ad_seeAlso; - + /* Undefined Attribute Type */ AttributeType *si_at_undefined; @@ -1481,6 +1481,53 @@ typedef struct slap_acl_state { #define ACL_STATE_INIT { ACL_STATE_NOT_RECORDED, NULL, NULL, 0UL, \ { { 0, 0 } }, 0, NULL, 0, 0, NULL } +#ifdef SLAPD_ACI_ENABLED +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; +#endif /* SLAPD_ACI_ENABLED */ + +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, +#if 0 + ACI_BV_ANONYMOUS = BER_BVC("anonymous"), +#endif + 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_IP_EQ, +#ifdef LDAP_PF_LOCAL + ACI_BV_PATH_EQ, +#if 0 + ACI_BV_DIRSEP, +#endif +#endif /* LDAP_PF_LOCAL */ + + ACI_BV_GROUP_CLASS, + ACI_BV_GROUP_ATTR, + ACI_BV_ROLE_CLASS, + ACI_BV_ROLE_ATTR, + ACI_BV_SET_ATTR, + + ACI_BV_LAST +}; + /* * Backend-info * represents a backend -- 2.39.5