/** BEGIN COPYRIGHT BLOCK
 * This Program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; version 2 of the License.
 * 
 * This Program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this Program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place, Suite 330, Boston, MA 02111-1307 USA.
 * 
 * In addition, as a special exception, Red Hat, Inc. gives You the additional
 * right to link the code of this Program with code not covered under the GNU
 * General Public License ("Non-GPL Code") and to distribute linked combinations
 * including the two, subject to the limitations in this paragraph. Non-GPL Code
 * permitted under this exception must only link to the code of this Program
 * through those well defined interfaces identified in the file named EXCEPTION
 * found in the source code files (the "Approved Interfaces"). The files of
 * Non-GPL Code may instantiate templates or use macros or inline functions from
 * the Approved Interfaces without causing the resulting work to be covered by
 * the GNU General Public License. Only Red Hat, Inc. may make changes or
 * additions to the list of Approved Interfaces. You must obey the GNU General
 * Public License in all respects for all of the Program code and other code used
 * in conjunction with the Program except the Non-GPL Code covered by this
 * exception. If you modify this file, you may extend this exception to your
 * version of the file, but you are not obligated to do so. If you do not wish to
 * provide this exception without modification, you must delete this exception
 * statement from your version and license this file solely under the GPL without
 * exception. 
 * 
 * 
 * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
 * Copyright (C) 2005 Red Hat, Inc.
 * All rights reserved.
 * END COPYRIGHT BLOCK **/

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

/* attrsyntax.c */

#include "slap.h"
#include <plhash.h>

/*
 * Note: if both the oid2asi and name2asi locks are acquired at the
 * same time, the old2asi one should be acquired first,
 */

/*
 * This hashtable maps the oid to the struct asyntaxinfo for that oid.
 */
static PLHashTable *oid2asi = NULL;
/* read/write lock to protect table */
static Slapi_RWLock *oid2asi_lock = NULL;
static PLHashTable *internalasi = NULL;

/*
 * This hashtable maps the name or alias of the attribute to the
 * syntax info structure for that attribute.  An attribute type has as
 * many entries in the name2asi table as it has names and aliases, but
 * all entries point to the same struct asyntaxinfo.
 */
static PLHashTable *name2asi = NULL;
/* read/write lock to protect table */
static Slapi_RWLock *name2asi_lock = NULL;

#define AS_LOCK_READ(l)		slapi_rwlock_rdlock(l)
#define AS_LOCK_WRITE(l)	slapi_rwlock_wrlock(l)
#define AS_UNLOCK_READ(l)	slapi_rwlock_unlock(l)
#define AS_UNLOCK_WRITE(l)	slapi_rwlock_unlock(l)



static void *attr_syntax_get_plugin_by_name_with_default( const char *type );
static void attr_syntax_delete_no_lock( struct asyntaxinfo *asip,
		PRBool remove_from_oid_table );
static struct asyntaxinfo *attr_syntax_get_by_oid_locking_optional( const
		char *oid, PRBool use_lock);

#ifdef ATTR_LDAP_DEBUG
static void attr_syntax_print();
#endif
static int attr_syntax_init(void);

void
attr_syntax_read_lock(void)
{
	if (0 != attr_syntax_init()) return;

	AS_LOCK_READ(oid2asi_lock);
	AS_LOCK_READ(name2asi_lock);
}

void
attr_syntax_unlock_read(void)
{
	if(name2asi_lock) AS_UNLOCK_READ(name2asi_lock);
	if(oid2asi_lock) AS_UNLOCK_READ(oid2asi_lock);
}



#if 0
static int 
check_oid( const char *oid ) {

  int i = 0, length_oid = 0, rc = 0;

  if ( oid == NULL) {
	/* this is bad */
	LDAPDebug (LDAP_DEBUG_ANY, "NULL passed to check_oid\n",0,0,0);
	return 0;
  }
  
  length_oid = strlen(oid);
  if (length_oid < 4) {
	/* this is probably bad */
	LDAPDebug (LDAP_DEBUG_ANY, "Bad oid %s passed to check_oid\n",oid,0,0);
	return 0;
  }

  rc = strcasecmp(oid+(length_oid-4), "-oid");

  if ( 0 == rc ) {
	return 1;
  }

  /* If not, the OID must begin and end with a digit, and contain only 
	 digits and dots */
  
  if ( !isdigit(oid[0]) || 
	   !isdigit(oid[length_oid-1]) ) {
	LDAPDebug (LDAP_DEBUG_ANY, "Non numeric oid %s passed to check_oid\n",oid,0,0);
	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] != '.'  ){
	  LDAPDebug (LDAP_DEBUG_ANY, "Non numeric oid %s passed to check_oid\n",oid,0,0);
	  return 0;
	}
  }

  /* The oid is OK if we're here */
  return 1;
  

}
#endif

#define NBUCKETS(ht)    (1 << (PL_HASH_BITS - (ht)->shift))

