]> git.sur5r.net Git - openldap/commitdiff
Added referential integrity and attribute uniqueness overlays
authorHoward Chu <hyc@openldap.org>
Sat, 17 Apr 2004 08:17:43 +0000 (08:17 +0000)
committerHoward Chu <hyc@openldap.org>
Sat, 17 Apr 2004 08:17:43 +0000 (08:17 +0000)
doc/man/man5/slapo-refint.5 [new file with mode: 0644]
doc/man/man5/slapo-unique.5 [new file with mode: 0644]
servers/slapd/overlays/Makefile.in
servers/slapd/overlays/refint.c [new file with mode: 0644]
servers/slapd/overlays/unique.c [new file with mode: 0644]

diff --git a/doc/man/man5/slapo-refint.5 b/doc/man/man5/slapo-refint.5
new file mode 100644 (file)
index 0000000..803e00c
--- /dev/null
@@ -0,0 +1,53 @@
+.TH SLAPO-REFINT 5 "RELEASEDATE" "OpenLDAP LDVERSION"
+.\" Copyright 2004 The OpenLDAP Foundation All Rights Reserved.
+.\" Copying restrictions apply.  See COPYRIGHT/LICENSE.
+.\" $OpenLDAP$
+.SH NAME
+slapo-refint \- Referential Integrity overlay
+.SH SYNOPSIS
+ETCDIR/slapd.conf
+.SH DESCRIPTION
+The Referential Integrity overlay can be used with a backend database such as
+.BR slapd-bdb (5)
+to maintain the cohesiveness of a schema which utilizes reference attributes.
+.LP
+Integrity is maintained by updating database records which contain the named
+attributes to match the results of a
+.B modrdn
+or
+.B delete
+operation. For example, if the integrity attribute were configured as
+.B manager ,
+deletion of the record "uid=robert,ou=people,o=openldap.org" would trigger a
+search for all other records which have a
+.B manager
+attribute containing that DN. Entries matching that search would have their
+.B manager
+attribute removed.
+.SH CONFIGURATION
+These
+.B slapd.conf
+options apply to the Referential Integrity overlay.
+They should appear after the
+.B overlay
+directive and before any subsequent
+.B database
+directive.
+.TP
+.B refint_attributes <attribute...>
+Specify one or more attributes which for which integrity will be maintained
+as described above.
+.TP
+.B refint_nothing <string>
+Specify an arbitrary value to be used as a placeholder when the last value
+would otherwise be deleted from an attribute. This can be useful in cases
+where the schema requires the existence of an attribute for which referential
+integrity is enforced. The attempted deletion of a required attribute will
+otherwise result in an Object Class Violation, causing the request to fail.
+.B
+.SH FILES
+.TP
+ETCDIR/slapd.conf
+default slapd configuration file
+.SH SEE ALSO
+.BR slapd.conf (5).
diff --git a/doc/man/man5/slapo-unique.5 b/doc/man/man5/slapo-unique.5
new file mode 100644 (file)
index 0000000..8a10269
--- /dev/null
@@ -0,0 +1,93 @@
+.TH SLAPO-UNIQUE 5 "RELEASEDATE" "OpenLDAP LDVERSION"
+.\" Copyright 2004 The OpenLDAP Foundation All Rights Reserved.
+.\" Copying restrictions apply.  See COPYRIGHT/LICENSE.
+.\" $OpenLDAP$
+.SH NAME
+slapo-unique \- Attribute Uniqueness overlay
+.SH SYNOPSIS
+ETCDIR/slapd.conf
+.SH DESCRIPTION
+The Attribute Uniqueness overlay can be used with a backend database such as
+.BR slapd-bdb (5)
+to enforce the uniqueness of some or all attributes within a subtree. This
+subtree defaults to the base DN of the database for which the Uniqueness
+overlay is configured.
+.LP
+Uniqueness is enforced by searching the subtree to ensure that the values of
+all attributes presented with an
+.B add ,
+.B modify
+or
+.B modrdn
+operation are unique within the subtree.
+For example, if uniquness were enforced for the
+.B uid
+attribute, the subtree would be searched for any other records which also
+have a
+.B uid
+attribute containing the same value. If any are found, the request is
+rejected.
+.SH CONFIGURATION
+These
+.B slapd.conf
+options apply to the Attribute Uniqueness overlay.
+They should appear after the
+.B overlay
+directive and before any subsequent
+.B database
+directive.
+.TP
+.B unique_base <basedn>
+Configure the subtree against which uniqueness searches will be invoked.
+The
+.B basedn
+defaults to the base DN of the database for which uniqueness is configured.
+.TP
+.B unique_ignore <attribute...>
+Configure one or more attributes for which uniqueness will not be enforced.
+If not configured, all non-operational (eg, system) attributes must be
+unique. Note that the
+.B unique_ignore
+list should generally contain the
+.B objectClass ,
+.B dc ,
+.B ou
+and
+.B o
+attributes, as these will generally not be unique, nor are they operational
+attributes.
+.TP
+.B unique_attributes <attribute...>
+Specify one or more attributes which for which uniqueness will be enforced.
+If not specified, all attributes which are not operational (eg, system
+attributes such as
+.B entryUUID )
+or specified via the
+.B unique_ignore
+directive above must be unique within the subtree.
+.TP
+.B unique_strict
+By default, uniqueness is not enforced for null values. Enabling
+.B unique_strict
+mode extends the concept of uniqueness to include null values, such that
+only one attribute within a subtree will be allowed to have a null value.
+.SH CAVEATS
+.LP
+The search key is generated with attributes that are non-operational, not
+on the
+.B unique_ignore
+list, and included in the
+.B unique_attributes
+list, in that order. This makes it possible to create interesting and
+unusable configurations.
+.LP
+Typical attributes for the
+.B unique_ignore
+directive are intentionally not hardcoded into the overlay to allow for
+maximum flexibility in meeting site-specific requirements.
+.SH FILES
+.TP
+ETCDIR/slapd.conf
+default slapd configuration file
+.SH SEE ALSO
+.BR slapd.conf (5).
index 71f26f88e0b471dc79bc1ade39a056b8d2c4516f..1a201973d122e897a4e7428355200bf7e7513551 100644 (file)
@@ -19,6 +19,8 @@ SRCS = overlays.c \
        dyngroup.c \
        lastmod.c \
        pcache.c \
+       refint.c \
+       unique.c \
        rwm.c rwmconf.c rwmdn.c rwmmap.c
 OBJS = overlays.lo \
        chain.lo \
@@ -26,6 +28,8 @@ OBJS = overlays.lo \
        dyngroup.lo \
        lastmod.lo \
        pcache.lo \
+       refint.lo \
+       unique.lo \
        rwm.lo rwmconf.lo rwmdn.lo rwmmap.lo
 
 LDAP_INCDIR= ../../../include       
@@ -58,6 +62,12 @@ lastmod.la : lastmod.lo $(@PLAT@_LINK_LIBS)
 pcache.la : pcache.lo $(@PLAT@_LINK_LIBS)
        $(LTLINK_MOD) -module -o $@ pcache.lo version.lo $(LINK_LIBS)
 
