From 584b21d20bec36baa7f2f8fcb3ab2a64a7b938ba Mon Sep 17 00:00:00 2001
From: Pierangelo Masarati <ando@openldap.org>
Date: Thu, 31 Mar 2005 18:10:11 +0000
Subject: [PATCH] initial commit of "level" styles for "dn" and "self" by
 clauses (ITS#3615)

---
 doc/man/man5/slapd.access.5 | 38 ++++++++++++++---
 servers/slapd/acl.c         | 57 +++++++++++++++++++++++--
 servers/slapd/aclparse.c    | 83 ++++++++++++++++++++++++++++++++++++-
 servers/slapd/slap.h        |  3 ++
 4 files changed, 171 insertions(+), 10 deletions(-)

diff --git a/doc/man/man5/slapd.access.5 b/doc/man/man5/slapd.access.5
index feae8d2b9c..058239b9fb 100644
--- a/doc/man/man5/slapd.access.5
+++ b/doc/man/man5/slapd.access.5
@@ -229,7 +229,7 @@ It can have the forms
 	*
 	anonymous
 	users
-	self
+	self[.<selfstyle>]
 
 	dn[.<dnstyle>[,<modifier>]]=<DN>
 	dnattr=<attrname>
@@ -253,8 +253,9 @@ with
 .LP
 .nf
 	<style>={exact|regex|expand}
+	<selfstyle>={level{<n>}}
 	<dnstyle>={{exact|base(object)}|regex
-		|one(level)|sub(tree)|children}
+		|one(level)|sub(tree)|children|level{<n>}}
 	<groupstyle>={exact|expand}
 	<peernamestyle>={<style>|ip|path}
 	<domainstyle>={exact|regex|sub(tree)}
@@ -286,6 +287,18 @@ The keyword
 .B self
 means access to an entry is allowed to the entry itself (e.g. the entry
 being accessed and the requesting entry must be the same).
+It allows the 
+.B level{<n>}
+style, where \fI<n>\fP indicates what ancestor of the DN 
+is to be used in matches.
+A positive value indicates that the <n>-th ancestor of the user's DN
+is to be considered; a negative value indicates that the <n>-th ancestor
+of the target is to be considered.
+For example, a "\fIby self.level{1} ...\fP" clause would match
+when the object "\fIdc=example,dc=com\fP" is accessed
+by "\fIcn=User,dc=example,dc=com\fP".
+A "\fIby self.level{-1} ...\fP" clause would match when the same user
+accesses the object "\fIou=Address Book,cn=User,dc=example,dc=com\fP".
 .LP
 The statement
 .B dn=<DN>
@@ -360,7 +373,7 @@ the
 the
 .BR one(level) ,
 and the
-.B children
+.BR children
 forms provide
 .B $0
 as the match of the entire string.
@@ -369,7 +382,7 @@ The
 the
 .BR one(level) ,
 and the
-.B children
+.BR children
 forms also provide
 .B $1
 as the match of the rightmost part of the DN as defined in the
@@ -387,6 +400,14 @@ which means that only access to entries that appear in the DN of the
 .B <by>
 clause is allowed.
 .LP
+The 
+.BR level{<n>}
+form is an extension and a generalization of the
+.BR onelevel
+form, which matches all DNs whose <n>-th ancestor is the pattern.
+So, \fIlevel{1}\fP is equivalent to \fIonelevel\fP, 
+and \fIlevel{0}\fP is equivalent to \fIbase\fP.
+.LP
 It is perfectly useless to give any access privileges to a DN 
 that exactly matches the
 .B rootdn
@@ -804,10 +825,15 @@ is set to 1.
 .LP
 The
 .B search
-operation, for each entry, requires
+operation, requires 
+.B search (=s)
+privileges on the 
+.B entry
+pseudo-attribute of the searchBase (NOTE: this was introduced with 2.3).
+Then, for each entry, it requires
 .B search (=s)
 privileges on the attributes that are defined in the filter.
-Then, the resulting entries are tested for 
+The resulting entries are finally tested for 
 .B read (=r)
 privileges on the pseudo-attribute
 .B entry