#if 0
static int
attr_syntax_check_oids()
{
	int ii = 0;
	int nbad = 0;
	AS_LOCK_READ(oid2asi_lock);
	ii = NBUCKETS(oid2asi);
    for (;ii;--ii) {
		PLHashEntry *he = oid2asi->buckets[ii-1];
		for (; he; he = he->next) {
			if (!check_oid(he->key)) {
				LDAPDebug(LDAP_DEBUG_ANY, "Error: bad oid %s in bucket %d\n",
						  he->key, ii-1, 0);
				nbad++;
			}
		}
    }

	AS_UNLOCK_READ(oid2asi_lock);
	return nbad;
}
#endif

void
attr_syntax_free( struct asyntaxinfo *a )
{
	PR_ASSERT( a->asi_refcnt == 0 );

	cool_charray_free( a->asi_aliases );
	slapi_ch_free( (void**)&a->asi_name );
	slapi_ch_free( (void **)&a->asi_desc );
	slapi_ch_free( (void **)&a->asi_oid );
	slapi_ch_free( (void **)&a->asi_superior );
	slapi_ch_free( (void **)&a->asi_mr_equality );
	slapi_ch_free( (void **)&a->asi_mr_ordering );
	slapi_ch_free( (void **)&a->asi_mr_substring );
	cool_charray_free( a->asi_origin );
	slapi_ch_free( (void **) &a );
}

static struct asyntaxinfo *
attr_syntax_new()
{
	return (struct asyntaxinfo *)slapi_ch_calloc(1, sizeof(struct asyntaxinfo));
}

/*
 * 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;
}

/*
 * hashNocaseCompare - used for case insensitive hash key comparisons
 */
static PRIntn
hashNocaseCompare(const void *v1, const void *v2)
{
	return (strcasecmp((char *)v1, (char *)v2) == 0);
}

/*
 * Given an OID, return the syntax info.  If there is more than one
 * attribute syntax with the same OID (i.e. aliases), the first one
 * will be returned.  This is usually the "canonical" one, but it may
 * not be.
 *
 * Note: once the caller is finished using it, the structure returned must
 * be returned by calling to attr_syntax_return().
 */
struct asyntaxinfo *
attr_syntax_get_by_oid(const char *oid)
{
	return attr_syntax_get_by_oid_locking_optional( oid, PR_TRUE);
}


/*
 * A version of attr_syntax_get_by_oid() that allows you to bypass using
 * a lock to access the global oid hash table.
 *
 * Note: once the caller is finished using it, the structure must be
 * returned by calling attr_syntax_return_locking_optional() with the
 * same use_lock parameter.
 */
static struct asyntaxinfo *
attr_syntax_get_by_oid_locking_optional( const char *oid, PRBool use_lock )
{
	struct asyntaxinfo *asi = 0;
	if (oid2asi)
	{
		if ( use_lock ) AS_LOCK_READ(oid2asi_lock);
		asi = (struct asyntaxinfo *)PL_HashTableLookup_const(oid2asi, oid);
		if (asi)
		{
			PR_AtomicIncrement( &asi->asi_refcnt );
		}
		if ( use_lock ) AS_UNLOCK_READ(oid2asi_lock);
	}

	return asi;
}

/*
 * Add the syntax info pointer to the look-up-by-oid table.
 * The lock parameter is used by the initialization code.  Normally, we want
 * to acquire a write lock before we modify the table, but during
 * initialization, we are running in single threaded mode, so we don't have
 * to worry about resource contention.
 */
static void
attr_syntax_add_by_oid(const char *oid, struct asyntaxinfo *a, int lock)
{
	if (0 != attr_syntax_init()) return;

	if (lock)
		AS_LOCK_WRITE(oid2asi_lock);

	PL_HashTableAdd(oid2asi, oid, a);

	if (lock)
		AS_UNLOCK_WRITE(oid2asi_lock);
}

/*
 * Return the syntax info given an attribute name.  The name may be the
 * "canonical" name, an alias, or an OID.  The given name need not be
 * normalized since the look up is done case insensitively.
 *
 * Note: once the caller is finished using it, the structure returned must
 * be returned by calling to attr_syntax_return().
 */
struct asyntaxinfo *
attr_syntax_get_by_name(const char *name)
{
	return attr_syntax_get_by_name_locking_optional(name, PR_TRUE);
}


/*
 * A version of attr_syntax_get_by_name() that allows you to bypass using
 * a lock around the global name hashtable.
 *
 * Note: once the caller is finished using it, the structure must be
 * returned by calling attr_syntax_return_locking_optional() with the
 * same use_lock parameter.
 */
struct asyntaxinfo *
attr_syntax_get_by_name_locking_optional(const char *name, PRBool use_lock)
{
	struct asyntaxinfo *asi = 0;
	if (name2asi)
	{
		if ( use_lock ) AS_LOCK_READ(name2asi_lock);
		asi = (struct asyntaxinfo *)PL_HashTableLookup_const(name2asi, name);
		if ( NULL != asi ) {
			PR_AtomicIncrement( &asi->asi_refcnt );
		}
		if ( use_lock ) AS_UNLOCK_READ(name2asi_lock);
	}
	if (!asi) /* given name may be an OID */
		asi = attr_syntax_get_by_oid_locking_optional(name, use_lock);

	return asi;
}


