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

/*
 * uid.c
 *
 * Implements a directory server pre-operation plugin to test
 * attributes for uniqueness within a defined subtree in the
 * directory.
 *
 * Called uid.c since the original purpose of the plugin was to
 * check the uid attribute in user entries.
 */
#include <slapi-plugin.h>
#include <portable.h>
#include <string.h>
#include "plugin-utils.h"
#include "nspr.h"

#if defined( LDAP_DEBUG ) && !defined( DEBUG )
#define DEBUG
#endif

#define UNTAGGED_PARAMETER 12

/* Quoting routine - this should be in a library somewhere (slapi?) */
int ldap_quote_filter_value(
      char *value, int len,
      char *out, int maxLen,
      int *outLen);


static int search_one_berval(Slapi_DN *baseDN, const char *attrName,
		const struct berval *value, const char *requiredObjectClass, Slapi_DN *target);

/*
 * ISSUES:
 *   How should this plugin handle ACL issues?  It seems wrong to reject
 *   adds and modifies because there is already a conflicting UID, when
 *   the request would have failed because of an ACL check anyway.
 *
 *   This code currently defines a maximum filter string size of 512.  Is
 *   this large enough?
 *
 *   This code currently does not quote the value portion of the filter as
 *   it is created.  This is a bug.
 */

/* */
#define BEGIN do {
#define END } while(0);

/*
 * Slapi plugin descriptor
 */
static char *plugin_name = "NSUniqueAttr";
static Slapi_PluginDesc
pluginDesc = { 
	"NSUniqueAttr", VENDOR, DS_PACKAGE_VERSION,
	"Enforce unique attribute values" 
};
static void* plugin_identity = NULL;


/*
 * More information about constraint failure
 */
static char *moreInfo =
  "Another entry with the same attribute value already exists (attribute: \"%s\")";

static void
freePblock( Slapi_PBlock *spb ) {
  if ( spb )
  {
        slapi_free_search_results_internal( spb );
        slapi_pblock_destroy( spb );
  }
}

/* ------------------------------------------------------------ */
/*
 * op_error - Record (and report) an operational error.
 * name changed to uid_op_error so as not to conflict with the external function
 * of the same name thereby preventing compiler warnings.
 */
static int
uid_op_error(int internal_error)
{
  slapi_log_error(
	SLAPI_LOG_PLUGIN, 
	plugin_name, 
	"Internal error: %d\n", 
	internal_error);

  return LDAP_OPERATIONS_ERROR;
}

/* ------------------------------------------------------------ */
/*
 * Create an LDAP search filter from the attribute
 *   name and value supplied.
 */

static char *
create_filter(const char *attribute, const struct berval *value, const char *requiredObjectClass)
{
  char *filter;
  char *fp;
  char *max;
  int attrLen;
  int valueLen;
  int classLen = 0;
  int filterLen;

  PR_ASSERT(attribute);

  /* Compute the length of the required buffer */
  attrLen = strlen(attribute);

  if (ldap_quote_filter_value(value->bv_val, 
	value->bv_len, 0, 0, &valueLen))
    return 0;

  if (requiredObjectClass) {
    classLen = strlen(requiredObjectClass);
    /* "(&(objectClass=)())" == 19 */
    filterLen = attrLen + 1 + valueLen + classLen + 19 + 1;
  } else {
    filterLen = attrLen + 1 + valueLen + 1;
  }

  /* Allocate the buffer */
  filter = slapi_ch_malloc(filterLen);
  fp = filter;
  max = &filter[filterLen];

  /* Place AND expression and objectClass in filter */
  if (requiredObjectClass) {
    strcpy(fp, "(&(objectClass=");
    fp += 15;
    strcpy(fp, requiredObjectClass);
    fp += classLen;
    *fp++ = ')';
    *fp++ = '(';
  }

  /* Place attribute name in filter */
  strcpy(fp, attribute);
  fp += attrLen;

  /* Place comparison operator */
  *fp++ = '=';

  /* Place value in filter */
  if (ldap_quote_filter_value(value->bv_val, value->bv_len,
    fp, max-fp, &valueLen)) { slapi_ch_free((void**)&filter); return 0; }
  fp += valueLen;

  /* Close AND expression if a requiredObjectClass was set */
  if (requiredObjectClass) {
    *fp++ = ')';
    *fp++ = ')';
  }

  /* Terminate */
  *fp = 0;

  return filter;
}

