diff options
Diffstat (limited to 'contrib/slapd-modules/autogroup')
-rw-r--r-- | contrib/slapd-modules/autogroup/Makefile | 46 | ||||
-rw-r--r-- | contrib/slapd-modules/autogroup/README | 120 | ||||
-rw-r--r-- | contrib/slapd-modules/autogroup/autogroup.c | 2234 | ||||
-rw-r--r-- | contrib/slapd-modules/autogroup/slapo-autogroup.5 | 102 |
4 files changed, 2502 insertions, 0 deletions
diff --git a/contrib/slapd-modules/autogroup/Makefile b/contrib/slapd-modules/autogroup/Makefile new file mode 100644 index 0000000..efd9678 --- /dev/null +++ b/contrib/slapd-modules/autogroup/Makefile @@ -0,0 +1,46 @@ +# $OpenLDAP$ + +LDAP_SRC = ../../.. +LDAP_BUILD = $(LDAP_SRC) +LDAP_INC = -I$(LDAP_BUILD)/include -I$(LDAP_SRC)/include -I$(LDAP_SRC)/servers/slapd +LDAP_LIB = $(LDAP_BUILD)/libraries/libldap_r/libldap_r.la \ + $(LDAP_BUILD)/libraries/liblber/liblber.la + +LIBTOOL = $(LDAP_BUILD)/libtool +CC = gcc +OPT = -g -O2 -Wall +DEFS = +INCS = $(LDAP_INC) +LIBS = $(LDAP_LIB) + +PROGRAMS = autogroup.la +LTVER = 0:0:0 + +prefix=/usr/local +exec_prefix=$(prefix) +ldap_subdir=/openldap + +libdir=$(exec_prefix)/lib +libexecdir=$(exec_prefix)/libexec +moduledir = $(libexecdir)$(ldap_subdir) + +.SUFFIXES: .c .o .lo + +.c.lo: + $(LIBTOOL) --mode=compile $(CC) $(OPT) $(DEFS) $(INCS) -c $< + +all: $(PROGRAMS) + +autogroup.la: autogroup.lo + $(LIBTOOL) --mode=link $(CC) $(OPT) -version-info $(LTVER) \ + -rpath $(moduledir) -module -o $@ $? $(LIBS) + +clean: + rm -rf *.o *.lo *.la .libs + +install: $(PROGRAMS) + mkdir -p $(DESTDIR)$(moduledir) + for p in $(PROGRAMS) ; do \ + $(LIBTOOL) --mode=install cp $$p $(DESTDIR)$(moduledir) ; \ + done + diff --git a/contrib/slapd-modules/autogroup/README b/contrib/slapd-modules/autogroup/README new file mode 100644 index 0000000..be32282 --- /dev/null +++ b/contrib/slapd-modules/autogroup/README @@ -0,0 +1,120 @@ +autogroup overlay Readme + +DESCRIPTION + The autogroup overlay allows automated updates of group memberships which + meet the requirements of any filter contained in the group definition. + The filters are built from LDAP URI-valued attributes. Any time an object + is added/deleted/updated, it is tested for compliance with the filters, + and its membership is accordingly updated. For searches and compares + it behaves like a static group. + If the attribute part of the URI is filled, the group entry is populated + by the values of this attribute in the entries resulting from the search. + +BUILDING + A Makefile is included. + +CONFIGURATION + # dyngroup.schema: + The dyngroup schema must be modified, adding the 'member' attribute + to the MAY clause of the groupOfURLs object class, i.e.: + + objectClass ( NetscapeLDAPobjectClass:33 + NAME 'groupOfURLs' + SUP top STRUCTURAL + MUST cn + MAY ( memberURL $ businessCategory $ description $ o $ ou $ + owner $ seeAlso $ member) ) + + + # slapd.conf: + + moduleload /path/to/autogroup.so + Loads the overlay (OpenLDAP must be built with --enable-modules). + + overlay autogroup + This directive adds the autogroup overlay to the current database. + + autogroup-attrset <group-oc> <URL-ad> <member-ad> + This configuration option is defined for the autogroup overlay. + It may have multiple occurrences, and it must appear after the + overlay directive. + + The value <group-oc> is the name of the objectClass that represents + the group. + + The value <URL-ad> is the name of the attributeDescription that + contains the URI that is converted to the filters. If no URI is + present, there will be no members in that group. It must be a subtype + of labeledURI. + + The value <member-ad> is the name of the attributeDescription that + specifies the member attribute. User modification of this attribute + is disabled for consistency. + + autogroup-memberof-ad <memberof-ad> + This configuration option is defined for the autogroup overlay. + + It defines the attribute that is used by the memberOf overlay + to store the names of groups that an entry is member of; it must be + DN-valued. It should be set to the same value as + memberof-memberof-ad. It defaults to 'memberOf'. + + +EXAMPLE + ### slapd.conf + include /path/to/dyngroup.schema + # ... + moduleload /path/to/autogroup.so + # ... + + database <database> + # ... + + overlay autogroup + autogroup-attrset groupOfURLs memberURL member + ### end slapd.conf + + ### slapd.conf + include /path/to/dyngroup.schema + # ... + moduleload /path/to/autogroup.so + moduleload /path/to/memberof.so + # ... + + database <database> + #... + + overlay memberof + memberof-memberof-ad foo + + overlay autogroup + autogroup-attrset groupOfURLs memberURL member + autogroup-memberof-ad foo + ### end slapd.conf + +CAVEATS + As with static groups, update operations on groups with a large number + of members may be slow. + If the attribute part of the URI is specified, modify and delete operations + are more difficult to handle. In these cases the overlay will try to detect + if groups have been modified and then simply refresh them. This can cause + performance hits if the search specified by the URI deals with a significant + number of entries. + +ACKNOWLEDGEMENTS + This module was originally written in 2007 by Michał Szulczyński. Further + enhancements were contributed by Howard Chu, Raphael Ouazana, + Norbert Pueschel, and Christian Manal. + +--- +Copyright 1998-2021 The OpenLDAP Foundation. +Portions Copyright (C) 2007 Michał Szulczyński. +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 file LICENSE in the +top-level directory of the distribution or, alternatively, at +http://www.OpenLDAP.org/license.html. diff --git a/contrib/slapd-modules/autogroup/autogroup.c b/contrib/slapd-modules/autogroup/autogroup.c new file mode 100644 index 0000000..8caf082 --- /dev/null +++ b/contrib/slapd-modules/autogroup/autogroup.c @@ -0,0 +1,2234 @@ +/* autogroup.c - automatic group overlay */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2007-2021 The OpenLDAP Foundation. + * Portions Copyright 2007 Michał Szulczyński. + * Portions Copyright 2009 Howard Chu. + * 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 Michał Szulczyński for inclusion in + * OpenLDAP Software. Additional significant contributors include: + * Howard Chu + * Raphael Ouazana + * Norbert Pueschel + * Christian Manal + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> + +#include "slap.h" +#include "config.h" +#include "lutil.h" + +#ifndef SLAPD_MEMBEROF_ATTR +#define SLAPD_MEMBEROF_ATTR "memberOf" +#endif + +static slap_overinst autogroup; + +/* Filter represents the memberURL of a group. */ +typedef struct autogroup_filter_t { + struct berval agf_dn; /* The base DN in memberURL */ + struct berval agf_ndn; + struct berval agf_filterstr; + Filter *agf_filter; + int agf_scope; + AttributeName *agf_anlist; + struct autogroup_filter_t *agf_next; +} autogroup_filter_t; + +/* Description of group attributes. */ +typedef struct autogroup_def_t { + ObjectClass *agd_oc; + AttributeDescription *agd_member_url_ad; + AttributeDescription *agd_member_ad; + struct autogroup_def_t *agd_next; +} autogroup_def_t; + +/* Represents the group entry. */ +typedef struct autogroup_entry_t { + BerValue age_dn; + BerValue age_ndn; + autogroup_filter_t *age_filter; /* List of filters made from memberURLs */ + autogroup_def_t *age_def; /* Attribute definition */ + ldap_pvt_thread_mutex_t age_mutex; + int age_mustrefresh; /* Defined in request to refresh in response */ + int age_modrdn_olddnmodified; /* Defined in request to refresh in response */ + struct autogroup_entry_t *age_next; +} autogroup_entry_t; + +/* Holds pointers to attribute definitions and groups. */ +typedef struct autogroup_info_t { + autogroup_def_t *agi_def; /* Group attributes definitions. */ + autogroup_entry_t *agi_entry; /* Group entries. */ + AttributeDescription *agi_memberof_ad; /* memberOf attribute description */ + ldap_pvt_thread_mutex_t agi_mutex; +} autogroup_info_t; + +/* Search callback for adding groups initially. */ +typedef struct autogroup_sc_t { + autogroup_info_t *ags_info; /* Group definitions and entries. */ + autogroup_def_t *ags_def; /* Attributes definition of the group being added. */ +} autogroup_sc_t; + +/* Used for adding members, found when searching, to a group. */ +typedef struct autogroup_ga_t { + autogroup_entry_t *agg_group; /* The group to which the members will be added. */ + autogroup_filter_t *agg_filter; /* Current filter */ + Entry *agg_entry; /* Used in autogroup_member_search_cb to modify + this entry with the search results. */ + + Modifications *agg_mod; /* Used in autogroup_member_search_modify_cb to hold the + search results which will be added to the group. */ + + Modifications *agg_mod_last; /* Used in autogroup_member_search_modify_cb so we don't + have to search for the last mod added. */ +} autogroup_ga_t; + + +/* +** dn, ndn - the DN of the member to add +** age - the group to which the member DN will be added +*/ +static int +autogroup_add_member_to_group( Operation *op, BerValue *dn, BerValue *ndn, autogroup_entry_t *age ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + Modifications *modlist = (Modifications *)ch_calloc( 1, sizeof( Modifications ) ); + SlapReply sreply = {REP_RESULT}; + BerValue *vals, *nvals; + slap_callback cb = { NULL, slap_null_cb, NULL, NULL }; + Operation o = *op; + unsigned long opid = op->o_opid; + OpExtra oex; + + assert( dn != NULL ); + assert( ndn != NULL ); + Debug(LDAP_DEBUG_TRACE, "==> autogroup_add_member_to_group adding <%s> to <%s>\n", + dn->bv_val, age->age_dn.bv_val, 0); + + vals = (BerValue *)ch_calloc( 2, sizeof( BerValue ) ); + nvals = (BerValue *)ch_calloc( 2, sizeof( BerValue ) ); + ber_dupbv( vals, dn ); + BER_BVZERO( &vals[ 1 ] ); + ber_dupbv( nvals, ndn ); + BER_BVZERO( &nvals[ 1 ] ); + + modlist->sml_op = LDAP_MOD_ADD; + modlist->sml_desc = age->age_def->agd_member_ad; + modlist->sml_type = age->age_def->agd_member_ad->ad_cname; + modlist->sml_values = vals; + modlist->sml_nvalues = nvals; + modlist->sml_numvals = 1; + modlist->sml_flags = SLAP_MOD_INTERNAL; + modlist->sml_next = NULL; + + o.o_opid = 0; /* shared with op, saved above */ + o.o_tag = LDAP_REQ_MODIFY; + o.o_callback = &cb; + o.orm_modlist = modlist; + o.o_dn = op->o_bd->be_rootdn; + o.o_ndn = op->o_bd->be_rootndn; + o.o_req_dn = age->age_dn; + o.o_req_ndn = age->age_ndn; + o.o_permissive_modify = 1; + o.o_dont_replicate = 1; + o.orm_no_opattrs = 1; + o.o_managedsait = SLAP_CONTROL_CRITICAL; + o.o_relax = SLAP_CONTROL_CRITICAL; + + oex.oe_key = (void *)&autogroup; + LDAP_SLIST_INSERT_HEAD( &o.o_extra, &oex, oe_next ); + + o.o_bd->bd_info = (BackendInfo *)on->on_info; + (void)op->o_bd->be_modify( &o, &sreply ); + o.o_bd->bd_info = (BackendInfo *)on; + + LDAP_SLIST_REMOVE( &o.o_extra, &oex, OpExtra, oe_next ); + + slap_mods_free( modlist, 1 ); + op->o_opid = opid; + + return sreply.sr_err; +} + +/* +** e - the entry where to get the attribute values +** age - the group to which the values will be added +*/ +static int +autogroup_add_member_values_to_group( Operation *op, struct berval *dn, autogroup_entry_t *age, Attribute *attr ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + Modifications modlist; + SlapReply sreply = {REP_RESULT}; + slap_callback cb = { NULL, slap_null_cb, NULL, NULL }; + Operation o = *op; + unsigned long opid = op->o_opid; + OpExtra oex; + + Debug(LDAP_DEBUG_TRACE, "==> autogroup_add_member_values_to_group adding <%s> to <%s>\n", + dn->bv_val, age->age_dn.bv_val, 0); + + modlist.sml_op = LDAP_MOD_ADD; + modlist.sml_desc = age->age_def->agd_member_ad; + modlist.sml_type = age->age_def->agd_member_ad->ad_cname; + modlist.sml_values = attr->a_vals; + modlist.sml_nvalues = attr->a_nvals; + modlist.sml_numvals = attr->a_numvals; + modlist.sml_flags = SLAP_MOD_INTERNAL; + modlist.sml_next = NULL; + + o.o_opid = 0; + o.o_tag = LDAP_REQ_MODIFY; + o.o_callback = &cb; + o.orm_modlist = &modlist; + o.o_dn = op->o_bd->be_rootdn; + o.o_ndn = op->o_bd->be_rootndn; + o.o_req_dn = age->age_dn; + o.o_req_ndn = age->age_ndn; + o.o_permissive_modify = 1; + o.o_dont_replicate = 1; + o.orm_no_opattrs = 1; + o.o_managedsait = SLAP_CONTROL_CRITICAL; + o.o_relax = SLAP_CONTROL_CRITICAL; + + oex.oe_key = (void *)&autogroup; + LDAP_SLIST_INSERT_HEAD( &o.o_extra, &oex, oe_next ); + + o.o_bd->bd_info = (BackendInfo *)on->on_info; + (void)op->o_bd->be_modify( &o, &sreply ); + o.o_bd->bd_info = (BackendInfo *)on; + op->o_opid = opid; + LDAP_SLIST_REMOVE( &o.o_extra, &oex, OpExtra, oe_next ); + + return sreply.sr_err; +} + +/* +** dn,ndn - the DN to be deleted +** age - the group from which the DN will be deleted +** If we pass a NULL dn and ndn, all members are deleted from the group. +*/ +static int +autogroup_delete_member_from_group( Operation *op, BerValue *dn, BerValue *ndn, autogroup_entry_t *age ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + Modifications *modlist = (Modifications *)ch_calloc( 1, sizeof( Modifications ) ); + SlapReply sreply = {REP_RESULT}; + BerValue *vals, *nvals; + slap_callback cb = { NULL, slap_null_cb, NULL, NULL }; + Operation o = *op; + unsigned long opid = op->o_opid; + OpExtra oex; + + if ( dn == NULL || ndn == NULL ) { + Debug(LDAP_DEBUG_TRACE, "==> autogroup_delete_member_from_group removing all members from <%s>\n", + age->age_dn.bv_val, 0 ,0); + + modlist->sml_values = NULL; + modlist->sml_nvalues = NULL; + modlist->sml_numvals = 0; + } else { + Debug(LDAP_DEBUG_TRACE, "==> autogroup_delete_member_from_group removing <%s> from <%s>\n", + dn->bv_val, age->age_dn.bv_val, 0); + + vals = (BerValue *)ch_calloc( 2, sizeof( BerValue ) ); + nvals = (BerValue *)ch_calloc( 2, sizeof( BerValue ) ); + ber_dupbv( vals, dn ); + BER_BVZERO( &vals[ 1 ] ); + ber_dupbv( nvals, ndn ); + BER_BVZERO( &nvals[ 1 ] ); + + modlist->sml_values = vals; + modlist->sml_nvalues = nvals; + modlist->sml_numvals = 1; + } + + + modlist->sml_op = LDAP_MOD_DELETE; + modlist->sml_desc = age->age_def->agd_member_ad; + modlist->sml_type = age->age_def->agd_member_ad->ad_cname; + modlist->sml_flags = SLAP_MOD_INTERNAL; + modlist->sml_next = NULL; + + o.o_opid = 0; + o.o_callback = &cb; + o.o_tag = LDAP_REQ_MODIFY; + o.orm_modlist = modlist; + o.o_dn = op->o_bd->be_rootdn; + o.o_ndn = op->o_bd->be_rootndn; + o.o_req_dn = age->age_dn; + o.o_req_ndn = age->age_ndn; + o.o_relax = SLAP_CONTROL_CRITICAL; + o.o_managedsait = SLAP_CONTROL_CRITICAL; + o.o_permissive_modify = 1; + o.o_dont_replicate = 1; + o.orm_no_opattrs = 1; + + oex.oe_key = (void *)&autogroup; + LDAP_SLIST_INSERT_HEAD( &o.o_extra, &oex, oe_next ); + + o.o_bd->bd_info = (BackendInfo *)on->on_info; + (void)op->o_bd->be_modify( &o, &sreply ); + o.o_bd->bd_info = (BackendInfo *)on; + + LDAP_SLIST_REMOVE( &o.o_extra, &oex, OpExtra, oe_next ); + + slap_mods_free( modlist, 1 ); + + op->o_opid = opid; + return sreply.sr_err; +} + +/* +** e - the entry where to get the attribute values +** age - the group from which the values will be deleted +*/ +static int +autogroup_delete_member_values_from_group( Operation *op, struct berval *dn, autogroup_entry_t *age, Attribute *attr ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + Modifications modlist; + SlapReply sreply = {REP_RESULT}; + slap_callback cb = { NULL, slap_null_cb, NULL, NULL }; + Operation o = *op; + unsigned long opid = op->o_opid; + OpExtra oex; + + Debug(LDAP_DEBUG_TRACE, "==> autogroup_delete_member_values_from_group removing <%s> from <%s>\n", + dn->bv_val, age->age_dn.bv_val, 0); + + modlist.sml_op = LDAP_MOD_DELETE; + modlist.sml_desc = age->age_def->agd_member_ad; + modlist.sml_type = age->age_def->agd_member_ad->ad_cname; + modlist.sml_values = attr->a_vals; + modlist.sml_nvalues = attr->a_nvals; + modlist.sml_numvals = attr->a_numvals; + modlist.sml_flags = SLAP_MOD_INTERNAL; + modlist.sml_next = NULL; + + o.o_opid = 0; + o.o_tag = LDAP_REQ_MODIFY; + o.o_callback = &cb; + o.orm_modlist = &modlist; + o.o_dn = op->o_bd->be_rootdn; + o.o_ndn = op->o_bd->be_rootndn; + o.o_req_dn = age->age_dn; + o.o_req_ndn = age->age_ndn; + o.o_permissive_modify = 1; + o.o_dont_replicate = 1; + o.orm_no_opattrs = 1; + o.o_managedsait = SLAP_CONTROL_CRITICAL; + o.o_relax = SLAP_CONTROL_CRITICAL; + + oex.oe_key = (void *)&autogroup; + LDAP_SLIST_INSERT_HEAD( &o.o_extra, &oex, oe_next ); + + o.o_bd->bd_info = (BackendInfo *)on->on_info; + (void)op->o_bd->be_modify( &o, &sreply ); + o.o_bd->bd_info = (BackendInfo *)on; + op->o_opid = opid; + + LDAP_SLIST_REMOVE( &o.o_extra, &oex, OpExtra, oe_next ); + + return sreply.sr_err; +} + +/* +** Callback used to add entries to a group, +** which are going to be written in the database +** (used in bi_op_add) +** The group is passed in autogroup_ga_t->agg_group +*/ +static int +autogroup_member_search_cb( Operation *op, SlapReply *rs ) +{ + assert( op->o_tag == LDAP_REQ_SEARCH ); + + if ( rs->sr_type == REP_SEARCH ) { + autogroup_ga_t *agg = (autogroup_ga_t *)op->o_callback->sc_private; + autogroup_entry_t *age = agg->agg_group; + autogroup_filter_t *agf = agg->agg_filter; + Modification mod; + const char *text = NULL; + char textbuf[1024]; + struct berval *vals, *nvals; + struct berval lvals[ 2 ], lnvals[ 2 ]; + int numvals; + + Debug(LDAP_DEBUG_TRACE, "==> autogroup_member_search_cb <%s>\n", + rs->sr_entry ? rs->sr_entry->e_name.bv_val : "UNKNOWN_DN", 0, 0); + + if ( agf->agf_anlist ) { + Attribute *attr = attrs_find( rs->sr_entry->e_attrs, agf->agf_anlist[0].an_desc ); + if (attr) { + vals = attr->a_vals; + nvals = attr->a_nvals; + numvals = attr->a_numvals; + } else { + // Nothing to add + return 0; + } + } else { + lvals[ 0 ] = rs->sr_entry->e_name; + BER_BVZERO( &lvals[ 1 ] ); + lnvals[ 0 ] = rs->sr_entry->e_nname; + BER_BVZERO( &lnvals[ 1 ] ); + vals = lvals; + nvals = lnvals; + numvals = 1; + } + + mod.sm_op = LDAP_MOD_ADD; + mod.sm_desc = age->age_def->agd_member_ad; + mod.sm_type = age->age_def->agd_member_ad->ad_cname; + mod.sm_values = vals; + mod.sm_nvalues = nvals; + mod.sm_numvals = numvals; + + modify_add_values( agg->agg_entry, &mod, /* permissive */ 1, &text, textbuf, sizeof( textbuf ) ); + } + + return 0; +} + +/* +** Callback used to add entries to a group, which is already in the database. +** (used in on_response) +** The group is passed in autogroup_ga_t->agg_group +** NOTE: Very slow. +*/ +static int +autogroup_member_search_modify_cb( Operation *op, SlapReply *rs ) +{ + assert( op->o_tag == LDAP_REQ_SEARCH ); + + if ( rs->sr_type == REP_SEARCH ) { + autogroup_ga_t *agg = (autogroup_ga_t *)op->o_callback->sc_private; + autogroup_entry_t *age = agg->agg_group; + autogroup_filter_t *agf = agg->agg_filter; + Modifications *modlist; + struct berval *vals, *nvals; + struct berval lvals[ 2 ], lnvals[ 2 ]; + int numvals; + + Debug(LDAP_DEBUG_TRACE, "==> autogroup_member_search_modify_cb <%s>\n", + rs->sr_entry ? rs->sr_entry->e_name.bv_val : "UNKNOWN_DN", 0, 0); + + if ( agf->agf_anlist ) { + Attribute *attr = attrs_find( rs->sr_entry->e_attrs, agf->agf_anlist[0].an_desc ); + if (attr) { + vals = attr->a_vals; + nvals = attr->a_nvals; + numvals = attr->a_numvals; + } else { + // Nothing to add + return 0; + } + } else { + lvals[ 0 ] = rs->sr_entry->e_name; + BER_BVZERO( &lvals[ 1 ] ); + lnvals[ 0 ] = rs->sr_entry->e_nname; + BER_BVZERO( &lnvals[ 1 ] ); + vals = lvals; + nvals = lnvals; + numvals = 1; + } + + if ( numvals ) { + modlist = (Modifications *)ch_calloc( 1, sizeof( Modifications ) ); + + modlist->sml_op = LDAP_MOD_ADD; + modlist->sml_desc = age->age_def->agd_member_ad; + modlist->sml_type = age->age_def->agd_member_ad->ad_cname; + + ber_bvarray_dup_x( &modlist->sml_values, vals, NULL ); + ber_bvarray_dup_x( &modlist->sml_nvalues, nvals, NULL ); + modlist->sml_numvals = numvals; + + modlist->sml_flags = SLAP_MOD_INTERNAL; + modlist->sml_next = NULL; + + if ( agg->agg_mod == NULL ) { + agg->agg_mod = modlist; + agg->agg_mod_last = modlist; + } else { + agg->agg_mod_last->sml_next = modlist; + agg->agg_mod_last = modlist; + } + } + + } + + return 0; +} + + +/* +** Adds all entries matching the passed filter to the specified group. +** If modify == 1, then we modify the group's entry in the database using be_modify. +** If modify == 0, then, we must supply a rw entry for the group, +** because we only modify the entry, without calling be_modify. +** e - the group entry, to which the members will be added +** age - the group +** agf - the filter +*/ +static int +autogroup_add_members_from_filter( Operation *op, Entry *e, autogroup_entry_t *age, autogroup_filter_t *agf, int modify) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + Operation o = *op; + SlapReply rs = { REP_SEARCH }; + slap_callback cb = { 0 }; + slap_callback null_cb = { NULL, slap_null_cb, NULL, NULL }; + autogroup_ga_t agg; + OpExtra oex; + + Debug(LDAP_DEBUG_TRACE, "==> autogroup_add_members_from_filter <%s>\n", + age->age_dn.bv_val, 0, 0); + + o.ors_attrsonly = 0; + o.o_tag = LDAP_REQ_SEARCH; + + o.o_dn = op->o_bd->be_rootdn; + o.o_ndn = op->o_bd->be_rootndn; + o.o_req_dn = agf->agf_dn; + o.o_req_ndn = agf->agf_ndn; + + o.ors_filterstr = agf->agf_filterstr; + o.ors_filter = agf->agf_filter; + + o.ors_scope = agf->agf_scope; + o.ors_deref = LDAP_DEREF_NEVER; + o.ors_limit = NULL; + o.ors_tlimit = SLAP_NO_LIMIT; + o.ors_slimit = SLAP_NO_LIMIT; + o.ors_attrs = agf->agf_anlist ? agf->agf_anlist : slap_anlist_no_attrs; + o.o_do_not_cache = 1; + + agg.agg_group = age; + agg.agg_filter = agf; + agg.agg_mod = NULL; + agg.agg_mod_last = NULL; + agg.agg_entry = e; + cb.sc_private = &agg; + + if ( modify == 1 ) { + cb.sc_response = autogroup_member_search_modify_cb; + } else { + cb.sc_response = autogroup_member_search_cb; + } + + cb.sc_cleanup = NULL; + cb.sc_next = NULL; + + o.o_callback = &cb; + + o.o_bd->bd_info = (BackendInfo *)on->on_info; + op->o_bd->be_search( &o, &rs ); + o.o_bd->bd_info = (BackendInfo *)on; + + if ( modify == 1 && agg.agg_mod ) { + unsigned long opid = op->o_opid; + + rs_reinit( &rs, REP_RESULT ); + + o = *op; + o.o_opid = 0; + o.o_callback = &null_cb; + o.o_tag = LDAP_REQ_MODIFY; + o.orm_modlist = agg.agg_mod; + o.o_dn = op->o_bd->be_rootdn; + o.o_ndn = op->o_bd->be_rootndn; + o.o_req_dn = age->age_dn; + o.o_req_ndn = age->age_ndn; + o.o_relax = SLAP_CONTROL_CRITICAL; + o.o_managedsait = SLAP_CONTROL_NONCRITICAL; + o.o_permissive_modify = 1; + o.o_dont_replicate = 1; + o.orm_no_opattrs = 1; + + oex.oe_key = (void *)&autogroup; + LDAP_SLIST_INSERT_HEAD( &o.o_extra, &oex, oe_next ); + + o.o_bd->bd_info = (BackendInfo *)on->on_info; + (void)op->o_bd->be_modify( &o, &rs ); + o.o_bd->bd_info = (BackendInfo *)on; + + LDAP_SLIST_REMOVE( &o.o_extra, &oex, OpExtra, oe_next ); + + slap_mods_free(agg.agg_mod, 1); + op->o_opid = opid; + } + + return 0; +} + +/* +** Adds a group to the internal list from the passed entry. +** scan specifies whether to add all maching members to the group. +** modify specifies whether to modify the given group entry (when modify == 0), +** or to modify the group entry in the database (when modify == 1 and e = NULL and ndn != NULL). +** agi - pointer to the groups and the attribute definitions +** agd - the attribute definition of the added group +** e - the entry representing the group, can be NULL if the ndn is specified, and modify == 1 +** ndn - the DN of the group, can be NULL if we give a non-NULL e +*/ +static int +autogroup_add_group( Operation *op, autogroup_info_t *agi, autogroup_def_t *agd, Entry *e, BerValue *ndn, int scan, int modify) +{ + autogroup_entry_t **agep = &agi->agi_entry; + autogroup_filter_t *agf, *agf_prev = NULL; + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + LDAPURLDesc *lud = NULL; + Attribute *a; + BerValue *bv, dn; + int rc = 0, match = 1, null_entry = 0; + + if ( e == NULL ) { + if ( overlay_entry_get_ov( op, ndn, NULL, NULL, 0, &e, on ) != + LDAP_SUCCESS || e == NULL ) { + Debug( LDAP_DEBUG_TRACE, "autogroup_add_group: cannot get entry for <%s>\n", ndn->bv_val, 0, 0); + return 1; + } + + null_entry = 1; + } + + Debug(LDAP_DEBUG_TRACE, "==> autogroup_add_group <%s>\n", + e->e_name.bv_val, 0, 0); + + if ( agi->agi_entry != NULL ) { + for ( ; *agep ; agep = &(*agep)->age_next ) { + dnMatch( &match, 0, NULL, NULL, &e->e_nname, &(*agep)->age_ndn ); + if ( match == 0 ) { + Debug( LDAP_DEBUG_TRACE, "autogroup_add_group: group already exists: <%s>\n", e->e_name.bv_val,0,0); + return 1; + } + /* goto last */; + } + } + + + *agep = (autogroup_entry_t *)ch_calloc( 1, sizeof( autogroup_entry_t ) ); + ldap_pvt_thread_mutex_init( &(*agep)->age_mutex ); + (*agep)->age_def = agd; + (*agep)->age_filter = NULL; + (*agep)->age_mustrefresh = 0; + (*agep)->age_modrdn_olddnmodified = 0; + + ber_dupbv( &(*agep)->age_dn, &e->e_name ); + ber_dupbv( &(*agep)->age_ndn, &e->e_nname ); + + a = attrs_find( e->e_attrs, agd->agd_member_url_ad ); + + if ( null_entry == 1 ) { + a = attrs_dup( a ); + overlay_entry_release_ov( op, e, 0, on ); + } + + if( a == NULL ) { + Debug( LDAP_DEBUG_TRACE, "autogroup_add_group: group has no memberURL\n", 0,0,0); + } else { + for ( bv = a->a_nvals; !BER_BVISNULL( bv ); bv++ ) { + + agf = (autogroup_filter_t*)ch_calloc( 1, sizeof( autogroup_filter_t ) ); + + if ( ldap_url_parse( bv->bv_val, &lud ) != LDAP_URL_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "autogroup_add_group: cannot parse url <%s>\n", bv->bv_val,0,0); + /* FIXME: error? */ + ch_free( agf ); + continue; + } + + agf->agf_scope = lud->lud_scope; + + if ( lud->lud_dn == NULL ) { + BER_BVSTR( &dn, "" ); + } else { + ber_str2bv( lud->lud_dn, 0, 0, &dn ); + } + + rc = dnPrettyNormal( NULL, &dn, &agf->agf_dn, &agf->agf_ndn, NULL ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "autogroup_add_group: cannot normalize DN <%s>\n", dn.bv_val,0,0); + /* FIXME: error? */ + goto cleanup; + } + + if ( lud->lud_filter != NULL ) { + ber_str2bv( lud->lud_filter, 0, 1, &agf->agf_filterstr); + agf->agf_filter = str2filter( lud->lud_filter ); + } else { + Debug( LDAP_DEBUG_TRACE, "autogroup_add_group: URL filter is missing <%s>\n", bv->bv_val,0,0); + /* FIXME: error? */ + goto cleanup; + } + + if ( lud->lud_attrs != NULL ) { + int i; + + for ( i=0 ; lud->lud_attrs[i]!=NULL ; i++) { + /* Just counting */; + } + + if ( i > 1 ) { + Debug( LDAP_DEBUG_ANY, "autogroup_add_group: too many attributes specified in url <%s>\n", + bv->bv_val, 0, 0); + /* FIXME: error? */ + filter_free( agf->agf_filter ); + ch_free( agf->agf_filterstr.bv_val ); + ch_free( agf->agf_dn.bv_val ); + ch_free( agf->agf_ndn.bv_val ); + ldap_free_urldesc( lud ); + ch_free( agf ); + continue; + } + + agf->agf_anlist = str2anlist( NULL, lud->lud_attrs[0], "," ); + + if ( agf->agf_anlist == NULL ) { + Debug( LDAP_DEBUG_ANY, "autogroup_add_group: unable to find AttributeDescription \"%s\".\n", + lud->lud_attrs[0], 0, 0 ); + /* FIXME: error? */ + filter_free( agf->agf_filter ); + ch_free( agf->agf_filterstr.bv_val ); + ch_free( agf->agf_dn.bv_val ); + ch_free( agf->agf_ndn.bv_val ); + ldap_free_urldesc( lud ); + ch_free( agf ); + continue; + } + } + + agf->agf_next = NULL; + + if( (*agep)->age_filter == NULL ) { + (*agep)->age_filter = agf; + } + + if( agf_prev != NULL ) { + agf_prev->agf_next = agf; + } + + agf_prev = agf; + + if ( scan == 1 ){ + autogroup_add_members_from_filter( op, e, (*agep), agf, modify ); + } + + Debug( LDAP_DEBUG_TRACE, "autogroup_add_group: added memberURL DN <%s> with filter <%s>\n", + agf->agf_ndn.bv_val, agf->agf_filterstr.bv_val, 0); + + ldap_free_urldesc( lud ); + + continue; + + +cleanup:; + + ch_free( agf->agf_ndn.bv_val ); + ch_free( agf->agf_dn.bv_val ); + ldap_free_urldesc( lud ); + ch_free( agf ); + } + } + + if ( null_entry == 1 ) { + attrs_free( a ); + } + return rc; +} + +/* +** Used when opening the database to add all existing +** groups from the database to our internal list. +*/ +static int +autogroup_group_add_cb( Operation *op, SlapReply *rs ) +{ + assert( op->o_tag == LDAP_REQ_SEARCH ); + + if ( rs->sr_type == REP_SEARCH ) { + autogroup_sc_t *ags = (autogroup_sc_t *)op->o_callback->sc_private; + + Debug(LDAP_DEBUG_TRACE, "==> autogroup_group_add_cb <%s>\n", + rs->sr_entry ? rs->sr_entry->e_name.bv_val : "UNKNOWN_DN", 0, 0); + + autogroup_add_group( op, ags->ags_info, ags->ags_def, rs->sr_entry, NULL, 0, 0); + } + + return 0; +} + +typedef struct ag_addinfo { + slap_overinst *on; + Entry *e; + autogroup_def_t *agd; +} ag_addinfo; + +static int +autogroup_add_entry_cb( Operation *op, SlapReply *rs ) +{ + slap_callback *sc = op->o_callback; + ag_addinfo *aa = sc->sc_private; + slap_overinst *on = aa->on; + autogroup_info_t *agi = (autogroup_info_t *)on->on_bi.bi_private; + BackendInfo *bi = op->o_bd->bd_info; + + if ( rs->sr_err != LDAP_SUCCESS ) + goto done; + + op->o_bd->bd_info = (BackendInfo *)on; + ldap_pvt_thread_mutex_lock( &agi->agi_mutex ); + if ( aa->agd ) { + autogroup_add_group( op, agi, aa->agd, aa->e, NULL, 1 , 0); + } else { + autogroup_entry_t *age; + autogroup_filter_t *agf; + struct berval odn, ondn; + int rc; + + /* must use rootdn when calling test_filter */ + odn = op->o_dn; + ondn = op->o_ndn; + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + + for ( age = agi->agi_entry; age ; age = age->age_next ) { + ldap_pvt_thread_mutex_lock( &age->age_mutex ); + + /* Check if any of the filters are the suffix to the entry DN. + If yes, we can test that filter against the entry. */ + + for ( agf = age->age_filter; agf ; agf = agf->agf_next ) { + if ( dnIsSuffix( &op->o_req_ndn, &agf->agf_ndn ) ) { + rc = test_filter( op, aa->e, agf->agf_filter ); + if ( rc == LDAP_COMPARE_TRUE ) { + if ( agf->agf_anlist ) { + Attribute *a = attr_find( aa->e->e_attrs, agf->agf_anlist[0].an_desc ); + if ( a ) + autogroup_add_member_values_to_group( op, &op->o_req_dn, age, a ); + } else { + autogroup_add_member_to_group( op, &aa->e->e_name, &aa->e->e_nname, age ); + } + break; + } + } + } + ldap_pvt_thread_mutex_unlock( &age->age_mutex ); + } + op->o_dn = odn; + op->o_ndn = ondn; + } + ldap_pvt_thread_mutex_unlock( &agi->agi_mutex ); + + op->o_bd->bd_info = bi; + +done: + op->o_callback = sc->sc_next; + op->o_tmpfree( sc, op->o_tmpmemctx ); + + return SLAP_CB_CONTINUE; +} + +/* +** When adding a group, we first strip any existing members, +** and add all which match the filters ourselfs. +*/ +static int +autogroup_add_entry( Operation *op, SlapReply *rs) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + autogroup_info_t *agi = (autogroup_info_t *)on->on_bi.bi_private; + autogroup_def_t *agd = agi->agi_def; + slap_callback *sc = NULL; + ag_addinfo *aa = NULL; + + Debug( LDAP_DEBUG_TRACE, "==> autogroup_add_entry <%s>\n", + op->ora_e->e_name.bv_val, 0, 0); + + sc = op->o_tmpcalloc( sizeof(slap_callback) + sizeof(ag_addinfo), 1, op->o_tmpmemctx ); + sc->sc_private = (sc+1); + sc->sc_response = autogroup_add_entry_cb; + aa = sc->sc_private; + aa->on = on; + aa->e = op->ora_e; + sc->sc_next = op->o_callback; + op->o_callback = sc; + + /* Check if it's a group. */ + for ( ; agd ; agd = agd->agd_next ) { + if ( is_entry_objectclass_or_sub( op->ora_e, agd->agd_oc ) ) { + Modification mod; + const char *text = NULL; + char textbuf[1024]; + + mod.sm_op = LDAP_MOD_DELETE; + mod.sm_desc = agd->agd_member_ad; + mod.sm_type = agd->agd_member_ad->ad_cname; + mod.sm_values = NULL; + mod.sm_nvalues = NULL; + + /* We don't want any member attributes added by the user. */ + modify_delete_values( op->ora_e, &mod, /* permissive */ 1, &text, textbuf, sizeof( textbuf ) ); + + aa->agd = agd; + + break; + } + } + + return SLAP_CB_CONTINUE; +} + +/* +** agi - internal group and attribute definitions list +** e - the group to remove from the internal list +*/ +static int +autogroup_delete_group( autogroup_info_t *agi, autogroup_entry_t *e ) +{ + autogroup_entry_t *age = agi->agi_entry, + *age_prev = NULL, + *age_next; + int rc = 1; + + Debug( LDAP_DEBUG_TRACE, "==> autogroup_delete_group <%s>\n", + age->age_dn.bv_val, 0, 0); + + for ( age_next = age ; age_next ; age_prev = age, age = age_next ) { + age_next = age->age_next; + + if ( age == e ) { + autogroup_filter_t *agf = age->age_filter, + *agf_next; + + if ( age_prev != NULL ) { + age_prev->age_next = age_next; + } else { + agi->agi_entry = NULL; + } + + ch_free( age->age_dn.bv_val ); + ch_free( age->age_ndn.bv_val ); + + for( agf_next = agf ; agf_next ; agf = agf_next ){ + agf_next = agf->agf_next; + + filter_free( agf->agf_filter ); + ch_free( agf->agf_filterstr.bv_val ); + ch_free( agf->agf_dn.bv_val ); + ch_free( agf->agf_ndn.bv_val ); + anlist_free( agf->agf_anlist, 1, NULL ); + ch_free( agf ); + } + + ldap_pvt_thread_mutex_unlock( &age->age_mutex ); + ldap_pvt_thread_mutex_destroy( &age->age_mutex ); + ch_free( age ); + + rc = 0; + return rc; + + } + } + + Debug( LDAP_DEBUG_TRACE, "autogroup_delete_group: group <%s> not found, should not happen\n", age->age_dn.bv_val, 0, 0); + + return rc; + +} + +static int +autogroup_delete_entry( Operation *op, SlapReply *rs) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + autogroup_info_t *agi = (autogroup_info_t *)on->on_bi.bi_private; + autogroup_entry_t *age, *age_prev, *age_next; + autogroup_filter_t *agf; + Entry *e; + int matched_group = 0, rc = 0; + struct berval odn, ondn; + OpExtra *oex; + + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + if ( oex->oe_key == (void *)&autogroup ) + return SLAP_CB_CONTINUE; + } + + Debug( LDAP_DEBUG_TRACE, "==> autogroup_delete_entry <%s>\n", op->o_req_dn.bv_val, 0, 0); + + ldap_pvt_thread_mutex_lock( &agi->agi_mutex ); + + if ( overlay_entry_get_ov( op, &op->o_req_ndn, NULL, NULL, 0, &e, on ) != + LDAP_SUCCESS || e == NULL ) { + Debug( LDAP_DEBUG_TRACE, "autogroup_delete_entry: cannot get entry for <%s>\n", op->o_req_dn.bv_val, 0, 0); + ldap_pvt_thread_mutex_unlock( &agi->agi_mutex ); + return SLAP_CB_CONTINUE; + } + + /* Check if the entry to be deleted is one of our groups. */ + for ( age_next = agi->agi_entry ; age_next ; age_prev = age ) { + age = age_next; + ldap_pvt_thread_mutex_lock( &age->age_mutex ); + age_next = age->age_next; + + if ( is_entry_objectclass_or_sub( e, age->age_def->agd_oc ) ) { + int match = 1; + + matched_group = 1; + + dnMatch( &match, 0, NULL, NULL, &e->e_nname, &age->age_ndn ); + + if ( match == 0 ) { + autogroup_delete_group( agi, age ); + break; + } + } + + ldap_pvt_thread_mutex_unlock( &age->age_mutex ); + } + + if ( matched_group == 1 ) { + overlay_entry_release_ov( op, e, 0, on ); + ldap_pvt_thread_mutex_unlock( &agi->agi_mutex ); + return SLAP_CB_CONTINUE; + } + + /* Check if the entry matches any of the groups. + If yes, we can delete the entry from that group. */ + + odn = op->o_dn; + ondn = op->o_ndn; + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + + for ( age = agi->agi_entry ; age ; age = age->age_next ) { + ldap_pvt_thread_mutex_lock( &age->age_mutex ); + + for ( agf = age->age_filter; agf ; agf = agf->agf_next ) { + if ( dnIsSuffix( &op->o_req_ndn, &agf->agf_ndn ) ) { + rc = test_filter( op, e, agf->agf_filter ); + if ( rc == LDAP_COMPARE_TRUE ) { + /* If the attribute is retrieved from the entry, we don't know what to delete + ** So the group must be entirely refreshed + ** But the refresh can't be done now because the entry is not deleted + ** So the group is marked as mustrefresh + */ + if ( agf->agf_anlist ) { + age->age_mustrefresh = 1; + } else { + autogroup_delete_member_from_group( op, &e->e_name, &e->e_nname, age ); + } + break; + } + } + } + ldap_pvt_thread_mutex_unlock( &age->age_mutex ); + } + op->o_dn = odn; + op->o_ndn = ondn; + + overlay_entry_release_ov( op, e, 0, on ); + ldap_pvt_thread_mutex_unlock( &agi->agi_mutex ); + + return SLAP_CB_CONTINUE; +} + +static int +autogroup_response( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + autogroup_info_t *agi = (autogroup_info_t *)on->on_bi.bi_private; + autogroup_def_t *agd = agi->agi_def; + autogroup_entry_t *age; + autogroup_filter_t *agf; + BerValue new_dn, new_ndn, pdn; + Entry *e, *group; + Attribute *a, *ea, *attrs; + int is_olddn, is_newdn, is_value_refresh, dn_equal; + OpExtra *oex; + + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + if ( oex->oe_key == (void *)&autogroup ) + break; + } + + /* Handle all cases where a refresh of the group is needed */ + if ( op->o_tag == LDAP_REQ_DELETE || op->o_tag == LDAP_REQ_MODIFY ) { + if ( rs->sr_type == REP_RESULT && rs->sr_err == LDAP_SUCCESS && !oex ) { + + ldap_pvt_thread_mutex_lock( &agi->agi_mutex ); + + for ( age = agi->agi_entry ; age ; age = age->age_next ) { + /* Request detected that the group must be refreshed */ + + ldap_pvt_thread_mutex_lock( &age->age_mutex ); + + if ( age->age_mustrefresh ) { + autogroup_delete_member_from_group( op, NULL, NULL, age) ; + + for ( agf = age->age_filter ; agf ; agf = agf->agf_next ) { + autogroup_add_members_from_filter( op, NULL, age, agf, 1 ); + } + } + + ldap_pvt_thread_mutex_unlock( &age->age_mutex ); + } + + ldap_pvt_thread_mutex_unlock( &agi->agi_mutex ); + } + } else if ( op->o_tag == LDAP_REQ_MODRDN ) { + if ( rs->sr_type == REP_RESULT && rs->sr_err == LDAP_SUCCESS && !oex ) { + + Debug( LDAP_DEBUG_TRACE, "==> autogroup_response MODRDN from <%s>\n", op->o_req_dn.bv_val, 0, 0); + + ldap_pvt_thread_mutex_lock( &agi->agi_mutex ); + + if ( op->oq_modrdn.rs_newSup ) { + pdn = *op->oq_modrdn.rs_newSup; + } else { + dnParent( &op->o_req_dn, &pdn ); + } + build_new_dn( &new_dn, &pdn, &op->orr_newrdn, op->o_tmpmemctx ); + + if ( op->oq_modrdn.rs_nnewSup ) { + pdn = *op->oq_modrdn.rs_nnewSup; + } else { + dnParent( &op->o_req_ndn, &pdn ); + } + build_new_dn( &new_ndn, &pdn, &op->orr_nnewrdn, op->o_tmpmemctx ); + + Debug( LDAP_DEBUG_TRACE, "autogroup_response MODRDN to <%s>\n", new_dn.bv_val, 0, 0); + + dnMatch( &dn_equal, 0, NULL, NULL, &op->o_req_ndn, &new_ndn ); + + if ( overlay_entry_get_ov( op, &new_ndn, NULL, NULL, 0, &e, on ) != + LDAP_SUCCESS || e == NULL ) { + Debug( LDAP_DEBUG_TRACE, "autogroup_response MODRDN cannot get entry for <%s>\n", new_dn.bv_val, 0, 0); + ldap_pvt_thread_mutex_unlock( &agi->agi_mutex ); + return SLAP_CB_CONTINUE; + } + + a = attrs_find( e->e_attrs, slap_schema.si_ad_objectClass ); + + + if ( a == NULL ) { + Debug( LDAP_DEBUG_TRACE, "autogroup_response MODRDN entry <%s> has no objectClass\n", new_dn.bv_val, 0, 0); + overlay_entry_release_ov( op, e, 0, on ); + ldap_pvt_thread_mutex_unlock( &agi->agi_mutex ); + return SLAP_CB_CONTINUE; + } + + + /* If a groups DN is modified, just update age_dn/ndn of that group with the new DN. */ + for ( ; agd; agd = agd->agd_next ) { + + if ( value_find_ex( slap_schema.si_ad_objectClass, + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH | + SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH, + a->a_nvals, &agd->agd_oc->soc_cname, + op->o_tmpmemctx ) == 0 ) + { + for ( age = agi->agi_entry ; age ; age = age->age_next ) { + int match = 1; + + dnMatch( &match, 0, NULL, NULL, &age->age_ndn, &op->o_req_ndn ); + if ( match == 0 ) { + Debug( LDAP_DEBUG_TRACE, "autogroup_response MODRDN updating group's DN to <%s>\n", new_dn.bv_val, 0, 0); + ber_dupbv( &age->age_dn, &new_dn ); + ber_dupbv( &age->age_ndn, &new_ndn ); + + op->o_tmpfree( new_dn.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( new_ndn.bv_val, op->o_tmpmemctx ); + overlay_entry_release_ov( op, e, 0, on ); + ldap_pvt_thread_mutex_unlock( &agi->agi_mutex ); + return SLAP_CB_CONTINUE; + } + } + + } + } + + /* For each group: + 1. check if the orginal entry's DN is in the group. + 2. chceck if the any of the group filter's base DN is a suffix of the new DN + + If 1 and 2 are both false, we do nothing. + If 1 and 2 is true, we remove the old DN from the group, and add the new DN. + If 1 is false, and 2 is true, we check the entry against the group's filters, + and add it's DN to the group. + If 1 is true, and 2 is false, we delete the entry's DN from the group. + */ + attrs = attrs_dup( e->e_attrs ); + overlay_entry_release_ov( op, e, 0, on ); + for ( age = agi->agi_entry ; age ; age = age->age_next ) { + is_olddn = 0; + is_newdn = 0; + is_value_refresh = 0; + + ldap_pvt_thread_mutex_lock( &age->age_mutex ); + + if ( age->age_filter && age->age_filter->agf_anlist ) { + ea = attrs_find( attrs, age->age_filter->agf_anlist[0].an_desc ); + } + else { + ea = NULL; + } + + if ( age->age_modrdn_olddnmodified ) { + /* Resquest already marked this group to be updated */ + is_olddn = 1; + is_value_refresh = 1; + age->age_modrdn_olddnmodified = 0; + } else { + + if ( overlay_entry_get_ov( op, &age->age_ndn, NULL, NULL, 0, &group, on ) != + LDAP_SUCCESS || group == NULL ) { + Debug( LDAP_DEBUG_TRACE, "autogroup_response MODRDN cannot get group entry <%s>\n", age->age_dn.bv_val, 0, 0); + + op->o_tmpfree( new_dn.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( new_ndn.bv_val, op->o_tmpmemctx ); + + attrs_free( attrs ); + ldap_pvt_thread_mutex_unlock( &age->age_mutex ); + ldap_pvt_thread_mutex_unlock( &agi->agi_mutex ); + return SLAP_CB_CONTINUE; + } + + a = attrs_find( group->e_attrs, age->age_def->agd_member_ad ); + + if ( a != NULL ) { + if ( value_find_ex( age->age_def->agd_member_ad, + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH | + SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH, + a->a_nvals, ea ? ea->a_nvals : &op->o_req_ndn, op->o_tmpmemctx ) == 0 ) + { + is_olddn = 1; + } + + } + + overlay_entry_release_ov( op, group, 0, on ); + + } + + for ( agf = age->age_filter ; agf ; agf = agf->agf_next ) { + if ( dnIsSuffix( &new_ndn, &agf->agf_ndn ) ) { + /* TODO: should retest filter as it could imply conditions on the dn */ + is_newdn = 1; + break; + } + } + + + if ( is_value_refresh ) { + if ( is_olddn != is_newdn ) { + /* group refresh */ + autogroup_delete_member_from_group( op, NULL, NULL, age) ; + + for ( agf = age->age_filter ; agf ; agf = agf->agf_next ) { + autogroup_add_members_from_filter( op, NULL, age, agf, 1 ); + } + } + ldap_pvt_thread_mutex_unlock( &age->age_mutex ); + continue; + } + if ( is_olddn == 1 && is_newdn == 0 ) { + if ( ea ) + autogroup_delete_member_values_from_group( op, &new_dn, age, ea ); + else + autogroup_delete_member_from_group( op, &op->o_req_dn, &op->o_req_ndn, age ); + } else + if ( is_olddn == 0 && is_newdn == 1 ) { + Entry etmp; + struct berval odn, ondn; + etmp.e_name = op->o_req_dn; + etmp.e_nname = op->o_req_ndn; + etmp.e_attrs = attrs; + odn = op->o_dn; + ondn = op->o_ndn; + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + + for ( agf = age->age_filter; agf; agf = agf->agf_next ) { + if ( test_filter( op, &etmp, agf->agf_filter ) == LDAP_COMPARE_TRUE ) { + if ( ea ) { + autogroup_add_member_values_to_group( op, &new_dn, age, ea ); + } else + autogroup_add_member_to_group( op, &new_dn, &new_ndn, age ); + break; + } + } + op->o_dn = odn; + op->o_ndn = ondn; + } else + if ( is_olddn == 1 && is_newdn == 1 && dn_equal != 0 ) { + if ( ea ) { + /* group refresh */ + autogroup_delete_member_from_group( op, NULL, NULL, age) ; + + for ( agf = age->age_filter ; agf ; agf = agf->agf_next ) { + autogroup_add_members_from_filter( op, NULL, age, agf, 1 ); + } + } + else { + autogroup_delete_member_from_group( op, &op->o_req_dn, &op->o_req_ndn, age ); + autogroup_add_member_to_group( op, &new_dn, &new_ndn, age ); + } + } + + ldap_pvt_thread_mutex_unlock( &age->age_mutex ); + } + + op->o_tmpfree( new_dn.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( new_ndn.bv_val, op->o_tmpmemctx ); + + attrs_free( attrs ); + + ldap_pvt_thread_mutex_unlock( &agi->agi_mutex ); + } + } + + if ( op->o_tag == LDAP_REQ_MODIFY ) { + if ( rs->sr_type == REP_RESULT && rs->sr_err == LDAP_SUCCESS && !oex ) { + Entry etmp; + struct berval odn, ondn; + Debug( LDAP_DEBUG_TRACE, "==> autogroup_response MODIFY <%s>\n", op->o_req_dn.bv_val, 0, 0); + + ldap_pvt_thread_mutex_lock( &agi->agi_mutex ); + + if ( overlay_entry_get_ov( op, &op->o_req_ndn, NULL, NULL, 0, &e, on ) != + LDAP_SUCCESS || e == NULL ) { + Debug( LDAP_DEBUG_TRACE, "autogroup_response MODIFY cannot get entry for <%s>\n", op->o_req_dn.bv_val, 0, 0); + ldap_pvt_thread_mutex_unlock( &agi->agi_mutex ); + return SLAP_CB_CONTINUE; + } + + a = attrs_find( e->e_attrs, slap_schema.si_ad_objectClass ); + + + if ( a == NULL ) { + Debug( LDAP_DEBUG_TRACE, "autogroup_response MODIFY entry <%s> has no objectClass\n", op->o_req_dn.bv_val, 0, 0); + overlay_entry_release_ov( op, e, 0, on ); + ldap_pvt_thread_mutex_unlock( &agi->agi_mutex ); + return SLAP_CB_CONTINUE; + } + + /* If we modify a group's memberURL, we have to delete all of it's members, + and add them anew, because we cannot tell from which memberURL a member was added. */ + for ( ; agd; agd = agd->agd_next ) { + + if ( value_find_ex( slap_schema.si_ad_objectClass, + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH | + SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH, + a->a_nvals, &agd->agd_oc->soc_cname, + op->o_tmpmemctx ) == 0 ) + { + Modifications *m; + int match = 1; + + m = op->orm_modlist; + + for ( age = agi->agi_entry ; age ; age = age->age_next ) { + ldap_pvt_thread_mutex_lock( &age->age_mutex ); + + dnMatch( &match, 0, NULL, NULL, &op->o_req_ndn, &age->age_ndn ); + + if ( match == 0 ) { + for ( ; m ; m = m->sml_next ) { + if ( m->sml_desc == age->age_def->agd_member_url_ad ) { + autogroup_def_t *group_agd = age->age_def; + Debug( LDAP_DEBUG_TRACE, "autogroup_response MODIFY changing memberURL for group <%s>\n", + op->o_req_dn.bv_val, 0, 0); + + overlay_entry_release_ov( op, e, 0, on ); + + autogroup_delete_member_from_group( op, NULL, NULL, age ); + autogroup_delete_group( agi, age ); + + autogroup_add_group( op, agi, group_agd, NULL, &op->o_req_ndn, 1, 1); + + ldap_pvt_thread_mutex_unlock( &agi->agi_mutex ); + return SLAP_CB_CONTINUE; + } + } + + ldap_pvt_thread_mutex_unlock( &age->age_mutex ); + break; + } + + ldap_pvt_thread_mutex_unlock( &age->age_mutex ); + } + + overlay_entry_release_ov( op, e, 0, on ); + ldap_pvt_thread_mutex_unlock( &agi->agi_mutex ); + return SLAP_CB_CONTINUE; + } + } + + /* When modifying any of the attributes of an entry, we must + check if the entry is in any of our groups, and if + the modified entry maches any of the filters of that group. + + If the entry exists in a group, but the modified attributes do + not match any of the group's filters, we delete the entry from that group. + If the entry doesn't exist in a group, but matches a filter, + we add it to that group. + */ + attrs = attrs_dup( e->e_attrs ); + overlay_entry_release_ov( op, e, 0, on ); + etmp.e_name = op->o_req_dn; + etmp.e_nname = op->o_req_ndn; + etmp.e_attrs = attrs; + odn = op->o_dn; + ondn = op->o_ndn; + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + + for ( age = agi->agi_entry ; age ; age = age->age_next ) { + is_olddn = 0; + is_newdn = 0; + + ldap_pvt_thread_mutex_lock( &age->age_mutex ); + + if ( age->age_filter && age->age_filter->agf_anlist ) { + ea = attrs_find( attrs, age->age_filter->agf_anlist[0].an_desc ); + } + else { + ea = NULL; + } + + if ( overlay_entry_get_ov( op, &age->age_ndn, NULL, NULL, 0, &group, on ) != + LDAP_SUCCESS || group == NULL ) { + Debug( LDAP_DEBUG_TRACE, "autogroup_response MODIFY cannot get entry for <%s>\n", + age->age_dn.bv_val, 0, 0); + + attrs_free( attrs ); + ldap_pvt_thread_mutex_unlock( &age->age_mutex ); + ldap_pvt_thread_mutex_unlock( &agi->agi_mutex ); + op->o_dn = odn; + op->o_ndn = ondn; + return SLAP_CB_CONTINUE; + } + + a = attrs_find( group->e_attrs, age->age_def->agd_member_ad ); + + if ( a != NULL ) { + if ( value_find_ex( age->age_def->agd_member_ad, + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH | + SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH, + a->a_nvals, ea ? ea->a_nvals : &op->o_req_ndn, op->o_tmpmemctx ) == 0 ) + { + is_olddn = 1; + } + + } + + overlay_entry_release_ov( op, group, 0, on ); + + for ( agf = age->age_filter ; agf ; agf = agf->agf_next ) { + if ( dnIsSuffix( &op->o_req_ndn, &agf->agf_ndn ) ) { + if ( test_filter( op, &etmp, agf->agf_filter ) == LDAP_COMPARE_TRUE ) { + is_newdn = 1; + break; + } + } + } + + if ( is_olddn == 1 && is_newdn == 0 ) { + if(ea) + autogroup_delete_member_values_from_group( op, &op->o_req_dn, age, ea ); + else + autogroup_delete_member_from_group( op, &op->o_req_dn, &op->o_req_ndn, age ); + } else + if ( is_olddn == 0 && is_newdn == 1 ) { + if(ea) + autogroup_add_member_values_to_group( op, &op->o_req_dn, age, ea ); + else + autogroup_add_member_to_group( op, &op->o_req_dn, &op->o_req_ndn, age ); + } + + ldap_pvt_thread_mutex_unlock( &age->age_mutex ); + } + + op->o_dn = odn; + op->o_ndn = ondn; + attrs_free( attrs ); + + ldap_pvt_thread_mutex_unlock( &agi->agi_mutex ); + } + } + + return SLAP_CB_CONTINUE; +} + +/* +** Detect if filter contains a memberOf check for dn +*/ +static int +autogroup_memberOf_filter( Filter *f, BerValue *dn, AttributeDescription *memberof_ad ) +{ + int result = 0; + if ( f == NULL ) return 0; + + switch ( f->f_choice & SLAPD_FILTER_MASK ) { + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + case LDAP_FILTER_NOT: + for ( f = f->f_un.f_un_complex; f && !result; f = f->f_next ) { + result = result || autogroup_memberOf_filter( f, dn, memberof_ad ); + } + break; + case LDAP_FILTER_EQUALITY: + result = ( f->f_ava->aa_desc == memberof_ad && + ber_bvcmp( &f->f_ava->aa_value, dn ) == 0 ); + break; + default: + break; + } + + return result; +} + +/* +** When modifing a group, we must deny any modifications to the member attribute, +** because the group would be inconsistent. +*/ +static int +autogroup_modify_entry( Operation *op, SlapReply *rs) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + autogroup_info_t *agi = (autogroup_info_t *)on->on_bi.bi_private; + autogroup_def_t *agd = agi->agi_def; + autogroup_entry_t *age; + Entry *e; + Attribute *a; + struct berval odn, ondn; + OpExtra *oex; + + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + if ( oex->oe_key == (void *)&autogroup ) + return SLAP_CB_CONTINUE; + } + + Debug( LDAP_DEBUG_TRACE, "==> autogroup_modify_entry <%s>\n", op->o_req_dn.bv_val, 0, 0); + ldap_pvt_thread_mutex_lock( &agi->agi_mutex ); + + if ( overlay_entry_get_ov( op, &op->o_req_ndn, NULL, NULL, 0, &e, on ) != + LDAP_SUCCESS || e == NULL ) { + Debug( LDAP_DEBUG_TRACE, "autogroup_modify_entry cannot get entry for <%s>\n", op->o_req_dn.bv_val, 0, 0); + ldap_pvt_thread_mutex_unlock( &agi->agi_mutex ); + return SLAP_CB_CONTINUE; + } + + odn = op->o_dn; + ondn = op->o_ndn; + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + + /* Must refresh groups if a matching member value is modified OR filter contains memberOf=DN */ + for ( age = agi->agi_entry; age ; age = age->age_next ) { + autogroup_filter_t *agf; + for ( agf = age->age_filter ; agf ; agf = agf->agf_next ) { + if ( agf->agf_anlist ) { + Modifications *m; + for ( m = op->orm_modlist ; m ; m = m->sml_next ) { + if ( m->sml_desc == agf->agf_anlist[0].an_desc ) { + if ( dnIsSuffix( &op->o_req_ndn, &agf->agf_ndn ) ) { + int rc = test_filter( op, e, agf->agf_filter ); + if ( rc == LDAP_COMPARE_TRUE ) { + age->age_mustrefresh = 1; + } + } + } + } + } + + if ( autogroup_memberOf_filter( agf->agf_filter, &op->o_req_ndn, agi->agi_memberof_ad ) ) { + age->age_mustrefresh = 1; + } + } + } + op->o_dn = odn; + op->o_ndn = ondn; + + a = attrs_find( e->e_attrs, slap_schema.si_ad_objectClass ); + + if ( a == NULL ) { + Debug( LDAP_DEBUG_TRACE, "autogroup_modify_entry entry <%s> has no objectClass\n", op->o_req_dn.bv_val, 0, 0); + ldap_pvt_thread_mutex_unlock( &agi->agi_mutex ); + return SLAP_CB_CONTINUE; + } + + + for ( ; agd; agd = agd->agd_next ) { + + if ( value_find_ex( slap_schema.si_ad_objectClass, + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH | + SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH, + a->a_nvals, &agd->agd_oc->soc_cname, + op->o_tmpmemctx ) == 0 ) + { + Modifications *m; + int match = 1; + + m = op->orm_modlist; + + for ( age = agi->agi_entry ; age ; age = age->age_next ) { + dnMatch( &match, 0, NULL, NULL, &op->o_req_ndn, &age->age_ndn ); + + if ( match == 0 ) { + for ( ; m ; m = m->sml_next ) { + if ( m->sml_desc == age->age_def->agd_member_ad ) { + overlay_entry_release_ov( op, e, 0, on ); + ldap_pvt_thread_mutex_unlock( &agi->agi_mutex ); + Debug( LDAP_DEBUG_TRACE, "autogroup_modify_entry attempted to modify group's <%s> member attribute\n", op->o_req_dn.bv_val, 0, 0); + send_ldap_error(op, rs, LDAP_CONSTRAINT_VIOLATION, "attempt to modify dynamic group member attribute"); + return LDAP_CONSTRAINT_VIOLATION; + } + } + break; + } + } + + /* an entry may only have one dynamic group class */ + break; + } + } + + overlay_entry_release_ov( op, e, 0, on ); + ldap_pvt_thread_mutex_unlock( &agi->agi_mutex ); + return SLAP_CB_CONTINUE; +} + +/* +** Detect if the olddn is part of a group and so if the group should be refreshed +*/ +static int +autogroup_modrdn_entry( Operation *op, SlapReply *rs) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + autogroup_info_t *agi = (autogroup_info_t *)on->on_bi.bi_private; + autogroup_entry_t *age; + Entry *e; + struct berval odn, ondn; + OpExtra *oex; + + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + if ( oex->oe_key == (void *)&autogroup ) + return SLAP_CB_CONTINUE; + } + + Debug( LDAP_DEBUG_TRACE, "==> autogroup_modrdn_entry <%s>\n", op->o_req_dn.bv_val, 0, 0); + ldap_pvt_thread_mutex_lock( &agi->agi_mutex ); + + if ( overlay_entry_get_ov( op, &op->o_req_ndn, NULL, NULL, 0, &e, on ) != + LDAP_SUCCESS || e == NULL ) { + Debug( LDAP_DEBUG_TRACE, "autogroup_modrdn_entry cannot get entry for <%s>\n", op->o_req_dn.bv_val, 0, 0); + ldap_pvt_thread_mutex_unlock( &agi->agi_mutex ); + return SLAP_CB_CONTINUE; + } + + odn = op->o_dn; + ondn = op->o_ndn; + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + + /* Must check if a dn is modified */ + for ( age = agi->agi_entry; age ; age = age->age_next ) { + autogroup_filter_t *agf; + for ( agf = age->age_filter ; agf ; agf = agf->agf_next ) { + if ( agf->agf_anlist ) { + if ( dnIsSuffix( &op->o_req_ndn, &agf->agf_ndn ) ) { + int rc = test_filter( op, e, agf->agf_filter ); + if ( rc == LDAP_COMPARE_TRUE ) { + age->age_modrdn_olddnmodified = 1; + } + } + } + } + } + op->o_dn = odn; + op->o_ndn = ondn; + + overlay_entry_release_ov( op, e, 0, on ); + ldap_pvt_thread_mutex_unlock( &agi->agi_mutex ); + return SLAP_CB_CONTINUE; +} + +/* +** Builds a filter for searching for the +** group entries, according to the objectClass. +*/ +static int +autogroup_build_def_filter( autogroup_def_t *agd, Operation *op ) +{ + char *ptr; + + Debug( LDAP_DEBUG_TRACE, "==> autogroup_build_def_filter\n", 0, 0, 0); + + op->ors_filterstr.bv_len = STRLENOF( "(=)" ) + + slap_schema.si_ad_objectClass->ad_cname.bv_len + + agd->agd_oc->soc_cname.bv_len; + ptr = op->ors_filterstr.bv_val = op->o_tmpalloc( op->ors_filterstr.bv_len + 1, op->o_tmpmemctx ); + *ptr++ = '('; + ptr = lutil_strcopy( ptr, slap_schema.si_ad_objectClass->ad_cname.bv_val ); + *ptr++ = '='; + ptr = lutil_strcopy( ptr, agd->agd_oc->soc_cname.bv_val ); + *ptr++ = ')'; + *ptr = '\0'; + + op->ors_filter = str2filter_x( op, op->ors_filterstr.bv_val ); + + assert( op->ors_filterstr.bv_len == ptr - op->ors_filterstr.bv_val ); + + return 0; +} + +enum { + AG_ATTRSET = 1, + AG_MEMBER_OF_AD, + AG_LAST +}; + +static ConfigDriver ag_cfgen; + +static ConfigTable agcfg[] = { + { "autogroup-attrset", "group-oc> <URL-ad> <member-ad", + 4, 4, 0, ARG_MAGIC|AG_ATTRSET, ag_cfgen, + "( OLcfgCtAt:2.1 NAME 'olcAGattrSet' " + "DESC 'Automatic groups: <group objectClass>, <URL attributeDescription>, <member attributeDescription>' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "X-ORDERED 'VALUES' )", + NULL, NULL }, + + { "autogroup-memberof-ad", "memberOf attribute", + 2, 2, 0, ARG_MAGIC|AG_MEMBER_OF_AD, ag_cfgen, + "( OLcfgCtAt:2.2 NAME 'olcAGmemberOfAd' " + "DESC 'memberOf attribute' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", + NULL, NULL }, + + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs agocs[] = { + { "( OLcfgCtOc:2.1 " + "NAME 'olcAutomaticGroups' " + "DESC 'Automatic groups configuration' " + "SUP olcOverlayConfig " + "MAY ( " + "olcAGattrSet " + "$ olcAGmemberOfAd " + ")" + ")", + Cft_Overlay, agcfg, NULL, NULL }, + { NULL, 0, NULL } +}; + + +static int +ag_cfgen( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + autogroup_info_t *agi = (autogroup_info_t *)on->on_bi.bi_private; + autogroup_def_t *agd; + autogroup_entry_t *age; + + int rc = 0, i; + + Debug( LDAP_DEBUG_TRACE, "==> autogroup_cfgen\n", 0, 0, 0); + + if( agi == NULL ) { + agi = (autogroup_info_t*)ch_calloc( 1, sizeof(autogroup_info_t) ); + ldap_pvt_thread_mutex_init( &agi->agi_mutex ); + agi->agi_def = NULL; + agi->agi_entry = NULL; + on->on_bi.bi_private = (void *)agi; + } + + agd = agi->agi_def; + age = agi->agi_entry; + + if ( c->op == SLAP_CONFIG_EMIT ) { + + switch( c->type ){ + case AG_ATTRSET: + for ( i = 0 ; agd ; i++, agd = agd->agd_next ) { + struct berval bv; + char *ptr = c->cr_msg; + + assert(agd->agd_oc != NULL); + assert(agd->agd_member_url_ad != NULL); + assert(agd->agd_member_ad != NULL); + + ptr += snprintf( c->cr_msg, sizeof( c->cr_msg ), + SLAP_X_ORDERED_FMT "%s %s %s", i, + agd->agd_oc->soc_cname.bv_val, + agd->agd_member_url_ad->ad_cname.bv_val, + agd->agd_member_ad->ad_cname.bv_val ); + + bv.bv_val = c->cr_msg; + bv.bv_len = ptr - bv.bv_val; + value_add_one ( &c->rvalue_vals, &bv ); + + } + break; + + case AG_MEMBER_OF_AD: + if ( agi->agi_memberof_ad != NULL ){ + value_add_one( &c->rvalue_vals, &agi->agi_memberof_ad->ad_cname ); + } + break; + + default: + assert( 0 ); + return 1; + } + + return rc; + + }else if ( c->op == LDAP_MOD_DELETE ) { + if ( c->valx < 0) { + autogroup_def_t *agd_next; + autogroup_entry_t *age_next; + autogroup_filter_t *agf = age->age_filter, + *agf_next; + + for ( agd_next = agd; agd_next; agd = agd_next ) { + agd_next = agd->agd_next; + + ch_free( agd ); + } + + for ( age_next = age ; age_next ; age = age_next ) { + age_next = age->age_next; + + ch_free( age->age_dn.bv_val ); + ch_free( age->age_ndn.bv_val ); + + for( agf_next = agf ; agf_next ; agf = agf_next ){ + agf_next = agf->agf_next; + + filter_free( agf->agf_filter ); + ch_free( agf->agf_filterstr.bv_val ); + ch_free( agf->agf_dn.bv_val ); + ch_free( agf->agf_ndn.bv_val ); + anlist_free( agf->agf_anlist, 1, NULL ); + ch_free( agf ); + } + + ldap_pvt_thread_mutex_init( &age->age_mutex ); + ch_free( age ); + } + + ch_free( agi ); + on->on_bi.bi_private = NULL; + + } else { + autogroup_def_t **agdp; + autogroup_entry_t *age_next, *age_prev; + autogroup_filter_t *agf, + *agf_next; + + for ( i = 0, agdp = &agi->agi_def; + i < c->valx; i++ ) + { + if ( *agdp == NULL) { + return 1; + } + agdp = &(*agdp)->agd_next; + } + + agd = *agdp; + *agdp = agd->agd_next; + + for ( age_next = age , age_prev = NULL ; age_next ; age_prev = age, age = age_next ) { + age_next = age->age_next; + + if( age->age_def == agd ) { + agf = age->age_filter; + + ch_free( age->age_dn.bv_val ); + ch_free( age->age_ndn.bv_val ); + + for ( agf_next = agf; agf_next ; agf = agf_next ) { + agf_next = agf->agf_next; + filter_free( agf->agf_filter ); + ch_free( agf->agf_filterstr.bv_val ); + ch_free( agf->agf_dn.bv_val ); + ch_free( agf->agf_ndn.bv_val ); + anlist_free( agf->agf_anlist, 1, NULL ); + ch_free( agf ); + } + + ldap_pvt_thread_mutex_destroy( &age->age_mutex ); + ch_free( age ); + + age = age_prev; + + if( age_prev != NULL ) { + age_prev->age_next = age_next; + } + } + } + + ch_free( agd ); + agd = agi->agi_def; + + } + + return rc; + } + + switch(c->type){ + case AG_ATTRSET: { + autogroup_def_t **agdp, + *agd_next = NULL; + ObjectClass *oc = NULL; + AttributeDescription *member_url_ad = NULL, + *member_ad = NULL; + const char *text; + + + oc = oc_find( c->argv[ 1 ] ); + if( oc == NULL ){ + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"autogroup-attrset <oc> <URL-ad> <member-ad>\": " + "unable to find ObjectClass \"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg, 0 ); + return 1; + } + + + rc = slap_str2ad( c->argv[ 2 ], &member_url_ad, &text ); + if( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"autogroup-attrset <oc> <URL-ad> <member-ad>\": " + "unable to find AttributeDescription \"%s\"", + c->argv[ 2 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg, 0 ); + return 1; + } + + if( !is_at_subtype( member_url_ad->ad_type, slap_schema.si_ad_labeledURI->ad_type ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"autogroup-attrset <oc> <URL-ad> <member-ad>\": " + "AttributeDescription \"%s\" ", + "must be of a subtype \"labeledURI\"", + c->argv[ 2 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg, 0 ); + return 1; + } + + rc = slap_str2ad( c->argv[3], &member_ad, &text ); + if( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"autogroup-attrset <oc> <URL-ad> <member-ad>\": " + "unable to find AttributeDescription \"%s\"", + c->argv[ 3 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg, 0 ); + return 1; + } + + for ( agdp = &agi->agi_def ; *agdp ; agdp = &(*agdp)->agd_next ) { + /* The same URL attribute / member attribute pair + * cannot be repeated */ + + if ( (*agdp)->agd_member_url_ad == member_url_ad && (*agdp)->agd_member_ad == member_ad ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"autogroup-attrset <oc> <URL-ad> <member-ad>\": " + "URL attributeDescription \"%s\" already mapped", + member_ad->ad_cname.bv_val ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg, 0 ); +/* return 1; //warning*/ + } + } + + if ( c->valx > 0 ) { + int i; + + for ( i = 0, agdp = &agi->agi_def ; + i < c->valx; i++ ) + { + if ( *agdp == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"autogroup-attrset <oc> <URL-ad> <member-ad>\": " + "invalid index {%d}", + c->valx ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg, 0 ); + + return 1; + } + agdp = &(*agdp)->agd_next; + } + agd_next = *agdp; + + } else { + for ( agdp = &agi->agi_def; *agdp; + agdp = &(*agdp)->agd_next ) + /* goto last */; + } + + *agdp = (autogroup_def_t *)ch_calloc( 1, sizeof(autogroup_info_t)); + + (*agdp)->agd_oc = oc; + (*agdp)->agd_member_url_ad = member_url_ad; + (*agdp)->agd_member_ad = member_ad; + (*agdp)->agd_next = agd_next; + + } break; + + case AG_MEMBER_OF_AD: { + AttributeDescription *memberof_ad = NULL; + const char *text; + + rc = slap_str2ad( c->argv[ 1 ], &memberof_ad, &text ); + if( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"autogroup-memberof-ad <memberof-ad>\": " + "unable to find AttributeDescription \"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg, 0 ); + return 1; + } + + if ( !is_at_syntax( memberof_ad->ad_type, SLAPD_DN_SYNTAX ) /* e.g. "member" */ + && !is_at_syntax( memberof_ad->ad_type, SLAPD_NAMEUID_SYNTAX ) ) /* e.g. "uniqueMember" */ + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "memberof attribute=\"%s\" must either " + "have DN (%s) or nameUID (%s) syntax", + c->argv[ 1 ], SLAPD_DN_SYNTAX, SLAPD_NAMEUID_SYNTAX ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg, 0 ); + return 1; + } + + agi->agi_memberof_ad = memberof_ad; + + } break; + + default: + rc = 1; + break; + } + + return rc; +} + +extern int slapMode; + +/* +** Do a search for all the groups in the +** database, and add them to out internal list. +*/ +static int +autogroup_db_open( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + autogroup_info_t *agi = on->on_bi.bi_private; + autogroup_def_t *agd; + autogroup_sc_t ags; + Operation *op; + slap_callback cb = { 0 }; + + void *thrctx = ldap_pvt_thread_pool_context(); + Connection conn = { 0 }; + OperationBuffer opbuf; + + Debug( LDAP_DEBUG_TRACE, "==> autogroup_db_open\n", 0, 0, 0); + + if ( agi == NULL || !( slapMode & SLAP_SERVER_MODE )) { + return 0; + } + + connection_fake_init( &conn, &opbuf, thrctx ); + op = &opbuf.ob_op; + + op->ors_attrsonly = 0; + op->o_tag = LDAP_REQ_SEARCH; + op->o_dn = be->be_rootdn; + op->o_ndn = be->be_rootndn; + + op->o_req_dn = be->be_suffix[0]; + op->o_req_ndn = be->be_nsuffix[0]; + + op->ors_scope = LDAP_SCOPE_SUBTREE; + op->ors_deref = LDAP_DEREF_NEVER; + op->ors_limit = NULL; + op->ors_tlimit = SLAP_NO_LIMIT; + op->ors_slimit = SLAP_NO_LIMIT; + op->ors_attrs = slap_anlist_no_attrs; + op->o_do_not_cache = 1; + + op->o_bd = be; + op->o_bd->bd_info = (BackendInfo *)on->on_info; + + ags.ags_info = agi; + cb.sc_private = &ags; + cb.sc_response = autogroup_group_add_cb; + cb.sc_cleanup = NULL; + cb.sc_next = NULL; + + op->o_callback = &cb; + + for (agd = agi->agi_def ; agd ; agd = agd->agd_next) { + SlapReply rs = { REP_RESULT }; + + autogroup_build_def_filter(agd, op); + + ags.ags_def = agd; + + op->o_bd->be_search( op, &rs ); + + filter_free_x( op, op->ors_filter, 1 ); + op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx ); + } + + if( ! agi->agi_memberof_ad ){ + int rc; + const char *text = NULL; + + rc = slap_str2ad( SLAPD_MEMBEROF_ATTR, &agi->agi_memberof_ad, &text ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "autogroup_db_open: " + "unable to find attribute=\"%s\": %s (%d)\n", + SLAPD_MEMBEROF_ATTR, text, rc ); + return rc; + } + } + + return 0; +} + +static int +autogroup_db_close( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + + Debug( LDAP_DEBUG_TRACE, "==> autogroup_db_close\n", 0, 0, 0); + + if ( on->on_bi.bi_private ) { + autogroup_info_t *agi = on->on_bi.bi_private; + autogroup_entry_t *age = agi->agi_entry, + *age_next; + autogroup_filter_t *agf, *agf_next; + + for ( age_next = age; age_next; age = age_next ) { + age_next = age->age_next; + + ch_free( age->age_dn.bv_val ); + ch_free( age->age_ndn.bv_val ); + + agf = age->age_filter; + + for ( agf_next = agf; agf_next; agf = agf_next ) { + agf_next = agf->agf_next; + + filter_free( agf->agf_filter ); + ch_free( agf->agf_filterstr.bv_val ); + ch_free( agf->agf_dn.bv_val ); + ch_free( agf->agf_ndn.bv_val ); + anlist_free( agf->agf_anlist, 1, NULL ); + ch_free( agf ); + } + + ldap_pvt_thread_mutex_destroy( &age->age_mutex ); + ch_free( age ); + } + } + + return 0; +} + +static int +autogroup_db_destroy( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + + Debug( LDAP_DEBUG_TRACE, "==> autogroup_db_destroy\n", 0, 0, 0); + + if ( on->on_bi.bi_private ) { + autogroup_info_t *agi = on->on_bi.bi_private; + autogroup_def_t *agd = agi->agi_def, + *agd_next; + + for ( agd_next = agd; agd_next; agd = agd_next ) { + agd_next = agd->agd_next; + + ch_free( agd ); + } + + ldap_pvt_thread_mutex_destroy( &agi->agi_mutex ); + ch_free( agi ); + } + + return 0; +} + +static +int +autogroup_initialize(void) +{ + int rc = 0; + autogroup.on_bi.bi_type = "autogroup"; + + autogroup.on_bi.bi_db_open = autogroup_db_open; + autogroup.on_bi.bi_db_close = autogroup_db_close; + autogroup.on_bi.bi_db_destroy = autogroup_db_destroy; + + autogroup.on_bi.bi_op_add = autogroup_add_entry; + autogroup.on_bi.bi_op_delete = autogroup_delete_entry; + autogroup.on_bi.bi_op_modify = autogroup_modify_entry; + autogroup.on_bi.bi_op_modrdn = autogroup_modrdn_entry; + + autogroup.on_response = autogroup_response; + + autogroup.on_bi.bi_cf_ocs = agocs; + + rc = config_register_schema( agcfg, agocs ); + if ( rc ) { + return rc; + } + + return overlay_register( &autogroup ); +} + +int +init_module( int argc, char *argv[] ) +{ + return autogroup_initialize(); +} diff --git a/contrib/slapd-modules/autogroup/slapo-autogroup.5 b/contrib/slapd-modules/autogroup/slapo-autogroup.5 new file mode 100644 index 0000000..c2ed916 --- /dev/null +++ b/contrib/slapd-modules/autogroup/slapo-autogroup.5 @@ -0,0 +1,102 @@ +.TH SLAPO-AUTOGROUP 5 "RELEASEDATE" "OpenLDAP LDVERSION" +.\" Copyright 1998-2021 The OpenLDAP Foundation All Rights Reserved. +.\" Portions Copyright \[u00A9] 2007 Michał Szulczyński. +.\" Copying restrictions apply. See the COPYRIGHT file. +.\" $OpenLDAP$ +.SH NAME +\FCslapo-autogroup\FT \- automatic updates of group memberships which meet the +requirements of any filter contained in the group definition. +.SH SYNOPSIS +In \FCslapd.conf\FT: + ... + \FCinclude ETCDIR/schema/dyngroup.schema\FT + ... + \FCmoduleload autogroup.so\FT + ... + \FCdatabase\FT ... + ... + \FCoverlay autogroup\FT + \FCautogroup-attrset groupOfURLs memberURL member\FT +.SH DESCRIPTION +The +.B autogroup +overlay to +.BR slapd (8) +allows automated updates of group memberships which meet the requirements +of any filter contained in the group definition. The filters are built from +LDAP URI-valued attributes. Any time an object is added/deleted/updated, it is +tested for compliance with the filters, and its membership is accordingly +updated. For searches and compares, it behaves like a static group. +If the attribute part of the URI is filled, the group entry is populated by +the values of this attribute in the entries resulting from the search. +.SH CONFIGURATION +Either +.BR \FCslapd.conf\FT (5) +or the \FCcn=config\FT methodology of +.BR \FCslapd-config\FT (5) +may be used for configuring autogroup. Both syntaxes are provided +here for convenience: +.TP +.B \FCautogroup-attrset\FT <group-oc> <URL-ad> <member-ad> +.TP +.B \FColcAGattrSet:\FT <group-oc> <URL-ad> <member-ad> +This defines the objectclass-attribute-URI mappings defining the +automatically managed groups, and may appear multiple times. + +The value <group-oc> is the name of the objectClass that represents +the group. + +The value <URL-ad> is the name of the attributeDescription that +contains the URI that is converted to the filters. If no URI is +present, there will be no members in that group. It must be a subtype +of labeledURI. + +The value <member-ad> is the name of the attributeDescription that +specifies the member attribute. User modification of this attribute is +disabled for consistency. +.TP +.B \FCautogroup-memberof-ad\FT <memberof-ad> +.TP +.B \FColcAGmemberOfAd\FT <memberof-ad> +This defines the attribute that is used by the memberOf overlay to +store the names of groups that an entry is member of; it must be +DN-valued. It should be set to the same value as +memberof-memberof-ad. It defaults to 'memberOf'. +.SH EXAMPLES +As above in SYNOPSIS, or with memberof: + + ... + \FCinclude ETCDIR/schema/dyngroup.schema\FT + \FCinclude ETCDIR/schema/memberof.schema\FT + ... + \FCmoduleload autogroup.so\FT + \FCmoduleload memberof.so\FT + ... + \FCdatabase\FT ... + ... + \FCoverlay memberof\FT + \FCmemberof-memberof-ad\FT foo + ... + \FCoverlay autogroup\FT + \FCautogroup-attrset groupOfURLs memberURL member\FT + \FCautogroup-memberof-ad\FT foo +.SH CAVEATS +As with static groups, update operations on groups with a large number +of members may be slow. If the attribute part of the URI is specified, +modify and delete operations are more difficult to handle. In these +cases the overlay will try to detect if groups have been modified and +then simply refresh them. This can cause performance hits if the +search specified by the URI deals with a significant number of +entries. +.SH ACKNOWLEDGEMENTS +This module was originally written in 2007 by Michał +Szulczyński. Further enhancements were contributed by Howard +Chu, Raphael Ouazana, Norbert Pueschel, and Christian Manal. Manpage +updates provided by Emily Backes. +.SH SEE ALSO +.BR slapd.conf (5), +.BR slapd (8). +.SH Copyrights +Copyright 1998-2021 The OpenLDAP Foundation. +Portions Copyright \[u00A9] 2007 Michał Szulczyński. +All rights reserved. |