/*
 * Give up a reference to an asi.
 * If the asi has been marked for delete, free it.  This would be a bit
 * easier if we could upgrade a read lock to a write one... but NSPR does
 * not support that paradigm.
 */
void
attr_syntax_return( struct asyntaxinfo *asi )
{
	attr_syntax_return_locking_optional( asi, PR_TRUE );
}

void
attr_syntax_return_locking_optional( struct asyntaxinfo *asi, PRBool use_lock )
{
	if ( NULL != asi ) {
		if ( 0 == PR_AtomicDecrement( &asi->asi_refcnt ))
		{
			PRBool		delete_it;

			if(use_lock) AS_LOCK_READ(name2asi_lock);
			delete_it = asi->asi_marked_for_delete;
			if(use_lock) AS_UNLOCK_READ(name2asi_lock);

			if ( delete_it )
			{
				AS_LOCK_WRITE(name2asi_lock);		/* get a write lock */
				if ( asi->asi_marked_for_delete )	/* one final check */
				{
					/* ref count is 0 and it's flagged for
					 * deletion, so it's safe to free now */
					attr_syntax_free(asi);
				}
				AS_UNLOCK_WRITE(name2asi_lock);
			}
		}
	}
}

/*
 * Add the syntax info to the look-up-by-name table.  The asi_name and
 * elements of the asi_aliasses field of the syntax info are the keys.
 * These need not be normalized since the look up table is case insensitive.
 * The lock parameter is used by the initialization code.  Normally, we want
 * to acquire a write lock before we modify the table, but during
 * initialization, we are running in single threaded mode, so we don't have
 * to worry about resource contention.
 */
static void
attr_syntax_add_by_name(struct asyntaxinfo *a, int lock)
{
	if (0 != attr_syntax_init()) return;

	if (lock)
		AS_LOCK_WRITE(name2asi_lock);

	PL_HashTableAdd(name2asi, a->asi_name, a);
	if ( a->asi_aliases != NULL ) {
		int		i;

		for ( i = 0; a->asi_aliases[i] != NULL; ++i ) {
			PL_HashTableAdd(name2asi, a->asi_aliases[i], a);
		}
	}

	if (lock)
		AS_UNLOCK_WRITE(name2asi_lock);
}


/*
 * Delete the attribute syntax and all entries corresponding to aliases
 * and oids.
 */
void
attr_syntax_delete( struct asyntaxinfo *asi )
{
	PR_ASSERT( asi );

	if (oid2asi && name2asi) {
		AS_LOCK_WRITE(oid2asi_lock);
		AS_LOCK_WRITE(name2asi_lock);

		attr_syntax_delete_no_lock( asi, PR_TRUE );

		AS_UNLOCK_WRITE(name2asi_lock);
		AS_UNLOCK_WRITE(oid2asi_lock);
	}
}


/*
 * Dispose of a node.  The caller is responsible for locking.  See
 * attr_syntax_delete() for an example.
 */
static void
attr_syntax_delete_no_lock( struct asyntaxinfo *asi,
		PRBool remove_from_oidtable )
{
	int		i;

	if (oid2asi && remove_from_oidtable ) {
		PL_HashTableRemove(oid2asi, asi->asi_oid);
	}

	if(name2asi) {
		PL_HashTableRemove(name2asi, asi->asi_name);
		if ( asi->asi_aliases != NULL ) {
			for ( i = 0; asi->asi_aliases[i] != NULL; ++i ) {
				PL_HashTableRemove(name2asi, asi->asi_aliases[i]);
			}
		}
		if ( asi->asi_refcnt > 0 ) {
			asi->asi_marked_for_delete = PR_TRUE;
		} else {
			/* This is ok, but the correct thing is to call delete first, 
			 * then to call return.  The last return will then take care of
			 * the free.  The only way this free would happen here is if
			 * you return the syntax before calling delete. */
			attr_syntax_free(asi);
		}
	}
}


/*
 * Look up the attribute type in the syntaxes and return a copy of the
 * normalised attribute type. If it's not there then return a normalised
 * copy of what the caller gave us.
 *
 * Warning: The caller must free the returned string.
 */



char *
slapi_attr_syntax_normalize( const char *s )
{
	struct asyntaxinfo *asi = NULL;
	char *r = NULL;

	if((asi=attr_syntax_get_by_name(s)) != NULL ) {
		r = slapi_ch_strdup(asi->asi_name);
		attr_syntax_return( asi );
	}
	if ( NULL == asi ) {
		slapi_ch_free_string( &r );
		r = attr_syntax_normalize_no_lookup( s );
	}
	return r;
}


/*
 * attr_syntax_exists: return 1 if attr_name exists, 0 otherwise
 *
 */