/* ------------------------------------------------------------ */
/*
 * search - search a subtree for entries with a named attribute matching
 *   the list of values.  An entry matching the 'target' DN is
 *   not considered in the search.
 *
 * If 'attr' is NULL, the values are taken from 'values'.
 * If 'attr' is non-NULL, the values are taken from 'attr'.
 *
 * Return:
 *   LDAP_SUCCESS - no matches, or the attribute matches the
 *     target dn.
 *   LDAP_CONSTRAINT_VIOLATION - an entry was found that already
 *     contains the attribute value.
 *   LDAP_OPERATIONS_ERROR - a server failure.
 */
static int
search(Slapi_DN *baseDN, const char *attrName, Slapi_Attr *attr,
  struct berval **values, const char *requiredObjectClass,
  Slapi_DN *target)
{
  int result;

#ifdef DEBUG
    slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
                    "SEARCH baseDN=%s attr=%s target=%s\n", slapi_sdn_get_dn(baseDN), attrName, 
                    target?slapi_sdn_get_dn(target):"None");
#endif

  result = LDAP_SUCCESS;

  /* If no values, can't possibly be a conflict */
  if ( (Slapi_Attr *)NULL == attr && (struct berval **)NULL == values )
    return result;

  /*
   * Perform the search for each value provided
   *
   * Another possibility would be to search for all the values at once.
   * However, this is more complex (for filter creation) and unique
   * attributes values are probably only changed one at a time anyway.
   */
  if ( (Slapi_Attr *)NULL != attr )
  {
	Slapi_Value	*v = NULL;
	int	vhint = -1;

	for ( vhint = slapi_attr_first_value( attr, &v );
		vhint != -1 && LDAP_SUCCESS == result;
		vhint = slapi_attr_next_value( attr, vhint, &v ))
	{
	  result = search_one_berval(baseDN, attrName,
					slapi_value_get_berval(v),
					requiredObjectClass, target);
	}
  }
  else
  {
	for (;*values != NULL && LDAP_SUCCESS == result; values++)
	{
	  result = search_one_berval(baseDN, attrName, *values, requiredObjectClass,
					target);
	}
  }

#ifdef DEBUG
  slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
    "SEARCH result = %d\n", result);
#endif

  return( result );
}


static int
search_one_berval(Slapi_DN *baseDN, const char *attrName,
		const struct berval *value, const char *requiredObjectClass,
		Slapi_DN *target)
{
	int result;
    char *filter;
    Slapi_PBlock *spb;

	result = LDAP_SUCCESS;

	/* If no value, can't possibly be a conflict */
	if ( (struct berval *)NULL == value )
		return result;

    filter = 0;
    spb = 0;

    BEGIN
      int err;
      int sres;
      Slapi_Entry **entries;
      static char *attrs[] = { "1.1", 0 };

      /* Create the filter - this needs to be freed */
      filter = create_filter(attrName, value, requiredObjectClass);

#ifdef DEBUG
      slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
        "SEARCH filter=%s\n", filter);
#endif

      /* Perform the search using the new internal API */
      spb = slapi_pblock_new();
      if (!spb) { result = uid_op_error(2); break; }

      slapi_search_internal_set_pb_ext(spb, baseDN, LDAP_SCOPE_SUBTREE,
      	filter, attrs, 0 /* attrs only */, NULL, NULL, plugin_identity, 0 /* actions */);
      slapi_search_internal_pb(spb);

      err = slapi_pblock_get(spb, SLAPI_PLUGIN_INTOP_RESULT, &sres);
      if (err) { result = uid_op_error(3); break; }
    
      /* Allow search to report that there is nothing in the subtree */
      if (sres == LDAP_NO_SUCH_OBJECT) break;

      /* Other errors are bad */
      if (sres) { result = uid_op_error(3); break; }

      err = slapi_pblock_get(spb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES,
              &entries);
      if (err) { result = uid_op_error(4); break; }

      /*
       * Look at entries returned.  Any entry found must be the
       * target entry or the constraint fails.
       */
      for(;*entries;entries++)
      {
#ifdef DEBUG
        slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
                        "SEARCH entry dn=%s\n", slapi_entry_get_dn(*entries));
