From b2093e3016027d6b5cf06b3f91f30769bfc099e2 Mon Sep 17 00:00:00 2001 From: cvsadm Date: Fri, 21 Jan 2005 00:44:34 +0000 Subject: Moving NSCP Directory Server from DirectoryBranch to TRUNK, initial drop. (foxworth) --- ldap/servers/slapd/attr.c | 849 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 849 insertions(+) create mode 100644 ldap/servers/slapd/attr.c (limited to 'ldap/servers/slapd/attr.c') diff --git a/ldap/servers/slapd/attr.c b/ldap/servers/slapd/attr.c new file mode 100644 index 00000000..78b1b2a0 --- /dev/null +++ b/ldap/servers/slapd/attr.c @@ -0,0 +1,849 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ +/* attr.c - routines for dealing with attributes */ + +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#include +#include +#include +#endif +#include "slap.h" +#undef DEBUG /* disable counters */ +#include + + +static int counters_created= 0; +PR_DEFINE_COUNTER(slapi_attr_counter_created); +PR_DEFINE_COUNTER(slapi_attr_counter_deleted); +PR_DEFINE_COUNTER(slapi_attr_counter_exist); + +/* + * structure used within AVL value trees. + */ +typedef struct slapi_attr_value_index { + int savi_index; /* index into a_vals[] */ + struct berval *savi_normval; /* normalized value */ +} SlapiAttrValueIndex; + +/* + * Utility function used by slapi_attr_type_cmp to + * find the next component of an attribute type. + */ +static const char * +next_comp( const char *s ) +{ + while ( *s && *s != ';' ) { + s++; + } + if ( *s == '\0' ) { + return( NULL ); + } else { + return( s + 1 ); + } +} + +/* + * Utility function used by slapi_attr_type_cmp to + * compare two components of an attribute type. + */ +static int +comp_cmp( const char *s1, const char *s2 ) +{ + while ( *s1 && *s1 != ';' && tolower( *s1 ) == tolower( *s2 ) ) { + s1++, s2++; + } + if ( *s1 != *s2 ) { + if ( (*s1 == '\0' || *s1 == ';') && + (*s2 == '\0' || *s2 == ';') ) { + return( 0 ); + } else { + return( 1 ); + } + } else { + return( 0 ); + } +} + +int +slapi_attr_type_cmp( const char *a1, const char *a2, int opt ) +{ + int rc= 0; + + switch ( opt ) { + case SLAPI_TYPE_CMP_EXACT: /* compare base name + options as given */ + rc = strcmp( a1, a2 ); + break; + + case SLAPI_TYPE_CMP_BASE: /* ignore options on both names - compare base names only */ + rc = comp_cmp( a1, a2 ); + break; + + case SLAPI_TYPE_CMP_SUBTYPE: /* ignore options on second name that are not in first name */ + /* + * first, check that the base types match + */ + if ( comp_cmp( a1, a2 ) != 0 ) { + rc = 1; + break; + } + /* + * next, for each component in a1, make sure there is a + * matching component in a2. the order must be the same, + * so we can keep looking where we left off each time in a2 + */ + rc = 0; + for ( a1 = next_comp( a1 ); a1 != NULL; a1 = next_comp( a1 ) ) { + for ( a2 = next_comp( a2 ); a2 != NULL; + a2 = next_comp( a2 ) ) { + if ( comp_cmp( a1, a2 ) == 0 ) { + break; + } + } + if ( a2 == NULL ) { + rc = 1; + break; + } + } + break; + } + + return( rc ); +} + + + + +/* + * Return 1 if the two types are equivalent -- either the same type name, + * or aliases for one another, including OIDs. + * + * Objective: don't allocate any memory! + */ +int +slapi_attr_types_equivalent(const char *t1, const char *t2) +{ + int retval = 0; + struct asyntaxinfo *asi1, *asi2; + + if (NULL == t1 || NULL == t2) { + return 0; + } + + asi1 = attr_syntax_get_by_name(t1); + asi2 = attr_syntax_get_by_name(t2); + if (NULL != asi1) { + if (NULL != asi2) { + /* Both found - compare normalized names */ + if (strcasecmp(asi1->asi_name, asi2->asi_name) == 0) { + retval = 1; + } else { + retval = 0; + } + } else { + /* One found, the other wasn't, so not equivalent */ + retval = 0; + } + } else if (NULL != asi2) { + /* One found, the other wasn't, so not equivalent */ + retval = 0; + } else { + /* Neither found - perform case-insensitive compare */ + if (strcasecmp(t1, t2) == 0) { + retval = 1; + } else { + retval = 0; + } + } + + attr_syntax_return( asi1 ); + attr_syntax_return( asi2 ); + + return retval; +} + +Slapi_Attr * +slapi_attr_new() +{ + Slapi_Attr *a= (Slapi_Attr *)slapi_ch_calloc( 1, sizeof(Slapi_Attr)); + if(!counters_created) + { + PR_CREATE_COUNTER(slapi_attr_counter_created,"Slapi_Attr","created",""); + PR_CREATE_COUNTER(slapi_attr_counter_deleted,"Slapi_Attr","deleted",""); + PR_CREATE_COUNTER(slapi_attr_counter_exist,"Slapi_Attr","exist",""); + counters_created= 1; + } + PR_INCREMENT_COUNTER(slapi_attr_counter_created); + PR_INCREMENT_COUNTER(slapi_attr_counter_exist); + return a; +} + +Slapi_Attr * +slapi_attr_init(Slapi_Attr *a, const char *type) +{ + return slapi_attr_init_locking_optional(a, type, PR_TRUE, PR_TRUE); +} + +Slapi_Attr * +slapi_attr_init_locking_optional(Slapi_Attr *a, const char *type, PRBool use_lock, PRBool ref_count) +{ + PR_ASSERT(a!=NULL); + + if(NULL != a) + { + struct asyntaxinfo *asi= NULL; + const char *basetype= NULL; + char *tmp= NULL; + + if(type!=NULL) + { + char buf[SLAPD_TYPICAL_ATTRIBUTE_NAME_MAX_LENGTH]; + basetype = buf; + tmp = slapi_attr_basetype(type, buf, sizeof(buf)); + if(tmp != NULL) + { + basetype = tmp; /* basetype was malloc'd */ + } + asi = attr_syntax_get_by_name_locking_optional(basetype, use_lock, ref_count); + } + if(NULL == asi) + { + a->a_type = attr_syntax_normalize_no_lookup( type ); + /* + * no syntax for this type... return DirectoryString + * syntax. we accomplish this by looking up a well known + * attribute type that has that syntax. + */ + asi = attr_syntax_get_by_name_locking_optional( + ATTR_WITH_DIRSTRING_SYNTAX, use_lock, ref_count); + } + else + { + char *attroptions = NULL; + + if ( NULL != type ) { + attroptions = strchr( type, ';' ); + } + + if ( NULL == attroptions ) { + a->a_type = slapi_ch_strdup(asi->asi_name); + } else { + /* + * If the original type includes any attribute options, + * the a_type field is set to the type contained in the + * attribute syntax followed by a normalized copy of the + * options. + */ + char *normalized_options; + + normalized_options = attr_syntax_normalize_no_lookup( attroptions ); + a->a_type = slapi_ch_malloc( strlen(asi->asi_name) + + strlen(normalized_options) +1 ); + sprintf( a->a_type, "%s%s", asi->asi_name, normalized_options ); + slapi_ch_free_string( &normalized_options ); + } + } + if ( asi != NULL ) + { + a->a_plugin = asi->asi_plugin; + a->a_flags = asi->asi_flags; + } + else + { + a->a_plugin = NULL; /* XXX - should be rare */ + a->a_flags = 0; /* XXX - should be rare */ + } + + attr_syntax_return_locking_optional( asi, use_lock ); + + if (NULL != tmp) + { + slapi_ch_free_string(&tmp); + } + slapi_valueset_init(&a->a_present_values); + slapi_valueset_init(&a->a_deleted_values); + a->a_listtofree= NULL; + a->a_deletioncsn= NULL; + a->a_next= NULL; + } + + return a; +} + +Slapi_Attr * +slapi_attr_dup(const Slapi_Attr *attr) +{ + Slapi_Attr *newattr= slapi_attr_new(); + Slapi_Value **present_va= valueset_get_valuearray(&attr->a_present_values); /* JCM Mucking about inside the value set */ + Slapi_Value **deleted_va= valueset_get_valuearray(&attr->a_deleted_values); /* JCM Mucking about inside the value set */ + slapi_attr_init(newattr, attr->a_type); + valueset_add_valuearray( &newattr->a_present_values, present_va ); + valueset_add_valuearray( &newattr->a_deleted_values, deleted_va ); + newattr->a_deletioncsn= csn_dup(attr->a_deletioncsn); + return newattr; +} + +void +slapi_attr_free( Slapi_Attr **ppa ) +{ + if(ppa!=NULL && *ppa!=NULL) + { + Slapi_Attr *a= *ppa; + attr_done(a); + slapi_ch_free( (void**)&a ); + PR_INCREMENT_COUNTER(slapi_attr_counter_deleted); + PR_DECREMENT_COUNTER(slapi_attr_counter_exist); + } +} + +void +attr_done(Slapi_Attr *a) +{ + if(a!=NULL) + { + slapi_ch_free((void**)&a->a_type); + csn_free(&a->a_deletioncsn); + slapi_valueset_done(&a->a_present_values); + slapi_valueset_done(&a->a_deleted_values); + { + struct bervals2free *freelist; + struct bervals2free *tmp; + freelist = a->a_listtofree; + while(freelist) { + ber_bvecfree(freelist->bvals); + tmp=freelist; + freelist = freelist->next; + slapi_ch_free((void **)&tmp); + } + } + } +} + +/* + * slapi_attr_basetype - extracts the attribute base type (without + * any attribute description options) from type. puts the result + * in buf if it can, otherwise, it malloc's and returns the base + * which should be free()'ed later. + */ +char * +slapi_attr_basetype( const char *type, char *buf, size_t bufsize ) +{ + unsigned int i; + + i = 0; + while ( *type && *type != ';' && i < bufsize ) { + buf[i++] = *type++; + } + if ( i < bufsize ) { + buf[i] = '\0'; + return( NULL ); + } else { + int len; + char *tmp; + + len = strlen( type ); + tmp = slapi_ch_malloc( len + 1 ); + slapi_attr_basetype( type, tmp, len + 1 ); + return( tmp ); + } +} + +/* + * returns 0 if "v" is already a value within "a" and non-zero if not. + */ +int +slapi_attr_value_find( const Slapi_Attr *a, const struct berval *v ) +{ + struct ava ava; + + if ( NULL == a ) { + return( -1 ); + } + + ava.ava_type = a->a_type; + ava.ava_value = *v; + return(plugin_call_syntax_filter_ava( a, LDAP_FILTER_EQUALITY, &ava )); +} + +int +slapi_attr_get_type( Slapi_Attr *a, char **type ) +{ + *type = a->a_type; + return( 0 ); +} + + +/* + * Fetch a copy of the values as an array of struct berval *'s. + * Returns 0 upon success and non-zero on failure. + * Free the array of values by calling ber_bvecfree(). + */ +int +slapi_attr_get_bervals_copy( Slapi_Attr *a, struct berval ***vals ) +{ + int retVal= 0; + + if ( NULL == vals ) + { + return -1; + } + + if(NULL==a || valueset_isempty(&a->a_present_values)) + { + *vals = NULL; + } + else + { + Slapi_Value **va= valueset_get_valuearray(&a->a_present_values); + valuearray_get_bervalarray(va,vals); + } + return retVal; +} + + +/* + * JCM: BEWARE.. HIGHLY EVIL.. DO NOT USE THIS FUNCTION. + * XXXmcs: Why not? Because it is only provided as a not-quite-backwards + * compatible interface for older (pre-iDS 5.0) plugins. It works by + * making a copy of the attribute values (the pre-iDS 5.0 function with + * the same name returned a pointer into the Slapi_Attr structure -- no + * copying). Since older users of this interface did not have to free + * the values, this function arranges to free them WHEN THE Slapi_Attr + * IS DESTROYED. This is accomplished by adding a pointer to the newly + * allocated copy to a "to be freed" linked list inside the Slapi_Attr. + * But if the Slapi_Attr is not destroyed very often, and this function + * is called repeatedly, memory usage will grow without bound. Not good. + * The value copies are freed inside attr_done() which is called from + * slapi_attr_free() and a few other places. + * + * If you really want a copy of the values as a struct berval ** array, + * call slapi_attr_get_bervals_copy() and free it yourself by calling + * ber_bvecfree(). + */ +int +slapi_attr_get_values( Slapi_Attr *a, struct berval ***vals ) +{ + int retVal = slapi_attr_get_bervals_copy( a, vals ); + + if ( 0 == retVal ) + { + struct bervals2free *newfree; + newfree = (struct bervals2free *)slapi_ch_malloc(sizeof(struct bervals2free)); + newfree->next = a->a_listtofree; + newfree->bvals = *vals; + a->a_listtofree = newfree; + } + + return retVal; +} + + +/* + * Fetch a copy of the present valueset. + * Caller must free the valueset. + */ +int +slapi_attr_get_valueset(const Slapi_Attr *a, Slapi_ValueSet **vs) +{ + int retVal= 0; + if(vs!=NULL) + { + *vs= valueset_dup(&a->a_present_values); + } + return retVal; +} + +/* + * Careful... this returns a pointer to the contents! + */ +Slapi_Value ** +attr_get_present_values(const Slapi_Attr *a) +{ + return valueset_get_valuearray(&a->a_present_values); +} + +int +slapi_attr_get_flags( const Slapi_Attr *a, unsigned long *flags ) +{ + *flags = a->a_flags; + return( 0 ); +} + +int +slapi_attr_flag_is_set( const Slapi_Attr *a, unsigned long flag ) +{ + return( a->a_flags & flag ); +} + +int +slapi_attr_value_cmp( const Slapi_Attr *a, const struct berval *v1, const struct berval *v2 ) +{ + int retVal; + + if ( a->a_flags & SLAPI_ATTR_FLAG_CMP_BITBYBIT ) + { + int cmplen = ( v1->bv_len <= v2->bv_len ? v1->bv_len : v2->bv_len ); + retVal = memcmp(v1->bv_val, v2->bv_val, cmplen); + if ( retVal == 0 && v1->bv_len < v2->bv_len ) + { + retVal = -1; + } + else if ( retVal == 0 && v1->bv_len > v2->bv_len ) + { + retVal = 1; + } + } + else + { + Slapi_Attr a2; + struct ava ava; + Slapi_Value *cvals[2]; + Slapi_Value tmpcval; + + a2 = *a; + cvals[0] = &tmpcval; + cvals[0]->v_csnset = NULL; + cvals[0]->bv = *v1; + cvals[1] = NULL; + a2.a_present_values.va = cvals; /* JCM - PUKE */ + ava.ava_type = a->a_type; + ava.ava_value = *v2; + retVal = plugin_call_syntax_filter_ava(&a2, LDAP_FILTER_EQUALITY, &ava); + } + return retVal; +} + +/* + * Set the CSN of all the present values. + */ +int +attr_set_csn( Slapi_Attr *a, const CSN *csn) +{ + PR_ASSERT(a!=NULL); + valueset_update_csn(&a->a_present_values, CSN_TYPE_VALUE_UPDATED, csn); + return 0; +} + +int +attr_set_deletion_csn( Slapi_Attr *a, const CSN *csn) +{ + PR_ASSERT(a!=NULL); + if(csn_compare(csn,a->a_deletioncsn)>0) + { + csn_free(&a->a_deletioncsn); + a->a_deletioncsn= csn_dup(csn); + } + return 0; +} + +const CSN * +attr_get_deletion_csn(const Slapi_Attr *a) +{ + PR_ASSERT(a!=NULL); + return a->a_deletioncsn; +} + +int +slapi_attr_first_value( Slapi_Attr *a, Slapi_Value **v ) +{ + int rc; + + if(NULL == a) { + rc = -1; + } else { + rc=slapi_valueset_first_value( &a->a_present_values, v ); + } + return rc; +} + +int +slapi_attr_next_value( Slapi_Attr *a, int hint, Slapi_Value **v) +{ + int rc; + + if(NULL == a) { + rc = -1; + } else { + rc=slapi_valueset_next_value( &a->a_present_values, hint, v ); + } + return rc; +} + +int +slapi_attr_get_numvalues( const Slapi_Attr *a, int *numValues ) +{ + if(NULL == a) { + *numValues = 0; + } else { + *numValues = slapi_valueset_count(&a->a_present_values); + } + return 0; +} + + +int +attr_first_deleted_value( Slapi_Attr *a, Slapi_Value **v ) +{ + return slapi_valueset_first_value( &a->a_deleted_values, v ); +} + +int +attr_next_deleted_value( Slapi_Attr *a, int hint, Slapi_Value **v) +{ + return slapi_valueset_next_value( &a->a_deleted_values, hint, v ); +} + +/* + * Note: We are passing in the entry so that we may be able to "optimize" + * the csn related information and roll it up higher to the level of entry. + */ +void +attr_purge_state_information(Slapi_Entry *entry, Slapi_Attr *attr, const CSN *csnUpTo) +{ + if(!valueset_isempty(&attr->a_deleted_values)) + { + valueset_purge(&attr->a_deleted_values, csnUpTo); + } +} + +/* + * Search an attribute for a value, it could be present, deleted, or not present. + */ +int +attr_value_find_wsi(Slapi_Attr *a, const struct berval *bval, Slapi_Value **value) +{ + int retVal=0; + struct ava ava; + + PR_ASSERT(a!=NULL); + PR_ASSERT(value!=NULL); + + /* + * we will first search the present values, and then, if + * necessary, the deleted values. + */ + ava.ava_type = a->a_type; + ava.ava_value = *bval; + retVal = plugin_call_syntax_filter_ava_sv(a, LDAP_FILTER_EQUALITY, &ava, value, 0 /* Present */); + + if(retVal==0) + { + /* we found the value, so we don't search the deleted list */ + retVal= VALUE_PRESENT; + } + else + { + retVal = plugin_call_syntax_filter_ava_sv(a, LDAP_FILTER_EQUALITY, &ava, value, 1 /* Deleted */); + if(retVal==0) + { + /* It was on the deleted value list */ + retVal= VALUE_DELETED; + } + else + { + /* Couldn't find it */ + retVal= VALUE_NOTFOUND; + } + } + + return retVal; +} + +int +slapi_attr_add_value(Slapi_Attr *a, const Slapi_Value *v) +{ + slapi_valueset_add_value( &a->a_present_values, v); + return 0; +} + +/* Make the valuset in SLapi_Attr be *vs--not a copy */ +int +slapi_attr_set_valueset(Slapi_Attr *a, const Slapi_ValueSet *vs) +{ + slapi_valueset_set_valueset( &a->a_present_values, vs); + return 0; +} + +int +attr_add_deleted_value(Slapi_Attr *a, const Slapi_Value *v) +{ + slapi_valueset_add_value( &a->a_deleted_values, v); + return 0; +} + +/* + * If we are adding or deleting SLAPD_MODUTIL_TREE_THRESHHOLD or more + * entries, we use an AVL tree to speed up searching for duplicates or + * values we are trying to delete. This threshhold is somewhat arbitrary; + * we should really take some measurements to determine an optimal number. + */ +#define SLAPD_MODUTIL_TREE_THRESHHOLD 5 + +/* + * Add a value array to an attribute. If SLAPD_MODUTIL_TREE_THRESHHOLD or + * more values are being added, we build an AVL tree of any existing + * values and then update that in parallel with the existing values. This + * is done so that we do not waste a lot of CPU time searching for duplicate + * values. The AVL tree is created and destroyed all within this function. + * + * Returns + * LDAP_SUCCESS - OK + * LDAP_OPERATIONS_ERROR - Existing duplicates in attribute. + * LDAP_TYPE_OR_VALUE_EXISTS - Duplicate values. + */ +int +attr_add_valuearray(Slapi_Attr *a, Slapi_Value **vals, const char *dn) +{ + int i = 0; + int duplicate_index = -1; + int was_present_null = 0; + int rc = LDAP_SUCCESS; + + if (valuearray_isempty(vals)) { + /* + * No values to add (unexpected but acceptable). + */ + return rc; + } + + /* + * determine whether we should use an AVL tree of values or not + */ + while ( i < SLAPD_MODUTIL_TREE_THRESHHOLD - 1 && vals[i] != NULL ) { + i++; + } + + /* + * detect duplicate values + */ + if ( i >= SLAPD_MODUTIL_TREE_THRESHHOLD - 1 ) { + /* + * Several values to add: use an AVL tree to detect duplicates. + */ + LDAPDebug( LDAP_DEBUG_TRACE, + "slapi_entry_add_values: using an AVL tree to " + "detect duplicate values\n", 0, 0, 0 ); + + if (valueset_isempty(&a->a_present_values)) { + /* if the attribute contains no values yet, just check the + * input vals array for duplicates + */ + Avlnode *vtree = NULL; + rc= valuetree_add_valuearray(a->a_type, a->a_plugin, vals, &vtree, &duplicate_index); + valuetree_free(&vtree); + was_present_null = 1; + } else { + /* the attr and vals both contain values, check intersection */ + rc= valueset_intersectswith_valuearray(&a->a_present_values, a, vals, &duplicate_index); + } + + } else if ( !valueset_isempty(&a->a_present_values) ) { + /* + * Small number of values to add: don't bother constructing + * an AVL tree, etc. since it probably isn't worth the time. + */ + for ( i = 0; vals[i] != NULL; ++i ) { + if ( slapi_attr_value_find( a, slapi_value_get_berval(vals[i]) ) == 0 ) { + duplicate_index = i; + rc = LDAP_TYPE_OR_VALUE_EXISTS; + break; + } + } + } + + /* + * add values if no duplicates detected + */ + if(rc==LDAP_SUCCESS) { + valueset_add_valuearray( &a->a_present_values, vals ); + } + + /* In the case of duplicate value, rc == LDAP_TYPE_OR_VALUE_EXISTS or + * LDAP_OPERATIONS_ERROR + */ + else if ( duplicate_index >= 0 ) { + char avdbuf[BUFSIZ]; + char bvvalcopy[BUFSIZ]; + char *duplicate_string = "null or non-ASCII"; + + i = 0; + while ( (unsigned int)i < vals[duplicate_index]->bv.bv_len && + i < BUFSIZ - 1 && + vals[duplicate_index]->bv.bv_val[i] && + isascii ( vals[duplicate_index]->bv.bv_val[i] )) { + i++; + } + + if ( i ) { + if ( vals[duplicate_index]->bv.bv_val[i] == 0 ) { + duplicate_string = vals[duplicate_index]->bv.bv_val; + } + else { + strncpy ( &bvvalcopy[0], vals[duplicate_index]->bv.bv_val, i ); + bvvalcopy[i] = '\0'; + duplicate_string = bvvalcopy; + } + } + + slapi_log_error( SLAPI_LOG_FATAL, NULL, "add value \"%s\" to " + "attribute type \"%s\" in entry \"%s\" failed: %s\n", + duplicate_string, + a->a_type, + dn ? escape_string(dn,avdbuf) : "", + (was_present_null ? "duplicate new value" : "value exists")); + } + return( rc ); +} + +/* quickly toss an attribute's values and replace them with new ones + * (used by attrlist_replace_fast) + */ +void attr_replace(Slapi_Attr *a, Slapi_Value **vals) +{ + valueset_replace(&a->a_present_values, vals); +} + +int +attr_check_onoff ( const char *attr_name, char *value, long minval, long maxval, char *errorbuf ) +{ + int retVal = LDAP_SUCCESS; + + if ( strcasecmp ( value, "on" ) != 0 && + strcasecmp ( value, "off") != 0 && + strcasecmp ( value, "1" ) != 0 && + strcasecmp ( value, "0" ) != 0 && + strcasecmp ( value, "true" ) != 0 && + strcasecmp ( value, "false" ) != 0 ) { + PR_snprintf ( errorbuf, BUFSIZ, + "%s: invalid value \"%s\".", attr_name, value ); + retVal = LDAP_CONSTRAINT_VIOLATION; + } + + return retVal; +} + +int +attr_check_minmax ( const char *attr_name, char *value, long minval, long maxval, char *errorbuf ) +{ + int retVal = LDAP_SUCCESS; + long val; + + val = strtol(value, NULL, 0); + if ( (minval != -1 ? (val < minval ? 1 : 0) : 0) || + (maxval != -1 ? (val > maxval ? 1 : 0) : 0) ) { + PR_snprintf ( errorbuf, BUFSIZ, + "%s: invalid value \"%s\".", + attr_name, value ); + retVal = LDAP_CONSTRAINT_VIOLATION; + } + + return retVal; +} -- cgit