int
attr_syntax_exists(const char *attr_name)
{
	struct asyntaxinfo	*asi;

	asi = attr_syntax_get_by_name(attr_name);
	attr_syntax_return( asi );

	if ( asi != NULL )
	{
		return 1;
	}
	return 0;
}

/* check syntax */

static void *
attr_syntax_get_plugin_by_name_with_default( const char *type )
{
	struct asyntaxinfo	*asi;
	void				*plugin = NULL;

	/*
	 * first we look for this attribute type explictly
	 */
	if ( (asi = attr_syntax_get_by_name(type)) == NULL ) {
		/*
		 * no syntax for this type... return Octet String
		 * syntax.  we accomplish this by looking up a well known
		 * attribute type that has that syntax.
		 */
		asi = attr_syntax_get_by_name(ATTR_WITH_OCTETSTRING_SYNTAX);
	}
	if ( NULL != asi ) {
		plugin = asi->asi_plugin;
		attr_syntax_return( asi );
	}
	return( plugin );
}


static struct asyntaxinfo *
attr_syntax_dup( struct asyntaxinfo *a )
{
	struct asyntaxinfo *newas = attr_syntax_new();

	newas->asi_aliases = cool_charray_dup( a->asi_aliases );
	newas->asi_name = slapi_ch_strdup( a->asi_name );
	newas->asi_desc = slapi_ch_strdup( a->asi_desc );
	newas->asi_superior = slapi_ch_strdup( a->asi_superior );
	newas->asi_mr_equality = slapi_ch_strdup( a->asi_mr_equality );
	newas->asi_mr_ordering = slapi_ch_strdup( a->asi_mr_ordering );
	newas->asi_mr_substring = slapi_ch_strdup( a->asi_mr_substring );
	newas->asi_origin = cool_charray_dup( a->asi_origin );
	newas->asi_plugin = a->asi_plugin;
	newas->asi_flags = a->asi_flags;
	newas->asi_oid = slapi_ch_strdup( a->asi_oid);
	newas->asi_syntaxlength = a->asi_syntaxlength;
	newas->asi_mr_eq_plugin = a->asi_mr_eq_plugin;
	newas->asi_mr_ord_plugin = a->asi_mr_ord_plugin;
	newas->asi_mr_sub_plugin = a->asi_mr_sub_plugin;

	return( newas );
}


/*
 * Add a new attribute type to the schema.
 *
 * Returns an LDAP error code (LDAP_SUCCESS if all goes well).
 */
int 
attr_syntax_add( struct asyntaxinfo *asip )
{
	int i, rc = LDAP_SUCCESS;
	int nolock = asip->asi_flags & SLAPI_ATTR_FLAG_NOLOCKING;
	struct asyntaxinfo *oldas_from_oid = NULL, *oldas_from_name = NULL;
	/* attr names may have subtypes in them, and we may not want this
	   if strip_subtypes is true, the ; and anything after it in the
	   attr name or alias will be stripped */
	/*int strip_subtypes = 1;*/

	/* make sure the oid is unique */
	if ( NULL != ( oldas_from_oid = attr_syntax_get_by_oid_locking_optional(
					asip->asi_oid, !nolock))) {
		if ( 0 == (asip->asi_flags & SLAPI_ATTR_FLAG_OVERRIDE)) {
			/* failure - OID is in use; no override flag */
			rc = LDAP_TYPE_OR_VALUE_EXISTS;
			goto cleanup_and_return;
		}
	}

	/* make sure the primary name is unique OR, if override is allowed, that
     * the primary name and OID point to the same schema definition.
	 */
	if ( NULL != ( oldas_from_name = attr_syntax_get_by_name_locking_optional(
					asip->asi_name, !nolock))) {
		if ( 0 == (asip->asi_flags & SLAPI_ATTR_FLAG_OVERRIDE)
					|| ( oldas_from_oid != oldas_from_name )) {
			/* failure; no override flag OR OID and name don't match */
			rc = LDAP_TYPE_OR_VALUE_EXISTS;
			goto cleanup_and_return;
		}
		/* Flag for deletion.  We are going to override this attr */
		attr_syntax_delete(oldas_from_name);
	} else if ( NULL != oldas_from_oid ) {
		/* failure - OID is in use but name does not exist */
		rc = LDAP_TYPE_OR_VALUE_EXISTS;
		goto cleanup_and_return;
	}

	if ( NULL != asip->asi_aliases ) {
		/* make sure the aliases are unique */
		for (i = 0; asip->asi_aliases[i] != NULL; ++i) {
			struct asyntaxinfo	*tmpasi;

			if ( NULL != ( tmpasi =
							attr_syntax_get_by_name_locking_optional(
							asip->asi_aliases[i], !nolock))) {
				if (asip->asi_flags & SLAPI_ATTR_FLAG_OVERRIDE) {
					/* Flag for tmpasi for deletion.  It will be free'd
					 * when attr_syntax_return is called. */
					attr_syntax_delete(tmpasi);
				} else {
					/* failure - one of the aliases is already in use */
					rc = LDAP_TYPE_OR_VALUE_EXISTS;
				}

				attr_syntax_return_locking_optional( tmpasi, !nolock );
				if ( LDAP_SUCCESS != rc ) {
					goto cleanup_and_return;
				}
			}
		}
	}

	/* the no lock flag is not worth keeping around */
	asip->asi_flags &= ~SLAPI_ATTR_FLAG_NOLOCKING;
	/* ditto for the override one */
	asip->asi_flags &= ~SLAPI_ATTR_FLAG_OVERRIDE;
	
	attr_syntax_add_by_oid( asip->asi_oid, asip, !nolock);
	attr_syntax_add_by_name( asip, !nolock);

cleanup_and_return:
	attr_syntax_return_locking_optional( oldas_from_oid, !nolock );
	attr_syntax_return_locking_optional( oldas_from_name, !nolock );
	return rc;
}


