From 8da6bf194a0970346fd02a8a1ac96ed39a619683 Mon Sep 17 00:00:00 2001 From: Howard Chu Date: Sat, 17 Apr 2004 08:17:43 +0000 Subject: [PATCH] Added referential integrity and attribute uniqueness overlays --- doc/man/man5/slapo-refint.5 | 53 +++ doc/man/man5/slapo-unique.5 | 93 ++++ servers/slapd/overlays/Makefile.in | 10 + servers/slapd/overlays/refint.c | 675 +++++++++++++++++++++++++++++ servers/slapd/overlays/unique.c | 669 ++++++++++++++++++++++++++++ 5 files changed, 1500 insertions(+) create mode 100644 doc/man/man5/slapo-refint.5 create mode 100644 doc/man/man5/slapo-unique.5 create mode 100644 servers/slapd/overlays/refint.c create mode 100644 servers/slapd/overlays/unique.c diff --git a/doc/man/man5/slapo-refint.5 b/doc/man/man5/slapo-refint.5 new file mode 100644 index 0000000000..803e00ccb1 --- /dev/null +++ b/doc/man/man5/slapo-refint.5 @@ -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 +Specify one or more attributes which for which integrity will be maintained +as described above. +.TP +.B refint_nothing +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 index 0000000000..8a10269450 --- /dev/null +++ b/doc/man/man5/slapo-unique.5 @@ -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 +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 +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 +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). diff --git a/servers/slapd/overlays/Makefile.in b/servers/slapd/overlays/Makefile.in index 71f26f88e0..1a201973d1 100644 --- a/servers/slapd/overlays/Makefile.in +++ b/servers/slapd/overlays/Makefile.in @@ -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 index 0000000000..d97f49dce4 --- /dev/null +++ b/servers/slapd/overlays/refint.c @@ -0,0 +1,675 @@ +/* refint.c - referential integrity module */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * 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 + * . + */ +/* 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 + +#include +#include + +#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 , 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 = ⅆ + 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 index 0000000000..4fe528d8df --- /dev/null +++ b/servers/slapd/overlays/unique.c @@ -0,0 +1,669 @@ +/* unique.c - attribute uniqueness module */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * 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 + * . + */ +/* 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 + +#include +#include + +#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 , 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 */ -- 2.39.5