+refint.la : refint.lo $(@PLAT@_LINK_LIBS)
+       $(LTLINK_MOD) -module -o $@ refint.lo version.lo $(LINK_LIBS)
+
+unique.la : unique.lo $(@PLAT@_LINK_LIBS)
+       $(LTLINK_MOD) -module -o $@ unique.lo version.lo $(LINK_LIBS)
+
 rwm.la : rwm.lo $(@PLAT@_LINK_LIBS)
        $(LTLINK_MOD) -module -o $@ rwm.lo rwmconf.lo rwmdn.lo rwmmap.lo version.lo $(LINK_LIBS)
 
diff --git a/servers/slapd/overlays/refint.c b/servers/slapd/overlays/refint.c
new file mode 100644 (file)
index 0000000..d97f49d
--- /dev/null
@@ -0,0 +1,675 @@
+/* refint.c - referential integrity module */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004 The OpenLDAP Foundation.
+ * Portions Copyright 2004 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Symas Corp. for inclusion in
+ * OpenLDAP Software.  This work was sponsored by Hewlett-Packard.
+ */
+
+#include "portable.h"
+
+/* This module maintains referential integrity for a set of
+ * DN-valued attributes by searching for all references to a given
+ * DN whenever the DN is changed or its entry is deleted, and making
+ * the appropriate update.
+ *
+ * Updates are performed using the database rootdn, but the ModifiersName
+ * is always set to refint_dn.
+ */
+
+#ifdef SLAPD_OVER_REFINT
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+
+static slap_overinst refint;
+
+/* The DN to use in the ModifiersName for all refint updates */
+static BerValue refint_dn = BER_BVC("cn=Referential Integrity Overlay");
+
+typedef struct refint_attrs_s {
+       struct refint_attrs_s *next;
+       AttributeDescription *attr;
+} refint_attrs;
+
+typedef struct dependents_s {
+       struct dependents_s *next;
+       BerValue dn;                            /* target dn */
+       Modifications *mm;
+} dependent_data;
+
+typedef struct refint_data_s {
+       const char *message;                    /* breadcrumbs */
+       struct refint_attrs_s *attrs;   /* list of known attrs */
+       struct dependents_s *mods;              /* modifications returned from callback */
+       BerValue dn;                            /* basedn in parent, searchdn in call */
+       BerValue newdn;                         /* replacement value for modrdn callback */
+       BerValue nnewdn;                        /* normalized replacement value */
+       BerValue nothing;                       /* the nothing value, if needed */
+       BerValue nnothing;                      /* normalized nothingness */
+} refint_data;
+
+/*
+** allocate new refint_data;
+** initialize, copy basedn;
+** store in on_bi.bi_private;
+**
+*/
+
+static int
+refint_db_init(
+       BackendDB       *be
+)
+{
+       slap_overinst *on = (slap_overinst *)be->bd_info;
+       refint_data *id = ch_malloc(sizeof(refint_data));
+       refint_attrs *ip;
+       id->message = "_init";
+       id->attrs = NULL;
+       id->newdn.bv_val = NULL;
+       id->nothing.bv_val = NULL;
+       id->nnothing.bv_val = NULL;
+       ber_dupbv( &id->dn, &be->be_nsuffix[0] );
+       on->on_bi.bi_private = id;
+       return(0);
+}
+
+
+/*
+** if command = attributes:
+**     foreach argument:
+**             convert to attribute;
+**             add to configured attribute list;
+** elseif command = basedn:
+**     set our basedn to argument;
+**
+*/
+
+static int
+refint_config(
+       BackendDB       *be,
+       const char      *fname,
+       int             lineno,
+       int             argc,
+       char            **argv
+)
+{
+       slap_overinst *on       = (slap_overinst *) be->bd_info;
+       refint_data *id = on->on_bi.bi_private;
+       refint_attrs *ip;
+       const char *text;
+       AttributeDescription *ad;
+       BerValue dn;
+       int i;
+
+       if(!strcasecmp(*argv, "refint_attributes")) {
+               for(i = 1; i < argc; i++) {
+                       for(ip = id->attrs; ip; ip = ip->next)
+                           if(!strcmp(argv[i], ip->attr->ad_cname.bv_val)) {
+                               Debug(LDAP_DEBUG_ANY,
+                                       "%s: line %d: duplicate attribute <s>, ignored\n",
+                                       fname, lineno, argv[i]);
+                               continue;
+                       }
+                       ad = NULL;
+                       if(slap_str2ad(argv[i], &ad, &text) != LDAP_SUCCESS) {
+                               Debug(LDAP_DEBUG_ANY,
+                                       "%s: line %d: bad attribute <%s>, ignored\n",
+                                       fname, lineno, text);
+                               continue;               /* XXX */
+                       } else if(ad->ad_next) {
+                               Debug(LDAP_DEBUG_ANY,
+                                       "%s: line %d: multiple attributes match <%s>, ignored\n",
+                                       fname, lineno, argv[i]);
+                               continue;
+                       }
+                       ip = ch_malloc(sizeof(refint_attrs));
+                       ip->attr = ad;
+                       ip->next = id->attrs;
+                       id->attrs = ip;
+                       Debug(LDAP_DEBUG_ANY, "%s: line %d: new attribute <%s>\n",
+                               fname, lineno, argv[i]);
+               }
+       } else if(!strcasecmp(*argv, "refint_base")) {
+               /* XXX only one basedn (yet) - need validate argument! */
+               if(id->dn.bv_val) ch_free(id->dn.bv_val);
+               ber_str2bv( argv[1], 0, 0, &dn );
+               Debug(LDAP_DEBUG_ANY, "%s: line %d: new baseDN <%s>\n",
+                       fname, lineno, argv[1]);
+               if(dnNormalize(0, NULL, NULL, &dn, &id->dn, NULL)) {
+                       Debug(LDAP_DEBUG_ANY, "%s: line %d: bad baseDN!\n", fname, lineno, 0);
+                       return(1);
+               }
+       } else if(!strcasecmp(*argv, "refint_nothing")) {
+               if(id->nothing.bv_val) ch_free(id->nothing.bv_val);
+               if(id->nnothing.bv_val) ch_free(id->nnothing.bv_val);
+               ber_str2bv( argv[1], 0, 1, &id->nothing );
+               if(dnNormalize(0, NULL, NULL, &id->nothing, &id->nnothing, NULL)) {
+                       Debug(LDAP_DEBUG_ANY, "%s: line %d: bad nothingDN!\n", fname, lineno, 0);
+                       return(1);
+               }
+               Debug(LDAP_DEBUG_ANY, "%s: line %d: new nothingDN<%s>\n",
+                       fname, lineno, argv[1]);
+       } else {
+               return(SLAP_CONF_UNKNOWN);
+       }
+
+       id->message = "_config";
+       return(0);
+}
+
+
+/*
+** nothing really happens here;
+**
+*/
+
+static int
+refint_open(
+       BackendDB *be
+)
+{
+       slap_overinst *on       = (slap_overinst *)be->bd_info;
+       refint_data *id = on->on_bi.bi_private;
+       id->message             = "_open";
+       return(0);
+}
+
+
+/*
+** foreach configured attribute:
+**     free it;
+** free our basedn;
+** (do not) free id->message;
+** reset on_bi.bi_private;
+** free our config data;
+**
+*/
+
+static int
+refint_close(
+       BackendDB *be
+)
+{
+       slap_overinst *on       = (slap_overinst *) be->bd_info;
+       refint_data *id = on->on_bi.bi_private;
+       refint_attrs *ii, *ij;
+       id->message             = "_close";
+
+       for(ii = id->attrs; ii; ii = ij) {
+               ij = ii->next;
+               ch_free(ii);
+       }
+
+       ch_free(id->dn.bv_val);
+       ch_free(id->nothing.bv_val);
+       ch_free(id->nnothing.bv_val);
+
+       on->on_bi.bi_private = NULL;    /* XXX */
+
+       ch_free(id);
+
+       return(0);
+}
+
+/*
+** delete callback
+** generates a list of Modification* from search results
+*/
+
+static int
+refint_delete_cb(
+       Operation *op,
+       SlapReply *rs
+)
+{
+       Attribute *a;
+       BerVarray b = NULL;
+       refint_data *id, *dd = op->o_callback->sc_private;
+       refint_attrs *ia, *da = dd->attrs;
+       dependent_data *ip, *dp = NULL;
+       Modifications *mp, *ma;
+       int i;
+
+       Debug(LDAP_DEBUG_TRACE, "refint_delete_cb <%s>\n",
+               rs->sr_entry ? rs->sr_entry->e_name.bv_val : "NOTHING", 0, 0);
+
+       if (rs->sr_type != REP_SEARCH || !rs->sr_entry) return(0);
+       dd->message = "_delete_cb";
+
+       /*
+       ** foreach configured attribute type:
+       **      if this attr exists in the search result,
+       **      and it has a value matching the target:
+       **              allocate a Modification;
+       **              allocate its array of 2 BerValues;
+       **              if only one value, and we have a configured Nothing:
+       **                      allocate additional Modification
+       **                      type = MOD_ADD
+       **                      BerValues[] = { Nothing, NULL };
+       **                      add to list
+       **              type = MOD_DELETE
+       **              BerValues[] = { our target dn, NULL };
+       **      add this mod to the list of mods;
+       **
+       */
+
+       ip = ch_malloc(sizeof(dependent_data));
+       ip->dn.bv_val = NULL;
+       ip->next = NULL;
+       ip->mm = NULL;
+       ma = NULL;
+       for(ia = da; ia; ia = ia->next) {
+           if(a = attr_find(rs->sr_entry->e_attrs, ia->attr))
+               for(i = 0, b = a->a_nvals; b[i].bv_val; i++)
+                   if(bvmatch(&dd->dn, &b[i])) {
+                       if(!ip->dn.bv_val) ber_dupbv(&ip->dn, &rs->sr_entry->e_nname);
+                       if(!b[1].bv_val && dd->nothing.bv_val) {
+                               mp = ch_malloc(sizeof(Modifications));
+                               mp->sml_desc = ia->attr;                /* XXX */
+                               mp->sml_type = a->a_desc->ad_cname;
+                               mp->sml_values  = ch_malloc(2 * sizeof(BerValue));
+                               mp->sml_nvalues = ch_malloc(2 * sizeof(BerValue));
+                               mp->sml_values[1].bv_len = mp->sml_nvalues[1].bv_len = 0;
+                               mp->sml_values[1].bv_val = mp->sml_nvalues[1].bv_val = NULL;
+
+                               mp->sml_op = LDAP_MOD_ADD;
+                               ber_dupbv(&mp->sml_values[0],  &dd->nothing);
+                               ber_dupbv(&mp->sml_nvalues[0], &dd->nnothing);
+                               mp->sml_next = ma;
+                               ma = mp;
+                       }
+                       /* this might violate the object class */
+                       mp = ch_malloc(sizeof(Modifications));
+                       mp->sml_desc = ia->attr;                /* XXX */
+                       mp->sml_type = a->a_desc->ad_cname;
+                       mp->sml_values  = ch_malloc(2 * sizeof(BerValue));
+                       mp->sml_nvalues = ch_malloc(2 * sizeof(BerValue));
+                       mp->sml_values[1].bv_len = mp->sml_nvalues[1].bv_len = 0;
+                       mp->sml_values[1].bv_val = mp->sml_nvalues[1].bv_val = NULL;
+                       mp->sml_op = LDAP_MOD_DELETE;
+                       ber_dupbv(&mp->sml_values[0], &dd->dn);
+                       ber_dupbv(&mp->sml_nvalues[0], &mp->sml_values[0]);
+                       mp->sml_next = ma;
+                       ma = mp;
+                       Debug(LDAP_DEBUG_TRACE, "refint_delete_cb: %s: %s\n",
+                               a->a_desc->ad_cname.bv_val, dd->dn.bv_val, 0);
+                       break;
+           }
+       }
+       ip->mm = ma;
+       ip->next = dd->mods;
+       dd->mods = ip;
+
+       return(0);
+}
+
+/*
+** null callback
+** does nothing
+*/
+
+static int
+refint_null_cb(
+       Operation *op,
+       SlapReply *rs
+)
+{
+       ((refint_data *)op->o_callback->sc_private)->message = "_null_cb";
+       return(LDAP_SUCCESS);
+}
+
+/*
+** modrdn callback
+** generates a list of Modification* from search results
+*/
+
+static int
+refint_modrdn_cb(
+       Operation *op,
+       SlapReply *rs
+)
+{
+       Attribute *a;
+       BerVarray b = NULL;
+       refint_data *id, *dd = op->o_callback->sc_private;
+       refint_attrs *ia, *da = dd->attrs;
+       dependent_data *ip = NULL, *dp = NULL;
+       Modifications *mp;
+       int i, j, fix;
+
+       Debug(LDAP_DEBUG_TRACE, "refint_modrdn_cb <%s>\n",
+               rs->sr_entry ? rs->sr_entry->e_name.bv_val : "NOTHING", 0, 0);
+
+       if (rs->sr_type != REP_SEARCH || !rs->sr_entry) return(0);
+       dd->message = "_modrdn_cb";
+
+       /*
+       ** foreach configured attribute type:
+       **   if this attr exists in the search result,
+       **   and it has a value matching the target:
+       **      allocate a pair of Modifications;
+       **      make it MOD_ADD the new value and MOD_DELETE the old;
+       **      allocate its array of BerValues;
+       **      foreach value in the search result:
+       **         if it matches our target value, replace it;
+       **         otherwise, copy from the search result;
+       **      terminate the array of BerValues;
+       **   add these mods to the list of mods;
+       **
+       */
+
+       for(ia = da; ia; ia = ia->next) {
+           if(a = attr_find(rs->sr_entry->e_attrs, ia->attr)) {
+                   for(fix = 0, i = 0, b = a->a_nvals; b[i].bv_val; i++)
+                       if(bvmatch(&dd->dn, &b[i])) { fix++; break; }
+                   if(fix) {
+                       if (!ip) {
+                           ip = ch_malloc(sizeof(dependent_data));
+                           ip->next = NULL;
+                           ip->mm = NULL;
+                           ber_dupbv(&ip->dn, &rs->sr_entry->e_nname);
+                       }
+                       mp = ch_malloc(sizeof(Modifications));
+                       mp->sml_op = LDAP_MOD_ADD;
+                       mp->sml_desc = ia->attr;                /* XXX */
+                       mp->sml_type = ia->attr->ad_cname;
+                       mp->sml_values  = ch_malloc(2 * sizeof(BerValue));
+                       mp->sml_nvalues = ch_malloc(2 * sizeof(BerValue));
+                       ber_dupbv(&mp->sml_values[0], &dd->newdn);
+                       ber_dupbv(&mp->sml_nvalues[0], &dd->nnewdn);
+                       mp->sml_values[1].bv_len = mp->sml_nvalues[1].bv_len = 0;
+                       mp->sml_values[1].bv_val = mp->sml_nvalues[1].bv_val = NULL;
+                       mp->sml_next = ip->mm;
+                       ip->mm = mp;
+                       mp = ch_malloc(sizeof(Modifications));
+                       mp->sml_op = LDAP_MOD_DELETE;
+                       mp->sml_desc = ia->attr;                /* XXX */
+                       mp->sml_type = ia->attr->ad_cname;
+                       mp->sml_values  = ch_malloc(2 * sizeof(BerValue));
+                       mp->sml_nvalues = ch_malloc(2 * sizeof(BerValue));
+                       ber_dupbv(&mp->sml_values[0], &dd->dn);
+                       ber_dupbv(&mp->sml_nvalues[0], &dd->dn);
+                       mp->sml_values[1].bv_len = mp->sml_nvalues[1].bv_len = 0;
+                       mp->sml_values[1].bv_val = mp->sml_nvalues[1].bv_val = NULL;
+                       mp->sml_next = ip->mm;
+                       ip->mm = mp;
+                       Debug(LDAP_DEBUG_TRACE, "refint_modrdn_cb: %s: %s\n",
+                               a->a_desc->ad_cname.bv_val, dd->dn.bv_val, 0);
+               }
+           }
+       }
+       if (ip) {
+               ip->next = dd->mods;
+               dd->mods = ip;
+       }
+
+       return(0);
+}
+
+
+/*
+** refint_response
+** search for matching records and modify them
+*/
+
+static int
+refint_response(
+       Operation *op,
+       SlapReply *rs
+)
+{
+       Operation nop = *op;
+       SlapReply nrs = { REP_RESULT };
+       slap_callback cb = { NULL, NULL, NULL, NULL };
+       slap_callback cb2 = { NULL, slap_replog_cb, NULL, NULL };
+       slap_callback *cbo, *cbp;
+       slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+       refint_data *id = on->on_bi.bi_private;
+       refint_data dd = *id;
+       refint_attrs *ip;
+       dependent_data *dp;
+       char *fstr, *key, *kp, **dnpp, **ndnpp, *cp;
+       BerValue ndn, moddn, pdn;
+       BerVarray b = NULL;
+       int rc, ac, i, j, ksize;
+
+       id->message = "_refint_response";
+
+       /* If the main op failed or is not a Delete or ModRdn, ignore it */
+       if (( op->o_tag != LDAP_REQ_DELETE && op->o_tag != LDAP_REQ_MODRDN ) ||
+               rs->sr_err != LDAP_SUCCESS )
+               return SLAP_CB_CONTINUE;
+
+       /*
+       ** validate (and count) the list of attrs;
+       **
+       */
+
+       for(ip = id->attrs, ac = 0; ip; ip = ip->next, ac++);
+       if(!ac) {
+               rs->sr_err = LDAP_OTHER;
+               rs->sr_text = "refint_response called without any attributes";
+               return SLAP_CB_CONTINUE;
+       }
+
+       /*
+       ** find the backend that matches our configured basedn;
+       ** make sure it exists and has search and modify methods;
+       **
+       */
+
+       nop.o_bd = select_backend(&id->dn, 0, 1);
+
+       if(nop.o_bd) {
+               if (!nop.o_bd->be_search || !nop.o_bd->be_modify) {
+                       rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+                       rs->sr_text = "backend missing search and/or modify";
+                       return SLAP_CB_CONTINUE;
+               }
+       } else {
+               rs->sr_err = LDAP_OTHER;
+               rs->sr_text = "no known backend? this shouldn't be happening!";
+               return SLAP_CB_CONTINUE;
+       }
+
+       cb2.sc_next = &cb;
+
+       /*
+       ** if delete: set delete callback;
+       ** else modrdn: create a newdn, set modify callback;
+       **
+       */
+
+       if(op->o_tag == LDAP_REQ_DELETE) {
+               cb.sc_response = &refint_delete_cb;
+               dd.newdn.bv_val = NULL;
+               dd.nnewdn.bv_val = NULL;
+       } else {
+               cb.sc_response = &refint_modrdn_cb;
+               if ( op->oq_modrdn.rs_newSup ) {
+                       pdn = *op->oq_modrdn.rs_newSup;
+               } else {
+                       dnParent( &op->o_req_dn, &pdn );
+               }
+               build_new_dn( &dd.newdn, &pdn, &op->orr_newrdn, NULL );
+               if ( op->oq_modrdn.rs_nnewSup ) {
+                       pdn = *op->oq_modrdn.rs_nnewSup;
+               } else {
+                       dnParent( &op->o_req_ndn, &pdn );
+               }
+               build_new_dn( &dd.nnewdn, &pdn, &op->orr_nnewrdn, NULL );
+       }
+
+       /*
+       ** calculate the search key size and allocate it;
+       ** build a search filter for all configured attributes;
+       ** populate our Operation;
+       ** pass our data (attr list, dn) to backend via sc_private;
+       ** call the backend search function;
+       ** nb: (|(one=thing)) is valid, but do smart formatting anyway;
+       ** nb: 16 is arbitrarily a dozen or so extra bytes;
+       **
+       */
+
+       for(ksize = 16, ip = id->attrs; ip; ip = ip->next)
+               ksize += ip->attr->ad_cname.bv_len + op->o_req_dn.bv_len + 3;
+       kp = key = ch_malloc(ksize);
+       if(--ac) kp += sprintf(key, "(|");
+       for(ip = id->attrs; ip; ip = ip->next)
+               kp += sprintf(kp, "(%s=%s)",
+                       ip->attr->ad_cname.bv_val, op->o_req_dn.bv_val);
+       if(ac) *kp++ = ')';
+       *kp = 0;
+
+       nop.ors_filter = str2filter_x(&nop, key);
+       ber_str2bv(key, 0, 0, &nop.ors_filterstr);
+
+       /* callback gets the searched dn instead */
+       dd.dn = op->o_req_ndn;
+       dd.message      = "_dependent_search";
+       dd.mods         = NULL;
+       cb.sc_private   = &dd;
+       nop.o_callback  = &cb;
+       nop.o_tag       = LDAP_REQ_SEARCH;
+       nop.ors_scope   = LDAP_SCOPE_SUBTREE;
+       nop.ors_deref   = LDAP_DEREF_NEVER;
+       nop.ors_slimit  = -1;
+       nop.ors_tlimit  = -1;
+       nop.o_req_ndn = id->dn;
+       nop.o_req_dn = id->dn;
+
+       /* search */
+       rc = nop.o_bd->be_search(&nop, &nrs);
+
+       filter_free_x(&nop, nop.ors_filter);
+       ch_free(key);
+       ch_free(dd.nnewdn.bv_val);
+       ch_free(dd.newdn.bv_val);
+       dd.newdn.bv_val = NULL;
+       dd.nnewdn.bv_val = NULL;
+
+       if(rc != LDAP_SUCCESS) {
+               rs->sr_err = nrs.sr_err;
+               rs->sr_text = "refint_response search failed";
+               goto done;
+       }
+
+       /* safety? paranoid just in case */
+       if(!cb.sc_private) {
+               rs->sr_err = LDAP_OTHER;
+               rs->sr_text = "whoa! refint_response callback wiped out sc_private?!";
+               goto done;
+       }
+
+       /* presto! now it's a modify request with null callback */
+       cb.sc_response  = &refint_null_cb;
+       nop.o_tag       = LDAP_REQ_MODIFY;
+       dd.message      = "_dependent_modify";
+
+       /* See if the parent operation is going into the replog */
+       cbo = NULL;
+       for (cbp = op->o_callback->sc_next; cbp; cbo=cbp,cbp=cbp->sc_next) {
+               if (cbp->sc_response == slap_replog_cb) {
+                       /* Invoke replog now, arrange for our
+                        * dependent mods to also be logged
+                        */
+                       cbo->sc_next = cbp->sc_next;
+                       replog( op );
+                       nop.o_callback = &cb2;
+                       break;
+               }
+       }
+
+       /*
+       ** [our search callback builds a list of mods]
+       ** foreach mod:
+       **      make sure its dn has a backend;
+       **      connect Modification* chain to our op;
+       **      call the backend modify function;
+       **      pass any errors upstream;
+       **
+       */
+
+       for(dp = dd.mods; dp; dp = dp->next) {
+               Modifications **tail, *m;
+
+               for(m = dp->mm; m && m->sml_next; m = m->sml_next);
+               tail = &m->sml_next;
+               nop.o_req_dn    = dp->dn;
+               nop.o_req_ndn   = dp->dn;
+               nop.o_bd = select_backend(&dp->dn, 0, 1);
+               if(!nop.o_bd) {
+                       rs->sr_err = LDAP_OTHER;
+                       rs->sr_text = "this should never happen either!";
+                       goto done;
+               }
+               nrs.sr_type     = REP_RESULT;
+               nop.orm_modlist = dp->mm;       /* callback did all the work */
+               nop.o_dn = refint_dn;
+               nop.o_ndn = refint_dn;
+               rs->sr_err = slap_mods_opattrs( &nop, nop.orm_modlist,
+                       tail, &rs->sr_text, NULL, 0 );
+               nop.o_dn = nop.o_bd->be_rootdn;
+               nop.o_ndn = nop.o_bd->be_rootndn;
+               if(rs->sr_err != LDAP_SUCCESS) goto done;
+               if((rc = nop.o_bd->be_modify(&nop, &nrs)) != LDAP_SUCCESS) {
+                       rs->sr_err = nrs.sr_err;
+                       rs->sr_text = "dependent modify failed";
+                       goto done;
+               }
+       }
+
+done:
+       for(dp = dd.mods; dp; dp = dd.mods) {
+               dd.mods = dp->next;
+               ch_free(dp->dn.bv_val);
+               slap_mods_free(dp->mm);
+       }
+       dd.mods = NULL;
+
+       return(SLAP_CB_CONTINUE);
+}
+
+/*
+** init_module is last so the symbols resolve "for free" --
+** it expects to be called automagically during dynamic module initialization
+*/
+
+int refint_init() {
+
+       /* statically declared just after the #includes at top */
+       refint.on_bi.bi_type = "refint";
+       refint.on_bi.bi_db_init = refint_db_init;
+       refint.on_bi.bi_db_config = refint_config;
+       refint.on_bi.bi_db_open = refint_open;
+       refint.on_bi.bi_db_close = refint_close;
+       refint.on_response = refint_response;
+
+       return(overlay_register(&refint));
+}
+
+#if SLAPD_OVER_REFINT == SLAPD_MOD_DYNAMIC && defined(PIC)
+int init_module(int argc, char *argv[]) {
+       return refint_init();
+}
+#endif
+
+#endif /* SLAPD_OVER_REFINT */
diff --git a/servers/slapd/overlays/unique.c b/servers/slapd/overlays/unique.c
new file mode 100644 (file)
index 0000000..4fe528d
--- /dev/null
@@ -0,0 +1,669 @@
+/* unique.c - attribute uniqueness module */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004 The OpenLDAP Foundation.
+ * Portions Copyright 2004 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Symas Corp. for inclusion in
+ * OpenLDAP Software.  This work was sponsored by Hewlett-Packard.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_UNIQUE
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+
+static slap_overinst unique;
+
+typedef struct unique_attrs_s {
+       struct unique_attrs_s *next;            /* list of attrs */
+       AttributeDescription *attr;
+} unique_attrs;
+
+typedef struct unique_data_s {
+       const char *message;                    /* breadcrumbs */
+       struct unique_attrs_s *attrs;           /* list of known attrs */
+       struct unique_attrs_s *ignore;          /* list of ignored attrs */
+       BerValue dn;                            /* base of "unique tree" */
+       char strict;                            /* null considered unique too */
+} unique_data;
+
+typedef struct unique_counter_s {
+       int count;
+} unique_counter;
+
+/*
+** allocate new unique_data;
+** initialize, copy basedn;
+** store in on_bi.bi_private;
+**
+*/
+
+static int unique_db_init(
+       BackendDB       *be
+)
+{
+       slap_overinst *on = (slap_overinst *)be->bd_info;
+       unique_data *ud   = ch_malloc(sizeof(unique_data));
+       unique_attrs *up;
+
+       /* Debug(LDAP_DEBUG_TRACE, "==> unique_init\n", 0, 0, 0); */
+
+       ud->message     = "_init";
+       ud->attrs       = NULL;
+       ud->ignore      = NULL;
+       ud->strict      = 0;
+
+       /* default to the base of our configured database */
+       ber_dupbv(&ud->dn, &be->be_nsuffix[0]);
+       on->on_bi.bi_private = ud;
+}
+
+
+/*
+** if command = attributes:
+**     foreach argument:
+**             convert to attribute;
+**             add to configured attribute list;
+** elseif command = base:
+**     set our basedn to argument;
+** else complain about invalid directive;
+**
+*/
+
+static int unique_config(
+       BackendDB       *be,
+       const char      *fname,
+       int             lineno,
+       int             argc,
+       char            **argv
+)
+{
+       slap_overinst *on = (slap_overinst *) be->bd_info;
+       unique_data *ud   = on->on_bi.bi_private;
+       unique_attrs *up;
+       const char *text;
+       AttributeDescription *ad;
+       int i;
+
+       ud->message = "_config";
+       Debug(LDAP_DEBUG_TRACE, "==> unique_config\n", 0, 0, 0);
+
+       if(!strcasecmp(*argv, "unique_attributes") ||
+          !strcasecmp(*argv, "unique_ignore")) {
+               for(i = 1; i < argc; i++) {
+                       for(up = ud->attrs; up; up = up->next)
+                           if(!strcmp(argv[i], up->attr->ad_cname.bv_val)) {
+                               Debug(LDAP_DEBUG_ANY,
+                                       "%s: line %d: duplicate attribute <s>, ignored\n",
+                                       fname, lineno, argv[i]);
+                               continue;
+                       }
+                       ad = NULL;
+                       if(slap_str2ad(argv[i], &ad, &text) != LDAP_SUCCESS) {
+                               Debug(LDAP_DEBUG_ANY,
+                                       "%s: line %d: bad attribute <%s>, ignored\n",
+                                       fname, lineno, text);
+                               continue;               /* XXX */
+                       } else if(ad->ad_next) {
+                               Debug(LDAP_DEBUG_ANY,
+                                       "%s: line %d: multiple attributes match <%s>, ignored\n",
+                                       fname, lineno, argv[i]);
+                               continue;
+                       }
+                       up = ch_malloc(sizeof(unique_attrs));
+                       up->attr = ad;
+                       if(!strcasecmp(*argv, "unique_ignore")) {
+                               up->next = ud->ignore;
+                               ud->ignore = up;
+                       } else {
+                               up->next = ud->attrs;
+                               ud->attrs = up;
+                       }
+                       Debug(LDAP_DEBUG_ANY, "%s: line %d: new attribute <%s>\n",
+                               fname, lineno, argv[i]);
+               }
+       } else if(!strcasecmp(*argv, "unique_strict")) {
+               ud->strict = 1;
+       } else if(!strcasecmp(*argv, "unique_base")) {
+               struct berval bv;
+               ber_str2bv( argv[1], 0, 0, &bv );
+               ch_free(ud->dn.bv_val);
+               dnNormalize(0, NULL, NULL, &bv, &ud->dn, NULL);
+               Debug(LDAP_DEBUG_ANY, "%s: line %d: new base dn <%s>\n",
+                       fname, lineno, argv[1]);
+       } else {
+               return(SLAP_CONF_UNKNOWN);
+       }
+
+       return(0);
+}
+
+
+/*
+** mostly, just print the init message;
+**
+*/
+
+static int
+unique_open(
+       BackendDB *be
+)
+{
+       slap_overinst *on       = (slap_overinst *)be->bd_info;
+       unique_data *ud         = on->on_bi.bi_private;
+       ud->message             = "_open";
+
+       Debug(LDAP_DEBUG_TRACE, "unique_open: overlay initialized\n", 0, 0, 0);
+
+       return(0);
+}
+
+
+/*
+** foreach configured attribute:
+**     free it;
+** free our basedn;
+** (do not) free ud->message;
+** reset on_bi.bi_private;
+** free our config data;
+**
+*/
+
+static int
+unique_close(
+       BackendDB *be
+)
+{
+       slap_overinst *on       = (slap_overinst *) be->bd_info;
+       unique_data *ud         = on->on_bi.bi_private;
+       unique_attrs *ii, *ij;
+       ud->message             = "_close";
+
+       Debug(LDAP_DEBUG_TRACE, "==> unique_close\n", 0, 0, 0);
+
+       for(ii = ud->attrs; ii; ii = ij) {
+               ij = ii->next;
+               ch_free(ii);
+       }
+
+       for(ii = ud->ignore; ii; ii = ij) {
+               ij = ii->next;
+               ch_free(ii);
+       }
+
+       ch_free(ud->dn.bv_val);
+
+       on->on_bi.bi_private = NULL;    /* XXX */
+
+       ch_free(ud);
+
+       return(0);
+}
+
+
+/*
+** search callback
+**     if this is a REP_SEARCH, count++;
+**
+*/
+
+static int count_attr_cb(
+       Operation *op,
+       SlapReply *rs
+)
+{
+       /* because you never know */
+       if(!op || !rs) return(0);
+
+       /* Only search entries are interesting */
+       if(rs->sr_type != REP_SEARCH) return(0);
+
+       Debug(LDAP_DEBUG_TRACE, "==> count_attr_cb <%s>\n",
+               rs->sr_entry ? rs->sr_entry->e_name.bv_val : "UNKNOWN_DN", 0, 0);
+
+       ((unique_counter*)op->o_callback->sc_private)->count++;
+
+       return(0);
+}
+
+/* XXX extraneous (slap_response*) to avoid compiler warning */
+
+static int unique_add(
+       Operation *op,
+       SlapReply *rs
+)
+{
+       Operation nop = *op;
+       SlapReply nrs = { REP_RESULT };
+       slap_callback cb = { NULL, NULL, NULL, NULL }; /* XXX */
+       slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+
+       Attribute *a;
+       AttributeDescription *st;
+       BerVarray b = NULL;
+       char *fstr, *key, *kp;
+       const char *why;
+       int i, rc, ks = 16;
+       unique_attrs *up;
+       unique_counter uq = { 0 };
+       unique_data *ud = on->on_bi.bi_private;
+
+       Debug(LDAP_DEBUG_TRACE, "==> unique_add <%s>\n", op->o_req_dn.bv_val, 0, 0);
+
+       /* validate backend. Should have already been done, but whatever */
+       nop.o_bd = select_backend(&ud->dn, 0, 1);
+       if(nop.o_bd) {
+               if (!nop.o_bd->be_search) {
+                       op->o_bd->bd_info = (BackendInfo *) on->on_info;
+                       send_ldap_error(op, rs, LDAP_UNWILLING_TO_PERFORM,
+                       "backend missing search function");
+                       return(rs->sr_err);
+               }
+       } else {
+               op->o_bd->bd_info = (BackendInfo *) on->on_info;
+               send_ldap_error(op, rs, LDAP_OTHER,
+                       "no known backend? this shouldn't be happening!");
+               return(rs->sr_err);
+       }
+
+/*
+** count everything first;
+** allocate some memory;
+** write the search key;
+**
+*/
+
+       if(!(a = op->ora_e->e_attrs)) {
+               op->o_bd->bd_info = (BackendInfo *) on->on_info;
+               send_ldap_error(op, rs, LDAP_INVALID_SYNTAX,
+                       "unique_add() got null op.ora_e.e_attrs");
+               return(rs->sr_err);
+       } else for(; a; a = a->a_next) {
+               if(is_at_operational(a->a_desc->ad_type)) continue;
+               if(ud->ignore) {
+                       for(up = ud->ignore; up; up = up->next)
+                               if(a->a_desc == up->attr) break;
+                       if(up) continue;
+               }
+               if(ud->attrs) {
+                       for(up = ud->attrs; up; up = up->next)
+                               if(a->a_desc == up->attr) break;
+                       if(!up) continue;
+               }
+               if((b = a->a_vals) && b[0].bv_val) for(i = 0; b[i].bv_val; i++)
+                       ks += b[i].bv_len + a->a_desc->ad_cname.bv_len + 3;
+               else if(ud->strict)
+                       ks += a->a_desc->ad_cname.bv_len + 4;   /* (attr=*) */
+       }
+
+       key = ch_malloc(ks);
+
+       kp = key + sprintf(key, "(|");
+
+       for(a = op->ora_e->e_attrs; a; a = a->a_next) {
+               if(is_at_operational(a->a_desc->ad_type)) continue;
+               if(ud->ignore) {
+                       for(up = ud->ignore; up; up = up->next)
+                               if(a->a_desc == up->attr) break;
+                       if(up) continue;
+               }
+               if(ud->attrs) {
+                       for(up = ud->attrs; up; up = up->next)
+                               if(a->a_desc == up->attr) break;
+                       if(!up) continue;
+               }
+               if((b = a->a_vals) && b[0].bv_val) for(i = 0; b[i].bv_val; i++)
+                       kp += sprintf(kp, "(%s=%s)", a->a_desc->ad_cname.bv_val, b[i].bv_val);
+               else if(ud->strict)
+                       kp += sprintf(kp, "(%s=*)", a->a_desc->ad_cname.bv_val);
+       }
+
+       kp += sprintf(kp, ")");
+
+       Debug(LDAP_DEBUG_TRACE, "=> unique_add %s\n", key, 0, 0);
+
+       nop.ors_filter = str2filter_x(&nop, key);
+       ber_str2bv(key, 0, 0, &nop.ors_filterstr);
+
+       cb.sc_response  = (slap_response*)count_attr_cb;
+       cb.sc_private   = &uq;
+       nop.o_callback  = &cb;
+       nop.o_tag       = LDAP_REQ_SEARCH;
+       nop.ors_scope   = LDAP_SCOPE_SUBTREE;
+       nop.ors_deref   = LDAP_DEREF_NEVER;
+       nop.ors_slimit  = -1;
+       nop.ors_tlimit  = -1;
+       nop.o_req_ndn   = ud->dn;
+       nop.o_ndn = op->o_bd->be_rootndn;
+
+       rc = nop.o_bd->be_search(&nop, &nrs);
+       filter_free_x(&nop, nop.ors_filter);
+       ch_free( key );
+
+       if(rc != LDAP_SUCCESS && rc != LDAP_NO_SUCH_OBJECT) {
+               op->o_bd->bd_info = (BackendInfo *) on->on_info;
+               send_ldap_error(op, rs, rc, "unique_add search failed");
+               return(rs->sr_err);
+       }
+
+       Debug(LDAP_DEBUG_TRACE, "=> unique_add found %d records\n", uq.count, 0, 0);
+
+       if(uq.count) {
+               op->o_bd->bd_info = (BackendInfo *) on->on_info;
+               send_ldap_error(op, rs, LDAP_CONSTRAINT_VIOLATION,
+                       "some attributes not unique");
+               return(rs->sr_err);
+       }
+
+       return(SLAP_CB_CONTINUE);
+}
+
+
+static int unique_modify(
+       Operation *op,
+       SlapReply *rs
+)
+{
+       Operation nop = *op;
+       SlapReply nrs = { REP_RESULT };
+       slap_callback cb = { NULL, (slap_response*)count_attr_cb, NULL, NULL };
+       slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+
+       Attribute *a;
+       AttributeDescription *st;
+       BerVarray b = NULL;
+       Modifications *m;
+       char *fstr, *key, *kp;
+       const char *why;
+       int i, rc, ks = 16;             /* a handful of extra bytes */
+       unique_attrs *up;
+       unique_counter uq = { 0 };
+       unique_data *ud = on->on_bi.bi_private;
+
+       Debug(LDAP_DEBUG_TRACE, "==> unique_modify <%s>\n", op->o_req_dn.bv_val, 0, 0);
+
+       nop.o_bd = select_backend(&ud->dn, 0, 1);
+       if(nop.o_bd) {
+               if (!nop.o_bd->be_search) {
+                       op->o_bd->bd_info = (BackendInfo *) on->on_info;
+                       send_ldap_error(op, rs, LDAP_UNWILLING_TO_PERFORM,
+                       "backend missing search function");
+                       return(rs->sr_err);
+               }
+       } else {
+               op->o_bd->bd_info = (BackendInfo *) on->on_info;
+               send_ldap_error(op, rs, LDAP_OTHER,
+                       "no known backend? this shouldn't be happening!");
+               return(rs->sr_err);
+       }
+
+/*
+** count everything first;
+** allocate some memory;
+** write the search key;
+**
+*/
+
+       if(!(m = op->orm_modlist)) {
+               op->o_bd->bd_info = (BackendInfo *) on->on_info;
+               send_ldap_error(op, rs, LDAP_INVALID_SYNTAX,
+                       "unique_modify() got null op.orm_modlist");
+               return(rs->sr_err);
+       } else for(; m; m = m->sml_next) {
+               if(is_at_operational(m->sml_desc->ad_type) ||
+                       ((m->sml_op & LDAP_MOD_OP) == LDAP_MOD_DELETE)) continue;
+               if(ud->ignore) {
+                       for(up = ud->ignore; up; up = up->next)
+                               if(m->sml_desc == up->attr) break;
+                       if(up) continue;
+               }
+               if(ud->attrs) {
+                       for(up = ud->attrs; up; up = up->next)
+                               if(m->sml_desc == up->attr) break;
+                       if(!up) continue;
+               }
+               if((b = m->sml_values) && b[0].bv_val) for(i = 0; b[i].bv_val; i++)
+                       ks += b[i].bv_len + m->sml_desc->ad_cname.bv_len + 3;
+               else if(ud->strict)
+                       ks += m->sml_desc->ad_cname.bv_len + 4; /* (attr=*) */
+       }
+
+       key = ch_malloc(ks);
+
+       kp = key + sprintf(key, "(|");
+
+       for(m = op->orm_modlist; m; m = m->sml_next) {
+               if(is_at_operational(m->sml_desc->ad_type) ||
+                       ((m->sml_op & LDAP_MOD_OP) == LDAP_MOD_DELETE)) continue;
+               if(ud->ignore) {
+                       for(up = ud->ignore; up; up = up->next)
+                               if(m->sml_desc == up->attr) break;
+                       if(up) continue;
+               }
+               if(ud->attrs) {
+                       for(up = ud->attrs; up; up = up->next)
+                               if(m->sml_desc == up->attr) break;
+                       if(!up) continue;
+               }
+               if((b = m->sml_values) && b[0].bv_val) for(i = 0; b[i].bv_val; i++)
+                       kp += sprintf(kp, "(%s=%s)", m->sml_desc->ad_cname.bv_val, b[i].bv_val);
+               else if(ud->strict)
+                       kp += sprintf(kp, "(%s=*)", m->sml_desc->ad_cname.bv_val);
+       }
+
+       kp += sprintf(kp, ")");
+
+       Debug(LDAP_DEBUG_TRACE, "=> unique_modify %s\n", key, 0, 0);
+
+       nop.ors_filter = str2filter_x(&nop, key);
+       ber_str2bv(key, 0, 0, &nop.ors_filterstr);
+
+       cb.sc_response  = (slap_response*)count_attr_cb;
+       cb.sc_private   = &uq;
+       nop.o_callback  = &cb;
+       nop.o_tag       = LDAP_REQ_SEARCH;
+       nop.ors_scope   = LDAP_SCOPE_SUBTREE;
+       nop.ors_deref   = LDAP_DEREF_NEVER;
+       nop.ors_slimit  = -1;
+       nop.ors_tlimit  = -1;
+       nop.o_req_ndn   = ud->dn;
+       nop.o_ndn = op->o_bd->be_rootndn;
+
+       rc = nop.o_bd->be_search(&nop, &nrs);
+       ch_free( key );
+
+       if(rc != LDAP_SUCCESS && rc != LDAP_NO_SUCH_OBJECT) {
+               op->o_bd->bd_info = (BackendInfo *) on->on_info;
+               send_ldap_error(op, rs, rc, "unique_modify search failed");
+               return(rs->sr_err);
+       }
+
+       Debug(LDAP_DEBUG_TRACE, "=> unique_modify found %d records\n", uq.count, 0, 0);
+
+       if(uq.count) {
+               op->o_bd->bd_info = (BackendInfo *) on->on_info;
+               send_ldap_error(op, rs, LDAP_CONSTRAINT_VIOLATION,
+                       "some attributes not unique");
+               return(rs->sr_err);
+       }
+
+       return(SLAP_CB_CONTINUE);
+
+}
+
+
+static int unique_modrdn(
+       Operation *op,
+       SlapReply *rs
+)
+{
+       Operation nop = *op;
+       SlapReply nrs = { REP_RESULT };
+       slap_callback cb = { NULL, (slap_response*)count_attr_cb, NULL, NULL };
+       slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+
+       char *fstr, *key, *kp;
+       const char *why;
+       int i, rc, ks = 16;             /* a handful of extra bytes */
+       unique_attrs *up;
+       unique_counter uq = { 0 };
+       unique_data *ud = on->on_bi.bi_private;
+       LDAPRDN newrdn;
+
+       Debug(LDAP_DEBUG_TRACE, "==> unique_modrdn <%s> <%s>\n",
+               op->o_req_dn.bv_val, op->orr_newrdn.bv_val, 0);
+
+       nop.o_bd = select_backend(&ud->dn, 0, 1);
+       if(nop.o_bd) {
+               if (!nop.o_bd->be_search) {
+                       op->o_bd->bd_info = (BackendInfo *) on->on_info;
+                       send_ldap_error(op, rs, LDAP_UNWILLING_TO_PERFORM,
+                       "backend missing search function");
+                       return(rs->sr_err);
+               }
+       } else {
+               op->o_bd->bd_info = (BackendInfo *) on->on_info;
+               send_ldap_error(op, rs, LDAP_OTHER,
+                       "no known backend? this shouldn't be happening!");
+               return(rs->sr_err);
+       }
+
+       if(ldap_bv2rdn_x(&op->oq_modrdn.rs_newrdn, &newrdn,
+               (char **)&rs->sr_text, LDAP_DN_FORMAT_LDAP, op->o_tmpmemctx )) {
+               op->o_bd->bd_info = (BackendInfo *) on->on_info;
+               send_ldap_error(op, rs, LDAP_INVALID_SYNTAX,
+                       "unknown type(s) used in RDN");
+               return(rs->sr_err);
+       }
+       for(i = 0; newrdn[i]; i++) {
+               AttributeDescription *ad = NULL;
+               if ( slap_bv2ad( &newrdn[i]->la_attr, &ad, &rs->sr_text )) {
+                       ldap_rdnfree_x( newrdn, op->o_tmpmemctx );
+                       rs->sr_err = LDAP_INVALID_SYNTAX;
+                       send_ldap_result( op, rs );
+                       return(rs->sr_err);
+               }
+               newrdn[i]->la_private = ad;
+       }
+
+       for(i = 0; newrdn[i]; i++) {
+               AttributeDescription *ad = newrdn[i]->la_private;
+               if(ud->ignore) {
+                       for(up = ud->ignore; up; up = up->next)
+                               if(ad == up->attr) break;
+                       if(up) continue;
+               }
+               if(ud->attrs) {
+                       for(up = ud->attrs; up; up = up->next)
+                               if(ad == up->attr) break;
+                       if(!up) continue;
+               }
+               ks += newrdn[i]->la_value.bv_len + ad->ad_cname.bv_len + 3;
+       }
+
+       key = ch_malloc(ks);
+       kp = key + sprintf(key, "(|");
+
+       for(i = 0; newrdn[i]; i++) {
+               AttributeDescription *ad = newrdn[i]->la_private;
+               if(ud->ignore) {
+                       for(up = ud->ignore; up; up = up->next)
+                               if(ad == up->attr) break;
+                       if(up) continue;
+               }
+               if(ud->attrs) {
+                       for(up = ud->attrs; up; up = up->next)
+                               if(ad == up->attr) break;
+                       if(!up) continue;
+               }
+               kp += sprintf(kp, "(%s=%s)", ad->ad_cname.bv_val,
+                       newrdn[i]->la_value.bv_val);
+       }
+
+       kp += sprintf(kp, ")");
+
+
+       Debug(LDAP_DEBUG_TRACE, "=> unique_modrdn %s\n", key, 0, 0);
+
+       nop.ors_filter = str2filter_x(&nop, key);
+       ber_str2bv(key, 0, 0, &nop.ors_filterstr);
+
+       cb.sc_response  = (slap_response*)count_attr_cb;
+       cb.sc_private   = &uq;
+       nop.o_callback  = &cb;
+       nop.o_tag       = LDAP_REQ_SEARCH;
+       nop.ors_scope   = LDAP_SCOPE_SUBTREE;
+       nop.ors_deref   = LDAP_DEREF_NEVER;
+       nop.ors_slimit  = -1;
+       nop.ors_tlimit  = -1;
+       nop.o_req_ndn   = ud->dn;
+       nop.o_ndn = op->o_bd->be_rootndn;
+
+       rc = nop.o_bd->be_search(&nop, &nrs);
+       ch_free( key );
+       ldap_rdnfree_x( newrdn, op->o_tmpmemctx );
+
+       if(rc != LDAP_SUCCESS && rc != LDAP_NO_SUCH_OBJECT) {
+               op->o_bd->bd_info = (BackendInfo *) on->on_info;
+               send_ldap_error(op, rs, rc, "unique_modrdn search failed");
+               return(rs->sr_err);
+       }
+
+       Debug(LDAP_DEBUG_TRACE, "=> unique_modrdn found %d records\n", uq.count, 0, 0);
+
+       if(uq.count) {
+               op->o_bd->bd_info = (BackendInfo *) on->on_info;
+               send_ldap_error(op, rs, LDAP_CONSTRAINT_VIOLATION,
+                       "some attributes not unique");
+               return(rs->sr_err);
+       }
+
+       return(SLAP_CB_CONTINUE);
+}
+
+/*
+** init_module is last so the symbols resolve "for free" --
+** it expects to be called automagically during dynamic module initialization
+*/
+
+int unique_init() {
+
+       /* statically declared just after the #includes at top */
+       unique.on_bi.bi_type = "unique";
+       unique.on_bi.bi_db_init = unique_db_init;
+       unique.on_bi.bi_db_config = unique_config;
+       unique.on_bi.bi_db_open = unique_open;
+       unique.on_bi.bi_db_close = unique_close;
+       unique.on_bi.bi_op_add = unique_add;
+       unique.on_bi.bi_op_modify = unique_modify;
+       unique.on_bi.bi_op_modrdn = unique_modrdn;
+       unique.on_bi.bi_op_delete = NULL;
+
+       return(overlay_register(&unique));
+}
+
+#if SLAPD_OVER_UNIQUE == SLAPD_MOD_DYNAMIC && defined(PIC)
+int init_module(int argc, char *argv[]) {
+       return unique_init();
+}
+#endif
+
+#endif /* SLAPD_OVER_UNIQUE */