Commit 8da6bf19 authored by Howard Chu's avatar Howard Chu
Browse files

Added referential integrity and attribute uniqueness overlays

parent 71921f21
.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).
.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).
......@@ -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)
......
/* 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;