Home | History | Annotate | Line # | Download | only in slapd
      1 /*	$NetBSD: schema_check.c,v 1.4 2025/09/05 21:16:25 christos Exp $	*/
      2 
      3 /* schema_check.c - routines to enforce schema definitions */
      4 /* $OpenLDAP$ */
      5 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
      6  *
      7  * Copyright 1998-2024 The OpenLDAP Foundation.
      8  * All rights reserved.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted only as authorized by the OpenLDAP
     12  * Public License.
     13  *
     14  * A copy of this license is available in the file LICENSE in the
     15  * top-level directory of the distribution or, alternatively, at
     16  * <http://www.OpenLDAP.org/license.html>.
     17  */
     18 
     19 #include <sys/cdefs.h>
     20 __RCSID("$NetBSD: schema_check.c,v 1.4 2025/09/05 21:16:25 christos Exp $");
     21 
     22 #include "portable.h"
     23 
     24 #include <stdio.h>
     25 
     26 #include <ac/ctype.h>
     27 #include <ac/string.h>
     28 #include <ac/socket.h>
     29 
     30 #include "slap.h"
     31 
     32 static char * oc_check_required(
     33 	Entry *e,
     34 	ObjectClass *oc,
     35 	struct berval *ocname );
     36 
     37 static int entry_naming_check(
     38 	Entry *e,
     39 	int manage,
     40 	int add_naming,
     41 	const char** text,
     42 	char *textbuf, size_t textlen );
     43 /*
     44  * entry_schema_check - check that entry e conforms to the schema required
     45  * by its object class(es).
     46  *
     47  * returns 0 if so, non-zero otherwise.
     48  */
     49 
     50 int
     51 entry_schema_check(
     52 	Operation *op,
     53 	Entry *e,
     54 	Attribute *oldattrs,
     55 	int manage,
     56 	int add,
     57 	Attribute **socp,
     58 	const char** text,
     59 	char *textbuf, size_t textlen )
     60 {
     61 	Attribute	*a, *asc = NULL, *aoc = NULL;
     62 	ObjectClass *sc, *oc, **socs = NULL;
     63 	AttributeType *at;
     64 	ContentRule *cr;
     65 	int	rc, i;
     66 	AttributeDescription *ad_structuralObjectClass
     67 		= slap_schema.si_ad_structuralObjectClass;
     68 	AttributeDescription *ad_objectClass
     69 		= slap_schema.si_ad_objectClass;
     70 	int extensible = 0;
     71 	int subentry = is_entry_subentry( e );
     72 	int collectiveSubentry = 0;
     73 
     74 	if ( SLAP_NO_SCHEMA_CHECK( op->o_bd )) {
     75 		return LDAP_SUCCESS;
     76 	}
     77 
     78 	if ( get_no_schema_check( op ) ) {
     79 		return LDAP_SUCCESS;
     80 	}
     81 
     82 	if( subentry ) {
     83 		collectiveSubentry = is_entry_collectiveAttributeSubentry( e );
     84 	}
     85 
     86 	*text = textbuf;
     87 
     88 	/* misc attribute checks */
     89 	for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
     90 		const char *type = a->a_desc->ad_cname.bv_val;
     91 
     92 		/* there should be at least one value */
     93 		assert( a->a_vals != NULL );
     94 		assert( a->a_vals[0].bv_val != NULL );
     95 
     96 		if( a->a_desc->ad_type->sat_check ) {
     97 			rc = (a->a_desc->ad_type->sat_check)(
     98 				op->o_bd, e, a, text, textbuf, textlen );
     99 			if( rc != LDAP_SUCCESS ) {
    100 				return rc;
    101 			}
    102 		}
    103 
    104 		if( a->a_desc == ad_structuralObjectClass )
    105 			asc = a;
    106 		else if ( a->a_desc == ad_objectClass )
    107 			aoc = a;
    108 
    109 		if( !collectiveSubentry && is_at_collective( a->a_desc->ad_type ) ) {
    110 			snprintf( textbuf, textlen,
    111 				"'%s' can only appear in collectiveAttributeSubentry",
    112 				type );
    113 			return LDAP_OBJECT_CLASS_VIOLATION;
    114 		}
    115 
    116 		/* if single value type, check for multiple values */
    117 		if( is_at_single_value( a->a_desc->ad_type ) &&
    118 			a->a_vals[1].bv_val != NULL )
    119 		{
    120 			snprintf( textbuf, textlen,
    121 				"attribute '%s' cannot have multiple values",
    122 				type );
    123 
    124 			Debug( LDAP_DEBUG_ANY,
    125 			    "Entry (%s), %s\n",
    126 			    e->e_dn, textbuf );
    127 
    128 			return LDAP_CONSTRAINT_VIOLATION;
    129 		}
    130 	}
    131 
    132 	/* check the object class attribute */
    133 	if ( aoc == NULL ) {
    134 		Debug( LDAP_DEBUG_ANY, "No objectClass for entry (%s)\n",
    135 		    e->e_dn );
    136 
    137 		*text = "no objectClass attribute";
    138 		return LDAP_OBJECT_CLASS_VIOLATION;
    139 	}
    140 
    141 	assert( aoc->a_vals != NULL );
    142 	assert( aoc->a_vals[0].bv_val != NULL );
    143 
    144 	/* check the structural object class attribute */
    145 	if ( asc == NULL && !add ) {
    146 		Debug( LDAP_DEBUG_ANY,
    147 			"No structuralObjectClass for entry (%s)\n",
    148 		    e->e_dn );
    149 
    150 		*text = "no structuralObjectClass operational attribute";
    151 		return LDAP_OTHER;
    152 	}
    153 
    154 	rc = structural_class( aoc->a_vals, &oc, &socs, text, textbuf, textlen,
    155 		op->o_tmpmemctx );
    156 	if( rc != LDAP_SUCCESS ) {
    157 		return rc;
    158 	}
    159 
    160 	if ( asc == NULL && add ) {
    161 		attr_merge_one( e, ad_structuralObjectClass, &oc->soc_cname, NULL );
    162 		asc = attr_find( e->e_attrs, ad_structuralObjectClass );
    163 		sc = oc;
    164 		goto got_soc;
    165 	}
    166 
    167 	assert( asc->a_vals != NULL );
    168 	assert( asc->a_vals[0].bv_val != NULL );
    169 	assert( asc->a_vals[1].bv_val == NULL );
    170 
    171 	sc = oc_bvfind( &asc->a_vals[0] );
    172 	if( sc == NULL ) {
    173 		snprintf( textbuf, textlen,
    174 			"unrecognized structuralObjectClass '%s'",
    175 			asc->a_vals[0].bv_val );
    176 
    177 		Debug( LDAP_DEBUG_ANY,
    178 			"entry_check_schema(%s): %s\n",
    179 			e->e_dn, textbuf );
    180 
    181 		rc = LDAP_OBJECT_CLASS_VIOLATION;
    182 		goto done;
    183 	}
    184 
    185 	if( sc->soc_kind != LDAP_SCHEMA_STRUCTURAL ) {
    186 		snprintf( textbuf, textlen,
    187 			"structuralObjectClass '%s' is not STRUCTURAL",
    188 			asc->a_vals[0].bv_val );
    189 
    190 		Debug( LDAP_DEBUG_ANY,
    191 			"entry_check_schema(%s): %s\n",
    192 			e->e_dn, textbuf );
    193 
    194 		rc = LDAP_OTHER;
    195 		goto done;
    196 	}
    197 
    198 got_soc:
    199 	if( !manage && sc->soc_obsolete ) {
    200 		snprintf( textbuf, textlen,
    201 			"structuralObjectClass '%s' is OBSOLETE",
    202 			asc->a_vals[0].bv_val );
    203 
    204 		Debug( LDAP_DEBUG_ANY,
    205 			"entry_check_schema(%s): %s\n",
    206 			e->e_dn, textbuf );
    207 
    208 		rc = LDAP_OBJECT_CLASS_VIOLATION;
    209 		goto done;
    210 	}
    211 
    212 	*text = textbuf;
    213 
    214 	if ( oc == NULL ) {
    215 		snprintf( textbuf, textlen,
    216 			"unrecognized objectClass '%s'",
    217 			aoc->a_vals[0].bv_val );
    218 		rc = LDAP_OBJECT_CLASS_VIOLATION;
    219 		goto done;
    220 
    221 	} else if ( sc != oc ) {
    222 		if ( !manage && sc != slap_schema.si_oc_glue ) {
    223 			snprintf( textbuf, textlen,
    224 				"structural object class modification "
    225 				"from '%s' to '%s' not allowed",
    226 				asc->a_vals[0].bv_val, oc->soc_cname.bv_val );
    227 			rc = LDAP_NO_OBJECT_CLASS_MODS;
    228 			goto done;
    229 		}
    230 
    231 		assert( asc->a_vals != NULL );
    232 		assert( !BER_BVISNULL( &asc->a_vals[0] ) );
    233 		assert( BER_BVISNULL( &asc->a_vals[1] ) );
    234 		assert( asc->a_nvals == asc->a_vals );
    235 
    236 		/* draft-zeilenga-ldap-relax: automatically modify
    237 		 * structuralObjectClass if changed with relax */
    238 		sc = oc;
    239 		ber_bvreplace( &asc->a_vals[ 0 ], &sc->soc_cname );
    240 		if ( socp ) {
    241 			*socp = asc;
    242 		}
    243 	}
    244 
    245 	/* naming check */
    246 	if ( !is_entry_glue ( e ) ) {
    247 		rc = entry_naming_check( e, manage, add, text, textbuf, textlen );
    248 		if( rc != LDAP_SUCCESS ) {
    249 			goto done;
    250 		}
    251 	} else {
    252 		/* Glue Entry */
    253 	}
    254 
    255 	/* find the content rule for the structural class */
    256 	cr = cr_find( sc->soc_oid );
    257 
    258 	/* the cr must be same as the structural class */
    259 	assert( !cr || !strcmp( cr->scr_oid, sc->soc_oid ) );
    260 
    261 	/* check that the entry has required attrs of the content rule */
    262 	if( cr ) {
    263 		if( !manage && cr->scr_obsolete ) {
    264 			snprintf( textbuf, textlen,
    265 				"content rule '%s' is obsolete",
    266 				ldap_contentrule2name( &cr->scr_crule ));
    267 
    268 			Debug( LDAP_DEBUG_ANY,
    269 				"Entry (%s): %s\n",
    270 				e->e_dn, textbuf );
    271 
    272 			rc = LDAP_OBJECT_CLASS_VIOLATION;
    273 			goto done;
    274 		}
    275 
    276 		if( cr->scr_required ) for( i=0; cr->scr_required[i]; i++ ) {
    277 			at = cr->scr_required[i];
    278 
    279 			for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
    280 				if( a->a_desc->ad_type == at ) {
    281 					break;
    282 				}
    283 			}
    284 
    285 			/* not there => schema violation */
    286 			if ( a == NULL ) {
    287 				snprintf( textbuf, textlen,
    288 					"content rule '%s' requires attribute '%s'",
    289 					ldap_contentrule2name( &cr->scr_crule ),
    290 					at->sat_cname.bv_val );
    291 
    292 				Debug( LDAP_DEBUG_ANY,
    293 					"Entry (%s): %s\n",
    294 					e->e_dn, textbuf );
    295 
    296 				rc = LDAP_OBJECT_CLASS_VIOLATION;
    297 				goto done;
    298 			}
    299 		}
    300 
    301 		if( cr->scr_precluded ) for( i=0; cr->scr_precluded[i]; i++ ) {
    302 			at = cr->scr_precluded[i];
    303 
    304 			for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
    305 				if( a->a_desc->ad_type == at ) {
    306 					break;
    307 				}
    308 			}
    309 
    310 			/* there => schema violation */
    311 			if ( a != NULL ) {
    312 				snprintf( textbuf, textlen,
    313 					"content rule '%s' precluded attribute '%s'",
    314 					ldap_contentrule2name( &cr->scr_crule ),
    315 					at->sat_cname.bv_val );
    316 
    317 				Debug( LDAP_DEBUG_ANY,
    318 					"Entry (%s): %s\n",
    319 					e->e_dn, textbuf );
    320 
    321 				rc = LDAP_OBJECT_CLASS_VIOLATION;
    322 				goto done;
    323 			}
    324 		}
    325 	}
    326 
    327 	/* check that the entry has required attrs for each oc */
    328 	for ( i = 0; socs[i]; i++ ) {
    329 		oc = socs[i];
    330 		if ( !manage && oc->soc_obsolete ) {
    331 			/* disallow obsolete classes */
    332 			snprintf( textbuf, textlen,
    333 				"objectClass '%s' is OBSOLETE",
    334 				aoc->a_vals[i].bv_val );
    335 
    336 			Debug( LDAP_DEBUG_ANY,
    337 				"entry_check_schema(%s): %s\n",
    338 				e->e_dn, textbuf );
    339 
    340 			rc = LDAP_OBJECT_CLASS_VIOLATION;
    341 			goto done;
    342 		}
    343 
    344 		if ( oc->soc_check ) {
    345 			rc = (oc->soc_check)( op->o_bd, e, oc,
    346 				text, textbuf, textlen );
    347 			if( rc != LDAP_SUCCESS ) {
    348 				goto done;
    349 			}
    350 		}
    351 
    352 		if ( oc->soc_kind == LDAP_SCHEMA_ABSTRACT ) {
    353 			/* object class is abstract */
    354 			if ( oc != slap_schema.si_oc_top &&
    355 				!is_object_subclass( oc, sc ))
    356 			{
    357 				int j;
    358 				ObjectClass *xc = NULL;
    359 				for( j=0; socs[j]; j++ ) {
    360 					if( i != j ) {
    361 						xc = socs[j];
    362 
    363 						/* since we previous check against the
    364 						 * structural object of this entry, the
    365 						 * abstract class must be a (direct or indirect)
    366 						 * superclass of one of the auxiliary classes of
    367 						 * the entry.
    368 						 */
    369 						if ( xc->soc_kind == LDAP_SCHEMA_AUXILIARY &&
    370 							is_object_subclass( oc, xc ) )
    371 						{
    372 							xc = NULL;
    373 							break;
    374 						}
    375 					}
    376 				}
    377 
    378 				if( xc != NULL ) {
    379 					snprintf( textbuf, textlen, "instantiation of "
    380 						"abstract objectClass '%s' not allowed",
    381 						aoc->a_vals[i].bv_val );
    382 
    383 					Debug( LDAP_DEBUG_ANY,
    384 						"entry_check_schema(%s): %s\n",
    385 						e->e_dn, textbuf );
    386 
    387 					rc = LDAP_OBJECT_CLASS_VIOLATION;
    388 					goto done;
    389 				}
    390 			}
    391 
    392 		} else if ( oc->soc_kind != LDAP_SCHEMA_STRUCTURAL || oc == sc ) {
    393 			char *s;
    394 
    395 			if( oc->soc_kind == LDAP_SCHEMA_AUXILIARY ) {
    396 				int k;
    397 
    398 				if( cr ) {
    399 					int j;
    400 
    401 					k = -1;
    402 					if( cr->scr_auxiliaries ) {
    403 						for( j = 0; cr->scr_auxiliaries[j]; j++ ) {
    404 							if( cr->scr_auxiliaries[j] == oc ) {
    405 								k = 0;
    406 								break;
    407 							}
    408 						}
    409 					}
    410 					if ( k ) {
    411 						snprintf( textbuf, textlen,
    412 							"class '%s' not allowed by content rule '%s'",
    413 							oc->soc_cname.bv_val,
    414 							ldap_contentrule2name( &cr->scr_crule ) );
    415 					}
    416 				} else if ( global_disallows & SLAP_DISALLOW_AUX_WO_CR ) {
    417 					k = -1;
    418 					snprintf( textbuf, textlen,
    419 						"class '%s' not allowed by any content rule",
    420 						oc->soc_cname.bv_val );
    421 				} else {
    422 					k = 0;
    423 				}
    424 
    425 				if( k == -1 ) {
    426 					Debug( LDAP_DEBUG_ANY,
    427 						"Entry (%s): %s\n",
    428 						e->e_dn, textbuf );
    429 
    430 					rc = LDAP_OBJECT_CLASS_VIOLATION;
    431 					goto done;
    432 				}
    433 			}
    434 
    435 			s = oc_check_required( e, oc, &aoc->a_vals[i] );
    436 			if (s != NULL) {
    437 				snprintf( textbuf, textlen,
    438 					"object class '%s' requires attribute '%s'",
    439 					aoc->a_vals[i].bv_val, s );
    440 
    441 				Debug( LDAP_DEBUG_ANY,
    442 					"Entry (%s): %s\n",
    443 					e->e_dn, textbuf );
    444 
    445 				rc = LDAP_OBJECT_CLASS_VIOLATION;
    446 				goto done;
    447 			}
    448 
    449 			if( oc == slap_schema.si_oc_extensibleObject ) {
    450 				extensible=1;
    451 			}
    452 		}
    453 	}
    454 
    455 	if( extensible ) {
    456 		*text = NULL;
    457 		rc = LDAP_SUCCESS;
    458 		goto done;
    459 	}
    460 
    461 	/* check that each attr in the entry is allowed by some oc */
    462 	for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
    463  		rc = LDAP_OBJECT_CLASS_VIOLATION;
    464 
    465 		if( cr && cr->scr_required ) {
    466 			for( i=0; cr->scr_required[i]; i++ ) {
    467 				if( cr->scr_required[i] == a->a_desc->ad_type ) {
    468 					rc = LDAP_SUCCESS;
    469 					break;
    470 				}
    471 			}
    472 		}
    473 
    474 		if( rc != LDAP_SUCCESS && cr && cr->scr_allowed ) {
    475 			for( i=0; cr->scr_allowed[i]; i++ ) {
    476 				if( cr->scr_allowed[i] == a->a_desc->ad_type ) {
    477 					rc = LDAP_SUCCESS;
    478 					break;
    479 				}
    480 			}
    481 		}
    482 
    483 		if( rc != LDAP_SUCCESS )
    484 		{
    485 			rc = oc_check_allowed( a->a_desc->ad_type, socs, sc );
    486 		}
    487 
    488 		if ( rc != LDAP_SUCCESS ) {
    489 			char *type = a->a_desc->ad_cname.bv_val;
    490 
    491 			snprintf( textbuf, textlen,
    492 				"attribute '%s' not allowed",
    493 				type );
    494 
    495 			Debug( LDAP_DEBUG_ANY,
    496 			    "Entry (%s), %s\n",
    497 			    e->e_dn, textbuf );
    498 
    499 			goto done;
    500 		}
    501 	}
    502 
    503 	*text = NULL;
    504 done:
    505 	slap_sl_free( socs, op->o_tmpmemctx );
    506 	return rc;
    507 }
    508 
    509 static char *
    510 oc_check_required(
    511 	Entry *e,
    512 	ObjectClass *oc,
    513 	struct berval *ocname )
    514 {
    515 	AttributeType	*at;
    516 	int		i;
    517 	Attribute	*a;
    518 
    519 	Debug( LDAP_DEBUG_TRACE,
    520 		"oc_check_required entry (%s), objectClass \"%s\"\n",
    521 		e->e_dn?e->e_dn:"(null)", ocname->bv_val );
    522 
    523 
    524 	/* check for empty oc_required */
    525 	if(oc->soc_required == NULL) {
    526 		return NULL;
    527 	}
    528 
    529 	/* for each required attribute */
    530 	for ( i = 0; oc->soc_required[i] != NULL; i++ ) {
    531 		at = oc->soc_required[i];
    532 		/* see if it's in the entry */
    533 		for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
    534 			if( a->a_desc->ad_type == at ) {
    535 				break;
    536 			}
    537 		}
    538 		/* not there => schema violation */
    539 		if ( a == NULL ) {
    540 			return at->sat_cname.bv_val;
    541 		}
    542 	}
    543 
    544 	return( NULL );
    545 }
    546 
    547 int oc_check_allowed(
    548 	AttributeType *at,
    549 	ObjectClass **socs,
    550 	ObjectClass *sc )
    551 {
    552 	int		i, j;
    553 
    554 	Debug( LDAP_DEBUG_TRACE,
    555 		"oc_check_allowed type \"%s\"\n",
    556 		at->sat_cname.bv_val );
    557 
    558 	/* always allow objectClass attribute */
    559 	if ( strcasecmp( at->sat_cname.bv_val, "objectClass" ) == 0 ) {
    560 		return LDAP_SUCCESS;
    561 	}
    562 
    563 	/*
    564 	 * All operational attributions are allowed by schema rules.
    565 	 */
    566 	if( is_at_operational(at) ) {
    567 		return LDAP_SUCCESS;
    568 	}
    569 
    570 	/* check to see if its allowed by the structuralObjectClass */
    571 	if( sc ) {
    572 		/* does it require the type? */
    573 		for ( j = 0; sc->soc_required != NULL &&
    574 			sc->soc_required[j] != NULL; j++ )
    575 		{
    576 			if( at == sc->soc_required[j] ) {
    577 				return LDAP_SUCCESS;
    578 			}
    579 		}
    580 
    581 		/* does it allow the type? */
    582 		for ( j = 0; sc->soc_allowed != NULL &&
    583 			sc->soc_allowed[j] != NULL; j++ )
    584 		{
    585 			if( at == sc->soc_allowed[j] ) {
    586 				return LDAP_SUCCESS;
    587 			}
    588 		}
    589 	}
    590 
    591 	/* check that the type appears as req or opt in at least one oc */
    592 	for ( i = 0; socs[i]; i++ ) {
    593 		/* if we know about the oc */
    594 		ObjectClass	*oc = socs[i];
    595 		/* extensibleObject allows all */
    596 		if ( oc == slap_schema.si_oc_extensibleObject ) {
    597 			return LDAP_SUCCESS;
    598 		}
    599 		if ( oc != NULL && oc->soc_kind != LDAP_SCHEMA_ABSTRACT &&
    600 			( sc == NULL || oc->soc_kind == LDAP_SCHEMA_AUXILIARY ))
    601 		{
    602 			/* does it require the type? */
    603 			for ( j = 0; oc->soc_required != NULL &&
    604 				oc->soc_required[j] != NULL; j++ )
    605 			{
    606 				if( at == oc->soc_required[j] ) {
    607 					return LDAP_SUCCESS;
    608 				}
    609 			}
    610 			/* does it allow the type? */
    611 			for ( j = 0; oc->soc_allowed != NULL &&
    612 				oc->soc_allowed[j] != NULL; j++ )
    613 			{
    614 				if( at == oc->soc_allowed[j] ) {
    615 					return LDAP_SUCCESS;
    616 				}
    617 			}
    618 		}
    619 	}
    620 
    621 	/* not allowed by any oc */
    622 	return LDAP_OBJECT_CLASS_VIOLATION;
    623 }
    624 
    625 /*
    626  * Determine the structural object class from a set of OIDs
    627  */
    628 int structural_class(
    629 	BerVarray ocs,
    630 	ObjectClass **scp,
    631 	ObjectClass ***socsp,
    632 	const char **text,
    633 	char *textbuf, size_t textlen,
    634 	void *ctx )
    635 {
    636 	int i, nocs;
    637 	ObjectClass *oc, **socs;
    638 	ObjectClass *sc = NULL;
    639 	int scn = -1;
    640 
    641 	*text = "structural_class: internal error";
    642 
    643 	/* count them */
    644 	for( i=0; ocs[i].bv_val; i++ ) ;
    645 	nocs = i;
    646 
    647 	socs = slap_sl_malloc( (nocs+1) * sizeof(ObjectClass *), ctx );
    648 
    649 	for( i=0; ocs[i].bv_val; i++ ) {
    650 		socs[i] = oc_bvfind( &ocs[i] );
    651 
    652 		if( socs[i] == NULL ) {
    653 			snprintf( textbuf, textlen,
    654 				"unrecognized objectClass '%s'",
    655 				ocs[i].bv_val );
    656 			*text = textbuf;
    657 			goto fail;
    658 		}
    659 	}
    660 	socs[i] = NULL;
    661 
    662 	for( i=0; ocs[i].bv_val; i++ ) {
    663 		oc = socs[i];
    664 		if( oc->soc_kind == LDAP_SCHEMA_STRUCTURAL ) {
    665 			if( sc == NULL || is_object_subclass( sc, oc ) ) {
    666 				sc = oc;
    667 				scn = i;
    668 
    669 			} else if ( !is_object_subclass( oc, sc ) ) {
    670 				int j;
    671 				ObjectClass *xc = NULL;
    672 
    673 				/* find common superior */
    674 				for( j=i+1; ocs[j].bv_val; j++ ) {
    675 					xc = socs[j];
    676 
    677 					if( xc == NULL ) {
    678 						snprintf( textbuf, textlen,
    679 							"unrecognized objectClass '%s'",
    680 							ocs[j].bv_val );
    681 						*text = textbuf;
    682 						goto fail;
    683 					}
    684 
    685 					if( xc->soc_kind != LDAP_SCHEMA_STRUCTURAL ) {
    686 						xc = NULL;
    687 						continue;
    688 					}
    689 
    690 					if( is_object_subclass( sc, xc ) &&
    691 						is_object_subclass( oc, xc ) )
    692 					{
    693 						/* found common subclass */
    694 						break;
    695 					}
    696 
    697 					xc = NULL;
    698 				}
    699 
    700 				if( xc == NULL ) {
    701 					/* no common subclass */
    702 					snprintf( textbuf, textlen,
    703 						"invalid structural object class chain (%s/%s)",
    704 						ocs[scn].bv_val, ocs[i].bv_val );
    705 					*text = textbuf;
    706 					goto fail;
    707 				}
    708 			}
    709 		}
    710 	}
    711 
    712 	if( scp ) {
    713 		*scp = sc;
    714 	}
    715 
    716 	if( sc == NULL ) {
    717 		*text = "no structural object class provided";
    718 		goto fail;
    719 	}
    720 
    721 	if( scn < 0 ) {
    722 		*text = "invalid structural object class";
    723 		goto fail;
    724 	}
    725 
    726 	if ( socsp ) {
    727 		*socsp = socs;
    728 	} else {
    729 		slap_sl_free( socs, ctx );
    730 	}
    731 	*text = NULL;
    732 
    733 	return LDAP_SUCCESS;
    734 
    735 fail:
    736 	slap_sl_free( socs, ctx );
    737 	return LDAP_OBJECT_CLASS_VIOLATION;
    738 }
    739 
    740 /*
    741  * Return structural object class from list of modifications
    742  */
    743 int mods_structural_class(
    744 	Modifications *mods,
    745 	struct berval *sc,
    746 	const char **text,
    747 	char *textbuf, size_t textlen, void *ctx )
    748 {
    749 	Modifications *ocmod = NULL;
    750 	ObjectClass *ssc;
    751 	int rc;
    752 
    753 	for( ; mods != NULL; mods = mods->sml_next ) {
    754 		if( mods->sml_desc == slap_schema.si_ad_objectClass ) {
    755 			if( ocmod != NULL ) {
    756 				*text = "entry has multiple objectClass attributes";
    757 				return LDAP_OBJECT_CLASS_VIOLATION;
    758 			}
    759 			ocmod = mods;
    760 		}
    761 	}
    762 
    763 	if( ocmod == NULL ) {
    764 		*text = "entry has no objectClass attribute";
    765 		return LDAP_OBJECT_CLASS_VIOLATION;
    766 	}
    767 
    768 	if( ocmod->sml_values == NULL || ocmod->sml_values[0].bv_val == NULL ) {
    769 		*text = "objectClass attribute has no values";
    770 		return LDAP_OBJECT_CLASS_VIOLATION;
    771 	}
    772 
    773 	rc = structural_class( ocmod->sml_values, &ssc, NULL,
    774 		text, textbuf, textlen, ctx );
    775 	if ( rc == LDAP_SUCCESS )
    776 		*sc = ssc->soc_cname;
    777 	return rc;
    778 }
    779 
    780 
    781 static int
    782 entry_naming_check(
    783 	Entry *e,
    784 	int manage,
    785 	int add_naming,
    786 	const char** text,
    787 	char *textbuf, size_t textlen )
    788 {
    789 	/* naming check */
    790 	LDAPRDN		rdn = NULL;
    791 	const char	*p = NULL;
    792 	ber_len_t	cnt;
    793 	int		rc = LDAP_SUCCESS;
    794 
    795 	if ( BER_BVISEMPTY( &e->e_name )) {
    796 		return LDAP_SUCCESS;
    797 	}
    798 
    799 	/*
    800 	 * Get attribute type(s) and attribute value(s) of our RDN
    801 	 */
    802 	if ( ldap_bv2rdn( &e->e_name, &rdn, (char **)&p,
    803 		LDAP_DN_FORMAT_LDAP ) )
    804 	{
    805 		*text = "unrecognized attribute type(s) in RDN";
    806 		return LDAP_INVALID_DN_SYNTAX;
    807 	}
    808 
    809 	/* Check that each AVA of the RDN is present in the entry */
    810 	/* FIXME: Should also check that each AVA lists a distinct type */
    811 	for ( cnt = 0; rdn[cnt]; cnt++ ) {
    812 		LDAPAVA *ava = rdn[cnt];
    813 		AttributeDescription *desc = NULL;
    814 		Attribute *attr;
    815 		const char *errtext;
    816 		int add = 0;
    817 
    818 		if( ava->la_flags & LDAP_AVA_BINARY ) {
    819 			snprintf( textbuf, textlen,
    820 				"value of naming attribute '%s' in unsupported BER form",
    821 				ava->la_attr.bv_val );
    822 			rc = LDAP_NAMING_VIOLATION;
    823 			break;
    824 		}
    825 
    826 		rc = slap_bv2ad( &ava->la_attr, &desc, &errtext );
    827 		if ( rc != LDAP_SUCCESS ) {
    828 			snprintf( textbuf, textlen, "%s (in RDN)", errtext );
    829 			break;
    830 		}
    831 
    832 		if( desc->ad_type->sat_usage ) {
    833 			snprintf( textbuf, textlen,
    834 				"naming attribute '%s' is operational",
    835 				ava->la_attr.bv_val );
    836 			rc = LDAP_NAMING_VIOLATION;
    837 			break;
    838 		}
    839 
    840 		if( desc->ad_type->sat_collective ) {
    841 			snprintf( textbuf, textlen,
    842 				"naming attribute '%s' is collective",
    843 				ava->la_attr.bv_val );
    844 			rc = LDAP_NAMING_VIOLATION;
    845 			break;
    846 		}
    847 
    848 		if( !manage && desc->ad_type->sat_obsolete ) {
    849 			snprintf( textbuf, textlen,
    850 				"naming attribute '%s' is obsolete",
    851 				ava->la_attr.bv_val );
    852 			rc = LDAP_NAMING_VIOLATION;
    853 			break;
    854 		}
    855 
    856 		if( !desc->ad_type->sat_equality ) {
    857 			snprintf( textbuf, textlen,
    858 				"naming attribute '%s' has no equality matching rule",
    859 				ava->la_attr.bv_val );
    860 			rc = LDAP_NAMING_VIOLATION;
    861 			break;
    862 		}
    863 
    864 		if( !desc->ad_type->sat_equality->smr_match ) {
    865 			snprintf( textbuf, textlen,
    866 				"naming attribute '%s' has unsupported equality matching rule",
    867 				ava->la_attr.bv_val );
    868 			rc = LDAP_NAMING_VIOLATION;
    869 			break;
    870 		}
    871 
    872 		/* find the naming attribute */
    873 		attr = attr_find( e->e_attrs, desc );
    874 		if ( attr == NULL ) {
    875 			snprintf( textbuf, textlen,
    876 				"naming attribute '%s' is not present in entry",
    877 				ava->la_attr.bv_val );
    878 			if ( add_naming ) {
    879 				add = 1;
    880 
    881 			} else {
    882 				rc = LDAP_NAMING_VIOLATION;
    883 			}
    884 
    885 		} else {
    886 			rc = attr_valfind( attr, SLAP_MR_VALUE_OF_ASSERTION_SYNTAX|
    887 				SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH,
    888 				&ava->la_value, NULL, NULL );
    889 
    890 			if ( rc != 0 ) {
    891 				switch( rc ) {
    892 				case LDAP_INAPPROPRIATE_MATCHING:
    893 					snprintf( textbuf, textlen,
    894 						"inappropriate matching for naming attribute '%s'",
    895 						ava->la_attr.bv_val );
    896 					break;
    897 				case LDAP_INVALID_SYNTAX:
    898 					snprintf( textbuf, textlen,
    899 						"value of naming attribute '%s' is invalid",
    900 						ava->la_attr.bv_val );
    901 					break;
    902 				case LDAP_NO_SUCH_ATTRIBUTE:
    903 					if ( add_naming ) {
    904 						if ( is_at_single_value( desc->ad_type ) ) {
    905 							snprintf( textbuf, textlen,
    906 								"value of single-valued naming attribute '%s' conflicts with value present in entry",
    907 								ava->la_attr.bv_val );
    908 
    909 						} else {
    910 							add = 1;
    911 							rc = LDAP_SUCCESS;
    912 						}
    913 
    914 					} else {
    915 						snprintf( textbuf, textlen,
    916 							"value of naming attribute '%s' is not present in entry",
    917 							ava->la_attr.bv_val );
    918 					}
    919 					break;
    920 				default:
    921 					snprintf( textbuf, textlen,
    922 						"naming attribute '%s' is inappropriate",
    923 						ava->la_attr.bv_val );
    924 				}
    925 
    926 				if ( !add ) {
    927 					rc = LDAP_NAMING_VIOLATION;
    928 				}
    929 			}
    930 		}
    931 
    932 		if ( add ) {
    933 			attr_merge_normalize_one( e, desc, &ava->la_value, NULL );
    934 
    935 		} else if ( rc != LDAP_SUCCESS ) {
    936 			break;
    937 		}
    938 	}
    939 
    940 	ldap_rdnfree( rdn );
    941 	return rc;
    942 }
    943 
    944