From 2ad1caaa0cd09e08297af1e5cf166dda52c2afa9 Mon Sep 17 00:00:00 2001
From: Quanah Gibson-Mount <quanah@openldap.org>
Date: Sat, 8 Nov 2008 00:30:42 +0000
Subject: [PATCH] ITS#5702

---
 CHANGES                             |   1 +
 doc/man/man5/slapo-constraint.5     |  27 +++-
 servers/slapd/overlays/constraint.c | 208 +++++++++++++++++++++++-----
 3 files changed, 196 insertions(+), 40 deletions(-)

diff --git a/CHANGES b/CHANGES
index 9ae1d45026..ec62e06ba6 100644
--- a/CHANGES
+++ b/CHANGES
@@ -4,6 +4,7 @@ OpenLDAP 2.4.13 Engineering
 	Fixed liblutil hex conversion (ITS#5699)
 	Added slapd support for certificateListExactMatch from RFC4523 (ITS#5700)
 	Fixed slapd-bdb/hdb invalid db crash (ITS#5698)
+	Added slapo-constraint "set" type (ITS#5702)
 	Added slapo-translucent try local bind when remote fails (ITS#5656)
 
 OpenLDAP 2.4.12 Release (2008/10/12)
diff --git a/doc/man/man5/slapo-constraint.5 b/doc/man/man5/slapo-constraint.5
index 7596de624e..28e49d40bb 100644
--- a/doc/man/man5/slapo-constraint.5
+++ b/doc/man/man5/slapo-constraint.5
@@ -27,15 +27,16 @@ It should appear after the
 .B overlay
 directive.
 .TP
-.B constraint_attribute <attribute_name> <type> <value>
+.B constraint_attribute <attribute_name>[,...] <type> <value>
 Specifies the constraint which should apply to the attribute named as
 the first parameter.
 Two types of constraint are currently supported -
-.B regex ,
-.B size ,
-.B count ,
+.BR regex ,
+.BR size ,
+.BR count ,
+.BR uri ,
 and
-.BR uri .
+.BR set .
 
 The parameter following the
 .B regex
@@ -47,6 +48,12 @@ type is an LDAP URI. The URI will be evaluated using an internal search.
 It must not include a hostname, and it must include a list of attributes
 to evaluate.
 
+The parameter following the
+.B set
+type is a string that is interpreted according to the syntax in use
+for ACL sets.  This allows to construct constraints based on the contents
+of the entry.
+
 The 
 .B size
 type can be used to enforce a limit on an attribute length, and the
@@ -67,8 +74,11 @@ constraint_attribute userPassword count 3
 constraint_attribute mail regex ^[:alnum:]+@mydomain.com$
 constraint_attribute title uri
   ldap:///dc=catalog,dc=example,dc=com?title?sub?(objectClass=titleCatalog)
+constraint_attribute cn,sn,givenName set
+  "(this/givenName + [ ] + this/sn) & this/cn"
 .fi
 
+.RE
 A specification like the above would reject any
 .B mail
 attribute which did not look like
@@ -80,6 +90,13 @@ attribute whose values were not listed in the
 attribute of any
 .B titleCatalog
 entries in the given scope.
+Finally, it requires the values of the attribute
+.B cn
+to be constructed by pairing values of the attributes
+.B sn
+and 
+.BR givenName ,
+separated by a space.
 .RE
 .SH FILES
 .TP
diff --git a/servers/slapd/overlays/constraint.c b/servers/slapd/overlays/constraint.c
index 6d8b40e5e7..711790080f 100644
--- a/servers/slapd/overlays/constraint.c
+++ b/servers/slapd/overlays/constraint.c
@@ -41,6 +41,7 @@
 
 #define REGEX_STR "regex"
 #define URI_STR "uri"
+#define SET_STR "set"
 #define SIZE_STR "size"
 #define COUNT_STR "count"
 
@@ -54,9 +55,10 @@
 
 typedef struct constraint {
 	struct constraint *ap_next;
-	AttributeDescription *ap;
+	AttributeDescription **ap;
 	regex_t *re;
 	LDAPURLDesc *lud;
+	int set;
 	size_t size;
 	size_t count;
 	AttributeDescription **attrs;
@@ -104,6 +106,8 @@ constraint_free( constraint *cp )
 		ldap_free_urldesc(cp->lud);
 	if (cp->attrs)
 		ch_free(cp->attrs);
+	if (cp->ap)
+		ch_free(cp->ap);
 	ch_free(cp);
 }
 
@@ -122,36 +126,55 @@ constraint_cf_gen( ConfigArgs *c )
 		switch (c->type) {
 		case CONSTRAINT_ATTRIBUTE:
 			for (cp=cn; cp; cp=cp->ap_next) {
-				int len;
 				char *s;
 				char *tstr = NULL;
+				int quotes = 0;
+				int j;
+
+				bv.bv_len = STRLENOF("  ");
+				for (j = 0; cp->ap[j]; j++) {
+					bv.bv_len += cp->ap[j]->ad_cname.bv_len;
+				}
+
+				/* room for commas */
+				bv.bv_len += j - 1;
 
-				len = cp->ap->ad_cname.bv_len + 3;
 				if (cp->re) {
-					len += STRLENOF(REGEX_STR);
 					tstr = REGEX_STR;
 				} else if (cp->lud) {
-					len += STRLENOF(URI_STR);
 					tstr = URI_STR;
+				} else if (cp->set) {
+					tstr = SET_STR;
+					quotes = 1;
 				} else if (cp->size) {
-					len += STRLENOF(SIZE_STR);
 					tstr = SIZE_STR;
 				} else if (cp->count) {
-					len += STRLENOF(COUNT_STR);
 					tstr = COUNT_STR;
 				}
-				len += cp->val.bv_len;
 
-				s = ch_malloc(len);
+				bv.bv_len += strlen(tstr);
+				bv.bv_len += cp->val.bv_len + 2*quotes;
+
+				s = bv.bv_val = ch_malloc(bv.bv_len + 1);
+
+				s = lutil_strncopy( s, cp->ap[0]->ad_cname.bv_val, cp->ap[0]->ad_cname.bv_len );
+				for (j = 1; cp->ap[j]; j++) {
+					*s++ = ',';
+					s = lutil_strncopy( s, cp->ap[j]->ad_cname.bv_val, cp->ap[j]->ad_cname.bv_len );
+				}
+				*s++ = ' ';
+				s = lutil_strcopy( s, tstr );
+				*s++ = ' ';
+				if ( quotes ) *s++ = '"';
+				s = lutil_strncopy( s, cp->val.bv_val, cp->val.bv_len );
+				if ( quotes ) *s++ = '"';
+				*s = '\0';
 
-				bv.bv_len = snprintf(s, len, "%s %s %s", cp->ap->ad_cname.bv_val,
-						 tstr, cp->val.bv_val);
-				bv.bv_val = s;
 				rc = value_add_one( &c->rvalue_vals, &bv );
+				if (rc == LDAP_SUCCESS)
+					rc = value_add_one( &c->rvalue_nvals, &bv );
+				ch_free(bv.bv_val);
 				if (rc) return rc;
-				rc = value_add_one( &c->rvalue_nvals, &bv );
-				if (rc) return rc;
-				ch_free(s);
 			}
 			break;
 		default:
@@ -198,14 +221,24 @@ constraint_cf_gen( ConfigArgs *c )
 	case SLAP_CONFIG_ADD:
 	case LDAP_MOD_ADD:
 		switch (c->type) {
-		case CONSTRAINT_ATTRIBUTE:
-			if ( slap_str2ad( c->argv[1], &ap.ap, &text ) ) {
-				snprintf( c->cr_msg, sizeof( c->cr_msg ),
-					"%s <%s>: %s\n", c->argv[0], c->argv[1], text );
-				Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
-					   "%s: %s\n", c->log, c->cr_msg, 0 );
-				return( ARG_BAD_CONF );
+		case CONSTRAINT_ATTRIBUTE: {
+			int j;
+			char **attrs = ldap_str2charray( c->argv[1], "," );
+
+			for ( j = 0; attrs[j]; j++)
+				/* just count */ ;
+			ap.ap = ch_calloc( sizeof(AttributeDescription*), j + 1 );
+			for ( j = 0; attrs[j]; j++) {
+				if ( slap_str2ad( attrs[j], &ap.ap[j], &text ) ) {
+					snprintf( c->cr_msg, sizeof( c->cr_msg ),
+						"%s <%s>: %s\n", c->argv[0], attrs[j], text );
+					Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+						   "%s: %s\n", c->log, c->cr_msg, 0 );
+					ldap_memvfree((void**)attrs);
+					return( ARG_BAD_CONF );
+				}
 			}
+			ldap_memvfree((void**)attrs);
 
 			if ( strcasecmp( c->argv[2], REGEX_STR ) == 0) {
 				int err;
@@ -294,6 +327,11 @@ constraint_cf_gen( ConfigArgs *c )
 				}
 
 				ber_str2bv( c->argv[3], 0, 1, &ap.val );
+
+			} else if ( strcasecmp( c->argv[2], SET_STR ) == 0 ) {
+				ap.set = 1;
+				ber_str2bv( c->argv[3], 0, 1, &ap.val );
+
 			} else {
 				snprintf( c->cr_msg, sizeof( c->cr_msg ),
 				   "%s %s: Unknown constraint type: %s",
@@ -309,6 +347,7 @@ constraint_cf_gen( ConfigArgs *c )
 			a2->re = ap.re;
 			a2->val = ap.val;
 			a2->lud = ap.lud;
+			a2->set = ap.set;
 			a2->size = ap.size;
 			a2->count = ap.count;
 			if ( a2->lud ) {
@@ -317,7 +356,7 @@ constraint_cf_gen( ConfigArgs *c )
 			}
 			a2->attrs = ap.attrs;
 			on->on_bi.bi_private = a2;
-			break;
+			} break;
 		default:
 			abort();
 			break;
@@ -468,7 +507,7 @@ constraint_violation( constraint *c, struct berval *bv, Operation *op, SlapReply
 			return LDAP_CONSTRAINT_VIOLATION; /* constraint violation */
 			
 	}
-	
+
 	return LDAP_SUCCESS;
 }
 
@@ -518,7 +557,11 @@ constraint_add( Operation *op, SlapReply *rs )
 		if (is_at_operational(a->a_desc->ad_type)) continue;
 
 		for(cp = c; cp; cp = cp->ap_next) {
-			if (cp->ap != a->a_desc) continue;
+			int j;
+			for (j = 0; cp->ap[j]; j++) {
+				if (cp->ap[j] == a->a_desc) break;
+			}
+			if (cp->ap[j] == NULL) continue;
 			if ((b = a->a_vals) == NULL) continue;
 				
 			Debug(LDAP_DEBUG_TRACE, 
@@ -537,8 +580,15 @@ constraint_add( Operation *op, SlapReply *rs )
 					goto add_violation;
 				}
 			}
+
+			if (cp->set && acl_match_set(&cp->val, op, op->ora_e, NULL) == 0) {
+				rc = LDAP_CONSTRAINT_VIOLATION;
+				goto add_violation; /* constraint violation */
+			}
+
 		}
 	}
+
 	/* Default is to just fall through to the normal processing */
 	return SLAP_CB_CONTINUE;
 
@@ -559,7 +609,7 @@ constraint_modify( Operation *op, SlapReply *rs )
 	slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
 	Backend *be = op->o_bd;
 	constraint *c = on->on_bi.bi_private, *cp;
-	Entry *target_entry = NULL;
+	Entry *target_entry = NULL, *target_entry_copy = NULL;
 	Modifications *m;
 	BerVarray b = NULL;
 	int i;
@@ -567,7 +617,7 @@ constraint_modify( Operation *op, SlapReply *rs )
 	int rc;
 	char *msg = NULL;
 	
-	Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "constraint_modify()", 0,0,0);
+	Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "constraint_modify()\n", 0,0,0);
 	if ((m = op->orm_modlist) == NULL) {
 		op->o_bd->bd_info = (BackendInfo *)(on->on_info);
 		send_ldap_error(op, rs, LDAP_INVALID_SYNTAX,
@@ -577,8 +627,7 @@ constraint_modify( Operation *op, SlapReply *rs )
 
 	/* Do we need to count attributes? */
 	for(cp = c; cp; cp = cp->ap_next) {
-		if (cp->count != 0) {
-
+		if (cp->count != 0 || cp->set) {
 			op->o_bd = on->on_info->oi_origdb;
 			rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &target_entry );
 			op->o_bd = be;
@@ -600,11 +649,8 @@ constraint_modify( Operation *op, SlapReply *rs )
 	for(;m; m = m->sml_next) {
 		int ce = 0;
 
-		/* Get this attribute count, if needed */
-		if (target_entry)
-			ce = constraint_count_attr(target_entry, m->sml_desc);
-
 		if (is_at_operational( m->sml_desc->ad_type )) continue;
+
 		if ((( m->sml_op & LDAP_MOD_OP ) != LDAP_MOD_ADD) &&
 			(( m->sml_op & LDAP_MOD_OP ) != LDAP_MOD_REPLACE) &&
 			(( m->sml_op & LDAP_MOD_OP ) != LDAP_MOD_DELETE))
@@ -614,8 +660,18 @@ constraint_modify( Operation *op, SlapReply *rs )
 		if ((( b = m->sml_values ) == NULL ) || (b[0].bv_val == NULL))
 			continue;
 
+		/* Get this attribute count, if needed */
+		if (target_entry)
+			ce = constraint_count_attr(target_entry, m->sml_desc);
+
 		for(cp = c; cp; cp = cp->ap_next) {
-			if (cp->ap != m->sml_desc) continue;
+			int j;
+			for (j = 0; cp->ap[j]; j++) {
+				if (cp->ap[j] == m->sml_desc) {
+					break;
+				}
+			}
+			if (cp->ap[j] == NULL) continue;
 			
 			if (cp->count != 0) {
 				int ca;
@@ -655,14 +711,91 @@ constraint_modify( Operation *op, SlapReply *rs )
 					goto mod_violation;
 				}
 			}
+
+			if (cp->set && target_entry) {
+				if (target_entry_copy == NULL) {
+					Modifications *ml;
+
+					target_entry_copy = entry_dup(target_entry);
+
+					/* apply modifications, in an attempt
+					 * to estimate what the entry would
+					 * look like in case all modifications
+					 * pass */
+					for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) {
+						Modification *mod = &ml->sml_mod;
+						const char *text;
+						char textbuf[SLAP_TEXT_BUFLEN];
+						size_t textlen = sizeof(textbuf);
+						int err;
+
+						switch ( mod->sm_op ) {
+						case LDAP_MOD_ADD:
+							err = modify_add_values( target_entry_copy,
+								mod, get_permissiveModify(op),
+								&text, textbuf, textlen );
+							break;
+
+						case LDAP_MOD_DELETE:
+							err = modify_delete_values( target_entry_copy,
+								mod, get_permissiveModify(op),
+								&text, textbuf, textlen );
+							break;
+
+						case LDAP_MOD_REPLACE:
+							err = modify_replace_values( target_entry_copy,
+								mod, get_permissiveModify(op),
+								&text, textbuf, textlen );
+							break;
+
+						case LDAP_MOD_INCREMENT:
+							err = modify_increment_values( target_entry_copy,
+								mod, get_permissiveModify(op),
+								&text, textbuf, textlen );
+							break;
+
+						case SLAP_MOD_SOFTADD:
+ 							mod->sm_op = LDAP_MOD_ADD;
+							err = modify_add_values( target_entry_copy,
+								mod, get_permissiveModify(op),
+								&text, textbuf, textlen );
+ 							mod->sm_op = SLAP_MOD_SOFTADD;
+ 							if ( err == LDAP_TYPE_OR_VALUE_EXISTS ) {
+ 								err = LDAP_SUCCESS;
+ 							}
+							break;
+
+						default:
+							err = LDAP_OTHER;
+							break;
+						}
+
+						if ( err != LDAP_SUCCESS ) {
+							rc = err;
+							goto mod_violation;
+						}
+					}
+				}
+
+		
+				if ( acl_match_set(&cp->val, op, target_entry_copy, NULL) == 0) {
+					rc = LDAP_CONSTRAINT_VIOLATION;
+					goto mod_violation;
+				}
+			}
 		}
 	}
-	
+
 	if (target_entry) {
 		op->o_bd = on->on_info->oi_origdb;
 		be_entry_release_r(op, target_entry);
 		op->o_bd = be;
 	}
+
+	if (target_entry_copy) {
+		entry_free(target_entry_copy);
+	}
+
 	return SLAP_CB_CONTINUE;
 
 mod_violation:
@@ -672,6 +805,11 @@ mod_violation:
 		be_entry_release_r(op, target_entry);
 		op->o_bd = be;
 	}
+
+	if (target_entry_copy) {
+		entry_free(target_entry_copy);
+	}
+
 	op->o_bd->bd_info = (BackendInfo *)(on->on_info);
 	if ( rc == LDAP_CONSTRAINT_VIOLATION ) {
 		msg = print_message( &rsv, m->sml_desc );
-- 
GitLab