/*
 * Returns an LDAP result code.
 */
int
attr_syntax_create(
	const char			*attr_oid,
	char *const			*attr_names,
	int					num_names,
	const char			*attr_desc,
	const char			*attr_superior,
	const char			*mr_equality,
	const char			*mr_ordering,
	const char			*mr_substring,
	char *const			*attr_origins,
	const char			*attr_syntax,
	int					syntaxlength,
	unsigned long		flags,
	struct asyntaxinfo	**asip
)
{
	char					*s;
	struct asyntaxinfo		a;
	int rc = LDAP_SUCCESS;

	/* XXXmcs: had to cast away const in many places below */
	memset(&a, 0, sizeof(a));
	*asip = NULL;
	a.asi_name = slapi_ch_strdup(attr_names[0]);
	if ( NULL != attr_names[1] ) {
		a.asi_aliases = (char **)&attr_names[1]; /* all but the zero'th element */
	}
	a.asi_desc = (char*)attr_desc;
	a.asi_oid = (char*)attr_oid;
	a.asi_superior = (char*)attr_superior;
	a.asi_mr_equality = (char*)mr_equality;
	a.asi_mr_ordering = (char*)mr_ordering;
	a.asi_mr_substring = (char*)mr_substring;
	a.asi_origin = (char **)attr_origins;
	a.asi_plugin = plugin_syntax_find( attr_syntax );
	a.asi_syntaxlength = syntaxlength;
	/* ideally, we would report an error and fail to start if there was some problem
	   with the matching rule - but since this functionality is new, and we might
	   cause havoc if lots of servers failed to start because of bogus schema, we
	   just report an error here - at some point in the future, we should actually
	   report an error and exit, or allow the user to control the behavior - for
	   now, just log an error, and address each case
	*/
	if (mr_equality && !slapi_matchingrule_is_compat(mr_equality, attr_syntax)) {
		slapi_log_error(SLAPI_LOG_FATAL, "attr_syntax_create",
						"Error: the EQUALITY matching rule [%s] is not compatible "
						"with the syntax [%s] for the attribute [%s]\n",
						mr_equality, attr_syntax, attr_names[0]);
#ifdef ENFORCE_MR_SYNTAX_COMPAT
		rc = LDAP_INAPPROPRIATE_MATCHING;
		goto done;
#endif /* ENFORCE_MR_SYNTAX_COMPAT */
	}
	a.asi_mr_eq_plugin = plugin_mr_find( mr_equality );
	if (mr_ordering && !slapi_matchingrule_is_compat(mr_ordering, attr_syntax)) {
		slapi_log_error(SLAPI_LOG_FATAL, "attr_syntax_create",
						"Error: the ORDERING matching rule [%s] is not compatible "
						"with the syntax [%s] for the attribute [%s]\n",
						mr_ordering, attr_syntax, attr_names[0]);
#ifdef ENFORCE_MR_SYNTAX_COMPAT
		rc = LDAP_INAPPROPRIATE_MATCHING;
		goto done;
#endif /* ENFORCE_MR_SYNTAX_COMPAT */
	}
	a.asi_mr_ord_plugin = plugin_mr_find( mr_ordering );
	if (mr_substring && !slapi_matchingrule_is_compat(mr_substring, attr_syntax)) {
		slapi_log_error(SLAPI_LOG_FATAL, "attr_syntax_create",
						"Error: the SUBSTR matching rule [%s] is not compatible "
						"with the syntax [%s] for the attribute [%s]\n",
						mr_substring, attr_syntax, attr_names[0]);
#ifdef ENFORCE_MR_SYNTAX_COMPAT
		rc = LDAP_INAPPROPRIATE_MATCHING;
		goto done;
#endif /* ENFORCE_MR_SYNTAX_COMPAT */
	}
	a.asi_mr_sub_plugin = plugin_mr_find( mr_substring );
	a.asi_flags = flags;

	/*
	 * If the 'return exact case' option is on (the default), we store the
	 * first name (the canonical one) unchanged so that attribute names are
	 * returned exactly as they appear in the schema configuration files.
	 * But if 'return exact case' has been turned off, we convert the name
	 * to lowercase.  In Netscape Directory Server 4.x and earlier versions,
	 * the default was to convert to lowercase.
	 */
	if (!config_get_return_exact_case()) {
		for (s = a.asi_name; *s; ++s) {
			*s = TOLOWER(*s);
		}
	}

	*asip = attr_syntax_dup(&a);

#ifdef ENFORCE_MR_SYNTAX_COMPAT
done:
#endif /* ENFORCE_MR_SYNTAX_COMPAT */
	slapi_ch_free((void **)&a.asi_name);

	return rc;
}


