+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 char *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 (aci_strbvcmp(attr, &bv) == 0)
+ return(1);
+ } else if (val == NULL) {
+ if (aci_strbvcmp(attr, &left) == 0)
+ return(1);
+ } else {
+ if (aci_strbvcmp(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 (aci_strbvcmp(val->bv_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 char *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 char *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 == 0 || strcasecmp(attr, "entry") == 0) {
+ attr = "[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 (aci_strbvcmp( "grant", &actn ) == 0) {
+ mask = grant;
+ } else if (aci_strbvcmp( "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, "[all]", NULL);
+ }
+ return(found);
+}
+
+static int
+aci_group_member (
+ struct berval *subj,
+ const char *defgrpoc,
+ const char *defgrpat,
+ Backend *be,
+ Entry *e,
+ Operation *op,
+ regmatch_t *matches
+)
+{
+ struct berval bv;
+ char *subjdn, *grpdn = NULL;
+ char *grpoc;
+ char *grpat;
+ ObjectClass *grp_oc = NULL;
+ AttributeDescription *grp_ad = NULL;
+ 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, '/', &bv) < 0) {
+ grpoc = ch_strdup( defgrpoc );
+ } else {
+ grpoc = aci_bvstrdup(&bv);
+ }
+
+ if (aci_get_part(subj, 2, '/', &bv) < 0) {
+ grpat = ch_strdup( defgrpat );
+ } else {
+ grpat = aci_bvstrdup(&bv);
+ }
+
+ rc = slap_str2ad( grpat, &grp_ad, &text );
+ if( rc != LDAP_SUCCESS ) {
+ rc = 0;
+ goto done;
+ }
+ rc = 0;
+
+ grp_oc = oc_find( grpoc );
+ grpdn = (char *)ch_malloc(1024);
+
+ if (grp_oc != NULL && grp_ad != NULL && grpdn != NULL) {
+ string_expand(grpdn, 1024, subjdn, e->e_ndn, matches);
+ if ( dn_normalize(grpdn) != NULL ) {
+ rc = (backend_group(be, e, grpdn, op->o_ndn, grp_oc, grp_ad) == 0);
+ }
+ }
+
+done:
+ if( grp_ad != NULL ) ad_free( grp_ad, 1 );
+ ch_free(grpdn);
+ ch_free(grpat);
+ ch_free(grpoc);
+ ch_free(subjdn);
+ return(rc);
+}
+
+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;
+ char *subjdn;
+ int rc;
+ char *attr = desc->ad_cname->bv_val;
+
+ /* 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
+ || aci_strbvcmp( "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, attr, 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 (aci_strbvcmp( "access-id", &bv ) == 0) {
+ subjdn = aci_bvstrdup(&sdn);
+ if (subjdn == NULL)
+ return(0);
+ rc = 1;
+ if ( dn_normalize(subjdn) != NULL )
+ if (strcasecmp(op->o_ndn, subjdn) != 0)
+ rc = 0;
+ ch_free(subjdn);
+ return(rc);
+ }
+
+ if (aci_strbvcmp( "self", &bv ) == 0) {
+ if (strcasecmp(op->o_ndn, e->e_ndn) == 0)
+ return(1);
+
+ } else if (aci_strbvcmp( "dnattr", &bv ) == 0) {
+ char *dnattr = aci_bvstrdup(&sdn);
+ Attribute *at;
+ AttributeDescription *ad = NULL;
+ const char *text;
+
+ rc = slap_str2ad( dnattr, &ad, &text );
+ ch_free( dnattr );
+
+ if( rc != LDAP_SUCCESS ) {
+ return 0;
+ }
+
+ rc = 0;
+
+ bv.bv_val = op->o_ndn;
+ bv.bv_len = strlen( bv.bv_val );
+
+ 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;
+ }
+ }
+
+ ad_free( ad, 1 );
+ return rc;
+
+
+ } else if (aci_strbvcmp( "group", &bv ) == 0) {
+ if (aci_group_member(&sdn, SLAPD_GROUP_CLASS, SLAPD_GROUP_ATTR, be, e, op, matches))
+ return(1);
+
+ } else if (aci_strbvcmp( "role", &bv ) == 0) {
+ if (aci_group_member(&sdn, SLAPD_ROLE_CLASS, SLAPD_ROLE_ATTR, be, e, op, matches))
+ return(1);
+
+ }
+
+ return(0);
+}
+
+#endif /* SLAPD_ACI_ENABLED */
+