From e992b8972de6f7de6994a2175ef35879f81b1a20 Mon Sep 17 00:00:00 2001 From: Howard Chu Date: Thu, 1 Feb 2024 18:58:50 +0000 Subject: [PATCH] ITS#10167 slapo-memberof: add addcheck option Check memberships of newly added entries. --- doc/man/man5/slapo-memberof.5 | 22 ++++-- servers/slapd/overlays/memberof.c | 115 ++++++++++++++++++++++++++++++ tests/data/memberof.out | 64 +++++++++++++++++ tests/scripts/test052-memberof | 55 ++++++++++++++ 4 files changed, 252 insertions(+), 4 deletions(-) diff --git a/doc/man/man5/slapo-memberof.5 b/doc/man/man5/slapo-memberof.5 index 45bf1b1c3c..8392f0dad2 100644 --- a/doc/man/man5/slapo-memberof.5 +++ b/doc/man/man5/slapo-memberof.5 @@ -107,6 +107,23 @@ If set to when an entry containing values of the "is member of" attribute is modified, the corresponding groups are modified as well. +.TP +.BI "memberof\-addcheck {" true "|" FALSE "}" +This option determines whether the overlay will check newly added +entries for membership in any existing groups. This check is useful +if populated groups are created in the directory before the entries +they reference. The situation often occurs during replication, which +may replicate entries in random order. +If set to +.IR TRUE , +every Add operation will search for groups referencing the added +entry and populate its memberof attribute with the group DNs. Note +that +.BR memberof\-dangling +must be left on its default setting of +.I ignore +for this option to work. + .LP The memberof overlay may be used with any backend that provides full read-write functionality, but it is mainly intended for use @@ -114,10 +131,7 @@ with local storage backends. The maintenance operations it performs are internal to the server on which the overlay is configured and are never replicated. Consumer servers should be configured with their own instances of the memberOf overlay if it is desired to maintain -these memberOf attributes on the consumers. Note that slapo-memberOf -is not compatible with syncrepl based replication, and should not be -used in a replicated environment. An alternative is to use slapo-dynlist -to emulate slapo-memberOf behavior. +these memberOf attributes on the consumers. .SH FILES .TP diff --git a/servers/slapd/overlays/memberof.c b/servers/slapd/overlays/memberof.c index 5affbbf3f5..aed6629ba5 100644 --- a/servers/slapd/overlays/memberof.c +++ b/servers/slapd/overlays/memberof.c @@ -159,6 +159,7 @@ typedef struct memberof_t { #define MEMBEROF_FDANGLING_MASK (MEMBEROF_FDANGLING_DROP|MEMBEROF_FDANGLING_ERROR) #define MEMBEROF_FREFINT 0x04U #define MEMBEROF_FREVERSE 0x08U +#define MEMBEROF_FADDCHECK 0x10U ber_int_t mo_dangling_err; @@ -174,6 +175,8 @@ typedef struct memberof_t { MEMBEROF_CHK((mo),MEMBEROF_FREFINT) #define MEMBEROF_REVERSE(mo) \ MEMBEROF_CHK((mo),MEMBEROF_FREVERSE) +#define MEMBEROF_ADDCHECK(mo) \ + MEMBEROF_CHK((mo),MEMBEROF_FADDCHECK) } memberof_t; typedef enum memberof_is_t { @@ -521,6 +524,87 @@ 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 ); +typedef struct mo_addcheck_t { + memberof_t *ma_mo; + Entry *ma_e; + Attribute *ma_a; +} mo_addcheck_t; + +static int memberof_res_addcheck( Operation *op, SlapReply *rs ) +{ + mo_addcheck_t *ma = op->o_callback->sc_private; + if ( rs->sr_type == REP_SEARCH ) { + if ( !ma->ma_a ) { + attr_merge_one( ma->ma_e, ma->ma_mo->mo_ad_memberof, + &rs->sr_entry->e_name, &rs->sr_entry->e_nname ); + ma->ma_a = attr_find( ma->ma_e->e_attrs, ma->ma_mo->mo_ad_memberof ); + } else { + if ( attr_valfind( ma->ma_a, SLAP_MR_EQUALITY | SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH | + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH, &rs->sr_entry->e_nname, NULL, NULL )) { + attr_valadd( ma->ma_a, &rs->sr_entry->e_name, &rs->sr_entry->e_nname, 1 ); + } + } + } + return 0; +} + +/* Check if an entry being added is already a member of existing groups; + * add those groups to the entry's memberof if any. + */ +static void +memberof_addcheck( Operation *op ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + Operation o = *op; + Filter mf; + AttributeAssertion mava; + slap_callback sc = {0}; + mo_addcheck_t ma; + SlapReply rs = {REP_SEARCH}; + + o.o_dn = op->o_bd->be_rootdn; + o.o_ndn = op->o_bd->be_rootndn; + o.o_bd->bd_info = (BackendInfo *)on->on_info; + o.o_tag = LDAP_REQ_SEARCH; + o.o_req_dn = op->o_bd->be_suffix[0]; + o.o_req_ndn = op->o_bd->be_nsuffix[0]; + o.o_do_not_cache = 1; + o.ors_scope = LDAP_SCOPE_SUBTREE; + o.ors_slimit = SLAP_NO_LIMIT; + o.ors_tlimit = SLAP_NO_LIMIT; + o.ors_limit = NULL; + o.ors_attrsonly = 1; + o.ors_attrs = slap_anlist_no_attrs; + mf.f_choice = LDAP_FILTER_EQUALITY; + mf.f_ava = &mava; + mf.f_next = NULL; + mf.f_av_desc = mo->mo_ad_member; + mf.f_av_value = op->o_req_ndn; + o.ors_filter = &mf; + o.ors_filterstr.bv_val = op->o_tmpalloc( mo->mo_ad_member->ad_cname.bv_len + 2 + + op->o_req_ndn.bv_len + 2, op->o_tmpmemctx ); + { + char *ptr = o.ors_filterstr.bv_val; + *ptr++ = '('; + ptr = lutil_strcopy( ptr, mo->mo_ad_member->ad_cname.bv_val ); + *ptr++ = '='; + ptr = lutil_strcopy( ptr, op->o_req_ndn.bv_val ); + *ptr++ = ')'; + *ptr = '\0'; + } + sc.sc_private = &ma; + sc.sc_response = memberof_res_addcheck; + ma.ma_mo = mo; + ma.ma_e = op->ora_e; + ma.ma_a = attr_find( op->ora_e->e_attrs, mo->mo_ad_memberof ); + o.o_callback = ≻ + + o.o_bd->be_search( &o, &rs ); + o.o_bd->bd_info = (BackendInfo *)on; + op->o_tmpfree( o.ors_filterstr.bv_val, op->o_tmpmemctx ); +} + static int memberof_op_add( Operation *op, SlapReply *rs ) { @@ -549,6 +633,10 @@ memberof_op_add( Operation *op, SlapReply *rs ) return SLAP_CB_CONTINUE; } + if ( MEMBEROF_ADDCHECK( mo )) { + memberof_addcheck( op ); + } + if ( MEMBEROF_REVERSE( mo ) ) { for ( ap = &op->ora_e->e_attrs; *ap; ap = &(*ap)->a_next ) { Attribute *a = *ap; @@ -1649,6 +1737,7 @@ enum { #endif MO_DANGLING_ERROR, + MO_ADDCHECK, MO_LAST }; @@ -1730,6 +1819,14 @@ static ConfigTable mo_cfg[] = { "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "memberof-addcheck", "true|FALSE", + 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|MO_ADDCHECK, mo_cf_gen, + "( OLcfgOvAt:18.8 NAME 'olcMemberOfAddCheck' " + "DESC 'Check for memberships on added entries' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", + NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } }; @@ -1749,6 +1846,7 @@ static ConfigOCs mo_ocs[] = { #if 0 "$ olcMemberOfReverse " #endif + "$ olcMemberOfAddCheck " ") " ")", Cft_Overlay, mo_cfg, NULL, NULL }, @@ -1887,6 +1985,10 @@ mo_cf_gen( ConfigArgs *c ) c->value_ad = mo->mo_ad_memberof; break; + case MO_ADDCHECK: + c->value_int = MEMBEROF_ADDCHECK( mo ); + break; + default: assert( 0 ); return 1; @@ -1937,6 +2039,10 @@ mo_cf_gen( ConfigArgs *c ) memberof_make_member_filter( mo ); break; + case MO_ADDCHECK: + mo->mo_flags &= ~MEMBEROF_FADDCHECK; + break; + default: assert( 0 ); return 1; @@ -2046,6 +2152,15 @@ mo_cf_gen( ConfigArgs *c ) memberof_make_member_filter( mo ); } break; + case MO_ADDCHECK: + if ( c->value_int ) { + mo->mo_flags |= MEMBEROF_FADDCHECK; + + } else { + mo->mo_flags &= ~MEMBEROF_FADDCHECK; + } + break; + default: assert( 0 ); return 1; diff --git a/tests/data/memberof.out b/tests/data/memberof.out index 82fb924f8d..987fd23f01 100644 --- a/tests/data/memberof.out +++ b/tests/data/memberof.out @@ -339,3 +339,67 @@ sn: person2 memberOfB: cn=group2,ou=Groups,dc=example,dc=com memberOfC: cn=group1,ou=Groups,dc=example,dc=com +# Re-search the entire database after adding out-of-order groups/users... +dn: dc=example,dc=com +objectClass: organization +objectClass: dcObject +o: Example, Inc. +dc: example + +dn: cn=group1,ou=Groups,dc=example,dc=com +objectClass: groupA +cn: group1 +memberA: cn=person1,ou=People,dc=example,dc=com +memberA: cn=person2,ou=People,dc=example,dc=com + +dn: cn=group2,ou=Groups,dc=example,dc=com +objectClass: groupB +cn: group2 +memberB: cn=person1,ou=People,dc=example,dc=com +memberB: cn=person2,ou=People,dc=example,dc=com + +dn: cn=group3,ou=Groups,dc=example,dc=com +objectClass: groupOfNames +cn: group3 +member: cn=New Person,ou=People,dc=example,dc=com +member: cn=New Group,ou=Groups,dc=example,dc=com + +dn: ou=Groups,dc=example,dc=com +objectClass: organizationalUnit +ou: Groups + +dn: cn=New Group,ou=Groups,dc=example,dc=com +objectClass: groupOfNames +cn: New Group +member: cn=New Person,ou=People,dc=example,dc=com +memberOf: cn=group3,ou=Groups,dc=example,dc=com + +dn: cn=New Person,ou=People,dc=example,dc=com +objectClass: person +cn: New Person +sn: Person +memberOf: cn=group3,ou=Groups,dc=example,dc=com +memberOf: cn=New Group,ou=Groups,dc=example,dc=com + +dn: ou=People,dc=example,dc=com +objectClass: organizationalUnit +ou: People + +dn: cn=person1,ou=People,dc=example,dc=com +objectClass: person +objectClass: groupMemberA +objectClass: groupMemberB +cn: person1 +sn: person1 +memberOfB: cn=group2,ou=Groups,dc=example,dc=com +memberOfC: cn=group1,ou=Groups,dc=example,dc=com + +dn: cn=person2,ou=People,dc=example,dc=com +objectClass: person +objectClass: groupMemberA +objectClass: groupMemberB +cn: person2 +sn: person2 +memberOfB: cn=group2,ou=Groups,dc=example,dc=com +memberOfC: cn=group1,ou=Groups,dc=example,dc=com + diff --git a/tests/scripts/test052-memberof b/tests/scripts/test052-memberof index afa5eb96ed..4874f87cdc 100755 --- a/tests/scripts/test052-memberof +++ b/tests/scripts/test052-memberof @@ -441,6 +441,61 @@ if test $RC != 0 ; then exit $RC fi +echo "Running ldapmodify to enable add checking..." +$LDAPMODIFY -H $URI1 -D 'cn=config' -w `cat $CONFIGPWF` \ + >> $TESTOUT 2>&1 <> $TESTOUT 2>&1 <> $SEARCHOUT +$LDAPSEARCH -S "" -b "$BASEDN" -H $URI1 \ + '(objectClass=*)' '*' memberOf >> $SEARCHOUT 2>&1 +RC=$? +if test $RC != 0 ; then + echo "ldapsearch failed ($RC)!" + test $KILLSERVERS != no && kill -HUP $KILLPIDS + exit $RC +fi + test $KILLSERVERS != no && kill -HUP $KILLPIDS LDIF=$MEMBEROFOUT