/*
 * slapi_attr_type2plugin - return the plugin handling the attribute type
 * if type is unknown, we return the caseIgnoreString plugin used by the
 *     objectClass attribute type.
 */

int
slapi_attr_type2plugin( const char *type, void **pi )
{
	char			buf[SLAPD_TYPICAL_ATTRIBUTE_NAME_MAX_LENGTH];
	char			*tmp, *basetype;
	int				rc;

	basetype = buf;
	if ( (tmp = slapi_attr_basetype( type, buf, sizeof(buf) )) != NULL ) {
		basetype = tmp;
	}
	rc = -1;
	*pi = attr_syntax_get_plugin_by_name_with_default( basetype );
	if ( NULL != *pi ) {
		rc = 0;
	}
	slapi_ch_free_string(&tmp);

	return( rc );
}

/* deprecated -- not MT safe (pointer into asi is returned!) */
int
slapi_attr_get_oid( const Slapi_Attr *a, char **oid )
{
	struct asyntaxinfo *asi = attr_syntax_get_by_name(a->a_type);
	if (asi) {
		*oid = asi->asi_oid;
		attr_syntax_return(asi);
		return( 0 );
	} else {
		*oid = NULL;
		return( -1 );
	}
}


/* The caller must dispose of oid by calling slapi_ch_free(). */
int
slapi_attr_get_oid_copy( const Slapi_Attr *a, char **oidp )
{
	struct asyntaxinfo *asi = attr_syntax_get_by_name(a->a_type);
	if (asi) {
		*oidp = slapi_ch_strdup( asi->asi_oid );
		attr_syntax_return(asi);
		return( 0 );
	} else {
		*oidp = NULL;
		return( -1 );
	}
}

/* Returns the oid of the syntax of the Slapi_Attr that's passed in.
 * The caller must dispose of oid by calling slapi_ch_free_string(). */
int
slapi_attr_get_syntax_oid_copy( const Slapi_Attr *a, char **oidp )
{
	const char *oid;
	if (a && ((oid = attr_get_syntax_oid(a)))) {
		*oidp = slapi_ch_strdup(oid);
		return( 0 );
	} else {
		*oidp = NULL;
		return( -1 );
	}
}

int
slapi_attr_is_dn_syntax_attr(Slapi_Attr *attr)
{
	const char *syntaxoid = NULL;
	int dn_syntax = 0; /* not DN, by default */

	if (attr && attr->a_plugin) { /* If not set, there is no way to get the info */
		if ((syntaxoid = attr_get_syntax_oid(attr))) {
			dn_syntax = ((0 == strcmp(syntaxoid, NAMEANDOPTIONALUID_SYNTAX_OID))
						 || (0 == strcmp(syntaxoid, DN_SYNTAX_OID)));
		}
	}
	return dn_syntax;
}

#ifdef ATTR_LDAP_DEBUG

PRIntn
attr_syntax_printnode(PLHashEntry *he, PRIntn i, void *arg)
{
	char *alias = (char *)he->key;
	struct asyntaxinfo *a = (struct asyntaxinfo *)he->value;
	printf( "  name: %s\n", a->asi_name );
	printf( "\t flags       : 0x%x\n", a->asi_flags );
	printf( "\t alias       : %s\n", alias );
	printf( "\t desc        : %s\n", a->asi_desc );
	printf( "\t oid         : %s\n", a->asi_oid );
	printf( "\t superior    : %s\n", a->asi_superior );
	printf( "\t mr_equality : %s\n", a->asi_mr_equality );
	printf( "\t mr_ordering : %s\n", a->asi_mr_ordering );
	printf( "\t mr_substring: %s\n", a->asi_mr_substring );
	if ( NULL != a->asi_origin ) {
		for ( i = 0; NULL != a->asi_origin[i]; ++i ) {
			printf( "\t origin      : %s\n", a->asi_origin[i] );
		}
	}
	printf( "\tplugin: %p\n", a->asi_plugin );
	printf( "--------------\n" );

	return HT_ENUMERATE_NEXT;
}

void
attr_syntax_print()
{
	printf( "*** attr_syntax_print ***\n" );
	PL_HashTableEnumerateEntries(name2asi, attr_syntax_printnode, 0);
}

#endif


