+/* $OpenLDAP$ */
+/*
+ * Copyright 1998-2002 The OpenLDAP Foundation, All Rights Reserved.
+ * COPYING RESTRICTIONS APPLY, see COPYRIGHT file
+ */
/*
* Copyright (c) 1995 Regents of the University of Michigan.
* All rights reserved.
* is provided ``as is'' without express or implied warranty.
*/
+#include "portable.h"
+
#include <stdio.h>
-#include <string.h>
-#include <time.h>
-#include <string.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include "slap.h"
-extern Backend *select_backend();
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
-extern char *default_referral;
-extern time_t currenttime;
-extern pthread_mutex_t currenttime_mutex;
-extern int global_lastmod;
+#include "lutil.h"
-static void modlist_free();
-static void add_lastmods();
+#include "ldap_pvt.h"
+#include "slap.h"
-void
+int
do_modify(
Connection *conn,
- Operation *op
-)
+ Operation *op )
{
- char *dn, *odn;
+ struct berval dn = { 0, NULL };
+ struct berval pdn = { 0, NULL };
+ struct berval ndn = { 0, NULL };
char *last;
- unsigned long tag, len;
- LDAPMod *mods, *tmp;
- LDAPMod **modtail;
+ ber_tag_t tag;
+ ber_len_t len;
+ Modifications *modlist = NULL;
+ Modifications **modtail = &modlist;
+#ifdef LDAP_DEBUG
+ Modifications *tmp;
+#endif
Backend *be;
+ int rc;
+ const char *text;
+ int manageDSAit;
+#ifdef NEW_LOGGING
+ LDAP_LOG(( "operation", LDAP_LEVEL_ENTRY,
+ "do_modify: enter\n" ));
+#else
Debug( LDAP_DEBUG_TRACE, "do_modify\n", 0, 0, 0 );
+#endif
/*
* Parse the modify request. It looks like this:
* }
*/
- if ( ber_scanf( op->o_ber, "{a", &dn ) == LBER_ERROR ) {
- Debug( LDAP_DEBUG_ANY, "ber_scanf failed\n", 0, 0, 0 );
- send_ldap_result( conn, op, LDAP_PROTOCOL_ERROR, NULL, "" );
- return;
+ if ( ber_scanf( op->o_ber, "{m" /*}*/, &dn ) == LBER_ERROR ) {
+#ifdef NEW_LOGGING
+ LDAP_LOG(( "operation", LDAP_LEVEL_ERR,
+ "do_modify: ber_scanf failed\n" ));
+#else
+ Debug( LDAP_DEBUG_ANY, "do_modify: ber_scanf failed\n", 0, 0, 0 );
+#endif
+
+ send_ldap_disconnect( conn, op,
+ LDAP_PROTOCOL_ERROR, "decoding error" );
+ return SLAPD_DISCONNECT;
}
- odn = strdup( dn );
- dn_normalize( dn );
- Debug( LDAP_DEBUG_ARGS, "do_modify: dn (%s)\n", dn, 0, 0 );
+#ifdef NEW_LOGGING
+ LDAP_LOG(( "operation", LDAP_LEVEL_ARGS,
+ "do_modify: dn (%s)\n", dn.bv_val ));
+#else
+ Debug( LDAP_DEBUG_ARGS, "do_modify: dn (%s)\n", dn.bv_val, 0, 0 );
+#endif
+
/* collect modifications & save for later */
- mods = NULL;
- modtail = &mods;
+
for ( tag = ber_first_element( op->o_ber, &len, &last );
tag != LBER_DEFAULT;
tag = ber_next_element( op->o_ber, &len, last ) )
{
- (*modtail) = (LDAPMod *) ch_calloc( 1, sizeof(LDAPMod) );
+ ber_int_t mop;
+ Modifications tmp, *mod;
+
- if ( ber_scanf( op->o_ber, "{i{a[V]}}", &(*modtail)->mod_op,
- &(*modtail)->mod_type, &(*modtail)->mod_bvalues )
+ if ( ber_scanf( op->o_ber, "{i{m[W]}}", &mop,
+ &tmp.sml_type, &tmp.sml_bvalues )
== LBER_ERROR )
{
- send_ldap_result( conn, op, LDAP_PROTOCOL_ERROR, NULL,
- "decoding error" );
- free( dn );
- free( odn );
- free( *modtail );
- modlist_free( mods );
- return;
+ send_ldap_disconnect( conn, op,
+ LDAP_PROTOCOL_ERROR, "decoding modlist error" );
+ rc = SLAPD_DISCONNECT;
+ goto cleanup;
}
- if ( (*modtail)->mod_op != LDAP_MOD_ADD &&
- (*modtail)->mod_op != LDAP_MOD_DELETE &&
- (*modtail)->mod_op != LDAP_MOD_REPLACE )
- {
- send_ldap_result( conn, op, LDAP_PROTOCOL_ERROR, NULL,
- "unrecognized modify operation" );
- free( dn );
- free( odn );
- modlist_free( mods );
- return;
- }
+ mod = (Modifications *) ch_malloc( sizeof(Modifications) );
+ mod->sml_op = mop;
+ mod->sml_type = tmp.sml_type;
+ mod->sml_bvalues = tmp.sml_bvalues;
+ mod->sml_desc = NULL;
+ mod->sml_next =NULL;
+ *modtail = mod;
- if ( (*modtail)->mod_bvalues == NULL && (*modtail)->mod_op
- != LDAP_MOD_DELETE ) {
- send_ldap_result( conn, op, LDAP_PROTOCOL_ERROR, NULL,
- "no values given" );
- free( dn );
- free( odn );
- modlist_free( mods );
- return;
+ switch( mop ) {
+ case LDAP_MOD_ADD:
+ if ( mod->sml_bvalues == NULL ) {
+#ifdef NEW_LOGGING
+ LDAP_LOG(( "operation", LDAP_LEVEL_ERR,
+ "do_modify: modify/add operation (%ld) requires values\n",
+ (long)mop ));
+#else
+ Debug( LDAP_DEBUG_ANY,
+ "do_modify: modify/add operation (%ld) requires values\n",
+ (long) mop, 0, 0 );
+#endif
+
+ send_ldap_result( conn, op, LDAP_PROTOCOL_ERROR,
+ NULL, "modify/add operation requires values",
+ NULL, NULL );
+ rc = LDAP_PROTOCOL_ERROR;
+ goto cleanup;
+ }
+
+ /* fall through */
+
+ case LDAP_MOD_DELETE:
+ case LDAP_MOD_REPLACE:
+ break;
+
+ default: {
+#ifdef NEW_LOGGING
+ LDAP_LOG(( "operation", LDAP_LEVEL_ERR,
+ "do_modify: invalid modify operation (%ld)\n",
+ (long)mop ));
+#else
+ Debug( LDAP_DEBUG_ANY,
+ "do_modify: invalid modify operation (%ld)\n",
+ (long) mop, 0, 0 );
+#endif
+
+ send_ldap_result( conn, op, LDAP_PROTOCOL_ERROR,
+ NULL, "unrecognized modify operation", NULL, NULL );
+ rc = LDAP_PROTOCOL_ERROR;
+ goto cleanup;
+ }
}
- attr_normalize( (*modtail)->mod_type );
- modtail = &(*modtail)->mod_next;
+ modtail = &mod->sml_next;
}
*modtail = NULL;
+ if( (rc = get_ctrls( conn, op, 1 )) != LDAP_SUCCESS ) {
+#ifdef NEW_LOGGING
+ LDAP_LOG(( "operation", LDAP_LEVEL_ERR,
+ "do_modify: get_ctrls failed\n" ));
+#else
+ Debug( LDAP_DEBUG_ANY, "do_modify: get_ctrls failed\n", 0, 0, 0 );
+#endif
+
+ goto cleanup;
+ }
+
+ rc = dnPrettyNormal( NULL, &dn, &pdn, &ndn );
+ if( rc != LDAP_SUCCESS ) {
+#ifdef NEW_LOGGING
+ LDAP_LOG(( "operation", LDAP_LEVEL_INFO,
+ "do_modify: conn %d invalid dn (%s)\n",
+ conn->c_connid, dn.bv_val ));
+#else
+ Debug( LDAP_DEBUG_ANY,
+ "do_modify: invalid dn (%s)\n", dn.bv_val, 0, 0 );
+#endif
+ send_ldap_result( conn, op, rc = LDAP_INVALID_DN_SYNTAX, NULL,
+ "invalid DN", NULL, NULL );
+ goto cleanup;
+ }
+
+ if( ndn.bv_len == 0 ) {
+#ifdef NEW_LOGGING
+ LDAP_LOG(( "operation", LDAP_LEVEL_ERR,
+ "do_modify: attempt to modify root DSE.\n" ));
+#else
+ Debug( LDAP_DEBUG_ANY, "do_modify: root dse!\n", 0, 0, 0 );
+#endif
+
+ send_ldap_result( conn, op, rc = LDAP_UNWILLING_TO_PERFORM,
+ NULL, "modify upon the root DSE not supported", NULL, NULL );
+ goto cleanup;
+
+#if defined( SLAPD_SCHEMA_DN )
+ } else if ( strcasecmp( ndn.bv_val, SLAPD_SCHEMA_DN ) == 0 ) {
+#ifdef NEW_LOGGING
+ LDAP_LOG(( "operation", LDAP_LEVEL_ERR,
+ "do_modify: attempt to modify subschema subentry.\n" ));
+#else
+ Debug( LDAP_DEBUG_ANY, "do_modify: subschema subentry!\n", 0, 0, 0 );
+#endif
+
+ send_ldap_result( conn, op, rc = LDAP_UNWILLING_TO_PERFORM,
+ NULL, "modification of subschema subentry not supported",
+ NULL, NULL );
+ goto cleanup;
+#endif
+ }
+
#ifdef LDAP_DEBUG
+#ifdef NEW_LOGGING
+ LDAP_LOG(( "operation", LDAP_LEVEL_DETAIL1,
+ "do_modify: modifications:\n" ));
+#else
Debug( LDAP_DEBUG_ARGS, "modifications:\n", 0, 0, 0 );
- for ( tmp = mods; tmp != NULL; tmp = tmp->mod_next ) {
- Debug( LDAP_DEBUG_ARGS, "\t%s: %s\n", tmp->mod_op
- == LDAP_MOD_ADD ? "add" : (tmp->mod_op == LDAP_MOD_DELETE ?
- "delete" : "replace"), tmp->mod_type, 0 );
+#endif
+
+ for ( tmp = modlist; tmp != NULL; tmp = tmp->sml_next ) {
+#ifdef NEW_LOGGING
+ LDAP_LOG(( "operation", LDAP_LEVEL_DETAIL1,
+ "\t%s: %s\n", tmp->sml_op == LDAP_MOD_ADD ?
+ "add" : (tmp->sml_op == LDAP_MOD_DELETE ?
+ "delete" : "replace"), tmp->sml_type.bv_val ));
+
+ if ( tmp->sml_bvalues == NULL ) {
+ LDAP_LOG(( "operation", LDAP_LEVEL_DETAIL1,
+ "\t\tno values" ));
+ } else if ( tmp->sml_bvalues[0].bv_val == NULL ) {
+ LDAP_LOG(( "operation", LDAP_LEVEL_DETAIL1,
+ "\t\tzero values" ));
+ } else if ( tmp->sml_bvalues[1].bv_val == NULL ) {
+ LDAP_LOG(( "operation", LDAP_LEVEL_DETAIL1,
+ "\t\tone value" ));
+ } else {
+ LDAP_LOG(( "operation", LDAP_LEVEL_DETAIL1,
+ "\t\tmultiple values" ));
+ }
+
+#else
+ Debug( LDAP_DEBUG_ARGS, "\t%s: %s\n",
+ tmp->sml_op == LDAP_MOD_ADD
+ ? "add" : (tmp->sml_op == LDAP_MOD_DELETE
+ ? "delete" : "replace"), tmp->sml_type.bv_val, 0 );
+
+ if ( tmp->sml_bvalues == NULL ) {
+ Debug( LDAP_DEBUG_ARGS, "%s\n",
+ "\t\tno values", NULL, NULL );
+ } else if ( tmp->sml_bvalues[0].bv_val == NULL ) {
+ Debug( LDAP_DEBUG_ARGS, "%s\n",
+ "\t\tzero values", NULL, NULL );
+ } else if ( tmp->sml_bvalues[1].bv_val == NULL ) {
+ Debug( LDAP_DEBUG_ARGS, "%s, length %ld\n",
+ "\t\tone value", (long) tmp->sml_bvalues[0].bv_len, NULL );
+ } else {
+ Debug( LDAP_DEBUG_ARGS, "%s\n",
+ "\t\tmultiple values", NULL, NULL );
+ }
+#endif
}
#endif
- Statslog( LDAP_DEBUG_STATS, "conn=%d op=%d MOD dn=\"%s\"\n",
- conn->c_connid, op->o_opid, dn, 0, 0 );
+ Statslog( LDAP_DEBUG_STATS, "conn=%ld op=%d MOD dn=\"%s\"\n",
+ op->o_connid, op->o_opid, dn.bv_val, 0, 0 );
+
+ manageDSAit = get_manageDSAit( op );
/*
* We could be serving multiple database backends. Select the
* appropriate one, or send a referral to our "referral server"
* if we don't hold it.
*/
- if ( (be = select_backend( dn )) == NULL ) {
- free( dn );
- free( odn );
- modlist_free( mods );
- send_ldap_result( conn, op, LDAP_PARTIAL_RESULTS, NULL,
- default_referral );
- return;
+ if ( (be = select_backend( &ndn, manageDSAit, 0 )) == NULL ) {
+ BerVarray ref = referral_rewrite( default_referral,
+ NULL, &pdn, LDAP_SCOPE_DEFAULT );
+
+ send_ldap_result( conn, op, rc = LDAP_REFERRAL,
+ NULL, NULL, ref ? ref : default_referral, NULL );
+
+ ber_bvarray_free( ref );
+ goto cleanup;
+ }
+
+ /* check restrictions */
+ rc = backend_check_restrictions( be, conn, op, NULL, &text ) ;
+ if( rc != LDAP_SUCCESS ) {
+ send_ldap_result( conn, op, rc,
+ NULL, text, NULL, NULL );
+ goto cleanup;
}
+ /* check for referrals */
+ rc = backend_check_referrals( be, conn, op, &pdn, &ndn );
+ if ( rc != LDAP_SUCCESS ) {
+ goto cleanup;
+ }
+
+ /* deref suffix alias if appropriate */
+ suffix_alias( be, &ndn );
+
/*
* do the modify if 1 && (2 || 3)
* 1) there is a modify function implemented in this backend;
* 2) this backend is master for what it holds;
- * 3) it's a replica and the dn supplied is the updatedn.
+ * 3) it's a replica and the dn supplied is the update_ndn.
*/
- if ( be->be_modify != NULL ) {
+ if ( be->be_modify ) {
/* do the update here */
- if ( be->be_updatedn == NULL || strcasecmp( be->be_updatedn,
- op->o_dn ) == 0 ) {
- if ( (be->be_lastmod == ON || be->be_lastmod == 0 &&
- global_lastmod == ON) && be->be_updatedn == NULL ) {
- add_lastmods( op, &mods );
+ int repl_user = be_isupdate( be, &op->o_ndn );
+#ifndef SLAPD_MULTIMASTER
+ /* Multimaster slapd does not have to check for replicator dn
+ * because it accepts each modify request
+ */
+ if ( !be->be_update_ndn.bv_len || repl_user )
+#endif
+ {
+ int update = be->be_update_ndn.bv_len;
+ const char *text;
+ char textbuf[SLAP_TEXT_BUFLEN];
+ size_t textlen = sizeof textbuf;
+
+ rc = slap_mods_check( modlist, update, &text,
+ textbuf, textlen );
+
+ if( rc != LDAP_SUCCESS ) {
+ send_ldap_result( conn, op, rc,
+ NULL, text, NULL, NULL );
+ goto cleanup;
}
- if ( (*be->be_modify)( be, conn, op, odn, mods )
- == 0 ) {
- replog( be, LDAP_REQ_MODIFY, dn, mods, 0 );
+
+ if ( SLAP_LASTMOD(be) && !repl_user ) {
+ for( modtail = &modlist;
+ *modtail != NULL;
+ modtail = &(*modtail)->sml_next )
+ {
+ /* empty */
+ }
+
+ rc = slap_mods_opattrs( op, modlist, modtail, &text,
+ textbuf, textlen );
+ if( rc != LDAP_SUCCESS ) {
+ send_ldap_result( conn, op, rc,
+ NULL, text,
+ NULL, NULL );
+ goto cleanup;
+ }
+ }
+
+ if ( (*be->be_modify)( be, conn, op, &pdn, &ndn, modlist ) == 0
+#ifdef SLAPD_MULTIMASTER
+ && !repl_user
+#endif
+ ) {
+ /* but we log only the ones not from a replicator user */
+ replog( be, op, &pdn, &ndn, modlist );
}
+#ifndef SLAPD_MULTIMASTER
/* send a referral */
} else {
- send_ldap_result( conn, op, LDAP_PARTIAL_RESULTS, NULL,
- default_referral );
+ BerVarray defref = be->be_update_refs
+ ? be->be_update_refs : default_referral;
+ BerVarray ref = referral_rewrite( defref,
+ NULL, &pdn, LDAP_SCOPE_DEFAULT );
+
+ send_ldap_result( conn, op, rc = LDAP_REFERRAL, NULL, NULL,
+ ref ? ref : defref, NULL );
+
+ ber_bvarray_free( ref );
+#endif
}
} else {
- send_ldap_result( conn, op, LDAP_UNWILLING_TO_PERFORM, NULL,
- "Function not implemented" );
+ send_ldap_result( conn, op, rc = LDAP_UNWILLING_TO_PERFORM,
+ NULL, "operation not supported within namingContext",
+ NULL, NULL );
}
- free( dn );
- free( odn );
- modlist_free( mods );
+cleanup:
+ free( pdn.bv_val );
+ free( ndn.bv_val );
+ if ( modlist != NULL )
+ slap_mods_free( modlist );
+ return rc;
}
-static void
-modlist_free(
- LDAPMod *mods
-)
+/*
+ * Do basic attribute type checking and syntax validation.
+ */
+int slap_mods_check(
+ Modifications *ml,
+ int update,
+ const char **text,
+ char *textbuf,
+ size_t textlen )
{
- LDAPMod *next;
-
- for ( ; mods != NULL; mods = next ) {
- next = mods->mod_next;
- free( mods->mod_type );
- if ( mods->mod_bvalues != NULL )
- ber_bvecfree( mods->mod_bvalues );
- free( mods );
- }
-}
+ int rc;
-static void
-add_lastmods( Operation *op, LDAPMod **mods )
-{
- char buf[20];
- struct berval bv;
- struct berval *bvals[2];
- LDAPMod **m;
- LDAPMod *tmp;
- struct tm *ltm;
-
- Debug( LDAP_DEBUG_TRACE, "add_lastmods\n", 0, 0, 0 );
-
- bvals[0] = &bv;
- bvals[1] = NULL;
-
- /* remove any attempts by the user to modify these attrs */
- for ( m = mods; *m != NULL; m = &(*m)->mod_next ) {
- if ( strcasecmp( (*m)->mod_type, "modifytimestamp" ) == 0
- || strcasecmp( (*m)->mod_type, "modifiersname" ) == 0 ) {
- tmp = *m;
- *m = (*m)->mod_next;
- free( tmp->mod_type );
- if ( tmp->mod_bvalues != NULL ) {
- ber_bvecfree( tmp->mod_bvalues );
+ for( ; ml != NULL; ml = ml->sml_next ) {
+ AttributeDescription *ad = NULL;
+
+ /* convert to attribute description */
+ rc = slap_bv2ad( &ml->sml_type, &ml->sml_desc, text );
+
+ if( rc != LDAP_SUCCESS ) {
+ snprintf( textbuf, textlen, "%s: %s",
+ ml->sml_type.bv_val, *text );
+ *text = textbuf;
+ return rc;
+ }
+
+ ad = ml->sml_desc;
+
+ if( slap_syntax_is_binary( ad->ad_type->sat_syntax )
+ && !slap_ad_is_binary( ad ))
+ {
+ /* attribute requires binary transfer */
+ snprintf( textbuf, textlen,
+ "%s: requires ;binary transfer",
+ ml->sml_type.bv_val );
+ *text = textbuf;
+ return LDAP_UNDEFINED_TYPE;
+ }
+
+ if( !slap_syntax_is_binary( ad->ad_type->sat_syntax )
+ && slap_ad_is_binary( ad ))
+ {
+ /* attribute requires binary transfer */
+ snprintf( textbuf, textlen,
+ "%s: disallows ;binary transfer",
+ ml->sml_type.bv_val );
+ *text = textbuf;
+ return LDAP_UNDEFINED_TYPE;
+ }
+
+ if( slap_ad_is_lang_range( ad )) {
+ /* attribute requires binary transfer */
+ snprintf( textbuf, textlen,
+ "%s: inappropriate use of language range option",
+ ml->sml_type.bv_val );
+ *text = textbuf;
+ return LDAP_UNDEFINED_TYPE;
+ }
+
+ if (!update && is_at_no_user_mod( ad->ad_type )) {
+ /* user modification disallowed */
+ snprintf( textbuf, textlen,
+ "%s: no user modification allowed",
+ ml->sml_type.bv_val );
+ *text = textbuf;
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+
+ if ( is_at_obsolete( ad->ad_type ) &&
+ ( ml->sml_op == LDAP_MOD_ADD || ml->sml_bvalues != NULL ) )
+ {
+ /*
+ * attribute is obsolete,
+ * only allow replace/delete with no values
+ */
+ snprintf( textbuf, textlen,
+ "%s: attribute is obsolete",
+ ml->sml_type.bv_val );
+ *text = textbuf;
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+
+ /*
+ * check values
+ */
+ if( ml->sml_bvalues != NULL ) {
+ ber_len_t nvals;
+ slap_syntax_validate_func *validate =
+ ad->ad_type->sat_syntax->ssyn_validate;
+ slap_syntax_transform_func *pretty =
+ ad->ad_type->sat_syntax->ssyn_pretty;
+
+ if( !pretty && !validate ) {
+ *text = "no validator for syntax";
+ snprintf( textbuf, textlen,
+ "%s: no validator for syntax %s",
+ ml->sml_type.bv_val,
+ ad->ad_type->sat_syntax->ssyn_oid );
+ *text = textbuf;
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /*
+ * check that each value is valid per syntax
+ * and pretty if appropriate
+ */
+ for( nvals = 0; ml->sml_bvalues[nvals].bv_val; nvals++ ) {
+ struct berval pval;
+ if( pretty ) {
+ rc = pretty( ad->ad_type->sat_syntax,
+ &ml->sml_bvalues[nvals], &pval );
+ } else {
+ rc = validate( ad->ad_type->sat_syntax,
+ &ml->sml_bvalues[nvals] );
+ }
+
+ if( rc != 0 ) {
+ snprintf( textbuf, textlen,
+ "%s: value #%ld invalid per syntax",
+ ml->sml_type.bv_val, (long) nvals );
+ *text = textbuf;
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if( pretty ) {
+ ber_memfree( ml->sml_bvalues[nvals].bv_val );
+ ml->sml_bvalues[nvals] = pval;
+ }
+ }
+
+ /*
+ * a rough single value check... an additional check is needed
+ * to catch add of single value to existing single valued attribute
+ */
+ if( ( ml->sml_op == LDAP_MOD_ADD || ml->sml_op == LDAP_MOD_REPLACE )
+ && nvals > 1 && is_at_single_value( ad->ad_type ))
+ {
+ snprintf( textbuf, textlen,
+ "%s: multiple value provided",
+ ml->sml_type.bv_val );
+ *text = textbuf;
+ return LDAP_CONSTRAINT_VIOLATION;
}
- free( tmp );
}
}
- if ( op->o_dn == NULL || op->o_dn[0] == '\0' ) {
- bv.bv_val = "NULLDN";
- bv.bv_len = strlen( bv.bv_val );
+ return LDAP_SUCCESS;
+}
+
+int slap_mods_opattrs(
+ Operation *op,
+ Modifications *mods,
+ Modifications **modtail,
+ const char **text,
+ char *textbuf, size_t textlen )
+{
+ struct berval name, timestamp, csn;
+ time_t now = slap_get_time();
+ char timebuf[22];
+ char csnbuf[64];
+ struct tm *ltm;
+ Modifications *mod;
+
+ int mop = op->o_tag == LDAP_REQ_ADD
+ ? LDAP_MOD_ADD : LDAP_MOD_REPLACE;
+
+ assert( modtail != NULL );
+ assert( *modtail == NULL );
+
+ ldap_pvt_thread_mutex_lock( &gmtime_mutex );
+ ltm = gmtime( &now );
+ strftime( timebuf, sizeof(timebuf), "%Y%m%d%H%M%SZ", ltm );
+
+ csn.bv_len = lutil_csnstr( csnbuf, sizeof( csnbuf ), 0, 0 );
+ ldap_pvt_thread_mutex_unlock( &gmtime_mutex );
+ csn.bv_val = csnbuf;
+
+ timestamp.bv_val = timebuf;
+ timestamp.bv_len = strlen(timebuf);
+
+ if( op->o_dn.bv_len == 0 ) {
+ name.bv_val = SLAPD_ANONYMOUS;
+ name.bv_len = sizeof(SLAPD_ANONYMOUS)-1;
} else {
- bv.bv_val = op->o_dn;
- bv.bv_len = strlen( bv.bv_val );
+ name = op->o_dn;
}
- tmp = (LDAPMod *) ch_calloc( 1, sizeof(LDAPMod) );
- tmp->mod_type = strdup( "modifiersname" );
- tmp->mod_op = LDAP_MOD_REPLACE;
- tmp->mod_bvalues = (struct berval **) ch_calloc( 1,
- 2 * sizeof(struct berval *) );
- tmp->mod_bvalues[0] = ber_bvdup( &bv );
- tmp->mod_next = *mods;
- *mods = tmp;
-
- pthread_mutex_lock( ¤ttime_mutex );
- ltm = localtime( ¤ttime );
- strftime( buf, sizeof(buf), "%y%m%d%H%M%SZ", ltm );
- pthread_mutex_unlock( ¤ttime_mutex );
- bv.bv_val = buf;
- bv.bv_len = strlen( bv.bv_val );
- tmp = (LDAPMod *) ch_calloc( 1, sizeof(LDAPMod) );
- tmp->mod_type = strdup( "modifytimestamp" );
- tmp->mod_op = LDAP_MOD_REPLACE;
- tmp->mod_bvalues = (struct berval **) ch_calloc( 1,
- 2 * sizeof(struct berval *) );
- tmp->mod_bvalues[0] = ber_bvdup( &bv );
- tmp->mod_next = *mods;
- *mods = tmp;
+
+ if( op->o_tag == LDAP_REQ_ADD ) {
+ struct berval tmpval;
+ char uuidbuf[40];
+ int rc;
+
+ rc = mods_structural_class( mods, &tmpval, text, textbuf, textlen );
+ if( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+ if ( tmpval.bv_len ) {
+ mod = (Modifications *) ch_malloc( sizeof( Modifications ) );
+ mod->sml_op = mop;
+ mod->sml_type.bv_val = NULL;
+ mod->sml_desc = slap_schema.si_ad_structuralObjectClass;
+ mod->sml_bvalues = (BerVarray) ch_malloc( 2 * sizeof( struct berval ) );
+ ber_dupbv( &mod->sml_bvalues[0], &tmpval );
+ mod->sml_bvalues[1].bv_val = NULL;
+ assert( mod->sml_bvalues[0].bv_val );
+ *modtail = mod;
+ modtail = &mod->sml_next;
+ }
+
+ tmpval.bv_len = lutil_uuidstr( uuidbuf, sizeof( uuidbuf ) );
+ tmpval.bv_val = uuidbuf;
+
+#if 0
+ mod = (Modifications *) ch_malloc( sizeof( Modifications ) );
+ mod->sml_op = mop;
+ mod->sml_type.bv_val = NULL;
+ mod->sml_desc = slap_schema.si_ad_entryUUID;
+ mod->sml_bvalues = (BerVarray) ch_malloc( 2 * sizeof( struct berval ) );
+ ber_dupbv( &mod->sml_bvalues[0], &tmpval );
+ mod->sml_bvalues[1].bv_val = NULL;
+ assert( mod->sml_bvalues[0].bv_val );
+ *modtail = mod;
+ modtail = &mod->sml_next;
+#endif
+
+ mod = (Modifications *) ch_malloc( sizeof( Modifications ) );
+ mod->sml_op = mop;
+ mod->sml_type.bv_val = NULL;
+ mod->sml_desc = slap_schema.si_ad_creatorsName;
+ mod->sml_bvalues = (BerVarray) ch_malloc( 2 * sizeof( struct berval ) );
+ ber_dupbv( &mod->sml_bvalues[0], &name );
+ mod->sml_bvalues[1].bv_val = NULL;
+ assert( mod->sml_bvalues[0].bv_val );
+ *modtail = mod;
+ modtail = &mod->sml_next;
+
+ mod = (Modifications *) ch_malloc( sizeof( Modifications ) );
+ mod->sml_op = mop;
+ mod->sml_type.bv_val = NULL;
+ mod->sml_desc = slap_schema.si_ad_createTimestamp;
+ mod->sml_bvalues = (BerVarray) ch_malloc( 2 * sizeof( struct berval ) );
+ ber_dupbv( &mod->sml_bvalues[0], ×tamp );
+ mod->sml_bvalues[1].bv_val = NULL;
+ assert( mod->sml_bvalues[0].bv_val );
+ *modtail = mod;
+ modtail = &mod->sml_next;
+ }
+
+#if 0
+ mod = (Modifications *) ch_malloc( sizeof( Modifications ) );
+ mod->sml_op = mop;
+ mod->sml_type.bv_val = NULL;
+ mod->sml_desc = slap_schema.si_ad_entryCSN;
+ mod->sml_bvalues = (BerVarray) ch_malloc( 2 * sizeof( struct berval ) );
+ ber_dupbv( &mod->sml_bvalues[0], &csn );
+ mod->sml_bvalues[1].bv_val = NULL;
+ assert( mod->sml_bvalues[0].bv_val );
+ *modtail = mod;
+ modtail = &mod->sml_next;
+#endif
+
+ mod = (Modifications *) ch_malloc( sizeof( Modifications ) );
+ mod->sml_op = mop;
+ mod->sml_type.bv_val = NULL;
+ mod->sml_desc = slap_schema.si_ad_modifiersName;
+ mod->sml_bvalues = (BerVarray) ch_malloc( 2 * sizeof( struct berval ) );
+ ber_dupbv( &mod->sml_bvalues[0], &name );
+ mod->sml_bvalues[1].bv_val = NULL;
+ assert( mod->sml_bvalues[0].bv_val );
+ *modtail = mod;
+ modtail = &mod->sml_next;
+
+ mod = (Modifications *) ch_malloc( sizeof( Modifications ) );
+ mod->sml_op = mop;
+ mod->sml_type.bv_val = NULL;
+ mod->sml_desc = slap_schema.si_ad_modifyTimestamp;
+ mod->sml_bvalues = (BerVarray) ch_malloc( 2 * sizeof( struct berval ) );
+ ber_dupbv( &mod->sml_bvalues[0], ×tamp );
+ mod->sml_bvalues[1].bv_val = NULL;
+ assert( mod->sml_bvalues[0].bv_val );
+ *modtail = mod;
+ modtail = &mod->sml_next;
+
+ *modtail = NULL;
+
+ return LDAP_SUCCESS;
}
--- /dev/null
+/* schema_check.c - routines to enforce schema definitions */
+/* $OpenLDAP$ */
+/*
+ * Copyright 1998-2002 The OpenLDAP Foundation, All Rights Reserved.
+ * COPYING RESTRICTIONS APPLY, see COPYRIGHT file
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/ctype.h>
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "ldap_pvt.h"
+
+static char * oc_check_required(
+ Entry *e,
+ ObjectClass *oc,
+ struct berval *ocname );
+
+/*
+ * entry_schema_check - check that entry e conforms to the schema required
+ * by its object class(es).
+ *
+ * returns 0 if so, non-zero otherwise.
+ */
+
+int
+entry_schema_check(
+ Backend *be,
+ Entry *e,
+ Attribute *oldattrs,
+ const char** text,
+ char *textbuf, size_t textlen )
+{
+ Attribute *a, *asc, *aoc;
+ ObjectClass *sc, *oc;
+ int rc, i;
+ struct berval nsc;
+ AttributeDescription *ad_structuralObjectClass
+ = slap_schema.si_ad_structuralObjectClass;
+ AttributeDescription *ad_objectClass
+ = slap_schema.si_ad_objectClass;
+ int extensible = 0;
+ int subentry = is_entry_subentry( e );
+ int collectiveSubentry = 0;
+
+#if 0
+ if( subentry) collectiveSubentry = is_entry_collectiveAttributeSubentry( e );
+#endif
+
+ *text = textbuf;
+
+ /* misc attribute checks */
+ for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
+ const char *type = a->a_desc->ad_cname.bv_val;
+
+ /* there should be at least one value */
+ assert( a->a_vals );
+ assert( a->a_vals[0].bv_val != NULL );
+
+ if( a->a_desc->ad_type->sat_check ) {
+ int rc = (a->a_desc->ad_type->sat_check)(
+ be, e, a, text, textbuf, textlen );
+ if( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+ }
+
+ if( !collectiveSubentry && is_at_collective( a->a_desc->ad_type ) ) {
+ snprintf( textbuf, textlen,
+ "'%s' can only appear in collectiveAttributeSubentry",
+ type );
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ /* if single value type, check for multiple values */
+ if( is_at_single_value( a->a_desc->ad_type ) &&
+ a->a_vals[1].bv_val != NULL )
+ {
+ snprintf( textbuf, textlen,
+ "attribute '%s' cannot have multiple values",
+ type );
+
+#ifdef NEW_LOGGING
+ LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
+ "entry_schema_check: dn=\"%s\" %s\n",
+ e->e_dn, textbuf ));
+#else
+ Debug( LDAP_DEBUG_ANY,
+ "Entry (%s), %s\n",
+ e->e_dn, textbuf, 0 );
+#endif
+
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+ }
+
+ /* it's a REALLY bad idea to disable schema checks */
+ if( !global_schemacheck ) return LDAP_SUCCESS;
+
+ /* find the object class attribute - could error out here */
+ asc = attr_find( e->e_attrs, ad_structuralObjectClass );
+ if ( asc == NULL ) {
+#ifdef NEW_LOGGING
+ LDAP_LOG(( "schema", LDAP_LEVEL_INFO, "entry_schema_check: "
+ "No structuralObjectClass for entry (%s)\n",
+ e->e_dn ));
+#else
+ Debug( LDAP_DEBUG_ANY,
+ "No structuralObjectClass for entry (%s)\n",
+ e->e_dn, 0, 0 );
+#endif
+
+ *text = "no structuralObjectClass operational attribute";
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ assert( asc->a_vals != NULL );
+ assert( asc->a_vals[0].bv_val != NULL );
+ assert( asc->a_vals[1].bv_val == NULL );
+
+ sc = oc_bvfind( &asc->a_vals[0] );
+ if( sc == NULL ) {
+ snprintf( textbuf, textlen,
+ "unrecognized structuralObjectClass '%s'",
+ asc->a_vals[0].bv_val );
+
+#ifdef NEW_LOGGING
+ LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
+ "entry_schema_check: dn (%s), %s\n",
+ e->e_dn, textbuf ));
+#else
+ Debug( LDAP_DEBUG_ANY,
+ "entry_check_schema(%s): %s\n",
+ e->e_dn, textbuf, 0 );
+#endif
+
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ if( sc->soc_kind != LDAP_SCHEMA_STRUCTURAL ) {
+ snprintf( textbuf, textlen,
+ "structuralObjectClass '%s' is not STRUCTURAL",
+ asc->a_vals[0].bv_val );
+
+#ifdef NEW_LOGGING
+ LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
+ "entry_schema_check: dn (%s), %s\n",
+ e->e_dn, textbuf ));
+#else
+ Debug( LDAP_DEBUG_ANY,
+ "entry_check_schema(%s): %s\n",
+ e->e_dn, textbuf, 0 );
+#endif
+
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ /* find the object class attribute */
+ aoc = attr_find( e->e_attrs, ad_objectClass );
+ if ( aoc == NULL ) {
+#ifdef NEW_LOGGING
+ LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
+ "entry_schema_check: No objectClass for entry (%s).\n"
+ e->e_dn ));
+#else
+ Debug( LDAP_DEBUG_ANY, "No objectClass for entry (%s)\n",
+ e->e_dn, 0, 0 );
+#endif
+
+ *text = "no objectClass attribute";
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ assert( aoc->a_vals != NULL );
+ assert( aoc->a_vals[0].bv_val != NULL );
+
+ rc = structural_class( aoc->a_vals, &nsc, &oc, text, textbuf, textlen );
+ if( rc != LDAP_SUCCESS ) {
+ return rc;
+ } else if ( nsc.bv_len == 0 ) {
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ *text = textbuf;
+
+ if ( oc == NULL ) {
+ snprintf( textbuf, textlen,
+ "unrecognized objectClass '%s'",
+ aoc->a_vals[0].bv_val );
+ return LDAP_OBJECT_CLASS_VIOLATION;
+
+ } else if ( sc != oc ) {
+ snprintf( textbuf, textlen,
+ "structuralObjectClass modification from '%s' to '%s' not allowed",
+ asc->a_vals[0].bv_val, nsc.bv_val );
+ return LDAP_NO_OBJECT_CLASS_MODS;
+ }
+
+ /* check that the entry has required attrs for each oc */
+ for ( i = 0; aoc->a_vals[i].bv_val != NULL; i++ ) {
+ if ( (oc = oc_bvfind( &aoc->a_vals[i] )) == NULL ) {
+ snprintf( textbuf, textlen,
+ "unrecognized objectClass '%s'",
+ aoc->a_vals[i].bv_val );
+
+#ifdef NEW_LOGGING
+ LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
+ "entry_schema_check: dn (%s), %s\n",
+ e->e_dn, textbuf ));
+#else
+ Debug( LDAP_DEBUG_ANY,
+ "entry_check_schema(%s): %s\n",
+ e->e_dn, textbuf, 0 );
+#endif
+
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ if ( oc->soc_check ) {
+ int rc = (oc->soc_check)( be, e, oc,
+ text, textbuf, textlen );
+ if( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+ }
+
+ if ( oc->soc_kind == LDAP_SCHEMA_ABSTRACT ) {
+ /* object class is abstract */
+ if ( oc != slap_schema.si_oc_top &&
+ !is_object_subclass( oc, sc ))
+ {
+ int j;
+ ObjectClass *xc = NULL;
+ for( j=0; aoc->a_vals[j].bv_val; j++ ) {
+ if( i != j ) {
+ xc = oc_bvfind( &aoc->a_vals[i] );
+ if( xc == NULL ) {
+ snprintf( textbuf, textlen,
+ "unrecognized objectClass '%s'",
+ aoc->a_vals[i].bv_val );
+
+#ifdef NEW_LOGGING
+ LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
+ "entry_schema_check: dn (%s), %s\n",
+ e->e_dn, textbuf ));
+#else
+ Debug( LDAP_DEBUG_ANY,
+ "entry_check_schema(%s): %s\n",
+ e->e_dn, textbuf, 0 );
+#endif
+
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ /* since we previous check against the
+ * structural object of this entry, the
+ * abstract class must be a (direct or indirect)
+ * superclass of one of the auxiliary classes of
+ * the entry.
+ */
+ if ( xc->soc_kind == LDAP_SCHEMA_AUXILIARY &&
+ is_object_subclass( oc, xc ) )
+ {
+ break;;
+ }
+
+ xc = NULL;
+ }
+ }
+
+ if( xc == NULL ) {
+ snprintf( textbuf, textlen, "instanstantiation of "
+ "abstract objectClass '%s' not allowed",
+ aoc->a_vals[i].bv_val );
+
+#ifdef NEW_LOGGING
+ LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
+ "entry_schema_check: dn (%s), %s\n",
+ e->e_dn, textbuf ));
+#else
+ Debug( LDAP_DEBUG_ANY,
+ "entry_check_schema(%s): %s\n",
+ e->e_dn, textbuf, 0 );
+#endif
+
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+ }
+
+ } else if ( oc->soc_kind != LDAP_SCHEMA_STRUCTURAL || oc == sc ) {
+ char *s = oc_check_required( e, oc, &aoc->a_vals[i] );
+
+ if (s != NULL) {
+ snprintf( textbuf, textlen,
+ "object class '%s' requires attribute '%s'",
+ aoc->a_vals[i].bv_val, s );
+
+#ifdef NEW_LOGGING
+ LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
+ "entry_schema_check: dn=\"%s\" %s",
+ e->e_dn, textbuf ));
+#else
+ Debug( LDAP_DEBUG_ANY,
+ "Entry (%s): %s\n",
+ e->e_dn, textbuf, 0 );
+#endif
+
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ if( oc == slap_schema.si_oc_extensibleObject ) {
+ extensible=1;
+ }
+ }
+ }
+
+ if( extensible ) {
+ return LDAP_SUCCESS;
+ }
+
+ /* check that each attr in the entry is allowed by some oc */
+ for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
+ int ret = oc_check_allowed( a->a_desc->ad_type, aoc->a_vals, sc );
+ if ( ret != LDAP_SUCCESS ) {
+ char *type = a->a_desc->ad_cname.bv_val;
+
+ snprintf( textbuf, textlen,
+ "attribute '%s' not allowed",
+ type );
+
+#ifdef NEW_LOGGING
+ LDAP_LOG(( "schema", LDAP_LEVEL_INFO,
+ "entry_schema_check: dn=\"%s\" %s\n",
+ e->e_dn, textbuf ));
+#else
+ Debug( LDAP_DEBUG_ANY,
+ "Entry (%s), %s\n",
+ e->e_dn, textbuf, 0 );
+#endif
+
+ return ret;
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static char *
+oc_check_required(
+ Entry *e,
+ ObjectClass *oc,
+ struct berval *ocname )
+{
+ AttributeType *at;
+ int i;
+ Attribute *a;
+
+#ifdef NEW_LOGGING
+ LDAP_LOG(( "schema", LDAP_LEVEL_ENTRY,
+ "oc_check_required: dn (%s), objectClass \"%s\"\n",
+ e->e_dn, ocname->bv_val ));
+#else
+ Debug( LDAP_DEBUG_TRACE,
+ "oc_check_required entry (%s), objectClass \"%s\"\n",
+ e->e_dn, ocname->bv_val, 0 );
+#endif
+
+
+ /* check for empty oc_required */
+ if(oc->soc_required == NULL) {
+ return NULL;
+ }
+
+ /* for each required attribute */
+ for ( i = 0; oc->soc_required[i] != NULL; i++ ) {
+ at = oc->soc_required[i];
+ /* see if it's in the entry */
+ for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
+ if( a->a_desc->ad_type == at ) {
+ break;
+ }
+ }
+ /* not there => schema violation */
+ if ( a == NULL ) {
+ return at->sat_cname.bv_val;
+ }
+ }
+
+ return( NULL );
+}
+
+int oc_check_allowed(
+ AttributeType *at,
+ BerVarray ocl,
+ ObjectClass *sc )
+{
+ int i, j;
+
+#ifdef NEW_LOGGING
+ LDAP_LOG(( "schema", LDAP_LEVEL_ENTRY,
+ "oc_check_allowed: type \"%s\"\n", at->sat_cname.bv_val ));
+#else
+ Debug( LDAP_DEBUG_TRACE,
+ "oc_check_allowed type \"%s\"\n",
+ at->sat_cname.bv_val, 0, 0 );
+#endif
+
+ /* always allow objectClass attribute */
+ if ( strcasecmp( at->sat_cname.bv_val, "objectClass" ) == 0 ) {
+ return LDAP_SUCCESS;
+ }
+
+ /*
+ * All operational attributions are allowed by schema rules.
+ */
+ if( is_at_operational(at) ) {
+ return LDAP_SUCCESS;
+ }
+
+ /* check to see if its allowed by the structuralObjectClass */
+ if( sc ) {
+ /* does it require the type? */
+ for ( j = 0; sc->soc_required != NULL &&
+ sc->soc_required[j] != NULL; j++ )
+ {
+ if( at == sc->soc_required[j] ) {
+ return LDAP_SUCCESS;
+ }
+ }
+
+ /* does it allow the type? */
+ for ( j = 0; sc->soc_allowed != NULL &&
+ sc->soc_allowed[j] != NULL; j++ )
+ {
+ if( at == sc->soc_allowed[j] ) {
+ return LDAP_SUCCESS;
+ }
+ }
+ }
+
+ /* check that the type appears as req or opt in at least one oc */
+ for ( i = 0; ocl[i].bv_val != NULL; i++ ) {
+ /* if we know about the oc */
+ ObjectClass *oc = oc_bvfind( &ocl[i] );
+ if ( oc != NULL && oc->soc_kind != LDAP_SCHEMA_ABSTRACT &&
+ ( sc == NULL || oc->soc_kind == LDAP_SCHEMA_AUXILIARY ))
+ {
+ /* does it require the type? */
+ for ( j = 0; oc->soc_required != NULL &&
+ oc->soc_required[j] != NULL; j++ )
+ {
+ if( at == oc->soc_required[j] ) {
+ return LDAP_SUCCESS;
+ }
+ }
+ /* does it allow the type? */
+ for ( j = 0; oc->soc_allowed != NULL &&
+ oc->soc_allowed[j] != NULL; j++ )
+ {
+ if( at == oc->soc_allowed[j] ) {
+ return LDAP_SUCCESS;
+ }
+ }
+ }
+ }
+
+ /* not allowed by any oc */
+ return LDAP_OBJECT_CLASS_VIOLATION;
+}
+
+/*
+ * Determine the structural object class from a set of OIDs
+ */
+int structural_class(
+ BerVarray ocs,
+ struct berval *scbv,
+ ObjectClass **scp,
+ const char **text,
+ char *textbuf, size_t textlen )
+{
+ int i;
+ ObjectClass *oc;
+ ObjectClass *sc = NULL;
+ int scn = -1;
+
+ *text = "structural_class: internal error";
+ scbv->bv_len = 0;
+
+ for( i=0; ocs[i].bv_val; i++ ) {
+ oc = oc_bvfind( &ocs[i] );
+
+ if( oc == NULL ) {
+ snprintf( textbuf, textlen,
+ "unrecognized objectClass '%s'",
+ ocs[i].bv_val );
+ *text = textbuf;
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ if( oc->soc_kind == LDAP_SCHEMA_STRUCTURAL ) {
+ if( sc == NULL || is_object_subclass( sc, oc ) ) {
+ sc = oc;
+ scn = i;
+
+ } else if ( !is_object_subclass( oc, sc ) ) {
+ int j;
+ ObjectClass *xc = NULL;
+
+ /* find common superior */
+ for( j=i+1; ocs[j].bv_val; j++ ) {
+ xc = oc_bvfind( &ocs[j] );
+
+ if( xc == NULL ) {
+ snprintf( textbuf, textlen,
+ "unrecognized objectClass '%s'",
+ ocs[i].bv_val );
+ *text = textbuf;
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ if( xc->soc_kind != LDAP_SCHEMA_STRUCTURAL ) {
+ xc = NULL;
+ continue;
+ }
+
+ if( is_object_subclass( sc, xc ) &&
+ is_object_subclass( oc, xc ) )
+ {
+ /* found common subclass */
+ break;
+ }
+
+ xc = NULL;
+ }
+
+ if( xc == NULL ) {
+ /* no common subclass */
+ snprintf( textbuf, textlen,
+ "invalid structural object class chain (%s/%s)",
+ ocs[scn].bv_val, ocs[i].bv_val );
+ *text = textbuf;
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+ }
+ }
+ }
+
+ if( scp )
+ *scp = sc;
+
+ if( sc == NULL ) {
+ *text = "no structural object classes provided";
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ *scbv = ocs[scn];
+ return LDAP_SUCCESS;
+}
+
+/*
+ * Return structural object class from list of modifications
+ */
+int mods_structural_class(
+ Modifications *mods,
+ struct berval *sc,
+ const char **text,
+ char *textbuf, size_t textlen )
+{
+ Modifications *ocmod = NULL;
+
+ for( ; mods != NULL; mods = mods->sml_next ) {
+ if( mods->sml_desc == slap_schema.si_ad_objectClass ) {
+ if( ocmod != NULL ) {
+ *text = "entry has multiple objectClass attributes";
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+ ocmod = mods;
+ }
+ }
+
+ if( ocmod == NULL ) {
+ *text = "entry has no objectClass attribute";
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ if( ocmod->sml_bvalues == NULL || ocmod->sml_bvalues[0].bv_val == NULL ) {
+ *text = "objectClass attribute has no values";
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ return structural_class( ocmod->sml_bvalues, sc, NULL,
+ text, textbuf, textlen );
+}