#endif

        /*
         * It is a Constraint Violation if any entry is found, unless
         * the entry is the target entry (if any).
         */
        if (!target || slapi_sdn_compare(slapi_entry_get_sdn(*entries), target) != 0)
        {
          result = LDAP_CONSTRAINT_VIOLATION;
          break;
        }
      }

#ifdef DEBUG
      slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
        "SEARCH complete result=%d\n", result);
#endif
    END

    /* Clean-up */
    if (spb) {
	slapi_free_search_results_internal(spb);
	slapi_pblock_destroy(spb);
    }

    slapi_ch_free((void**)&filter);

  return result;
}

/* ------------------------------------------------------------ */
/*
 * searchAllSubtrees - search all subtrees in argv for entries
 *   with a named attribute matching the list of values, by
 *   calling search for each one.
 *
 * If 'attr' is NULL, the values are taken from 'values'.
 * If 'attr' is non-NULL, the values are taken from 'attr'.
 *
 * Return:
 *   LDAP_SUCCESS - no matches, or the attribute matches the
 *     target dn.
 *   LDAP_CONSTRAINT_VIOLATION - an entry was found that already
 *     contains the attribute value.
 *   LDAP_OPERATIONS_ERROR - a server failure.
 */
static int
searchAllSubtrees(int argc, char *argv[], const char *attrName,
  Slapi_Attr *attr, struct berval **values, const char *requiredObjectClass,
  Slapi_DN *dn)
{
  int result = LDAP_SUCCESS;

  /*
   * For each DN in the managed list, do uniqueness checking if
   * the target DN is a subnode in the tree.
   */
  for(;argc > 0;argc--,argv++)
  {
    Slapi_DN *sufdn = slapi_sdn_new_dn_byref(*argv);
    /*
     * The DN should already be normalized, so we don't have to
     * worry about that here.
     */
    if (slapi_sdn_issuffix(dn, sufdn)) {
      result = search(sufdn, attrName, attr, values, requiredObjectClass, dn);
      slapi_sdn_free(&sufdn);
      if (result) break;
    } else {
      slapi_sdn_free(&sufdn);
    }
  }
  return result;
}

/* ------------------------------------------------------------ */
/*
 * getArguments - parse invocation parameters
 * Return:
 *   0 - success
 *   >0 - error parsing parameters
 */
static int
getArguments(Slapi_PBlock *pb, char **attrName, char **markerObjectClass,
                         char **requiredObjectClass)
{
  int argc;
  char **argv;

  /*
   * Get the arguments
   */
  if (slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc))
  {
    return uid_op_error(10);
  }

  if (slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv))
  {
        return uid_op_error(11);
  }

  /*
   * Required arguments: attribute and markerObjectClass
   * Optional argument: requiredObjectClass
   */
  for(;argc > 0;argc--,argv++)
  {
        char *param = *argv;
        char *delimiter = strchr(param, '=');
        if (NULL == delimiter)
        {
          /* Old style untagged parameter */
          *attrName = *argv;
          return UNTAGGED_PARAMETER;
        }
        if (strncasecmp(param, "attribute", delimiter-param) == 0)
        {
          /* It's OK to set a pointer here, because ultimately it points
           * inside the argv array of the pblock, which will be staying
           * arround.
           */
          *attrName = delimiter+1;
        } else if (strncasecmp(param, "markerobjectclass", delimiter-param) == 0)
        {
          *markerObjectClass = delimiter+1;
        } else if (strncasecmp(param, "requiredobjectclass", delimiter-param) == 0)
        {
          *requiredObjectClass = delimiter+1;
        }
  }
  if (!*attrName || !*markerObjectClass)
  {
        return uid_op_error(13);
  }

  return 0;
}

