+ for (i=0; co->co_table[i].name; i++)
+ if ( co->co_table[i].ad == ad )
+ return &co->co_table[i];
+ return NULL;
+}
+
+/* Sort the values in an X-ORDERED VALUES attribute.
+ * If the values have no index, leave them in their given order.
+ * If the values have indexes, sort them.
+ * If some are indexed and some are not, return Error.
+ *
+ * FIXME: This function probably belongs in the frontend somewhere,
+ * like slap_mods_check.
+ */
+static int
+sort_vals( Attribute *a )
+{
+ int i;
+ int index = 0, noindex = 0;
+
+ /* count attrs, look for index */
+ for (i=0; a->a_vals[i].bv_val; i++) {
+ if ( a->a_vals[i].bv_val[0] == '{' ) {
+ char *ptr;
+ index = 1;
+ ptr = strchr( a->a_vals[i].bv_val, '}' );
+ if ( !ptr || !ptr[1] )
+ return LDAP_INVALID_SYNTAX;
+ if ( noindex )
+ return LDAP_INVALID_SYNTAX;
+ } else {
+ noindex = 1;
+ if ( index )
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ if ( index ) {
+ int vals = i, *indexes, j, idx;
+ struct berval tmp, ntmp;
+ char *ptr;
+
+#if 0
+ /* Strip index from normalized values */
+ if ( !a->a_nvals || a->a_vals == a->a_nvals ) {
+ a->a_nvals = ch_malloc( (vals+1)*sizeof(struct berval));
+ BER_BVZERO(a->a_nvals+vals);
+ for ( i=0; i<vals; i++ ) {
+ ptr = strchr(a->a_vals[i].bv_val, '}') + 1;
+ a->a_nvals[i].bv_len = a->a_vals[i].bv_len -
+ (ptr - a->a_vals[i].bv_val);
+ a->a_nvals[i].bv_val = ch_malloc( a->a_nvals[i].bv_len + 1);
+ strcpy(a->a_nvals[i].bv_val, ptr );
+ }
+ } else {
+ for ( i=0; i<vals; i++ ) {
+ ptr = strchr(a->a_nvals[i].bv_val, '}') + 1;
+ a->a_nvals[i].bv_len -= ptr - a->a_nvals[i].bv_val;
+ strcpy(a->a_nvals[i].bv_val, ptr);
+ }
+ }
+#endif
+
+ indexes = ch_malloc( vals * sizeof(int) );
+ for ( i=0; i<vals; i++)
+ indexes[i] = atoi(a->a_vals[i].bv_val+1);
+
+ /* Insertion sort */
+ for ( i=1; i<vals; i++ ) {
+ idx = indexes[i];
+ tmp = a->a_vals[i];
+ ntmp = a->a_nvals[i];
+ j = i;
+ while ((j > 0) && (indexes[j-1] > idx)) {
+ indexes[j] = indexes[j-1];
+ a->a_vals[j] = a->a_vals[j-1];
+ a->a_nvals[j] = a->a_nvals[j-1];
+ j--;
+ }
+ indexes[j] = idx;
+ a->a_vals[j] = tmp;
+ a->a_nvals[j] = ntmp;
+ }
+ }
+ return 0;
+}
+
+static int
+check_attr( ConfigTable *ct, ConfigArgs *ca, Attribute *a )
+{
+ int i, rc = 0, sort = 0;
+
+ if ( a->a_desc->ad_type->sat_flags & SLAP_AT_ORDERED ) {
+ sort = 1;
+ rc = sort_vals( a );
+ if ( rc )
+ return rc;
+ }
+ for ( i=0; a->a_nvals[i].bv_val; i++ ) {
+ ca->line = a->a_nvals[i].bv_val;
+ if ( sort ) ca->line = strchr( ca->line, '}' ) + 1;
+ rc = config_parse_vals( ct, ca, i );
+ if ( rc )
+ break;
+ }
+ return rc;
+}
+
+static int
+check_name_index( CfEntryInfo *parent, ConfigType ce_type, Entry *e,
+ SlapReply *rs, int *renum )
+{
+ CfEntryInfo *ce;
+ int index = -1, gotindex = 0, nsibs;
+ int renumber = 0, tailindex = 0;
+ char *ptr1, *ptr2;
+ struct berval rdn;
+
+ if ( renum ) *renum = 0;
+
+ /* These entries don't get indexed/renumbered */
+ if ( ce_type == Cft_Global ) return 0;
+ if ( ce_type == Cft_Schema && parent->ce_type == Cft_Global ) return 0;
+
+ if ( ce_type == Cft_Include || ce_type == Cft_Module )
+ tailindex = 1;
+
+ /* See if the rdn has an index already */
+ dnRdn( &e->e_name, &rdn );
+ ptr1 = strchr( e->e_name.bv_val, '{' );
+ if ( ptr1 && ptr1 - e->e_name.bv_val < rdn.bv_len ) {
+ ptr2 = strchr( ptr1, '}' );
+ if (!ptr2 || ptr2 - e->e_name.bv_val > rdn.bv_len)
+ return LDAP_NAMING_VIOLATION;
+ if ( ptr2-ptr1 == 1)
+ return LDAP_NAMING_VIOLATION;
+ gotindex = 1;
+ index = atoi(ptr1+1);
+ if ( index < 0 )
+ return LDAP_NAMING_VIOLATION;
+ }
+
+ /* count related kids */
+ for (nsibs=0, ce=parent->ce_kids; ce; ce=ce->ce_sibs) {
+ if ( ce->ce_type == ce_type ) nsibs++;
+ }
+
+ if ( index != nsibs ) {
+ if ( gotindex ) {
+ if ( index < nsibs ) {
+ if ( tailindex ) return LDAP_NAMING_VIOLATION;
+ /* Siblings need to be renumbered */
+ renumber = 1;
+ }
+ }
+ if ( !renumber ) {
+ struct berval ival, newrdn, nnewrdn;
+ struct berval rtype, rval;
+ Attribute *a;
+ AttributeDescription *ad = NULL;
+ char ibuf[32];
+ const char *text;
+
+ rval.bv_val = strchr(rdn.bv_val, '=' ) + 1;
+ rval.bv_len = rdn.bv_len - (rval.bv_val - rdn.bv_val);
+ rtype.bv_val = rdn.bv_val;
+ rtype.bv_len = rval.bv_val - rtype.bv_val - 1;
+
+ /* Find attr */
+ slap_bv2ad( &rtype, &ad, &text );
+ a = attr_find( e->e_attrs, ad );
+ if (!a ) return LDAP_NAMING_VIOLATION;
+
+ ival.bv_val = ibuf;
+ ival.bv_len = sprintf( ibuf, IFMT, nsibs );
+
+ newrdn.bv_len = rdn.bv_len + ival.bv_len;
+ newrdn.bv_val = ch_malloc( newrdn.bv_len+1 );
+
+ if ( tailindex ) {
+ ptr1 = lutil_strncopy( newrdn.bv_val, rdn.bv_val, rdn.bv_len );
+ ptr1 = lutil_strcopy( ptr1, ival.bv_val );
+ } else {
+ int xlen;
+ if ( !gotindex ) {
+ ptr2 = rval.bv_val;
+ xlen = rval.bv_len;
+ } else {
+ xlen = rdn.bv_len - (ptr2 - rdn.bv_val);
+ }
+ ptr1 = lutil_strncopy( newrdn.bv_val, rtype.bv_val,
+ rtype.bv_len );
+ *ptr1++ = '=';
+ ptr1 = lutil_strcopy( ptr1, ival.bv_val );
+ ptr1 = lutil_strncopy( ptr1, ptr2, xlen );
+ *ptr1 = '\0';
+ }
+
+ /* Do the equivalent of ModRDN */
+ /* Replace DN / NDN */
+ newrdn.bv_len = ptr1 - newrdn.bv_val;
+ rdnNormalize( 0, NULL, NULL, &newrdn, &nnewrdn, NULL );
+ free( e->e_name.bv_val );
+ build_new_dn( &e->e_name, &parent->ce_entry->e_name,
+ &newrdn, NULL );
+ free( e->e_nname.bv_val );
+ build_new_dn( &e->e_nname, &parent->ce_entry->e_nname,
+ &nnewrdn, NULL );
+
+ /* Replace attr */
+ free( a->a_vals[0].bv_val );
+ ptr1 = strchr( newrdn.bv_val, '=' ) + 1;
+ a->a_vals[0].bv_len = newrdn.bv_len - (ptr1 - newrdn.bv_val);
+ a->a_vals[0].bv_val = ch_malloc( a->a_vals[0].bv_len + 1 );
+ strcpy( a->a_vals[0].bv_val, ptr1 );
+
+ if ( a->a_nvals != a->a_vals ) {
+ free( a->a_nvals[0].bv_val );
+ ptr1 = strchr( nnewrdn.bv_val, '=' ) + 1;
+ a->a_nvals[0].bv_len = nnewrdn.bv_len - (ptr1 - nnewrdn.bv_val);
+ a->a_nvals[0].bv_val = ch_malloc( a->a_nvals[0].bv_len + 1 );
+ strcpy( a->a_nvals[0].bv_val, ptr1 );
+ }
+ free( nnewrdn.bv_val );
+ free( newrdn.bv_val );
+ }
+ }
+ if ( renum ) *renum = renumber;
+ return 0;
+}
+
+/* Parse an LDAP entry into config directives */
+static int
+config_add_internal( CfBackInfo *cfb, Entry *e, SlapReply *rs, int *renum )
+{
+ CfEntryInfo *ce, *last;
+ CfOcInfo co, *coptr, **colst = NULL;
+ Attribute *a, *oc_at, *type_attr;
+ AttributeDescription *type_ad = NULL;
+ int i, j, nocs, rc;
+ ConfigArgs ca = {0};
+ struct berval pdn;
+ Entry *xe = NULL;
+ ConfigTable *ct, *type_ct = NULL;
+
+ /* Make sure parent exists and entry does not */
+ ce = config_find_base( cfb->cb_root, &e->e_nname, &last );
+ if ( ce )
+ return LDAP_ALREADY_EXISTS;
+
+ dnParent( &e->e_nname, &pdn );
+
+ /* If last is NULL, the new entry is the root/suffix entry,
+ * otherwise last should be the parent.
+ */
+ if ( last && !dn_match( &last->ce_entry->e_nname, &pdn )) {
+ if ( rs )
+ rs->sr_matched = last->ce_entry->e_name.bv_val;
+ return LDAP_NO_SUCH_OBJECT;
+ }
+
+ oc_at = attr_find( e->e_attrs, slap_schema.si_ad_objectClass );
+ if ( !oc_at ) return LDAP_OBJECT_CLASS_VIOLATION;
+
+ /* count the objectclasses */
+ for ( i=0; oc_at->a_nvals[i].bv_val; i++ );
+ nocs = i;
+ colst = (CfOcInfo **)ch_malloc( nocs * sizeof(CfOcInfo *));
+
+ for ( i=0, j=0; i<nocs; i++) {
+ co.co_name = &oc_at->a_nvals[i];
+ coptr = avl_find( CfOcTree, &co, CfOcInfo_cmp );
+
+ /* ignore non-config objectclasses. probably should be
+ * an error, general data doesn't belong here.
+ */
+ if ( !coptr ) continue;
+
+ /* Ignore the root objectclass, it has no implementation.
+ */
+ if ( coptr->co_type == Cft_Abstract ) continue;
+ colst[j++] = coptr;
+ }
+ nocs = j;
+
+ /* Only the root can be Cft_Global, everything else must
+ * have a parent. Only limited nesting arrangements are allowed.
+ */
+ switch( colst[0]->co_type ) {
+ case Cft_Global:
+ if ( last ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto leave;
+ }
+ break;
+ case Cft_Schema:
+ case Cft_Backend:
+ case Cft_Database:
+ case Cft_Include:
+ if ( !last || ( last->ce_type != Cft_Global &&
+ last->ce_type != colst[0]->co_type )) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto leave;
+ }
+ break;
+ case Cft_Overlay:
+ if ( !last || ( last->ce_type != Cft_Global &&
+ last->ce_type != Cft_Database &&
+ last->ce_type != colst[0]->co_type )) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto leave;
+ }
+ break;
+#ifdef SLAPD_MODULES
+ case Cft_Module:
+ if ( !last || last->ce_type != Cft_Global ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto leave;
+ }
+#endif
+ break;
+ }
+
+ /* Parse all the values and check for simple syntax errors before
+ * performing any set actions.
+ */
+ switch (colst[0]->co_type) {
+ case Cft_Schema:
+ /* The cn=schema entry is all hardcoded, so never reparse it */
+ if (last->ce_type == Cft_Global )
+ goto ok;
+ /* FALLTHRU */
+ case Cft_Global:
+ ca.be = LDAP_STAILQ_FIRST(&backendDB);
+ break;
+
+ case Cft_Backend:
+ if ( last->ce_type == Cft_Backend )
+ ca.bi = last->ce_bi;
+ else
+ type_ad = cfAd_backend;
+ break;
+ case Cft_Database:
+ if ( last->ce_type == Cft_Database ) {
+ ca.be = last->ce_be;
+ } else {
+ type_ad = cfAd_database;
+ /* dummy, just to get past check_attr */
+ ca.be = frontendDB;
+ }
+ break;
+
+ case Cft_Overlay:
+ ca.be = last->ce_be;
+ type_ad = cfAd_overlay;
+ break;
+
+ case Cft_Include:
+ if ( !rs ) /* ignored */
+ break;
+ type_ad = cfAd_include;
+ break;
+#ifdef SLAPD_MODULES
+ case Cft_Module: {
+ ModPaths *mp;
+ char *ptr;
+ ptr = strchr( e->e_name.bv_val, '{' );
+ if ( !ptr ) {
+ rc = LDAP_NAMING_VIOLATION;
+ goto leave;
+ }
+ j = atoi(ptr+1);
+ for (i=0, mp=&modpaths; mp && i<j; mp=mp->mp_next);
+ /* There is no corresponding modpath for this load? */
+ if ( i != j ) {
+ rc = LDAP_NAMING_VIOLATION;
+ goto leave;
+ }
+ module_path( mp->mp_path.bv_val );
+ ca.private = mp;
+ }
+ break;
+#endif
+ }
+
+ /* If doing an LDAPadd, check for indexed names and any necessary
+ * renaming/renumbering. Entries that don't need indexed names are
+ * ignored. Entries that need an indexed name and arrive without one
+ * are assigned to the end. Entries that arrive with an index may
+ * cause the following entries to be renumbered/bumped down.
+ *
+ * Note that "pseudo-indexed" entries (cn=Include{xx}, cn=Module{xx})
+ * don't allow Adding an entry with an index that's already in use.
+ * This is flagged as an error (LDAP_ALREADY_EXISTS) up above.
+ *
+ * These entries can have auto-assigned indexes (appended to the end)
+ * but only the other types support auto-renumbering of siblings.
+ */
+ rc = check_name_index( last, colst[0]->co_type, e, rs, renum );
+ if ( rc )
+ goto leave;
+
+ init_config_argv( &ca );
+ if ( type_ad ) {
+ type_attr = attr_find( e->e_attrs, type_ad );
+ if ( !type_attr ) {
+ rc = LDAP_OBJECT_CLASS_VIOLATION;
+ goto leave;
+ }
+ for ( i=0; i<nocs; i++ ) {
+ type_ct = config_find_table( colst[i], type_ad );
+ if ( type_ct ) break;
+ }
+ if ( !type_ct ) {
+ rc = LDAP_OBJECT_CLASS_VIOLATION;
+ goto leave;
+ }
+ rc = check_attr( type_ct, &ca, type_attr );
+ if ( rc ) goto leave;
+ }
+ for ( a=e->e_attrs; a; a=a->a_next ) {
+ if ( a == type_attr || a == oc_at ) continue;
+ ct = NULL;
+ for ( i=0; i<nocs; i++ ) {
+ ct = config_find_table( colst[i], a->a_desc );
+ if ( ct ) break;
+ }
+ if ( !ct ) continue; /* user data? */
+ rc = check_attr( ct, &ca, a );
+ if ( rc ) goto leave;
+ }
+
+ /* Basic syntax checks are OK. Do the actual settings. */
+ if ( type_ct ) {
+ ca.line = type_attr->a_vals[0].bv_val;
+ if ( type_ad->ad_type->sat_flags & SLAP_AT_ORDERED )
+ ca.line = strchr( ca.line, '}' ) + 1;
+ rc = config_parse_add( type_ct, &ca, 0 );
+ if ( rc ) {
+ rc = LDAP_OTHER;
+ goto leave;
+ }
+ }
+ for ( a=e->e_attrs; a; a=a->a_next ) {
+ if ( a == type_attr || a == oc_at ) continue;
+ ct = NULL;
+ for ( i=0; i<nocs; i++ ) {
+ ct = config_find_table( colst[i], a->a_desc );
+ if ( ct ) break;
+ }
+ if ( !ct ) continue; /* user data? */
+ for (i=0; a->a_vals[i].bv_val; i++) {
+ ca.line = a->a_vals[i].bv_val;
+ if ( a->a_desc->ad_type->sat_flags & SLAP_AT_ORDERED )
+ ca.line = strchr( ca.line, '}' ) + 1;
+ rc = config_parse_add( ct, &ca, i );
+ if ( rc ) {
+ rc = LDAP_OTHER;
+ goto leave;
+ }
+ }
+ }
+ok:
+ ce = ch_calloc( 1, sizeof(CfEntryInfo) );
+ ce->ce_parent = last;
+ ce->ce_entry = entry_dup( e );
+ ce->ce_entry->e_private = ce;
+ ce->ce_type = colst[0]->co_type;
+ if ( !last ) {
+ cfb->cb_root = ce;
+ } else if ( last->ce_kids ) {
+ CfEntryInfo *c2;
+
+ for (c2=last->ce_kids; c2 && c2->ce_sibs; c2 = c2->ce_sibs);
+
+ c2->ce_sibs = ce;
+ } else {
+ last->ce_kids = ce;
+ }
+
+leave:
+ ch_free( ca.argv );
+ if ( colst ) ch_free( colst );
+ return rc;
+}
+
+/* Parse an LDAP entry into config directives, then store in underlying
+ * database.
+ */
+static int
+config_back_add( Operation *op, SlapReply *rs )
+{
+ CfBackInfo *cfb;
+ CfEntryInfo *ce, *last;
+ int renumber;
+
+ if ( !be_isroot( op ) ) {
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ send_ldap_result( op, rs );
+ }
+
+ cfb = (CfBackInfo *)op->o_bd->be_private;
+
+ ldap_pvt_thread_pool_pause( &connection_pool );
+
+ /* Strategy:
+ * 1) check for existence of entry
+ * 2) check for sibling renumbering
+ * 3) perform internal add
+ * 4) store entry in underlying database
+ * 5) perform any necessary renumbering
+ */
+ rs->sr_err = config_add_internal( cfb, op->ora_e, rs, &renumber );
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ BackendDB *be = op->o_bd;
+ slap_callback sc = { NULL, slap_null_cb, NULL, NULL };
+ op->o_bd = &cfb->cb_db;
+ sc.sc_next = op->o_callback;
+ op->o_callback = ≻
+ op->o_bd->be_add( op, rs );
+ op->o_bd = be;
+ op->o_callback = sc.sc_next;
+ }
+ if ( renumber ) {
+ }
+
+ ldap_pvt_thread_pool_resume( &connection_pool );
+
+out:
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+}
+
+/* Modify rules:
+ * for single-valued attributes, should just use REPLACE.
+ * any received DELETE/ADD on a single-valued attr will
+ * be checked (if a DEL value is provided) and then
+ * rewritten as a REPLACE.
+ * any DELETE received without a corresponding ADD will be
+ * rejected with LDAP_CONSTRAINT_VIOLATION.
+ */
+static int
+config_back_modify( Operation *op, SlapReply *rs )
+{
+ CfBackInfo *cfb;
+ CfEntryInfo *ce, *last;
+
+ if ( !be_isroot( op ) ) {
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ send_ldap_result( op, rs );
+ }
+
+ cfb = (CfBackInfo *)op->o_bd->be_private;
+
+ ce = config_find_base( cfb->cb_root, &op->o_req_ndn, &last );
+ if ( !ce ) {
+ if ( last )
+ rs->sr_matched = last->ce_entry->e_name.bv_val;
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ goto out;
+ }
+ ldap_pvt_thread_pool_pause( &connection_pool );
+
+ /* Strategy:
+ * 1) perform the Modify on the cached Entry.
+ * 2) verify that the Entry still satisfies the schema.
+ * 3) perform the individual config operations.
+ * 4) store Modified entry in underlying LDIF backend.
+ */
+ ldap_pvt_thread_pool_resume( &connection_pool );
+out:
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+}
+
+static int
+config_back_modrdn( Operation *op, SlapReply *rs )
+{
+ CfBackInfo *cfb;
+ CfEntryInfo *ce, *last;
+
+ if ( !be_isroot( op ) ) {
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ send_ldap_result( op, rs );
+ }
+
+ cfb = (CfBackInfo *)op->o_bd->be_private;
+
+ ce = config_find_base( cfb->cb_root, &op->o_req_ndn, &last );
+ if ( !ce ) {
+ if ( last )
+ rs->sr_matched = last->ce_entry->e_name.bv_val;
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ goto out;
+ }
+
+ /* We don't allow moving objects to new parents.
+ * Generally we only allow reordering a set of ordered entries.
+ */
+ if ( op->orr_newSup ) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ goto out;
+ }
+ ldap_pvt_thread_pool_pause( &connection_pool );
+
+ ldap_pvt_thread_pool_resume( &connection_pool );
+out:
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+}
+
+static int
+config_back_search( Operation *op, SlapReply *rs )
+{
+ CfBackInfo *cfb;
+ CfEntryInfo *ce, *last;
+ int rc;
+
+ if ( !be_isroot( op ) ) {