Added referential integrity and attribute uniqueness overlays

This commit is contained in:
Howard Chu 2004-04-17 08:17:43 +00:00
parent 71921f219c
commit 8da6bf194a
5 changed files with 1500 additions and 0 deletions

View File

@ -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).

View File

@ -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).

View 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)

View File

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

View File

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