/* ------------------------------------------------------------ */
/*
 * findSubtreeAndSearch - walk up the tree to find an entry with
 * the marker object class; if found, call search from there and
 * return the result it returns
 *
 * If 'attr' is NULL, the values are taken from 'values'.
 * If 'attr' is non-NULL, the values are taken from 'attr'.
 *
 * Return:
 *   LDAP_SUCCESS - no matches, or the attribute matches the
 *     target dn.
 *   LDAP_CONSTRAINT_VIOLATION - an entry was found that already
 *     contains the attribute value.
 *   LDAP_OPERATIONS_ERROR - a server failure.
 */
static int
findSubtreeAndSearch(Slapi_DN *parentDN, const char *attrName, Slapi_Attr *attr,
  struct berval **values, const char *requiredObjectClass, Slapi_DN *target,
  const char *markerObjectClass)
{
  int result = LDAP_SUCCESS;
  Slapi_PBlock *spb = NULL;
  Slapi_DN *curpar = slapi_sdn_new();
  Slapi_DN *newpar = NULL;

  slapi_sdn_get_parent(parentDN, curpar);
  while ((curpar != NULL) && (slapi_sdn_get_dn(curpar) != NULL))
  {
        if ((spb = dnHasObjectClass(curpar, markerObjectClass)))
        {
          freePblock(spb);
          /*
           * Do the search.   There is no entry that is allowed
           * to have the attribute already.
           */
          result = search(curpar, attrName, attr, values, requiredObjectClass,
                          target);
          break;
        }
        newpar = slapi_sdn_new();
        slapi_sdn_copy(curpar, newpar);
        slapi_sdn_get_parent(newpar, curpar);
        slapi_sdn_free(&newpar);
  }
  slapi_sdn_free(&curpar);
  return result;
}


/* ------------------------------------------------------------ */
/*
 * preop_add - pre-operation plug-in for add
 */
static int
preop_add(Slapi_PBlock *pb)
{
  int result;
  char *errtext = NULL;
  char *attrName = NULL;

#ifdef DEBUG
  slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "ADD begin\n");
#endif

  result = LDAP_SUCCESS;

  /*
   * Do constraint check on the added entry.  Set result.
   */

 BEGIN
    int err;
    char *markerObjectClass = NULL;
    char *requiredObjectClass = NULL;
    Slapi_DN *sdn = NULL;
    int isupdatedn;
    Slapi_Entry *e;
    Slapi_Attr *attr;
    int argc;
    char **argv = NULL;

        /*
         * If this is a replication update, just be a noop.
         */
        err = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &isupdatedn);
    if (err) { result = uid_op_error(50); break; }
        if (isupdatedn)
        {
          break;
        }

    /*
     * Get the arguments
     */
        result = getArguments(pb, &attrName, &markerObjectClass,
                                                  &requiredObjectClass);
        if (UNTAGGED_PARAMETER == result)
        {
          slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, 
                          "ADD parameter untagged: %s\n", attrName);
          result = LDAP_SUCCESS;
          /* Statically defined subtrees to monitor */
          err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc);
          if (err) { result = uid_op_error(53); break; }

          err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv);
          if (err) { result = uid_op_error(54); break; }
          argc--; argv++; /* First argument was attribute name */
        } else if (0 != result)
        {
          break;
        }

    /*
     * Get the target DN for this add operation
     */
    err = slapi_pblock_get(pb, SLAPI_ADD_TARGET_SDN, &sdn);
    if (err) { result = uid_op_error(51); break; }

#ifdef DEBUG
    slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "ADD target=%s\n", slapi_sdn_get_dn(sdn));
