static int aci_list_get_rights( struct berval *list, struct berval *attr, struct berval *val, slap_access_t *grant, slap_access_t *deny ) { struct berval perm, actn, baseattr; slap_access_t *mask; int i, found; if ( attr == NULL || BER_BVISEMPTY( attr ) ) { attr = (struct berval *)&aci_bv[ ACI_BV_ENTRY ]; } else if ( acl_get_part( attr, 0, ';', &baseattr ) > 0 ) { attr = &baseattr; } found = 0; ACL_INIT(*grant); ACL_INIT(*deny); /* loop through each permissions clause */ for ( i = 0; acl_get_part( list, i, '$', &perm ) >= 0; i++ ) { if ( acl_get_part( &perm, 0, ';', &actn ) < 0 ) { continue; } if ( ber_bvstrcasecmp( &aci_bv[ ACI_BV_GRANT ], &actn ) == 0 ) { mask = grant; } else if ( ber_bvstrcasecmp( &aci_bv[ ACI_BV_DENY ], &actn ) == 0 ) { mask = deny; } else { continue; } *mask |= aci_list_get_attr_rights( &perm, attr, val ); *mask |= aci_list_get_attr_rights( &perm, &aci_bv[ ACI_BV_BR_ALL ], NULL ); if ( *mask != ACL_PRIV_NONE ) { found = 1; } } return found; }
static int aci_list_map_rights( struct berval *list ) { struct berval bv; slap_access_t mask; int i; ACL_INIT( mask ); for ( i = 0; acl_get_part( list, i, ',', &bv ) >= 0; i++ ) { if ( bv.bv_len <= 0 ) { continue; } switch ( *bv.bv_val ) { case 'x': /* **** NOTE: draft-ietf-ldapext-aci-model-0.3.txt does not * define any equivalent to the AUTH right, so I've just used * 'x' for now. */ ACL_PRIV_SET(mask, ACL_PRIV_AUTH); break; case 'd': /* **** NOTE: draft-ietf-ldapext-aci-model-0.3.txt defines * the right 'd' to mean "delete"; we hijack it to mean * "disclose" for consistency wuith the rest of slapd. */ ACL_PRIV_SET(mask, ACL_PRIV_DISCLOSE); break; case 'c': ACL_PRIV_SET(mask, ACL_PRIV_COMPARE); break; case 's': /* **** NOTE: draft-ietf-ldapext-aci-model-0.3.txt defines * the right 's' to mean "set", but in the examples states * that the right 's' means "search". The latter definition * is used here. */ ACL_PRIV_SET(mask, ACL_PRIV_SEARCH); break; case 'r': ACL_PRIV_SET(mask, ACL_PRIV_READ); break; case 'w': ACL_PRIV_SET(mask, ACL_PRIV_WRITE); break; default: break; } } return mask; }
static slap_access_t aci_list_get_attr_rights( struct berval *list, const struct berval *attr, struct berval *val ) { struct berval bv; slap_access_t mask; int i; /* loop through each rights/attr pair, skip first part (action) */ ACL_INIT(mask); for ( i = 1; acl_get_part( list, i + 1, ';', &bv ) >= 0; i += 2 ) { if ( aci_list_has_attr( &bv, attr, val ) == 0 ) { Debug( LDAP_DEBUG_ACL, " <= aci_list_get_attr_rights " "test %s for %s -> failed\n", bv.bv_val, attr->bv_val, 0 ); continue; } Debug( LDAP_DEBUG_ACL, " <= aci_list_get_attr_rights " "test %s for %s -> ok\n", bv.bv_val, attr->bv_val, 0 ); if ( acl_get_part( list, i, ';', &bv ) < 0 ) { Debug( LDAP_DEBUG_ACL, " <= aci_list_get_attr_rights " "test no rights\n", 0, 0, 0 ); continue; } mask |= aci_list_map_rights( &bv ); Debug( LDAP_DEBUG_ACL, " <= aci_list_get_attr_rights " "rights %s to mask 0x%x\n", bv.bv_val, mask, 0 ); } return mask; }
static int dynacl_aci_mask( void *priv, Operation *op, Entry *e, AttributeDescription *desc, struct berval *val, int nmatch, regmatch_t *matches, slap_access_t *grantp, slap_access_t *denyp ) { AttributeDescription *ad = ( AttributeDescription * )priv; Attribute *at; slap_access_t tgrant, tdeny, grant, deny; #ifdef LDAP_DEBUG char accessmaskbuf[ACCESSMASK_MAXLEN]; char accessmaskbuf1[ACCESSMASK_MAXLEN]; #endif /* LDAP_DEBUG */ if ( BER_BVISEMPTY( &e->e_nname ) ) { /* no ACIs in the root DSE */ return -1; } /* start out with nothing granted, nothing denied */ ACL_INIT(tgrant); ACL_INIT(tdeny); /* get the aci attribute */ at = attr_find( e->e_attrs, ad ); if ( at != NULL ) { int i; /* the aci is an multi-valued attribute. The * rights are determined by OR'ing the individual * rights given by the acis. */ for ( i = 0; !BER_BVISNULL( &at->a_nvals[i] ); i++ ) { if ( aci_mask( op, e, desc, val, &at->a_nvals[i], nmatch, matches, &grant, &deny, SLAP_ACI_SCOPE_ENTRY ) != 0 ) { tgrant |= grant; tdeny |= deny; } } Debug( LDAP_DEBUG_ACL, " <= aci_mask grant %s deny %s\n", accessmask2str( tgrant, accessmaskbuf, 1 ), accessmask2str( tdeny, accessmaskbuf1, 1 ) ); } /* If the entry level aci didn't contain anything valid for the * current operation, climb up the tree and evaluate the * acis with scope set to subtree */ if ( tgrant == ACL_PRIV_NONE && tdeny == ACL_PRIV_NONE ) { struct berval parent_ndn; dnParent( &e->e_nname, &parent_ndn ); while ( !BER_BVISEMPTY( &parent_ndn ) ){ int i; BerVarray bvals = NULL; int ret, stop; /* to solve the chicken'n'egg problem of accessing * the OpenLDAPaci attribute, the direct access * to the entry's attribute is unchecked; however, * further accesses to OpenLDAPaci values in the * ancestors occur through backend_attribute(), i.e. * with the identity of the operation, requiring * further access checking. For uniformity, this * makes further requests occur as the rootdn, if * any, i.e. searching for the OpenLDAPaci attribute * is considered an internal search. If this is not * acceptable, then the same check needs be performed * when accessing the entry's attribute. */ struct berval save_o_dn = {0}, save_o_ndn = {0}; if ( !BER_BVISNULL( &op->o_bd->be_rootndn ) ) { save_o_dn = op->o_dn; save_o_ndn = op->o_ndn; op->o_dn = op->o_bd->be_rootdn; op->o_ndn = op->o_bd->be_rootndn; } Debug( LDAP_DEBUG_ACL, " checking ACI of \"%s\"\n", parent_ndn.bv_val ); ret = backend_attribute( op, NULL, &parent_ndn, ad, &bvals, ACL_AUTH ); if ( !BER_BVISNULL( &op->o_bd->be_rootndn ) ) { op->o_dn = save_o_dn; op->o_ndn = save_o_ndn; } switch ( ret ) { case LDAP_SUCCESS : stop = 0; if ( !bvals ) { break; } for ( i = 0; !BER_BVISNULL( &bvals[i] ); i++ ) { if ( aci_mask( op, e, desc, val, &bvals[i], nmatch, matches, &grant, &deny, SLAP_ACI_SCOPE_CHILDREN ) != 0 ) { tgrant |= grant; tdeny |= deny; /* evaluation stops as soon as either a "deny" or a * "grant" directive matches. */ if ( tgrant != ACL_PRIV_NONE || tdeny != ACL_PRIV_NONE ) { stop = 1; } } Debug( LDAP_DEBUG_ACL, "<= aci_mask grant %s deny %s\n", accessmask2str( tgrant, accessmaskbuf, 1 ), accessmask2str( tdeny, accessmaskbuf1, 1 ) ); } 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" ); stop = 0; break; case LDAP_NO_SUCH_OBJECT: /* We have reached the base object */ Debug( LDAP_DEBUG_ACL, "no such object\n" ); stop = 1; break; default: stop = 1; break; } if ( stop ) { break; } dnParent( &parent_ndn, &parent_ndn ); } } *grantp = tgrant; *denyp = tdeny; return 0; }
static int aci_mask( Operation *op, Entry *e, AttributeDescription *desc, struct berval *val, struct berval *aci, int nmatch, regmatch_t *matches, slap_access_t *grant, slap_access_t *deny, slap_aci_scope_t asserted_scope ) { struct berval bv, scope, perms, type, opts, sdn; int rc; ACL_INIT( *grant ); ACL_INIT( *deny ); assert( !BER_BVISNULL( &desc->ad_cname ) ); /* parse an aci of the form: oid # scope # action;rights;attr;rights;attr $ action;rights;attr;rights;attr # type # subject [NOTE: the following comment is very outdated, as the draft version it refers to (Ando, 2004-11-20)]. See draft-ietf-ldapext-aci-model-04.txt section 9.1 for a full description of the format for this attribute. Differences: "this" in the draft is "self" here, and "self" and "public" is in the position of type. <scope> = {entry|children|subtree} <type> = {public|users|access-id|subtree|onelevel|children| self|dnattr|group|role|set|set-ref} This routine now supports scope={ENTRY,CHILDREN} with the semantics: - ENTRY applies to "entry" and "subtree"; - CHILDREN applies to "children" and "subtree" */ /* check that the aci has all 5 components */ if ( acl_get_part( aci, 4, '#', NULL ) < 0 ) { return 0; } /* check that the aci family is supported */ /* FIXME: the OID is ignored? */ if ( acl_get_part( aci, 0, '#', &bv ) < 0 ) { return 0; } /* check that the scope matches */ if ( acl_get_part( aci, 1, '#', &scope ) < 0 ) { return 0; } /* note: scope can be either ENTRY or CHILDREN; * they respectively match "entry" and "children" in bv * both match "subtree" */ switch ( asserted_scope ) { case SLAP_ACI_SCOPE_ENTRY: if ( ber_bvcmp( &scope, &aci_bv[ ACI_BV_ENTRY ] ) != 0 && ber_bvstrcasecmp( &scope, &aci_bv[ ACI_BV_SUBTREE ] ) != 0 ) { return 0; } break; case SLAP_ACI_SCOPE_CHILDREN: if ( ber_bvcmp( &scope, &aci_bv[ ACI_BV_CHILDREN ] ) != 0 && ber_bvstrcasecmp( &scope, &aci_bv[ ACI_BV_SUBTREE ] ) != 0 ) { return 0; } break; case SLAP_ACI_SCOPE_SUBTREE: /* TODO: add assertion? */ return 0; } /* get the list of permissions clauses, bail if empty */ if ( acl_get_part( aci, 2, '#', &perms ) <= 0 ) { LDAP_BUG(); 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 ) { LDAP_BUG(); return 0; } /* see if we have a public (i.e. anonymous) access */ if ( ber_bvcmp( &aci_bv[ ACI_BV_PUBLIC ], &type ) == 0 ) { return 1; } /* otherwise require an identity */ if ( BER_BVISNULL( &op->o_ndn ) || BER_BVISEMPTY( &op->o_ndn ) ) { return 0; } /* see if we have a users access */ if ( ber_bvcmp( &aci_bv[ ACI_BV_USERS ], &type ) == 0 ) { return 1; } /* NOTE: this may fail if a DN contains a valid '#' (unescaped); * just grab all the berval up to its end (ITS#3303). * NOTE: the problem could be solved by providing the DN with * the embedded '#' encoded as hexpairs: "cn=Foo#Bar" would * become "cn=Foo\23Bar" and be safely used by aci_mask(). */ #if 0 if ( acl_get_part( aci, 4, '#', &sdn ) < 0 ) { return 0; } #endif sdn.bv_val = type.bv_val + type.bv_len + STRLENOF( "#" ); sdn.bv_len = aci->bv_len - ( sdn.bv_val - aci->bv_val ); /* get the type options, if any */ if ( acl_get_part( &type, 1, '/', &opts ) > 0 ) { opts.bv_len = type.bv_len - ( opts.bv_val - type.bv_val ); type.bv_len = opts.bv_val - type.bv_val - 1; } else { BER_BVZERO( &opts ); } if ( ber_bvcmp( &aci_bv[ ACI_BV_ACCESS_ID ], &type ) == 0 ) { return dn_match( &op->o_ndn, &sdn ); } else if ( ber_bvcmp( &aci_bv[ ACI_BV_SUBTREE ], &type ) == 0 ) { return dnIsSuffix( &op->o_ndn, &sdn ); } else if ( ber_bvcmp( &aci_bv[ ACI_BV_ONELEVEL ], &type ) == 0 ) { struct berval pdn; dnParent( &sdn, &pdn ); return dn_match( &op->o_ndn, &pdn ); } else if ( ber_bvcmp( &aci_bv[ ACI_BV_CHILDREN ], &type ) == 0 ) { return ( !dn_match( &op->o_ndn, &sdn ) && dnIsSuffix( &op->o_ndn, &sdn ) ); } else if ( ber_bvcmp( &aci_bv[ ACI_BV_SELF ], &type ) == 0 ) { return dn_match( &op->o_ndn, &e->e_nname ); } else if ( ber_bvcmp( &aci_bv[ ACI_BV_DNATTR ], &type ) == 0 ) { Attribute *at; AttributeDescription *ad = NULL; const char *text; rc = slap_bv2ad( &sdn, &ad, &text ); assert( rc == LDAP_SUCCESS ); rc = 0; for ( at = attrs_find( e->e_attrs, ad ); at != NULL; at = attrs_find( at->a_next, ad ) ) { if ( attr_valfind( at, SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH | SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH, &op->o_ndn, NULL, op->o_tmpmemctx ) == 0 ) { rc = 1; break; } } return rc; } else if ( ber_bvcmp( &aci_bv[ ACI_BV_GROUP ], &type ) == 0 ) { struct berval oc, at; if ( BER_BVISNULL( &opts ) ) { oc = aci_bv[ ACI_BV_GROUP_CLASS ]; at = aci_bv[ ACI_BV_GROUP_ATTR ]; } else { if ( acl_get_part( &opts, 0, '/', &oc ) < 0 ) { LDAP_BUG(); } if ( acl_get_part( &opts, 1, '/', &at ) < 0 ) { at = aci_bv[ ACI_BV_GROUP_ATTR ]; } } if ( aci_group_member( &sdn, &oc, &at, op, e, nmatch, matches ) ) { return 1; } } else if ( ber_bvcmp( &aci_bv[ ACI_BV_ROLE ], &type ) == 0 ) { struct berval oc, at; if ( BER_BVISNULL( &opts ) ) { oc = aci_bv[ ACI_BV_ROLE_CLASS ]; at = aci_bv[ ACI_BV_ROLE_ATTR ]; } else { if ( acl_get_part( &opts, 0, '/', &oc ) < 0 ) { LDAP_BUG(); } if ( acl_get_part( &opts, 1, '/', &at ) < 0 ) { at = aci_bv[ ACI_BV_ROLE_ATTR ]; } } if ( aci_group_member( &sdn, &oc, &at, op, e, nmatch, matches ) ) { return 1; } } else if ( ber_bvcmp( &aci_bv[ ACI_BV_SET ], &type ) == 0 ) { if ( acl_match_set( &sdn, op, e, NULL ) ) { return 1; } } else if ( ber_bvcmp( &aci_bv[ ACI_BV_SET_REF ], &type ) == 0 ) { if ( acl_match_set( &sdn, op, e, (struct berval *)&aci_bv[ ACI_BV_SET_ATTR ] ) ) { return 1; } } else { /* it passed normalization! */ LDAP_BUG(); } return 0; }