summaryrefslogtreecommitdiffstats
path: root/ldap/servers/slapd/schema.c
diff options
context:
space:
mode:
authorcvsadm <cvsadm>2005-01-21 00:44:34 +0000
committercvsadm <cvsadm>2005-01-21 00:44:34 +0000
commitb2093e3016027d6b5cf06b3f91f30769bfc099e2 (patch)
treecf58939393a9032182c4fbc4441164a9456e82f8 /ldap/servers/slapd/schema.c
downloadds-b2093e3016027d6b5cf06b3f91f30769bfc099e2.tar.gz
ds-b2093e3016027d6b5cf06b3f91f30769bfc099e2.tar.xz
ds-b2093e3016027d6b5cf06b3f91f30769bfc099e2.zip
Moving NSCP Directory Server from DirectoryBranch to TRUNK, initial drop. (foxworth)ldapserver7x
Diffstat (limited to 'ldap/servers/slapd/schema.c')
-rw-r--r--ldap/servers/slapd/schema.c4664
1 files changed, 4664 insertions, 0 deletions
diff --git a/ldap/servers/slapd/schema.c b/ldap/servers/slapd/schema.c
new file mode 100644
index 00000000..9404dccb
--- /dev/null
+++ b/ldap/servers/slapd/schema.c
@@ -0,0 +1,4664 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* schema.c - routines to enforce schema definitions */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <prio.h>
+#include <plstr.h>
+#include <plhash.h>
+#include "slap.h"
+
+typedef struct sizedbuffer
+{
+ char *buffer;
+ size_t size;
+} sizedbuffer;
+
+
+typedef char *(*schema_strstr_fn_t)( const char *big, const char *little);
+
+
+
+/*
+ * The schema_oc_kind_strings array is indexed by oc_kind values, i.e.,
+ * OC_KIND_STRUCTURAL (0), OC_KIND_AUXILIARY (1), or OC_KIND_ABSTRACT (2).
+ * The leading and trailing spaces are intentional.
+ */
+#define SCHEMA_OC_KIND_COUNT 3
+static char *schema_oc_kind_strings_with_spaces[] = {
+ " STRUCTURAL ",
+ " AUXILIARY ",
+ " ABSTRACT ",
+};
+
+/* constant strings (used in a few places) */
+static const char *schema_obsolete_with_spaces = " OBSOLETE ";
+static const char *schema_collective_with_spaces = " COLLECTIVE ";
+static const char *schema_nousermod_with_spaces = " NO-USER-MODIFICATION ";
+
+/* user defined origin array */
+static char *schema_user_defined_origin[] = {
+ "user defined",
+ NULL
+};
+
+/*
+ * pschemadse is based on the general implementation in dse
+ */
+
+static struct dse *pschemadse= NULL;
+
+static void oc_add_nolock(struct objclass *newoc);
+static int oc_delete_nolock (char *ocname);
+static int oc_replace_nolock(const char *ocname, struct objclass *newoc);
+static int oc_check_required(Slapi_PBlock *, Slapi_Entry *,struct objclass *);
+static int oc_check_allowed_sv(Slapi_PBlock *, Slapi_Entry *e, const char *type, struct objclass **oclist );
+static char **read_dollar_values ( char *vals);
+static int schema_delete_objectclasses ( Slapi_Entry *entryBefore,
+ LDAPMod *mod, char *errorbuf, size_t errorbufsize,
+ int schema_ds4x_compat );
+static int schema_delete_attributes ( Slapi_Entry *entryBefore,
+ LDAPMod *mod, char *errorbuf, size_t errorbufsize);
+static int schema_add_attribute ( Slapi_PBlock *pb, LDAPMod *mod,
+ char *errorbuf, size_t errorbufsize, int schema_ds4x_compat );
+static int schema_add_objectclass ( Slapi_PBlock *pb, LDAPMod *mod,
+ char *errorbuf, size_t errorbufsize, int schema_ds4x_compat );
+static int schema_replace_attributes ( Slapi_PBlock *pb, LDAPMod *mod,
+ char *errorbuf, size_t errorbufsize );
+static int schema_replace_objectclasses ( Slapi_PBlock *pb, LDAPMod *mod,
+ char *errorbuf, size_t errorbufsize );
+static int read_oc_ldif ( const char *input, struct objclass **oc,
+ char *errorbuf, size_t errorbufsize, int nolock, int is_user_defined,
+ int schema_ds4x_compat );
+static int schema_check_name(char *name, PRBool isAttribute, char *errorbuf,
+ size_t errorbufsize );
+static int schema_check_oid(const char *name, const char *oid,
+ PRBool isAttribute, char *errorbuf, size_t errorbufsize);
+static int has_smart_referral( Slapi_Entry *e );
+static int isExtensibleObjectclass(const char *objectclass);
+static int strip_oc_options ( struct objclass *poc );
+static char *stripOption (char *attr);
+static int read_at_ldif(const char *input, struct asyntaxinfo **asipp,
+ char *errorbuf, size_t errorbufsize, int nolock, int is_user_defined,
+ int schema_ds4x_compat, int is_remote);
+static char **parse_qdlist(const char *s, int *n, int strip_options);
+static void free_qdlist(char **vals, int n);
+static char **parse_qdescrs(const char *s, int *n);
+static char **parse_qdstrings(const char *s, int *n);
+static int get_flag_keyword( const char *keyword, int flag_value,
+ const char **inputp, schema_strstr_fn_t strstr_fn );
+static char *get_tagged_oid( const char *tag, const char **inputp,
+ schema_strstr_fn_t strstr_fn );
+static int put_tagged_oid( char *outp, const char *tag, const char *oid,
+ const char *suffix, int enquote );
+static void strcat_oids( char *buf, char *prefix, char **oids,
+ int schema_ds4x_compat );
+static size_t strcat_qdlist( char *buf, char *prefix, char **qdlist );
+static size_t strlen_null_ok(const char *s);
+static int strcpy_count( char *dst, const char *src );
+static int element_is_user_defined( char * const * origins );
+static char **parse_origin_list( const char *schema_value, int *num_originsp,
+ char **default_list );
+static int refresh_user_defined_schema(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode, char *returntext, void *arg);
+static int schema_check_oc_attrs ( struct objclass *poc, char *errorbuf,
+ size_t errorbufsize, int stripOptions );
+static struct objclass *oc_find_nolock( const char *ocname_or_oid );
+static struct objclass *oc_find_oid_nolock( const char *ocoid );
+static void oc_free( struct objclass **ocp );
+static PRBool oc_equal( struct objclass *oc1, struct objclass *oc2 );
+static PRBool attr_syntax_equal( struct asyntaxinfo *asi1,
+ struct asyntaxinfo *asi2 );
+static int schema_strcmp( const char *s1, const char *s2 );
+static int schema_strcmp_array( char **sa1, char **sa2,
+ const char *ignorestr );
+static PRBool schema_type_is_interesting( const char *type );
+static void schema_create_errormsg( char *errorbuf, size_t errorbufsize,
+ const char *prefix, const char *name, const char *fmt, ... );
+
+/* Some utility functions for dealing with a dynamic buffer */
+
+static struct sizedbuffer *sizedbuffer_construct(size_t size);
+static void sizedbuffer_destroy(struct sizedbuffer *p);
+static void sizedbuffer_allocate(struct sizedbuffer *p, size_t sizeneeded);
+
+
+/*
+ * Constant strings that we pass to schema_create_errormsg().
+ */
+static const char *schema_errprefix_oc = "object class %s: ";
+static const char *schema_errprefix_at = "attribute type %s: ";
+static const char *schema_errprefix_generic = "%s: ";
+
+
+/*
+ * A "cached" copy of the "ignore trailing spaces" config. setting.
+ * This is set during initialization only (server restart required for
+ * changes to take effect). We do things this way to avoid lock/unlock
+ * mutex sequences inside performance critical code.
+ */
+static int schema_ignore_trailing_spaces =
+ SLAPD_DEFAULT_SCHEMA_IGNORE_TRAILING_SPACES;
+
+/* R/W lock used to serialize access to the schema DSE */
+static PRRWLock *schema_dse_lock = NULL;
+
+/*
+ * The schema_dse_mandatory_init_callonce structure is used by NSPR to ensure
+ * that schema_dse_mandatory_init() is called at most once.
+ */
+static PRCallOnceType schema_dse_mandatory_init_callonce = { 0, 0, 0 };
+
+
+/* Essential initialization. Returns PRSuccess if successful */
+static PRStatus
+schema_dse_mandatory_init( void )
+{
+ if ( NULL == ( schema_dse_lock = PR_NewRWLock( PR_RWLOCK_RANK_NONE,
+ "schema DSE rwlock" ))) {
+ slapi_log_error( SLAPI_LOG_FATAL, "schema_dse_mandatory_init",
+ "PR_NewRWLock() for schema DSE lock failed\n" );
+ return PR_FAILURE;
+ }
+
+ schema_ignore_trailing_spaces = config_get_schema_ignore_trailing_spaces();
+ return PR_SUCCESS;
+}
+
+
+static void
+schema_dse_lock_read( void )
+{
+ if ( NULL != schema_dse_lock ||
+ PR_SUCCESS == PR_CallOnce( &schema_dse_mandatory_init_callonce,
+ schema_dse_mandatory_init )) {
+ PR_RWLock_Rlock( schema_dse_lock );
+ }
+}
+
+
+static void
+schema_dse_lock_write( void )
+{
+ if ( NULL != schema_dse_lock ||
+ PR_SUCCESS == PR_CallOnce( &schema_dse_mandatory_init_callonce,
+ schema_dse_mandatory_init )) {
+ PR_RWLock_Wlock( schema_dse_lock );
+ }
+}
+
+
+static void
+schema_dse_unlock( void )
+{
+ if ( schema_dse_lock != NULL ) {
+ PR_RWLock_Unlock( schema_dse_lock );
+ }
+}
+
+
+static int
+dont_allow_that(Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry* e, int *returncode, char *returntext, void *arg)
+{
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ return SLAPI_DSE_CALLBACK_ERROR;
+}
+
+#if 0
+/*
+ * hashNocaseString - used for case insensitive hash lookups
+ */
+static PLHashNumber
+hashNocaseString(const void *key)
+{
+ PLHashNumber h = 0;
+ const unsigned char *s;
+
+ for (s = key; *s; s++)
+ h = (h >> 28) ^ (h << 4) ^ (tolower(*s));
+ return h;
+}
+#endif
+
+static const char *
+skipWS(const char *s)
+{
+ while (s && isascii(*s) && isspace(*s) )
+ ++s;
+
+ if ((isascii(*s)) == 0) {
+ return NULL;
+ }
+ return s;
+}
+
+
+/*
+ * like strchr() but strings within single quotes are skipped.
+ */
+static char *
+strchr_skip_quoted_strings( char *s, int c )
+{
+ int in_quote = 0;
+
+ while ( *s != '\0' ) {
+ if ( *s == '\'' ) {
+ in_quote = 1 - in_quote; /* toggle */
+ } else if ( !in_quote && *s == c ) {
+ return s;
+ }
+ ++s;
+ }
+
+ return( NULL );
+}
+
+
+/**
+ * parses a string containing a qdescrs or qdstrings (as described by
+ * RFC 2252, section 4.1) into an array of strings; the second parameter
+ * will hold the actual number of strings in the array. The returned array
+ * is NULL terminated.
+ *
+ * This function can handle qdescrs or qdstrings because the only
+ * difference between the two is that fewer characters are allowed in
+ * a qdescr (our parsing code does not check anyway) and we want to
+ * strip attribute options when parsing qdescrs (indicated by a non-zero
+ * strip_options parameter).
+ */
+static char **
+parse_qdlist(const char *s, int *n, int strip_options)
+{
+ char **retval = 0;
+ char *work = 0;
+ char *start = 0, *end = 0;
+ int num = 0;
+ int in_quote = 0;
+
+ if (n)
+ *n = 0;
+
+ if (!s || !*s || !n) {
+ return retval;
+ }
+
+ /* make a working copy of the given string */
+ work = slapi_ch_strdup(s);
+
+ /* count the number of qdescr items in the string e.g. just count
+ the number of spaces */
+ /* for a single qdescr, the terminal character will be the final
+ single quote; for a qdesclist, the terminal will be the close
+ parenthesis */
+ end = strrchr(work, '\'');
+ if ((start = strchr_skip_quoted_strings(work, '(')) != NULL)
+ end = strchr_skip_quoted_strings(work, ')');
+ else
+ start = strchr(work, '\'');
+
+ if (!end) /* already nulled out */
+ end = work + strlen(work);
+
+ if (start) {
+ num = 1;
+ /* first pass: count number of items and zero out non useful tokens */
+ for (; *start && (start != end); ++start) {
+ if (*start == '\'' ) {
+ in_quote = 1 - in_quote; /* toggle */
+ *start = 0;
+ } else if ( !in_quote && ((*start == ' ') || (*start == '(') ||
+ (*start == ')'))) {
+ if (*start == ' ') {
+ num++;
+ }
+ *start = 0;
+ }
+ }
+ *start = 0;
+
+ /* allocate retval; num will be >= actual number of items */
+ retval = (char**)slapi_ch_calloc(num+1, sizeof(char *));
+
+ /* second pass: copy strings into the return value and set the
+ actual number of items returned */
+ start = work;
+ while (start != end) {
+ /* skip over nulls */
+ while (!*start && (start != end))
+ ++start;
+ if (start == end)
+ break;
+ retval[*n] = slapi_ch_strdup(start);
+ /*
+ * A qdescr list may contain attribute options; we just strip
+ * them here. In the future, we may want to support them or do
+ * something really fancy with them
+ */
+ if ( strip_options ) {
+ stripOption(retval[*n]);
+ }
+ (*n)++;
+ start += strlen(start);
+ }
+ PR_ASSERT( *n <= num ); /* sanity check */
+ retval[*n] = NULL;
+ } else {
+ /* syntax error - no start and/or end delimiters */
+ }
+
+ /* free the working string */
+ slapi_ch_free((void **)&work);
+
+ return retval;
+}
+
+
+static void
+free_qdlist(char **vals, int n)
+{
+ int ii;
+ for (ii = 0; ii < n; ++ii)
+ slapi_ch_free((void **)&(vals[ii]));
+ slapi_ch_free((void **)&vals);
+}
+
+
+/**
+ * parses a string containing a qdescrs (as described by RFC 2252, section 4.1)
+ * into an array of strings; the second parameter will hold the actual number
+ * of strings in the array. The returned array is NULL terminated.
+ */
+static char **
+parse_qdescrs(const char *s, int *n)
+{
+ return parse_qdlist( s, n, 1 /* strip attribute options */ );
+}
+
+
+/*
+ * Parses a string containing a qdstrings (see RFC 2252, section 4.1) into
+ * an array of strings; the second parameter will hold the actual number
+ * of strings in the array.
+ */
+static char **
+parse_qdstrings(const char *s, int *n)
+{
+ return parse_qdlist( s, n, 0 /* DO NOT strip attribute options */ );
+}
+
+
+/*
+ * slapi_entry_schema_check - check that entry e conforms to the schema
+ * required by its object class(es). returns 0 if so, non-zero otherwise.
+ * [ the pblock is only used to check if this is a replicated operation.
+ * you may pass in NULL if this isn't part of an operation. ]
+ */
+int
+slapi_entry_schema_check( Slapi_PBlock *pb, Slapi_Entry *e )
+{
+ struct objclass **oclist;
+ struct objclass *oc;
+ const char *ocname;
+ Slapi_Attr *a, *aoc;
+ Slapi_Value *v;
+ int ret = 0;
+ int schemacheck = config_get_schemacheck();
+ int is_replicated_operation = 0;
+ int is_extensible_object = 0;
+ int i, oc_count = 0;
+ int unknown_class = 0;
+ char errtext[ BUFSIZ ];
+
+ /* smart referrals are not allowed in Directory Lite */
+ if ( config_is_slapd_lite() ) {
+ if ( has_smart_referral(e) ) {
+ return 1;
+ }
+ }
+
+ /*
+ * say the schema checked out ok if we're not checking schema at
+ * all, or if this is a replication update.
+ */
+ if (pb != NULL)
+ slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_replicated_operation);
+ if ( schemacheck == 0 || is_replicated_operation ) {
+ return( 0 );
+ }
+
+ /* find the object class attribute - could error out here */
+ if ( (aoc = attrlist_find( e->e_attrs, "objectclass" )) == NULL ) {
+ char ebuf[ BUFSIZ ];
+ LDAPDebug( LDAP_DEBUG_ANY,
+ "Entry \"%s\" required attribute \"objectclass\" missing\n",
+ escape_string( slapi_entry_get_dn_const(e), ebuf ), 0, 0 );
+ if (pb) {
+ PR_snprintf( errtext, sizeof( errtext ),
+ "missing required attribute \"objectclass\"\n" );
+ slapi_pblock_set( pb, SLAPI_PB_RESULT_TEXT, errtext );
+ }
+ return( 1 );
+ }
+
+ /*
+ * Create an array of pointers to the objclass definitions.
+ */
+
+ i= slapi_attr_first_value(aoc,&v);
+ while (i != -1) {
+ oc_count++;
+ i= slapi_attr_next_value(aoc,i,&v);
+ }
+
+ oclist = (struct objclass**)
+ slapi_ch_malloc((oc_count+1)*sizeof(struct objclass*));
+
+ /*
+ * Need the read lock to create the oc array and while we use it.
+ */
+ oc_lock_read();
+
+ oc_count = 0;
+ for (i= slapi_attr_first_value(aoc,&v); i != -1;
+ i= slapi_attr_next_value(aoc,i,&v)) {
+
+ ocname = slapi_value_get_string(v);
+
+ if ( isExtensibleObjectclass( ocname )) {
+ /*
+ * if the entry is an extensibleObject, just check to see if
+ * the required attributes for whatever other objectclasses the
+ * entry might be are present. All other attributes are allowed
+ */
+ is_extensible_object = 1;
+ continue;
+ }
+
+ if ((oc = oc_find_nolock( ocname )) != NULL ) {
+ oclist[oc_count++] = oc;
+ } else {
+ /* we don't know about the oc; return an appropriate error message */
+ char ebuf[ BUFSIZ ];
+ char ebuf2[ BUFSIZ ];
+ size_t ocname_len = ( ocname == NULL ) ? 0 : strlen( ocname );
+ const char *extra_msg = "";
+
+ if ( ocname_len > 0 && isspace( ocname[ ocname_len-1 ] )) {
+ if ( ocname_len > 1 && isspace( ocname[ ocname_len-2 ] )) {
+ extra_msg = " (remove the trailing spaces)";
+ } else {
+ extra_msg = " (remove the trailing space)";
+ }
+ }
+
+ LDAPDebug( LDAP_DEBUG_ANY,
+ "Entry \"%s\" has unknown object class \"%s\"%s\n",
+ escape_string( slapi_entry_get_dn_const(e), ebuf ),
+ escape_string(ocname, ebuf2), extra_msg );
+ if (pb) {
+ PR_snprintf( errtext, sizeof( errtext ),
+ "unknown object class \"%s\"%s\n",
+ escape_string(ocname, ebuf2), extra_msg );
+ slapi_pblock_set( pb, SLAPI_PB_RESULT_TEXT, errtext );
+ }
+ unknown_class = 1;
+ }
+
+ }
+ oclist[oc_count] = NULL;
+
+ if (unknown_class) {
+ /* failure */
+ ret = 1;
+ goto out;
+ }
+
+ /*
+ * go through all the checking so we can log everything
+ * wrong with the entry. some day, we might want to return
+ * this information to the client as an error message.
+ */
+
+ /*
+ * check that the entry has required attrs for each oc
+ */
+ for (i = 0; oclist[i] != NULL; i++) {
+ if ( oc_check_required( pb, e, oclist[i] ) != 0 ) {
+ ret = 1;
+ goto out;
+ }
+ }
+
+ /*
+ * check that each attr in the entry is allowed by some oc,
+ * and that single-valued attrs only have one value
+ */
+
+ {
+ Slapi_Attr *prevattr;
+ i = slapi_entry_first_attr(e, &a);
+ while (-1 != i && 0 == ret)
+ {
+ if (is_extensible_object == 0 &&
+ unknown_class == 0 &&
+ !slapi_attr_flag_is_set(a, SLAPI_ATTR_FLAG_OPATTR))
+ {
+ char *attrtype;
+ slapi_attr_get_type(a, &attrtype);
+ if (oc_check_allowed_sv(pb, e, attrtype, oclist) != 0)
+ {
+ ret = 1;
+ }
+ }
+
+ if ( slapi_attr_flag_is_set( a, SLAPI_ATTR_FLAG_SINGLE ) ) {
+ if (slapi_valueset_count(&a->a_present_values) > 1)
+ {
+ char ebuf[ BUFSIZ ];
+ LDAPDebug( LDAP_DEBUG_ANY,
+ "Entry \"%s\" single-valued attribute \"%s\" has multiple values\n",
+ escape_string( slapi_entry_get_dn_const(e), ebuf ),
+ a->a_type, 0 );
+ if (pb) {
+ PR_snprintf( errtext, sizeof( errtext ),
+ "single-valued attribute \"%s\" has multiple values\n",
+ a->a_type );
+ slapi_pblock_set( pb, SLAPI_PB_RESULT_TEXT, errtext );
+ }
+ ret = 1;
+ }
+ }
+ prevattr = a;
+ i = slapi_entry_next_attr(e, prevattr, &a);
+ }
+ }
+
+ out:
+ /* Done with the oc array so can release the lock */
+ oc_unlock();
+ slapi_ch_free((void**)&oclist);
+
+ return( ret );
+}
+
+/*
+ * The caller must obtain a read lock first by calling oc_lock_read().
+ */
+static int
+oc_check_required( Slapi_PBlock *pb, Slapi_Entry *e, struct objclass *oc )
+{
+ int i;
+ int rc = 0; /* success, by default */
+ Slapi_Attr *a;
+
+ if (oc == NULL || oc->oc_required == NULL || oc->oc_required[0] == NULL) {
+ return 0; /* success, as none required */
+ }
+
+ /* for each required attribute */
+ for ( i = 0; oc->oc_required[i] != NULL; i++ ) {
+ /* see if it's in the entry */
+ for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
+ if ( slapi_attr_type_cmp( oc->oc_required[i], a->a_type,
+ SLAPI_TYPE_CMP_SUBTYPE ) == 0 ) {
+ break;
+ }
+ }
+
+ /* not there => schema violation */
+ if ( a == NULL ) {
+ char errtext[ BUFSIZ ];
+ char ebuf[ BUFSIZ ];
+ LDAPDebug( LDAP_DEBUG_ANY,
+ "Entry \"%s\" missing attribute \"%s\" required"
+ " by object class \"%s\"\n",
+ escape_string( slapi_entry_get_dn_const(e), ebuf ),
+ oc->oc_required[i], oc->oc_name);
+ if (pb) {
+ PR_snprintf( errtext, sizeof( errtext ),
+ "missing attribute \"%s\" required"
+ " by object class \"%s\"\n",
+ oc->oc_required[i], oc->oc_name );
+ slapi_pblock_set( pb, SLAPI_PB_RESULT_TEXT, errtext );
+ }
+ rc = 1; /* failure */
+ }
+ }
+
+ return rc;
+}
+
+
+
+/*
+ * The caller must obtain a read lock first by calling oc_lock_read().
+ */
+static int
+oc_check_allowed_sv(Slapi_PBlock *pb, Slapi_Entry *e, const char *type, struct objclass **oclist )
+{
+ struct objclass *oc;
+ int i, j;
+ int rc = 1; /* failure */
+
+ /* always allow objectclass and entryid attributes */
+ /* MFW XXX THESE SHORTCUTS SHOULD NOT BE NECESSARY BUT THEY MASK
+ * MFW XXX OTHER BUGS IN THE SERVER.
+ */
+ if ( slapi_attr_type_cmp( type, "objectclass", SLAPI_TYPE_CMP_EXACT ) == 0 ) {
+ return( 0 );
+ } else if ( slapi_attr_type_cmp( type, "entryid", SLAPI_TYPE_CMP_EXACT ) == 0 ) {
+ return( 0 );
+ }
+
+ /* check that the type appears as req or opt in at least one oc */
+ for (i = 0; rc != 0 && oclist[i] != NULL; i++) {
+ oc = oclist[i];
+
+ /* does it require the type? */
+ for ( j = 0; oc->oc_required && oc->oc_required[j] != NULL; j++ ) {
+ if ( slapi_attr_type_cmp( oc->oc_required[j],
+ type, SLAPI_TYPE_CMP_SUBTYPE ) == 0 ) {
+ rc = 0;
+ break;
+ }
+ }
+
+ if ( 0 != rc ) {
+ /* does it allow the type? */
+ for ( j = 0; oc->oc_allowed && oc->oc_allowed[j] != NULL; j++ ) {
+ if ( slapi_attr_type_cmp( oc->oc_allowed[j],
+ type, SLAPI_TYPE_CMP_SUBTYPE ) == 0 ||
+ strcmp( oc->oc_allowed[j],"*" ) == 0 ) {
+ rc = 0;
+ break;
+ }
+ }
+ /* maybe the next oc allows it */
+ }
+ }
+
+ if ( 0 != rc ) {
+ char errtext[ BUFSIZ ];
+ char ebuf[ BUFSIZ ];
+ char ebuf2[ BUFSIZ ];
+ LDAPDebug( LDAP_DEBUG_ANY,
+ "Entry \"%s\" -- attribute \"%s\" not allowed\n",
+ escape_string( slapi_entry_get_dn_const(e), ebuf ),
+ escape_string( type, ebuf2 ),
+ 0);
+
+ if (pb) {
+ PR_snprintf( errtext, sizeof( errtext ),
+ "attribute \"%s\" not allowed\n",
+ escape_string( type, ebuf2 ) );
+ slapi_pblock_set( pb, SLAPI_PB_RESULT_TEXT, errtext );
+ }
+ }
+
+ return rc;
+}
+
+
+
+
+/*
+ * oc_find_name() will return a strdup'd string or NULL if the objectclass
+ * could not be found.
+ */
+char *
+oc_find_name( const char *name_or_oid )
+{
+ struct objclass *oc;
+ char *ocname = NULL;
+
+ oc_lock_read();
+ if ( NULL != ( oc = oc_find_nolock( name_or_oid ))) {
+ ocname = slapi_ch_strdup( oc->oc_name );
+ }
+ oc_unlock();
+
+ return ocname;
+}
+
+
+/*
+ * oc_find_nolock will return a pointer to the objectclass which has the
+ * same name OR oid.
+ * NULL is returned if no match is found or `name_or_oid' is NULL.
+ */
+static struct objclass *
+oc_find_nolock( const char *ocname_or_oid )
+{
+ struct objclass *oc;
+
+ if ( NULL != ocname_or_oid ) {
+ if ( !schema_ignore_trailing_spaces ) {
+ for ( oc = g_get_global_oc_nolock(); oc != NULL; oc = oc->oc_next ) {
+ if ( ( strcasecmp( oc->oc_name, ocname_or_oid ) == 0 )
+ || ( oc->oc_oid &&
+ strcasecmp( oc->oc_oid, ocname_or_oid ) == 0 )) {
+ return( oc );
+ }
+ }
+ } else {
+ const char *p;
+ size_t len;
+
+ /*
+ * Ignore trailing spaces when comparing object class names.
+ */
+ for ( p = ocname_or_oid, len = 0; (*p != '\0') && (*p != ' ');
+ p++, len++ ) {
+ ; /* NULL */
+ }
+
+ for ( oc = g_get_global_oc_nolock(); oc != NULL; oc = oc->oc_next ) {
+ if ( ( (strncasecmp( oc->oc_name, ocname_or_oid, len ) == 0)
+ && (len == strlen(oc->oc_name)) )
+ ||
+ ( oc->oc_oid &&
+ ( strncasecmp( oc->oc_oid, ocname_or_oid, len ) == 0)
+ && (len == strlen(oc->oc_oid)) ) ) {
+ return( oc );
+ }
+ }
+ }
+ }
+
+ return( NULL );
+}
+
+/*
+ * oc_find_oid_nolock will return a pointer to the objectclass which has
+ * the same oid.
+ * NULL is returned if no match is found or `ocoid' is NULL.
+ */
+static struct objclass *
+oc_find_oid_nolock( const char *ocoid )
+{
+ struct objclass *oc;
+
+ if ( NULL != ocoid ) {
+ for ( oc = g_get_global_oc_nolock(); oc != NULL; oc = oc->oc_next ) {
+ if ( ( oc->oc_oid &&
+ ( strcasecmp( oc->oc_oid, ocoid ) == 0)) ){
+ return( oc );
+ }
+ }
+ }
+
+ return( NULL );
+}
+
+
+/*
+ We need to keep the objectclasses in the same order as defined in the ldif files. If not
+ SUP dependencies will break. When the user redefines an existing objectclass this code
+ makes sure it is put back in the same order it was read to from the ldif file. It also
+ verifies that the entries oc_superior value preceeds it in the chain. If not it will not
+ allow the entry to be added. This makes sure that the ldif will be written back correctly.
+*/
+
+static int
+oc_replace_nolock(const char *ocname, struct objclass *newoc) {
+ struct objclass *oc, *pnext;
+ int rc = LDAP_SUCCESS;
+ PRBool saw_sup=PR_FALSE;
+
+ oc = g_get_global_oc_nolock();
+
+ if(newoc->oc_superior == NULL)
+ {
+ saw_sup=PR_TRUE;
+ }
+ /* don't check SUP dependency for first one because it always/should be top */
+ if (strcasecmp (oc->oc_name, ocname) == 0) {
+ newoc->oc_next=oc->oc_next;
+ g_set_global_oc_nolock ( newoc );
+ oc_free( &oc );
+ } else {
+ for (pnext = oc ; pnext != NULL;
+ oc = pnext, pnext = pnext->oc_next) {
+ if((pnext->oc_name != NULL) && (newoc->oc_superior != NULL)) {
+ if(strcasecmp( pnext->oc_name, newoc->oc_superior) == 0)
+ {
+ saw_sup=PR_TRUE;
+ }
+ }
+ if (strcasecmp ( pnext->oc_name, ocname ) == 0) {
+ if(saw_sup)
+ {
+ oc->oc_next=newoc;
+ newoc->oc_next=pnext->oc_next;
+ oc_free( &pnext );
+ break;
+
+ } else
+ {
+ rc = LDAP_TYPE_OR_VALUE_EXISTS;
+ break;
+ }
+
+ }
+ }
+ }
+ return rc;
+}
+
+
+static int
+oc_delete_nolock (char *ocname)
+{
+ struct objclass *oc, *pnext;
+ int rc = 0; /* failure */
+
+ oc = g_get_global_oc_nolock();
+
+ /* special case if we're removing the first oc */
+ if (strcasecmp (oc->oc_name, ocname) == 0) {
+ g_set_global_oc_nolock ( oc->oc_next );
+ oc_free( &oc );
+ rc = 1;
+ } else {
+ for (pnext = oc->oc_next ; pnext != NULL;
+ oc = pnext, pnext = pnext->oc_next) {
+ if (strcasecmp ( pnext->oc_name, ocname ) == 0) {
+ oc->oc_next = pnext->oc_next;
+ oc_free( &pnext );
+ rc = 1;
+ break;
+ }
+ }
+ }
+
+ return rc;
+}
+
+
+/*
+ * Compare two objectclass definitions for equality. Return PR_TRUE if
+ * they are equivalent and PR_FALSE if not.
+ *
+ * The oc_required and oc_allowed arrays are ignored.
+ * The string "user defined" is ignored within the origins array.
+ * The following flags are ignored:
+ * OC_FLAG_STANDARD_OC
+ * OC_FLAG_USER_OC
+ * OC_FLAG_REDEFINED_OC
+ */
+static PRBool
+oc_equal( struct objclass *oc1, struct objclass *oc2 )
+{
+ PRUint8 flagmask;
+
+ if ( schema_strcmp( oc1->oc_name, oc2->oc_name ) != 0
+ || schema_strcmp( oc1->oc_desc, oc2->oc_desc ) != 0
+ || schema_strcmp( oc1->oc_oid, oc2->oc_oid ) != 0
+ || schema_strcmp( oc1->oc_superior, oc2->oc_superior ) != 0 ) {
+ return PR_FALSE;
+ }
+
+ flagmask = ~(OC_FLAG_STANDARD_OC | OC_FLAG_USER_OC | OC_FLAG_REDEFINED_OC);
+ if ( oc1->oc_kind != oc2->oc_kind
+ || ( oc1->oc_flags & flagmask ) != ( oc2->oc_flags & flagmask )) {
+ return PR_FALSE;
+ }
+
+ if ( schema_strcmp_array( oc1->oc_orig_required, oc2->oc_orig_required,
+ NULL ) != 0
+ || schema_strcmp_array( oc1->oc_orig_allowed, oc2->oc_orig_allowed,
+ NULL ) != 0
+ || schema_strcmp_array( oc1->oc_origin, oc2->oc_origin,
+ schema_user_defined_origin[0] ) != 0 ) {
+ return PR_FALSE;
+ }
+
+ return PR_TRUE;
+}
+
+
+#ifdef OC_DEBUG
+
+static int
+oc_print( struct objclass *oc )
+{
+ int i;
+
+ printf( "object class %s\n", oc->oc_name );
+ if ( oc->oc_required != NULL ) {
+ printf( "\trequires %s", oc->oc_required[0] );
+ for ( i = 1; oc->oc_required[i] != NULL; i++ ) {
+ printf( ",%s", oc->oc_required[i] );
+ }
+ printf( "\n" );
+ }
+ if ( oc->oc_allowed != NULL ) {
+ printf( "\tallows %s", oc->oc_allowed[0] );
+ for ( i = 1; oc->oc_allowed[i] != NULL; i++ ) {
+ printf( ",%s", oc->oc_allowed[i] );
+ }
+ printf( "\n" );
+ }
+ return 0;
+}
+#endif
+
+
+/*
+ * Compare two attrsyntax definitions for equality. Return PR_TRUE if
+ * they are equivalent and PR_FALSE if not.
+ *
+ * The string "user defined" is ignored within the origins array.
+ * The following flags are ignored:
+ * SLAPI_ATTR_FLAG_STD_ATTR
+ * SLAPI_ATTR_FLAG_NOLOCKING
+ * SLAPI_ATTR_FLAG_OVERRIDE
+ */
+static PRBool
+attr_syntax_equal( struct asyntaxinfo *asi1, struct asyntaxinfo *asi2 )
+{
+ unsigned long flagmask;
+
+ flagmask = ~( SLAPI_ATTR_FLAG_STD_ATTR | SLAPI_ATTR_FLAG_NOLOCKING
+ | SLAPI_ATTR_FLAG_OVERRIDE );
+
+ if ( schema_strcmp( asi1->asi_oid, asi2->asi_oid ) != 0
+ || schema_strcmp( asi1->asi_name, asi2->asi_name ) != 0
+ || schema_strcmp( asi1->asi_desc, asi2->asi_desc ) != 0
+ || schema_strcmp( asi1->asi_superior, asi2->asi_superior ) != 0
+ || schema_strcmp( asi1->asi_mr_equality, asi2->asi_mr_equality )
+ != 0
+ || schema_strcmp( asi1->asi_mr_ordering, asi2->asi_mr_ordering )
+ != 0
+ || schema_strcmp( asi1->asi_mr_substring,
+ asi2->asi_mr_substring ) != 0 ) {
+ return PR_FALSE;
+ }
+
+ if ( schema_strcmp_array( asi1->asi_aliases, asi2->asi_aliases, NULL ) != 0
+ || schema_strcmp_array( asi1->asi_origin, asi2->asi_origin,
+ schema_user_defined_origin[0] ) != 0
+ || asi1->asi_plugin != asi2->asi_plugin
+ || ( asi1->asi_flags & flagmask ) !=
+ ( asi2->asi_flags & flagmask )
+ || asi1->asi_syntaxlength != asi2->asi_syntaxlength ) {
+ return PR_FALSE;
+ }
+
+ return PR_TRUE;
+}
+
+
+
+/*
+ * Like strcmp(), but a NULL string pointer is treated as equivalent to
+ * another NULL one and NULL is treated as "less than" all non-NULL values.
+ */
+static int
+schema_strcmp( const char *s1, const char *s2 )
+{
+ if ( s1 == NULL ) {
+ if ( s2 == NULL ) {
+ return 0; /* equal */
+ }
+ return -1; /* s1 < s2 */
+ }
+
+ if ( s2 == NULL ) {
+ return 1; /* s1 > s2 */
+ }
+
+ return strcmp( s1, s2 );
+}
+
+
+/*
+ * Invoke strcmp() on each string in an array. If one array has fewer elements
+ * than the other, it is treated as "less than" the other. Two NULL or
+ * empty arrays (or one NULL and one empty) are considered to be equivalent.
+ *
+ * If ignorestr is non-NULL, occurrences of that string are ignored.
+ */
+static int
+schema_strcmp_array( char **sa1, char **sa2, const char *ignorestr )
+{
+ int i1, i2, rc;
+
+ if ( sa1 == NULL || *sa1 == NULL ) {
+ if ( sa2 == NULL || *sa2 == NULL ) {
+ return 0; /* equal */
+ }
+ return -1; /* sa1 < sa2 */
+ }
+
+ if ( sa2 == NULL || *sa2 == NULL ) {
+ return 1; /* sa1 > sa2 */
+ }
+
+ rc = 0;
+ i1 = i2 = 0;
+ while ( sa1[i1] != NULL && sa2[i2] != NULL ) {
+ if ( NULL != ignorestr ) {
+ if ( 0 == strcmp( sa1[i1], ignorestr )) {
+ ++i1;
+ continue;
+ }
+ if ( 0 == strcmp( sa2[i2], ignorestr )) {
+ ++i2;
+ continue;
+ }
+ }
+ rc = strcmp( sa1[i1], sa2[i2] );
+ ++i1;
+ ++i2;
+ }
+
+ if ( rc == 0 ) { /* all matched so far */
+ /* get rid of trailing ignored strings (if any) */
+ if ( NULL != ignorestr ) {
+ if ( sa1[i1] != NULL && 0 == strcmp( sa1[i1], ignorestr )) {
+ ++i1;
+ }
+ if ( sa2[i2] != NULL && 0 == strcmp( sa2[i2], ignorestr )) {
+ ++i2;
+ }
+ }
+
+ /* check for differing array lengths */
+ if ( sa2[i2] != NULL ) {
+ rc = -1; /* sa1 < sa2 -- fewer elements */
+ } else if ( sa1[i1] != NULL ) {
+ rc = 1; /* sa1 > sa2 -- more elements */
+ }
+ }
+
+ return rc;
+}
+
+
+struct attr_enum_wrapper {
+ Slapi_Attr **attrs;
+ int enquote_sup_oc;
+ struct sizedbuffer *psbAttrTypes;
+ int user_defined_only;
+ int schema_ds4x_compat;
+};
+
+static int
+schema_attr_enum_callback(struct asyntaxinfo *asip, void *arg)
+{
+ struct attr_enum_wrapper *aew = (struct attr_enum_wrapper *)arg;
+ int aliaslen = 0;
+ struct berval val;
+ struct berval *vals[2] = {0, 0};
+ const char *attr_desc, *syntaxoid;
+ char *outp, syntaxlengthbuf[ 128 ];
+ int i;
+
+ vals[0] = &val;
+
+ if (!asip) {
+ LDAPDebug(LDAP_DEBUG_ANY,
+ "Error: no attribute types in schema_attr_enum_callback\n",
+ 0, 0, 0);
+ return ATTR_SYNTAX_ENUM_NEXT;
+ }
+
+ if (aew->user_defined_only &&
+ (asip->asi_flags & SLAPI_ATTR_FLAG_STD_ATTR)) {
+ return ATTR_SYNTAX_ENUM_NEXT; /* not user defined */
+ }
+
+ if ( aew->schema_ds4x_compat ) {
+ attr_desc = ( asip->asi_flags & SLAPI_ATTR_FLAG_STD_ATTR)
+ ? ATTR_STANDARD_STRING : ATTR_USERDEF_STRING;
+ } else {
+ attr_desc = asip->asi_desc;
+ }
+
+ if ( asip->asi_aliases != NULL ) {
+ for ( i = 0; asip->asi_aliases[i] != NULL; ++i ) {
+ aliaslen += strlen( asip->asi_aliases[i] );
+ }
+ }
+
+ syntaxoid = plugin_syntax2oid(asip->asi_plugin);
+
+ if ( !aew->schema_ds4x_compat &&
+ asip->asi_syntaxlength != SLAPI_SYNTAXLENGTH_NONE ) {
+ /* sprintf() is safe because syntaxlengthbuf is large enough */
+ sprintf( syntaxlengthbuf, "{%d}", asip->asi_syntaxlength );
+ } else {
+ *syntaxlengthbuf = '\0';
+ }
+
+ /*
+ * XXX: 256 is a magic number... it must be big enough to account for
+ * all of the fixed sized items we output.
+ */
+ sizedbuffer_allocate(aew->psbAttrTypes,256+strlen(asip->asi_oid)+
+ strlen(asip->asi_name) +
+ aliaslen + strlen_null_ok(attr_desc) +
+ strlen(syntaxoid) +
+ strlen_null_ok(asip->asi_superior) +
+ strlen_null_ok(asip->asi_mr_equality) +
+ strlen_null_ok(asip->asi_mr_ordering) +
+ strlen_null_ok(asip->asi_mr_substring) +
+ strcat_qdlist( NULL, "X-ORIGIN", asip->asi_origin ));
+
+ /*
+ * Overall strategy is to maintain a pointer to the next location in
+ * the output buffer so we can do simple strcpy's, sprintf's, etc.
+ * That pointer is `outp'. Each item that is output includes a trailing
+ * space, so there is no need to include a leading one in the next item.
+ */
+ outp = aew->psbAttrTypes->buffer;
+ outp += sprintf(outp, "( %s NAME ", asip->asi_oid);
+ if ( asip->asi_aliases == NULL || asip->asi_aliases[0] == NULL ) {
+ /* only one name */
+ outp += sprintf(outp, "'%s' ", asip->asi_name);
+ } else {
+ /* several names */
+ outp += sprintf(outp, "( '%s' ", asip->asi_name);
+ for ( i = 0; asip->asi_aliases[i] != NULL; ++i ) {
+ outp += sprintf(outp, "'%s' ", asip->asi_aliases[i]);
+ }
+ outp += strcpy_count(outp, ") ");
+ }
+
+ /* DESC is optional */
+ if (attr_desc && *attr_desc) {
+ outp += sprintf( outp, "DESC '%s'", attr_desc );
+ }
+ if ( !aew->schema_ds4x_compat &&
+ ( asip->asi_flags & SLAPI_ATTR_FLAG_OBSOLETE )) {
+ outp += strcpy_count( outp, schema_obsolete_with_spaces );
+ } else {
+ outp += strcpy_count( outp, " " );
+ }
+
+ if ( !aew->schema_ds4x_compat ) {
+ outp += put_tagged_oid( outp, "SUP ",
+ asip->asi_superior, NULL, aew->enquote_sup_oc );
+ outp += put_tagged_oid( outp, "EQUALITY ",
+ asip->asi_mr_equality, NULL, aew->enquote_sup_oc );
+ outp += put_tagged_oid( outp, "ORDERING ",
+ asip->asi_mr_ordering, NULL, aew->enquote_sup_oc );
+ outp += put_tagged_oid( outp, "SUBSTR ",
+ asip->asi_mr_substring, NULL, aew->enquote_sup_oc );
+ }
+
+ outp += put_tagged_oid( outp, "SYNTAX ", syntaxoid, syntaxlengthbuf,
+ aew->enquote_sup_oc );
+
+ if (asip->asi_flags & SLAPI_ATTR_FLAG_SINGLE) {
+ outp += strcpy_count(outp, "SINGLE-VALUE ");
+ }
+ if ( !aew->schema_ds4x_compat ) {
+ if (asip->asi_flags & SLAPI_ATTR_FLAG_COLLECTIVE ) {
+ outp += strcpy_count( outp, 1 + schema_collective_with_spaces );
+ }
+ if (asip->asi_flags & SLAPI_ATTR_FLAG_NOUSERMOD ) {
+ outp += strcpy_count( outp, 1 + schema_nousermod_with_spaces );
+ }
+ if (asip->asi_flags & SLAPI_ATTR_FLAG_OPATTR) {
+ outp += strcpy_count(outp, "USAGE directoryOperation ");
+ }
+
+ outp += strcat_qdlist( outp, "X-ORIGIN", asip->asi_origin );
+ }
+
+ outp += strcpy_count(outp, ")");
+
+ val.bv_val = aew->psbAttrTypes->buffer;
+ val.bv_len = outp - aew->psbAttrTypes->buffer;
+ attrlist_merge(aew->attrs, "attributetypes", vals);
+
+ return ATTR_SYNTAX_ENUM_NEXT;
+}
+
+
+struct syntax_enum_wrapper {
+ Slapi_Attr **attrs;
+ struct sizedbuffer *psbSyntaxDescription;
+};
+
+static int
+schema_syntax_enum_callback(char **names, Slapi_PluginDesc *plugindesc,
+ void *arg)
+{
+ struct syntax_enum_wrapper *sew = (struct syntax_enum_wrapper *)arg;
+ char *oid, *desc;
+ int i;
+ struct berval val;
+ struct berval *vals[2] = {0, 0};
+ vals[0] = &val;
+
+ oid = NULL;
+ if ( names != NULL ) {
+ for ( i = 0; names[i] != NULL; ++i ) {
+ if ( isdigit( names[i][0] )) {
+ oid = names[i];
+ break;
+ }
+ }
+ }
+
+ if ( oid == NULL ) { /* must have an OID */
+ LDAPDebug(LDAP_DEBUG_ANY, "Error: no OID found in"
+ " schema_syntax_enum_callback for syntax %s\n",
+ ( names == NULL ) ? "unknown" : names[0], 0, 0);
+ return 1;
+ }
+
+ desc = names[0]; /* by convention, the first name is the "official" one */
+
+ /*
+ * RFC 2252 section 4.3.3 Syntax Description says:
+ *
+ * The following BNF may be used to associate a short description with a
+ * syntax OBJECT IDENTIFIER. Implementors should note that future
+ * versions of this document may expand this definition to include
+ * additional terms. Terms whose identifier begins with "X-" are
+ * reserved for private experiments, and MUST be followed by a
+ * <qdstrings>.
+ *
+ * SyntaxDescription = "(" whsp
+ * numericoid whsp
+ * [ "DESC" qdstring ]
+ * whsp ")"
+ *
+ * And section 5.3.1 ldapSyntaxes says:
+ *
+ * Servers MAY use this attribute to list the syntaxes which are
+ * implemented. Each value corresponds to one syntax.
+ *
+ * ( 1.3.6.1.4.1.1466.101.120.16 NAME 'ldapSyntaxes'
+ * EQUALITY objectIdentifierFirstComponentMatch
+ * SYNTAX 1.3.6.1.4.1.1466.115.121.1.54 USAGE directoryOperation )
+ */
+ if ( desc == NULL ) {
+ /* allocate enough room for "( )" and '\0' at end */
+ sizedbuffer_allocate(sew->psbSyntaxDescription, strlen(oid) + 5);
+ sprintf(sew->psbSyntaxDescription->buffer, "( %s )", oid );
+ } else {
+ /* allocate enough room for "( ) DESC '' " and '\0' at end */
+ sizedbuffer_allocate(sew->psbSyntaxDescription,
+ strlen(oid) + strlen(desc) + 13);
+ sprintf(sew->psbSyntaxDescription->buffer, "( %s DESC '%s' )",
+ oid, desc );
+ }
+
+ val.bv_val = sew->psbSyntaxDescription->buffer;
+ val.bv_len = strlen(sew->psbSyntaxDescription->buffer);
+ attrlist_merge(sew->attrs, "ldapSyntaxes", vals);
+
+ return 1;
+}
+
+struct listargs{
+ char **attrs;
+ unsigned long flag;
+};
+
+static int
+schema_list_attributes_callback(struct asyntaxinfo *asi, void *arg)
+{
+ struct listargs *aew = (struct listargs *)arg;
+
+ if (!asi) {
+ LDAPDebug(LDAP_DEBUG_ANY, "Error: no attribute types in schema_list_attributes_callback\n",
+ 0, 0, 0);
+ return ATTR_SYNTAX_ENUM_NEXT;
+ }
+ if (aew->flag && (asi->asi_flags & aew->flag)) {
+ charray_add(&aew->attrs, slapi_ch_strdup(asi->asi_name));
+ if (NULL != asi->asi_aliases) {
+ int i;
+
+ for ( i = 0; asi->asi_aliases[i] != NULL; ++i ) {
+ charray_add(&aew->attrs,
+ slapi_ch_strdup(asi->asi_aliases[i]));
+ }
+ }
+ }
+ return ATTR_SYNTAX_ENUM_NEXT;
+}
+
+/* Return the list of attributes names matching attribute flags */
+
+char ** slapi_schema_list_attribute_names(unsigned long flag)
+{
+ struct listargs aew;
+ memset(&aew,0,sizeof(struct listargs));
+ aew.flag=flag;
+
+ attr_syntax_enumerate_attrs(schema_list_attributes_callback, &aew,
+ PR_FALSE);
+ return aew.attrs;
+}
+
+
+/*
+ * returntext is always at least SLAPI_DSE_RETURNTEXT_SIZE bytes in size.
+ */
+int
+read_schema_dse(
+ Slapi_PBlock *pb,
+ Slapi_Entry *pschema_info_e,
+ Slapi_Entry *entryAfter,
+ int *returncode,
+ char *returntext /* not used */,
+ void *arg /* not used */ )
+{
+ struct berval val;
+ struct berval *vals[2];
+ int i;
+ struct objclass *oc;
+ struct matchingRuleList *mrl=NULL;
+ struct sizedbuffer *psbObjectClasses= sizedbuffer_construct(BUFSIZ);
+ struct sizedbuffer *psbAttrTypes= sizedbuffer_construct(BUFSIZ);
+ struct sizedbuffer *psbMatchingRule= sizedbuffer_construct(BUFSIZ);
+ struct sizedbuffer *psbSyntaxDescription = sizedbuffer_construct(BUFSIZ);
+ struct attr_enum_wrapper aew;
+ struct syntax_enum_wrapper sew;
+ int enquote_sup_oc = config_get_enquote_sup_oc();
+ int user_defined_only = 0;
+ char **allowed, **required;
+ char *mr_desc, *mr_name, *oc_description;
+ int schema_ds4x_compat = config_get_ds4_compatible_schema();
+ const CSN *csn;
+
+ vals[0] = &val;
+ vals[1] = NULL;
+
+ slapi_pblock_get(pb, SLAPI_SCHEMA_USER_DEFINED_ONLY, (void*)&user_defined_only);
+
+ attrlist_delete (&pschema_info_e->e_attrs, "objectclasses");
+ attrlist_delete (&pschema_info_e->e_attrs, "attributetypes");
+ attrlist_delete (&pschema_info_e->e_attrs, "matchingRules");
+ attrlist_delete (&pschema_info_e->e_attrs, "ldapSyntaxes");
+ /*
+ attrlist_delete (&pschema_info_e->e_attrs, "matchingRuleUse");
+ */
+
+ schema_dse_lock_read();
+
+ /* return the objectclasses */
+ oc_lock_read();
+ for (oc = g_get_global_oc_nolock(); oc != NULL; oc = oc->oc_next)
+ {
+ size_t size= 0;
+ int need_extra_space = 1;
+
+ if (user_defined_only &&
+ !(oc->oc_flags & OC_FLAG_USER_OC ||
+ oc->oc_flags & OC_FLAG_REDEFINED_OC)) {
+ continue;
+ }
+
+ /*
+ * XXX: 256 is a magic number... it must be large enough to fit
+ * all of the fixed size items including description (DESC),
+ * kind (STRUCTURAL, AUXILIARY, or ABSTRACT), and the OBSOLETE flag.
+ */
+ if ( schema_ds4x_compat ) {
+ oc_description = (oc->oc_flags & OC_FLAG_STANDARD_OC) ?
+ OC_STANDARD_STRING : OC_USERDEF_STRING;
+ } else {
+ oc_description = oc->oc_desc;
+ }
+
+ size= 256+strlen_null_ok(oc->oc_oid) + strlen(oc->oc_name) +
+ strlen_null_ok(oc_description) +
+ strcat_qdlist( NULL, "X-ORIGIN", oc->oc_origin );
+ required = schema_ds4x_compat ? oc->oc_required : oc->oc_orig_required;
+ if (required && required[0]) {
+ for (i = 0 ; required[i]; i++)
+ size+= 16 + strlen(required[i]);
+ }
+ allowed = schema_ds4x_compat ? oc->oc_allowed : oc->oc_orig_allowed;
+ if (allowed && allowed[0]) {
+ for (i = 0 ; allowed[i]; i++)
+ size+= 16 + strlen(allowed[i]);
+ }
+ sizedbuffer_allocate(psbObjectClasses,size);
+ /* put the OID and the NAME */
+ sprintf (psbObjectClasses->buffer,
+ "( %s NAME '%s'",
+ (oc->oc_oid) ? oc->oc_oid : "",
+ oc->oc_name);
+ /* The DESC (description) is OPTIONAL */
+ if (oc_description && *oc_description) {
+ strcat(psbObjectClasses->buffer, " DESC '");
+ strcat(psbObjectClasses->buffer, oc_description);
+ strcat(psbObjectClasses->buffer, "'");
+ need_extra_space = 1;
+ }
+ /* put the OBSOLETE keyword */
+ if (!schema_ds4x_compat && (oc->oc_flags & OC_FLAG_OBSOLETE)) {
+ strcat(psbObjectClasses->buffer, schema_obsolete_with_spaces);
+ need_extra_space = 0;
+ }
+ /* put the SUP superior objectclass */
+ if (0 != strcasecmp(oc->oc_name, "top")) { /* top has no SUP */
+ /* some AUXILIARY AND ABSTRACT objectclasses may not have a SUP either */
+ /* for compatability, every objectclass other than top must have a SUP */
+ if (schema_ds4x_compat || (oc->oc_superior && *oc->oc_superior)) {
+ if (need_extra_space) {
+ strcat(psbObjectClasses->buffer, " ");
+ }
+ strcat(psbObjectClasses->buffer, "SUP ");
+ strcat(psbObjectClasses->buffer, (enquote_sup_oc ? "'" : ""));
+ strcat(psbObjectClasses->buffer,
+ ((oc->oc_superior && *oc->oc_superior) ?
+ oc->oc_superior : "top"));
+ strcat(psbObjectClasses->buffer, (enquote_sup_oc ? "'" : ""));
+ need_extra_space = 1;
+ }
+ }
+ /* put the kind of objectclass */
+ if (schema_ds4x_compat) {
+ if (need_extra_space) {
+ strcat(psbObjectClasses->buffer, " ");
+ }
+ } else {
+ strcat(psbObjectClasses->buffer, schema_oc_kind_strings_with_spaces[oc->oc_kind]);
+ }
+
+ strcat_oids( psbObjectClasses->buffer, "MUST", required,
+ schema_ds4x_compat );
+ strcat_oids( psbObjectClasses->buffer, "MAY", allowed,
+ schema_ds4x_compat );
+ if ( !schema_ds4x_compat ) {
+ strcat_qdlist( psbObjectClasses->buffer, "X-ORIGIN", oc->oc_origin );
+ }
+ strcat( psbObjectClasses->buffer, ")");
+
+ val.bv_val = psbObjectClasses->buffer;
+ val.bv_len = strlen (psbObjectClasses->buffer);
+ attrlist_merge (&pschema_info_e->e_attrs, "objectclasses", vals);
+ }
+ oc_unlock();
+
+ /* now return the attrs */
+
+ aew.attrs = &pschema_info_e->e_attrs;
+ aew.enquote_sup_oc = enquote_sup_oc;
+ aew.psbAttrTypes = psbAttrTypes;
+ aew.user_defined_only = user_defined_only;
+ aew.schema_ds4x_compat = schema_ds4x_compat;
+ attr_syntax_enumerate_attrs(schema_attr_enum_callback, &aew, PR_FALSE);
+
+ /* return the set of matching rules we support */
+ for (mrl = g_get_global_mrl(); !user_defined_only && mrl != NULL; mrl = mrl->mrl_next) {
+ mr_name = mrl->mr_entry->mr_name ? mrl->mr_entry->mr_name : "";
+ mr_desc = mrl->mr_entry->mr_desc ? mrl->mr_entry->mr_desc : "";
+ sizedbuffer_allocate(psbMatchingRule,128+
+ strlen_null_ok(mrl->mr_entry->mr_oid) +
+ strlen(mr_name)+ strlen(mr_desc)+
+ strlen_null_ok(mrl->mr_entry->mr_syntax));
+ if ( schema_ds4x_compat ) {
+ sprintf(psbMatchingRule->buffer,
+ "( %s NAME '%s' DESC '%s' SYNTAX %s%s%s )",
+ (mrl->mr_entry->mr_oid ? mrl->mr_entry->mr_oid : ""),
+ mr_name, mr_desc,
+ enquote_sup_oc ? "'" : "",
+ mrl->mr_entry->mr_syntax ? mrl->mr_entry->mr_syntax : "" ,
+ enquote_sup_oc ? "'" : "");
+ } else if ( NULL != mrl->mr_entry->mr_oid &&
+ NULL != mrl->mr_entry->mr_syntax ) {
+ char *p;
+
+ sprintf(psbMatchingRule->buffer, "( %s ", mrl->mr_entry->mr_oid );
+ p = psbMatchingRule->buffer + strlen(psbMatchingRule->buffer);
+ if ( *mr_name != '\0' ) {
+ sprintf(p, "NAME '%s' ", mr_name );
+ p += strlen(p);
+ }
+
+ if ( *mr_desc != '\0' ) {
+ sprintf(p, "DESC '%s' ", mr_desc );
+ p += strlen(p);
+ }
+ sprintf(p, "SYNTAX %s )", mrl->mr_entry->mr_syntax );
+ }
+
+ val.bv_val = psbMatchingRule->buffer;
+ val.bv_len = strlen (psbMatchingRule->buffer);
+ attrlist_merge (&pschema_info_e->e_attrs, "matchingRules", vals);
+ }
+
+ if ( !schema_ds4x_compat && !user_defined_only ) {
+ /* return the set of syntaxes we support */
+ sew.attrs = &pschema_info_e->e_attrs;
+ sew.psbSyntaxDescription = psbSyntaxDescription;
+ plugin_syntax_enumerate(schema_syntax_enum_callback, &sew);
+ }
+
+ csn = g_get_global_schema_csn();
+ if (NULL != csn) {
+ char csn_str[CSN_STRSIZE + 1];
+ csn_as_string(csn, PR_FALSE, csn_str);
+ slapi_entry_attr_delete(pschema_info_e, "nsschemacsn");
+ slapi_entry_add_string(pschema_info_e, "nsschemacsn", csn_str);
+ }
+
+ schema_dse_unlock();
+
+ sizedbuffer_destroy(psbObjectClasses);
+ sizedbuffer_destroy(psbAttrTypes);
+ sizedbuffer_destroy(psbMatchingRule);
+ sizedbuffer_destroy(psbSyntaxDescription);
+ *returncode= LDAP_SUCCESS;
+ return SLAPI_DSE_CALLBACK_OK;
+}
+
+/* helper for deleting mods (we do not want to be applied) from the mods array */
+static void
+mod_free(LDAPMod *mod)
+{
+ ber_bvecfree(mod->mod_bvalues);
+ slapi_ch_free((void**)&(mod->mod_type));
+ slapi_ch_free((void**)&mod);
+}
+
+/*
+ * modify_schema_dse: called by do_modify() when target is cn=schema
+ *
+ * Add/Delete attributes and objectclasses from the schema
+ * Supported mod_ops are LDAP_MOD_DELETE and LDAP_MOD_ADD
+ *
+ * Note that the in-memory DSE Slapi_Entry object does NOT hold the
+ * attributeTypes and objectClasses attributes -- it only holds
+ * non-schema related attributes such as aci.
+ *
+ * returntext is always at least SLAPI_DSE_RETURNTEXT_SIZE bytes in size.
+ */
+int
+modify_schema_dse (Slapi_PBlock *pb, Slapi_Entry *entryBefore, Slapi_Entry *entryAfter, int *returncode, char *returntext, void *arg)
+{
+ int i, rc= SLAPI_DSE_CALLBACK_OK; /* default is to apply changes to the DSE */
+ char *schema_dse_attr_name;
+ LDAPMod **mods = NULL;
+ int num_mods = 0; /* count the number of mods */
+ int schema_ds4x_compat = config_get_ds4_compatible_schema();
+ int reapply_mods = 0;
+ int is_replicated_operation = 0;
+
+ slapi_pblock_get( pb, SLAPI_MODIFY_MODS, &mods );
+ slapi_pblock_get( pb, SLAPI_IS_REPLICATED_OPERATION, &is_replicated_operation);
+ schema_dse_lock_write();
+
+ /*
+ * Process each modification. Stop as soon as we hit an error.
+ *
+ * XXXmcs: known bugs: we don't operate on a copy of the schema, so it
+ * is possible for some schema changes to be made but not all of them.
+ * True for DS 4.x as well, although it tried to keep going even after
+ * an error was detected (which was very wrong).
+ */
+ for (i = 0; rc == SLAPI_DSE_CALLBACK_OK && mods[i]; i++) {
+ schema_dse_attr_name = (char *) mods[i]->mod_type;
+ num_mods++; /* incr the number of mods */
+
+ /*
+ * skip attribute types that we do not recognize (the DSE code will
+ * handle them).
+ */
+ if ( !schema_type_is_interesting( schema_dse_attr_name )) {
+ continue;
+ }
+
+ /*
+ * Delete an objectclass or attribute
+ */
+ if ( (mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_DELETE) {
+ if (strcasecmp (mods[i]->mod_type, "objectclasses") == 0) {
+ *returncode = schema_delete_objectclasses (entryBefore, mods[i],
+ returntext, SLAPI_DSE_RETURNTEXT_SIZE, schema_ds4x_compat );
+ }
+ else if (strcasecmp (mods[i]->mod_type, "attributetypes") == 0) {
+ *returncode = schema_delete_attributes (entryBefore, mods[i],
+ returntext, SLAPI_DSE_RETURNTEXT_SIZE );
+ }
+ else {
+ *returncode= LDAP_NO_SUCH_ATTRIBUTE;
+ schema_create_errormsg( returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ schema_errprefix_generic, mods[i]->mod_type,
+ "Only object classes and attribute types may be deleted" );
+ }
+
+ if ( LDAP_SUCCESS != *returncode ) {
+ rc= SLAPI_DSE_CALLBACK_ERROR;
+ } else {
+ reapply_mods = 1;
+ }
+ }
+
+ /*
+ * Replace an objectclass,attribute, or schema CSN
+ */
+ else if ( (mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_REPLACE) {
+ int replace_allowed = 0;
+ slapdFrontendConfig_t *slapdFrontendConfig;
+
+ slapdFrontendConfig = getFrontendConfig();
+ CFG_LOCK_READ( slapdFrontendConfig );
+ if ( 0 == strcasecmp( slapdFrontendConfig->schemareplace,
+ CONFIG_SCHEMAREPLACE_STR_ON )) {
+ replace_allowed = 1;
+ } else if ( 0 == strcasecmp( slapdFrontendConfig->schemareplace,
+ CONFIG_SCHEMAREPLACE_STR_REPLICATION_ONLY )) {
+ replace_allowed = is_replicated_operation;
+ }
+ CFG_UNLOCK_READ( slapdFrontendConfig );
+
+ if ( !replace_allowed ) {
+ *returncode= LDAP_UNWILLING_TO_PERFORM;
+ schema_create_errormsg( returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ schema_errprefix_generic, mods[i]->mod_type,
+ "Replace is not allowed on the subschema subentry" );
+ rc = SLAPI_DSE_CALLBACK_ERROR;
+ } else {
+ if (strcasecmp (mods[i]->mod_type, "attributetypes") == 0) {
+ /*
+ * Replace all attribute types
+ */
+ *returncode = schema_replace_attributes( pb, mods[i], returntext,
+ SLAPI_DSE_RETURNTEXT_SIZE );
+ } else if (strcasecmp (mods[i]->mod_type, "objectclasses") == 0) {
+ /*
+ * Replace all objectclasses
+ */
+ *returncode = schema_replace_objectclasses( pb, mods[i],
+ returntext, SLAPI_DSE_RETURNTEXT_SIZE );
+ } else if (strcasecmp (mods[i]->mod_type, "nsschemacsn") == 0) {
+ if (is_replicated_operation) {
+ /* Update the schema CSN */
+ if (mods[i]->mod_bvalues && mods[i]->mod_bvalues[0] &&
+ mods[i]->mod_bvalues[0]->bv_val &&
+ mods[i]->mod_bvalues[0]->bv_len > 0) {
+ char new_csn_string[CSN_STRSIZE + 1];
+ CSN *new_schema_csn;
+ memcpy(new_csn_string, mods[i]->mod_bvalues[0]->bv_val,
+ mods[i]->mod_bvalues[0]->bv_len);
+ new_csn_string[mods[i]->mod_bvalues[0]->bv_len] = '\0';
+ new_schema_csn = csn_new_by_string(new_csn_string);
+ if (NULL != new_schema_csn) {
+ g_set_global_schema_csn(new_schema_csn); /* csn is consumed */
+ }
+ }
+ }
+ } else {
+ *returncode= LDAP_UNWILLING_TO_PERFORM; /* XXXmcs: best error? */
+ schema_create_errormsg( returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ schema_errprefix_generic, mods[i]->mod_type,
+ "Only object classes and attribute types may be replaced" );
+ }
+ }
+
+ if ( LDAP_SUCCESS != *returncode ) {
+ rc= SLAPI_DSE_CALLBACK_ERROR;
+ } else {
+ reapply_mods = 1; /* we have at least some modifications we need to reapply */
+ }
+ }
+
+
+ /*
+ * Add an objectclass or attribute
+ */
+ else if ( (mods[i]->mod_op & ~LDAP_MOD_BVALUES) == LDAP_MOD_ADD) {
+ if (strcasecmp (mods[i]->mod_type, "attributetypes") == 0) {
+ /*
+ * Add a new attribute
+ */
+ *returncode = schema_add_attribute ( pb, mods[i], returntext,
+ SLAPI_DSE_RETURNTEXT_SIZE, schema_ds4x_compat );
+ }
+ else if (strcasecmp (mods[i]->mod_type, "objectclasses") == 0) {
+ /*
+ * Add a new objectclass
+ */
+ *returncode = schema_add_objectclass ( pb, mods[i], returntext,
+ SLAPI_DSE_RETURNTEXT_SIZE, schema_ds4x_compat );
+ }
+ else {
+ if ( schema_ds4x_compat ) {
+ *returncode= LDAP_NO_SUCH_ATTRIBUTE;
+ } else {
+ *returncode= LDAP_UNWILLING_TO_PERFORM; /* XXXmcs: best error? */
+ }
+ schema_create_errormsg( returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ schema_errprefix_generic, mods[i]->mod_type,
+ "Only object classes and attribute types may be added" );
+ }
+
+ if ( LDAP_SUCCESS != *returncode ) {
+ rc= SLAPI_DSE_CALLBACK_ERROR;
+ } else {
+ reapply_mods = 1; /* we have at least some modifications we need to reapply */
+ }
+ }
+
+ /*
+ ** No value was specified to modify, the user probably tried
+ ** to delete all attributetypes or all objectclasses, which
+ ** isn't allowed
+ */
+ if (!mods[i]->mod_vals.modv_strvals)
+ {
+ if ( schema_ds4x_compat ) {
+ *returncode= LDAP_INVALID_SYNTAX;
+ } else {
+ *returncode= LDAP_UNWILLING_TO_PERFORM; /* XXXmcs: best error? */
+ }
+ schema_create_errormsg( returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+ schema_errprefix_generic, mods[i]->mod_type,
+ "No target attribute type or object class specified" );
+ rc= SLAPI_DSE_CALLBACK_ERROR;
+ }
+ }
+
+ if(rc==SLAPI_DSE_CALLBACK_OK && reapply_mods)
+ {
+ CSN *new_schema_csn;
+ int newindex = 0; /* mods array index */
+
+ /* tell the "unholy" dse_modify code to reapply the mods and use
+ that result instead of the initial result; we must remove the attributes
+ we manage in this code from the mods
+ */
+ slapi_pblock_set(pb, SLAPI_DSE_REAPPLY_MODS, (void *)&reapply_mods);
+
+ /* because we are reapplying the mods, we want the entryAfter to
+ look just like the entryBefore, except that "our" attributes
+ will have been removed
+ */
+ /* delete the mods from the mods array */
+ for (i = 0; i < num_mods ; i++) {
+ const char *attrname = mods[i]->mod_type;
+
+ /* delete this attr from the entry */
+ slapi_entry_attr_delete(entryAfter, attrname);
+
+ if ( schema_type_is_interesting( attrname )) {
+ mod_free(mods[i]);
+ mods[i] = NULL;
+ } else {
+ /* add the original value of the attr back to the entry after */
+ Slapi_Attr *origattr = NULL;
+ Slapi_ValueSet *origvalues = NULL;
+ slapi_entry_attr_find(entryBefore, attrname, &origattr);
+ if (NULL != origattr) {
+ slapi_attr_get_valueset(origattr, &origvalues);
+ if (NULL != origvalues) {
+ slapi_entry_add_valueset(entryAfter, attrname, origvalues);
+ slapi_valueset_free(origvalues);
+ }
+ }
+ mods[newindex++] = mods[i];
+ }
+ }
+ mods[newindex] = NULL;
+
+ /*
+ * Since we successfully updated the schema, we need to generate
+ * a new schema CSN for non-replicated operations.
+ */
+ /* XXXmcs: I wonder if we should update the schema CSN even when no
+ * attribute types or OCs were changed? That way, an administrator
+ * could force schema replication to occur by submitting a modify
+ * operation that did not really do anything, such as:
+ *
+ * dn:cn=schema
+ * changetype:modify
+ * replace:cn
+ * cn:schema
+ */
+ if (!is_replicated_operation)
+ {
+ new_schema_csn = csn_new();
+ if (NULL != new_schema_csn) {
+ char csn_str[CSN_STRSIZE + 1];
+ csn_set_replicaid(new_schema_csn, 0);
+ csn_set_time(new_schema_csn, current_time());
+ g_set_global_schema_csn(new_schema_csn);
+ slapi_entry_attr_delete(entryBefore, "nsschemacsn");
+ csn_as_string(new_schema_csn, PR_FALSE, csn_str);
+ slapi_entry_add_string(entryBefore, "nsschemacsn", csn_str);
+ }
+ }
+ }
+
+ schema_dse_unlock();
+
+ return rc;
+}
+
+CSN *
+dup_global_schema_csn()
+{
+ CSN *schema_csn;
+
+ schema_dse_lock_read();
+ schema_csn = csn_dup ( g_get_global_schema_csn() );
+ schema_dse_unlock();
+ return schema_csn;
+}
+
+/*
+ * Remove all attribute types and objectclasses from the entry and
+ * then add back the user defined ones based on the contents of the
+ * schema hash tables.
+ *
+ * Returns SLAPI_DSE_CALLBACK_OK is all goes well.
+ *
+ * returntext is always at least SLAPI_DSE_RETURNTEXT_SIZE bytes in size.
+ */
+static int
+refresh_user_defined_schema( Slapi_PBlock *pb, Slapi_Entry *pschema_info_e, Slapi_Entry *entryAfter, int *returncode, char *returntext, void *arg /* not used */ )
+{
+ int user_defined_only = 1;
+ int rc;
+ Slapi_PBlock *mypbptr = pb;
+ Slapi_PBlock mypb;
+ const CSN *schema_csn;
+
+ pblock_init(&mypb);
+
+ slapi_entry_attr_delete( pschema_info_e, "objectclasses");
+ slapi_entry_attr_delete( pschema_info_e, "attributetypes");
+
+ /* for write callbacks, no pb is supplied, so use our own */
+ if (!mypbptr) {
+ mypbptr = &mypb;
+ }
+
+ slapi_pblock_set(mypbptr, SLAPI_SCHEMA_USER_DEFINED_ONLY, &user_defined_only);
+ rc = read_schema_dse(mypbptr, pschema_info_e, NULL, returncode, returntext, NULL);
+ schema_csn = g_get_global_schema_csn();
+ if (NULL != schema_csn) {
+ char csn_str[CSN_STRSIZE + 1];
+ slapi_entry_attr_delete(pschema_info_e, "nsschemacsn");
+ csn_as_string(schema_csn, PR_FALSE, csn_str);
+ slapi_entry_add_string(pschema_info_e, "nsschemacsn", csn_str);
+ }
+ pblock_done(&mypb);
+
+ return rc;
+}
+
+
+/* oc_add_nolock
+ * Add the objectClass newoc to the global list of objectclasses
+ */
+static void
+oc_add_nolock(struct objclass *newoc)
+{
+ struct objclass *poc;
+
+ poc = g_get_global_oc_nolock();
+
+ if ( NULL == poc ) {
+ g_set_global_oc_nolock(newoc);
+ } else {
+ for ( ; (poc != NULL) && (poc->oc_next != NULL); poc = poc->oc_next) {
+ ;
+ }
+ poc->oc_next = newoc;
+ newoc->oc_next = NULL;
+ }
+}
+
+
+static char **read_dollar_values ( char *vals) {
+ int i,k;
+ char **retVal;
+ static const char *charsToRemove = " ()";
+
+ /* get rid of all the parens and spaces */
+ for ( i = 0, k = 0; vals[i]; i++) {
+ if (!strchr(charsToRemove, vals[i])) {
+ vals[k++] = vals[i];
+ }
+ }
+ vals[k] = '\0';
+ retVal = str2charray (vals, "$");
+ return retVal;
+}
+
+/*
+ * Delete one or more objectClasses from our internal data structure.
+ *
+ * Return an LDAP error code (LDAP_SUCCESS if all goes well).
+ * If an error occurs, explanatory text is copied into 'errorbuf'.
+ *
+ * This function should not send an LDAP result; that is the caller's
+ * responsibility.
+ */
+static int
+schema_delete_objectclasses( Slapi_Entry *entryBefore, LDAPMod *mod,
+ char *errorbuf, size_t errorbufsize, int schema_ds4x_compat )
+{
+ int i;
+ int rc = LDAP_SUCCESS; /* optimistic */
+ struct objclass *poc, *poc2, *delete_oc = NULL;
+
+ if ( NULL == mod->mod_bvalues ) {
+ schema_create_errormsg( errorbuf, errorbufsize, schema_errprefix_oc,
+ NULL, "Cannot remove all schema object classes" );
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
+
+ for (i = 0; mod->mod_bvalues[i]; i++) {
+ if ( LDAP_SUCCESS != ( rc = read_oc_ldif (
+ (const char *)mod->mod_bvalues[i]->bv_val, &delete_oc,
+ errorbuf, errorbufsize, 0, 0, schema_ds4x_compat))) {
+ return rc;
+ }
+
+ oc_lock_write();
+
+ if ((poc = oc_find_nolock(delete_oc->oc_name)) != NULL) {
+
+ /* check to see if any objectclasses inherit from this oc */
+ for (poc2 = g_get_global_oc_nolock(); poc2 != NULL; poc2 = poc2->oc_next) {
+ if (poc2->oc_superior &&
+ (strcasecmp (poc2->oc_superior, delete_oc->oc_name) == 0)) {
+ schema_create_errormsg( errorbuf, errorbufsize, schema_errprefix_oc,
+ delete_oc->oc_name, "Cannot delete an object class"
+ " which has child object classes" );
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ goto unlock_and_return;
+ }
+ }
+
+ if ( (poc->oc_flags & OC_FLAG_STANDARD_OC) == 0) {
+ oc_delete_nolock (poc->oc_name);
+ }
+
+ else {
+ schema_create_errormsg( errorbuf, errorbufsize, schema_errprefix_oc,
+ delete_oc->oc_name, "Cannot delete a standard object class" );
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ goto unlock_and_return;
+ }
+ }
+ else {
+ schema_create_errormsg( errorbuf, errorbufsize, schema_errprefix_oc,
+ delete_oc->oc_name, "Is unknown. Cannot delete." );
+ rc = schema_ds4x_compat ? LDAP_NO_SUCH_OBJECT : LDAP_NO_SUCH_ATTRIBUTE;
+ goto unlock_and_return;
+ }
+
+ oc_free( &delete_oc );
+ oc_unlock();
+ }
+
+ return rc;
+
+unlock_and_return:
+ oc_free( &delete_oc );
+ oc_unlock();
+ return rc;
+}
+
+
+static int
+schema_return(int rc,struct sizedbuffer * psb1,struct sizedbuffer *psb2,struct sizedbuffer *psb3,struct sizedbuffer *psb4)
+{
+ sizedbuffer_destroy(psb1);
+ sizedbuffer_destroy(psb2);
+ sizedbuffer_destroy(psb3);
+ sizedbuffer_destroy(psb4);
+ return rc;
+}
+
+/*
+ * Delete one or more attributeTypes from our internal data structure.
+ *
+ * Return an LDAP error code (LDAP_SUCCESS if all goes well).
+ * If an error occurs, explanatory text is copied into 'errorbuf'.
+ *
+ * This function should not send an LDAP result; that is the caller's
+ * responsibility.
+ */
+static int
+schema_delete_attributes ( Slapi_Entry *entryBefore, LDAPMod *mod,
+ char *errorbuf, size_t errorbufsize)
+{
+ char *attr_ldif, *oc_list_type = "";
+ asyntaxinfo *a;
+ struct objclass *oc = NULL;
+ int i, k, attr_in_use_by_an_oc = 0;
+ struct sizedbuffer *psbAttrName= sizedbuffer_construct(BUFSIZ);
+ struct sizedbuffer *psbAttrOid= sizedbuffer_construct(BUFSIZ);
+ struct sizedbuffer *psbAttrSyntax= sizedbuffer_construct(BUFSIZ);
+
+ if (NULL == mod->mod_bvalues) {
+ schema_create_errormsg( errorbuf, errorbufsize, schema_errprefix_at,
+ NULL, "Cannot remove all schema attribute types" );
+ return schema_return(LDAP_UNWILLING_TO_PERFORM,psbAttrOid,psbAttrName,
+ psbAttrSyntax,NULL);
+ }
+
+ for (i = 0; mod->mod_bvalues[i]; i++) {
+ attr_ldif =(char *) mod->mod_bvalues[i]->bv_val;
+
+ /* normalize the attr ldif */
+ for ( k = 0; attr_ldif[k]; k++) {
+ if (attr_ldif[k] == '\'' ||
+ attr_ldif[k] == '(' ||
+ attr_ldif[k] == ')' ) {
+ attr_ldif[k] = ' ';
+ }
+ attr_ldif[k] = tolower (attr_ldif[k]);
+
+ }
+
+ sizedbuffer_allocate(psbAttrName,strlen(attr_ldif));
+ sizedbuffer_allocate(psbAttrOid,strlen(attr_ldif));
+ sizedbuffer_allocate(psbAttrSyntax,strlen(attr_ldif));
+
+ sscanf (attr_ldif, "%s name %s syntax %s",
+ psbAttrOid->buffer, psbAttrName->buffer, psbAttrSyntax->buffer);
+ if ((a = attr_syntax_get_by_name ( psbAttrName->buffer)) != NULL ) {
+ /* only modify attrs which were user defined */
+ if (a->asi_flags & SLAPI_ATTR_FLAG_STD_ATTR) {
+ schema_create_errormsg( errorbuf, errorbufsize, schema_errprefix_at,
+ psbAttrName->buffer,
+ "Cannot delete a standard attribute type" );
+ attr_syntax_return( a );
+ return schema_return(LDAP_UNWILLING_TO_PERFORM,psbAttrOid,psbAttrName,
+ psbAttrSyntax,NULL);
+ }
+
+ /* Do not allow deletion if referenced by an object class. */
+ oc_lock_read();
+ attr_in_use_by_an_oc = 0;
+ for ( oc = g_get_global_oc_nolock(); oc != NULL; oc = oc->oc_next ) {
+ if (NULL != oc->oc_required) {
+ for ( k = 0; oc->oc_required[k] != NULL; k++ ) {
+ if ( 0 == slapi_attr_type_cmp( oc->oc_required[k], a->asi_name,
+ SLAPI_TYPE_CMP_EXACT )) {
+ oc_list_type = "MUST";
+ attr_in_use_by_an_oc = 1;
+ break;
+ }
+ }
+ }
+
+ if (!attr_in_use_by_an_oc && NULL != oc->oc_allowed) {
+ for ( k = 0; oc->oc_allowed[k] != NULL; k++ ) {
+ if ( 0 == slapi_attr_type_cmp( oc->oc_allowed[k], a->asi_name,
+ SLAPI_TYPE_CMP_EXACT )) {
+ oc_list_type = "MAY";
+ attr_in_use_by_an_oc = 1;
+ break;
+ }
+ }
+ }
+
+ if (attr_in_use_by_an_oc) {
+ schema_create_errormsg( errorbuf, errorbufsize, schema_errprefix_at,
+ psbAttrName->buffer, "Is included in the %s list for object class %s. Cannot delete.",
+ oc_list_type, oc->oc_name );
+ break;
+ }
+ }
+ oc_unlock();
+ if (attr_in_use_by_an_oc) {
+ attr_syntax_return( a );
+ return schema_return(LDAP_UNWILLING_TO_PERFORM,psbAttrOid,psbAttrName,
+ psbAttrSyntax,NULL);
+ }
+
+ /* Delete it. */
+ attr_syntax_delete( a );
+ attr_syntax_return( a );
+ }
+ else {
+ /* unknown attribute */
+ schema_create_errormsg( errorbuf, errorbufsize, schema_errprefix_at,
+ psbAttrName->buffer, "Is unknown. Cannot delete." );
+ return schema_return(LDAP_NO_SUCH_ATTRIBUTE,psbAttrOid,psbAttrName,
+ psbAttrSyntax,NULL);
+ }
+ }
+
+ return schema_return(LDAP_SUCCESS,psbAttrOid,psbAttrName,psbAttrSyntax,
+ NULL);
+}
+
+static int
+schema_add_attribute ( Slapi_PBlock *pb, LDAPMod *mod, char *errorbuf,
+ size_t errorbufsize, int schema_ds4x_compat )
+{
+ int i;
+ char *attr_ldif;
+/* LPXXX: Eventually, we should not allocate the buffers in read_at_ldif
+ * for each attribute, but use the same buffer for all.
+ * This is not done yet, so it's useless to allocate buffers for nothing.
+ */
+/* struct sizedbuffer *psbAttrName= sizedbuffer_construct(BUFSIZ); */
+/* struct sizedbuffer *psbAttrOid= sizedbuffer_construct(BUFSIZ); */
+/* struct sizedbuffer *psbAttrDesc= sizedbuffer_construct(BUFSIZ); */
+/* struct sizedbuffer *psbAttrSyntax= sizedbuffer_construct(BUFSIZ); */
+ int status = 0;
+
+ for (i = 0; LDAP_SUCCESS == status && mod->mod_bvalues[i]; i++) {
+ int nolock = 0; /* lock global resources during normal operation */
+ attr_ldif = (char *) mod->mod_bvalues[i]->bv_val;
+
+ status = read_at_ldif(attr_ldif, NULL, errorbuf, errorbufsize,
+ nolock, 1 /* user defined */, schema_ds4x_compat, 1);
+ if ( LDAP_SUCCESS != status ) {
+ break; /* stop on first error */
+ }
+ }
+
+ /* free everything */
+/* sizedbuffer_destroy(psbAttrOid); */
+/* sizedbuffer_destroy(psbAttrName); */
+/* sizedbuffer_destroy(psbAttrDesc); */
+/* sizedbuffer_destroy(psbAttrSyntax); */
+
+ return status;
+}
+
+/*
+ * Returns an LDAP error code (LDAP_SUCCESS if all goes well)
+ */
+static int
+add_oc_internal(struct objclass *pnew_oc, char *errorbuf, size_t errorbufsize,
+ int schema_ds4x_compat )
+{
+ struct objclass *oldoc_by_name, *oldoc_by_oid, *psup_oc = NULL;
+ int redefined_oc = 0, rc=0;
+ asyntaxinfo *pasyntaxinfo = 0;
+
+ oc_lock_write();
+
+ oldoc_by_name = oc_find_nolock (pnew_oc->oc_name);
+ oldoc_by_oid = oc_find_nolock (pnew_oc->oc_oid);
+
+ /* Check to see if the objectclass name and the objectclass oid are already
+ * in use by an existing objectclass. If an existing objectclass is already
+ * using the name or oid, the name and the oid should map to the same objectclass.
+ * Otherwise, return an error.
+ */
+ if ( oldoc_by_name != oldoc_by_oid ) {
+ schema_create_errormsg( errorbuf, errorbufsize, schema_errprefix_oc,
+ pnew_oc->oc_name, "The name does not match the OID \"%s\". "
+ "Another object class is already using the name or OID.",
+ pnew_oc->oc_oid);
+ rc = LDAP_TYPE_OR_VALUE_EXISTS;
+ }
+
+ /*
+ * Set a flag so we know if we are updating an existing OC definition.
+ */
+ if ( !rc ) {
+ if ( NULL != oldoc_by_name ) {
+ redefined_oc = 1;
+ } else {
+ /*
+ * If we are not updating an existing OC, check that the new
+ * oid is not already in use.
+ */
+ if ( NULL != oldoc_by_oid ) {
+ schema_create_errormsg( errorbuf, errorbufsize,
+ schema_errprefix_oc, pnew_oc->oc_name,
+ "The OID \"%s\" is already used by the object class \"%s\"",
+ pnew_oc->oc_oid, oldoc_by_oid->oc_name);
+ rc = LDAP_TYPE_OR_VALUE_EXISTS;
+ }
+ }
+ }
+
+ /* check to see if the superior oc exists */
+ if (!rc && pnew_oc->oc_superior &&
+ ((psup_oc = oc_find_nolock (pnew_oc->oc_superior)) == NULL)) {
+ schema_create_errormsg( errorbuf, errorbufsize, schema_errprefix_oc,
+ pnew_oc->oc_name, "Superior object class \"%s\" does not exist",
+ pnew_oc->oc_superior);
+ rc = LDAP_TYPE_OR_VALUE_EXISTS;
+ }
+
+ /* inherit the attributes from the superior oc */
+ if (!rc && psup_oc ) {
+ if ( psup_oc->oc_required ) {
+ charray_merge( &pnew_oc->oc_required, psup_oc->oc_required, 1 );
+ }
+ if ( psup_oc->oc_allowed ) {
+ charray_merge ( &pnew_oc->oc_allowed, psup_oc->oc_allowed, 1 );
+ }
+ }
+
+ /* check to see if the oid is already in use by an attribute */
+ if (!rc && (pasyntaxinfo = attr_syntax_get_by_oid(pnew_oc->oc_oid))) {
+ schema_create_errormsg( errorbuf, errorbufsize, schema_errprefix_oc,
+ pnew_oc->oc_name,
+ "The OID \"%s\" is also used by the attribute type \"%s\"",
+ pnew_oc->oc_oid, pasyntaxinfo->asi_name);
+ rc = LDAP_TYPE_OR_VALUE_EXISTS;
+ attr_syntax_return( pasyntaxinfo );
+ }
+
+ /* check to see if the objectclass name is valid */
+ if (!rc && schema_check_name ( pnew_oc->oc_name, PR_FALSE,
+ errorbuf, errorbufsize ) == 0 ) {
+ rc = schema_ds4x_compat ? LDAP_OPERATIONS_ERROR : LDAP_INVALID_SYNTAX;
+ }
+
+ /* check to see if the oid is valid */
+ if (!rc)
+ {
+ struct sizedbuffer *psbOcOid, *psbOcName;
+
+ psbOcName = sizedbuffer_construct(strlen(pnew_oc->oc_name) + 1);
+ psbOcOid = sizedbuffer_construct(strlen(pnew_oc->oc_oid) + 1);
+ strcpy(psbOcName->buffer, pnew_oc->oc_name);
+ strcpy(psbOcOid->buffer, pnew_oc->oc_oid);
+
+ if (!schema_check_oid ( psbOcName->buffer, psbOcOid->buffer, PR_FALSE,
+ errorbuf, errorbufsize))
+ rc = schema_ds4x_compat ? LDAP_OPERATIONS_ERROR : LDAP_INVALID_SYNTAX;
+
+ sizedbuffer_destroy(psbOcName);
+ sizedbuffer_destroy(psbOcOid);
+ }
+
+ /* check to see if the oc's attributes are valid */
+ if (!rc && schema_check_oc_attrs ( pnew_oc, errorbuf, errorbufsize,
+ 0 /* don't strip options */ ) == 0 ) {
+ rc = schema_ds4x_compat ? LDAP_OPERATIONS_ERROR : LDAP_INVALID_SYNTAX;
+ }
+ /* insert new objectclass exactly where the old one one in the linked list*/
+ if ( !rc && redefined_oc ) {
+ pnew_oc->oc_flags |= OC_FLAG_REDEFINED_OC;
+ rc=oc_replace_nolock( pnew_oc->oc_name, pnew_oc);
+ }
+
+ if (!rc && !redefined_oc ) {
+ oc_add_nolock(pnew_oc);
+ }
+
+ if (!rc && redefined_oc ) {
+ oc_update_inheritance_nolock( pnew_oc );
+ }
+
+ oc_unlock();
+ return rc;
+}
+
+
+/*
+ * Process a replace modify suboperation for attributetypes.
+ *
+ * XXXmcs: At present, readonly (bundled) schema definitions can't be
+ * removed. If that is attempted, we just keep them without generating
+ * an error.
+ *
+ * Our algorithm is:
+ *
+ * Clear the "keep" flags on the all existing attr. definitions.
+ *
+ * For each replacement value:
+ * If the value exactly matches an existing schema definition,
+ * set that definition's keep flag.
+ *
+ * Else if the OID in the replacement value matches an existing
+ * definition, delete the old definition and add the new one. Set
+ * the keep flag on the newly added definition.
+ *
+ * Else add the new definition. Set the keep flag on the newly
+ * added definition.
+ *
+ * For each definition that is not flagged keep, delete.
+ *
+ * Clear all remaining "keep" flags.
+ *
+ * Note that replace was not supported at all before iDS 5.0.
+ */
+static int
+schema_replace_attributes ( Slapi_PBlock *pb, LDAPMod *mod, char *errorbuf,
+ size_t errorbufsize )
+{
+ int i, rc = LDAP_SUCCESS;
+ struct asyntaxinfo *newasip, *oldasip;
+
+ if ( NULL == mod->mod_bvalues ) {
+ schema_create_errormsg( errorbuf, errorbufsize, schema_errprefix_at,
+ NULL, "Cannot remove all schema attribute types" );
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
+
+ /* clear all of the "keep" flags */
+ attr_syntax_all_clear_flag( SLAPI_ATTR_FLAG_KEEP );
+
+ for ( i = 0; mod->mod_bvalues[i] != NULL; ++i ) {
+ if ( LDAP_SUCCESS != ( rc = read_at_ldif( mod->mod_bvalues[i]->bv_val,
+ &newasip, errorbuf, errorbufsize, 0, 1, 0, 0 ))) {
+ goto clean_up_and_return;
+ }
+
+ /*
+ * Check for a match with an existing type and
+ * handle the various cases.
+ */
+ if ( NULL == ( oldasip =
+ attr_syntax_get_by_oid( newasip->asi_oid ))) {
+ /* new attribute type */
+ LDAPDebug( LDAP_DEBUG_TRACE, "schema_replace_attributes:"
+ " new type %s (OID %s)\n",
+ newasip->asi_name, newasip->asi_oid, 0 );
+ } else {
+ /* the name matches -- check the rest */
+ if ( attr_syntax_equal( newasip, oldasip )) {
+ /* unchanged attribute type -- just mark it as one to keep */
+ oldasip->asi_flags |= SLAPI_ATTR_FLAG_KEEP;
+ attr_syntax_free( newasip );
+ newasip = NULL;
+ } else {
+ /* modified attribute type */
+ LDAPDebug( LDAP_DEBUG_TRACE, "schema_replace_attributes:"
+ " replacing type %s (OID %s)\n",
+ newasip->asi_name, newasip->asi_oid, 0 );
+ attr_syntax_delete( oldasip );
+ }
+
+ attr_syntax_return( oldasip );
+ }
+
+ if ( NULL != newasip ) { /* add new or replacement definition */
+ rc = attr_syntax_add( newasip );
+ if ( LDAP_SUCCESS != rc ) {
+ schema_create_errormsg( errorbuf, errorbufsize,
+ schema_errprefix_at, newasip->asi_name,
+ "Could not be added (OID is \"%s\")",
+ newasip->asi_oid );
+ attr_syntax_free( newasip );
+ goto clean_up_and_return;
+ }
+
+ newasip->asi_flags |= SLAPI_ATTR_FLAG_KEEP;
+ }
+ }
+
+ /*
+ * Delete all of the definitions that are not marked "keep" or "standard".
+ *
+ * XXXmcs: we should consider reporting an error if any read only types
+ * remain....
+ */
+ attr_syntax_delete_all_not_flagged( SLAPI_ATTR_FLAG_KEEP
+ | SLAPI_ATTR_FLAG_STD_ATTR );
+
+clean_up_and_return:
+ /* clear all of the "keep" flags */
+ attr_syntax_all_clear_flag( SLAPI_ATTR_FLAG_KEEP );
+
+ return rc;
+}
+
+
+static int
+schema_add_objectclass ( Slapi_PBlock *pb, LDAPMod *mod, char *errorbuf,
+ size_t errorbufsize, int schema_ds4x_compat )
+{
+ struct objclass *pnew_oc;
+ char *newoc_ldif;
+ int j, rc=0;
+
+ for (j = 0; mod->mod_bvalues[j]; j++) {
+ newoc_ldif = (char *) mod->mod_bvalues[j]->bv_val;
+ if ( LDAP_SUCCESS != (rc = read_oc_ldif ( newoc_ldif, &pnew_oc,
+ errorbuf, errorbufsize, 0, 1 /* user defined */,
+ schema_ds4x_compat))) {
+ return rc;
+ }
+
+ if ( LDAP_SUCCESS != (rc = add_oc_internal(pnew_oc, errorbuf,
+ errorbufsize, schema_ds4x_compat))) {
+ oc_free( &pnew_oc );
+ return rc;
+ }
+
+ normalize_oc();
+ }
+
+ return LDAP_SUCCESS;
+}
+
+
+
+/*
+ * Process a replace modify suboperation for objectclasses.
+ *
+ * XXXmcs: At present, readonly (bundled) schema definitions can't be
+ * removed. If that is attempted, we just keep them without generating
+ * an error.
+ *
+ * Our algorithm is:
+ *
+ * Lock the global objectclass linked list.
+ *
+ * Create a new empty (temporary) linked list, initially empty.
+ *
+ * For each replacement value:
+ * If the value exactly matches an existing schema definition,
+ * move the existing definition from the current global list to the
+ * temporary list
+ *
+ * Else if the OID in the replacement value matches an existing
+ * definition, delete the old definition from the current global
+ * list and add the new one to the temporary list.
+ *
+ * Else add the new definition to the temporary list.
+ *
+ * Delete all definitions that remain on the current global list.
+ *
+ * Make the temporary list the current global list.
+ *
+ * Note that since the objectclass definitions are stored in a linked list,
+ * this algorithm is O(N * M) where N is the number of existing objectclass
+ * definitions and M is the number of replacement definitions.
+ * XXXmcs: Yuck. We should use a hash table for the OC definitions.
+ *
+ * Note that replace was not supported at all by DS versions prior to 5.0
+ */
+
+static int
+schema_replace_objectclasses ( Slapi_PBlock *pb, LDAPMod *mod, char *errorbuf,
+ size_t errorbufsize )
+{
+ struct objclass *newocp, *curlisthead, *prevocp, *tmpocp;
+ struct objclass *newlisthead = NULL, *newlistend = NULL;
+ int i, rc = LDAP_SUCCESS;
+
+ if ( NULL == mod->mod_bvalues ) {
+ schema_create_errormsg( errorbuf, errorbufsize, schema_errprefix_oc,
+ NULL, "Cannot remove all schema object classes" );
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
+
+ oc_lock_write();
+
+ curlisthead = g_get_global_oc_nolock();
+
+ for ( i = 0; mod->mod_bvalues[i] != NULL; ++i ) {
+ struct objclass *addocp = NULL;
+
+ if ( LDAP_SUCCESS != ( rc = read_oc_ldif( mod->mod_bvalues[i]->bv_val,
+ &newocp, errorbuf, errorbufsize, 1 /* no locking */,
+ 1 /* user defined */, 0 /* no DS 4.x compat issues */ ))) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto clean_up_and_return;
+ }
+
+ prevocp = NULL;
+ for ( tmpocp = curlisthead; tmpocp != NULL; tmpocp = tmpocp->oc_next ) {
+ if ( 0 == strcasecmp( tmpocp->oc_oid, newocp->oc_oid ) ) {
+ /* the names match -- remove from the current list */
+ if ( tmpocp == curlisthead ) {
+ curlisthead = tmpocp->oc_next;
+ } else {
+ prevocp->oc_next = tmpocp->oc_next;
+ }
+ tmpocp->oc_next = NULL;
+
+ /* check for a full match */
+ if ( oc_equal( tmpocp, newocp )) {
+ /* no changes: keep existing definition and discard new */
+ oc_free( &newocp );
+ addocp = tmpocp;
+ } else {
+ /* some differences: discard old and keep the new one */
+ oc_free( &tmpocp );
+ LDAPDebug( LDAP_DEBUG_TRACE, "schema_replace_objectclasses:"
+ " replacing object class %s (OID %s)\n",
+ newocp->oc_name, newocp->oc_oid, 0 );
+ addocp = newocp;
+ }
+ break; /* we found it -- exit the loop */
+
+ }
+ prevocp = tmpocp;
+ }
+
+ if ( NULL == addocp ) {
+ LDAPDebug( LDAP_DEBUG_TRACE, "schema_replace_objectclasses:"
+ " new object class %s (OID %s)\n",
+ newocp->oc_name, newocp->oc_oid, 0 );
+ addocp = newocp;
+ }
+
+ /* add the objectclass to the end of the new list */
+ if ( NULL != addocp ) {
+ if ( NULL == newlisthead ) {
+ newlisthead = addocp;
+ } else {
+ newlistend->oc_next = addocp;
+ }
+ newlistend = addocp;
+ }
+ }
+
+clean_up_and_return:
+ if ( LDAP_SUCCESS == rc ) {
+ /*
+ * Delete all remaining OCs that are on the old list AND are not
+ * "standard" classes.
+ */
+ struct objclass *nextocp;
+
+ prevocp = NULL;
+ for ( tmpocp = curlisthead; tmpocp != NULL; tmpocp = nextocp ) {
+ if ( 0 == ( tmpocp->oc_flags & OC_FLAG_STANDARD_OC )) {
+ /* not a standard definition -- remove it */
+ if ( tmpocp == curlisthead ) {
+ curlisthead = tmpocp->oc_next;
+ } else {
+ prevocp->oc_next = tmpocp->oc_next;
+ }
+ nextocp = tmpocp->oc_next;
+ oc_free( &tmpocp );
+ } else {
+ /*
+ * XXXmcs: we could generate an error, but for now we do not.
+ */
+ nextocp = tmpocp->oc_next;
+ prevocp = tmpocp;
+
+#if 0
+
+ schema_create_errormsg( errorbuf, errorbufsize,
+ schema_errprefix_oc, tmpocp->oc_name,
+ "Cannot delete a standard object class" );
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ break;
+#endif
+ }
+ }
+ }
+
+ /*
+ * Combine the two lists by adding the new list to the end of the old
+ * one.
+ */
+ if ( NULL != curlisthead ) {
+ for ( tmpocp = curlisthead; tmpocp->oc_next != NULL;
+ tmpocp = tmpocp->oc_next ) {
+ ;/*NULL*/
+ }
+ tmpocp->oc_next = newlisthead;
+ newlisthead = curlisthead;
+ }
+
+ /*
+ * Install the new list as the global one, replacing the old one.
+ */
+ g_set_global_oc_nolock( newlisthead );
+
+ oc_unlock();
+ return rc;
+}
+
+
+/*
+ * read_oc_ldif_return
+ * Free all the memory that read_oc_ldif() allocated, and return the retVal
+ *
+ * It's nice to do all the freeing in one spot, as read_oc_ldif() returns sideways
+ */
+
+static int
+read_oc_ldif_return( int retVal,
+ char *oid,
+ struct sizedbuffer *name,
+ char *sup,
+ char **origins,
+ int num_origins,
+ char *desc )
+{
+ slapi_ch_free((void **)&oid);
+ sizedbuffer_destroy( name );
+ slapi_ch_free((void **)&sup);
+ free_qdlist( origins, num_origins );
+ slapi_ch_free((void **)&desc);
+ return retVal;
+}
+
+/*
+ * read_oc_ldif
+ * Read the value of the objectclasses attribute in cn=schema, convert it
+ * into an objectclass struct.
+ *
+ * Arguments:
+ *
+ * input : value of objectclasses attribute to read
+ * oc : pointer write the objectclass to
+ * errorbuf : buffer to write any errors to
+ * is_user_defined : if non-zero, force objectclass to be user defined
+ * nolock : if non-zero, assume oc_lock_*() has been called.
+ * schema_ds4x_compat: if non-zero, act like Netscape DS 4.x
+ *
+ * Returns: an LDAP error code
+ *
+ * LDAP_SUCCESS if the objectclass was sucessfully read, the new
+ * objectclass will be written to oc
+ *
+ * All others: there was an error, an error message will
+ * be written to errorbuf
+ */
+static int
+read_oc_ldif ( const char *input, struct objclass **oc, char *errorbuf,
+ size_t errorbufsize, int nolock, int is_user_defined,
+ int schema_ds4x_compat ) {
+ int i, j, num_origins;
+ const char *pstart, *nextinput;
+ struct objclass *pnew_oc, *psup_oc;
+ char **RequiredAttrsArray, **AllowedAttrsArray;
+ char **OrigRequiredAttrsArray, **OrigAllowedAttrsArray;
+ char **oc_origins;
+ char *pend, *pOcOid, *pOcSup, *pOcDesc;
+ struct sizedbuffer *psbOcName= sizedbuffer_construct(BUFSIZ);
+ PRUint8 kind, flags;
+ int invalid_syntax_error;
+ schema_strstr_fn_t keyword_strstr_fn;
+
+ /*
+ * From RFC 2252 section 4.4:
+ *
+ * ObjectClassDescription = "(" whsp
+ * numericoid whsp ; ObjectClass identifier
+ * [ "NAME" qdescrs ]
+ * [ "DESC" qdstring ]
+ * [ "OBSOLETE" whsp ]
+ * [ "SUP" oids ] ; Superior ObjectClasses
+ * [ ( "ABSTRACT" / "STRUCTURAL" / "AUXILIARY" ) whsp ]
+ * ; default structural
+ * [ "MUST" oids ] ; AttributeTypes
+ * [ "MAY" oids ] ; AttributeTypes
+ * whsp ")"
+ *
+ * XXXmcs: Our parsing technique is poor. In (Netscape) DS 4.12 and earlier
+ * releases, parsing was mostly done by looking anywhere within the input
+ * string for various keywords such as "MUST". But if, for example, a
+ * description contains the word "must", the parser would take assume that
+ * the tokens following the word were attribute types or OIDs. Bad news.
+ *
+ * In iDS 5.0 and later, we parse in order left to right and advance a
+ * pointer as we consume the input string (the nextinput variable). We
+ * also use a case-insensitive search when looking for keywords such as
+ * DESC. But the parser will still be fooled by sequences like:
+ *
+ * ( 1.2.3.4 NAME 'testOC' MUST ( DESC cn ) )
+ *
+ * Someday soon we will need to write a real parser.
+ *
+ * Compatibility notes: if schema_ds4x_compat is set, we:
+ * 1. always parse from the beginning of the string
+ * 2. use a case-insensitive compare when looking for keywords, e.g., MUST
+ */
+
+ if ( schema_ds4x_compat ) {
+ keyword_strstr_fn = PL_strcasestr;
+ invalid_syntax_error = LDAP_OPERATIONS_ERROR;
+ } else {
+ keyword_strstr_fn = PL_strstr;
+ invalid_syntax_error = LDAP_INVALID_SYNTAX;
+ }
+
+ flags = 0;
+ num_origins = 0;
+ oc_origins = NULL;
+ pOcOid = pOcSup = pOcDesc = NULL;
+
+ if ( NULL == input || '\0' == input[0] ) {
+
+ schema_create_errormsg( errorbuf, errorbufsize, schema_errprefix_oc, NULL,
+ "One or more values are required for the objectClasses attribute" );
+ LDAPDebug ( LDAP_DEBUG_ANY, "NULL args passed to read_oc_ldif\n",0,0,0);
+ return read_oc_ldif_return( LDAP_OPERATIONS_ERROR, pOcOid, psbOcName,
+ pOcSup, oc_origins, num_origins, pOcDesc );
+ }
+
+
+ nextinput = input;
+
+ /* look for the OID */
+ if ( NULL == ( pOcOid = get_tagged_oid( "(", &nextinput,
+ keyword_strstr_fn ))) {
+ schema_create_errormsg( errorbuf, errorbufsize, schema_errprefix_oc,
+ input, "Value is malformed. It must include a \"(\"");
+ return read_oc_ldif_return( invalid_syntax_error, pOcOid, psbOcName,
+ pOcSup, oc_origins, num_origins, pOcDesc );
+ }
+
+ if ( schema_ds4x_compat || ( strcasecmp(pOcOid, "NAME") == 0))
+ nextinput = input;
+
+ /* look for the NAME */
+ if ( (pstart = (*keyword_strstr_fn)(nextinput, "NAME '")) != NULL ) {
+ pstart += 6;
+ sizedbuffer_allocate(psbOcName,strlen(pstart));
+ if ( sscanf ( pstart, "%s", psbOcName->buffer ) > 0 ) {
+ /* strip the trailing single quote */
+ if ( psbOcName->buffer[strlen(psbOcName->buffer)-1] == '\'' ) {
+ psbOcName->buffer[strlen(psbOcName->buffer)-1] = '\0';
+ nextinput = pstart + strlen(psbOcName->buffer) + 1;
+ } else {
+ schema_create_errormsg( errorbuf, errorbufsize, schema_errprefix_oc,
+ input, "Value is malformed. It must include a single quote around"
+ " the name" );
+ return read_oc_ldif_return( invalid_syntax_error, pOcOid, psbOcName,
+ pOcSup, oc_origins, num_origins, pOcDesc );
+ }
+ }
+ }
+ else {
+ schema_create_errormsg( errorbuf, errorbufsize, schema_errprefix_oc,
+ input, "Value is malformed. It must include a \"NAME '\"");
+ return read_oc_ldif_return( invalid_syntax_error, pOcOid, psbOcName,
+ pOcSup, oc_origins, num_origins, pOcDesc );
+ }
+
+ /*
+ ** if the objectclass ldif doesn't have an OID, we'll make the oid
+ ** ocname-oid
+ */
+ if ( strcasecmp ( pOcOid, "NAME" ) == 0 ) {
+ free( pOcOid );
+ pOcOid = slapi_ch_malloc( 8 + strlen(psbOcName->buffer));
+ sprintf(pOcOid, "%s-oid", psbOcName->buffer );
+ }
+
+ if ( schema_ds4x_compat ) nextinput = input;
+
+ /* look for an optional DESCription */
+ if ( (pstart = (*keyword_strstr_fn) ( nextinput, " DESC '")) != NULL ) {
+ pstart += 7;
+ if (( pend = strchr( pstart, '\'' )) == NULL ) {
+ pend = (char *)(pstart + strlen(pstart));
+ }
+ pOcDesc = slapi_ch_malloc( pend - pstart + 1 );
+ memcpy( pOcDesc, pstart, pend - pstart );
+ pOcDesc[ pend - pstart ] = '\0';
+ nextinput = pend + 1;
+ }
+
+ if ( schema_ds4x_compat ) nextinput = input;
+
+ /* look for the optional OBSOLETE marker */
+ flags |= get_flag_keyword( schema_obsolete_with_spaces,
+ OC_FLAG_OBSOLETE, &nextinput, keyword_strstr_fn );
+
+ if (!nolock) {
+ oc_lock_read(); /* needed because we access the superior oc */
+ }
+
+ if ( schema_ds4x_compat ) nextinput = input;
+
+ /*
+ * Look for the superior objectclass. We first look for a parenthesized
+ * list and if not found we look for a simple OID.
+ *
+ * XXXmcs: Since we do not yet support multiple superior objectclasses, we
+ * just grab the first OID in a parenthesized list.
+ */
+ if ( NULL == ( pOcSup = get_tagged_oid( " SUP ( ", &nextinput,
+ keyword_strstr_fn ))) {
+ pOcSup = get_tagged_oid( " SUP ", &nextinput, keyword_strstr_fn );
+ }
+ psup_oc = oc_find_nolock ( pOcSup );
+
+ if ( schema_ds4x_compat ) nextinput = input;
+
+ /* look for the optional kind (ABSTRACT, STRUCTURAL, AUXILIARY) */
+ for ( i = 0; i < SCHEMA_OC_KIND_COUNT; ++i ) {
+ if ( NULL != ( pstart = (*keyword_strstr_fn)( nextinput,
+ schema_oc_kind_strings_with_spaces[i] ))) {
+ kind = i;
+ nextinput = pstart + strlen( schema_oc_kind_strings_with_spaces[i] ) - 1;
+ break;
+ }
+ }
+ if ( i >= SCHEMA_OC_KIND_COUNT ) { /* not found */
+ if ( NULL != psup_oc && OC_KIND_ABSTRACT != psup_oc->oc_kind ) {
+ /* inherit kind from superior class if not ABSTRACT */
+ kind = psup_oc->oc_kind;
+ } else {
+ /* according to RFC 2252, the default is structural */
+ kind = OC_KIND_STRUCTURAL;
+ }
+ }
+
+ if ( schema_ds4x_compat ) nextinput = input;
+
+ /* look for required attributes (MUST) */
+ if ( (pstart = (*keyword_strstr_fn) (nextinput, " MUST ")) != NULL ) {
+ char *pRequiredAttrs;
+ int saw_open_paren = 0;
+
+ pstart += 6;
+ pstart = skipWS( pstart ); /* skip past any extra white space */
+ if ( *pstart == '(' ) {
+ saw_open_paren = 1;
+ ++pstart;
+ }
+ pRequiredAttrs = slapi_ch_strdup ( pstart );
+ if ( saw_open_paren && (pend = strchr (pRequiredAttrs, ')')) != NULL ) {
+ *pend = '\0';
+ } else if ((pend = strchr (pRequiredAttrs, ' ' )) != NULL ) {
+ *pend = '\0';
+ } else {
+ pend = pRequiredAttrs + strlen(pRequiredAttrs); /* at end of string */
+ }
+ nextinput = pstart + ( pend - pRequiredAttrs );
+ RequiredAttrsArray = read_dollar_values (pRequiredAttrs);
+ slapi_ch_free((void**)&pRequiredAttrs);
+ } else {
+ RequiredAttrsArray = (char **) slapi_ch_malloc (1 * sizeof(char *)) ;
+ RequiredAttrsArray[0] = NULL;
+ }
+
+ if ( schema_ds4x_compat ) nextinput = input;
+
+ /* look for allowed attributes (MAY) */
+ if ( (pstart = (*keyword_strstr_fn) (nextinput, " MAY ")) != NULL ) {
+ char *pAllowedAttrs;
+ int saw_open_paren = 0;
+
+ pstart += 5;
+ pstart = skipWS( pstart ); /* skip past any extra white space */
+ if ( *pstart == '(' ) {
+ saw_open_paren = 1;
+ ++pstart;
+ }
+ pAllowedAttrs = slapi_ch_strdup ( pstart );
+ if ( saw_open_paren && (pend = strchr (pAllowedAttrs, ')')) != NULL ) {
+ *pend = '\0';
+ } else if ((pend = strchr (pAllowedAttrs, ' ' )) != NULL ) {
+ *pend = '\0';
+ } else {
+ pend = pAllowedAttrs + strlen(pAllowedAttrs); /* at end of string */
+ }
+ nextinput = pstart + ( pend - pAllowedAttrs );
+ AllowedAttrsArray = read_dollar_values (pAllowedAttrs);
+ slapi_ch_free((void**)&pAllowedAttrs);
+ } else {
+ AllowedAttrsArray = (char **) slapi_ch_malloc (1 * sizeof(char *)) ;
+ AllowedAttrsArray[0] = NULL;
+ }
+
+ if ( schema_ds4x_compat ) nextinput = input;
+
+ /* look for X-ORIGIN list */
+ oc_origins = parse_origin_list( nextinput, &num_origins,
+ schema_user_defined_origin );
+
+ /* set remaining flags */
+ if ( element_is_user_defined( oc_origins )) {
+ flags |= OC_FLAG_USER_OC;
+ } else if ( is_user_defined ) {
+ flags |= OC_FLAG_USER_OC;
+ /* add missing user defined origin string */
+ charray_add( &oc_origins, slapi_ch_strdup( schema_user_defined_origin[0] ));
+ ++num_origins;
+ } else {
+ flags |= OC_FLAG_STANDARD_OC;
+ }
+
+ /* generate OrigRequiredAttrsArray and OrigAllowedAttrsArray */
+
+ if (psup_oc) {
+ int found_it;
+
+ OrigRequiredAttrsArray = (char **) slapi_ch_malloc (1 * sizeof(char *)) ;
+ OrigRequiredAttrsArray[0] = NULL;
+ OrigAllowedAttrsArray = (char **) slapi_ch_malloc (1 * sizeof(char *)) ;
+ OrigAllowedAttrsArray[0] = NULL;
+
+ if (psup_oc->oc_required) {
+ for (i = 0; RequiredAttrsArray[i]; i++) {
+ for (j = 0, found_it = 0; psup_oc->oc_required[j]; j++) {
+ if (strcasecmp (psup_oc->oc_required[j], RequiredAttrsArray[i]) == 0) {
+ found_it = 1;
+ }
+ }
+ if (!found_it) {
+ charray_add (&OrigRequiredAttrsArray, slapi_ch_strdup ( RequiredAttrsArray[i] ) );
+ }
+ }
+ }
+ if (psup_oc->oc_allowed) {
+ for (i = 0; AllowedAttrsArray[i]; i++) {
+ for (j = 0, found_it=0; psup_oc->oc_allowed[j]; j++) {
+ if (strcasecmp (psup_oc->oc_allowed[j], AllowedAttrsArray[i]) == 0) {
+ found_it = 1;
+ }
+ }
+ if (!found_it) {
+ charray_add (&OrigAllowedAttrsArray, slapi_ch_strdup (AllowedAttrsArray[i]) );
+ }
+ }
+ }
+ }
+ else {
+ /* if no parent oc */
+ OrigRequiredAttrsArray = charray_dup ( RequiredAttrsArray );
+ OrigAllowedAttrsArray = charray_dup ( AllowedAttrsArray );
+ }
+
+ if (!nolock) {
+ oc_unlock(); /* we are done accessing superior oc (psup_oc) */
+ }
+
+ /* finally -- create new objclass structure */
+ pnew_oc = (struct objclass *) slapi_ch_malloc (1 * sizeof (struct objclass));
+ pnew_oc->oc_name = slapi_ch_strdup ( psbOcName->buffer );
+ pnew_oc->oc_superior = pOcSup;
+ pOcSup = NULL; /* don't free this later */
+ pnew_oc->oc_oid = pOcOid;
+ pOcOid = NULL; /* don't free this later */
+ pnew_oc->oc_desc = pOcDesc;
+ pOcDesc = NULL; /* don't free this later */
+ pnew_oc->oc_required = RequiredAttrsArray;
+ pnew_oc->oc_allowed = AllowedAttrsArray;
+ pnew_oc->oc_orig_required = OrigRequiredAttrsArray;
+ pnew_oc->oc_orig_allowed = OrigAllowedAttrsArray;
+ pnew_oc->oc_origin = cool_charray_dup( oc_origins );
+ pnew_oc->oc_next = NULL;
+ pnew_oc->oc_flags = flags;
+ pnew_oc->oc_kind = kind;
+
+ *oc = pnew_oc;
+ return read_oc_ldif_return( LDAP_SUCCESS, pOcOid, psbOcName, pOcSup,
+ oc_origins, num_origins, pOcDesc );
+}
+
+
+static void
+oc_free( struct objclass **ocp )
+{
+ struct objclass *oc;
+
+ if ( NULL != ocp && NULL != *ocp ) {
+ oc = *ocp;
+ slapi_ch_free( (void **)&oc->oc_name );
+ slapi_ch_free( (void **)&oc->oc_desc );
+ slapi_ch_free( (void **)&oc->oc_oid );
+ slapi_ch_free( (void **)&oc->oc_superior );
+ charray_free( oc->oc_required );
+ charray_free( oc->oc_allowed );
+ charray_free( oc->oc_orig_required );
+ charray_free( oc->oc_orig_allowed );
+ cool_charray_free( oc->oc_origin );
+ slapi_ch_free( (void **)&oc );
+ *ocp = NULL;
+ }
+}
+
+struct supargs{
+ char *sup, *oid;
+ unsigned long rc;
+};
+
+static int
+at_sup_dependency_callback(struct asyntaxinfo *asi, void *arg)
+{
+ struct supargs *aew = (struct supargs *)arg;
+ int rc=ATTR_SYNTAX_ENUM_NEXT;
+
+ if (!asi) {
+ LDAPDebug(LDAP_DEBUG_ANY, "Error: no attribute types in at_schema_attributes_callback\n",
+ 0, 0, 0);
+ }
+ else
+ {
+ if (strcasecmp (asi->asi_oid, aew->oid ) == 0) {
+ rc=ATTR_SYNTAX_ENUM_STOP;
+ } else {
+ if(asi->asi_name != NULL) {
+ if (strcasecmp (asi->asi_name, aew->sup ) == 0) {
+ aew->rc=0;
+ }
+ }
+ }
+ }
+ return rc;
+}
+
+/* walks down attribute types and makes sure that the superior value is found in an attribute type
+ preceeding in the hash table. I have concerns about collisions messing with the order here but this
+ may be the best we can do.
+*/
+
+static int
+slapi_check_at_sup_dependency(char *sup, char *oid)
+{
+ struct supargs aew;
+
+ memset(&aew,0,sizeof(struct supargs));
+ aew.rc=LDAP_TYPE_OR_VALUE_EXISTS;
+ aew.sup=sup;
+ aew.oid=oid;
+ attr_syntax_enumerate_attrs(at_sup_dependency_callback, &aew, PR_FALSE);
+ return aew.rc;
+}
+
+
+
+/*
+ * if asipp is NULL, the attribute type is added to the global set of schema.
+ * if asipp is not NULL, the AT is not added but *asipp is set.
+ *
+ * if nolock is true, locking of global resources is turned off; this saves
+ * time during initialization since the server operates in single threaded
+ * mode at that time.
+ *
+ * if is_user_defined is true, force attribute type to be user defined.
+ *
+ * returns an LDAP error code (LDAP_SUCCESS if all goes well)
+*/
+static int
+read_at_ldif(const char *input, struct asyntaxinfo **asipp, char *errorbuf,
+ size_t errorbufsize, int nolock, int is_user_defined,
+ int schema_ds4x_compat, int is_remote)
+{
+ char *pStart, *pEnd;
+ char *pOid, *pSyntax, *pSuperior, *pMREquality, *pMROrdering, *pMRSubstring;
+ const char *nextinput;
+ struct sizedbuffer *psbAttrName= sizedbuffer_construct(BUFSIZ);
+ struct sizedbuffer *psbAttrDesc= sizedbuffer_construct(BUFSIZ);
+ int status = 0;
+ int syntaxlength;
+ char **attr_names = NULL;
+ char *first_attr_name = NULL;
+ char **attr_origins = NULL;
+ int num_names = 0;
+ int num_origins = 0;
+ unsigned long flags = SLAPI_ATTR_FLAG_OVERRIDE;
+ const char *ss = 0;
+ struct asyntaxinfo *tmpasip;
+ int invalid_syntax_error;
+ schema_strstr_fn_t keyword_strstr_fn;
+
+ /*
+ * From RFC 2252 section 4.2:
+ *
+ * AttributeTypeDescription = "(" whsp
+ * numericoid whsp ; AttributeType identifier
+ * [ "NAME" qdescrs ] ; name used in AttributeType
+ * [ "DESC" qdstring ] ; description
+ * [ "OBSOLETE" whsp ]
+ * [ "SUP" woid ] ; derived from this other
+ * ; AttributeType
+ * [ "EQUALITY" woid ; Matching Rule name
+ * [ "ORDERING" woid ; Matching Rule name
+ * [ "SUBSTR" woid ] ; Matching Rule name
+ * [ "SYNTAX" whsp noidlen whsp ] ; see section 4.3
+ * [ "SINGLE-VALUE" whsp ] ; default multi-valued
+ * [ "COLLECTIVE" whsp ] ; default not collective
+ * [ "NO-USER-MODIFICATION" whsp ]; default user modifiable
+ * [ "USAGE" whsp AttributeUsage ]; default userApplications
+ * whsp ")"
+ *
+ * AttributeUsage =
+ * "userApplications" /
+ * "directoryOperation" /
+ * "distributedOperation" / ; DSA-shared
+ * "dSAOperation" ; DSA-specific, value depends on server
+ *
+ * XXXmcs: Our parsing technique is poor. In (Netscape) DS 4.12 and earlier
+ * releases, parsing was mostly done by looking anywhere within the input
+ * string for various keywords such as "EQUALITY". But if, for example, a
+ * description contains the word "equality", the parser would take assume
+ * that the token following the word was a matching rule. Bad news.
+ *
+ * In iDS 5.0 and later, we parse in order left to right and advance a
+ * pointer as we consume the input string (the nextinput variable). We
+ * also use a case-insensitive search when looking for keywords such as
+ * DESC. This is still less than ideal.
+ *
+ * Someday soon we will need to write a real parser.
+ *
+ * Compatibility notes: if schema_ds4x_compat is set, we:
+ * 1. always parse from the beginning of the string
+ * 2. use a case-insensitive compare when looking for keywords, e.g., DESC
+ */
+
+ if ( schema_ds4x_compat ) {
+ keyword_strstr_fn = PL_strcasestr;
+ invalid_syntax_error = LDAP_OPERATIONS_ERROR;
+ } else {
+ keyword_strstr_fn = PL_strstr;
+ invalid_syntax_error = LDAP_INVALID_SYNTAX;
+ }
+
+ if (nolock)
+ flags |= SLAPI_ATTR_FLAG_NOLOCKING;
+
+ psbAttrName->buffer[0] = '\0';
+ psbAttrDesc->buffer[0] = '\0';
+ pOid = pSyntax = pSuperior = NULL;
+ pMREquality = pMROrdering = pMRSubstring = NULL;
+ syntaxlength = SLAPI_SYNTAXLENGTH_NONE;
+
+ nextinput = input;
+
+ /* get the OID */
+ pOid = get_tagged_oid( "(", &nextinput, keyword_strstr_fn );
+
+ if (NULL == pOid) {
+ schema_create_errormsg( errorbuf, errorbufsize, schema_errprefix_at,
+ input, "Missing or invalid OID" );
+ status = invalid_syntax_error;
+ goto done;
+ }
+
+ if ( schema_ds4x_compat || (strcasecmp(pOid, "NAME") == 0))
+ nextinput = input;
+
+ /* look for the NAME (single or list of names) */
+ if ( (pStart = (*keyword_strstr_fn) ( nextinput, "NAME ")) != NULL ) {
+ pStart += 5;
+ sizedbuffer_allocate(psbAttrName,strlen(pStart)+1);
+ strcpy ( psbAttrName->buffer, pStart);
+ if (*pStart == '(')
+ pEnd = strchr(psbAttrName->buffer, ')');
+ else
+ pEnd = strchr(psbAttrName->buffer+1, '\'');
+ if (pEnd)
+ *(pEnd+1) = 0;
+ nextinput = pStart + strlen(psbAttrName->buffer) + 1;
+ attr_names = parse_qdescrs(psbAttrName->buffer, &num_names);
+ if ( NULL != attr_names ) {
+ first_attr_name = attr_names[0];
+ }
+ }
+
+ if ( schema_ds4x_compat ) nextinput = input;
+
+ /*
+ * if the attribute ldif doesn't have an OID, we'll make the oid
+ * attrname-oid
+ */
+ if ( strcasecmp ( pOid, "NAME" ) == 0 ) {
+ free( pOid );
+ pOid = slapi_ch_malloc( 8 + strlen(first_attr_name));
+ sprintf(pOid, "%s-oid", first_attr_name );
+ }
+
+ /* look for the optional DESCription */
+ if ( (pStart = (*keyword_strstr_fn) ( nextinput, "DESC '")) != NULL ) {
+ pStart += 6;
+ sizedbuffer_allocate(psbAttrDesc,strlen(pStart));
+ strcpy ( psbAttrDesc->buffer, pStart);
+ if ( (pEnd = strchr (psbAttrDesc->buffer, '\'' )) != NULL ){
+ *pEnd ='\0';
+ }
+ nextinput = pStart + strlen(psbAttrDesc->buffer) + 1;
+ }
+
+ if ( schema_ds4x_compat ) nextinput = input;
+
+ /* look for the optional OBSOLETE marker */
+ flags |= get_flag_keyword( schema_obsolete_with_spaces,
+ SLAPI_ATTR_FLAG_OBSOLETE, &nextinput, keyword_strstr_fn );
+
+ if ( schema_ds4x_compat ) nextinput = input;
+
+ /* look for the optional SUPerior type */
+ pSuperior = get_tagged_oid( "SUP ", &nextinput, keyword_strstr_fn );
+
+ if ( schema_ds4x_compat ) nextinput = input;
+
+ /* look for the optional matching rules */
+ pMREquality = get_tagged_oid( "EQUALITY ", &nextinput, keyword_strstr_fn );
+ if ( schema_ds4x_compat ) nextinput = input;
+ pMROrdering = get_tagged_oid( "ORDERING ", &nextinput, keyword_strstr_fn );
+ if ( schema_ds4x_compat ) nextinput = input;
+ pMRSubstring = get_tagged_oid( "SUBSTR ", &nextinput, keyword_strstr_fn );
+ if ( schema_ds4x_compat ) nextinput = input;
+
+ /* look for the optional SYNTAX */
+ if ( NULL != ( pSyntax = get_tagged_oid( "SYNTAX ", &nextinput,
+ keyword_strstr_fn ))) {
+ /*
+ * Check for an optional {LEN}, which if present indicates a
+ * suggested maximum size for values of this attribute type.
+ *
+ * XXXmcs: we do not enforce length restrictions, but we do read
+ * and include them in the subschemasubentry.
+ */
+ if ( (pEnd = strchr ( pSyntax, '{')) != NULL /* balance } */ ) {
+ *pEnd = '\0';
+ syntaxlength = atoi( pEnd + 1 );
+ }
+ }
+
+ if ( schema_ds4x_compat ) nextinput = input;
+
+ /* look for the optional SINGLE-VALUE marker */
+ flags |= get_flag_keyword( " SINGLE-VALUE ",
+ SLAPI_ATTR_FLAG_SINGLE, &nextinput, keyword_strstr_fn );
+
+ if ( schema_ds4x_compat ) nextinput = input;
+
+ /* look for the optional COLLECTIVE marker */
+ flags |= get_flag_keyword( schema_collective_with_spaces,
+ SLAPI_ATTR_FLAG_COLLECTIVE, &nextinput, keyword_strstr_fn );
+
+ if ( schema_ds4x_compat ) nextinput = input;
+
+ /* look for the optional NO-USER-MODIFICATION marker */
+ flags |= get_flag_keyword( schema_nousermod_with_spaces,
+ SLAPI_ATTR_FLAG_NOUSERMOD, &nextinput, keyword_strstr_fn );
+
+ if ( schema_ds4x_compat ) nextinput = input;
+
+ /* look for the optional USAGE */
+ if (NULL != (ss = (*keyword_strstr_fn)(nextinput, " USAGE "))) {
+ ss += 7;
+ ss = skipWS(ss);
+ if (ss) {
+ if ( !PL_strncmp(ss, "directoryOperation",
+ strlen("directoryOperation"))) {
+ flags |= SLAPI_ATTR_FLAG_OPATTR;
+ }
+ if ( NULL == ( nextinput = strchr( ss, ' ' ))) {
+ nextinput = ss + strlen(ss);
+ }
+ }
+ }
+
+ if ( schema_ds4x_compat ) nextinput = input;
+
+ /* X-ORIGIN list */
+ attr_origins = parse_origin_list( nextinput, &num_origins,
+ schema_user_defined_origin );
+
+ /* Do some sanity checking to make sure everything was read correctly */
+
+ if (NULL == pOid) {
+ schema_create_errormsg( errorbuf, errorbufsize, schema_errprefix_at,
+ first_attr_name, "Missing OID" );
+ status = invalid_syntax_error;
+ }
+ if (!status && (!attr_names || !num_names)) {
+ schema_create_errormsg( errorbuf, errorbufsize, schema_errprefix_at,
+ first_attr_name,
+ "Missing name (OID is \"%s\")", pOid );
+ status = invalid_syntax_error;
+ }
+
+ if (!status && (NULL != pSuperior)) {
+ struct asyntaxinfo *asi_parent;
+
+ asi_parent = attr_syntax_get_by_name(pSuperior);
+ /* if we find no match then server won't start or add the attribute type */
+ if (asi_parent == NULL) {
+ LDAPDebug (LDAP_DEBUG_PARSE,
+ "Cannot find parent attribute type \"%s\"\n",pSuperior,
+ NULL,NULL);
+ schema_create_errormsg( errorbuf, errorbufsize,
+ schema_errprefix_at, first_attr_name,
+ "Missing parent attribute syntax OID");
+ status = invalid_syntax_error;
+ } else {
+ char *pso = plugin_syntax2oid(asi_parent->asi_plugin);
+
+ if (pso) {
+ slapi_ch_free ((void **)&pSyntax);
+ pSyntax = slapi_ch_strdup(pso);
+ LDAPDebug (LDAP_DEBUG_TRACE,
+ "Inheriting syntax %s from parent type %s\n",
+ pSyntax, pSuperior,NULL);
+ } else {
+ schema_create_errormsg( errorbuf, errorbufsize,
+ schema_errprefix_at, first_attr_name,
+ "Missing parent attribute syntax OID");
+ status = invalid_syntax_error;
+ }
+ }
+ }
+ /*
+ if we are remote (via modify_schema_dse) then check sup dependencies. Locally
+ was done in if statement above
+ */
+
+ if(!status)
+ {
+ if(is_remote && (pSuperior != NULL))
+ {
+ status=slapi_check_at_sup_dependency(pSuperior, pOid);
+ }
+ if(LDAP_SUCCESS != status) {
+ schema_create_errormsg( errorbuf, errorbufsize,
+ schema_errprefix_at, first_attr_name,
+ "Missing parent attribute syntax OID");
+ status = LDAP_TYPE_OR_VALUE_EXISTS;
+ }
+ }
+
+ if (!status && (NULL == pSyntax)) {
+ schema_create_errormsg( errorbuf, errorbufsize, schema_errprefix_at,
+ first_attr_name, "Missing attribute syntax OID");
+ status = invalid_syntax_error;
+
+ }
+
+ if (!status && (plugin_syntax_find ( pSyntax ) == NULL) ) {
+ schema_create_errormsg( errorbuf, errorbufsize, schema_errprefix_at,
+ first_attr_name, "Unknown attribute syntax OID \"%s\"",
+ pSyntax );
+ status = invalid_syntax_error;
+ }
+
+ if (!status) {
+ struct objclass *poc;
+ /* check to make sure that the OID isn't being used by an objectclass */
+ oc_lock_read();
+ poc = oc_find_oid_nolock( pOid );
+ if ( poc != NULL) {
+ schema_create_errormsg( errorbuf, errorbufsize,
+ schema_errprefix_at, first_attr_name,
+ "The OID \"%s\" is also used by the object class \"%s\"",
+ pOid, poc->oc_name);
+ status = LDAP_TYPE_OR_VALUE_EXISTS;
+ }
+ oc_unlock();
+ }
+
+ if (!status && !element_is_user_defined( attr_origins )) {
+ if ( is_user_defined ) {
+ /* add missing user defined origin string */
+ charray_add( &attr_origins,
+ slapi_ch_strdup( schema_user_defined_origin[0] ));
+ ++num_origins;
+ } else {
+ flags |= SLAPI_ATTR_FLAG_STD_ATTR;
+ }
+ }
+
+ if (!status) {
+ int ii;
+ /* check to see if the attribute name is valid */
+ for (ii = 0; !status && (ii < num_names); ++ii) {
+ if ( schema_check_name(attr_names[ii], PR_TRUE, errorbuf,
+ errorbufsize) == 0 ) {
+ status = invalid_syntax_error;
+ }
+ else if (!(flags & SLAPI_ATTR_FLAG_OVERRIDE) &&
+ attr_syntax_exists(attr_names[ii])) {
+ schema_create_errormsg( errorbuf, errorbufsize,
+ schema_errprefix_at, attr_names[ii],
+ "Could not be added because it already exists" );
+ status = LDAP_TYPE_OR_VALUE_EXISTS;
+ }
+ }
+ }
+
+ if (!status) {
+ if ( schema_check_oid ( first_attr_name, pOid, PR_TRUE, errorbuf,
+ errorbufsize ) == 0 ) {
+ status = invalid_syntax_error;
+ }
+ }
+
+ if (!status) {
+ struct asyntaxinfo *tmpasi;
+
+ if (!(flags & SLAPI_ATTR_FLAG_OVERRIDE) &&
+ ( NULL != ( tmpasi = attr_syntax_get_by_oid(pOid)))) {
+ schema_create_errormsg( errorbuf, errorbufsize,
+ schema_errprefix_at, first_attr_name,
+ "Could not be added because the OID \"%s\" is already in use",
+ pOid);
+ status = LDAP_TYPE_OR_VALUE_EXISTS;
+ attr_syntax_return( tmpasi );
+ }
+ }
+
+
+ if (!status) {
+ status = attr_syntax_create( pOid, attr_names, num_names,
+ *psbAttrDesc->buffer == '\0' ? NULL : psbAttrDesc->buffer,
+ pSuperior,
+ pMREquality, pMROrdering, pMRSubstring, attr_origins,
+ pSyntax, syntaxlength, flags, &tmpasip );
+ }
+
+ if (!status) {
+ if ( NULL != asipp ) {
+ *asipp = tmpasip; /* just return it */
+ } else { /* add the new attribute to the global store */
+ status = attr_syntax_add( tmpasip );
+ if ( LDAP_SUCCESS != status ) {
+ if ( 0 != (flags & SLAPI_ATTR_FLAG_OVERRIDE) &&
+ LDAP_TYPE_OR_VALUE_EXISTS == status ) {
+ /*
+ * This can only occur if the name and OID don't match the
+ * attribute we are trying to override (all other cases of
+ * "type or value exists" were trapped above).
+ */
+ schema_create_errormsg( errorbuf, errorbufsize,
+ schema_errprefix_at, first_attr_name,
+ "Does not match the OID \"%s\". Another attribute"
+ " type is already using the name or OID.", pOid);
+ } else {
+ schema_create_errormsg( errorbuf, errorbufsize,
+ schema_errprefix_at, first_attr_name,
+ "Could not be added (OID is \"%s\")", pOid );
+ }
+ attr_syntax_free( tmpasip );
+ }
+ }
+ }
+
+ done:
+ /* free everything */
+ free_qdlist(attr_names, num_names);
+ free_qdlist(attr_origins, num_origins);
+ sizedbuffer_destroy(psbAttrName);
+ sizedbuffer_destroy(psbAttrDesc);
+ slapi_ch_free((void **)&pOid);
+ slapi_ch_free((void **)&pSuperior);
+ slapi_ch_free((void **)&pMREquality);
+ slapi_ch_free((void **)&pMROrdering);
+ slapi_ch_free((void **)&pMRSubstring);
+ slapi_ch_free((void **)&pSyntax);
+
+ return status;
+}
+
+
+/*
+ * schema_check_oc_attrs:
+ * Check to see if the required and allowed attributes are valid attributes
+ *
+ * arguments: poc : pointer to the objectclass to check
+ * errorbuf : buffer to write any error messages to
+ * stripOptions: 1 if you want to silently strip any options
+ * 0 if options should cause an error
+ *
+ * Returns:
+ *
+ * 0 if there's a unknown attribute, and errorbuf will contain an
+ * error message.
+ *
+ * 1 if everything is ok
+ *
+ * Note: no locking of poc is needed because poc is always a newly allocated
+ * objclass struct (this function is only called by add_oc_internal).
+ */
+static int
+schema_check_oc_attrs ( struct objclass *poc,
+ char *errorbuf, size_t errorbufsize,
+ int stripOptions )
+{
+ int i;
+
+ if ( errorbuf == NULL || poc == NULL || poc->oc_name == NULL) {
+ /* error */
+ LDAPDebug (LDAP_DEBUG_PARSE,
+ "Null args passed to schema_check_oc_attrs\n",
+ NULL, NULL, NULL);
+ return -1;
+ }
+
+ /* remove any options like ;binary from the oc's attributes */
+ if ( strip_oc_options( poc ) && !stripOptions) {
+ /* there were options present, this oc should be rejected */
+ schema_create_errormsg( errorbuf, errorbufsize, schema_errprefix_oc,
+ poc->oc_name, "Contains attribute options. "
+ "Attribute options, such as \";binary\" are not allowed in "
+ "object class definitions." );
+ return 0;
+ }
+
+ for ( i = 0; poc->oc_allowed && poc->oc_allowed[i]; i++ ) {
+ if ( attr_syntax_exists ( poc->oc_allowed[i] ) == 0 ) {
+ schema_create_errormsg( errorbuf, errorbufsize, schema_errprefix_oc,
+ poc->oc_name, "Unknown allowed attribute type \"%s\"",
+ poc->oc_allowed[i]);
+ return 0;
+ }
+ }
+
+
+ for ( i = 0; poc->oc_required && poc->oc_required[i]; i++ ) {
+ if ( attr_syntax_exists ( poc->oc_required[i] ) == 0 ) {
+ schema_create_errormsg( errorbuf, errorbufsize, schema_errprefix_oc,
+ poc->oc_name, "Unknown required attribute type \"%s\"",
+ poc->oc_required[i]);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+/*
+ * schema_check_name:
+ * Check if the attribute or objectclass name is valid. Names can only contain
+ * characters, digits, and hyphens. In addition, names must begin with
+ * a character. If the nsslapd-attribute-name-exceptions attribute in cn=config
+ * is true, then we also allow underscores.
+ *
+ * XXX We're also supposed to allow semicolons, but we already use them to deal
+ * with attribute options XXX
+ *
+ * returns 1 if the attribute has a legal name
+ * 0 if not
+ *
+ * If the attribute name is invalid, an error message will be written to msg
+ */
+
+static int
+schema_check_name(char *name, PRBool isAttribute, char *errorbuf,
+ size_t errorbufsize )
+{
+ int i;
+
+ /* allowed characters */
+ static char allowed[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-";
+
+ /* additional characters to allow if allow_exceptions is true */
+ static char allowedExceptions[] = "_";
+ int allow_exceptions = config_get_attrname_exceptions();
+
+ if ( name == NULL || errorbuf == NULL) {
+ /* this is bad */
+ return 0;
+ }
+
+ /* attribute names must begin with a letter */
+ if ( (isascii (name[0]) == 0) || (isalpha (name[0]) == 0)) {
+ if ( (strlen(name) + 80) < BUFSIZ ) {
+ schema_create_errormsg( errorbuf, errorbufsize, schema_errprefix_at,
+ name, "The name is invalid. Names must begin with a letter" );
+ }
+ else {
+ schema_create_errormsg( errorbuf, errorbufsize, schema_errprefix_at,
+ name, "The name is invalid, and probably too long. "
+ "Names must begin with a letter" );
+ }
+ return 0;
+ }
+
+ for (i = 1; name[i]; i++ ) {
+ if ( (NULL == strchr( allowed, name[i] )) &&
+ (!allow_exceptions ||
+ (NULL == strchr(allowedExceptions, name[i])) ) ) {
+ if ( (strlen(name) + 80) < BUFSIZ ) {
+ schema_create_errormsg( errorbuf, errorbufsize, schema_errprefix_at,
+ name, "The name contains the invalid character \"%c\"", name[i] );
+ }
+ else {
+ schema_create_errormsg( errorbuf, errorbufsize, schema_errprefix_at,
+ name, "The name contains the invalid character \"%c\". The name"
+ " is also probably too long.", name[i] );
+ }
+ return 0;
+ }
+ }
+ return 1;
+}
+
+
+
+/*
+ * schema_check_oid:
+ * Check if the oid is valid.
+ *
+ * returns 1 if the attribute has a legal oid
+ * 0 if not
+ *
+ * If the oid is invalid, an error message will be written to errorbuf
+ *
+ * Oids can either have the form <attr/oc name>-oid or
+ * start and end with a digit, and contain only digits and periods
+ */
+
+static int
+schema_check_oid( const char *name, const char *oid, PRBool isAttribute,
+ char *errorbuf, size_t errorbufsize ) {
+
+ int i = 0, length_oid = 0, rc = 0;
+ char *namePlusOid = NULL;
+
+ if ( name == NULL || oid == NULL) {
+ /* this is bad */
+ LDAPDebug (LDAP_DEBUG_ANY, "NULL passed to schema_check_oid\n",0,0,0);
+ return 0;
+ }
+
+ /* check to see if the OID is <name>-oid */
+ namePlusOid = (char *) slapi_ch_malloc( strlen(name) + 256);
+ sprintf(namePlusOid, "%s-oid", name );
+ rc = strcasecmp( oid, namePlusOid );
+ slapi_ch_free( (void **) &namePlusOid );
+
+ if ( 0 == rc ) {
+ return 1;
+ }
+
+ /* If not, the OID must begin and end with a digit, and contain only
+ digits and dots */
+
+ /* check to see that it begins and ends with a digit */
+ length_oid = strlen(oid);
+ if ( !isdigit(oid[0]) ||
+ !isdigit(oid[length_oid-1]) ) {
+ schema_create_errormsg( errorbuf, errorbufsize,
+ isAttribute ? schema_errprefix_at : schema_errprefix_oc,
+ name,
+ "The OID \"%s\" must begin and end with a digit, or be \"%s-oid\"",
+ oid, name );
+ return 0;
+ }
+
+ /* check to see that it contains only digits and dots */
+ for ( i = 0; i < length_oid; i++ ) {
+ if ( !isdigit(oid[i]) && oid[i] != '.' ){
+ schema_create_errormsg( errorbuf, errorbufsize,
+ isAttribute ? schema_errprefix_at : schema_errprefix_oc,
+ name,
+ "The OID \"%s\" contains an invalid character: \"%c\"; the"
+ " OID must contain only digits and periods, or be \"%s-oid\"",
+ oid, oid[i], name );
+ return 0;
+ }
+ }
+
+ /* The oid is OK if we're here */
+ return 1;
+
+
+}
+
+
+/*
+ * Some utility functions for dealing with a dynamically
+ * allocated buffer.
+ */
+
+static struct sizedbuffer *sizedbuffer_construct(size_t size)
+{
+ struct sizedbuffer *p= (struct sizedbuffer *)slapi_ch_malloc(sizeof(struct sizedbuffer));
+ p->size= size;
+ if(size>0)
+ {
+ p->buffer= (char*)slapi_ch_malloc(size);
+ p->buffer[0]= '\0';
+ }
+ else
+ {
+ p->buffer= NULL;
+ }
+ return p;
+}
+
+static void sizedbuffer_destroy(struct sizedbuffer *p)
+{
+ if(p!=NULL)
+ {
+ slapi_ch_free((void**)&p->buffer);
+ }
+ slapi_ch_free((void**)&p);
+}
+
+static void sizedbuffer_allocate(struct sizedbuffer *p, size_t sizeneeded)
+{
+ if(p!=NULL)
+ {
+ if(sizeneeded>p->size)
+ {
+ if(p->buffer!=NULL)
+ {
+ slapi_ch_free((void**)&p->buffer);
+ }
+ p->buffer= (char*)slapi_ch_malloc(sizeneeded);
+ p->buffer[0]= '\0';
+ p->size= sizeneeded;
+ }
+ }
+}
+
+/*
+ * has_smart_referral: returns 1 if the entry contains a ref attribute,
+ * or a referral objectclass.
+ *
+ * Returns 0 If not.
+ */
+
+
+static int
+has_smart_referral( Slapi_Entry *e ) {
+
+ Slapi_Attr *aoc;
+ char ebuf[BUFSIZ];
+
+ /* Look for the ref attribute */
+ if ( (aoc = attrlist_find( e->e_attrs, "ref" )) != NULL ) {
+ LDAPDebug ( LDAP_DEBUG_ANY, "Entry \"%s\" contains a ref attrbute. Smart referrals are disabled in Directory Lite.\n", escape_string(slapi_entry_get_dn_const(e), ebuf),0,0 );
+ return 1;
+ }
+
+ /* Look for the referral objectclass */
+ if ( (aoc = attrlist_find( e->e_attrs, "objectclass" )) != NULL ) {
+ Slapi_Value target, *found;
+ slapi_value_init(&target);
+ slapi_value_set_string(&target,"referral");
+ found= slapi_valueset_find(aoc, &aoc->a_present_values, &target);
+ value_done(&target);
+ if(found!=NULL)
+ {
+ LDAPDebug ( LDAP_DEBUG_ANY, "Entry \"%s\" is a referral object class. Smart referrals are disabled in Directory Lite.\n", escape_string(slapi_entry_get_dn_const(e), ebuf),0,0 );
+ return 1;
+ }
+ }
+
+ /* No smart referral here */
+ return 0;
+}
+
+/*
+ * Check if the object class is extensible
+ */
+static int isExtensibleObjectclass(const char *objectclass)
+{
+ if ( strcasecmp( objectclass, "extensibleobject" ) == 0 ) {
+ return( 1 );
+ }
+ /* The Easter Egg is based on a special object class */
+ if ( strcasecmp( objectclass, EGG_OBJECT_CLASS ) == 0 ) {
+ return( 1 );
+ }
+ return 0;
+}
+
+
+
+/*
+ * strip_oc_options: strip any attribute options from the objectclass'
+ * attributes (remove things like ;binary from the attrs)
+ *
+ * argument: pointer to an objectclass, attributes will have their
+ * options removed in place
+ *
+ * returns: number of options removed
+ *
+ * Note: no locking of poc is needed because poc is always a newly allocated
+ * objclass struct (this function is only called by schema_check_oc_attrs,
+ * which is only called by add_oc_internal).
+ */
+
+static int
+strip_oc_options( struct objclass *poc ) {
+ int i, numRemoved = 0;
+ char *mod = NULL;
+
+ for ( i = 0; poc->oc_allowed && poc->oc_allowed[i]; i++ ) {
+ if ( (mod = stripOption( poc->oc_allowed[i] )) != NULL ){
+ LDAPDebug (LDAP_DEBUG_ANY,
+ "Removed option \"%s\" from allowed attribute type "
+ "\"%s\" in object class \"%s\".\n",
+ mod, poc->oc_allowed[i], poc->oc_name );
+ numRemoved++;
+ }
+ }
+
+ for ( i = 0; poc->oc_required && poc->oc_required[i]; i++ ) {
+ if ( (mod = stripOption( poc->oc_required[i] )) != NULL ){
+ LDAPDebug (LDAP_DEBUG_ANY,
+ "Removed option \"%s\" from required attribute type "
+ "\"%s\" in object class \"%s\".\n",
+ mod, poc->oc_required[i], poc->oc_name );
+ numRemoved++;
+ }
+ }
+ return numRemoved;
+}
+
+
+/*
+ * stripOption:
+ * removes options such as ";binary" from attribute names
+ *
+ * argument: pointer to an attribute name, such as "userCertificate;binary"
+ *
+ * returns: pointer to the option, such as "binary"
+ * NULL if there's no option
+ *
+ */
+
+static char *
+stripOption(char *attr) {
+ char *pSemiColon = strchr( attr, ';' );
+
+ if (pSemiColon) {
+ *pSemiColon = '\0';
+ }
+
+ return pSemiColon ? pSemiColon + 1 : NULL;
+}
+
+
+/*
+ * load_schema_dse: called by dse_read_file() when target is cn=schema
+ *
+ * Initialize attributes and objectclasses from the schema
+ *
+ * Note that this function removes all values for `attributetypes'
+ * and `objectclasses' attributes from the entry `e'.
+ *
+ * returntext is always at least SLAPI_DSE_RETURNTEXT_SIZE bytes in size.
+ */
+int
+load_schema_dse(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *ignored,
+ int *returncode, char *returntext, void *arg)
+{
+ Slapi_Attr *attr = 0;
+ int nolock = 1; /* don't lock global resources during initialization */
+ int primary_file = 0; /* this is the primary (writeable) schema file */
+ int schema_ds4x_compat = config_get_ds4_compatible_schema();
+
+ *returncode = 0;
+
+ /*
+ * Note: there is no need to call schema_lock_write() here because this
+ * function is only called during server startup.
+ */
+
+ slapi_pblock_get( pb, SLAPI_DSE_IS_PRIMARY_FILE, &primary_file );
+
+ if (!slapi_entry_attr_find(e, "attributetypes", &attr) && attr)
+ {
+ /* enumerate the values in attr */
+ Slapi_Value *v = 0;
+ int index = 0;
+ for (index = slapi_attr_first_value(attr, &v);
+ v && (index != -1);
+ index = slapi_attr_next_value(attr, index, &v))
+ {
+ const char *s = slapi_value_get_string(v);
+ if (!s)
+ continue;
+ if ((*returncode = read_at_ldif(s, NULL, returntext,
+ SLAPI_DSE_RETURNTEXT_SIZE, nolock,
+ primary_file /* force user defined? */,
+ schema_ds4x_compat, 0)) != 0)
+ break;
+ }
+ slapi_entry_attr_delete(e, "attributetypes");
+ }
+
+ if (*returncode)
+ return SLAPI_DSE_CALLBACK_ERROR;
+
+ if (!slapi_entry_attr_find(e, "objectclasses", &attr) && attr)
+ {
+ /* enumerate the values in attr */
+ Slapi_Value *v = 0;
+ int index = 0;
+ for (index = slapi_attr_first_value(attr, &v);
+ v && (index != -1);
+ index = slapi_attr_next_value(attr, index, &v))
+ {
+ struct objclass *oc = 0;
+ const char *s = slapi_value_get_string(v);
+ if (!s)
+ continue;
+ if ( LDAP_SUCCESS != (*returncode = read_oc_ldif(s, &oc, returntext,
+ SLAPI_DSE_RETURNTEXT_SIZE, nolock,
+ primary_file /* force user defined? */,
+ schema_ds4x_compat))) {
+ break;
+ }
+ if ( LDAP_SUCCESS != (*returncode = add_oc_internal(oc, returntext,
+ SLAPI_DSE_RETURNTEXT_SIZE, schema_ds4x_compat))) {
+ oc_free( &oc );
+ break;
+ }
+ }
+ slapi_entry_attr_delete(e, "objectclasses");
+ }
+
+ /* Set the schema CSN */
+ if (!slapi_entry_attr_find(e, "nsschemacsn", &attr) && attr)
+ {
+ Slapi_Value *v = NULL;
+ slapi_attr_first_value(attr, &v);
+ if (NULL != v) {
+ const char *s = slapi_value_get_string(v);
+ if (NULL != s) {
+ CSN *csn = csn_new_by_string(s);
+ g_set_global_schema_csn(csn);
+ }
+ }
+ }
+
+ return (*returncode == LDAP_SUCCESS) ? SLAPI_DSE_CALLBACK_OK
+ : SLAPI_DSE_CALLBACK_ERROR;
+}
+
+/*
+ * Try to initialize the schema from the LDIF file. Read
+ * the file and convert it to the avl tree of DSEs. If the
+ * file doesn't exist, we try to create it and put a minimal
+ * schema entry into it.
+ *
+ * Returns 1 for OK, 0 for Fail.
+ */
+int
+init_schema_dse(const char *configdir)
+{
+ int rc= 1; /* OK */
+ char *schemadir = 0;
+ char *userschemafile = 0;
+ char *userschematmpfile = 0;
+ char **filelist = 0;
+ Slapi_DN schema;
+
+ slapi_sdn_init_dn_byref(&schema,"cn=schema");
+
+ schemadir = slapi_ch_calloc(1, strlen(configdir)
+ + strlen(SCHEMA_SUBDIR_NAME) + 5);
+ sprintf(schemadir, "%s/%s", configdir, SCHEMA_SUBDIR_NAME);
+
+ filelist = get_priority_filelist(schemadir, ".*ldif$");
+ if (!filelist || !*filelist)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, "schema",
+ "No schema files were found in the directory %s\n", schemadir);
+ free_filelist(filelist);
+ rc = 0;
+ }
+ else
+ {
+ /* figure out the last file in the list; it is the user schema */
+ int ii = 0;
+ while (filelist[ii]) ++ii;
+ userschemafile = filelist[ii-1];
+ userschematmpfile = slapi_ch_calloc(1, strlen(userschemafile) + 5);
+ sprintf(userschematmpfile, "%s.tmp", userschemafile);
+ }
+
+ if(rc && (pschemadse==NULL))
+ {
+ pschemadse= dse_new_with_filelist(userschemafile,userschematmpfile, NULL, NULL,
+ schemadir,filelist);
+ PR_ASSERT(pschemadse);
+ if ((rc= (pschemadse!=NULL)) != 0)
+ dse_register_callback(pschemadse,DSE_OPERATION_READ,DSE_FLAG_PREOP,&schema,
+ LDAP_SCOPE_BASE,NULL,
+ load_schema_dse,NULL);
+ slapi_ch_free((void**)&userschematmpfile);
+ }
+ slapi_ch_free((void**)&schemadir);
+
+ if(rc)
+ {
+ char errorbuf[SLAPI_DSE_RETURNTEXT_SIZE] = {0};
+ int dont_write = 1;
+ int merge = 1;
+ int dont_dup_check = 1;
+ Slapi_PBlock pb;
+ memset(&pb, 0, sizeof(pb));
+ /* don't write out the file when reading */
+ slapi_pblock_set(&pb, SLAPI_DSE_DONT_WRITE_WHEN_ADDING, (void*)&dont_write);
+ /* duplicate entries are allowed */
+ slapi_pblock_set(&pb, SLAPI_DSE_MERGE_WHEN_ADDING, (void*)&merge);
+ /* use the non duplicate checking str2entry */
+ slapi_pblock_set(&pb, SLAPI_DSE_DONT_CHECK_DUPS, (void*)&dont_dup_check);
+
+ /* add the objectclass attribute so we can do some basic schema
+ checking during initialization; this will be overridden when
+ its "real" definition is read from the schema conf files */
+ rc = read_at_ldif("attributeTypes: ( 2.5.4.0 NAME 'objectClass' "
+ "DESC 'Standard schema for LDAP' SYNTAX "
+ "1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'RFC 2252' )",
+ NULL, errorbuf, SLAPI_DSE_RETURNTEXT_SIZE, 1, 0, 0, 0);
+ if (rc)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, "schema", "Could not add"
+ " attribute type \"objectClass\" to the schema: %s\n",
+ errorbuf, 0, 0);
+ }
+
+ rc = dse_read_file(pschemadse, &pb);
+ }
+
+ if (rc)
+ {
+ /* make sure the schema is normalized */
+ normalize_oc();
+
+ /* register callbacks */
+ dse_register_callback(pschemadse,SLAPI_OPERATION_SEARCH,DSE_FLAG_PREOP,&schema,
+ LDAP_SCOPE_BASE,NULL,read_schema_dse,NULL);
+ dse_register_callback(pschemadse,SLAPI_OPERATION_MODIFY,DSE_FLAG_PREOP,&schema,
+ LDAP_SCOPE_BASE,NULL,modify_schema_dse,NULL);
+ dse_register_callback(pschemadse,SLAPI_OPERATION_DELETE,DSE_FLAG_PREOP,&schema,
+ LDAP_SCOPE_BASE,NULL,dont_allow_that,NULL);
+ dse_register_callback(pschemadse,DSE_OPERATION_WRITE,DSE_FLAG_PREOP,&schema,
+ LDAP_SCOPE_BASE,NULL,refresh_user_defined_schema,
+ NULL);
+
+ if (rc) {
+ Slapi_Backend *be;
+
+ /* add as a backend */
+ be= be_new_internal(pschemadse,"DSE", "schema-internal"); /* JCM - should be a #define */
+ be_addsuffix(be,&schema);
+ }
+ }
+
+ slapi_sdn_done(&schema);
+ return rc;
+}
+
+
+
+/*
+ * Look for `keyword' within `*inputp' and return the flag_value if found
+ * (zero if returned if not found).
+ *
+ * If the keyword is found, `*inputp' is set to point just beyond the end of
+ * the keyword. If the keyword is not found, `*inputp' is not changed.
+ *
+ * The `strstr_fn' function pointer is used to search for `keyword', e.g., it
+ * could be PL_strcasestr().
+ *
+ * The string passed in `keyword' MUST include a trailing space, e.g.,
+ *
+ * flag |= get_flag_keyword( " COLLECTIVE ", SLAPI_ATTR_FLAG_COLLECTIVE,
+ * &input, PL_strcasestr );
+ *
+ */
+static int
+get_flag_keyword( const char *keyword, int flag_value, const char **inputp,
+ schema_strstr_fn_t strstr_fn )
+{
+ const char *kw;
+
+ PR_ASSERT( NULL != inputp );
+ PR_ASSERT( NULL != *inputp );
+ PR_ASSERT( ' ' == keyword[ strlen( keyword ) - 1 ] );
+
+ if ( NULL == strstr_fn ) {
+ strstr_fn = PL_strcasestr;
+ }
+
+ kw = (*strstr_fn)( *inputp, keyword );
+ if ( NULL == kw ) {
+ flag_value = 0; /* not found -- return no value */
+ } else {
+ *inputp = kw + strlen( keyword ) - 1; /* advance input */
+ }
+
+ return flag_value;
+}
+
+/*
+ * Look for `tag' within `*inputp' and return the OID string following `tag'.
+ * If the OID has single quotes around it they are removed (they are allowed
+ * for compatibility with DS 3.x and 4.x).
+ *
+ * If the tag is found, `*inputp' is set to point just beyond the end of
+ * the OID that was extracted and returned. If the tag is not found,
+ * `*inputp' is not changed.
+ *
+ * The `strstr_fn' function pointer is used to search for `tag', e.g., it
+ * could be PL_strcasestr().
+ *
+ * The string passed in `tag' MUST include a trailing space, e.g.,
+ *
+ * pSuperior = get_tagged_oid( "SUP ", &input, PL_strcasestr );
+ *
+ * A malloc'd string is returned if `tag; is found and NULL if not.
+ */
+static char *
+get_tagged_oid( const char *tag, const char **inputp,
+ schema_strstr_fn_t strstr_fn )
+{
+ const char *startp, *endp;
+ char *oid;
+
+ PR_ASSERT( NULL != inputp );
+ PR_ASSERT( NULL != *inputp );
+ PR_ASSERT( NULL != tag );
+ PR_ASSERT( '\0' != tag[ 0 ] );
+ if('(' !=tag[0])
+ PR_ASSERT( ' ' == tag[ strlen( tag ) - 1 ] );
+
+ if ( NULL == strstr_fn ) {
+ strstr_fn = PL_strcasestr;
+ }
+
+ oid = NULL;
+ if ( NULL != ( startp = (*strstr_fn)( *inputp, tag ))) {
+ startp += strlen( tag );
+
+ /* skip past any extra white space */
+ if ( NULL == ( startp = skipWS( startp ))) {
+ return( NULL );
+ }
+
+ /* skip past the leading single quote, if present */
+ if ( *startp == '\'' ) {
+ ++startp;
+ /* skip past any extra white space */
+ startp = skipWS( startp );
+ }
+
+ /* locate the end of the OID */
+ if ((NULL != ( endp = strchr( startp, ' '))) ||
+ (NULL != (endp = strchr( startp, ')'))) ) {
+ if ( '\'' == *(endp-1) && endp > startp ) {
+ --endp; /* ignore trailing quote */
+ }
+ } else {
+ endp = startp + strlen( startp ); /* remainder of input */
+ }
+
+ oid = slapi_ch_malloc( endp - startp + 1 );
+ memcpy( oid, startp, endp - startp );
+ oid[ endp - startp ] = '\0';
+ *inputp = endp;
+ }
+
+ return( oid );
+}
+
+
+/*
+ * sprintf to `outp' the contents of `tag' followed by `oid' followed by a
+ * trailing space. If enquote is non-zero, single quotes are included
+ * around the `oid' string. If `suffix' is not NULL, it is output directly
+ * after the `oid' (before the trailing space).
+ * Note that `tag' should typically include a trailing space, e.g.,
+ *
+ * outp += put_tagged_oid( outp, "SUP ", "1.2.3.4", NULL, enquote_oids );
+ *
+ * Returns the number of bytes copied to `outp' or 0 if `oid' is NULL.
+ */
+static int
+put_tagged_oid( char *outp, const char *tag, const char *oid,
+ const char *suffix, int enquote )
+{
+ int count = 0;
+
+ if ( NULL == suffix ) {
+ suffix = "";
+ }
+
+ if ( NULL != oid ) {
+ if ( enquote ) {
+ count = sprintf( outp, "%s'%s%s' ", tag, oid, suffix );
+ } else {
+ count = sprintf( outp, "%s%s%s ", tag, oid, suffix );
+ }
+ }
+
+ return( count );
+}
+
+
+/*
+ * Add to `buf' a string of the form:
+ *
+ * prefix SPACE ( oid1 $ oid2 ... ) SPACE
+ * OR
+ * prefix SPACE oid SPACE
+ *
+ * The part after <prefix> matches the `oids' definition
+ * from RFC 2252 section 4.1.
+ *
+ * If oids is NULL or an empty array, `buf' is not touched.
+ */
+static void
+strcat_oids( char *buf, char *prefix, char **oids, int schema_ds4x_compat )
+{
+ char *p;
+ int i;
+
+ if ( NULL != oids && NULL != oids[0] ) {
+ p = buf + strlen(buf); /* skip past existing content */
+ if ( NULL == oids[1] && !schema_ds4x_compat ) {
+ sprintf( p, "%s %s ", prefix, oids[0] ); /* just one oid */
+ } else {
+ sprintf( p, "%s ( ", prefix ); /* oidlist */
+ for ( i = 0; oids[i] != NULL; ++i ) {
+ if ( i > 0 ) {
+ strcat( p, " $ " );
+ }
+ strcat( p, oids[i] );
+ }
+ strcat( p, " ) " );
+ }
+ }
+}
+
+
+/*
+ * Add to `buf' a string of the form:
+ *
+ * prefix SPACE ( 's1' 's2' ... ) SPACE
+ * OR
+ * prefix SPACE 's1' SPACE
+ *
+ * The part after <prefix> matches the qdescs definition
+ * from RFC 2252 section 4.1.
+ *
+ * A count of the number of bytes added to buf or needed is returned.
+ *
+ * If buf is NULL, no copying is done but the number of bytes needed
+ * is calculated and returned. This is useful if you need to allocate
+ * space before calling this function will a buffer.
+ *
+ * If qdlist is NULL or an empty array, `buf' is not touched and zero
+ * is returned.
+ */
+static size_t
+strcat_qdlist( char *buf, char *prefix, char **qdlist )
+{
+ int i;
+ char *start, *p;
+ size_t len = 0;
+
+ if ( NULL != qdlist && NULL != qdlist[0] ) {
+ if ( NULL == buf ) { /* calculate length only */
+ len += strlen( prefix );
+ if ( NULL != qdlist[1] ) {
+ len += 4; /* surrounding spaces and '(' and ')' */
+ }
+ for ( i = 0; NULL != qdlist[i]; ++i ) {
+ len += 3; /* leading space and quote marks */
+ len += strlen(qdlist[i]);
+ }
+ ++len; /* trailing space */
+
+ } else {
+ p = start = buf + strlen(buf); /* skip past existing content */
+ if ( NULL == qdlist[1] ) { /* just one string */
+ p += sprintf( p, "%s '%s' ", prefix, qdlist[0] );
+ } else { /* a list of strings */
+ p += sprintf( p, "%s (", prefix );
+ for ( i = 0; qdlist[i] != NULL; ++i ) {
+ p += sprintf( p, " '%s'", qdlist[i] );
+ }
+ *p++ = ' ';
+ *p++ = ')';
+ *p++ = ' ';
+ *p = '\0';
+ }
+ len = p - start;
+ }
+ }
+
+ return( len );
+}
+
+
+/*
+ * Just like strlen() except that 0 is returned if `s' is NULL.
+ */
+static size_t
+strlen_null_ok(const char *s)
+{
+ if ( NULL == s ) {
+ return( 0 );
+ }
+
+ return( strlen( s ));
+}
+
+
+
+/*
+ * Like strcpy() except a count of the number of bytes copied is returned.
+ */
+static int
+strcpy_count( char *dst, const char *src )
+{
+ char *p;
+
+ p = dst;
+ while ( *src != '\0' ) {
+ *p++ = *src++;
+ }
+
+ *p = '\0';
+ return( p - dst );
+}
+
+
+/*
+ * Look for an X-ORIGIN list in `schema_value' and return a
+ * qdstrings array (or NULL if no list is present). *num_originsp is
+ * set to a count of the number of origin strings returned.
+ *
+ * If no X-ORIGIN list is found and `default_list' is non-NULL, a copy
+ * of default list is returned.
+ */
+static char **
+parse_origin_list( const char *schema_value, int *num_originsp,
+ char **default_list )
+{
+ char *start, *end, *origin_tmp;
+ char **origins = NULL;
+
+ if (( start = PL_strstr ( schema_value, "X-ORIGIN ")) != NULL ) {
+ start += 9;
+ origin_tmp = slapi_ch_strdup( start );
+ if ( *start == '(' ) {
+ end = strchr( origin_tmp, ')' );
+ } else {
+ end = strchr( origin_tmp + 1, '\'' );
+ }
+ if (end) {
+ *(end+1) = 0;
+ }
+ origins = parse_qdstrings( origin_tmp, num_originsp );
+ slapi_ch_free( (void **)&origin_tmp );
+ } else {
+ origins = NULL;
+ *num_originsp = 0;
+ }
+
+ if ( NULL == origins && NULL != default_list ) {
+ int i;
+
+ for ( i = 0; default_list[i] != NULL; ++i ) {
+ ;
+ }
+ *num_originsp = i;
+ origins = (char **)slapi_ch_malloc( (i+1) * sizeof(char *));
+ for ( i = 0; default_list[i] != NULL; ++i ) {
+ origins[i] = slapi_ch_strdup( default_list[i] );
+ }
+ origins[i] = NULL;
+ }
+
+if ( origins == NULL || origins[0] == NULL ) {
+ LDAPDebug( LDAP_DEBUG_ANY, "no origin (%s)\n", schema_value, 0, 0 );
+}
+
+ return origins;
+}
+
+
+/*
+ * Determine based on origin whether a schema element is standard or
+ * user-defined. Algorithm: perform a case insensitive search for
+ * "user defined". If found, return 1 (user defined); if not, return 0.
+ */
+static int
+element_is_user_defined( char * const *origins )
+{
+ int i;
+
+ if ( origins != NULL ) {
+ for ( i = 0; origins[i] != NULL; ++i ) {
+ if ( 0 == strcasecmp( schema_user_defined_origin[0], origins[i] )) {
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+/*
+ * Return PR_TRUE if the attribute type named 'type' is one of those that
+ * we handle directly in this file (in the scheme DSE callbacks).
+ * Other types are handled by the generic DSE code in dse.c.
+ */
+/* subschema DSE attribute types we handle within the DSE callback */
+static char *schema_interesting_attr_types[] = {
+ "dITStructureRules",
+ "nameForms",
+ "dITContentRules",
+ "objectClasses",
+ "attributeTypes",
+ "matchingRules",
+ "matchingRuleUse",
+ "ldapSyntaxes",
+ "nsschemacsn",
+ NULL
+};
+
+
+static PRBool
+schema_type_is_interesting( const char *type )
+{
+ int i;
+
+ for ( i = 0; schema_interesting_attr_types[i] != NULL; ++i ) {
+ if ( 0 == strcasecmp( type, schema_interesting_attr_types[i] )) {
+ return PR_TRUE;
+ }
+ }
+
+ return PR_FALSE;
+}
+
+
+static void
+schema_create_errormsg(
+ char *errorbuf,
+ size_t errorbufsize,
+ const char *prefix,
+ const char *name,
+ const char *fmt,
+ ...
+)
+{
+ if ( NULL != errorbuf ) {
+ va_list ap;
+ int rc = 0;
+
+ va_start( ap, fmt );
+
+ if ( NULL != name ) {
+ rc = PR_snprintf( errorbuf, errorbufsize, prefix, name );
+ }
+ if ( rc >= 0 ) {
+ (void)PR_vsnprintf( errorbuf + rc, errorbufsize - rc, fmt, ap );
+ }
+ va_end( ap );
+ }
+}
+
+
+/*
+ * va_locate_oc_val finds an objectclass within the array of values in va.
+ * First oc_name is used, falling back to oc_oid. oc_oid can be NULL.
+ * oc_name and oc_oid should be official names (no trailing spaces). But
+ * trailing spaces within the va are ignored if appropriate.
+ *
+ * Returns >=0 if found (index into va) and -1 if not found.
+ */
+static int
+va_locate_oc_val( Slapi_Value **va, const char *oc_name, const char *oc_oid )
+{
+ int i;
+ const char *strval;
+
+ if ( NULL == va || oc_name == NULL ) { /* nothing to look for */
+ return -1;
+ }
+
+ if ( !schema_ignore_trailing_spaces ) {
+ for ( i = 0; va[i] != NULL; i++ ) {
+ strval = slapi_value_get_string(va[i]);
+ if ( NULL != strval ) {
+ if ( 0 == strcasecmp(strval, oc_name)) {
+ return i;
+ }
+
+ if ( NULL != oc_oid
+ && 0 == strcasecmp( strval, oc_oid )) {
+ return i;
+ }
+ }
+ }
+ } else {
+ /*
+ * Ignore trailing spaces when comparing object class names.
+ */
+ size_t len;
+ const char *p;
+
+ for ( i = 0; va[i] != NULL; i++ ) {
+ strval = slapi_value_get_string(va[i]);
+ if ( NULL != strval ) {
+ for ( p = strval, len = 0; (*p != '\0') && (*p != ' ');
+ p++, len++ ) {
+ ; /* NULL */
+ }
+
+ if ( 0 == strncasecmp(oc_name, strval, len )
+ && ( len == strlen(oc_name))) {
+ return i;
+ }
+
+ if ( NULL != oc_oid
+ && ( 0 == strncasecmp( oc_oid, strval, len ))
+ && ( len == strlen(oc_oid))) {
+ return i;
+ }
+ }
+ }
+ }
+
+ return -1; /* not found */
+}
+
+
+/*
+ * va_expand_one_oc is used to add missing superclass values to the
+ * objectclass attribute when an entry is added or modified.
+ *
+ * missing values are always added to the end of the 'vap' array.
+ *
+ * Note: calls to this function MUST be bracketed by lock()/unlock(), i.e.,
+ *
+ * oc_lock_read();
+ * va_expand_one_oc( b, o );
+ * oc_unlock();
+ */
+static void
+va_expand_one_oc( const char *dn, Slapi_Value ***vap, const char *ocs )
+{
+ struct objclass *this_oc, *sup_oc;
+ int p,i;
+ Slapi_Value **newva;
+ char ebuf[BUFSIZ];
+
+ this_oc = oc_find_nolock( ocs );
+
+ if ( this_oc == NULL ) {
+ return; /* skip unknown object classes */
+ }
+
+ if ( this_oc->oc_superior == NULL ) {
+ return; /* no superior */
+ }
+
+ sup_oc = oc_find_nolock( this_oc->oc_superior );
+ if ( sup_oc == NULL ) {
+ return; /* superior is unknown -- ignore */
+ }
+
+ p = va_locate_oc_val( *vap, sup_oc->oc_name, sup_oc->oc_oid );
+
+ if ( p != -1 ) {
+ return; /* value already present -- done! */
+ }
+
+ /* parent was not found. add to the end */
+ for ( i = 0; (*vap)[i] != NULL; i++ ) {
+ ;
+ }
+
+ /* prevent loops: stop if more than 1000 OC values are present */
+ if ( i > 1000 ) {
+ return;
+ }
+
+ newva = (Slapi_Value **)slapi_ch_realloc( (char *)*vap,
+ ( i + 2 )*sizeof(Slapi_Value **));
+
+ newva[i] = slapi_value_new_string(sup_oc->oc_name);
+ newva[i+1] = NULL;
+
+ *vap = newva;
+ LDAPDebug( LDAP_DEBUG_TRACE,
+ "Entry \"%s\": added missing objectClass value %s\n",
+ escape_string( dn, ebuf ), sup_oc->oc_name, 0 );
+}
+
+
+/*
+ * Expand the objectClass values in 'e' to take superior classes into account.
+ * All missing superior classes are added to the objectClass attribute, as
+ * is 'top' if it is missing.
+ */
+void
+slapi_schema_expand_objectclasses( Slapi_Entry *e )
+{
+ Slapi_Attr *sa;
+ Slapi_Value **va;
+ const char *dn = slapi_entry_get_dn_const( e );
+ int i;
+
+ if ( 0 != slapi_entry_attr_find( e, SLAPI_ATTR_OBJECTCLASS, &sa )) {
+ return; /* no OC values -- nothing to do */
+ }
+
+ va = attr_get_present_values( sa );
+
+ if ( va == NULL || va[0] == NULL ) {
+ return; /* no OC values -- nothing to do */
+ }
+
+ oc_lock_read();
+
+ /*
+ * This loop relies on the fact that bv_expand_one_oc()
+ * always adds to the end
+ */
+ for ( i = 0; va[i] != NULL; ++i ) {
+ if ( NULL != slapi_value_get_string(va[i]) ) {
+ va_expand_one_oc( dn, &va, slapi_value_get_string(va[i]) );
+ }
+ }
+
+ /* top must always be present */
+ va_expand_one_oc( dn, &va, "top" );
+
+ /*
+ * Reset the present values in the set because we may have realloc'd it.
+ * Note that this is the counterpart to the attr_get_present_values()
+ * call we made above... nothing new has been allocated, but sa holds
+ * a pointer to the original (pre realloc) va.
+ */
+ sa->a_present_values.va = va;
+
+ oc_unlock();
+}