#endif

       /*
         * Get the entry data for this add. Check whether it
         * contains a value for the unique attribute
         */
        err = slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &e);
        if (err) { result = uid_op_error(52); break; }

        err = slapi_entry_attr_find(e, attrName, &attr);
        if (err) break;  /* no unique attribute */

        /*
         * Check if it contains the required object class
         */
        if (NULL != requiredObjectClass)
        {
          if (!entryHasObjectClass(pb, e, requiredObjectClass))
          {
                /* No, so we don't have to do anything */
                break;
          }
        }

        /*
         * Passed all the requirements - this is an operation we
         * need to enforce uniqueness on. Now find all parent entries
         * with the marker object class, and do a search for each one.
         */
        if (NULL != markerObjectClass)
        {
          /* Subtree defined by location of marker object class */
                result = findSubtreeAndSearch(sdn, attrName, attr, NULL,
                                              requiredObjectClass, sdn,
                                              markerObjectClass);
        } else
        {
          /* Subtrees listed on invocation line */
          result = searchAllSubtrees(argc, argv, attrName, attr, NULL,
                                     requiredObjectClass, sdn);
        }
  END

  if (result)
  {
    slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
      "ADD result %d\n", result);

    if (result == LDAP_CONSTRAINT_VIOLATION) {
      errtext = slapi_ch_smprintf(moreInfo, attrName);
    } else {
      errtext = slapi_ch_strdup("Error checking for attribute uniqueness.");
    }

    /* Send failure to the client */
    slapi_send_ldap_result(pb, result, 0, errtext, 0, 0);

    slapi_ch_free_string(&errtext);
  }

  return (result==LDAP_SUCCESS)?0:-1;
}

static void
addMod(LDAPMod ***modary, int *capacity, int *nmods, LDAPMod *toadd)
{
  if (*nmods == *capacity) {
    *capacity += 4;
    if (*modary) {
      *modary = (LDAPMod **)slapi_ch_realloc((char *)*modary, *capacity * sizeof(LDAPMod *));
    } else {
      *modary = (LDAPMod **)slapi_ch_malloc(*capacity * sizeof(LDAPMod *));
    }
  }
  (*modary)[*nmods] = toadd;
  (*nmods)++;
}

/* ------------------------------------------------------------ */
/*
 * preop_modify - pre-operation plug-in for modify
 */
static int
preop_modify(Slapi_PBlock *pb)
{

  int result = LDAP_SUCCESS;
  Slapi_PBlock *spb = NULL;
  LDAPMod **checkmods = NULL;
  int checkmodsCapacity = 0;
  char *errtext = NULL;
  char *attrName = NULL;

#ifdef DEBUG
    slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
      "MODIFY begin\n");
#endif

  BEGIN
    int err;
    char *markerObjectClass=NULL;
    char *requiredObjectClass=NULL;
    LDAPMod **mods;
    int modcount = 0;
    int ii;
    LDAPMod *mod;
    Slapi_DN *sdn = NULL;
    int isupdatedn;
    int argc;
    char **argv = NULL;

    /*
     * If this is a replication update, just be a noop.
     */
    err = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &isupdatedn);
    if (err) { result = uid_op_error(60); break; }
    if (isupdatedn)
    {
      break;
    }

    /*
     * Get the arguments
     */
        result = getArguments(pb, &attrName, &markerObjectClass,
                                                  &requiredObjectClass);
        if (UNTAGGED_PARAMETER == result)
        {
          result = LDAP_SUCCESS;
          /* Statically defined subtrees to monitor */
          err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc);
          if (err) { result = uid_op_error(53); break; }

          err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv);
          if (err) { result = uid_op_error(54); break; }
          argc--; /* First argument was attribute name */
          argv++;
        } else if (0 != result)
        {
          break;
        }

    err = slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods);
    if (err) { result = uid_op_error(61); break; }

    /* There may be more than one mod that matches e.g.
       changetype: modify
       delete: uid
       uid: balster1950
       -
       add: uid
       uid: scottg
       
       So, we need to first find all mods that contain the attribute
       which are add or replace ops and are bvalue encoded
    */
    /* find out how many mods meet this criteria */
    for(;*mods;mods++)
    {
        mod = *mods;
        if ((slapi_attr_type_cmp(mod->mod_type, attrName, 1) == 0) && /* mod contains target attr */
            (mod->mod_op & LDAP_MOD_BVALUES) && /* mod is bval encoded (not string val) */
            (mod->mod_bvalues && mod->mod_bvalues[0]) && /* mod actually contains some values */
            (SLAPI_IS_MOD_ADD(mod->mod_op) || /* mod is add */
             SLAPI_IS_MOD_REPLACE(mod->mod_op))) /* mod is replace */
        {
          addMod(&checkmods, &checkmodsCapacity, &modcount, mod);
        }
    }
    if (modcount == 0) {
        break; /* no mods to check, we are done */
    }

    /* Get the target DN */
    err = slapi_pblock_get(pb, SLAPI_MODIFY_TARGET_SDN, &sdn);
    if (err) { result = uid_op_error(11); break; }

    /*
     * Check if it has the required object class
     */
    if (requiredObjectClass &&
        !(spb = dnHasObjectClass(sdn, requiredObjectClass))) {
        break;
    }

    /*
     * Passed all the requirements - this is an operation we
     * need to enforce uniqueness on. Now find all parent entries
     * with the marker object class, and do a search for each one.
     */
    /*
     * stop checking at first mod that fails the check
     */
    for (ii = 0; (result == 0) && (ii < modcount); ++ii)
    {
        mod = checkmods[ii];
        if (NULL != markerObjectClass)
        {
            /* Subtree defined by location of marker object class */
            result = findSubtreeAndSearch(sdn, attrName, NULL, 
                                          mod->mod_bvalues, requiredObjectClass,
                                          sdn, markerObjectClass);
        } else
        {
            /* Subtrees listed on invocation line */
            result = searchAllSubtrees(argc, argv, attrName, NULL,
                                       mod->mod_bvalues, requiredObjectClass, sdn);
        }
    }
  END

  slapi_ch_free((void **)&checkmods);
  freePblock(spb);
 if (result)
  {
    slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
      "MODIFY result %d\n", result);

    if (result == LDAP_CONSTRAINT_VIOLATION) {
      errtext = slapi_ch_smprintf(moreInfo, attrName);
    } else {
      errtext = slapi_ch_strdup("Error checking for attribute uniqueness.");
    }

    slapi_send_ldap_result(pb, result, 0, errtext, 0, 0);

    slapi_ch_free_string(&errtext);
  }

  return (result==LDAP_SUCCESS)?0:-1;

}