/* lowercase the attr name and chop trailing spaces */
/* note that s may contain options also, e.g., userCertificate;binary */
char *
attr_syntax_normalize_no_lookup( const char *s )
{
	char	*save, *tmps;

    tmps = slapi_ch_strdup(s);
    for ( save = tmps; (*tmps != '\0') && (*tmps != ' '); tmps++ )
	{
	  *tmps = TOLOWER( *tmps );
	}
	*tmps = '\0';

	return save;
}

struct enum_arg_wrapper {
	AttrEnumFunc aef;
	void *arg;
};

PRIntn
attr_syntax_enumerate_internal(PLHashEntry *he, PRIntn i, void *arg)
{
	struct enum_arg_wrapper *eaw = (struct enum_arg_wrapper *)arg;
	int	rc;

	rc = (eaw->aef)((struct asyntaxinfo *)he->value, eaw->arg);
	if ( ATTR_SYNTAX_ENUM_STOP == rc ) {
		rc = HT_ENUMERATE_STOP;
	} else if ( ATTR_SYNTAX_ENUM_REMOVE == rc ) {
		rc = HT_ENUMERATE_REMOVE;
	} else {
		rc = HT_ENUMERATE_NEXT;
	}

	return rc;
}

static void
attr_syntax_enumerate_attrs_ext( PLHashTable *ht,
                                 AttrEnumFunc aef, void *arg )
{
	struct enum_arg_wrapper eaw;
	eaw.aef = aef;
	eaw.arg = arg;

	if (!ht)
		return;

	PL_HashTableEnumerateEntries(ht, attr_syntax_enumerate_internal, &eaw);
}

void
attr_syntax_enumerate_attrs(AttrEnumFunc aef, void *arg, PRBool writelock )
{
	if (!oid2asi)
		return;

	if ( writelock ) {
		AS_LOCK_WRITE(oid2asi_lock);
		AS_LOCK_WRITE(name2asi_lock);
	} else {
		AS_LOCK_READ(oid2asi_lock);
		AS_LOCK_READ(name2asi_lock);
	}

	attr_syntax_enumerate_attrs_ext(oid2asi, aef, arg);

	if ( writelock ) {
		AS_UNLOCK_WRITE(oid2asi_lock);
		AS_UNLOCK_WRITE(name2asi_lock);
	} else {
		AS_UNLOCK_READ(oid2asi_lock);
		AS_UNLOCK_READ(name2asi_lock);
	}
}


struct attr_syntax_enum_flaginfo {
	unsigned long	asef_flag;
};

static int
attr_syntax_clear_flag_callback(struct asyntaxinfo *asip, void *arg)
{
	struct attr_syntax_enum_flaginfo	*fi;

	PR_ASSERT( asip != NULL );
	fi = (struct attr_syntax_enum_flaginfo *)arg;
	PR_ASSERT( fi != NULL );

	asip->asi_flags &= ~(fi->asef_flag);

	return ATTR_SYNTAX_ENUM_NEXT;
}


static int
attr_syntax_delete_if_not_flagged(struct asyntaxinfo *asip, void *arg)
{
	struct attr_syntax_enum_flaginfo	*fi;

	PR_ASSERT( asip != NULL );
	fi = (struct attr_syntax_enum_flaginfo *)arg;
	PR_ASSERT( fi != NULL );

	if ( 0 == ( asip->asi_flags & fi->asef_flag )) {
		attr_syntax_delete_no_lock( asip, PR_FALSE );
		return ATTR_SYNTAX_ENUM_REMOVE;
	} else {
		return ATTR_SYNTAX_ENUM_NEXT;
	}
}

static int
attr_syntax_force_to_delete(struct asyntaxinfo *asip, void *arg)
{
	struct attr_syntax_enum_flaginfo	*fi;

	PR_ASSERT( asip != NULL );
	fi = (struct attr_syntax_enum_flaginfo *)arg;
	PR_ASSERT( fi != NULL );

	attr_syntax_delete_no_lock( asip, PR_FALSE );
	return ATTR_SYNTAX_ENUM_REMOVE;
}


/*
 * Clear 'flag' within all attribute definitions.
 */
void
attr_syntax_all_clear_flag( unsigned long flag )
{
	struct attr_syntax_enum_flaginfo fi;

	memset( &fi, 0, sizeof(fi));
	fi.asef_flag = flag;
	attr_syntax_enumerate_attrs( attr_syntax_clear_flag_callback,
				(void *)&fi, PR_TRUE );
}


/*
 * Delete all attribute definitions that do not contain any bits of 'flag'
 * in their flags.
 */
void
attr_syntax_delete_all_not_flagged( unsigned long flag )
{
	struct attr_syntax_enum_flaginfo fi;

	memset( &fi, 0, sizeof(fi));
	fi.asef_flag = flag;
	attr_syntax_enumerate_attrs( attr_syntax_delete_if_not_flagged,
				(void *)&fi, PR_TRUE );
}

/*
 * Delete all attribute definitions 
 */
void
attr_syntax_delete_all()
{
	struct attr_syntax_enum_flaginfo fi;

	memset( &fi, 0, sizeof(fi));
	attr_syntax_enumerate_attrs( attr_syntax_force_to_delete,
				(void *)&fi, PR_TRUE );
}

