1 /* aci.c - routines to parse and check acl's */
3 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
5 * Copyright 1998-2007 The OpenLDAP Foundation.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted only as authorized by the OpenLDAP
12 * A copy of this license is available in the file LICENSE in the
13 * top-level directory of the distribution or, alternatively, at
14 * <http://www.OpenLDAP.org/license.html>.
16 /* Portions Copyright (c) 1995 Regents of the University of Michigan.
17 * All rights reserved.
19 * Redistribution and use in source and binary forms are permitted
20 * provided that this notice is preserved and that due credit is given
21 * to the University of Michigan at Ann Arbor. The name of the University
22 * may not be used to endorse or promote products derived from this
23 * software without specific prior written permission. This software
24 * is provided ``as is'' without express or implied warranty.
29 #ifdef SLAPD_ACI_ENABLED
35 #include <ac/socket.h>
36 #include <ac/string.h>
37 #include <ac/unistd.h>
43 #define ACI_BUF_SIZE 1024 /* use most appropriate size */
77 static const struct berval aci_bv[] = {
104 BER_BVC(SLAPD_GROUP_CLASS),
105 BER_BVC(SLAPD_GROUP_ATTR),
106 BER_BVC(SLAPD_ROLE_CLASS),
107 BER_BVC(SLAPD_ROLE_ATTR),
109 BER_BVC(SLAPD_ACI_SET_ATTR),
116 #endif /* SLAP_DYNACL */
117 AttributeDescription *slap_ad_aci;
122 struct berval *val );
132 OpenLDAPaciNormalize(
140 #define OpenLDAPaciMatch octetStringMatch
144 struct berval *list )
151 for ( i = 0; acl_get_part( list, i, ',', &bv ) >= 0; i++ ) {
152 if ( bv.bv_len <= 0 ) {
156 switch ( *bv.bv_val ) {
158 /* **** NOTE: draft-ietf-ldapext-aci-model-0.3.txt does not
159 * define any equivalent to the AUTH right, so I've just used
162 ACL_PRIV_SET(mask, ACL_PRIV_AUTH);
165 /* **** NOTE: draft-ietf-ldapext-aci-model-0.3.txt defines
166 * the right 'd' to mean "delete"; we hijack it to mean
167 * "disclose" for consistency wuith the rest of slapd.
169 ACL_PRIV_SET(mask, ACL_PRIV_DISCLOSE);
172 ACL_PRIV_SET(mask, ACL_PRIV_COMPARE);
175 /* **** NOTE: draft-ietf-ldapext-aci-model-0.3.txt defines
176 * the right 's' to mean "set", but in the examples states
177 * that the right 's' means "search". The latter definition
180 ACL_PRIV_SET(mask, ACL_PRIV_SEARCH);
183 ACL_PRIV_SET(mask, ACL_PRIV_READ);
186 ACL_PRIV_SET(mask, ACL_PRIV_WRITE);
200 const struct berval *attr,
203 struct berval bv, left, right;
206 for ( i = 0; acl_get_part( list, i, ',', &bv ) >= 0; i++ ) {
207 if ( acl_get_part(&bv, 0, '=', &left ) < 0
208 || acl_get_part( &bv, 1, '=', &right ) < 0 )
210 if ( ber_bvstrcasecmp( attr, &bv ) == 0 ) {
214 } else if ( val == NULL ) {
215 if ( ber_bvstrcasecmp( attr, &left ) == 0 ) {
220 if ( ber_bvstrcasecmp( attr, &left ) == 0 ) {
221 /* FIXME: this is also totally undocumented! */
222 /* this is experimental code that implements a
223 * simple (prefix) match of the attribute value.
224 * the ACI draft does not provide for aci's that
225 * apply to specific values, but it would be
226 * nice to have. If the <attr> part of an aci's
227 * rights list is of the form <attr>=<value>,
228 * that means the aci applies only to attrs with
229 * the given value. Furthermore, if the attr is
230 * of the form <attr>=<value>*, then <value> is
231 * treated as a prefix, and the aci applies to
232 * any value with that prefix.
234 * Ideally, this would allow r.e. matches.
236 if ( acl_get_part( &right, 0, '*', &left ) < 0
237 || right.bv_len <= left.bv_len )
239 if ( ber_bvstrcasecmp( val, &right ) == 0 ) {
243 } else if ( val->bv_len >= left.bv_len ) {
244 if ( strncasecmp( val->bv_val, left.bv_val, left.bv_len ) == 0 ) {
256 aci_list_get_attr_rights(
258 const struct berval *attr,
265 /* loop through each rights/attr pair, skip first part (action) */
267 for ( i = 1; acl_get_part( list, i + 1, ';', &bv ) >= 0; i += 2 ) {
268 if ( aci_list_has_attr( &bv, attr, val ) == 0 ) {
272 if ( acl_get_part( list, i, ';', &bv ) < 0 ) {
276 mask |= aci_list_map_rights( &bv );
285 const struct berval *attr,
287 slap_access_t *grant,
288 slap_access_t *deny )
290 struct berval perm, actn;
294 if ( attr == NULL || BER_BVISEMPTY( attr ) ) {
295 attr = &aci_bv[ ACI_BV_ENTRY ];
301 /* loop through each permissions clause */
302 for ( i = 0; acl_get_part( list, i, '$', &perm ) >= 0; i++ ) {
303 if ( acl_get_part( &perm, 0, ';', &actn ) < 0 ) {
307 if ( ber_bvstrcasecmp( &aci_bv[ ACI_BV_GRANT ], &actn ) == 0 ) {
310 } else if ( ber_bvstrcasecmp( &aci_bv[ ACI_BV_DENY ], &actn ) == 0 ) {
318 *mask |= aci_list_get_attr_rights( &perm, attr, val );
319 *mask |= aci_list_get_attr_rights( &perm, &aci_bv[ ACI_BV_BR_ALL ], NULL );
328 const struct berval *defgrpoc,
329 const struct berval *defgrpat,
336 struct berval subjdn;
339 ObjectClass *grp_oc = NULL;
340 AttributeDescription *grp_ad = NULL;
344 /* format of string is "{group|role}/objectClassValue/groupAttrName" */
345 if ( acl_get_part( subj, 0, '/', &subjdn ) < 0 ) {
349 if ( acl_get_part( subj, 1, '/', &grpoc ) < 0 ) {
353 if ( acl_get_part( subj, 2, '/', &grpat ) < 0 ) {
357 rc = slap_bv2ad( &grpat, &grp_ad, &text );
358 if ( rc != LDAP_SUCCESS ) {
364 grp_oc = oc_bvfind( &grpoc );
366 if ( grp_oc != NULL && grp_ad != NULL ) {
367 char buf[ ACI_BUF_SIZE ];
368 struct berval bv, ndn;
370 bv.bv_len = sizeof( buf ) - 1;
371 bv.bv_val = (char *)&buf;
372 if ( acl_string_expand( &bv, &subjdn,
373 e->e_ndn, nmatch, matches ) )
379 if ( dnNormalize( 0, NULL, NULL, &bv, &ndn, op->o_tmpmemctx ) == LDAP_SUCCESS )
381 rc = ( backend_group( op, e, &ndn, &op->o_ndn,
382 grp_oc, grp_ad ) == 0 );
383 slap_sl_free( ndn.bv_val, op->o_tmpmemctx );
395 AttributeDescription *desc,
400 slap_access_t *grant,
402 slap_aci_scope_t asserted_scope )
413 assert( !BER_BVISNULL( &desc->ad_cname ) );
415 /* parse an aci of the form:
416 oid # scope # action;rights;attr;rights;attr
417 $ action;rights;attr;rights;attr # type # subject
419 [NOTE: the following comment is very outdated,
420 as the draft version it refers to (Ando, 2004-11-20)].
422 See draft-ietf-ldapext-aci-model-04.txt section 9.1 for
423 a full description of the format for this attribute.
424 Differences: "this" in the draft is "self" here, and
425 "self" and "public" is in the position of type.
427 <scope> = {entry|children|subtree}
428 <type> = {public|users|access-id|subtree|onelevel|children|
429 self|dnattr|group|role|set|set-ref}
431 This routine now supports scope={ENTRY,CHILDREN}
433 - ENTRY applies to "entry" and "subtree";
434 - CHILDREN applies to "children" and "subtree"
437 /* check that the aci has all 5 components */
438 if ( acl_get_part( aci, 4, '#', NULL ) < 0 ) {
442 /* check that the aci family is supported */
443 /* FIXME: the OID is ignored? */
444 if ( acl_get_part( aci, 0, '#', &bv ) < 0 ) {
448 /* check that the scope matches */
449 if ( acl_get_part( aci, 1, '#', &scope ) < 0 ) {
453 /* note: scope can be either ENTRY or CHILDREN;
454 * they respectively match "entry" and "children" in bv
455 * both match "subtree" */
456 switch ( asserted_scope ) {
457 case SLAP_ACI_SCOPE_ENTRY:
458 if ( ber_bvcmp( &scope, &aci_bv[ ACI_BV_ENTRY ] ) != 0
459 && ber_bvstrcasecmp( &scope, &aci_bv[ ACI_BV_SUBTREE ] ) != 0 )
465 case SLAP_ACI_SCOPE_CHILDREN:
466 if ( ber_bvcmp( &scope, &aci_bv[ ACI_BV_CHILDREN ] ) != 0
467 && ber_bvstrcasecmp( &scope, &aci_bv[ ACI_BV_SUBTREE ] ) != 0 )
473 case SLAP_ACI_SCOPE_SUBTREE:
474 /* TODO: add assertion? */
478 /* get the list of permissions clauses, bail if empty */
479 if ( acl_get_part( aci, 2, '#', &perms ) <= 0 ) {
484 /* check if any permissions allow desired access */
485 if ( aci_list_get_rights( &perms, &desc->ad_cname, val, grant, deny ) == 0 ) {
489 /* see if we have a DN match */
490 if ( acl_get_part( aci, 3, '#', &type ) < 0 ) {
495 /* see if we have a public (i.e. anonymous) access */
496 if ( ber_bvcmp( &aci_bv[ ACI_BV_PUBLIC ], &type ) == 0 ) {
500 /* otherwise require an identity */
501 if ( BER_BVISNULL( &op->o_ndn ) || BER_BVISEMPTY( &op->o_ndn ) ) {
505 /* see if we have a users access */
506 if ( ber_bvcmp( &aci_bv[ ACI_BV_USERS ], &type ) == 0 ) {
510 /* NOTE: this may fail if a DN contains a valid '#' (unescaped);
511 * just grab all the berval up to its end (ITS#3303).
512 * NOTE: the problem could be solved by providing the DN with
513 * the embedded '#' encoded as hexpairs: "cn=Foo#Bar" would
514 * become "cn=Foo\23Bar" and be safely used by aci_mask(). */
516 if ( acl_get_part( aci, 4, '#', &sdn ) < 0 ) {
520 sdn.bv_val = type.bv_val + type.bv_len + STRLENOF( "#" );
521 sdn.bv_len = aci->bv_len - ( sdn.bv_val - aci->bv_val );
523 /* get the type options, if any */
524 if ( acl_get_part( &type, 1, '/', &opts ) > 0 ) {
525 opts.bv_len = type.bv_len - ( opts.bv_val - type.bv_val );
526 type.bv_len = opts.bv_val - type.bv_val - 1;
532 if ( ber_bvcmp( &aci_bv[ ACI_BV_ACCESS_ID ], &type ) == 0 ) {
533 return dn_match( &op->o_ndn, &sdn );
535 } else if ( ber_bvcmp( &aci_bv[ ACI_BV_SUBTREE ], &type ) == 0 ) {
536 return dnIsSuffix( &op->o_ndn, &sdn );
538 } else if ( ber_bvcmp( &aci_bv[ ACI_BV_ONELEVEL ], &type ) == 0 ) {
541 dnParent( &sdn, &pdn );
543 return dn_match( &op->o_ndn, &pdn );
545 } else if ( ber_bvcmp( &aci_bv[ ACI_BV_CHILDREN ], &type ) == 0 ) {
546 return ( !dn_match( &op->o_ndn, &sdn ) && dnIsSuffix( &op->o_ndn, &sdn ) );
548 } else if ( ber_bvcmp( &aci_bv[ ACI_BV_SELF ], &type ) == 0 ) {
549 return dn_match( &op->o_ndn, &e->e_nname );
551 } else if ( ber_bvcmp( &aci_bv[ ACI_BV_DNATTR ], &type ) == 0 ) {
553 AttributeDescription *ad = NULL;
556 rc = slap_bv2ad( &sdn, &ad, &text );
557 assert( rc == LDAP_SUCCESS );
560 for ( at = attrs_find( e->e_attrs, ad );
562 at = attrs_find( at->a_next, ad ) )
564 if ( value_find_ex( ad,
565 SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH |
566 SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH,
568 &op->o_ndn, op->o_tmpmemctx ) == 0 )
577 } else if ( ber_bvcmp( &aci_bv[ ACI_BV_GROUP ], &type ) == 0 ) {
581 if ( BER_BVISNULL( &opts ) ) {
582 oc = aci_bv[ ACI_BV_GROUP_CLASS ];
583 at = aci_bv[ ACI_BV_GROUP_ATTR ];
586 if ( acl_get_part( &opts, 0, '/', &oc ) < 0 ) {
590 if ( acl_get_part( &opts, 1, '/', &at ) < 0 ) {
591 at = aci_bv[ ACI_BV_GROUP_ATTR ];
595 if ( aci_group_member( &sdn, &oc, &at, op, e, nmatch, matches ) )
600 } else if ( ber_bvcmp( &aci_bv[ ACI_BV_ROLE ], &type ) == 0 ) {
604 if ( BER_BVISNULL( &opts ) ) {
605 oc = aci_bv[ ACI_BV_ROLE_CLASS ];
606 at = aci_bv[ ACI_BV_ROLE_ATTR ];
609 if ( acl_get_part( &opts, 0, '/', &oc ) < 0 ) {
613 if ( acl_get_part( &opts, 1, '/', &at ) < 0 ) {
614 at = aci_bv[ ACI_BV_ROLE_ATTR ];
618 if ( aci_group_member( &sdn, &oc, &at, op, e, nmatch, matches ) )
623 } else if ( ber_bvcmp( &aci_bv[ ACI_BV_SET ], &type ) == 0 ) {
624 if ( acl_match_set( &sdn, op, e, NULL ) ) {
628 } else if ( ber_bvcmp( &aci_bv[ ACI_BV_SET_REF ], &type ) == 0 ) {
629 if ( acl_match_set( &sdn, op, e, (struct berval *)&aci_bv[ ACI_BV_SET_ATTR ] ) ) {
634 /* it passed normalization! */
644 /* OpenLDAP eXperimental Syntax */
645 static slap_syntax_defs_rec aci_syntax_def = {
646 "( 1.3.6.1.4.1.4203.666.2.1 DESC 'OpenLDAP Experimental ACI' )",
651 static slap_mrule_defs_rec aci_mr_def = {
652 "( 1.3.6.1.4.1.4203.666.4.2 NAME 'OpenLDAPaciMatch' "
653 "SYNTAX 1.3.6.1.4.1.4203.666.2.1 )",
654 SLAP_MR_HIDE | SLAP_MR_EQUALITY, NULL,
655 NULL, OpenLDAPaciNormalize, OpenLDAPaciMatch,
663 AttributeDescription **ad;
665 "OpenLDAPaci", "( 1.3.6.1.4.1.4203.666.1.5 "
666 "NAME 'OpenLDAPaci' "
667 "DESC 'OpenLDAP access control information (experimental)' "
668 "EQUALITY OpenLDAPaciMatch "
669 "SYNTAX 1.3.6.1.4.1.4203.666.2.1 "
670 "USAGE directoryOperation )",
675 LDAPAttributeType *at;
681 rc = register_syntax( &aci_syntax_def );
686 /* ACI equality rule */
687 rc = register_matching_rule( &aci_mr_def );
693 at = ldap_str2attributetype( aci_at.desc,
694 &rc, &text, LDAP_SCHEMA_ALLOW_ALL );
696 Debug( LDAP_DEBUG_ANY,
697 "aci_init: AttributeType \"%s\" parse failed: %s %s\n",
698 aci_at.name, ldap_scherr2str( rc ), text );
702 rc = at_add( at, 0, &sat, &text );
703 if ( rc != LDAP_SUCCESS ) {
704 ldap_attributetype_free( at );
705 Debug( LDAP_DEBUG_ANY,
706 "aci_init: AttributeType \"%s\" load failed: %s %s\n",
707 aci_at.name, scherr2str( rc ), text );
712 rc = slap_str2ad( aci_at.name,
714 if ( rc != LDAP_SUCCESS ) {
715 Debug( LDAP_DEBUG_ANY,
716 "aci_init: unable to find AttributeDescription "
718 aci_at.name, rc, text );
723 sat->sat_flags |= aci_at.flags;
738 AttributeDescription *ad = NULL;
739 const char *text = NULL;
741 if ( sty != ACL_STYLE_REGEX && sty != ACL_STYLE_BASE ) {
742 fprintf( stderr, "%s: line %d: "
743 "inappropriate style \"%s\" in \"aci\" by clause\n",
744 fname, lineno, style_strings[sty] );
748 if ( right != NULL && *right != '\0' ) {
749 if ( slap_str2ad( right, &ad, &text ) != LDAP_SUCCESS ) {
751 "%s: line %d: aci \"%s\": %s\n",
752 fname, lineno, right, text );
760 if ( !is_at_syntax( ad->ad_type, SLAPD_ACI_SYNTAX) ) {
761 fprintf( stderr, "%s: line %d: "
762 "aci \"%s\": inappropriate syntax: %s\n",
763 fname, lineno, right,
764 ad->ad_type->sat_syntax_oid );
774 dynacl_aci_unparse( void *priv, struct berval *bv )
776 AttributeDescription *ad = ( AttributeDescription * )priv;
779 assert( ad != NULL );
781 bv->bv_val = ch_malloc( STRLENOF(" aci=") + ad->ad_cname.bv_len + 1 );
782 ptr = lutil_strcopy( bv->bv_val, " aci=" );
783 ptr = lutil_strcopy( ptr, ad->ad_cname.bv_val );
784 bv->bv_len = ptr - bv->bv_val;
794 AttributeDescription *desc,
798 slap_access_t *grantp,
799 slap_access_t *denyp )
801 AttributeDescription *ad = ( AttributeDescription * )priv;
803 slap_access_t tgrant, tdeny, grant, deny;
805 char accessmaskbuf[ACCESSMASK_MAXLEN];
806 char accessmaskbuf1[ACCESSMASK_MAXLEN];
807 #endif /* LDAP_DEBUG */
809 if ( BER_BVISEMPTY( &e->e_nname ) ) {
810 /* no ACIs in the root DSE */
814 /* start out with nothing granted, nothing denied */
818 /* get the aci attribute */
819 at = attr_find( e->e_attrs, ad );
823 /* the aci is an multi-valued attribute. The
824 * rights are determined by OR'ing the individual
825 * rights given by the acis.
827 for ( i = 0; !BER_BVISNULL( &at->a_nvals[i] ); i++ ) {
828 if ( aci_mask( op, e, desc, val, &at->a_nvals[i],
829 nmatch, matches, &grant, &deny,
830 SLAP_ACI_SCOPE_ENTRY ) != 0 )
837 Debug( LDAP_DEBUG_ACL, " <= aci_mask grant %s deny %s\n",
838 accessmask2str( tgrant, accessmaskbuf, 1 ),
839 accessmask2str( tdeny, accessmaskbuf1, 1 ), 0 );
842 /* If the entry level aci didn't contain anything valid for the
843 * current operation, climb up the tree and evaluate the
844 * acis with scope set to subtree
846 if ( tgrant == ACL_PRIV_NONE && tdeny == ACL_PRIV_NONE ) {
847 struct berval parent_ndn;
849 dnParent( &e->e_nname, &parent_ndn );
850 while ( !BER_BVISEMPTY( &parent_ndn ) ){
852 BerVarray bvals = NULL;
855 /* to solve the chicken'n'egg problem of accessing
856 * the OpenLDAPaci attribute, the direct access
857 * to the entry's attribute is unchecked; however,
858 * further accesses to OpenLDAPaci values in the
859 * ancestors occur through backend_attribute(), i.e.
860 * with the identity of the operation, requiring
861 * further access checking. For uniformity, this
862 * makes further requests occur as the rootdn, if
863 * any, i.e. searching for the OpenLDAPaci attribute
864 * is considered an internal search. If this is not
865 * acceptable, then the same check needs be performed
866 * when accessing the entry's attribute. */
867 struct berval save_o_dn, save_o_ndn;
869 if ( !BER_BVISNULL( &op->o_bd->be_rootndn ) ) {
870 save_o_dn = op->o_dn;
871 save_o_ndn = op->o_ndn;
873 op->o_dn = op->o_bd->be_rootdn;
874 op->o_ndn = op->o_bd->be_rootndn;
877 Debug( LDAP_DEBUG_ACL, " checking ACI of \"%s\"\n", parent_ndn.bv_val, 0, 0 );
878 ret = backend_attribute( op, NULL, &parent_ndn, ad, &bvals, ACL_AUTH );
880 if ( !BER_BVISNULL( &op->o_bd->be_rootndn ) ) {
881 op->o_dn = save_o_dn;
882 op->o_ndn = save_o_ndn;
892 for ( i = 0; !BER_BVISNULL( &bvals[i] ); i++ ) {
893 if ( aci_mask( op, e, desc, val,
897 SLAP_ACI_SCOPE_CHILDREN ) != 0 )
901 /* evaluation stops as soon as either a "deny" or a
902 * "grant" directive matches.
904 if ( tgrant != ACL_PRIV_NONE || tdeny != ACL_PRIV_NONE ) {
908 Debug( LDAP_DEBUG_ACL, "<= aci_mask grant %s deny %s\n",
909 accessmask2str( tgrant, accessmaskbuf, 1 ),
910 accessmask2str( tdeny, accessmaskbuf1, 1 ), 0 );
914 case LDAP_NO_SUCH_ATTRIBUTE:
915 /* just go on if the aci-Attribute is not present in
918 Debug( LDAP_DEBUG_ACL, "no such attribute\n", 0, 0, 0 );
922 case LDAP_NO_SUCH_OBJECT:
923 /* We have reached the base object */
924 Debug( LDAP_DEBUG_ACL, "no such object\n", 0, 0, 0 );
936 dnParent( &parent_ndn, &parent_ndn );
946 /* need to register this at some point */
947 static slap_dynacl_t dynacl_aci = {
958 dynacl_aci_init( void )
965 rc = slap_dynacl_register( &dynacl_aci );
971 #endif /* SLAP_DYNACL */
973 /* ACI syntax validation */
976 * Matches given berval to array of bervals
978 * >=0 if one if the array elements equals to this berval
979 * -1 if string was not found in array
984 const struct berval *arr[] )
988 if ( BER_BVISEMPTY( bv ) ) {
992 for ( i = 0; arr[ i ] != NULL ; i++ ) {
993 if ( ber_bvstrcasecmp( bv, arr[ i ] ) == 0 ) {
1002 /* Returns what have left in input berval after current sub */
1007 struct berval *tail )
1011 tail->bv_val = sub->bv_val + sub->bv_len;
1012 head_len = (unsigned long) tail->bv_val - (unsigned long) val->bv_val;
1013 tail->bv_len = val->bv_len - head_len;
1018 * aci is accepted in following form:
1019 * oid#scope#rights#type#subject
1021 * oid := numeric OID
1022 * scope := entry|children
1023 * rights := right[[$right]...]
1024 * right := (grant|deny);action
1025 * action := perms;attr[[;perms;attr]...]
1026 * perms := perm[[,perm]...]
1028 * attr := attributeType|"[all]"
1029 * type := public|users|self|dnattr|group|role|set|set-ref|
1030 * access_id|subtree|onelevel|children
1033 OpenLDAPaciValidatePerms(
1034 struct berval *perms )
1038 for ( i = 0; i < perms->bv_len; ) {
1039 switch ( perms->bv_val[ i ] ) {
1049 return LDAP_INVALID_SYNTAX;
1052 if ( ++i == perms->bv_len ) {
1053 return LDAP_SUCCESS;
1056 while ( i < perms->bv_len && perms->bv_val[ i ] == ' ' )
1059 assert( i != perms->bv_len );
1061 if ( perms->bv_val[ i ] != ',' ) {
1062 return LDAP_INVALID_SYNTAX;
1067 } while ( perms->bv_val[ i ] == ' ' );
1070 return LDAP_SUCCESS;
1073 static const struct berval *ACIgrantdeny[] = {
1074 &aci_bv[ ACI_BV_GRANT ],
1075 &aci_bv[ ACI_BV_DENY ],
1080 OpenLDAPaciValidateRight(
1081 struct berval *action )
1083 struct berval bv = BER_BVNULL;
1087 if ( acl_get_part( action, 0, ';', &bv ) < 0 ||
1088 bv_getcaseidx( &bv, ACIgrantdeny ) == -1 )
1090 return LDAP_INVALID_SYNTAX;
1093 for ( i = 0; acl_get_part( action, i + 1, ';', &bv ) >= 0; i++ ) {
1096 if ( OpenLDAPaciValidatePerms( &bv ) != LDAP_SUCCESS )
1098 return LDAP_INVALID_SYNTAX;
1103 AttributeDescription *ad = NULL;
1104 const char *text = NULL;
1106 /* could be "[all]" or an attribute description */
1107 if ( ber_bvstrcasecmp( &bv, &aci_bv[ ACI_BV_BR_ALL ] ) == 0 ) {
1111 /* "[entry]" is tolerated for backward compatibility */
1112 if ( ber_bvstrcasecmp( &bv, &aci_bv[ ACI_BV_BR_ENTRY ] ) == 0 ) {
1116 if ( slap_bv2ad( &bv, &ad, &text ) != LDAP_SUCCESS ) {
1117 return LDAP_INVALID_SYNTAX;
1122 /* "perms;attr" go in pairs */
1123 if ( i > 0 && ( i & 1 ) == 0 ) {
1124 return LDAP_SUCCESS;
1127 return LDAP_INVALID_SYNTAX;
1130 return LDAP_SUCCESS;
1134 OpenLDAPaciNormalizeRight(
1135 struct berval *action,
1136 struct berval *naction,
1139 struct berval grantdeny,
1146 if ( acl_get_part( action, 0, ';', &grantdeny ) < 0 ) {
1147 return LDAP_INVALID_SYNTAX;
1149 idx = bv_getcaseidx( &grantdeny, ACIgrantdeny );
1151 return LDAP_INVALID_SYNTAX;
1154 ber_dupbv_x( naction, (struct berval *)ACIgrantdeny[ idx ], ctx );
1156 for ( i = 1; acl_get_part( action, i, ';', &bv ) >= 0; i++ ) {
1159 if ( OpenLDAPaciValidatePerms( &bv ) != LDAP_SUCCESS )
1161 return LDAP_INVALID_SYNTAX;
1169 /* could be "[all]" or an attribute description */
1170 if ( ber_bvstrcasecmp( &bv, &aci_bv[ ACI_BV_BR_ALL ] ) == 0 ) {
1171 bv = aci_bv[ ACI_BV_BR_ALL ];
1173 /* "[entry]" is tolerated for backward compatibility */
1174 } else if ( ber_bvstrcasecmp( &bv, &aci_bv[ ACI_BV_BR_ENTRY ] ) == 0 ) {
1175 bv = aci_bv[ ACI_BV_ENTRY ];
1178 AttributeDescription *ad = NULL;
1179 const char *text = NULL;
1182 rc = slap_bv2ad( &bv, &ad, &text );
1183 if ( rc != LDAP_SUCCESS ) {
1184 return LDAP_INVALID_SYNTAX;
1190 naction->bv_val = ber_memrealloc_x( naction->bv_val,
1191 naction->bv_len + STRLENOF( ";" )
1192 + perms.bv_len + STRLENOF( ";" )
1196 ptr = &naction->bv_val[ naction->bv_len ];
1199 ptr = lutil_strncopy( ptr, perms.bv_val, perms.bv_len );
1202 ptr = lutil_strncopy( ptr, bv.bv_val, bv.bv_len );
1204 naction->bv_len += STRLENOF( ";" ) + perms.bv_len
1205 + STRLENOF( ";" ) + bv.bv_len;
1209 /* perms;attr go in pairs */
1210 if ( i > 1 && ( i & 1 ) ) {
1211 return LDAP_SUCCESS;
1214 return LDAP_INVALID_SYNTAX;
1219 OpenLDAPaciValidateRights(
1220 struct berval *actions )
1223 struct berval bv = BER_BVNULL;
1226 for ( i = 0; acl_get_part( actions, i, '$', &bv ) >= 0; i++ ) {
1227 if ( OpenLDAPaciValidateRight( &bv ) != LDAP_SUCCESS ) {
1228 return LDAP_INVALID_SYNTAX;
1232 return LDAP_SUCCESS;
1236 OpenLDAPaciNormalizeRights(
1237 struct berval *actions,
1238 struct berval *nactions,
1242 struct berval bv = BER_BVNULL;
1245 BER_BVZERO( nactions );
1246 for ( i = 0; acl_get_part( actions, i, '$', &bv ) >= 0; i++ ) {
1250 rc = OpenLDAPaciNormalizeRight( &bv, &nbv, ctx );
1251 if ( rc != LDAP_SUCCESS ) {
1252 ber_memfree_x( nactions->bv_val, ctx );
1253 BER_BVZERO( nactions );
1254 return LDAP_INVALID_SYNTAX;
1261 nactions->bv_val = ber_memrealloc_x( nactions->bv_val,
1262 nactions->bv_len + STRLENOF( "$" )
1265 nactions->bv_val[ nactions->bv_len ] = '$';
1266 AC_MEMCPY( &nactions->bv_val[ nactions->bv_len + 1 ],
1267 nbv.bv_val, nbv.bv_len + 1 );
1268 ber_memfree_x( nbv.bv_val, ctx );
1269 nactions->bv_len += STRLENOF( "$" ) + nbv.bv_len;
1274 return LDAP_SUCCESS;
1277 static const struct berval *OpenLDAPaciscopes[] = {
1278 &aci_bv[ ACI_BV_ENTRY ],
1279 &aci_bv[ ACI_BV_CHILDREN ],
1280 &aci_bv[ ACI_BV_SUBTREE ],
1285 static const struct berval *OpenLDAPacitypes[] = {
1287 &aci_bv[ ACI_BV_GROUP ],
1288 &aci_bv[ ACI_BV_ROLE ],
1290 /* set to one past the last DN-valued type with options (/) */
1291 #define LAST_OPTIONAL 2
1293 &aci_bv[ ACI_BV_ACCESS_ID ],
1294 &aci_bv[ ACI_BV_SUBTREE ],
1295 &aci_bv[ ACI_BV_ONELEVEL ],
1296 &aci_bv[ ACI_BV_CHILDREN ],
1298 /* set to one past the last DN-valued type */
1299 #define LAST_DNVALUED 6
1302 &aci_bv[ ACI_BV_DNATTR ],
1303 &aci_bv[ ACI_BV_PUBLIC ],
1304 &aci_bv[ ACI_BV_USERS ],
1305 &aci_bv[ ACI_BV_SELF ],
1306 &aci_bv[ ACI_BV_SET ],
1307 &aci_bv[ ACI_BV_SET_REF ],
1313 OpenLDAPaciValidate(
1315 struct berval *val )
1317 struct berval oid = BER_BVNULL,
1319 rights = BER_BVNULL,
1321 subject = BER_BVNULL;
1324 if ( BER_BVISEMPTY( val ) ) {
1325 return LDAP_INVALID_SYNTAX;
1329 if ( acl_get_part( val, 0, '#', &oid ) < 0 ||
1330 numericoidValidate( NULL, &oid ) != LDAP_SUCCESS )
1332 /* NOTE: the numericoidValidate() is rather pedantic;
1333 * I'd replace it with X-ORDERED VALUES so that
1334 * it's guaranteed values are maintained and used
1335 * in the desired order */
1336 return LDAP_INVALID_SYNTAX;
1340 if ( acl_get_part( val, 1, '#', &scope ) < 0 ||
1341 bv_getcaseidx( &scope, OpenLDAPaciscopes ) == -1 )
1343 return LDAP_INVALID_SYNTAX;
1347 if ( acl_get_part( val, 2, '#', &rights ) < 0 ||
1348 OpenLDAPaciValidateRights( &rights ) != LDAP_SUCCESS )
1350 return LDAP_INVALID_SYNTAX;
1354 if ( acl_get_part( val, 3, '#', &type ) < 0 ) {
1355 return LDAP_INVALID_SYNTAX;
1357 idx = bv_getcaseidx( &type, OpenLDAPacitypes );
1361 if ( acl_get_part( &type, 0, '/', &isgr ) < 0 ) {
1362 return LDAP_INVALID_SYNTAX;
1365 idx = bv_getcaseidx( &isgr, OpenLDAPacitypes );
1366 if ( idx == -1 || idx >= LAST_OPTIONAL ) {
1367 return LDAP_INVALID_SYNTAX;
1372 bv_get_tail( val, &type, &subject );
1373 if ( subject.bv_val[ 0 ] != '#' ) {
1374 return LDAP_INVALID_SYNTAX;
1377 if ( idx >= LAST_DNVALUED ) {
1378 if ( OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_DNATTR ] ) {
1379 AttributeDescription *ad = NULL;
1380 const char *text = NULL;
1383 rc = slap_bv2ad( &subject, &ad, &text );
1384 if ( rc != LDAP_SUCCESS ) {
1385 return LDAP_INVALID_SYNTAX;
1388 if ( ad->ad_type->sat_syntax != slap_schema.si_syn_distinguishedName ) {
1389 /* FIXME: allow nameAndOptionalUID? */
1390 return LDAP_INVALID_SYNTAX;
1395 return LDAP_SUCCESS;
1397 } else if ( OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_GROUP ]
1398 || OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_ROLE ] )
1400 /* do {group|role}/oc/at check */
1401 struct berval ocbv = BER_BVNULL,
1404 ocbv.bv_val = ber_bvchr( &type, '/' );
1405 if ( ocbv.bv_val != NULL ) {
1407 ocbv.bv_len = type.bv_len
1408 - ( ocbv.bv_val - type.bv_val );
1410 atbv.bv_val = ber_bvchr( &ocbv, '/' );
1411 if ( atbv.bv_val != NULL ) {
1412 AttributeDescription *ad = NULL;
1413 const char *text = NULL;
1417 atbv.bv_len = type.bv_len
1418 - ( atbv.bv_val - type.bv_val );
1419 ocbv.bv_len = atbv.bv_val - ocbv.bv_val - 1;
1421 rc = slap_bv2ad( &atbv, &ad, &text );
1422 if ( rc != LDAP_SUCCESS ) {
1423 return LDAP_INVALID_SYNTAX;
1427 if ( oc_bvfind( &ocbv ) == NULL ) {
1428 return LDAP_INVALID_SYNTAX;
1433 if ( BER_BVISEMPTY( &subject ) ) {
1434 /* empty DN invalid */
1435 return LDAP_INVALID_SYNTAX;
1441 /* FIXME: pass DN syntax? */
1442 return dnValidate( NULL, &subject );
1446 OpenLDAPaciPrettyNormal(
1452 struct berval oid = BER_BVNULL,
1454 rights = BER_BVNULL,
1455 nrights = BER_BVNULL,
1458 subject = BER_BVNULL,
1459 nsubject = BER_BVNULL;
1468 if ( BER_BVISEMPTY( val ) ) {
1469 return LDAP_INVALID_SYNTAX;
1472 /* oid: if valid, it's already normalized */
1473 if ( acl_get_part( val, 0, '#', &oid ) < 0 ||
1474 numericoidValidate( NULL, &oid ) != LDAP_SUCCESS )
1476 return LDAP_INVALID_SYNTAX;
1479 /* scope: normalize by replacing with OpenLDAPaciscopes */
1480 if ( acl_get_part( val, 1, '#', &scope ) < 0 ) {
1481 return LDAP_INVALID_SYNTAX;
1483 idx = bv_getcaseidx( &scope, OpenLDAPaciscopes );
1485 return LDAP_INVALID_SYNTAX;
1487 scope = *OpenLDAPaciscopes[ idx ];
1490 if ( acl_get_part( val, 2, '#', &rights ) < 0 ) {
1491 return LDAP_INVALID_SYNTAX;
1493 if ( OpenLDAPaciNormalizeRights( &rights, &nrights, ctx )
1496 return LDAP_INVALID_SYNTAX;
1500 if ( acl_get_part( val, 3, '#', &type ) < 0 ) {
1501 rc = LDAP_INVALID_SYNTAX;
1504 idx = bv_getcaseidx( &type, OpenLDAPacitypes );
1508 if ( acl_get_part( &type, 0, '/', &isgr ) < 0 ) {
1509 rc = LDAP_INVALID_SYNTAX;
1513 idx = bv_getcaseidx( &isgr, OpenLDAPacitypes );
1514 if ( idx == -1 || idx >= LAST_OPTIONAL ) {
1515 rc = LDAP_INVALID_SYNTAX;
1519 ntype = *OpenLDAPacitypes[ idx ];
1522 bv_get_tail( val, &type, &subject );
1524 if ( BER_BVISEMPTY( &subject ) || subject.bv_val[ 0 ] != '#' ) {
1525 rc = LDAP_INVALID_SYNTAX;
1532 if ( idx < LAST_DNVALUED ) {
1533 /* FIXME: pass DN syntax? */
1535 rc = dnNormalize( 0, NULL, NULL,
1536 &subject, &nsubject, ctx );
1538 rc = dnPretty( NULL, &subject, &nsubject, ctx );
1541 if ( rc == LDAP_SUCCESS ) {
1548 if ( OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_GROUP ]
1549 || OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_ROLE ] )
1551 /* do {group|role}/oc/at check */
1552 struct berval ocbv = BER_BVNULL,
1555 ocbv.bv_val = ber_bvchr( &type, '/' );
1556 if ( ocbv.bv_val != NULL ) {
1557 ObjectClass *oc = NULL;
1558 AttributeDescription *ad = NULL;
1559 const char *text = NULL;
1563 bv.bv_len = ntype.bv_len;
1566 ocbv.bv_len = type.bv_len - ( ocbv.bv_val - type.bv_val );
1568 atbv.bv_val = ber_bvchr( &ocbv, '/' );
1569 if ( atbv.bv_val != NULL ) {
1571 atbv.bv_len = type.bv_len
1572 - ( atbv.bv_val - type.bv_val );
1573 ocbv.bv_len = atbv.bv_val - ocbv.bv_val - 1;
1575 rc = slap_bv2ad( &atbv, &ad, &text );
1576 if ( rc != LDAP_SUCCESS ) {
1577 rc = LDAP_INVALID_SYNTAX;
1581 bv.bv_len += STRLENOF( "/" ) + ad->ad_cname.bv_len;
1584 oc = oc_bvfind( &ocbv );
1586 rc = LDAP_INVALID_SYNTAX;
1590 bv.bv_len += STRLENOF( "/" ) + oc->soc_cname.bv_len;
1591 bv.bv_val = ber_memalloc_x( bv.bv_len + 1, ctx );
1594 ptr = lutil_strncopy( ptr, ntype.bv_val, ntype.bv_len );
1597 ptr = lutil_strncopy( ptr,
1598 oc->soc_cname.bv_val,
1599 oc->soc_cname.bv_len );
1603 ptr = lutil_strncopy( ptr,
1604 ad->ad_cname.bv_val,
1605 ad->ad_cname.bv_len );
1614 } else if ( OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_DNATTR ] ) {
1615 AttributeDescription *ad = NULL;
1616 const char *text = NULL;
1619 rc = slap_bv2ad( &subject, &ad, &text );
1620 if ( rc != LDAP_SUCCESS ) {
1621 rc = LDAP_INVALID_SYNTAX;
1625 if ( ad->ad_type->sat_syntax != slap_schema.si_syn_distinguishedName ) {
1626 /* FIXME: allow nameAndOptionalUID? */
1627 rc = LDAP_INVALID_SYNTAX;
1631 nsubject = ad->ad_cname;
1636 oid.bv_len + STRLENOF( "#" )
1637 + scope.bv_len + STRLENOF( "#" )
1638 + nrights.bv_len + STRLENOF( "#" )
1639 + ntype.bv_len + STRLENOF( "#" )
1642 out->bv_val = ber_memalloc_x( out->bv_len + 1, ctx );
1643 ptr = lutil_strncopy( out->bv_val, oid.bv_val, oid.bv_len );
1646 ptr = lutil_strncopy( ptr, scope.bv_val, scope.bv_len );
1649 ptr = lutil_strncopy( ptr, nrights.bv_val, nrights.bv_len );
1652 ptr = lutil_strncopy( ptr, ntype.bv_val, ntype.bv_len );
1655 if ( !BER_BVISNULL( &nsubject ) ) {
1656 ptr = lutil_strncopy( ptr, nsubject.bv_val, nsubject.bv_len );
1661 if ( freesubject ) {
1662 ber_memfree_x( nsubject.bv_val, ctx );
1666 ber_memfree_x( ntype.bv_val, ctx );
1669 if ( !BER_BVISNULL( &nrights ) ) {
1670 ber_memfree_x( nrights.bv_val, ctx );
1683 return OpenLDAPaciPrettyNormal( val, out, ctx, 0 );
1687 OpenLDAPaciNormalize(
1695 return OpenLDAPaciPrettyNormal( val, out, ctx, 1 );
1698 #if SLAPD_ACI_ENABLED == SLAPD_MOD_DYNAMIC
1700 * FIXME: need config and Makefile.am code to ease building
1704 init_module( int argc, char *argv[] )
1706 return slap_dynacl_register();
1708 #endif /* SLAPD_ACI_ENABLED == SLAPD_MOD_DYNAMIC */
1710 #endif /* SLAPD_ACI_ENABLED */