/* ------------------------------------------------------------ */
/*
 * preop_modrdn - Pre-operation call for modify RDN
 *
 * Check that the new RDN does not include attributes that
 * cause a constraint violation
 */
static int
preop_modrdn(Slapi_PBlock *pb)
{
  int result = LDAP_SUCCESS;
  Slapi_Entry *e = NULL;
  Slapi_Value *sv_requiredObjectClass = NULL;
  char *errtext = NULL;
  char *attrName = NULL;

#ifdef DEBUG
    slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
      "MODRDN begin\n");
#endif

  BEGIN
    int err;
    char *markerObjectClass=NULL;
    char *requiredObjectClass=NULL;
    Slapi_DN *sdn = NULL;
    Slapi_DN *superior;
    char *rdn;
    int deloldrdn = 0;
    int isupdatedn;
    Slapi_Attr *attr;
    int argc;
    char **argv = NULL;

        /*
         * If this is a replication update, just be a noop.
         */
        err = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &isupdatedn);
    if (err) { result = uid_op_error(30); break; }
        if (isupdatedn)
        {
          break;
        }

    /*
     * Get the arguments
     */
        result = getArguments(pb, &attrName, &markerObjectClass,
                                                  &requiredObjectClass);
        if (UNTAGGED_PARAMETER == result)
        {
          result = LDAP_SUCCESS;
          /* Statically defined subtrees to monitor */
          err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc);
          if (err) { result = uid_op_error(53); break; }

          err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv);
          if (err) { result = uid_op_error(54); break; }
          argc--; /* First argument was attribute name */
          argv++; 
        } else if (0 != result)
        {
          break;
        }

    /* Create a Slapi_Value for the requiredObjectClass to use
     * for checking the entry. */
    if (requiredObjectClass) {
        sv_requiredObjectClass = slapi_value_new_string(requiredObjectClass);
    }

    /* Get the DN of the entry being renamed */
    err = slapi_pblock_get(pb, SLAPI_MODRDN_TARGET_SDN, &sdn);
    if (err) { result = uid_op_error(31); break; }

    /* Get superior value - unimplemented in 3.0/4.0/5.0 DS */
    err = slapi_pblock_get(pb, SLAPI_MODRDN_NEWSUPERIOR_SDN, &superior);
    if (err) { result = uid_op_error(32); break; }

    /*
     * No superior means the entry is just renamed at
     * its current level in the tree.  Use the target DN for
     * determining which managed tree this belongs to
     */
    if (!superior) superior = sdn;

    /* Get the new RDN - this has the attribute values */
    err = slapi_pblock_get(pb, SLAPI_MODRDN_NEWRDN, &rdn);
    if (err) { result = uid_op_error(33); break; }