diff --git a/servers/slapd/acl.c b/servers/slapd/acl.c
index cbf1b677f2..29e348494f 100644
--- a/servers/slapd/acl.c
+++ b/servers/slapd/acl.c
@@ -726,11 +726,33 @@ acl_mask(
 				}
 
 			} else if ( b->a_dn_style == ACL_STYLE_SELF ) {
-				if ( BER_BVISEMPTY( &op->o_ndn ) ) {
+				struct berval	ndn, selfndn;
+				int		level;
+
+				if ( BER_BVISEMPTY( &op->o_ndn ) || BER_BVISNULL( &e->e_nname ) ) {
 					continue;
 				}
+
+				level = b->a_dn_self_level;
+				if ( level < 0 ) {
+					selfndn = op->o_ndn;
+					ndn = e->e_nname;
+					level = -level;
+
+				} else {
+					ndn = op->o_ndn;
+					selfndn = e->e_nname;
+				}
+
+				for ( ; level > 0; level-- ) {
+					if ( BER_BVISEMPTY( &ndn ) ) {
+						break;
+					}
+					dnParent( &ndn, &ndn );
+				}
 				
-				if ( e->e_dn == NULL || !dn_match( &e->e_nname, &op->o_ndn ) ) {
+				if ( BER_BVISEMPTY( &ndn ) || !dn_match( &ndn, &selfndn ) )
+				{
 					continue;
 				}
 
@@ -901,9 +923,38 @@ acl_mask(
 					if ( !DN_SEPARATOR( op->o_ndn.bv_val[odnlen - patlen - 1] ) ) {
 						goto dn_match_cleanup;
 					}
+
+				} else if ( b->a_dn_style == ACL_STYLE_LEVEL ) {
+					int level;
+					struct berval ndn;
+
+					if ( odnlen <= patlen ) {
+						goto dn_match_cleanup;
+					}
+
+					if ( level > 0 && !DN_SEPARATOR( op->o_ndn.bv_val[odnlen - patlen - 1] ) )
+					{
+						goto dn_match_cleanup;
+					}
+					
+					level = b->a_dn_level;
+					ndn = op->o_ndn;
+					for ( ; level > 0; level-- ) {
+						if ( BER_BVISEMPTY( &ndn ) ) {
+							goto dn_match_cleanup;
+						}
+						dnParent( &ndn, &ndn );
+						if ( ndn.bv_len < patlen ) {
+							goto dn_match_cleanup;
+						}
+					}
+					
+					if ( ndn.bv_len != patlen ) {
+						goto dn_match_cleanup;
+					}
 				}
 
-				got_match = !strcmp( pat.bv_val, op->o_ndn.bv_val + odnlen - patlen );
+				got_match = !strcmp( pat.bv_val, &op->o_ndn.bv_val[ odnlen - patlen ] );
 
 dn_match_cleanup:;
 				if ( pat.bv_val != b->a_dn_pat.bv_val ) {
diff --git a/servers/slapd/aclparse.c b/servers/slapd/aclparse.c
index 064267970b..b72ad12d4a 100644
--- a/servers/slapd/aclparse.c
+++ b/servers/slapd/aclparse.c
@@ -45,7 +45,11 @@ static char *style_strings[] = {
 	"one",
 	"subtree",
 	"children",
+	"level",
 	"attrof",
+	"anonymous",
+	"users",
+	"self",
 	"ip",
 	"path",
 	NULL
@@ -587,12 +591,35 @@ parse_acl(
 			for ( ; i < argc; i++ ) {
 				slap_style_t sty = ACL_STYLE_REGEX;
 				char *style_modifier = NULL;
+				char *style_level = NULL;
+				int level = 0;
 				int expand = 0;
 
 				split( argv[i], '=', &left, &right );
 				split( left, '.', &left, &style );
 				if ( style ) {
-					split( style, ',', &style, &style_modifier);
+					split( style, ',', &style, &style_modifier );
+
+					if ( strncasecmp( style, "level", STRLENOF( "level" ) ) == 0 ) {
+						split( style, '{', &style, &style_level );
+						if ( style_level != NULL ) {
+							char *p = strchr( style_level, '}' );
+							if ( p == NULL ) {
+								fprintf( stderr,
+									"%s: line %d: premature eol: "
+									"expecting closing '}' in \"level{n}\"\n",
+									fname, lineno );
+								acl_usage();
+							} else if ( p == style_level ) {
+								fprintf( stderr,
+									"%s: line %d: empty level "
+									"in \"level{n}\"\n",
+									fname, lineno );
+								acl_usage();
+							}
+							p[0] = '\0';
+						}
+					}
 				}
 
 				if ( style == NULL || *style == '\0' ||
@@ -615,6 +642,21 @@ parse_acl(
 				} else if ( strcasecmp( style, "children" ) == 0 ) {
 					sty = ACL_STYLE_CHILDREN;
 
+				} else if ( strcasecmp( style, "level" ) == 0 )
+				{
+					char	*next;
+
+					level = strtol( style_level, &next, 10 );
+					if ( next[0] != '\0' ) {
+						fprintf( stderr,
+							"%s: line %d: unable to parse level "
+							"in \"level{n}\"\n",
+							fname, lineno );
+						acl_usage();
+					}
+
+					sty = ACL_STYLE_LEVEL;
+
 				} else if ( strcasecmp( style, "regex" ) == 0 ) {
 					sty = ACL_STYLE_REGEX;
 
@@ -794,6 +836,30 @@ parse_acl(
 					}
 					b->a_dn_style = sty;
 					b->a_dn_expand = expand;
+					if ( sty == ACL_STYLE_SELF ) {
+						b->a_dn_self_level = level;
+
+					} else {
+						if ( level < 0 ) {
+							fprintf( stderr,
+								"%s: line %d: bad negative level \"%d\" "
+								"in by DN clause\n",
+								fname, lineno, level );
+							acl_usage();
+						} else if ( level == 1 ) {
+							fprintf( stderr,
+								"%s: line %d: \"onelevel\" should be used "
+								"instead of \"level{1}\" in by DN clause\n",
+								fname, lineno, 0 );
+						} else if ( level == 0 && sty == ACL_STYLE_LEVEL ) {
+							fprintf( stderr,
+								"%s: line %d: \"base\" should be used "
+								"instead of \"level{0}\" in by DN clause\n",
+								fname, lineno, 0 );
+						}
+
+						b->a_dn_level = level;
+					}
 					continue;
 				}
 
@@ -2160,10 +2226,25 @@ access2text( Access *b, char *ptr )
 			b->a_dn_style == ACL_STYLE_SELF )
 		{
 			ptr = lutil_strcopy( ptr, b->a_dn_pat.bv_val );
+			if ( b->a_dn_style == ACL_STYLE_SELF && b->a_dn_self_level != 0 ) {
+				int n = sprintf( ptr, ".level{%d}", b->a_dn_self_level );
+				if ( n > 0 ) {
+					ptr += n;
+				} /* else ? */
+			}
 
 		} else {
 			ptr = lutil_strcopy( ptr, "dn." );
 			ptr = lutil_strcopy( ptr, style_strings[b->a_dn_style] );
+			if ( b->a_dn_style == ACL_STYLE_LEVEL ) {
+				int n = sprintf( ptr, "{%d}", b->a_dn_level );
+				if ( n > 0 ) {
+					ptr += n;
+				} /* else ? */
+			}
+			if ( b->a_dn_expand ) {
+				ptr = lutil_strcopy( ptr, ",expand" );
+			}
 			*ptr++ = '=';
 			*ptr++ = '"';
 			ptr = lutil_strcopy( ptr, b->a_dn_pat.bv_val );
diff --git a/servers/slapd/slap.h b/servers/slapd/slap.h
index 28b63a4c9c..2768a8face 100644
--- a/servers/slapd/slap.h
+++ b/servers/slapd/slap.h
@@ -1173,6 +1173,7 @@ typedef enum slap_style_e {
 	ACL_STYLE_ONE,
 	ACL_STYLE_SUBTREE,
 	ACL_STYLE_CHILDREN,
+	ACL_STYLE_LEVEL,
 	ACL_STYLE_ATTROF,
 	ACL_STYLE_ANONYMOUS,
 	ACL_STYLE_USERS,
@@ -1302,6 +1303,8 @@ typedef struct slap_access {
 #define a_dn_pat	a_authz.sai_dn
 
 	slap_style_t a_dn_style;
+	int			a_dn_level;
+	int			a_dn_self_level;
 	AttributeDescription	*a_dn_at;
 	int			a_dn_self;
 	int 			a_dn_expand;
-- 
GitLab