static int
attr_syntax_init(void)
{
	if (!oid2asi)
	{
		oid2asi = PL_NewHashTable(2047, hashNocaseString,
								  hashNocaseCompare,
								  PL_CompareValues, 0, 0);
		if ( NULL == ( oid2asi_lock = slapi_new_rwlock())) {
			if(oid2asi) PL_HashTableDestroy(oid2asi);
			oid2asi = NULL;

			slapi_log_error( SLAPI_LOG_FATAL, "attr_syntax_init",
					"slapi_new_rwlock() for oid2asi lock failed\n" );
			return 1;
		}
	}

	if (!name2asi)
	{
		name2asi = PL_NewHashTable(2047, hashNocaseString,
								   hashNocaseCompare,
								   PL_CompareValues, 0, 0);
		if ( NULL == ( name2asi_lock = slapi_new_rwlock())) {
			if(name2asi) PL_HashTableDestroy(name2asi);
			name2asi = NULL;

			slapi_log_error( SLAPI_LOG_FATAL, "attr_syntax_init",
					"slapi_new_rwlock() for oid2asi lock failed\n" );
			return 1;
		}
	}
	return 0;
}

int
slapi_attr_syntax_exists(const char *attr_name)
{
    return attr_syntax_exists(attr_name);
}

/*
 * Keep the internally added schema in the hash table,
 * which are re-added if the schema is reloaded.
 */
static int
attr_syntax_internal_asi_add_ht(struct asyntaxinfo *asip)
{
	if (!internalasi) {
		internalasi = PL_NewHashTable(64, hashNocaseString,
		                              hashNocaseCompare,
		                              PL_CompareValues, 0, 0);
	}
	if (!internalasi) {
		slapi_log_error(SLAPI_LOG_FATAL, "attr_syntax_internal_asi_add_ht",
		                "Failed to create HashTable.\n");
		return 1;
	}
	if (!PL_HashTableLookup(internalasi, asip->asi_oid)) {
		struct asyntaxinfo *asip_copy = attr_syntax_dup(asip);
		if (!asip_copy) {
			slapi_log_error(SLAPI_LOG_FATAL, "attr_syntax_internal_asi_add_ht",
		                    "Failed to duplicate asyntaxinfo: %s.\n",
		                    asip->asi_name);
			return 1;
		}
		PL_HashTableAdd(internalasi, asip_copy->asi_oid, asip_copy);
	}
	return 0;
}

/*
 * Add an attribute syntax using some default flags, etc.
 * Returns an LDAP error code (LDAP_SUCCESS if all goes well)
 */
int
slapi_add_internal_attr_syntax( const char *name, const char *oid,
		const char *syntax, const char *mr_equality, unsigned long extraflags )
{
	int rc = LDAP_SUCCESS;
	struct asyntaxinfo	*asip;
	char *names[2];
	char *origins[2];
	unsigned long std_flags = SLAPI_ATTR_FLAG_STD_ATTR | SLAPI_ATTR_FLAG_OPATTR;

	names[0] = (char *)name;
	names[1] = NULL;

	origins[0] = SLAPD_VERSION_STR;
	origins[1] = NULL;

	rc = attr_syntax_create( oid, names, 1,
			"internal server defined attribute type",
			 NULL,						/* superior */
			 mr_equality, NULL, NULL,	/* matching rules */
			 origins, syntax,
			 SLAPI_SYNTAXLENGTH_NONE,
			 std_flags | extraflags,
			 &asip );

	if ( rc == LDAP_SUCCESS ) {
		rc = attr_syntax_add( asip );
		if ( rc == LDAP_SUCCESS ) {
			if (attr_syntax_internal_asi_add_ht(asip)) {
				slapi_log_error(SLAPI_LOG_FATAL,
				                "slapi_add_internal_attr_syntax",
				                "Failed to stash internal asyntaxinfo: %s.\n",
				                asip->asi_name);
			}
		}
	}

	return rc;
}

/* Adding internal asyncinfo via slapi_reload_internal_attr_syntax */
static int
attr_syntax_internal_asi_add(struct asyntaxinfo *asip, void *arg)
{
	struct asyntaxinfo *asip_copy;
	if (!asip) {
		return 1;
	}
	/* Copy is needed since when reloading the schema,
	 * existing syntax info is cleaned up. */
	asip_copy = attr_syntax_dup(asip);
	return attr_syntax_add(asip_copy);
}

/* Reload internal attribute syntax stashed in the internalasi hashtable. */
int
slapi_reload_internal_attr_syntax()
{
	int rc = LDAP_SUCCESS;
	if (!internalasi) {
		slapi_log_error(SLAPI_LOG_TRACE, "attr_reload_internal_attr_syntax",
		                "No internal attribute syntax to reload.\n");
		return rc;
	}
	attr_syntax_enumerate_attrs_ext(internalasi, attr_syntax_internal_asi_add, NULL);
	return rc;
}