#ifdef DEBUG
    slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
      "MODRDN newrdn=%s\n", rdn);
#endif

    /* See if the old RDN value is being deleted. */
    err = slapi_pblock_get(pb, SLAPI_MODRDN_DELOLDRDN, &deloldrdn);
    if (err) { result = uid_op_error(34); break; }

    /* Get the entry that is being renamed so we can make a dummy copy
     * of what it will look like after the rename. */
    err = slapi_search_internal_get_entry(sdn, NULL, &e, plugin_identity);
    if (err != LDAP_SUCCESS) {
        result = uid_op_error(35);
        /* We want to return a no such object error if the target doesn't exist. */
        if (err == LDAP_NO_SUCH_OBJECT) {
            result = err;
        }
        break;
    }

    /* Apply the rename operation to the dummy entry. */
    /* slapi_entry_rename does not expect rdn normalized */
    err = slapi_entry_rename(e, rdn, deloldrdn, superior);
    if (err != LDAP_SUCCESS) { result = uid_op_error(36); break; }

        /*
         * Find any unique attribute data in the new RDN
         */
        err = slapi_entry_attr_find(e, attrName, &attr);
        if (err) break;  /* no UID attribute */

        /*
         * Check if it has the required object class
         */
        if (requiredObjectClass &&
            !slapi_entry_attr_has_syntax_value(e, SLAPI_ATTR_OBJECTCLASS, sv_requiredObjectClass)) { break; }

        /*
         * Passed all the requirements - this is an operation we
         * need to enforce uniqueness on. Now find all parent entries
         * with the marker object class, and do a search for each one.
         */
        if (NULL != markerObjectClass)
        {
          /* Subtree defined by location of marker object class */
                result = findSubtreeAndSearch(slapi_entry_get_sdn(e), attrName, attr, NULL,
                                              requiredObjectClass, sdn,
                                              markerObjectClass);
        } else
        {
          /* Subtrees listed on invocation line */
          result = searchAllSubtrees(argc, argv, attrName, attr, NULL,
                                     requiredObjectClass, sdn);
        }
  END
  /* Clean-up */
  slapi_value_free(&sv_requiredObjectClass);
  if (e) slapi_entry_free(e);

  if (result)
  {
    slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
      "MODRDN result %d\n", result);

    if (result == LDAP_CONSTRAINT_VIOLATION) {
      errtext = slapi_ch_smprintf(moreInfo, attrName);
    } else {
      errtext = slapi_ch_strdup("Error checking for attribute uniqueness.");
    }

    slapi_send_ldap_result(pb, result, 0, errtext, 0, 0);

    slapi_ch_free_string(&errtext);
  }

  return (result==LDAP_SUCCESS)?0:-1;

}

/* ------------------------------------------------------------ */
/*
 * Initialize the plugin
 *
 * uidunique_init (the old name) is deprecated
 */
