/* $NetBSD: memberof.c,v 1.2 2021/08/14 16:15:02 christos Exp $ */
/* memberof.c - back-reference for group membership */
/* $OpenLDAP$ */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
*
* Copyright 2005-2007 Pierangelo Masarati <ando@sys-net.it>
* 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>.
*/
/* ACKNOWLEDGMENTS:
* This work was initially developed by Pierangelo Masarati for inclusion
* in OpenLDAP Software, sponsored by SysNet s.r.l.
*/
#include <sys/cdefs.h>
__RCSID("$NetBSD: memberof.c,v 1.2 2021/08/14 16:15:02 christos Exp $");
#include "portable.h"
#ifdef SLAPD_OVER_MEMBEROF
#include <stdio.h>
#include "ac/string.h"
#include "ac/socket.h"
#include "slap.h"
#include "slap-config.h"
#include "lutil.h"
/*
* Glossary:
*
* GROUP a group object (an entry with GROUP_OC
* objectClass)
* MEMBER a member object (an entry whose DN is
* listed as MEMBER_AT value of a GROUP)
* GROUP_OC the objectClass of the group object
* (default: groupOfNames)
* MEMBER_AT the membership attribute, DN-valued;
* note: nameAndOptionalUID is tolerated
* as soon as the optionalUID is absent
* (default: member)
* MEMBER_OF reverse membership attribute
* (default: memberOf)
*
* - add:
* - if the entry that is being added is a GROUP,
* the MEMBER_AT defined as values of the add operation
* get the MEMBER_OF value directly from the request.
*
* if configured to do so, the MEMBER objects do not exist,
* and no relax control is issued, either:
* - fail
* - drop non-existing members
* (by default: don't muck with values)
*
* - if (configured to do so,) the referenced GROUP exists,
* the relax control is set and the user has
* "manage" privileges, allow to add MEMBER_OF values to
* generic entries.
*
* - modify:
* - if the entry being modified is a GROUP_OC and the
* MEMBER_AT attribute is modified, the MEMBER_OF value
* of the (existing) MEMBER_AT entries that are affected
* is modified according to the request:
* - if a MEMBER is removed from the group,
* delete the corresponding MEMBER_OF
* - if a MEMBER is added to a group,
* add the corresponding MEMBER_OF
*
* We need to determine, from the database, if it is
* a GROUP_OC, and we need to check, from the
* modification list, if the MEMBER_AT attribute is being
* affected, and what MEMBER_AT values are affected.
*
* if configured to do so, the entries corresponding to
* the MEMBER_AT values do not exist, and no relax control
* is issued, either:
* - fail
* - drop non-existing members
* (by default: don't muck with values)
*
* - if configured to do so, the referenced GROUP exists,
* (the relax control is set) and the user has
* "manage" privileges, allow to add MEMBER_OF values to
* generic entries; the change is NOT automatically reflected
* in the MEMBER attribute of the GROUP referenced
* by the value of MEMBER_OF; a separate modification,
* with or without relax control, needs to be performed.
*
* - modrdn:
* - if the entry being renamed is a GROUP, the MEMBER_OF
* value of the (existing) MEMBER objects is modified
* accordingly based on the newDN of the GROUP.
*
* We need to determine, from the database, if it is
* a GROUP; the list of MEMBER objects is obtained from
* the database.
*
* Non-existing MEMBER objects are ignored, since the
* MEMBER_AT is not being addressed by the operation.
*
* - if the entry being renamed has the MEMBER_OF attribute,
* the corresponding MEMBER value must be modified in the
* respective group entries.
*
*
* - delete:
* - if the entry being deleted is a GROUP, the (existing)
* MEMBER objects are modified accordingly; a copy of the
* values of the MEMBER_AT is saved and, if the delete
* succeeds, the MEMBER_OF value of the (existing) MEMBER
* objects is deleted.
*
* We need to determine, from the database, if it is
* a GROUP.
*
* Non-existing MEMBER objects are ignored, since the entry
* is being deleted.
*
* - if the entry being deleted has the MEMBER_OF attribute,
* the corresponding value of the MEMBER_AT must be deleted
* from the respective GROUP entries.
*/
#define SLAPD_MEMBEROF_ATTR "memberOf"
static AttributeDescription *ad_member;
static AttributeDescription *ad_memberOf;
static ObjectClass *oc_group;
static slap_overinst memberof;
typedef struct memberof_t {
struct berval mo_dn;
struct berval mo_ndn;
ObjectClass *mo_oc_group;
AttributeDescription *mo_ad_member;
AttributeDescription *mo_ad_memberof;
struct berval mo_groupFilterstr;
AttributeAssertion mo_groupAVA;
Filter mo_groupFilter;
struct berval mo_memberFilterstr;
Filter mo_memberFilter;
unsigned mo_flags;
#define MEMBEROF_NONE 0x00U
#define MEMBEROF_FDANGLING_DROP 0x01U
#define MEMBEROF_FDANGLING_ERROR 0x02U
#define MEMBEROF_FDANGLING_MASK (MEMBEROF_FDANGLING_DROP|MEMBEROF_FDANGLING_ERROR)
#define MEMBEROF_FREFINT 0x04U
#define MEMBEROF_FREVERSE 0x08U
ber_int_t mo_dangling_err;
#define MEMBEROF_CHK(mo,f) \
(((mo)->mo_flags & (f)) == (f))
#define MEMBEROF_DANGLING_CHECK(mo) \
((mo)->mo_flags & MEMBEROF_FDANGLING_MASK)
#define MEMBEROF_DANGLING_DROP(mo) \
MEMBEROF_CHK((mo),MEMBEROF_FDANGLING_DROP)
#define MEMBEROF_DANGLING_ERROR(mo) \
MEMBEROF_CHK((mo),MEMBEROF_FDANGLING_ERROR)
#define MEMBEROF_REFINT(mo) \
MEMBEROF_CHK((mo),MEMBEROF_FREFINT)
#define MEMBEROF_REVERSE(mo) \
MEMBEROF_CHK((mo),MEMBEROF_FREVERSE)
} memberof_t;
typedef enum memberof_is_t {
MEMBEROF_IS_NONE = 0x00,
MEMBEROF_IS_GROUP = 0x01,
MEMBEROF_IS_MEMBER = 0x02,
MEMBEROF_IS_BOTH = (MEMBEROF_IS_GROUP|MEMBEROF_IS_MEMBER)
} memberof_is_t;
typedef struct memberof_cookie_t {
AttributeDescription *ad;
BerVarray vals;
int foundit;
} memberof_cookie_t;
typedef struct memberof_cbinfo_t {
slap_overinst *on;
BerVarray member;
BerVarray memberof;
memberof_is_t what;
} memberof_cbinfo_t;
static void
memberof_set_backend( Operation *op_target, Operation *op, slap_overinst *on )
{
BackendInfo *bi = op->o_bd->bd_info;
if ( bi->bi_type == memberof.on_bi.bi_type )
op_target->o_bd->bd_info = (BackendInfo *)on->on_info;
}
static int
memberof_isGroupOrMember_cb( Operation *op, SlapReply *rs )
{
if ( rs->sr_type == REP_SEARCH ) {
memberof_cookie_t *mc;
mc = (memberof_cookie_t *)op->o_callback->sc_private;
mc->foundit = 1;
}
return 0;
}
/*
* callback for internal search that saves the member attribute values
* of groups being deleted.
*/
static int
memberof_saveMember_cb( Operation *op, SlapReply *rs )
{
if ( rs->sr_type == REP_SEARCH ) {
memberof_cookie_t *mc;
Attribute *a;
mc = (memberof_cookie_t *)op->o_callback->sc_private;
mc->foundit = 1;
assert( rs->sr_entry != NULL );
assert( rs->sr_entry->e_attrs != NULL );
a = attr_find( rs->sr_entry->e_attrs, mc->ad );
if ( a != NULL ) {
ber_bvarray_dup_x( &mc->vals, a->a_nvals, op->o_tmpmemctx );
assert( attr_find( a->a_next, mc->ad ) == NULL );
}
}
return 0;
}
/*
* the delete hook performs an internal search that saves the member
* attribute values of groups being deleted.
*/
static int
memberof_isGroupOrMember( Operation *op, memberof_cbinfo_t *mci )
{
slap_overinst *on = mci->on;
memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
Operation op2 = *op;
slap_callback cb = { 0 };
BackendInfo *bi = op->o_bd->bd_info;
AttributeName an[ 2 ];
memberof_is_t iswhat = MEMBEROF_IS_NONE;
memberof_cookie_t mc;
assert( mci->what != MEMBEROF_IS_NONE );
cb.sc_private = &mc;
if ( op->o_tag == LDAP_REQ_DELETE ) {
cb.sc_response = memberof_saveMember_cb;
} else {
cb.sc_response = memberof_isGroupOrMember_cb;
}
op2.o_tag = LDAP_REQ_SEARCH;
op2.o_callback = &cb;
op2.o_dn = op->o_bd->be_rootdn;
op2.o_ndn = op->o_bd->be_rootndn;
op2.ors_scope = LDAP_SCOPE_BASE;
op2.ors_deref = LDAP_DEREF_NEVER;
BER_BVZERO( &an[ 1 ].an_name );
op2.ors_attrs = an;
op2.ors_attrsonly = 0;
op2.ors_limit = NULL;
op2.ors_slimit = 1;
op2.ors_tlimit = SLAP_NO_LIMIT;
if ( mci->what & MEMBEROF_IS_GROUP ) {
SlapReply rs2 = { REP_RESULT };
mc.ad = mo->mo_ad_member;
mc.foundit = 0;
mc.vals = NULL;
an[ 0 ].an_desc = mo->mo_ad_member;
an[ 0 ].an_name = an[ 0 ].an_desc->ad_cname;
op2.ors_filterstr = mo->mo_groupFilterstr;
op2.ors_filter = &mo->mo_groupFilter;
op2.o_do_not_cache = 1; /* internal search, don't log */
memberof_set_backend( &op2, op, on );
(void)op->o_bd->be_search( &op2, &rs2 );
op2.o_bd->bd_info = bi;
if ( mc.foundit ) {
iswhat |= MEMBEROF_IS_GROUP;
if ( mc.vals ) mci->member = mc.vals;
}
}
if ( mci->what & MEMBEROF_IS_MEMBER ) {
SlapReply rs2 = { REP_RESULT };
mc.ad = mo->mo_ad_memberof;
mc.foundit = 0;
mc.vals = NULL;
an[ 0 ].an_desc = mo->mo_ad_memberof;
an[ 0 ].an_name = an[ 0 ].an_desc->ad_cname;
op2.ors_filterstr = mo->mo_memberFilterstr;
op2.ors_filter = &mo->mo_memberFilter;
op2.o_do_not_cache = 1; /* internal search, don't log */
memberof_set_backend( &op2, op, on );
(void)op->o_bd->be_search( &op2, &rs2 );
op2.o_bd->bd_info = bi;
if ( mc.foundit ) {
iswhat |= MEMBEROF_IS_MEMBER;
if ( mc.vals ) mci->memberof = mc.vals;
}
}
mci->what = iswhat;
return LDAP_SUCCESS;
}
/*
* response callback that adds memberof values when a group is modified.
*/
static void
memberof_value_modify(
Operation *op,
struct berval *ndn,
AttributeDescription *ad,
struct berval *old_dn,
struct berval *old_ndn,
struct berval *new_dn,
struct berval *new_ndn )
{
memberof_cbinfo_t *mci = op->o_callback->sc_private;
slap_overinst *on = mci->on;
memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
Operation op2 = *op;
unsigned long opid = op->o_opid;
SlapReply rs2 = { REP_RESULT };
slap_callback cb = { NULL, slap_null_cb, NULL, NULL };
Modifications mod[ 2 ] = { { { 0 } } }, *ml;
struct berval values[ 4 ], nvalues[ 4 ];
int mcnt = 0;
if ( old_ndn != NULL && new_ndn != NULL &&
ber_bvcmp( old_ndn, new_ndn ) == 0 ) {
/* DNs compare equal, it's a noop */
return;
}
op2.o_tag = LDAP_REQ_MODIFY;
op2.o_req_dn = *ndn;
op2.o_req_ndn = *ndn;
op2.o_callback = &cb;
op2.o_dn = op->o_bd->be_rootdn;
op2.o_ndn = op->o_bd->be_rootndn;
op2.orm_modlist = NULL;
/* Internal ops, never replicate these */
op2.o_opid = 0; /* shared with op, saved above */
op2.orm_no_opattrs = 1;
op2.o_dont_replicate = 1;
if ( !BER_BVISNULL( &mo->mo_ndn ) ) {
ml = &mod[ mcnt ];
ml->sml_numvals = 1;
ml->sml_values = &values[ 0 ];
ml->sml_values[ 0 ] = mo->mo_dn;
BER_BVZERO( &ml->sml_values[ 1 ] );
ml->sml_nvalues = &nvalues[ 0 ];
ml->sml_nvalues[ 0 ] = mo->mo_ndn;
BER_BVZERO( &ml->sml_nvalues[ 1 ] );
ml->sml_desc = slap_schema.si_ad_modifiersName;
ml->sml_type = ml->sml_desc->ad_cname;
ml->sml_op = LDAP_MOD_REPLACE;
ml->sml_flags = SLAP_MOD_INTERNAL;
ml->sml_next = op2.orm_modlist;
op2.orm_modlist = ml;
mcnt++;
}
ml = &mod[ mcnt ];
ml->sml_numvals = 1;
ml->sml_values = &values[ 2 ];
BER_BVZERO( &ml->sml_values[ 1 ] );
ml->sml_nvalues = &nvalues[ 2 ];
BER_BVZERO( &ml->sml_nvalues[ 1 ] );
ml->sml_desc = ad;
ml->sml_type = ml->sml_desc->ad_cname;
ml->sml_flags = SLAP_MOD_INTERNAL;
ml->sml_next = op2.orm_modlist;
op2.orm_modlist = ml;
if ( new_ndn != NULL ) {
BackendInfo *bi = op2.o_bd->bd_info;
OpExtra oex;
assert( !BER_BVISNULL( new_dn ) );
assert( !BER_BVISNULL( new_ndn ) );
ml = &mod[ mcnt ];
ml->sml_op = LDAP_MOD_ADD;
ml->sml_values[ 0 ] = *new_dn;
ml->sml_nvalues[ 0 ] = *new_ndn;
oex.oe_key = (void *)&memberof;
LDAP_SLIST_INSERT_HEAD(&op2.o_extra, &oex, oe_next);
memberof_set_backend( &op2, op, on );
(void)op->o_bd->be_modify( &op2, &rs2 );
op2.o_bd->bd_info = bi;
LDAP_SLIST_REMOVE(&op2.o_extra, &oex, OpExtra, oe_next);
if ( rs2.sr_err != LDAP_SUCCESS ) {
Debug(LDAP_DEBUG_ANY,
"%s: memberof_value_modify DN=\"%s\" add %s=\"%s\" failed err=%d\n",
op->o_log_prefix, op2.o_req_dn.bv_val,
ad->ad_cname.bv_val, new_dn->bv_val, rs2.sr_err );
}
assert( op2.orm_modlist == &mod[ mcnt ] );
assert( mcnt == 0 || op2.orm_modlist->sml_next == &mod[ 0 ] );
ml = op2.orm_modlist->sml_next;
if ( mcnt == 1 ) {
assert( ml == &mod[ 0 ] );
ml = ml->sml_next;
}
if ( ml != NULL ) {
slap_mods_free( ml, 1 );
}
mod[ 0 ].sml_next = NULL;
}
if ( old_ndn != NULL ) {
BackendInfo *bi = op2.o_bd->bd_info;
OpExtra oex;
assert( !BER_BVISNULL( old_dn ) );
assert( !BER_BVISNULL( old_ndn ) );
ml = &mod[ mcnt ];
ml->sml_op = LDAP_MOD_DELETE;
ml->sml_values[ 0 ] = *old_dn;
ml->sml_nvalues[ 0 ] = *old_ndn;
oex.oe_key = (void *)&memberof;
LDAP_SLIST_INSERT_HEAD(&op2.o_extra, &oex, oe_next);
memberof_set_backend( &op2, op, on );
(void)op->o_bd->be_modify( &op2, &rs2 );
op2.o_bd->bd_info = bi;
LDAP_SLIST_REMOVE(&op2.o_extra, &oex, OpExtra, oe_next);
if ( rs2.sr_err != LDAP_SUCCESS ) {
Debug(LDAP_DEBUG_ANY,
"%s: memberof_value_modify DN=\"%s\" delete %s=\"%s\" failed err=%d\n",
op->o_log_prefix, op2.o_req_dn.bv_val,
ad->ad_cname.bv_val, old_dn->bv_val, rs2.sr_err );
}
assert( op2.orm_modlist == &mod[ mcnt ] );
ml = op2.orm_modlist->sml_next;
if ( mcnt == 1 ) {
assert( ml == &mod[ 0 ] );
ml = ml->sml_next;
}
if ( ml != NULL ) {
slap_mods_free( ml, 1 );
}
}
/* restore original opid */
op->o_opid = opid;
/* FIXME: if old_group_ndn doesn't exist, both delete __and__
* add will fail; better split in two operations, although
* not optimal in terms of performance. At least it would
* move towards self-repairing capabilities. */
}
static int
memberof_cleanup( Operation *op, SlapReply *rs )
{
slap_callback *sc = op->o_callback;
memberof_cbinfo_t *mci = sc->sc_private;
op->o_callback = sc->sc_next;
if ( mci->memberof )
ber_bvarray_free_x( mci->memberof, op->o_tmpmemctx );
if ( mci->member )
ber_bvarray_free_x( mci->member, op->o_tmpmemctx );
op->o_tmpfree( sc, op->o_tmpmemctx );
return 0;
}
static int memberof_res_add( Operation *op, SlapReply *rs );
static int memberof_res_delete( Operation *op, SlapReply *rs );
static int memberof_res_modify( Operation *op, SlapReply *rs );
static int memberof_res_modrdn( Operation *op, SlapReply *rs );
static int
memberof_op_add( Operation *op, SlapReply *rs )
{
slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
Attribute **ap, **map = NULL;
int rc = SLAP_CB_CONTINUE;
int i;
struct berval save_dn, save_ndn;
slap_callback *sc;
memberof_cbinfo_t *mci;
OpExtra *oex;
LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) {
if ( oex->oe_key == (void *)&memberof )
return SLAP_CB_CONTINUE;
}
if ( op->ora_e->e_attrs == NULL ) {
/* FIXME: global overlay; need to deal with */
Debug( LDAP_DEBUG_ANY, "%s: memberof_op_add(\"%s\"): "
"consistency checks not implemented when overlay "
"is instantiated as global.\n",
op->o_log_prefix, op->o_req_dn.bv_val );
return SLAP_CB_CONTINUE;
}
if ( MEMBEROF_REVERSE( mo ) ) {
for ( ap = &op->ora_e->e_attrs; *ap; ap = &(*ap)->a_next ) {
Attribute *a = *ap;
if ( a->a_desc == mo->mo_ad_memberof ) {
map = ap;
break;
}
}
}
save_dn = op->o_dn;
save_ndn = op->o_ndn;
if ( MEMBEROF_DANGLING_CHECK( mo )
&& !get_relax( op )
&& is_entry_objectclass_or_sub( op->ora_e, mo->mo_oc_group ) )
{
op->o_dn = op->o_bd->be_rootdn;
op->o_ndn = op->o_bd->be_rootndn;
op->o_bd->bd_info = (BackendInfo *)on->on_info;
for ( ap = &op->ora_e->e_attrs; *ap; ) {
Attribute *a = *ap;
if ( !is_ad_subtype( a->a_desc, mo->mo_ad_member ) ) {
ap = &a->a_next;
continue;
}
assert( a->a_nvals != NULL );
for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) {
Entry *e = NULL;
/* ITS#6670 Ignore member pointing to this entry */
if ( dn_match( &a->a_nvals[i], &save_ndn ))
continue;
rc = be_entry_get_rw( op, &a->a_nvals[ i ],
NULL, NULL, 0, &e );
if ( rc == LDAP_SUCCESS ) {
be_entry_release_r( op, e );
continue;
}
if ( MEMBEROF_DANGLING_ERROR( mo ) ) {
rc = rs->sr_err = mo->mo_dangling_err;
rs->sr_text = "adding non-existing object "
"as group member";
send_ldap_result( op, rs );
goto done;
}
if ( MEMBEROF_DANGLING_DROP( mo ) ) {
int j;
Debug( LDAP_DEBUG_ANY, "%s: memberof_op_add(\"%s\"): "
"member=\"%s\" does not exist (stripping...)\n",
op->o_log_prefix, op->ora_e->e_name.bv_val,
a->a_vals[ i ].bv_val );
for ( j = i + 1; !BER_BVISNULL( &a->a_nvals[ j ] ); j++ );
ber_memfree( a->a_vals[ i ].bv_val );
BER_BVZERO( &a->a_vals[ i ] );
if ( a->a_nvals != a->a_vals ) {
ber_memfree( a->a_nvals[ i ].bv_val );
BER_BVZERO( &a->a_nvals[ i ] );
}
a->a_numvals--;
if ( j - i == 1 ) {
break;
}
AC_MEMCPY( &a->a_vals[ i ], &a->a_vals[ i + 1 ],
sizeof( struct berval ) * ( j - i ) );
if ( a->a_nvals != a->a_vals ) {
AC_MEMCPY( &a->a_nvals[ i ], &a->a_nvals[ i + 1 ],
sizeof( struct berval ) * ( j - i ) );
}
i--;
}
}
/* If all values have been removed,
* remove the attribute itself. */
if ( BER_BVISNULL( &a->a_nvals[ 0 ] ) ) {
*ap = a->a_next;
attr_free( a );
} else {
ap = &a->a_next;
}
}
op->o_dn = save_dn;
op->o_ndn = save_ndn;
op->o_bd->bd_info = (BackendInfo *)on;
}
if ( map != NULL ) {
Attribute *a = *map;
AccessControlState acl_state = ACL_STATE_INIT;
for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) {
Entry *e;
op->o_bd->bd_info = (BackendInfo *)on->on_info;
/* access is checked with the original identity */
rc = access_allowed( op, op->ora_e, mo->mo_ad_memberof,
&a->a_nvals[ i ], ACL_WADD,
&acl_state );
if ( rc == 0 ) {
rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
rs->sr_text = NULL;
send_ldap_result( op, rs );
goto done;
}
/* ITS#6670 Ignore member pointing to this entry */
if ( dn_match( &a->a_nvals[i], &save_ndn ))
continue;
rc = be_entry_get_rw( op, &a->a_nvals[ i ],
NULL, NULL, 0, &e );
op->o_bd->bd_info = (BackendInfo *)on;
if ( rc != LDAP_SUCCESS ) {
if ( get_relax( op ) ) {
continue;
}
if ( MEMBEROF_DANGLING_ERROR( mo ) ) {
rc = rs->sr_err = mo->mo_dangling_err;
rs->sr_text = "adding non-existing object "
"as memberof";
send_ldap_result( op, rs );
goto done;
}
if ( MEMBEROF_DANGLING_DROP( mo ) ) {
int j;
Debug( LDAP_DEBUG_ANY, "%s: memberof_op_add(\"%s\"): "
"memberof=\"%s\" does not exist (stripping...)\n",
op->o_log_prefix, op->ora_e->e_name.bv_val,
a->a_nvals[ i ].bv_val );
for ( j = i + 1; !BER_BVISNULL( &a->a_nvals[ j ] ); j++ );
ber_memfree( a->a_vals[ i ].bv_val );
BER_BVZERO( &a->a_vals[ i ] );
if ( a->a_nvals != a->a_vals ) {
ber_memfree( a->a_nvals[ i ].bv_val );
BER_BVZERO( &a->a_nvals[ i ] );
}
if ( j - i == 1 ) {
break;
}
AC_MEMCPY( &a->a_vals[ i ], &a->a_vals[ i + 1 ],
sizeof( struct berval ) * ( j - i ) );
if ( a->a_nvals != a->a_vals ) {
AC_MEMCPY( &a->a_nvals[ i ], &a->a_nvals[ i + 1 ],
sizeof( struct berval ) * ( j - i ) );
}
i--;
}
continue;
}
/* access is checked with the original identity */
op->o_bd->bd_info = (BackendInfo *)on->on_info;
rc = access_allowed( op, e, mo->mo_ad_member,
&op->o_req_ndn, ACL_WADD, NULL );
be_entry_release_r( op, e );
op->o_bd->bd_info = (BackendInfo *)on;
if ( !rc ) {
rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
rs->sr_text = "insufficient access to object referenced by memberof";
send_ldap_result( op, rs );
goto done;
}
}
if ( BER_BVISNULL( &a->a_nvals[ 0 ] ) ) {
*map = a->a_next;
attr_free( a );
}
}
rc = SLAP_CB_CONTINUE;
sc = op->o_tmpalloc( sizeof(slap_callback)+sizeof(*mci), op->o_tmpmemctx );
sc->sc_private = sc+1;
sc->sc_response = memberof_res_add;
sc->sc_cleanup = memberof_cleanup;
sc->sc_writewait = 0;
mci = sc->sc_private;
mci->on = on;
mci->member = NULL;
mci->memberof = NULL;
sc->sc_next = op->o_callback;
op->o_callback = sc;
done:;
op->o_dn = save_dn;
op->o_ndn = save_ndn;
op->o_bd->bd_info = (BackendInfo *)on;
return rc;
}
static int
memberof_op_delete( Operation *op, SlapReply *rs )
{
slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
slap_callback *sc;
memberof_cbinfo_t *mci;
OpExtra *oex;
LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) {
if ( oex->oe_key == (void *)&memberof )
return SLAP_CB_CONTINUE;
}
sc = op->o_tmpalloc( sizeof(slap_callback)+sizeof(*mci), op->o_tmpmemctx );
sc->sc_private = sc+1;
sc->sc_response = memberof_res_delete;
sc->sc_cleanup = memberof_cleanup;
sc->sc_writewait = 0;
mci = sc->sc_private;
mci->on = on;
mci->member = NULL;
mci->memberof = NULL;
mci->what = MEMBEROF_IS_GROUP;
if ( MEMBEROF_REFINT( mo ) ) {
mci->what = MEMBEROF_IS_BOTH;
}
memberof_isGroupOrMember( op, mci );
sc->sc_next = op->o_callback;
op->o_callback = sc;
return SLAP_CB_CONTINUE;
}
static int
memberof_op_modify( Operation *op, SlapReply *rs )
{
slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
Modifications **mlp, **mmlp = NULL;
int rc = SLAP_CB_CONTINUE, save_member = 0;
struct berval save_dn, save_ndn;
slap_callback *sc;
memberof_cbinfo_t *mci, mcis;
OpExtra *oex;
LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) {
if ( oex->oe_key == (void *)&memberof )
return SLAP_CB_CONTINUE;
}
if ( MEMBEROF_REVERSE( mo ) ) {
for ( mlp = &op->orm_modlist; *mlp; mlp = &(*mlp)->sml_next ) {
Modifications *ml = *mlp;
if ( ml->sml_desc == mo->mo_ad_memberof ) {
mmlp = mlp;
break;
}
}
}
save_dn = op->o_dn;
save_ndn = op->o_ndn;
mcis.on = on;
mcis.what = MEMBEROF_IS_GROUP;
if ( memberof_isGroupOrMember( op, &mcis ) == LDAP_SUCCESS
&& ( mcis.what & MEMBEROF_IS_GROUP ) )
{
Modifications *ml;
for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) {
if ( ml->sml_desc == mo->mo_ad_member ) {
switch ( ml->sml_op ) {
case LDAP_MOD_DELETE:
case LDAP_MOD_REPLACE:
case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */
save_member = 1;
break;
}
}
}
if ( MEMBEROF_DANGLING_CHECK( mo )
&& !get_relax( op ) )
{
op->o_dn = op->o_bd->be_rootdn;
op->o_ndn = op->o_bd->be_rootndn;
op->o_bd->bd_info = (BackendInfo *)on->on_info;
assert( op->orm_modlist != NULL );
for ( mlp = &op->orm_modlist; *mlp; ) {
Modifications *ml = *mlp;
int i;
if ( !is_ad_subtype( ml->sml_desc, mo->mo_ad_member ) ) {
mlp = &ml->sml_next;
continue;
}
switch ( ml->sml_op ) {
case LDAP_MOD_DELETE:
case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */
/* we don't care about cancellations: if the value
* exists, fine; if it doesn't, we let the underlying
* database fail as appropriate; */
mlp = &ml->sml_next;
break;
case LDAP_MOD_REPLACE:
/* Handle this just like a delete (see above) */
if ( !ml->sml_values ) {
mlp = &ml->sml_next;
break;
}
case LDAP_MOD_ADD:
case SLAP_MOD_SOFTADD: /* ITS#7487 */
case SLAP_MOD_ADD_IF_NOT_PRESENT: /* ITS#7487 */
/* NOTE: right now, the attributeType we use
* for member must have a normalized value */
assert( ml->sml_nvalues != NULL );
for ( i = 0; !BER_BVISNULL( &ml->sml_nvalues[ i ] ); i++ ) {
Entry *e;
/* ITS#6670 Ignore member pointing to this entry */
if ( dn_match( &ml->sml_nvalues[i], &save_ndn ))
continue;
if ( be_entry_get_rw( op, &ml->sml_nvalues[ i ],
NULL, NULL, 0, &e ) == LDAP_SUCCESS )
{
be_entry_release_r( op, e );
continue;
}
if ( MEMBEROF_DANGLING_ERROR( mo ) ) {
rc = rs->sr_err = mo->mo_dangling_err;
rs->sr_text = "adding non-existing object "
"as group member";
send_ldap_result( op, rs );
goto done;
}
if ( MEMBEROF_DANGLING_DROP( mo ) ) {
int j;
Debug( LDAP_DEBUG_ANY, "%s: memberof_op_modify(\"%s\"): "
"member=\"%s\" does not exist (stripping...)\n",
op->o_log_prefix, op->o_req_dn.bv_val,
ml->sml_nvalues[ i ].bv_val );
for ( j = i + 1; !BER_BVISNULL( &ml->sml_nvalues[ j ] ); j++ );
ber_memfree( ml->sml_values[ i ].bv_val );
BER_BVZERO( &ml->sml_values[ i ] );
ber_memfree( ml->sml_nvalues[ i ].bv_val );
BER_BVZERO( &ml->sml_nvalues[ i ] );
ml->sml_numvals--;
if ( j - i == 1 ) {
break;
}
AC_MEMCPY( &ml->sml_values[ i ], &ml->sml_values[ i + 1 ],
sizeof( struct berval ) * ( j - i ) );
AC_MEMCPY( &ml->sml_nvalues[ i ], &ml->sml_nvalues[ i + 1 ],
sizeof( struct berval ) * ( j - i ) );
i--;
}
}
if ( BER_BVISNULL( &ml->sml_nvalues[ 0 ] ) ) {
*mlp = ml->sml_next;
slap_mod_free( &ml->sml_mod, 0 );
free( ml );
} else {
mlp = &ml->sml_next;
}
break;
default:
assert( 0 );
}
}
}
}
if ( mmlp != NULL ) {
Modifications *ml = *mmlp;
int i;
Entry *target;
op->o_bd->bd_info = (BackendInfo *)on->on_info;
rc = be_entry_get_rw( op, &op->o_req_ndn,
NULL, NULL, 0, &target );
op->o_bd->bd_info = (BackendInfo *)on;
if ( rc != LDAP_SUCCESS ) {
rc = rs->sr_err = LDAP_NO_SUCH_OBJECT;
send_ldap_result( op, rs );
goto done;
}
switch ( ml->sml_op ) {
case LDAP_MOD_DELETE:
case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */
if ( ml->sml_nvalues != NULL ) {
AccessControlState acl_state = ACL_STATE_INIT;
for ( i = 0; !BER_BVISNULL( &ml->sml_nvalues[ i ] ); i++ ) {
Entry *e;
op->o_bd->bd_info = (BackendInfo *)on->on_info;
/* access is checked with the original identity */
rc = access_allowed( op, target,
mo->mo_ad_memberof,
&ml->sml_nvalues[ i ],
ACL_WDEL,
&acl_state );
if ( rc == 0 ) {
rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
rs->sr_text = NULL;
send_ldap_result( op, rs );
goto done2;
}
rc = be_entry_get_rw( op, &ml->sml_nvalues[ i ],
NULL, NULL, 0, &e );
op->o_bd->bd_info = (BackendInfo *)on;
if ( rc != LDAP_SUCCESS ) {
if ( get_relax( op ) ) {
continue;
}
if ( MEMBEROF_DANGLING_ERROR( mo ) ) {
rc = rs->sr_err = mo->mo_dangling_err;
rs->sr_text = "deleting non-existing object "
"as memberof";
send_ldap_result( op, rs );
goto done2;
}
if ( MEMBEROF_DANGLING_DROP( mo ) ) {
int j;
Debug( LDAP_DEBUG_ANY, "%s: memberof_op_modify(\"%s\"): "
"memberof=\"%s\" does not exist (stripping...)\n",
op->o_log_prefix, op->o_req_ndn.bv_val,
ml->sml_nvalues[ i ].bv_val );
for ( j = i + 1; !BER_BVISNULL( &ml->sml_nvalues[ j ] ); j++ );
ber_memfree( ml->sml_values[ i ].bv_val );
BER_BVZERO( &ml->sml_values[ i ] );
if ( ml->sml_nvalues != ml->sml_values ) {
ber_memfree( ml->sml_nvalues[ i ].bv_val );
BER_BVZERO( &ml->sml_nvalues[ i ] );
}
ml->sml_numvals--;
if ( j - i == 1 ) {
break;
}
AC_MEMCPY( &ml->sml_values[ i ], &ml->sml_values[ i + 1 ],
sizeof( struct berval ) * ( j - i ) );
if ( ml->sml_nvalues != ml->sml_values ) {
AC_MEMCPY( &ml->sml_nvalues[ i ], &ml->sml_nvalues[ i + 1 ],
sizeof( struct berval ) * ( j - i ) );
}
i--;
}
continue;
}
/* access is checked with the original identity */
op->o_bd->bd_info = (BackendInfo *)on->on_info;
rc = access_allowed( op, e, mo->mo_ad_member,
&op->o_req_ndn,
ACL_WDEL, NULL );
be_entry_release_r( op, e );
op->o_bd->bd_info = (BackendInfo *)on;
if ( !rc ) {
rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
rs->sr_text = "insufficient access to object referenced by memberof";
send_ldap_result( op, rs );
goto done;
}
}
if ( BER_BVISNULL( &ml->sml_nvalues[ 0 ] ) ) {
*mmlp = ml->sml_next;
slap_mod_free( &ml->sml_mod, 0 );
free( ml );
}
break;
}
/* fall thru */
case LDAP_MOD_REPLACE:
op->o_bd->bd_info = (BackendInfo *)on->on_info;
/* access is checked with the original identity */
rc = access_allowed( op, target,
mo->mo_ad_memberof,
NULL,
ACL_WDEL, NULL );
op->o_bd->bd_info = (BackendInfo *)on;
if ( rc == 0 ) {
rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
rs->sr_text = NULL;
send_ldap_result( op, rs );
goto done2;
}
if ( ml->sml_op == LDAP_MOD_DELETE || ml->sml_op == SLAP_MOD_SOFTDEL || !ml->sml_values ) {
break;
}
/* fall thru */
case LDAP_MOD_ADD:
case SLAP_MOD_SOFTADD: /* ITS#7487 */
case SLAP_MOD_ADD_IF_NOT_PRESENT: /* ITS#7487 */
{
AccessControlState acl_state = ACL_STATE_INIT;
for ( i = 0; !BER_BVISNULL( &ml->sml_nvalues[ i ] ); i++ ) {
Entry *e;
op->o_bd->bd_info = (BackendInfo *)on->on_info;
/* access is checked with the original identity */
rc = access_allowed( op, target,
mo->mo_ad_memberof,
&ml->sml_nvalues[ i ],
ACL_WADD,
&acl_state );
if ( rc == 0 ) {
rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
rs->sr_text = NULL;
send_ldap_result( op, rs );
goto done2;
}
/* ITS#6670 Ignore member pointing to this entry */
if ( dn_match( &ml->sml_nvalues[i], &save_ndn ))
continue;
rc = be_entry_get_rw( op, &ml->sml_nvalues[ i ],
NULL, NULL, 0, &e );
op->o_bd->bd_info = (BackendInfo *)on;
if ( rc != LDAP_SUCCESS ) {
if ( MEMBEROF_DANGLING_ERROR( mo ) ) {
rc = rs->sr_err = mo->mo_dangling_err;
rs->sr_text = "adding non-existing object "
"as memberof";
send_ldap_result( op, rs );
goto done2;
}
if ( MEMBEROF_DANGLING_DROP( mo ) ) {
int j;
Debug( LDAP_DEBUG_ANY, "%s: memberof_op_modify(\"%s\"): "
"memberof=\"%s\" does not exist (stripping...)\n",
op->o_log_prefix, op->o_req_ndn.bv_val,
ml->sml_nvalues[ i ].bv_val );
for ( j = i + 1; !BER_BVISNULL( &ml->sml_nvalues[ j ] ); j++ );
ber_memfree( ml->sml_values[ i ].bv_val );
BER_BVZERO( &ml->sml_values[ i ] );
if ( ml->sml_nvalues != ml->sml_values ) {
ber_memfree( ml->sml_nvalues[ i ].bv_val );
BER_BVZERO( &ml->sml_nvalues[ i ] );
}
ml->sml_numvals--;
if ( j - i == 1 ) {
break;
}
AC_MEMCPY( &ml->sml_values[ i ], &ml->sml_values[ i + 1 ],
sizeof( struct berval ) * ( j - i ) );
if ( ml->sml_nvalues != ml->sml_values ) {
AC_MEMCPY( &ml->sml_nvalues[ i ], &ml->sml_nvalues[ i + 1 ],
sizeof( struct berval ) * ( j - i ) );
}
i--;
}
continue;
}
/* access is checked with the original identity */
op->o_bd->bd_info = (BackendInfo *)on->on_info;
rc = access_allowed( op, e, mo->mo_ad_member,
&op->o_req_ndn,
ACL_WDEL, NULL );
be_entry_release_r( op, e );
op->o_bd->bd_info = (BackendInfo *)on;
if ( !rc ) {
rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
rs->sr_text = "insufficient access to object referenced by memberof";
send_ldap_result( op, rs );
goto done;
}
}
if ( BER_BVISNULL( &ml->sml_nvalues[ 0 ] ) ) {
*mmlp = ml->sml_next;
slap_mod_free( &ml->sml_mod, 0 );
free( ml );
}
} break;
default:
assert( 0 );
}
done2:;
op->o_bd->bd_info = (BackendInfo *)on->on_info;
be_entry_release_r( op, target );
op->o_bd->bd_info = (BackendInfo *)on;
}
sc = op->o_tmpalloc( sizeof(slap_callback)+sizeof(*mci), op->o_tmpmemctx );
sc->sc_private = sc+1;
sc->sc_response = memberof_res_modify;
sc->sc_cleanup = memberof_cleanup;
sc->sc_writewait = 0;
mci = sc->sc_private;
mci->on = on;
mci->member = NULL;
mci->memberof = NULL;
mci->what = mcis.what;
if ( save_member ) {
op->o_dn = op->o_bd->be_rootdn;
op->o_ndn = op->o_bd->be_rootndn;
op->o_bd->bd_info = (BackendInfo *)on->on_info;
rc = backend_attribute( op, NULL, &op->o_req_ndn,
mo->mo_ad_member, &mci->member, ACL_READ );
op->o_bd->bd_info = (BackendInfo *)on;
}
sc->sc_next = op->o_callback;
op->o_callback = sc;
rc = SLAP_CB_CONTINUE;
done:;
op->o_dn = save_dn;
op->o_ndn = save_ndn;
op->o_bd->bd_info = (BackendInfo *)on;
return rc;
}
static int
memberof_op_modrdn( Operation *op, SlapReply *rs )
{
slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
slap_callback *sc;
memberof_cbinfo_t *mci;
OpExtra *oex;
LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) {
if ( oex->oe_key == (void *)&memberof )
return SLAP_CB_CONTINUE;
}
sc = op->o_tmpalloc( sizeof(slap_callback)+sizeof(*mci), op->o_tmpmemctx );
sc->sc_private = sc+1;
sc->sc_response = memberof_res_modrdn;
sc->sc_cleanup = memberof_cleanup;
sc->sc_writewait = 0;
mci = sc->sc_private;
mci->on = on;
mci->member = NULL;
mci->memberof = NULL;
sc->sc_next = op->o_callback;
op->o_callback = sc;
return SLAP_CB_CONTINUE;
}
/*
* response callback that adds memberof values when a group is added.
*/
static int
memberof_res_add( Operation *op, SlapReply *rs )
{
memberof_cbinfo_t *mci = op->o_callback->sc_private;
slap_overinst *on = mci->on;
memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
int i;
if ( rs->sr_err != LDAP_SUCCESS ) {
return SLAP_CB_CONTINUE;
}
if ( MEMBEROF_REVERSE( mo ) ) {
Attribute *ma;
ma = attr_find( op->ora_e->e_attrs, mo->mo_ad_memberof );
if ( ma != NULL ) {
/* relax is required to allow to add
* a non-existing member */
op->o_relax = SLAP_CONTROL_CRITICAL;
for ( i = 0; !BER_BVISNULL( &ma->a_nvals[ i ] ); i++ ) {
/* ITS#6670 Ignore member pointing to this entry */
if ( dn_match( &ma->a_nvals[i], &op->o_req_ndn ))
continue;
/* the modification is attempted
* with the original identity */
memberof_value_modify( op,
&ma->a_nvals[ i ], mo->mo_ad_member,
NULL, NULL, &op->o_req_dn, &op->o_req_ndn );
}
}
}
if ( is_entry_objectclass_or_sub( op->ora_e, mo->mo_oc_group ) ) {
Attribute *a;
for ( a = attrs_find( op->ora_e->e_attrs, mo->mo_ad_member );
a != NULL;
a = attrs_find( a->a_next, mo->mo_ad_member ) )
{
for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) {
/* ITS#6670 Ignore member pointing to this entry */
if ( dn_match( &a->a_nvals[i], &op->o_req_ndn ))
continue;
memberof_value_modify( op,
&a->a_nvals[ i ],
mo->mo_ad_memberof,
NULL, NULL,
&op->o_req_dn,
&op->o_req_ndn );
}
}
}
return SLAP_CB_CONTINUE;
}
/*
* response callback that deletes memberof values when a group is deleted.
*/
static int
memberof_res_delete( Operation *op, SlapReply *rs )
{
memberof_cbinfo_t *mci = op->o_callback->sc_private;
slap_overinst *on = mci->on;
memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
BerVarray vals;
int i;
if ( rs->sr_err != LDAP_SUCCESS ) {
return SLAP_CB_CONTINUE;
}
vals = mci->member;
if ( vals != NULL ) {
for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
memberof_value_modify( op,
&vals[ i ], mo->mo_ad_memberof,
&op->o_req_dn, &op->o_req_ndn,
NULL, NULL );
}
}
if ( MEMBEROF_REFINT( mo ) ) {
vals = mci->memberof;
if ( vals != NULL ) {
for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
memberof_value_modify( op,
&vals[ i ], mo->mo_ad_member,
&op->o_req_dn, &op->o_req_ndn,
NULL, NULL );
}
}
}
return SLAP_CB_CONTINUE;
}
/*
* response callback that adds/deletes memberof values when a group
* is modified.
*/
static int
memberof_res_modify( Operation *op, SlapReply *rs )
{
memberof_cbinfo_t *mci = op->o_callback->sc_private;
slap_overinst *on = mci->on;
memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
int i, rc;
Modifications *ml, *mml = NULL;
BerVarray vals;
if ( rs->sr_err != LDAP_SUCCESS ) {
return SLAP_CB_CONTINUE;
}
if ( MEMBEROF_REVERSE( mo ) ) {
for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) {
if ( ml->sml_desc == mo->mo_ad_memberof ) {
mml = ml;
break;
}
}
}
if ( mml != NULL ) {
BerVarray vals = mml->sml_nvalues;
switch ( mml->sml_op ) {
case LDAP_MOD_DELETE:
case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */
if ( vals != NULL ) {
for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
memberof_value_modify( op,
&vals[ i ], mo->mo_ad_member,
&op->o_req_dn, &op->o_req_ndn,
NULL, NULL );
}
break;
}
/* fall thru */
case LDAP_MOD_REPLACE:
/* delete all ... */
op->o_bd->bd_info = (BackendInfo *)on->on_info;
rc = backend_attribute( op, NULL, &op->o_req_ndn,
mo->mo_ad_memberof, &vals, ACL_READ );
op->o_bd->bd_info = (BackendInfo *)on;
if ( rc == LDAP_SUCCESS ) {
for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
memberof_value_modify( op,
&vals[ i ], mo->mo_ad_member,
&op->o_req_dn, &op->o_req_ndn,
NULL, NULL );
}
ber_bvarray_free_x( vals, op->o_tmpmemctx );
}
if ( ml->sml_op == LDAP_MOD_DELETE || !mml->sml_values ) {
break;
}
/* fall thru */
case LDAP_MOD_ADD:
case SLAP_MOD_SOFTADD: /* ITS#7487 */
case SLAP_MOD_ADD_IF_NOT_PRESENT: /* ITS#7487 */
assert( vals != NULL );
for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
memberof_value_modify( op,
&vals[ i ], mo->mo_ad_member,
NULL, NULL,
&op->o_req_dn, &op->o_req_ndn );
}
break;
default:
assert( 0 );
}
}
if ( mci->what & MEMBEROF_IS_GROUP )
{
for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) {
if ( ml->sml_desc != mo->mo_ad_member ) {
continue;
}
switch ( ml->sml_op ) {
case LDAP_MOD_DELETE:
case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */
vals = ml->sml_nvalues;
if ( vals != NULL ) {
for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
memberof_value_modify( op,
&vals[ i ], mo->mo_ad_memberof,
&op->o_req_dn, &op->o_req_ndn,
NULL, NULL );
}
break;
}
/* fall thru */
case LDAP_MOD_REPLACE:
vals = mci->member;
/* delete all ... */
if ( vals != NULL ) {
for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
memberof_value_modify( op,
&vals[ i ], mo->mo_ad_memberof,
&op->o_req_dn, &op->o_req_ndn,
NULL, NULL );
}
}
if ( ml->sml_op == LDAP_MOD_DELETE || ml->sml_op == SLAP_MOD_SOFTDEL || !ml->sml_values ) {
break;
}
/* fall thru */
case LDAP_MOD_ADD:
case SLAP_MOD_SOFTADD: /* ITS#7487 */
case SLAP_MOD_ADD_IF_NOT_PRESENT : /* ITS#7487 */
assert( ml->sml_nvalues != NULL );
vals = ml->sml_nvalues;
for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
memberof_value_modify( op,
&vals[ i ], mo->mo_ad_memberof,
NULL, NULL,
&op->o_req_dn, &op->o_req_ndn );
}
break;
default:
assert( 0 );
}
}
}
return SLAP_CB_CONTINUE;
}
/*
* response callback that adds/deletes member values when a group member
* is renamed.
*/
static int
memberof_res_modrdn( Operation *op, SlapReply *rs )
{
memberof_cbinfo_t *mci = op->o_callback->sc_private;
slap_overinst *on = mci->on;
memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
struct berval newPDN, newDN = BER_BVNULL, newPNDN, newNDN;
int i, rc;
BerVarray vals;
struct berval save_dn, save_ndn;
if ( rs->sr_err != LDAP_SUCCESS ) {
return SLAP_CB_CONTINUE;
}
mci->what = MEMBEROF_IS_GROUP;
if ( MEMBEROF_REFINT( mo ) ) {
mci->what |= MEMBEROF_IS_MEMBER;
}
if ( op->orr_nnewSup ) {
newPNDN = *op->orr_nnewSup;
} else {
dnParent( &op->o_req_ndn, &newPNDN );
}
build_new_dn( &newNDN, &newPNDN, &op->orr_nnewrdn, op->o_tmpmemctx );
save_dn = op->o_req_dn;
save_ndn = op->o_req_ndn;
op->o_req_dn = newNDN;
op->o_req_ndn = newNDN;
rc = memberof_isGroupOrMember( op, mci );
op->o_req_dn = save_dn;
op->o_req_ndn = save_ndn;
if ( rc != LDAP_SUCCESS || mci->what == MEMBEROF_IS_NONE ) {
goto done;
}
if ( op->orr_newSup ) {
newPDN = *op->orr_newSup;
} else {
dnParent( &op->o_req_dn, &newPDN );
}
build_new_dn( &newDN, &newPDN, &op->orr_newrdn, op->o_tmpmemctx );
if ( mci->what & MEMBEROF_IS_GROUP ) {
op->o_bd->bd_info = (BackendInfo *)on->on_info;
rc = backend_attribute( op, NULL, &newNDN,
mo->mo_ad_member, &vals, ACL_READ );
op->o_bd->bd_info = (BackendInfo *)on;
if ( rc == LDAP_SUCCESS ) {
for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
memberof_value_modify( op,
&vals[ i ], mo->mo_ad_memberof,
&op->o_req_dn, &op->o_req_ndn,
&newDN, &newNDN );
}
ber_bvarray_free_x( vals, op->o_tmpmemctx );
}
}
if ( MEMBEROF_REFINT( mo ) && ( mci->what & MEMBEROF_IS_MEMBER ) ) {
op->o_bd->bd_info = (BackendInfo *)on->on_info;
rc = backend_attribute( op, NULL, &newNDN,
mo->mo_ad_memberof, &vals, ACL_READ );
op->o_bd->bd_info = (BackendInfo *)on;
if ( rc == LDAP_SUCCESS ) {
for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
memberof_value_modify( op,
&vals[ i ], mo->mo_ad_member,
&op->o_req_dn, &op->o_req_ndn,
&newDN, &newNDN );
}
ber_bvarray_free_x( vals, op->o_tmpmemctx );
}
}
done:;
if ( !BER_BVISNULL( &newDN ) ) {
op->o_tmpfree( newDN.bv_val, op->o_tmpmemctx );
}
op->o_tmpfree( newNDN.bv_val, op->o_tmpmemctx );
return SLAP_CB_CONTINUE;
}
static int
memberof_db_init(
BackendDB *be,
ConfigReply *cr )
{
slap_overinst *on = (slap_overinst *)be->bd_info;
memberof_t *mo;
const char *text = NULL;
int rc;
mo = (memberof_t *)ch_calloc( 1, sizeof( memberof_t ) );
/* safe default */
mo->mo_dangling_err = LDAP_CONSTRAINT_VIOLATION;
if ( !ad_memberOf ) {
rc = slap_str2ad( SLAPD_MEMBEROF_ATTR, &ad_memberOf, &text );
if ( rc != LDAP_SUCCESS ) {
Debug( LDAP_DEBUG_ANY, "memberof_db_init: "
"unable to find attribute=\"%s\": %s (%d)\n",
SLAPD_MEMBEROF_ATTR, text, rc );
return rc;
}
}
if ( !ad_member ) {
rc = slap_str2ad( SLAPD_GROUP_ATTR, &ad_member, &text );
if ( rc != LDAP_SUCCESS ) {
Debug( LDAP_DEBUG_ANY, "memberof_db_init: "
"unable to find attribute=\"%s\": %s (%d)\n",
SLAPD_GROUP_ATTR, text, rc );
return rc;
}
}
if ( !oc_group ) {
oc_group = oc_find( SLAPD_GROUP_CLASS );
if ( oc_group == NULL ) {
Debug( LDAP_DEBUG_ANY,
"memberof_db_init: "
"unable to find objectClass=\"%s\"\n",
SLAPD_GROUP_CLASS );
return 1;
}
}
on->on_bi.bi_private = (void *)mo;
return 0;
}
enum {
MO_DN = 1,
MO_DANGLING,
MO_REFINT,
MO_GROUP_OC,
MO_MEMBER_AD,
MO_MEMBER_OF_AD,
#if 0
MO_REVERSE,
#endif
MO_DANGLING_ERROR,
MO_LAST
};
static ConfigDriver mo_cf_gen;
#define OID "1.3.6.1.4.1.7136.2.666.4"
#define OIDAT OID ".1.1"
#define OIDCFGAT OID ".1.2"
#define OIDOC OID ".2.1"
#define OIDCFGOC OID ".2.2"
static ConfigTable mo_cfg[] = {
{ "memberof-dn", "modifiersName",
2, 2, 0, ARG_MAGIC|ARG_QUOTE|ARG_DN|MO_DN, mo_cf_gen,
"( OLcfgOvAt:18.0 NAME 'olcMemberOfDN' "
"DESC 'DN to be used as modifiersName' "
"EQUALITY distinguishedNameMatch "
"SYNTAX OMsDN SINGLE-VALUE )",
NULL, NULL },
{ "memberof-dangling", "ignore|drop|error",
2, 2, 0, ARG_MAGIC|MO_DANGLING, mo_cf_gen,
"( OLcfgOvAt:18.1 NAME 'olcMemberOfDangling' "
"DESC 'Behavior with respect to dangling members, "
"constrained to ignore, drop, error' "
"EQUALITY caseIgnoreMatch "
"SYNTAX OMsDirectoryString SINGLE-VALUE )",
NULL, NULL },
{ "memberof-refint", "true|FALSE",
2, 2, 0, ARG_MAGIC|ARG_ON_OFF|MO_REFINT, mo_cf_gen,
"( OLcfgOvAt:18.2 NAME 'olcMemberOfRefInt' "
"DESC 'Take care of referential integrity' "
"EQUALITY booleanMatch "
"SYNTAX OMsBoolean SINGLE-VALUE )",
NULL, NULL },
{ "memberof-group-oc", "objectClass",
2, 2, 0, ARG_MAGIC|MO_GROUP_OC, mo_cf_gen,
"( OLcfgOvAt:18.3 NAME 'olcMemberOfGroupOC' "
"DESC 'Group objectClass' "
"EQUALITY caseIgnoreMatch "
"SYNTAX OMsDirectoryString SINGLE-VALUE )",
NULL, NULL },
{ "memberof-member-ad", "member attribute",
2, 2, 0, ARG_MAGIC|ARG_ATDESC|MO_MEMBER_AD, mo_cf_gen,
"( OLcfgOvAt:18.4 NAME 'olcMemberOfMemberAD' "
"DESC 'member attribute' "
"EQUALITY caseIgnoreMatch "
"SYNTAX OMsDirectoryString SINGLE-VALUE )",
NULL, NULL },
{ "memberof-memberof-ad", "memberOf attribute",
2, 2, 0, ARG_MAGIC|ARG_ATDESC|MO_MEMBER_OF_AD, mo_cf_gen,
"( OLcfgOvAt:18.5 NAME 'olcMemberOfMemberOfAD' "
"DESC 'memberOf attribute' "
"EQUALITY caseIgnoreMatch "
"SYNTAX OMsDirectoryString SINGLE-VALUE )",
NULL, NULL },
#if 0
{ "memberof-reverse", "true|FALSE",
2, 2, 0, ARG_MAGIC|ARG_ON_OFF|MO_REVERSE, mo_cf_gen,
"( OLcfgOvAt:18.6 NAME 'olcMemberOfReverse' "
"DESC 'Take care of referential integrity "
"also when directly modifying memberOf' "
"SYNTAX OMsBoolean SINGLE-VALUE )",
NULL, NULL },
#endif
{ "memberof-dangling-error", "error code",
2, 2, 0, ARG_MAGIC|MO_DANGLING_ERROR, mo_cf_gen,
"( OLcfgOvAt:18.7 NAME 'olcMemberOfDanglingError' "
"DESC 'Error code returned in case of dangling back reference' "
"EQUALITY caseIgnoreMatch "
"SYNTAX OMsDirectoryString SINGLE-VALUE )",
NULL, NULL },
{ NULL, NULL, 0, 0, 0, ARG_IGNORED }
};
static ConfigOCs mo_ocs[] = {
{ "( OLcfgOvOc:18.1 "
"NAME ( 'olcMemberOfConfig' 'olcMemberOf' ) "
"DESC 'Member-of configuration' "
"SUP olcOverlayConfig "
"MAY ( "
"olcMemberOfDN "
"$ olcMemberOfDangling "
"$ olcMemberOfDanglingError"
"$ olcMemberOfRefInt "
"$ olcMemberOfGroupOC "
"$ olcMemberOfMemberAD "
"$ olcMemberOfMemberOfAD "
#if 0
"$ olcMemberOfReverse "
#endif
") "
")",
Cft_Overlay, mo_cfg, NULL, NULL },
{ NULL, 0, NULL }
};
static slap_verbmasks dangling_mode[] = {
{ BER_BVC( "ignore" ), MEMBEROF_NONE },
{ BER_BVC( "drop" ), MEMBEROF_FDANGLING_DROP },
{ BER_BVC( "error" ), MEMBEROF_FDANGLING_ERROR },
{ BER_BVNULL, 0 }
};
static int
memberof_make_group_filter( memberof_t *mo )
{
char *ptr;
if ( !BER_BVISNULL( &mo->mo_groupFilterstr ) ) {
ch_free( mo->mo_groupFilterstr.bv_val );
}
mo->mo_groupFilter.f_choice = LDAP_FILTER_EQUALITY;
mo->mo_groupFilter.f_ava = &mo->mo_groupAVA;
mo->mo_groupFilter.f_av_desc = slap_schema.si_ad_objectClass;
mo->mo_groupFilter.f_av_value = mo->mo_oc_group->soc_cname;
mo->mo_groupFilterstr.bv_len = STRLENOF( "(=)" )
+ slap_schema.si_ad_objectClass->ad_cname.bv_len
+ mo->mo_oc_group->soc_cname.bv_len;
ptr = mo->mo_groupFilterstr.bv_val = ch_malloc( mo->mo_groupFilterstr.bv_len + 1 );
*ptr++ = '(';
ptr = lutil_strcopy( ptr, slap_schema.si_ad_objectClass->ad_cname.bv_val );
*ptr++ = '=';
ptr = lutil_strcopy( ptr, mo->mo_oc_group->soc_cname.bv_val );
*ptr++ = ')';
*ptr = '\0';
return 0;
}
static int
memberof_make_member_filter( memberof_t *mo )
{
char *ptr;
if ( !BER_BVISNULL( &mo->mo_memberFilterstr ) ) {
ch_free( mo->mo_memberFilterstr.bv_val );
}
mo->mo_memberFilter.f_choice = LDAP_FILTER_PRESENT;
mo->mo_memberFilter.f_desc = mo->mo_ad_memberof;
mo->mo_memberFilterstr.bv_len = STRLENOF( "(=*)" )
+ mo->mo_ad_memberof->ad_cname.bv_len;
ptr = mo->mo_memberFilterstr.bv_val = ch_malloc( mo->mo_memberFilterstr.bv_len + 1 );
*ptr++ = '(';
ptr = lutil_strcopy( ptr, mo->mo_ad_memberof->ad_cname.bv_val );
ptr = lutil_strcopy( ptr, "=*)" );
return 0;
}
static int
mo_cf_gen( ConfigArgs *c )
{
slap_overinst *on = (slap_overinst *)c->bi;
memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
int i, rc = 0;
if ( c->op == SLAP_CONFIG_EMIT ) {
struct berval bv = BER_BVNULL;
switch( c->type ) {
case MO_DN:
if ( mo->mo_dn.bv_val != NULL) {
value_add_one( &c->rvalue_vals, &mo->mo_dn );
value_add_one( &c->rvalue_nvals, &mo->mo_ndn );
}
break;
case MO_DANGLING:
enum_to_verb( dangling_mode, (mo->mo_flags & MEMBEROF_FDANGLING_MASK), &bv );
if ( BER_BVISNULL( &bv ) ) {
/* there's something wrong... */
assert( 0 );
rc = 1;
} else {
value_add_one( &c->rvalue_vals, &bv );
}
break;
case MO_DANGLING_ERROR:
if ( mo->mo_flags & MEMBEROF_FDANGLING_ERROR ) {
char buf[ SLAP_TEXT_BUFLEN ];
enum_to_verb( slap_ldap_response_code, mo->mo_dangling_err, &bv );
if ( BER_BVISNULL( &bv ) ) {
bv.bv_len = snprintf( buf, sizeof( buf ), "0x%x", mo->mo_dangling_err );
if ( bv.bv_len < sizeof( buf ) ) {
bv.bv_val = buf;
} else {
rc = 1;
break;
}
}
value_add_one( &c->rvalue_vals, &bv );
} else {
rc = 1;
}
break;
case MO_REFINT:
c->value_int = MEMBEROF_REFINT( mo );
break;
#if 0
case MO_REVERSE:
c->value_int = MEMBEROF_REVERSE( mo );
break;
#endif
case MO_GROUP_OC:
if ( mo->mo_oc_group != NULL ){
value_add_one( &c->rvalue_vals, &mo->mo_oc_group->soc_cname );
}
break;
case MO_MEMBER_AD:
c->value_ad = mo->mo_ad_member;
break;
case MO_MEMBER_OF_AD:
c->value_ad = mo->mo_ad_memberof;
break;
default:
assert( 0 );
return 1;
}
return rc;
} else if ( c->op == LDAP_MOD_DELETE ) {
switch( c->type ) {
case MO_DN:
if ( !BER_BVISNULL( &mo->mo_dn ) ) {
ber_memfree( mo->mo_dn.bv_val );
ber_memfree( mo->mo_ndn.bv_val );
BER_BVZERO( &mo->mo_dn );
BER_BVZERO( &mo->mo_ndn );
}
break;
case MO_DANGLING:
mo->mo_flags &= ~MEMBEROF_FDANGLING_MASK;
break;
case MO_DANGLING_ERROR:
mo->mo_dangling_err = LDAP_CONSTRAINT_VIOLATION;
break;
case MO_REFINT:
mo->mo_flags &= ~MEMBEROF_FREFINT;
break;
#if 0
case MO_REVERSE:
mo->mo_flags &= ~MEMBEROF_FREVERSE;
break;
#endif
case MO_GROUP_OC:
mo->mo_oc_group = oc_group;
memberof_make_group_filter( mo );
break;
case MO_MEMBER_AD:
mo->mo_ad_member = ad_member;
break;
case MO_MEMBER_OF_AD:
mo->mo_ad_memberof = ad_memberOf;
memberof_make_member_filter( mo );
break;
default:
assert( 0 );
return 1;
}
} else {
switch( c->type ) {
case MO_DN:
if ( !BER_BVISNULL( &mo->mo_dn ) ) {
ber_memfree( mo->mo_dn.bv_val );
ber_memfree( mo->mo_ndn.bv_val );
}
mo->mo_dn = c->value_dn;
mo->mo_ndn = c->value_ndn;
break;
case MO_DANGLING:
i = verb_to_mask( c->argv[ 1 ], dangling_mode );
if ( BER_BVISNULL( &dangling_mode[ i ].word ) ) {
return 1;
}
mo->mo_flags &= ~MEMBEROF_FDANGLING_MASK;
mo->mo_flags |= dangling_mode[ i ].mask;
break;
case MO_DANGLING_ERROR:
i = verb_to_mask( c->argv[ 1 ], slap_ldap_response_code );
if ( !BER_BVISNULL( &slap_ldap_response_code[ i ].word ) ) {
mo->mo_dangling_err = slap_ldap_response_code[ i ].mask;
} else if ( lutil_atoix( &mo->mo_dangling_err, c->argv[ 1 ], 0 ) ) {
return 1;
}
break;
case MO_REFINT:
if ( c->value_int ) {
mo->mo_flags |= MEMBEROF_FREFINT;
} else {
mo->mo_flags &= ~MEMBEROF_FREFINT;
}
break;
#if 0
case MO_REVERSE:
if ( c->value_int ) {
mo->mo_flags |= MEMBEROF_FREVERSE;
} else {
mo->mo_flags &= ~MEMBEROF_FREVERSE;
}
break;
#endif
case MO_GROUP_OC: {
ObjectClass *oc = NULL;
oc = oc_find( c->argv[ 1 ] );
if ( oc == NULL ) {
snprintf( c->cr_msg, sizeof( c->cr_msg ),
"unable to find group objectClass=\"%s\"",
c->argv[ 1 ] );
Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n",
c->log, c->cr_msg );
return 1;
}
mo->mo_oc_group = oc;
memberof_make_group_filter( mo );
} break;
case MO_MEMBER_AD: {
AttributeDescription *ad = c->value_ad;
if ( !is_at_syntax( ad->ad_type, SLAPD_DN_SYNTAX ) /* e.g. "member" */
&& !is_at_syntax( ad->ad_type, SLAPD_NAMEUID_SYNTAX ) ) /* e.g. "uniqueMember" */
{
snprintf( c->cr_msg, sizeof( c->cr_msg ),
"member attribute=\"%s\" must either "
"have DN (%s) or nameUID (%s) syntax",
c->argv[ 1 ], SLAPD_DN_SYNTAX, SLAPD_NAMEUID_SYNTAX );
Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n",
c->log, c->cr_msg );
return 1;
}
mo->mo_ad_member = ad;
} break;
case MO_MEMBER_OF_AD: {
AttributeDescription *ad = c->value_ad;
if ( !is_at_syntax( ad->ad_type, SLAPD_DN_SYNTAX ) /* e.g. "member" */
&& !is_at_syntax( 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_CONFIG, "%s: %s.\n",
c->log, c->cr_msg );
return 1;
}
mo->mo_ad_memberof = ad;
memberof_make_member_filter( mo );
} break;
default:
assert( 0 );
return 1;
}
}
return 0;
}
static int
memberof_db_open(
BackendDB *be,
ConfigReply *cr )
{
slap_overinst *on = (slap_overinst *)be->bd_info;
memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
int rc;
if ( !mo->mo_ad_memberof ) {
mo->mo_ad_memberof = ad_memberOf;
}
if ( ! mo->mo_ad_member ) {
mo->mo_ad_member = ad_member;
}
if ( ! mo->mo_oc_group ) {
mo->mo_oc_group = oc_group;
}
if ( BER_BVISNULL( &mo->mo_dn ) && !BER_BVISNULL( &be->be_rootdn ) ) {
ber_dupbv( &mo->mo_dn, &be->be_rootdn );
ber_dupbv( &mo->mo_ndn, &be->be_rootndn );
}
if ( BER_BVISNULL( &mo->mo_groupFilterstr ) ) {
memberof_make_group_filter( mo );
}
if ( BER_BVISNULL( &mo->mo_memberFilterstr ) ) {
memberof_make_member_filter( mo );
}
return 0;
}
static int
memberof_db_destroy(
BackendDB *be,
ConfigReply *cr )
{
slap_overinst *on = (slap_overinst *)be->bd_info;
memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
if ( mo ) {
if ( !BER_BVISNULL( &mo->mo_dn ) ) {
ber_memfree( mo->mo_dn.bv_val );
ber_memfree( mo->mo_ndn.bv_val );
}
if ( !BER_BVISNULL( &mo->mo_groupFilterstr ) ) {
ber_memfree( mo->mo_groupFilterstr.bv_val );
}
if ( !BER_BVISNULL( &mo->mo_memberFilterstr ) ) {
ber_memfree( mo->mo_memberFilterstr.bv_val );
}
ber_memfree( mo );
}
return 0;
}
static struct {
char *desc;
AttributeDescription **adp;
} as[] = {
{ "( 1.2.840.113556.1.2.102 "
"NAME 'memberOf' "
"DESC 'Group that the entry belongs to' "
"SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' "
"EQUALITY distinguishedNameMatch " /* added */
"USAGE dSAOperation " /* added; questioned */
"NO-USER-MODIFICATION " /* added */
"X-ORIGIN 'iPlanet Delegated Administrator' )",
&ad_memberOf },
{ NULL }
};
#if SLAPD_OVER_MEMBEROF == SLAPD_MOD_DYNAMIC
static
#endif /* SLAPD_OVER_MEMBEROF == SLAPD_MOD_DYNAMIC */
int
memberof_initialize( void )
{
int code, i;
for ( i = 0; as[ i ].desc != NULL; i++ ) {
code = register_at( as[ i ].desc, as[ i ].adp, 1 );
if ( code && code != SLAP_SCHERR_ATTR_DUP ) {
Debug( LDAP_DEBUG_ANY,
"memberof_initialize: register_at #%d failed\n",
i );
return code;
}
}
memberof.on_bi.bi_type = "memberof";
memberof.on_bi.bi_db_init = memberof_db_init;
memberof.on_bi.bi_db_open = memberof_db_open;
memberof.on_bi.bi_db_destroy = memberof_db_destroy;
memberof.on_bi.bi_op_add = memberof_op_add;
memberof.on_bi.bi_op_delete = memberof_op_delete;
memberof.on_bi.bi_op_modify = memberof_op_modify;
memberof.on_bi.bi_op_modrdn = memberof_op_modrdn;
memberof.on_bi.bi_cf_ocs = mo_ocs;
code = config_register_schema( mo_cfg, mo_ocs );
if ( code ) return code;
return overlay_register( &memberof );
}
#if SLAPD_OVER_MEMBEROF == SLAPD_MOD_DYNAMIC
int
init_module( int argc, char *argv[] )
{
return memberof_initialize();
}
#endif /* SLAPD_OVER_MEMBEROF == SLAPD_MOD_DYNAMIC */
#endif /* SLAPD_OVER_MEMBEROF */