openldap/servers/slapd/acl.c
Kurt Zeilenga 492762f1c5 Don't use BDB group/attribute callbacks as they may cause deadlock.
Add code to bdb_attribute and bdb_group where use TXN id and to
provide error, but need to rework callers (and their callers) to
ensure error is properly bubbled up to the backend operation routine
handling the transaction.  Ugh.
2002-01-17 03:58:52 +00:00

1813 lines
44 KiB
C

/* acl.c - routines to parse and check acl's */
/* $OpenLDAP$ */
/*
* Copyright 1998-2002 The OpenLDAP Foundation, All Rights Reserved.
* COPYING RESTRICTIONS APPLY, see COPYRIGHT file
*/
#include "portable.h"
#include <stdio.h>
#include <ac/regex.h>
#include <ac/socket.h>
#include <ac/string.h>
#include "slap.h"
#include "sets.h"
#include "lber_pvt.h"
/*
* speed up compares
*/
static struct berval
aci_bv_entry = { sizeof("entry") - 1, "entry" },
aci_bv_br_entry = { sizeof("[entry]") - 1, "[entry]" },
aci_bv_br_all = { sizeof("[all]") - 1, "[all]" },
aci_bv_access_id = { sizeof("access-id") - 1, "access-id" },
aci_bv_anonymous = { sizeof("anonymous") - 1, "anonymous" },
aci_bv_users = { sizeof("users") - 1, "users" },
aci_bv_self = { sizeof("self") - 1, "self" },
aci_bv_dnattr = { sizeof("dnattr") - 1, "dnattr" },
aci_bv_group = { sizeof("group") - 1, "group" },
aci_bv_role = { sizeof("role") - 1, "role" },
aci_bv_set = { sizeof("set") - 1, "set" },
aci_bv_set_ref = { sizeof("set-ref") - 1, "set-ref"},
aci_bv_grant = { sizeof("grant") - 1, "grant" },
aci_bv_deny = { sizeof("deny") - 1, "deny" };
static AccessControl * acl_get(
AccessControl *ac, int *count,
Backend *be, Operation *op,
Entry *e,
AttributeDescription *desc,
int nmatches, regmatch_t *matches );
static slap_control_t acl_mask(
AccessControl *ac, slap_mask_t *mask,
Backend *be, Connection *conn, Operation *op,
Entry *e,
AttributeDescription *desc,
struct berval *val,
regmatch_t *matches );
#ifdef SLAPD_ACI_ENABLED
static int aci_mask(
Backend *be,
Connection *conn,
Operation *op,
Entry *e,
AttributeDescription *desc,
struct berval *val,
struct berval *aci,
regmatch_t *matches,
slap_access_t *grant,
slap_access_t *deny );
#endif
static int regex_matches(
char *pat, char *str, char *buf, regmatch_t *matches);
static void string_expand(
struct berval *newbuf, char *pattern,
char *match, regmatch_t *matches);
typedef struct AciSetCookie {
Backend *be;
Entry *e;
Connection *conn;
Operation *op;
} AciSetCookie;
BerVarray aci_set_gather (void *cookie, char *name, struct berval *attr);
static int aci_match_set ( struct berval *subj, Backend *be,
Entry *e, Connection *conn, Operation *op, int setref );
/*
* access_allowed - check whether op->o_ndn is allowed the requested access
* to entry e, attribute attr, value val. if val is null, access to
* the whole attribute is assumed (all values).
*
* This routine loops through all access controls and calls
* acl_mask() on each applicable access control.
* The loop exits when a definitive answer is reached or
* or no more controls remain.
*
* returns:
* 0 access denied
* 1 access granted
*/
int
access_allowed(
Backend *be,
Connection *conn,
Operation *op,
Entry *e,
AttributeDescription *desc,
struct berval *val,
slap_access_t access )
{
int count;
AccessControl *a;
#ifdef LDAP_DEBUG
char accessmaskbuf[ACCESSMASK_MAXLEN];
#endif
slap_mask_t mask;
slap_control_t control;
const char *attr;
regmatch_t matches[MAXREMATCHES];
assert( e != NULL );
assert( desc != NULL );
assert( access > ACL_NONE );
attr = desc->ad_cname.bv_val;
assert( attr != NULL );
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_ENTRY,
"access_allowed: conn %d %s access to \"%s\" \"%s\" requested\n",
conn ? conn->c_connid : -1, access2str( access ), e->e_dn, attr ));
#else
Debug( LDAP_DEBUG_ACL,
"=> access_allowed: %s access to \"%s\" \"%s\" requested\n",
access2str( access ), e->e_dn, attr );
#endif
if ( op == NULL ) {
/* no-op call */
return 1;
}
if ( be == NULL ) be = &backends[0];
assert( be != NULL );
/* grant database root access */
if ( be != NULL && be_isroot( be, &op->o_ndn ) ) {
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_INFO,
"access_allowed: conn %d root access granted\n",
conn->c_connid));
#else
Debug( LDAP_DEBUG_ACL,
"<= root access granted\n",
0, 0, 0 );
#endif
return 1;
}
/*
* no-user-modification operational attributes are ignored
* by ACL_WRITE checking as any found here are not provided
* by the user
*/
if ( access >= ACL_WRITE && is_at_no_user_mod( desc->ad_type )
&& desc != slap_schema.si_ad_entry
&& desc != slap_schema.si_ad_children )
{
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1,
"access_allowed: conn %d NoUserMod Operational attribute: %s access granted\n",
conn->c_connid, attr ));
#else
Debug( LDAP_DEBUG_ACL, "NoUserMod Operational attribute:"
" %s access granted\n",
attr, 0, 0 );
#endif
return 1;
}
/* use backend default access if no backend acls */
if( be != NULL && be->be_acl == NULL ) {
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1,
"access_allowed: conn %d backend default %s access %s to \"%s\"\n",
conn->c_connid, access2str( access ),
be->be_dfltaccess >= access ? "granted" : "denied", op->o_dn.bv_val ));
#else
Debug( LDAP_DEBUG_ACL,
"=> access_allowed: backend default %s access %s to \"%s\"\n",
access2str( access ),
be->be_dfltaccess >= access ? "granted" : "denied", op->o_dn.bv_val );
#endif
return be->be_dfltaccess >= access;
#ifdef notdef
/* be is always non-NULL */
/* use global default access if no global acls */
} else if ( be == NULL && global_acl == NULL ) {
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1,
"access_allowed: conn %d global default %s access %s to \"%s\"\n",
conn->c_connid, access2str( access ),
global_default_access >= access ? "granted" : "denied", op->o_dn.bv_val ));
#else
Debug( LDAP_DEBUG_ACL,
"=> access_allowed: global default %s access %s to \"%s\"\n",
access2str( access ),
global_default_access >= access ? "granted" : "denied", op->o_dn.bv_val );
#endif
return global_default_access >= access;
#endif
}
ACL_INIT(mask);
memset(matches, '\0', sizeof(matches));
control = ACL_BREAK;
a = NULL;
count = 0;
while((a = acl_get( a, &count, be, op, e, desc, MAXREMATCHES, matches )) != NULL)
{
int i;
for (i = 0; i < MAXREMATCHES && matches[i].rm_so > 0; i++) {
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1,
"access_allowed: conn %d match[%d]: %d %d ",
conn->c_connid, i, (int)matches[i].rm_so, (int)matches[i].rm_eo ));
#else
Debug( LDAP_DEBUG_ACL, "=> match[%d]: %d %d ", i,
(int)matches[i].rm_so, (int)matches[i].rm_eo );
#endif
if( matches[i].rm_so <= matches[0].rm_eo ) {
int n;
for ( n = matches[i].rm_so; n < matches[i].rm_eo; n++) {
Debug( LDAP_DEBUG_ACL, "%c", e->e_ndn[n], 0, 0 );
}
}
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_ARGS, "\n" ));
#else
Debug( LDAP_DEBUG_ARGS, "\n", 0, 0, 0 );
#endif
}
control = acl_mask( a, &mask, be, conn, op,
e, desc, val, matches );
if ( control != ACL_BREAK ) {
break;
}
memset(matches, '\0', sizeof(matches));
}
if ( ACL_IS_INVALID( mask ) ) {
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1,
"access_allowed: conn %d \"%s\" (%s) invalid!\n",
conn->c_connid, e->e_dn, attr ));
#else
Debug( LDAP_DEBUG_ACL,
"=> access_allowed: \"%s\" (%s) invalid!\n",
e->e_dn, attr, 0 );
#endif
ACL_INIT( mask );
} else if ( control == ACL_BREAK ) {
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1,
"access_allowed: conn %d no more rules\n", conn->c_connid ));
#else
Debug( LDAP_DEBUG_ACL,
"=> access_allowed: no more rules\n", 0, 0, 0);
#endif
ACL_INIT( mask );
}
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_ENTRY,
"access_allowed: conn %d %s access %s by %s\n",
conn->c_connid,
access2str( access ),
ACL_GRANT( mask, access ) ? "granted" : "denied",
accessmask2str( mask, accessmaskbuf ) ));
#else
Debug( LDAP_DEBUG_ACL,
"=> access_allowed: %s access %s by %s\n",
access2str( access ),
ACL_GRANT(mask, access) ? "granted" : "denied",
accessmask2str( mask, accessmaskbuf ) );
#endif
return ACL_GRANT(mask, access);
}
/*
* acl_get - return the acl applicable to entry e, attribute
* attr. the acl returned is suitable for use in subsequent calls to
* acl_access_allowed().
*/
static AccessControl *
acl_get(
AccessControl *a,
int *count,
Backend *be,
Operation *op,
Entry *e,
AttributeDescription *desc,
int nmatch,
regmatch_t *matches )
{
const char *attr;
int dnlen, patlen;
assert( e != NULL );
assert( count != NULL );
assert( desc != NULL );
attr = desc->ad_cname.bv_val;
assert( attr != NULL );
if( a == NULL ) {
if( be == NULL ) {
a = global_acl;
} else {
a = be->be_acl;
}
assert( a != NULL );
} else {
a = a->acl_next;
}
dnlen = e->e_nname.bv_len;
for ( ; a != NULL; a = a->acl_next ) {
(*count) ++;
if (a->acl_dn_pat.bv_len != 0) {
if ( a->acl_dn_style == ACL_STYLE_REGEX ) {
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1,
"acl_get: dnpat [%d] %s nsub: %d\n",
*count, a->acl_dn_pat.bv_val, (int) a->acl_dn_re.re_nsub ));
#else
Debug( LDAP_DEBUG_ACL, "=> dnpat: [%d] %s nsub: %d\n",
*count, a->acl_dn_pat.bv_val, (int) a->acl_dn_re.re_nsub );
#endif
if (regexec(&a->acl_dn_re, e->e_ndn, nmatch, matches, 0))
continue;
} else {
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1,
"acl_get: dn [%d] %s\n",
*count, a->acl_dn_pat.bv_val ));
#else
Debug( LDAP_DEBUG_ACL, "=> dn: [%d] %s\n",
*count, a->acl_dn_pat.bv_val, 0 );
#endif
patlen = a->acl_dn_pat.bv_len;
if ( dnlen < patlen )
continue;
if ( a->acl_dn_style == ACL_STYLE_BASE ) {
/* base dn -- entire object DN must match */
if ( dnlen != patlen )
continue;
} else if ( a->acl_dn_style == ACL_STYLE_ONE ) {
int rdnlen = -1;
if ( dnlen <= patlen )
continue;
if ( !DN_SEPARATOR( e->e_ndn[dnlen - patlen - 1] ) || DN_ESCAPE( e->e_ndn[dnlen - patlen - 2] ) )
continue;
rdnlen = dn_rdnlen( NULL, &e->e_nname );
if ( rdnlen != dnlen - patlen - 1 )
continue;
} else if ( a->acl_dn_style == ACL_STYLE_SUBTREE ) {
if ( dnlen > patlen && ( !DN_SEPARATOR( e->e_ndn[dnlen - patlen - 1] ) || DN_ESCAPE( e->e_ndn[dnlen - patlen - 2] ) ) )
continue;
} else if ( a->acl_dn_style == ACL_STYLE_CHILDREN ) {
if ( dnlen <= patlen )
continue;
if ( !DN_SEPARATOR( e->e_ndn[dnlen - patlen - 1] ) || DN_ESCAPE( e->e_ndn[dnlen - patlen - 2] ) )
continue;
}
if ( strcmp( a->acl_dn_pat.bv_val, e->e_ndn + dnlen - patlen ) != 0 )
continue;
}
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1,
"acl_get: [%d] matched\n",
*count ));
#else
Debug( LDAP_DEBUG_ACL, "=> acl_get: [%d] matched\n",
*count, 0, 0 );
#endif
}
if ( a->acl_filter != NULL ) {
ber_int_t rc = test_filter( NULL, NULL, NULL, e, a->acl_filter );
if ( rc != LDAP_COMPARE_TRUE ) {
continue;
}
}
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1,
"acl_get: [%d] check attr %s\n",
*count, attr ));
#else
Debug( LDAP_DEBUG_ACL, "=> acl_get: [%d] check attr %s\n",
*count, attr, 0);
#endif
if ( attr == NULL || a->acl_attrs == NULL ||
ad_inlist( desc, a->acl_attrs ) )
{
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1,
"acl_get: [%d] acl %s attr: %s\n",
*count, e->e_dn, attr ));
#else
Debug( LDAP_DEBUG_ACL,
"<= acl_get: [%d] acl %s attr: %s\n",
*count, e->e_dn, attr );
#endif
return a;
}
matches[0].rm_so = matches[0].rm_eo = -1;
}
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_ENTRY,
"acl_get: done.\n" ));
#else
Debug( LDAP_DEBUG_ACL, "<= acl_get: done.\n", 0, 0, 0 );
#endif
return( NULL );
}
/*
* acl_mask - modifies mask based upon the given acl and the
* requested access to entry e, attribute attr, value val. if val
* is null, access to the whole attribute is assumed (all values).
*
* returns 0 access NOT allowed
* 1 access allowed
*/
static slap_control_t
acl_mask(
AccessControl *a,
slap_mask_t *mask,
Backend *be,
Connection *conn,
Operation *op,
Entry *e,
AttributeDescription *desc,
struct berval *val,
regmatch_t *matches
)
{
int i, odnlen, patlen;
Access *b;
#ifdef LDAP_DEBUG
char accessmaskbuf[ACCESSMASK_MAXLEN];
#endif
const char *attr;
assert( a != NULL );
assert( mask != NULL );
assert( desc != NULL );
attr = desc->ad_cname.bv_val;
assert( attr != NULL );
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_ENTRY,
"acl_mask: conn %d access to entry \"%s\", attr \"%s\" requested\n",
conn->c_connid, e->e_dn, attr ));
LDAP_LOG(( "acl", LDAP_LEVEL_ARGS,
" to %s by \"%s\", (%s) \n",
val ? "value" : "all values",
op->o_ndn.bv_val ? op->o_ndn.bv_val : "",
accessmask2str( *mask, accessmaskbuf ) ));
#else
Debug( LDAP_DEBUG_ACL,
"=> acl_mask: access to entry \"%s\", attr \"%s\" requested\n",
e->e_dn, attr, 0 );
Debug( LDAP_DEBUG_ACL,
"=> acl_mask: to %s by \"%s\", (%s) \n",
val ? "value" : "all values",
op->o_ndn.bv_val ? op->o_ndn.bv_val : "",
accessmask2str( *mask, accessmaskbuf ) );
#endif
for ( i = 1, b = a->acl_access; b != NULL; b = b->a_next, i++ ) {
slap_mask_t oldmask, modmask;
ACL_INVALIDATE( modmask );
/* AND <who> clauses */
if ( b->a_dn_pat.bv_len != 0 ) {
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1,
"acl_mask: conn %d check a_dn_pat: %s\n",
conn->c_connid, b->a_dn_pat.bv_val ));
#else
Debug( LDAP_DEBUG_ACL, "<= check a_dn_pat: %s\n",
b->a_dn_pat.bv_val, 0, 0);
#endif
/*
* if access applies to the entry itself, and the
* user is bound as somebody in the same namespace as
* the entry, OR the given dn matches the dn pattern
*/
if ( ber_bvstrcmp( &b->a_dn_pat, &aci_bv_anonymous ) == 0 ) {
if ( op->o_ndn.bv_len != 0 ) {
continue;
}
} else if ( ber_bvstrcmp( &b->a_dn_pat, &aci_bv_users ) == 0 ) {
if ( op->o_ndn.bv_len == 0 ) {
continue;
}
} else if ( ber_bvstrcmp( &b->a_dn_pat, &aci_bv_self ) == 0 ) {
if ( op->o_ndn.bv_len == 0 ) {
continue;
}
if ( e->e_dn == NULL || !dn_match( &e->e_nname, &op->o_ndn ) ) {
continue;
}
} else if ( b->a_dn_style == ACL_STYLE_REGEX ) {
if ( ber_bvccmp( &b->a_dn_pat, '*' ) == 0 ) {
int ret = regex_matches( b->a_dn_pat.bv_val,
op->o_ndn.bv_val, e->e_ndn, matches );
if( ret == 0 ) {
continue;
}
}
} else {
if ( e->e_dn == NULL )
continue;
patlen = b->a_dn_pat.bv_len;
odnlen = op->o_ndn.bv_len;
if ( odnlen < patlen )
continue;
if ( b->a_dn_style == ACL_STYLE_BASE ) {
/* base dn -- entire object DN must match */
if ( odnlen != patlen )
continue;
} else if ( b->a_dn_style == ACL_STYLE_ONE ) {
int rdnlen = -1;
if ( odnlen <= patlen )
continue;
if ( !DN_SEPARATOR( op->o_ndn.bv_val[odnlen - patlen - 1] ) || DN_ESCAPE( op->o_ndn.bv_val[odnlen - patlen - 2] ) )
continue;
rdnlen = dn_rdnlen( NULL, &op->o_ndn );
if ( rdnlen != odnlen - patlen - 1 )
continue;
} else if ( b->a_dn_style == ACL_STYLE_SUBTREE ) {
if ( odnlen > patlen && ( !DN_SEPARATOR( op->o_ndn.bv_val[odnlen - patlen - 1] ) || DN_ESCAPE( op->o_ndn.bv_val[odnlen - patlen - 2] ) ) )
continue;
} else if ( b->a_dn_style == ACL_STYLE_CHILDREN ) {
if ( odnlen <= patlen )
continue;
if ( !DN_SEPARATOR( op->o_ndn.bv_val[odnlen - patlen - 1] ) || DN_ESCAPE( op->o_ndn.bv_val[odnlen - patlen - 2] ) )
continue;
}
if ( strcmp( b->a_dn_pat.bv_val, op->o_ndn.bv_val + odnlen - patlen ) != 0 )
continue;
}
}
if ( b->a_sockurl_pat != NULL ) {
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1,
"acl_mask: conn %d check a_sockurl_pat: %s\n",
conn->c_connid, b->a_sockurl_pat ));
#else
Debug( LDAP_DEBUG_ACL, "<= check a_sockurl_pat: %s\n",
b->a_sockurl_pat, 0, 0 );
#endif
if ( ber_strccmp( b->a_sockurl_pat, '*' ) != 0) {
if ( b->a_sockurl_style == ACL_STYLE_REGEX) {
if (!regex_matches( b->a_sockurl_pat, conn->c_listener_url,
e->e_ndn, matches ) )
{
continue;
}
} else {
if ( strcasecmp( b->a_sockurl_pat, conn->c_listener_url ) != 0 )
continue;
}
}
}
if ( b->a_domain_pat != NULL ) {
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1,
"acl_mask: conn %d check a_domain_pat: %s\n",
conn->c_connid, b->a_domain_pat ));
#else
Debug( LDAP_DEBUG_ACL, "<= check a_domain_pat: %s\n",
b->a_domain_pat, 0, 0 );
#endif
if ( ber_strccmp( b->a_domain_pat, '*' ) != 0) {
if ( b->a_domain_style == ACL_STYLE_REGEX) {
if (!regex_matches( b->a_domain_pat, conn->c_peer_domain,
e->e_ndn, matches ) )
{
continue;
}
} else {
if ( strcasecmp( b->a_domain_pat, conn->c_peer_domain ) != 0 )
continue;
}
}
}
if ( b->a_peername_pat != NULL ) {
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1,
"acl_mask: conn %d check a_perrname_path: %s\n",
conn->c_connid, b->a_peername_pat ));
#else
Debug( LDAP_DEBUG_ACL, "<= check a_peername_path: %s\n",
b->a_peername_pat, 0, 0 );
#endif
if ( ber_strccmp( b->a_peername_pat, '*' ) != 0) {
if ( b->a_peername_style == ACL_STYLE_REGEX) {
if (!regex_matches( b->a_peername_pat, conn->c_peer_name,
e->e_ndn, matches ) )
{
continue;
}
} else {
if ( strcasecmp( b->a_peername_pat, conn->c_peer_name ) != 0 )
continue;
}
}
}
if ( b->a_sockname_pat != NULL ) {
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1,
"acl_mask: conn %d check a_sockname_path: %s\n",
conn->c_connid, b->a_sockname_pat ));
#else
Debug( LDAP_DEBUG_ACL, "<= check a_sockname_path: %s\n",
b->a_sockname_pat, 0, 0 );
#endif
if ( ber_strccmp( b->a_sockname_pat, '*' ) != 0) {
if ( b->a_sockname_style == ACL_STYLE_REGEX) {
if (!regex_matches( b->a_sockname_pat, conn->c_sock_name,
e->e_ndn, matches ) )
{
continue;
}
} else {
if ( strcasecmp( b->a_sockname_pat, conn->c_sock_name ) != 0 )
continue;
}
}
}
if ( b->a_dn_at != NULL && op->o_ndn.bv_len != 0 ) {
Attribute *at;
struct berval bv;
int rc, match = 0;
const char *text;
const char *attr = b->a_dn_at->ad_cname.bv_val;
assert( attr != NULL );
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1,
"acl_mask: conn %d check a_dn_pat: %s\n",
conn->c_connid, attr ));
#else
Debug( LDAP_DEBUG_ACL, "<= check a_dn_at: %s\n",
attr, 0, 0);
#endif
bv = op->o_ndn;
/* see if asker is listed in dnattr */
for( at = attrs_find( e->e_attrs, b->a_dn_at );
at != NULL;
at = attrs_find( at->a_next, b->a_dn_at ) )
{
if( value_find( b->a_dn_at, at->a_vals, &bv ) == 0 ) {
/* found it */
match = 1;
break;
}
}
if( match ) {
/* have a dnattr match. if this is a self clause then
* the target must also match the op dn.
*/
if ( b->a_dn_self ) {
/* check if the target is an attribute. */
if ( val == NULL )
continue;
/* target is attribute, check if the attribute value
* is the op dn.
*/
rc = value_match( &match, b->a_dn_at,
b->a_dn_at->ad_type->sat_equality, 0,
val, &bv, &text );
/* on match error or no match, fail the ACL clause */
if (rc != LDAP_SUCCESS || match != 0 )
continue;
}
} else {
/* no dnattr match, check if this is a self clause */
if ( ! b->a_dn_self )
continue;
/* this is a self clause, check if the target is an
* attribute.
*/
if ( val == NULL )
continue;
/* target is attribute, check if the attribute value
* is the op dn.
*/
rc = value_match( &match, b->a_dn_at,
b->a_dn_at->ad_type->sat_equality, 0,
val, &bv, &text );
/* on match error or no match, fail the ACL clause */
if (rc != LDAP_SUCCESS || match != 0 )
continue;
}
}
if ( b->a_group_pat.bv_len && op->o_ndn.bv_len ) {
char buf[1024];
struct berval bv = { sizeof(buf) - 1, buf };
struct berval ndn = { 0, NULL };
int rc;
/* b->a_group is an unexpanded entry name, expanded it should be an
* entry with objectclass group* and we test to see if odn is one of
* the values in the attribute group
*/
/* see if asker is listed in dnattr */
if ( b->a_group_style == ACL_STYLE_REGEX ) {
string_expand(&bv, b->a_group_pat.bv_val, e->e_ndn, matches);
if ( dnNormalize2(NULL, &bv, &ndn) != LDAP_SUCCESS ) {
/* did not expand to a valid dn */
continue;
}
bv = ndn;
} else {
bv = b->a_group_pat;
}
rc = backend_group(be, conn, op, e, &bv, &op->o_ndn,
b->a_group_oc, b->a_group_at);
if ( ndn.bv_val )
free( ndn.bv_val );
if ( rc != 0 ) {
continue;
}
}
if ( b->a_set_pat.bv_len != 0 ) {
if (aci_match_set( &b->a_set_pat, be, e, conn, op, 0 ) == 0) {
continue;
}
}
if ( b->a_authz.sai_ssf ) {
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1,
"acl_mask: conn %d check a_authz.sai_ssf: ACL %u > OP %u\n",
conn->c_connid, b->a_authz.sai_ssf, op->o_ssf ));
#else
Debug( LDAP_DEBUG_ACL, "<= check a_authz.sai_ssf: ACL %u > OP %u\n",
b->a_authz.sai_ssf, op->o_ssf, 0 );
#endif
if ( b->a_authz.sai_ssf > op->o_ssf ) {
continue;
}
}
if ( b->a_authz.sai_transport_ssf ) {
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1,
"acl_mask: conn %d check a_authz.sai_transport_ssf: ACL %u > OP %u\n",
conn->c_connid, b->a_authz.sai_transport_ssf, op->o_transport_ssf ));
#else
Debug( LDAP_DEBUG_ACL,
"<= check a_authz.sai_transport_ssf: ACL %u > OP %u\n",
b->a_authz.sai_transport_ssf, op->o_transport_ssf, 0 );
#endif
if ( b->a_authz.sai_transport_ssf > op->o_transport_ssf ) {
continue;
}
}
if ( b->a_authz.sai_tls_ssf ) {
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1,
"acl_mask: conn %d check a_authz.sai_tls_ssf: ACL %u > OP %u\n",
conn->c_connid, b->a_authz.sai_tls_ssf, op->o_tls_ssf ));
#else
Debug( LDAP_DEBUG_ACL,
"<= check a_authz.sai_tls_ssf: ACL %u > OP %u\n",
b->a_authz.sai_tls_ssf, op->o_tls_ssf, 0 );
#endif
if ( b->a_authz.sai_tls_ssf > op->o_tls_ssf ) {
continue;
}
}
if ( b->a_authz.sai_sasl_ssf ) {
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1,
"acl_mask: conn %d check a_authz.sai_sasl_ssf: ACL %u > OP %u\n",
conn->c_connid, b->a_authz.sai_sasl_ssf, op->o_sasl_ssf ));
#else
Debug( LDAP_DEBUG_ACL,
"<= check a_authz.sai_sasl_ssf: ACL %u > OP %u\n",
b->a_authz.sai_sasl_ssf, op->o_sasl_ssf, 0 );
#endif
if ( b->a_authz.sai_sasl_ssf > op->o_sasl_ssf ) {
continue;
}
}
#ifdef SLAPD_ACI_ENABLED
if ( b->a_aci_at != NULL ) {
Attribute *at;
slap_access_t grant, deny, tgrant, tdeny;
/* this case works different from the others above.
* since aci's themselves give permissions, we need
* to first check b->a_access_mask, the ACL's access level.
*/
if ( e->e_nname.bv_len == 0 ) {
/* no ACIs in the root DSE */
continue;
}
/* first check if the right being requested
* is allowed by the ACL clause.
*/
if ( ! ACL_GRANT( b->a_access_mask, *mask ) ) {
continue;
}
/* get the aci attribute */
at = attr_find( e->e_attrs, b->a_aci_at );
if ( at == NULL ) {
continue;
}
/* start out with nothing granted, nothing denied */
ACL_INIT(tgrant);
ACL_INIT(tdeny);
/* the aci is an multi-valued attribute. The
* rights are determined by OR'ing the individual
* rights given by the acis.
*/
for ( i = 0; at->a_vals[i].bv_val != NULL; i++ ) {
if (aci_mask( be, conn, op,
e, desc, val, &at->a_vals[i],
matches, &grant, &deny ) != 0)
{
tgrant |= grant;
tdeny |= deny;
}
}
/* remove anything that the ACL clause does not allow */
tgrant &= b->a_access_mask & ACL_PRIV_MASK;
tdeny &= ACL_PRIV_MASK;
/* see if we have anything to contribute */
if( ACL_IS_INVALID(tgrant) && ACL_IS_INVALID(tdeny) ) {
continue;
}
/* this could be improved by changing acl_mask so that it can deal with
* by clauses that return grant/deny pairs. Right now, it does either
* additive or subtractive rights, but not both at the same time. So,
* we need to combine the grant/deny pair into a single rights mask in
* a smart way: if either grant or deny is "empty", then we use the
* opposite as is, otherwise we remove any denied rights from the grant
* rights mask and construct an additive mask.
*/
if (ACL_IS_INVALID(tdeny)) {
modmask = tgrant | ACL_PRIV_ADDITIVE;
} else if (ACL_IS_INVALID(tgrant)) {
modmask = tdeny | ACL_PRIV_SUBSTRACTIVE;
} else {
modmask = (tgrant & ~tdeny) | ACL_PRIV_ADDITIVE;
}
} else
#endif
{
modmask = b->a_access_mask;
}
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_RESULTS,
"acl_mask: conn %d [%d] applying %s (%s)\n",
conn->c_connid, i, accessmask2str( modmask, accessmaskbuf),
b->a_type == ACL_CONTINUE ? "continue" : b->a_type == ACL_BREAK
? "break" : "stop" ));
#else
Debug( LDAP_DEBUG_ACL,
"<= acl_mask: [%d] applying %s (%s)\n",
i, accessmask2str( modmask, accessmaskbuf ),
b->a_type == ACL_CONTINUE
? "continue"
: b->a_type == ACL_BREAK
? "break"
: "stop" );
#endif
/* save old mask */
oldmask = *mask;
if( ACL_IS_ADDITIVE(modmask) ) {
/* add privs */
ACL_PRIV_SET( *mask, modmask );
/* cleanup */
ACL_PRIV_CLR( *mask, ~ACL_PRIV_MASK );
} else if( ACL_IS_SUBTRACTIVE(modmask) ) {
/* substract privs */
ACL_PRIV_CLR( *mask, modmask );
/* cleanup */
ACL_PRIV_CLR( *mask, ~ACL_PRIV_MASK );
} else {
/* assign privs */
*mask = modmask;
}
#ifdef NEW_LOGGING
LDAP_LOG(( "aci", LDAP_LEVEL_DETAIL1,
"acl_mask: conn %d [%d] mask: %s\n",
conn->c_connid, i, accessmask2str( *mask, accessmaskbuf) ));
#else
Debug( LDAP_DEBUG_ACL,
"<= acl_mask: [%d] mask: %s\n",
i, accessmask2str(*mask, accessmaskbuf), 0 );
#endif
if( b->a_type == ACL_CONTINUE ) {
continue;
} else if ( b->a_type == ACL_BREAK ) {
return ACL_BREAK;
} else {
return ACL_STOP;
}
}
/* implicit "by * none" clause */
ACL_INIT(*mask);
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_RESULTS,
"acl_mask: conn %d no more <who> clauses, returning %d (stop)\n",
conn->c_connid, accessmask2str( *mask, accessmaskbuf) ));
#else
Debug( LDAP_DEBUG_ACL,
"<= acl_mask: no more <who> clauses, returning %s (stop)\n",
accessmask2str(*mask, accessmaskbuf), 0, 0 );
#endif
return ACL_STOP;
}
/*
* acl_check_modlist - check access control on the given entry to see if
* it allows the given modifications by the user associated with op.
* returns 1 if mods allowed ok
* 0 mods not allowed
*/
int
acl_check_modlist(
Backend *be,
Connection *conn,
Operation *op,
Entry *e,
Modifications *mlist
)
{
struct berval *bv;
assert( be != NULL );
/* short circuit root database access */
if ( be_isroot( be, &op->o_ndn ) ) {
#ifdef NEW_LOGGING
LDAP_LOG(( "acl", LDAP_LEVEL_DETAIL1,
"acl_check_modlist: conn %d access granted to root user\n",
conn->c_connid ));
#else
Debug( LDAP_DEBUG_ACL,
"<= acl_access_allowed: granted to database root\n",
0, 0, 0 );
#endif
return 1;
}
/* use backend default access if no backend acls */
if( be != NULL && be->be_acl == NULL ) {
#ifdef NEW_LOGGING
LDAP_LOG(( "aci", LDAP_LEVEL_DETAIL1,
"acl_check_modlist: conn %d backend default %s access %s to \"%s\"\n",
conn->c_connid, access2str( ACL_WRITE ),
be->be_dfltaccess >= ACL_WRITE ? "granted" : "denied", op->o_dn.bv_val ));
#else
Debug( LDAP_DEBUG_ACL,
"=> access_allowed: backend default %s access %s to \"%s\"\n",
access2str( ACL_WRITE ),
be->be_dfltaccess >= ACL_WRITE ? "granted" : "denied", op->o_dn.bv_val );
#endif
return be->be_dfltaccess >= ACL_WRITE;
#ifdef notdef
/* be is always non-NULL */
/* use global default access if no global acls */
} else if ( be == NULL && global_acl == NULL ) {
#ifdef NEW_LOGGING
LDAP_LOG(( "aci", LDAP_LEVEL_DETAIL1,
"acl_check_modlist: conn %d global default %s access %s to \"%s\"\n",
conn->c_connid, access2str( ACL_WRITE ),
global_default_access >= ACL_WRITE ? "granted" : "denied", op->o_dn ));
#else
Debug( LDAP_DEBUG_ACL,
"=> access_allowed: global default %s access %s to \"%s\"\n",
access2str( ACL_WRITE ),
global_default_access >= ACL_WRITE ? "granted" : "denied", op->o_dn );
#endif
return global_default_access >= ACL_WRITE;
#endif
}
for ( ; mlist != NULL; mlist = mlist->sml_next ) {
/*
* no-user-modification operational attributes are ignored
* by ACL_WRITE checking as any found here are not provided
* by the user
*/
if ( is_at_no_user_mod( mlist->sml_desc->ad_type ) ) {
#ifdef NEW_LOGGING
LDAP_LOG(( "aci", LDAP_LEVEL_DETAIL1,
"acl_check_modlist: conn %d no-user-mod %s: modify access granted\n",
conn->c_connid, mlist->sml_desc->ad_cname.bv_val ));
#else
Debug( LDAP_DEBUG_ACL, "acl: no-user-mod %s:"
" modify access granted\n",
mlist->sml_desc->ad_cname.bv_val, 0, 0 );
#endif
continue;
}
switch ( mlist->sml_op ) {
case LDAP_MOD_REPLACE:
/*
* We must check both permission to delete the whole
* attribute and permission to add the specific attributes.
* This prevents abuse from selfwriters.
*/
if ( ! access_allowed( be, conn, op, e,
mlist->sml_desc, NULL, ACL_WRITE ) )
{
return( 0 );
}
if ( mlist->sml_bvalues == NULL ) break;
/* fall thru to check value to add */
case LDAP_MOD_ADD:
assert( mlist->sml_bvalues != NULL );
for ( bv = mlist->sml_bvalues; bv->bv_val != NULL; bv++ ) {
if ( ! access_allowed( be, conn, op, e,
mlist->sml_desc, bv, ACL_WRITE ) )
{
return( 0 );
}
}
break;
case LDAP_MOD_DELETE:
if ( mlist->sml_bvalues == NULL ) {
if ( ! access_allowed( be, conn, op, e,
mlist->sml_desc, NULL, ACL_WRITE ) )
{
return( 0 );
}
break;
}
for ( bv = mlist->sml_bvalues; bv->bv_val != NULL; bv++ ) {
if ( ! access_allowed( be, conn, op, e,
mlist->sml_desc, bv, ACL_WRITE ) )
{
return( 0 );
}
}
break;
default:
assert( 0 );
return( 0 );
}
}
return( 1 );
}
static char *
aci_bvstrdup( struct berval *bv )
{
char *s;
s = (char *)ch_malloc(bv->bv_len + 1);
if (s != NULL) {
AC_MEMCPY(s, bv->bv_val, bv->bv_len);
s[bv->bv_len] = 0;
}
return(s);
}
#ifdef SLAPD_ACI_ENABLED
static int
aci_strbvcmp(
const char *s,
struct berval *bv )
{
int res, len;
res = strncasecmp( s, bv->bv_val, bv->bv_len );
if (res)
return(res);
len = strlen(s);
if (len > (int)bv->bv_len)
return(1);
if (len < (int)bv->bv_len)
return(-1);
return(0);
}
#endif
static int
aci_get_part(
struct berval *list,
int ix,
char sep,
struct berval *bv )
{
int len;
char *p;
if (bv) {
bv->bv_len = 0;
bv->bv_val = NULL;
}
len = list->bv_len;
p = list->bv_val;
while (len >= 0 && --ix >= 0) {
while (--len >= 0 && *p++ != sep) ;
}
while (len >= 0 && *p == ' ') {
len--;
p++;
}
if (len < 0)
return(-1);
if (!bv)
return(0);
bv->bv_val = p;
while (--len >= 0 && *p != sep) {
bv->bv_len++;
p++;
}
while (bv->bv_len > 0 && *--p == ' ')
bv->bv_len--;
return(bv->bv_len);
}
BerVarray
aci_set_gather (void *cookie, char *name, struct berval *attr)
{
AciSetCookie *cp = cookie;
BerVarray bvals = NULL;
struct berval bv, ndn;
/* this routine needs to return the bervals instead of
* plain strings, since syntax is not known. It should
* also return the syntax or some "comparison cookie".
*/
bv.bv_val = name;
bv.bv_len = strlen( name );
if (dnNormalize2(NULL, &bv, &ndn) == LDAP_SUCCESS) {
const char *text;
AttributeDescription *desc = NULL;
if (slap_bv2ad(attr, &desc, &text) == LDAP_SUCCESS) {
backend_attribute(cp->be, NULL, NULL,
cp->e, &ndn, desc, &bvals);
}
free(ndn.bv_val);
}
return(bvals);
}
static int
aci_match_set (
struct berval *subj,
Backend *be,
Entry *e,
Connection *conn,
Operation *op,
int setref
)
{
struct berval set = { 0, NULL };
int rc = 0;
AciSetCookie cookie;
if (setref == 0) {
ber_dupbv( &set, subj );
} else {
struct berval subjdn, ndn = { 0, NULL };
struct berval setat;
BerVarray bvals;
const char *text;
AttributeDescription *desc = NULL;
/* format of string is "entry/setAttrName" */
if (aci_get_part(subj, 0, '/', &subjdn) < 0) {
return(0);
} else {
/* FIXME: If dnNormalize was based on ldap_bv2dn
* instead of ldap_str2dn and would honor the bv_len
* we could skip this step and not worry about the
* unterminated string.
*/
char *s = ch_malloc(subjdn.bv_len + 1);
AC_MEMCPY(s, subjdn.bv_val, subjdn.bv_len);
subjdn.bv_val = s;
}
if ( aci_get_part(subj, 1, '/', &setat) < 0 ) {
setat.bv_val = SLAPD_ACI_SET_ATTR;
setat.bv_len = sizeof(SLAPD_ACI_SET_ATTR)-1;
}
if ( setat.bv_val != NULL ) {
if ( dnNormalize2(NULL, &subjdn, &ndn) == LDAP_SUCCESS
&& slap_bv2ad(&setat, &desc, &text) == LDAP_SUCCESS )
{
backend_attribute(be, NULL, NULL, e,
&ndn, desc, &bvals);
if ( bvals != NULL ) {
if ( bvals[0].bv_val != NULL ) {
int i;
set = bvals[0];
bvals[0].bv_val = NULL;
for (i=1;bvals[i].bv_val;i++);
bvals[0].bv_val = bvals[i-1].bv_val;
bvals[i-1].bv_val = NULL;
}
ber_bvarray_free(bvals);
}
}
if (ndn.bv_val)
free(ndn.bv_val);
}
ch_free(subjdn.bv_val);
}
if (set.bv_val != NULL) {
cookie.be = be;
cookie.e = e;
cookie.conn = conn;
cookie.op = op;
rc = (slap_set_filter(aci_set_gather, &cookie, &set,
op->o_ndn.bv_val, e->e_ndn, NULL) > 0);
ch_free(set.bv_val);
}
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 <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 (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,
Backend *be,
Entry *e,
Connection *conn,
Operation *op,
regmatch_t *matches
)
{
struct berval bv;
char *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, '/', &bv) < 0) {
return(0);
}
subjdn = aci_bvstrdup(&bv);
if (subjdn == NULL) {
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 ) {
struct berval ndn;
bv.bv_val = (char *)ch_malloc(1024);
bv.bv_len = 1024;
string_expand(&bv, subjdn, e->e_ndn, matches);
if ( dnNormalize2(NULL, &bv, &ndn) == LDAP_SUCCESS ) {
rc = (backend_group(be, conn, op, e, &ndn, &op->o_ndn, grp_oc, grp_ad) == 0);
free( ndn.bv_val );
}
ch_free(bv.bv_val);
}
done:
ch_free(subjdn);
return(rc);
}
static struct berval GroupClass = {
sizeof(SLAPD_GROUP_CLASS)-1, SLAPD_GROUP_CLASS };
static struct berval GroupAttr = {
sizeof(SLAPD_GROUP_ATTR)-1, SLAPD_GROUP_ATTR };
static struct berval RoleClass = {
sizeof(SLAPD_ROLE_CLASS)-1, SLAPD_ROLE_CLASS };
static struct berval RoleAttr = {
sizeof(SLAPD_ROLE_ATTR)-1, SLAPD_ROLE_ATTR };
static int
aci_mask(
Backend *be,
Connection *conn,
Operation *op,
Entry *e,
AttributeDescription *desc,
struct berval *val,
struct berval *aci,
regmatch_t *matches,
slap_access_t *grant,
slap_access_t *deny
)
{
struct berval bv, perms, sdn;
int rc;
assert( desc->ad_cname.bv_val != NULL );
/* parse an aci of the form:
oid#scope#action;rights;attr;rights;attr$action;rights;attr;rights;attr#dnType#subjectDN
See draft-ietf-ldapext-aci-model-04.txt section 9.1 for
a full description of the format for this attribute.
For now, this routine only supports scope=entry.
*/
/* 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 is "entry" */
if (aci_get_part(aci, 1, '#', &bv) < 0
|| ber_bvstrcasecmp( &aci_bv_entry, &bv ) != 0)
{
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, '#', &bv) < 0)
return(0);
if (aci_get_part(aci, 4, '#', &sdn) < 0)
return(0);
if (ber_bvstrcasecmp( &aci_bv_access_id, &bv ) == 0) {
struct berval ndn;
rc = 1;
if ( dnNormalize2(NULL, &sdn, &ndn) == LDAP_SUCCESS ) {
if (!dn_match( &op->o_ndn, &ndn))
rc = 0;
free(ndn.bv_val);
}
return(rc);
}
if (ber_bvstrcasecmp( &aci_bv_self, &bv ) == 0) {
if (dn_match(&op->o_ndn, &e->e_nname))
return(1);
} else if (ber_bvstrcasecmp( &aci_bv_dnattr, &bv ) == 0) {
Attribute *at;
AttributeDescription *ad = NULL;
const char *text;
rc = slap_bv2ad( &sdn, &ad, &text );
if( rc != LDAP_SUCCESS ) {
return 0;
}
rc = 0;
bv = op->o_ndn;
for(at = attrs_find( e->e_attrs, ad );
at != NULL;
at = attrs_find( at->a_next, ad ) )
{
if (value_find( ad, at->a_vals, &bv) == 0 ) {
rc = 1;
break;
}
}
return rc;
} else if (ber_bvstrcasecmp( &aci_bv_group, &bv ) == 0) {
if (aci_group_member(&sdn, &GroupClass, &GroupAttr, be, e, conn, op, matches))
return(1);
} else if (ber_bvstrcasecmp( &aci_bv_role, &bv ) == 0) {
if (aci_group_member(&sdn, &RoleClass, &RoleAttr, be, e, conn, op, matches))
return(1);
} else if (ber_bvstrcasecmp( &aci_bv_set, &bv ) == 0) {
if (aci_match_set(&sdn, be, e, conn, op, 0))
return(1);
} else if (ber_bvstrcasecmp( &aci_bv_set_ref, &bv ) == 0) {
if (aci_match_set(&sdn, be, e, conn, op, 1))
return(1);
}
return(0);
}
#endif /* SLAPD_ACI_ENABLED */
static void
string_expand(
struct berval *bv,
char *pat,
char *match,
regmatch_t *matches)
{
ber_len_t size;
char *sp;
char *dp;
int flag;
size = 0;
bv->bv_val[0] = '\0';
bv->bv_len--; /* leave space for lone $ */
flag = 0;
for ( dp = bv->bv_val, sp = pat; size < bv->bv_len && *sp ; sp++) {
/* did we previously see a $ */
if (flag) {
if (*sp == '$') {
*dp++ = '$';
size++;
} else if (*sp >= '0' && *sp <= '9' ) {
int n;
int i;
int l;
n = *sp - '0';
*dp = '\0';
i = matches[n].rm_so;
l = matches[n].rm_eo;
for ( ; size < bv->bv_len && i < l; size++, i++ ) {
*dp++ = match[i];
size++;
}
*dp = '\0';
}
flag = 0;
} else {
if (*sp == '$') {
flag = 1;
} else {
*dp++ = *sp;
size++;
}
}
}
if (flag) {
/* must have ended with a single $ */
*dp++ = '$';
size++;
}
*dp = '\0';
bv->bv_len = size;
#ifdef NEW_LOGGING
LDAP_LOG(( "aci", LDAP_LEVEL_DETAIL1,
"string_expand: pattern = %s\n", pat ));
LDAP_LOG(( "aci", LDAP_LEVEL_DETAIL1,
"string_expand: expanded = %s\n", bv->bv_val ));
#else
Debug( LDAP_DEBUG_TRACE, "=> string_expand: pattern: %s\n", pat, 0, 0 );
Debug( LDAP_DEBUG_TRACE, "=> string_expand: expanded: %s\n", bv->bv_val, 0, 0 );
#endif
}
static int
regex_matches(
char *pat, /* pattern to expand and match against */
char *str, /* string to match against pattern */
char *buf, /* buffer with $N expansion variables */
regmatch_t *matches /* offsets in buffer for $N expansion variables */
)
{
regex_t re;
char newbuf[512];
struct berval bv = {sizeof(newbuf), newbuf};
int rc;
if(str == NULL) str = "";
string_expand(&bv, pat, buf, matches);
if (( rc = regcomp(&re, newbuf, REG_EXTENDED|REG_ICASE))) {
char error[512];
regerror(rc, &re, error, sizeof(error));
#ifdef NEW_LOGGING
LDAP_LOG(( "aci", LDAP_LEVEL_ERR,
"regex_matches: compile( \"%s\", \"%s\") failed %s\n",
pat, str, error ));
#else
Debug( LDAP_DEBUG_TRACE,
"compile( \"%s\", \"%s\") failed %s\n",
pat, str, error );
#endif
return( 0 );
}
rc = regexec(&re, str, 0, NULL, 0);
regfree( &re );
#ifdef NEW_LOGGING
LDAP_LOG(( "aci", LDAP_LEVEL_DETAIL2,
"regex_matches: string: %s\n", str ));
LDAP_LOG(( "aci", LDAP_LEVEL_DETAIL2,
"regex_matches: rc: %d %s\n",
rc, rc ? "matches" : "no matches" ));
#else
Debug( LDAP_DEBUG_TRACE,
"=> regex_matches: string: %s\n", str, 0, 0 );
Debug( LDAP_DEBUG_TRACE,
"=> regex_matches: rc: %d %s\n",
rc, !rc ? "matches" : "no matches", 0 );
#endif
return( !rc );
}