int
NSUniqueAttr_Init(Slapi_PBlock *pb)
{
  int err = 0;
  Slapi_Entry *plugin_entry = NULL;
  char *plugin_type = NULL;
  int preadd = SLAPI_PLUGIN_PRE_ADD_FN;
  int premod = SLAPI_PLUGIN_PRE_MODIFY_FN;
  int premdn = SLAPI_PLUGIN_PRE_MODRDN_FN;

  BEGIN
    int argc;
    char **argv;

    /* Declare plugin version */
    err = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION,
            SLAPI_PLUGIN_VERSION_01);
    if (err) break;

    /*
     * Get plugin identity and store it for later use
     * Used for internal operations
     */

    slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &plugin_identity);
    /* PR_ASSERT (plugin_identity); */

    if ((slapi_pblock_get(pb, SLAPI_PLUGIN_CONFIG_ENTRY, &plugin_entry) == 0) &&
        plugin_entry &&
        (plugin_type = slapi_entry_attr_get_charptr(plugin_entry, "nsslapd-plugintype")) &&
        plugin_type && strstr(plugin_type, "betxn")) {
        preadd = SLAPI_PLUGIN_BE_TXN_PRE_ADD_FN;
        premod = SLAPI_PLUGIN_BE_TXN_PRE_MODIFY_FN;
        premdn = SLAPI_PLUGIN_BE_TXN_PRE_MODRDN_FN;
    }
    slapi_ch_free_string(&plugin_type);

    /*
     * Get and normalize arguments
     */
    err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc);
    if (err) break;
 
    err = slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv);
    if (err) break;

    /* First argument is the unique attribute name */
    if (argc < 1) { err = -1; break; }
    argv++; argc--;

    for(;argc > 0;argc--, argv++) {
        char *normdn = slapi_create_dn_string_case("%s", *argv);
        slapi_ch_free_string(argv);
        *argv = normdn;
    }

    /* Provide descriptive information */
    err = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION,
            (void*)&pluginDesc);
    if (err) break;

    /* Register functions */
    err = slapi_pblock_set(pb, preadd, (void*)preop_add);
    if (err) break;

    err = slapi_pblock_set(pb, premod, (void*)preop_modify);
    if (err) break;

    err = slapi_pblock_set(pb, premdn, (void*)preop_modrdn);
    if (err) break;

  END

  if (err) {
    slapi_log_error(SLAPI_LOG_PLUGIN, "NSUniqueAttr_Init",
             "Error: %d\n", err);
    err = -1;
  }
  else
    slapi_log_error(SLAPI_LOG_PLUGIN, "NSUniqueAttr_Init",
             "plugin loaded\n");

  return err;
}

int
uidunique_init(Slapi_PBlock *pb)
{
  return NSUniqueAttr_Init(pb);
}


/* ------------------------------------------------------------ */
/*
 * ldap_quote_filter_value
 *
 * Quote the filter value according to RFC 2254 (Dec 1997)
 *
 * value - a UTF8 string containing the value.  It may contain
 *   any of the chars needing quotes ( '(' ')' '*' '/' and NUL ).
 * len - the length of the UTF8 value
 * out - a buffer to recieve the converted value.  May be NULL, in
 *   which case, only the length of the output is computed (and placed in
 *   outLen).
 * maxLen - the size of the output buffer.  It is an error if this length
 *   is exceeded.  Ignored if out is NULL.
 * outLen - recieves the size of the output.  If an error occurs, this
 *   result is not available.
 *
 * Returns
 *   0 - success
 *  -1 - failure (usually a buffer overflow)
 */
int /* Error value */
ldap_quote_filter_value(
  char *value, int len,
  char *out, int maxLen,
  int *outLen)
{
  int err;
  char *eValue;
  int resLen;
#ifdef SLAPI_SUPPORTS_V3_ESCAPING
  static char hexchars[16] = "0123456789abcdef";
#endif

  err = 0;
  eValue = &value[len];
  resLen = 0;

  /*
   * Convert each character in the input string
   */
  while(value < eValue)
  {
    switch(*value)
    {
    case '(':
    case ')':
    case '*':
    case '\\':
#ifdef SLAPI_SUPPORTS_V3_ESCAPING
    case 0:
#endif
      /* Handle characters needing special escape sequences */

      /* Compute size of output */
#ifdef SLAPI_SUPPORTS_V3_ESCAPING
      resLen += 3;
#else
      resLen += 2;
#endif

      /* Generate output if requested */
      if (out)
      {
        /* Check for overflow */
        if (resLen > maxLen) { err = -1; break; }

        *out++ = '\\';
#ifdef SLAPI_SUPPORTS_V3_ESCAPING
        *out++ = hexchars[(*value >> 4) & 0xF];
        *out++ = hexchars[*value & 0xF];
#else
        *out++ = *value;
#endif
      }

      break;

    default:
      /* Compute size of output */
      resLen += 1;
      
      /* Generate output if requested */
      if (out)
      {
        if (resLen > maxLen) { err = -1; break; }
        *out++ = *value;
      }

      break;
    }

    if (err) break;

    value++;
  }

  if (!err) *outLen = resLen;

  return err;
}
