From 61dffea97b8bf1d18df28bd09c233d4e19d8c048 Mon Sep 17 00:00:00 2001 From: Nathan Kinder Date: Tue, 19 Apr 2011 11:35:46 -0700 Subject: [PATCH] Add Auto Membership Plug-in This adds a new auto-membership plug-in. This plug-in allows one to define rules that can assign newly added entries to groups. For details, see the design document on the 389 wiki, located at http://directory.fedoraproject.org/wiki/Auto_Membership_Design --- Makefile.am | 18 +- Makefile.in | 59 +- ldap/admin/src/scripts/50automemberplugin.ldif | 15 + ldap/ldif/template-dse.ldif.in | 11 + ldap/schema/01core389.ldif | 3 +- ldap/schema/10automember-plugin.ldif | 106 ++ ldap/servers/plugins/automember/automember.c | 1842 ++++++++++++++++++++++++ ldap/servers/plugins/automember/automember.h | 128 ++ ldap/servers/slapd/pblock.c | 6 + ldap/servers/slapd/plugin.c | 11 + ldap/servers/slapd/slap.h | 2 + ldap/servers/slapd/slapi-plugin.h | 2 + 12 files changed, 2192 insertions(+), 11 deletions(-) create mode 100644 ldap/admin/src/scripts/50automemberplugin.ldif create mode 100644 ldap/schema/10automember-plugin.ldif create mode 100644 ldap/servers/plugins/automember/automember.c create mode 100644 ldap/servers/plugins/automember/automember.h diff --git a/Makefile.am b/Makefile.am index a6c1c67..5a29505 100644 --- a/Makefile.am +++ b/Makefile.am @@ -189,9 +189,10 @@ enable_acctpolicy = 1 endif serverplugin_LTLIBRARIES = libacl-plugin.la libattr-unique-plugin.la \ - libback-ldbm.la libchainingdb-plugin.la libcollation-plugin.la \ - libcos-plugin.la libderef-plugin.la libdes-plugin.la libdistrib-plugin.la \ - libhttp-client-plugin.la liblinkedattrs-plugin.la libmanagedentries-plugin.la \ + libautomember-plugin.la libback-ldbm.la libchainingdb-plugin.la \ + libcollation-plugin.la libcos-plugin.la libderef-plugin.la \ + libdes-plugin.la libdistrib-plugin.la libhttp-client-plugin.la \ + liblinkedattrs-plugin.la libmanagedentries-plugin.la \ libmemberof-plugin.la libpassthru-plugin.la libpwdstorage-plugin.la \ libreferint-plugin.la libreplication-plugin.la libretrocl-plugin.la \ libroles-plugin.la libstatechange-plugin.la libsyntax-plugin.la \ @@ -267,6 +268,7 @@ schema_DATA = $(srcdir)/ldap/schema/00core.ldif \ $(srcdir)/ldap/schema/05rfc4523.ldif \ $(srcdir)/ldap/schema/05rfc4524.ldif \ $(srcdir)/ldap/schema/06inetorgperson.ldif \ + $(srcdir)/ldap/schema/10automember-plugin.ldif \ $(srcdir)/ldap/schema/10mep-plugin.ldif \ $(srcdir)/ldap/schema/10rfc2307.ldif \ $(srcdir)/ldap/schema/20subscriber.ldif \ @@ -441,6 +443,7 @@ update_DATA = ldap/admin/src/scripts/exampleupdate.pl \ ldap/admin/src/scripts/10delautodnsuffix.pl \ ldap/admin/src/scripts/10fixrundir.pl \ ldap/admin/src/scripts/50addchainingsaslpwroles.ldif \ + ldap/admin/src/scripts/50automemberplugin.ldif \ ldap/admin/src/scripts/50memberofindex.ldif \ ldap/admin/src/scripts/50bitstringsyntaxplugin.ldif \ ldap/admin/src/scripts/50managedentriesplugin.ldif \ @@ -789,6 +792,15 @@ libacl_plugin_la_LDFLAGS = -avoid-version libacl_plugin_la_LINK = $(CXXLINK) -avoid-version #------------------------ +# libautomember-plugin +#------------------------ +libautomember_plugin_la_SOURCES = ldap/servers/plugins/automember/automember.c + +libautomember_plugin_la_CPPFLAGS = $(PLUGIN_CPPFLAGS) +libautomember_plugin_la_LIBADD = libslapd.la $(NSPR_LINK) +libautomember_plugin_la_LDFLAGS = -avoid-version + +#------------------------ # libattr-unique-plugin #------------------------ libattr_unique_plugin_la_SOURCES = ldap/servers/plugins/uiduniq/7bit.c \ diff --git a/Makefile.in b/Makefile.in index 47aa1f4..c653e04 100644 --- a/Makefile.in +++ b/Makefile.in @@ -166,6 +166,14 @@ libattr_unique_plugin_la_OBJECTS = \ libattr_unique_plugin_la_LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(libattr_unique_plugin_la_LDFLAGS) $(LDFLAGS) -o $@ +libautomember_plugin_la_DEPENDENCIES = libslapd.la \ + $(am__DEPENDENCIES_1) +am_libautomember_plugin_la_OBJECTS = ldap/servers/plugins/automember/libautomember_plugin_la-automember.lo +libautomember_plugin_la_OBJECTS = \ + $(am_libautomember_plugin_la_OBJECTS) +libautomember_plugin_la_LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(libautomember_plugin_la_LDFLAGS) $(LDFLAGS) -o $@ libback_ldbm_la_DEPENDENCIES = libslapd.la $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) am_libback_ldbm_la_OBJECTS = \ @@ -964,7 +972,8 @@ CXXLINK = $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ $(LDFLAGS) -o $@ SOURCES = $(libavl_a_SOURCES) $(libldaputil_a_SOURCES) \ $(libacctpolicy_plugin_la_SOURCES) $(libacl_plugin_la_SOURCES) \ - $(libattr_unique_plugin_la_SOURCES) $(libback_ldbm_la_SOURCES) \ + $(libattr_unique_plugin_la_SOURCES) \ + $(libautomember_plugin_la_SOURCES) $(libback_ldbm_la_SOURCES) \ $(libbitwise_plugin_la_SOURCES) \ $(libchainingdb_plugin_la_SOURCES) \ $(libcollation_plugin_la_SOURCES) $(libcos_plugin_la_SOURCES) \ @@ -993,7 +1002,8 @@ SOURCES = $(libavl_a_SOURCES) $(libldaputil_a_SOURCES) \ $(rsearch_bin_SOURCES) DIST_SOURCES = $(libavl_a_SOURCES) $(libldaputil_a_SOURCES) \ $(libacctpolicy_plugin_la_SOURCES) $(libacl_plugin_la_SOURCES) \ - $(libattr_unique_plugin_la_SOURCES) $(libback_ldbm_la_SOURCES) \ + $(libattr_unique_plugin_la_SOURCES) \ + $(libautomember_plugin_la_SOURCES) $(libback_ldbm_la_SOURCES) \ $(libbitwise_plugin_la_SOURCES) \ $(libchainingdb_plugin_la_SOURCES) \ $(libcollation_plugin_la_SOURCES) $(libcos_plugin_la_SOURCES) \ @@ -1368,9 +1378,10 @@ server_LTLIBRARIES = libslapd.la libns-dshttpd.la @enable_acctpolicy_TRUE@LIBACCTPOLICY_SCHEMA = $(srcdir)/ldap/schema/60acctpolicy.ldif @enable_acctpolicy_TRUE@enable_acctpolicy = 1 serverplugin_LTLIBRARIES = libacl-plugin.la libattr-unique-plugin.la \ - libback-ldbm.la libchainingdb-plugin.la libcollation-plugin.la \ - libcos-plugin.la libderef-plugin.la libdes-plugin.la libdistrib-plugin.la \ - libhttp-client-plugin.la liblinkedattrs-plugin.la libmanagedentries-plugin.la \ + libautomember-plugin.la libback-ldbm.la libchainingdb-plugin.la \ + libcollation-plugin.la libcos-plugin.la libderef-plugin.la \ + libdes-plugin.la libdistrib-plugin.la libhttp-client-plugin.la \ + liblinkedattrs-plugin.la libmanagedentries-plugin.la \ libmemberof-plugin.la libpassthru-plugin.la libpwdstorage-plugin.la \ libreferint-plugin.la libreplication-plugin.la libretrocl-plugin.la \ libroles-plugin.la libstatechange-plugin.la libsyntax-plugin.la \ @@ -1444,6 +1455,7 @@ schema_DATA = $(srcdir)/ldap/schema/00core.ldif \ $(srcdir)/ldap/schema/05rfc4523.ldif \ $(srcdir)/ldap/schema/05rfc4524.ldif \ $(srcdir)/ldap/schema/06inetorgperson.ldif \ + $(srcdir)/ldap/schema/10automember-plugin.ldif \ $(srcdir)/ldap/schema/10mep-plugin.ldif \ $(srcdir)/ldap/schema/10rfc2307.ldif \ $(srcdir)/ldap/schema/20subscriber.ldif \ @@ -1620,6 +1632,7 @@ update_DATA = ldap/admin/src/scripts/exampleupdate.pl \ ldap/admin/src/scripts/10delautodnsuffix.pl \ ldap/admin/src/scripts/10fixrundir.pl \ ldap/admin/src/scripts/50addchainingsaslpwroles.ldif \ + ldap/admin/src/scripts/50automemberplugin.ldif \ ldap/admin/src/scripts/50memberofindex.ldif \ ldap/admin/src/scripts/50bitstringsyntaxplugin.ldif \ ldap/admin/src/scripts/50managedentriesplugin.ldif \ @@ -1914,6 +1927,14 @@ libacl_plugin_la_LDFLAGS = -avoid-version libacl_plugin_la_LINK = $(CXXLINK) -avoid-version #------------------------ +# libautomember-plugin +#------------------------ +libautomember_plugin_la_SOURCES = ldap/servers/plugins/automember/automember.c +libautomember_plugin_la_CPPFLAGS = $(PLUGIN_CPPFLAGS) +libautomember_plugin_la_LIBADD = libslapd.la $(NSPR_LINK) +libautomember_plugin_la_LDFLAGS = -avoid-version + +#------------------------ # libattr-unique-plugin #------------------------ libattr_unique_plugin_la_SOURCES = ldap/servers/plugins/uiduniq/7bit.c \ @@ -2800,6 +2821,17 @@ ldap/servers/plugins/uiduniq/libattr_unique_plugin_la-utils.lo: \ ldap/servers/plugins/uiduniq/$(DEPDIR)/$(am__dirstamp) libattr-unique-plugin.la: $(libattr_unique_plugin_la_OBJECTS) $(libattr_unique_plugin_la_DEPENDENCIES) $(libattr_unique_plugin_la_LINK) -rpath $(serverplugindir) $(libattr_unique_plugin_la_OBJECTS) $(libattr_unique_plugin_la_LIBADD) $(LIBS) +ldap/servers/plugins/automember/$(am__dirstamp): + @$(MKDIR_P) ldap/servers/plugins/automember + @: > ldap/servers/plugins/automember/$(am__dirstamp) +ldap/servers/plugins/automember/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) ldap/servers/plugins/automember/$(DEPDIR) + @: > ldap/servers/plugins/automember/$(DEPDIR)/$(am__dirstamp) +ldap/servers/plugins/automember/libautomember_plugin_la-automember.lo: \ + ldap/servers/plugins/automember/$(am__dirstamp) \ + ldap/servers/plugins/automember/$(DEPDIR)/$(am__dirstamp) +libautomember-plugin.la: $(libautomember_plugin_la_OBJECTS) $(libautomember_plugin_la_DEPENDENCIES) + $(libautomember_plugin_la_LINK) -rpath $(serverplugindir) $(libautomember_plugin_la_OBJECTS) $(libautomember_plugin_la_LIBADD) $(LIBS) ldap/servers/slapd/back-ldbm/$(am__dirstamp): @$(MKDIR_P) ldap/servers/slapd/back-ldbm @: > ldap/servers/slapd/back-ldbm/$(am__dirstamp) @@ -4667,6 +4699,8 @@ mostlyclean-compile: -rm -f ldap/servers/plugins/acl/libacl_plugin_la-aclplugin.lo -rm -f ldap/servers/plugins/acl/libacl_plugin_la-aclutil.$(OBJEXT) -rm -f ldap/servers/plugins/acl/libacl_plugin_la-aclutil.lo + -rm -f ldap/servers/plugins/automember/libautomember_plugin_la-automember.$(OBJEXT) + -rm -f ldap/servers/plugins/automember/libautomember_plugin_la-automember.lo -rm -f ldap/servers/plugins/bitwise/libbitwise_plugin_la-bitwise.$(OBJEXT) -rm -f ldap/servers/plugins/bitwise/libbitwise_plugin_la-bitwise.lo -rm -f ldap/servers/plugins/chainingdb/libchainingdb_plugin_la-cb_abandon.$(OBJEXT) @@ -5493,6 +5527,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@ldap/servers/plugins/acl/$(DEPDIR)/libacl_plugin_la-aclparse.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@ldap/servers/plugins/acl/$(DEPDIR)/libacl_plugin_la-aclplugin.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@ldap/servers/plugins/acl/$(DEPDIR)/libacl_plugin_la-aclutil.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@ldap/servers/plugins/automember/$(DEPDIR)/libautomember_plugin_la-automember.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@ldap/servers/plugins/bitwise/$(DEPDIR)/libbitwise_plugin_la-bitwise.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@ldap/servers/plugins/chainingdb/$(DEPDIR)/libchainingdb_plugin_la-cb_abandon.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@ldap/servers/plugins/chainingdb/$(DEPDIR)/libchainingdb_plugin_la-cb_acl.Plo@am__quote@ @@ -6225,6 +6260,13 @@ ldap/servers/plugins/uiduniq/libattr_unique_plugin_la-utils.lo: ldap/servers/plu @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libattr_unique_plugin_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ldap/servers/plugins/uiduniq/libattr_unique_plugin_la-utils.lo `test -f 'ldap/servers/plugins/uiduniq/utils.c' || echo '$(srcdir)/'`ldap/servers/plugins/uiduniq/utils.c +ldap/servers/plugins/automember/libautomember_plugin_la-automember.lo: ldap/servers/plugins/automember/automember.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libautomember_plugin_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ldap/servers/plugins/automember/libautomember_plugin_la-automember.lo -MD -MP -MF ldap/servers/plugins/automember/$(DEPDIR)/libautomember_plugin_la-automember.Tpo -c -o ldap/servers/plugins/automember/libautomember_plugin_la-automember.lo `test -f 'ldap/servers/plugins/automember/automember.c' || echo '$(srcdir)/'`ldap/servers/plugins/automember/automember.c +@am__fastdepCC_TRUE@ $(am__mv) ldap/servers/plugins/automember/$(DEPDIR)/libautomember_plugin_la-automember.Tpo ldap/servers/plugins/automember/$(DEPDIR)/libautomember_plugin_la-automember.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='ldap/servers/plugins/automember/automember.c' object='ldap/servers/plugins/automember/libautomember_plugin_la-automember.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libautomember_plugin_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ldap/servers/plugins/automember/libautomember_plugin_la-automember.lo `test -f 'ldap/servers/plugins/automember/automember.c' || echo '$(srcdir)/'`ldap/servers/plugins/automember/automember.c + ldap/servers/slapd/back-ldbm/libback_ldbm_la-ancestorid.lo: ldap/servers/slapd/back-ldbm/ancestorid.c @am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libback_ldbm_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ldap/servers/slapd/back-ldbm/libback_ldbm_la-ancestorid.lo -MD -MP -MF ldap/servers/slapd/back-ldbm/$(DEPDIR)/libback_ldbm_la-ancestorid.Tpo -c -o ldap/servers/slapd/back-ldbm/libback_ldbm_la-ancestorid.lo `test -f 'ldap/servers/slapd/back-ldbm/ancestorid.c' || echo '$(srcdir)/'`ldap/servers/slapd/back-ldbm/ancestorid.c @am__fastdepCC_TRUE@ $(am__mv) ldap/servers/slapd/back-ldbm/$(DEPDIR)/libback_ldbm_la-ancestorid.Tpo ldap/servers/slapd/back-ldbm/$(DEPDIR)/libback_ldbm_la-ancestorid.Plo @@ -9659,6 +9701,7 @@ clean-libtool: -rm -rf ldap/libraries/libavl/.libs ldap/libraries/libavl/_libs -rm -rf ldap/servers/plugins/acctpolicy/.libs ldap/servers/plugins/acctpolicy/_libs -rm -rf ldap/servers/plugins/acl/.libs ldap/servers/plugins/acl/_libs + -rm -rf ldap/servers/plugins/automember/.libs ldap/servers/plugins/automember/_libs -rm -rf ldap/servers/plugins/bitwise/.libs ldap/servers/plugins/bitwise/_libs -rm -rf ldap/servers/plugins/chainingdb/.libs ldap/servers/plugins/chainingdb/_libs -rm -rf ldap/servers/plugins/collation/.libs ldap/servers/plugins/collation/_libs @@ -10261,6 +10304,8 @@ distclean-generic: -rm -f ldap/servers/plugins/acctpolicy/$(am__dirstamp) -rm -f ldap/servers/plugins/acl/$(DEPDIR)/$(am__dirstamp) -rm -f ldap/servers/plugins/acl/$(am__dirstamp) + -rm -f ldap/servers/plugins/automember/$(DEPDIR)/$(am__dirstamp) + -rm -f ldap/servers/plugins/automember/$(am__dirstamp) -rm -f ldap/servers/plugins/bitwise/$(DEPDIR)/$(am__dirstamp) -rm -f ldap/servers/plugins/bitwise/$(am__dirstamp) -rm -f ldap/servers/plugins/chainingdb/$(DEPDIR)/$(am__dirstamp) @@ -10351,7 +10396,7 @@ clean-am: clean-binPROGRAMS clean-generic clean-libtool clean-local \ distclean: distclean-am -rm -f $(am__CONFIG_DISTCLEAN_FILES) - -rm -rf ldap/libraries/libavl/$(DEPDIR) ldap/servers/plugins/acctpolicy/$(DEPDIR) ldap/servers/plugins/acl/$(DEPDIR) ldap/servers/plugins/bitwise/$(DEPDIR) ldap/servers/plugins/chainingdb/$(DEPDIR) ldap/servers/plugins/collation/$(DEPDIR) ldap/servers/plugins/cos/$(DEPDIR) ldap/servers/plugins/deref/$(DEPDIR) ldap/servers/plugins/distrib/$(DEPDIR) ldap/servers/plugins/dna/$(DEPDIR) ldap/servers/plugins/http/$(DEPDIR) ldap/servers/plugins/linkedattrs/$(DEPDIR) ldap/servers/plugins/memberof/$(DEPDIR) ldap/servers/plugins/mep/$(DEPDIR) ldap/servers/plugins/pam_passthru/$(DEPDIR) ldap/servers/plugins/passthru/$(DEPDIR) ldap/servers/plugins/presence/$(DEPDIR) ldap/servers/plugins/pwdstorage/$(DEPDIR) ldap/servers/plugins/referint/$(DEPDIR) ldap/servers/plugins/replication/$(DEPDIR) ldap/servers/plugins/retrocl/$(DEPDIR) ldap/servers/plugins/rever/$(DEPDIR) ldap/servers/plugins/roles/$(DEPDIR) ldap/servers/plugins/schema_reload/$(DEPDIR) ldap/servers/plugins/statechange/$(DEPDIR) ldap/servers/plugins/syntaxes/$(DEPDIR) ldap/servers/plugins/uiduniq/$(DEPDIR) ldap/servers/plugins/usn/$(DEPDIR) ldap/servers/plugins/views/$(DEPDIR) ldap/servers/slapd/$(DEPDIR) ldap/servers/slapd/back-ldbm/$(DEPDIR) ldap/servers/slapd/tools/$(DEPDIR) ldap/servers/slapd/tools/ldclt/$(DEPDIR) ldap/servers/slapd/tools/rsearch/$(DEPDIR) ldap/servers/snmp/$(DEPDIR) ldap/systools/$(DEPDIR) lib/base/$(DEPDIR) lib/ldaputil/$(DEPDIR) lib/libaccess/$(DEPDIR) lib/libadmin/$(DEPDIR) lib/libsi18n/$(DEPDIR) + -rm -rf ldap/libraries/libavl/$(DEPDIR) ldap/servers/plugins/acctpolicy/$(DEPDIR) ldap/servers/plugins/acl/$(DEPDIR) ldap/servers/plugins/automember/$(DEPDIR) ldap/servers/plugins/bitwise/$(DEPDIR) ldap/servers/plugins/chainingdb/$(DEPDIR) ldap/servers/plugins/collation/$(DEPDIR) ldap/servers/plugins/cos/$(DEPDIR) ldap/servers/plugins/deref/$(DEPDIR) ldap/servers/plugins/distrib/$(DEPDIR) ldap/servers/plugins/dna/$(DEPDIR) ldap/servers/plugins/http/$(DEPDIR) ldap/servers/plugins/linkedattrs/$(DEPDIR) ldap/servers/plugins/memberof/$(DEPDIR) ldap/servers/plugins/mep/$(DEPDIR) ldap/servers/plugins/pam_passthru/$(DEPDIR) ldap/servers/plugins/passthru/$(DEPDIR) ldap/servers/plugins/presence/$(DEPDIR) ldap/servers/plugins/pwdstorage/$(DEPDIR) ldap/servers/plugins/referint/$(DEPDIR) ldap/servers/plugins/replication/$(DEPDIR) ldap/servers/plugins/retrocl/$(DEPDIR) ldap/servers/plugins/rever/$(DEPDIR) ldap/servers/plugins/roles/$(DEPDIR) ldap/servers/plugins/schema_reload/$(DEPDIR) ldap/servers/plugins/statechange/$(DEPDIR) ldap/servers/plugins/syntaxes/$(DEPDIR) ldap/servers/plugins/uiduniq/$(DEPDIR) ldap/servers/plugins/usn/$(DEPDIR) ldap/servers/plugins/views/$(DEPDIR) ldap/servers/slapd/$(DEPDIR) ldap/servers/slapd/back-ldbm/$(DEPDIR) ldap/servers/slapd/tools/$(DEPDIR) ldap/servers/slapd/tools/ldclt/$(DEPDIR) ldap/servers/slapd/tools/rsearch/$(DEPDIR) ldap/servers/snmp/$(DEPDIR) ldap/systools/$(DEPDIR) lib/base/$(DEPDIR) lib/ldaputil/$(DEPDIR) lib/libaccess/$(DEPDIR) lib/libadmin/$(DEPDIR) lib/libsi18n/$(DEPDIR) -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-hdr distclean-libtool distclean-tags @@ -10407,7 +10452,7 @@ installcheck-am: maintainer-clean: maintainer-clean-am -rm -f $(am__CONFIG_DISTCLEAN_FILES) -rm -rf $(top_srcdir)/autom4te.cache - -rm -rf ldap/libraries/libavl/$(DEPDIR) ldap/servers/plugins/acctpolicy/$(DEPDIR) ldap/servers/plugins/acl/$(DEPDIR) ldap/servers/plugins/bitwise/$(DEPDIR) ldap/servers/plugins/chainingdb/$(DEPDIR) ldap/servers/plugins/collation/$(DEPDIR) ldap/servers/plugins/cos/$(DEPDIR) ldap/servers/plugins/deref/$(DEPDIR) ldap/servers/plugins/distrib/$(DEPDIR) ldap/servers/plugins/dna/$(DEPDIR) ldap/servers/plugins/http/$(DEPDIR) ldap/servers/plugins/linkedattrs/$(DEPDIR) ldap/servers/plugins/memberof/$(DEPDIR) ldap/servers/plugins/mep/$(DEPDIR) ldap/servers/plugins/pam_passthru/$(DEPDIR) ldap/servers/plugins/passthru/$(DEPDIR) ldap/servers/plugins/presence/$(DEPDIR) ldap/servers/plugins/pwdstorage/$(DEPDIR) ldap/servers/plugins/referint/$(DEPDIR) ldap/servers/plugins/replication/$(DEPDIR) ldap/servers/plugins/retrocl/$(DEPDIR) ldap/servers/plugins/rever/$(DEPDIR) ldap/servers/plugins/roles/$(DEPDIR) ldap/servers/plugins/schema_reload/$(DEPDIR) ldap/servers/plugins/statechange/$(DEPDIR) ldap/servers/plugins/syntaxes/$(DEPDIR) ldap/servers/plugins/uiduniq/$(DEPDIR) ldap/servers/plugins/usn/$(DEPDIR) ldap/servers/plugins/views/$(DEPDIR) ldap/servers/slapd/$(DEPDIR) ldap/servers/slapd/back-ldbm/$(DEPDIR) ldap/servers/slapd/tools/$(DEPDIR) ldap/servers/slapd/tools/ldclt/$(DEPDIR) ldap/servers/slapd/tools/rsearch/$(DEPDIR) ldap/servers/snmp/$(DEPDIR) ldap/systools/$(DEPDIR) lib/base/$(DEPDIR) lib/ldaputil/$(DEPDIR) lib/libaccess/$(DEPDIR) lib/libadmin/$(DEPDIR) lib/libsi18n/$(DEPDIR) + -rm -rf ldap/libraries/libavl/$(DEPDIR) ldap/servers/plugins/acctpolicy/$(DEPDIR) ldap/servers/plugins/acl/$(DEPDIR) ldap/servers/plugins/automember/$(DEPDIR) ldap/servers/plugins/bitwise/$(DEPDIR) ldap/servers/plugins/chainingdb/$(DEPDIR) ldap/servers/plugins/collation/$(DEPDIR) ldap/servers/plugins/cos/$(DEPDIR) ldap/servers/plugins/deref/$(DEPDIR) ldap/servers/plugins/distrib/$(DEPDIR) ldap/servers/plugins/dna/$(DEPDIR) ldap/servers/plugins/http/$(DEPDIR) ldap/servers/plugins/linkedattrs/$(DEPDIR) ldap/servers/plugins/memberof/$(DEPDIR) ldap/servers/plugins/mep/$(DEPDIR) ldap/servers/plugins/pam_passthru/$(DEPDIR) ldap/servers/plugins/passthru/$(DEPDIR) ldap/servers/plugins/presence/$(DEPDIR) ldap/servers/plugins/pwdstorage/$(DEPDIR) ldap/servers/plugins/referint/$(DEPDIR) ldap/servers/plugins/replication/$(DEPDIR) ldap/servers/plugins/retrocl/$(DEPDIR) ldap/servers/plugins/rever/$(DEPDIR) ldap/servers/plugins/roles/$(DEPDIR) ldap/servers/plugins/schema_reload/$(DEPDIR) ldap/servers/plugins/statechange/$(DEPDIR) ldap/servers/plugins/syntaxes/$(DEPDIR) ldap/servers/plugins/uiduniq/$(DEPDIR) ldap/servers/plugins/usn/$(DEPDIR) ldap/servers/plugins/views/$(DEPDIR) ldap/servers/slapd/$(DEPDIR) ldap/servers/slapd/back-ldbm/$(DEPDIR) ldap/servers/slapd/tools/$(DEPDIR) ldap/servers/slapd/tools/ldclt/$(DEPDIR) ldap/servers/slapd/tools/rsearch/$(DEPDIR) ldap/servers/snmp/$(DEPDIR) ldap/systools/$(DEPDIR) lib/base/$(DEPDIR) lib/ldaputil/$(DEPDIR) lib/libaccess/$(DEPDIR) lib/libadmin/$(DEPDIR) lib/libsi18n/$(DEPDIR) -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic diff --git a/ldap/admin/src/scripts/50automemberplugin.ldif b/ldap/admin/src/scripts/50automemberplugin.ldif new file mode 100644 index 0000000..d0c201a --- /dev/null +++ b/ldap/admin/src/scripts/50automemberplugin.ldif @@ -0,0 +1,15 @@ +dn: cn=Auto Membership Plugin,cn=plugins,cn=config +objectclass: top +objectclass: nsSlapdPlugin +objectclass: extensibleObject +cn: Auto Membership Plugin +nsslapd-pluginpath: libautomember-plugin +nsslapd-plugininitfunc: automember_init +nsslapd-plugintype: preoperation +nsslapd-pluginenabled: on +nsslapd-plugin-depends-on-type: database +# these will be replaced when the server loads the plugin +nsslapd-pluginId: ID +nsslapd-pluginVersion: PACKAGE_VERSION +nsslapd-pluginVendor: VENDOR +nsslapd-pluginDescription: DESC diff --git a/ldap/ldif/template-dse.ldif.in b/ldap/ldif/template-dse.ldif.in index cd98d16..a345a40 100644 --- a/ldap/ldif/template-dse.ldif.in +++ b/ldap/ldif/template-dse.ldif.in @@ -619,6 +619,17 @@ nsslapd-pluginarg3: , nsslapd-pluginarg4: %ds_suffix% nsslapd-plugin-depends-on-type: database +dn: cn=Auto Membership Plugin,cn=plugins,cn=config +objectclass: top +objectclass: nsSlapdPlugin +objectclass: extensibleObject +cn: Auto Membership Plugin +nsslapd-pluginpath: libautomember-plugin +nsslapd-plugininitfunc: automember_init +nsslapd-plugintype: preoperation +nsslapd-pluginenabled: on +nsslapd-plugin-depends-on-type: database + dn: cn=deref,cn=plugins,cn=config objectclass: top objectclass: nsSlapdPlugin diff --git a/ldap/schema/01core389.ldif b/ldap/schema/01core389.ldif index 95607fc..3402430 100644 --- a/ldap/schema/01core389.ldif +++ b/ldap/schema/01core389.ldif @@ -54,6 +54,7 @@ attributeTypes: ( 2.16.840.1.113730.3.1.228 NAME 'nsslapd-pluginVersion' DESC 'N attributeTypes: ( 2.16.840.1.113730.3.1.229 NAME 'nsslapd-pluginVendor' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'Netscape Directory Server' ) attributeTypes: ( 2.16.840.1.113730.3.1.230 NAME 'nsslapd-pluginDescription' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'Netscape Directory Server' ) attributeTypes: ( 2.16.840.1.113730.3.1.231 NAME 'nsslapd-pluginEnabled' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'Netscape Directory Server' ) +attributeTypes: ( 2.16.840.1.113730.3.1.2104 NAME 'nsslapd-pluginConfigArea' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' ) attributeTypes: ( 2.16.840.1.113730.3.1.232 NAME 'nsSNMPEnabled' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'Netscape Directory Server' ) attributeTypes: ( 2.16.840.1.113730.3.1.233 NAME 'nsSNMPOrganization' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'Netscape Directory Server' ) attributeTypes: ( 2.16.840.1.113730.3.1.234 NAME 'nsSNMPLocation' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'Netscape Directory Server' ) @@ -129,7 +130,7 @@ attributeTypes: ( 2.16.840.1.113730.3.1.2095 NAME 'connection' DESC 'Netscape de # objectclasses # objectClasses: ( 2.16.840.1.113730.3.2.40 NAME 'directoryServerFeature' DESC 'Netscape defined objectclass' SUP top MAY ( oid $ cn $ multiLineDescription ) X-ORIGIN 'Netscape Directory Server' ) -objectClasses: ( 2.16.840.1.113730.3.2.41 NAME 'nsslapdPlugin' DESC 'Netscape defined objectclass' SUP top MUST ( cn $ nsslapd-pluginPath $ nsslapd-pluginInitFunc $ nsslapd-pluginType $ nsslapd-pluginId $ nsslapd-pluginVersion $ nsslapd-pluginVendor $ nsslapd-pluginDescription $ nsslapd-pluginEnabled ) X-ORIGIN 'Netscape Directory Server' ) +objectClasses: ( 2.16.840.1.113730.3.2.41 NAME 'nsslapdPlugin' DESC 'Netscape defined objectclass' SUP top MUST ( cn $ nsslapd-pluginPath $ nsslapd-pluginInitFunc $ nsslapd-pluginType $ nsslapd-pluginId $ nsslapd-pluginVersion $ nsslapd-pluginVendor $ nsslapd-pluginDescription $ nsslapd-pluginEnabled ) MAY ( nsslapd-pluginConfigArea ) X-ORIGIN 'Netscape Directory Server' ) objectClasses: ( 2.16.840.1.113730.3.2.44 NAME 'nsIndex' DESC 'Netscape defined objectclass' SUP top MUST ( cn $ nsSystemIndex ) MAY ( description $ nsIndexType $ nsMatchingRule ) X-ORIGIN 'Netscape Directory Server' ) objectClasses: ( 2.16.840.1.113730.3.2.109 NAME 'nsBackendInstance' DESC 'Netscape defined objectclass' SUP top MUST ( CN ) X-ORIGIN 'Netscape Directory Server' ) objectClasses: ( 2.16.840.1.113730.3.2.110 NAME 'nsMappingTree' DESC 'Netscape defined objectclass' SUP top MUST ( CN ) X-ORIGIN 'Netscape Directory Server' ) diff --git a/ldap/schema/10automember-plugin.ldif b/ldap/schema/10automember-plugin.ldif new file mode 100644 index 0000000..3428f07 --- /dev/null +++ b/ldap/schema/10automember-plugin.ldif @@ -0,0 +1,106 @@ +# +# BEGIN COPYRIGHT BLOCK +# This Program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; version 2 of the License. +# +# This Program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this Program; if not, write to the Free Software Foundation, Inc., 59 Temple +# Place, Suite 330, Boston, MA 02111-1307 USA. +# +# In addition, as a special exception, Red Hat, Inc. gives You the additional +# right to link the code of this Program with code not covered under the GNU +# General Public License ("Non-GPL Code") and to distribute linked combinations +# including the two, subject to the limitations in this paragraph. Non-GPL Code +# permitted under this exception must only link to the code of this Program +# through those well defined interfaces identified in the file named EXCEPTION +# found in the source code files (the "Approved Interfaces"). The files of +# Non-GPL Code may instantiate templates or use macros or inline functions from +# the Approved Interfaces without causing the resulting work to be covered by +# the GNU General Public License. Only Red Hat, Inc. may make changes or +# additions to the list of Approved Interfaces. You must obey the GNU General +# Public License in all respects for all of the Program code and other code used +# in conjunction with the Program except the Non-GPL Code covered by this +# exception. If you modify this file, you may extend this exception to your +# version of the file, but you are not obligated to do so. If you do not wish to +# provide this exception without modification, you must delete this exception +# statement from your version and license this file solely under the GPL without +# exception. +# +# +# Copyright (C) 2011 Red Hat, Inc. +# All rights reserved. +# END COPYRIGHT BLOCK +# +# +# Schema for Auto Membership Plugin +# +dn: cn=schema +# +################################################################################ +# +attributeTypes: ( 2.16.840.1.113730.3.1.2097 NAME 'autoMemberScope' + DESC 'Auto Membership scope criteria' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + SINGLE-VALUE + X-ORIGIN '389 Directory Server' ) +# +################################################################################ +# +attributeTypes: ( 2.16.840.1.113730.3.1.2098 NAME 'autoMemberFilter' + DESC 'Auto Membership filter criteria' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + SINGLE-VALUE + X-ORIGIN '389 Directory Server' ) +# +################################################################################ +# +attributeTypes: ( 2.16.840.1.113730.3.1.2099 NAME 'autoMemberExclusiveRegex' + DESC 'Auto Membership exclusive regex rule' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN '389 Directory Server' ) +# +################################################################################ +# +attributeTypes: ( 2.16.840.1.113730.3.1.2100 NAME 'autoMemberInclusiveRegex' + DESC 'Auto Membership inclusive regex rule' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + X-ORIGIN '389 Directory Server' ) +# +################################################################################ +# +attributeTypes: ( 2.16.840.1.113730.3.1.2101 NAME 'autoMemberDefaultGroup' + DESC 'Auto Membership default group' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + X-ORIGIN '389 Directory Server' ) +# +################################################################################ +# +attributeTypes: ( 2.16.840.1.113730.3.1.2102 NAME 'autoMemberGroupingAttr' + DESC 'Auto Membership grouping attribute' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE + X-ORIGIN '389 Directory Server' ) +# +################################################################################ +# +attributeTypes: ( 2.16.840.1.113730.3.1.2103 NAME 'autoMemberDisabled' + DESC 'Auto Membership disabled attribute' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + SINGLE-VALUE + X-ORIGIN '389 Directory Server' ) +# +################################################################################ +# +objectClasses: ( 2.16.840.1.113730.3.2.322 NAME 'autoMemberDefinition' + DESC 'Auto Membership Config Definition Entry' + SUP top + STRUCTURAL + MUST ( cn $ autoMemberScope $ autoMemberFilter $ autoMemberGroupingAttr ) + MAY ( autoMemberExclusiveRegex $ autoMemberInclusiveRegex $ autoMemberDefaultGroup + $ autoMemberDisabled ) + X-ORIGIN '389 Directory Server' ) diff --git a/ldap/servers/plugins/automember/automember.c b/ldap/servers/plugins/automember/automember.c new file mode 100644 index 0000000..89b492a --- /dev/null +++ b/ldap/servers/plugins/automember/automember.c @@ -0,0 +1,1842 @@ +/** BEGIN COPYRIGHT BLOCK + * This Program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; version 2 of the License. + * + * This Program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * this Program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, Boston, MA 02111-1307 USA. + * + * In addition, as a special exception, Red Hat, Inc. gives You the additional + * right to link the code of this Program with code not covered under the GNU + * General Public License ("Non-GPL Code") and to distribute linked combinations + * including the two, subject to the limitations in this paragraph. Non-GPL Code + * permitted under this exception must only link to the code of this Program + * through those well defined interfaces identified in the file named EXCEPTION + * found in the source code files (the "Approved Interfaces"). The files of + * Non-GPL Code may instantiate templates or use macros or inline functions from + * the Approved Interfaces without causing the resulting work to be covered by + * the GNU General Public License. Only Red Hat, Inc. may make changes or + * additions to the list of Approved Interfaces. You must obey the GNU General + * Public License in all respects for all of the Program code and other code used + * in conjunction with the Program except the Non-GPL Code covered by this + * exception. If you modify this file, you may extend this exception to your + * version of the file, but you are not obligated to do so. If you do not wish to + * provide this exception without modification, you must delete this exception + * statement from your version and license this file solely under the GPL without + * exception. + * + * + * Copyright (C) 2011 Red Hat, Inc. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* + * Auto Membership Plug-in + */ +#include "automember.h" + + +/* + * Plug-in globals + */ +static PRCList *g_automember_config = NULL; +static PRRWLock *g_automember_config_lock; + +static void *_PluginID = NULL; +static char *_PluginDN = NULL; +static char *_ConfigAreaDN = NULL; +static int g_plugin_started = 0; + +static Slapi_PluginDesc pdesc = { AUTOMEMBER_FEATURE_DESC, + VENDOR, + DS_PACKAGE_VERSION, + AUTOMEMBER_PLUGIN_DESC }; + +/* + * Plug-in management functions + */ +int automember_init(Slapi_PBlock * pb); +static int automember_start(Slapi_PBlock * pb); +static int automember_close(Slapi_PBlock * pb); +static int automember_postop_init(Slapi_PBlock * pb); +static int automember_internal_postop_init(Slapi_PBlock *pb); + +/* + * Operation callbacks (where the real work is done) + */ +static int automember_mod_post_op(Slapi_PBlock *pb); +static int automember_add_post_op(Slapi_PBlock *pb); +static int automember_del_post_op(Slapi_PBlock *pb); +static int automember_modrdn_post_op(Slapi_PBlock *pb); +static int automember_pre_op(Slapi_PBlock *pb, int modop); +static int automember_mod_pre_op(Slapi_PBlock *pb); +static int automember_add_pre_op(Slapi_PBlock *pb); + +/* + * Config cache management functions + */ +static int automember_load_config(); +static void automember_delete_config(); +static int automember_parse_config_entry(Slapi_Entry * e, int apply); +static void automember_free_config_entry(struct configEntry ** entry); + +/* + * helpers + */ +static char *automember_get_dn(Slapi_PBlock * pb); +static char *automember_get_config_area(); +static void automember_set_config_area(char *dn); +static int automember_dn_is_config(char *dn); +static int automember_oktodo(Slapi_PBlock *pb); +static int automember_isrepl(Slapi_PBlock *pb); +static struct automemberRegexRule *automember_parse_regex_rule(char *rule_string); +static void automember_free_regex_rule(struct automemberRegexRule *rule); +static int automember_parse_grouping_attr(char *value, char **grouping_attr, + char **grouping_value); +static void automember_update_membership(struct configEntry *config, Slapi_Entry *e); +static void automember_add_member_value(Slapi_Entry *member_e, const char *group_dn, + char *grouping_attr, char *grouping_value); + +/* + * Config cache locking functions + */ +void +automember_config_read_lock() +{ + PR_RWLock_Rlock(g_automember_config_lock); +} + +void +automember_config_write_lock() +{ + PR_RWLock_Wlock(g_automember_config_lock); +} + +void +automember_config_unlock() +{ + PR_RWLock_Unlock(g_automember_config_lock); +} + + +/* + * Plugin identity functions + */ +void +automember_set_plugin_id(void *pluginID) +{ + _PluginID = pluginID; +} + +void * +automember_get_plugin_id() +{ + return _PluginID; +} + +void +automember_set_plugin_dn(char *pluginDN) +{ + _PluginDN = pluginDN; +} + +char * +automember_get_plugin_dn() +{ + return _PluginDN; +} + + +/* + * Plug-in initialization functions + */ +int +automember_init(Slapi_PBlock *pb) +{ + int status = 0; + char *plugin_identity = NULL; + + slapi_log_error(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "--> automember_init\n"); + + /* Store the plugin identity for later use. + * Used for internal operations. */ + slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &plugin_identity); + PR_ASSERT(plugin_identity); + automember_set_plugin_id(plugin_identity); + + /* Register callbacks */ + if (slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, + SLAPI_PLUGIN_VERSION_01) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, + (void *) automember_start) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN, + (void *) automember_close) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, + (void *) &pdesc) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODIFY_FN, + (void *) automember_mod_pre_op) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_ADD_FN, + (void *) automember_add_pre_op) != 0 || + slapi_register_plugin("internalpostoperation", /* op type */ + 1, /* Enabled */ + "automember_init", /* this function desc */ + automember_internal_postop_init, /* init func */ + AUTOMEMBER_INT_POSTOP_DESC, /* plugin desc */ + NULL, /* ? */ + plugin_identity /* access control */ + ) || + slapi_register_plugin("postoperation", /* op type */ + 1, /* Enabled */ + "automember_init", /* this function desc */ + automember_postop_init, /* init func for post op */ + AUTOMEMBER_POSTOP_DESC, /* plugin desc */ + NULL, /* ? */ + plugin_identity /* access control */ + ) + ) { + slapi_log_error(SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_init: failed to register plugin\n"); + status = -1; + } + + slapi_log_error(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "<-- automember_init\n"); + return status; +} + +static int +automember_internal_postop_init(Slapi_PBlock *pb) +{ + int status = 0; + + if (slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, + SLAPI_PLUGIN_VERSION_01) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, + (void *) &pdesc) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_POST_ADD_FN, + (void *) automember_add_post_op) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_POST_DELETE_FN, + (void *) automember_del_post_op) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_POST_MODIFY_FN, + (void *) automember_mod_post_op) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_POST_MODRDN_FN, + (void *) automember_modrdn_post_op) != 0) { + slapi_log_error(SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_internal_postop_init: failed to register plugin\n"); + status = -1; + } + + return status; +} + +static int +automember_postop_init(Slapi_PBlock *pb) +{ + int status = 0; + + if (slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, + SLAPI_PLUGIN_VERSION_01) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, + (void *) &pdesc) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_POST_ADD_FN, + (void *) automember_add_post_op) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_POST_DELETE_FN, + (void *) automember_del_post_op) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODIFY_FN, + (void *) automember_mod_post_op) != 0 || + slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODRDN_FN, + (void *) automember_modrdn_post_op) != 0) { + slapi_log_error(SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_postop_init: failed to register plugin\n"); + status = -1; + } + + return status; +} + + +/* Stash the config area in the pblock for start functions? */ +/* + * automember_start() + * + * Creates config lock and loads config cache. + */ +static int +automember_start(Slapi_PBlock * pb) +{ + char *plugindn = NULL; + char *config_area = NULL; + + slapi_log_error(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "--> automember_start\n"); + + /* Check if we're already started */ + if (g_plugin_started) { + goto done; + } + + g_automember_config_lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "automember_config"); + + if (!g_automember_config_lock) { + slapi_log_error(SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_start: lock creation failed\n"); + + return -1; + } + + /* + * Get the plug-in target dn from the system + * and store it for future use. */ + slapi_pblock_get(pb, SLAPI_TARGET_DN, &plugindn); + if (NULL == plugindn || 0 == strlen(plugindn)) { + slapi_log_error(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_start: unable to retrieve plugin dn\n"); + return -1; + } + + automember_set_plugin_dn(plugindn); + + /* Set the alternate config area if one is defined. */ + slapi_pblock_get(pb, SLAPI_PLUGIN_CONFIG_AREA, &config_area); + if (config_area) { + automember_set_config_area(slapi_ch_strdup(config_area)); + } + + /* + * Load the config cache + */ + g_automember_config = (PRCList *)slapi_ch_calloc(1, sizeof(struct configEntry)); + PR_INIT_CLIST(g_automember_config); + + if (automember_load_config() != 0) { + slapi_log_error(SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_start: unable to load plug-in configuration\n"); + return -1; + } + + g_plugin_started = 1; + slapi_log_error(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "auto membership plug-in: ready for service\n"); + slapi_log_error(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "<-- automember_start\n"); + +done: + return 0; +} + +/* + * automember_close() + * + * Cleans up the config cache. + */ +static int +automember_close(Slapi_PBlock * pb) +{ + slapi_log_error(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "--> automember_close\n"); + + if (!g_plugin_started) { + goto done; + } + + automember_delete_config(); + + slapi_ch_free((void **)&g_automember_config); + slapi_ch_free_string(&_ConfigAreaDN); + + if (g_automember_config_lock) { + PR_DestroyRWLock(g_automember_config_lock); + } + +done: + slapi_log_error(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "<-- automember_close\n"); + + return 0; +} + +/* + * automember_get_config() + * + * Get the config list. + */ +PRCList * +automember_get_config() +{ + return g_automember_config; +} + +/* + * automember_load_config() + * + * Parse and load the config entries. + */ +static int +automember_load_config() +{ + int status = 0; + int result; + int i; + Slapi_PBlock *search_pb; + Slapi_Entry **entries = NULL; + + slapi_log_error(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "--> automember_load_config\n"); + + /* Clear out any old config. */ + automember_config_write_lock(); + automember_delete_config(); + + slapi_log_error(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_load_config: Looking for config entries " + "beneath \"%s\".\n", automember_get_plugin_dn()); + + /* Find the config entries beneath our plugin entry. */ + search_pb = slapi_pblock_new(); + slapi_search_internal_set_pb(search_pb, automember_get_plugin_dn(), + LDAP_SCOPE_SUBTREE, "objectclass=*", + NULL, 0, NULL, NULL, automember_get_plugin_id(), 0); + slapi_search_internal_pb(search_pb); + slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_RESULT, &result); + + if (LDAP_SUCCESS != result) { + status = -1; + goto cleanup; + } + + slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, + &entries); + + /* Loop through all of the entries we found and parse them. */ + for (i = 0; entries && (entries[i] != NULL); i++) { + slapi_log_error(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_load_config: parsing config entry " + "\"%s\".\n", slapi_entry_get_dn(entries[i])); + /* We don't care about the status here because we may have + * some invalid config entries, but we just want to continue + * looking for valid ones. */ + automember_parse_config_entry(entries[i], 1); + } + + /* If an alternate config area is configured, find + * the config entries that are beneath it. */ + if (automember_get_config_area()) { + /* Clean up the previous search results + * and clear out the pblock for reuse. */ + slapi_free_search_results_internal(search_pb); + slapi_pblock_init(search_pb); + + slapi_log_error(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_load_config: Looking for config entries " + "beneath \"%s\".\n", automember_get_config_area()); + + slapi_search_internal_set_pb(search_pb, automember_get_config_area(), + LDAP_SCOPE_SUBTREE, "objectclass=*", + NULL, 0, NULL, NULL, automember_get_plugin_id(), 0); + slapi_search_internal_pb(search_pb); + slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_RESULT, &result); + + if ((LDAP_SUCCESS != result) && (LDAP_NO_SUCH_OBJECT != result)) { + status = -1; + goto cleanup; + } + + slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, + &entries); + + /* Loop through all of the entries we found and parse them. */ + for (i = 0; entries && (entries[i] != NULL); i++) { + slapi_log_error(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_load_config: parsing config entry " + "\"%s\".\n", slapi_entry_get_dn(entries[i])); + + /* We don't care about the status here because we may have + * some invalid config entries, but we just want to continue + * looking for valid ones. */ + automember_parse_config_entry(entries[i], 1); + } + } + + cleanup: + slapi_free_search_results_internal(search_pb); + slapi_pblock_destroy(search_pb); + automember_config_unlock(); + slapi_log_error(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "<-- automember_load_config\n"); + + return status; +} + +/* + * automember_parse_config_entry() + * + * Parses a single config entry. If apply is non-zero, then + * we will load and start using the new config. You can simply + * validate config without making any changes by setting apply + * to 0. + * + * Returns 0 if the entry is valid and -1 if it is invalid. + */ +static int +automember_parse_config_entry(Slapi_Entry * e, int apply) +{ + char *value = NULL; + char **values = NULL; + struct configEntry *entry = NULL; + struct configEntry *config_entry; + PRCList *list; + int entry_added = 0; + int i = 0; + int ret = 0; + + slapi_log_error(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "--> automember_parse_config_entry\n"); + + /* If this is the main plug-in config entry or + * the config area container, just bail. */ + if ((strcasecmp(automember_get_plugin_dn(), slapi_entry_get_ndn(e)) == 0) || + (automember_get_config_area() && (strcasecmp(automember_get_config_area(), + slapi_entry_get_ndn(e)) == 0))) { + goto bail; + } + + /* If marked as disabled, just bail. */ + value = slapi_entry_attr_get_charptr(e, AUTOMEMBER_DISABLED_TYPE); + if (value) { + slapi_ch_free_string(&value); + goto bail; + } + + entry = (struct configEntry *)slapi_ch_calloc(1, sizeof(struct configEntry)); + if (NULL == entry) { + ret = -1; + goto bail; + } + + value = slapi_entry_get_ndn(e); + if (value) { + entry->dn = slapi_ch_strdup(value); + } else { + slapi_log_error(SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_parse_config_entry: Error " + "reading dn from config entry\n"); + ret = -1; + goto bail; + } + + slapi_log_error(SLAPI_LOG_CONFIG, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "----------> dn [%s]\n", entry->dn); + + /* Load the scope */ + value = slapi_entry_attr_get_charptr(e, AUTOMEMBER_SCOPE_TYPE); + if (value) { + entry->scope = value; + } else { + slapi_log_error(SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_parse_config_entry: The %s config " + "setting is required for config entry \"%s\".\n", + AUTOMEMBER_SCOPE_TYPE, entry->dn); + ret = -1; + goto bail; + } + + /* Load the filter */ + value = slapi_entry_attr_get_charptr(e, AUTOMEMBER_FILTER_TYPE); + if (value) { + /* Convert to a Slapi_Filter to improve performance. */ + if (NULL == (entry->filter = slapi_str2filter(value))) { + slapi_log_error(SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM , + "automember_parse_config_entry: Invalid search filter in " + "%s config setting for config entry \"%s\" " + "(filter = \"%s\").\n", AUTOMEMBER_FILTER_TYPE, entry->dn, value); + ret = -1; + } + + slapi_ch_free_string(&value); + + if (ret != 0) { + goto bail; + } + } else { + slapi_log_error(SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_parse_config_entry: The %s config " + "setting is required for config entry \"%s\".\n", + AUTOMEMBER_FILTER_TYPE, entry->dn); + ret = -1; + goto bail; + } + + /* Load exclusive regex rules */ + values = slapi_entry_attr_get_charray(e, AUTOMEMBER_EXC_REGEX_TYPE); + if (values) { + struct automemberRegexRule *rule = NULL; + + /* Create a list to hold our regex rules */ + entry->exclusive_rules = (struct automemberRegexRule *)slapi_ch_calloc(1, sizeof(struct automemberRegexRule)); + PR_INIT_CLIST((PRCList *)entry->exclusive_rules); + + /* Parse each regex rule and add to the list */ + for (i = 0; values && values[i]; ++i) { + rule = automember_parse_regex_rule(values[i]); + if (rule) { + if (!PR_CLIST_IS_EMPTY((PRCList *)entry->exclusive_rules)) { + list = PR_LIST_HEAD((PRCList *)entry->exclusive_rules); + while (list != (PRCList *)entry->exclusive_rules) { + struct automemberRegexRule *curr_rule = (struct automemberRegexRule *)list; + /* Order rules by target group DN */ + if (strcmp(slapi_sdn_get_dn(rule->target_group_dn), + slapi_sdn_get_dn(curr_rule->target_group_dn)) < 0) { + PR_INSERT_BEFORE(&(rule->list), list); + break; + } + + list = PR_NEXT_LINK(list); + + /* If we hit the end of the list, add to the tail. */ + if ((PRCList *)entry->exclusive_rules == list) { + PR_INSERT_BEFORE(&(rule->list), list); + break; + } + } + } else { + /* Add to head of list */ + PR_INSERT_LINK(&(rule->list), (PRCList *)entry->exclusive_rules); + } + } else { + slapi_log_error(SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_parse_config_entry: Invalid exclusive " + "regex rule in config entry \"%s\" (rule = \"%s\").\n", + entry->dn, values[i]); + ret = -1; + } + } + slapi_ch_array_free(values); + values = NULL; + + /* Bail if we had a bad regex rule */ + if (ret == -1) { + goto bail; + } + } + + /* Load inclusive regex rules */ + values = slapi_entry_attr_get_charray(e, AUTOMEMBER_INC_REGEX_TYPE); + if (values) { + struct automemberRegexRule *rule = NULL; + + /* Create a list to hold our regex rules */ + entry->inclusive_rules = (struct automemberRegexRule *)slapi_ch_calloc(1, sizeof(struct automemberRegexRule)); + PR_INIT_CLIST((PRCList *)entry->inclusive_rules); + + /* Parse each regex rule and add to the list */ + for (i = 0; values && values[i]; ++i) { + rule = automember_parse_regex_rule(values[i]); + if (rule) { + if (!PR_CLIST_IS_EMPTY((PRCList *)entry->inclusive_rules)) { + list = PR_LIST_HEAD((PRCList *)entry->inclusive_rules); + while (list != (PRCList *)entry->inclusive_rules) { + struct automemberRegexRule *curr_rule = (struct automemberRegexRule *)list; + /* Order rules by target group DN */ + if (strcmp(slapi_sdn_get_dn(rule->target_group_dn), + slapi_sdn_get_dn(curr_rule->target_group_dn)) < 0) { + PR_INSERT_BEFORE(&(rule->list), list); + break; + } + + list = PR_NEXT_LINK(list); + + /* If we hit the end of the list, add to the tail. */ + if ((PRCList *)entry->inclusive_rules == list) { + PR_INSERT_BEFORE(&(rule->list), list); + break; + } + } + } else { + /* Add to head of list */ + PR_INSERT_LINK(&(rule->list), (PRCList *)entry->inclusive_rules); + } + } else { + slapi_log_error(SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_parse_config_entry: Invalid inclusive " + "regex rule in config entry \"%s\" (rule = \"%s\").\n", + entry->dn, values[i]); + ret = -1; + } + } + slapi_ch_array_free(values); + values = NULL; + } + + /* Load the default groups */ + values = slapi_entry_attr_get_charray(e, AUTOMEMBER_DEFAULT_GROUP_TYPE); + if (values) { + /* Just hand off the values */ + entry->default_groups = values; + values = NULL; + } + + /* Load the grouping attr */ + value = slapi_entry_attr_get_charptr(e, AUTOMEMBER_GROUPING_ATTR_TYPE); + if (value) { + if (automember_parse_grouping_attr(value, &(entry->grouping_attr), + &(entry->grouping_value)) != 0) { + slapi_log_error(SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_parse_config_entry: Invalid " + "%s config setting for config entry \"%s\" " + "(value: \"%s\").\n", AUTOMEMBER_GROUPING_ATTR_TYPE, + entry->dn, value); + ret = -1; + } + + slapi_ch_free_string(&value); + if (ret != 0) { + goto bail; + } + } else { + slapi_log_error(SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_parse_config_entry: The %s config " + "setting is required for config entry \"%s\".\n", + AUTOMEMBER_GROUPING_ATTR_TYPE, entry->dn); + ret = -1; + goto bail; + } + + /* If we were only called to validate config, we can + * just bail out before applying the config changes */ + if (apply == 0) { + goto bail; + } + + /* Add the config object to the list. We order by scope. */ + if (!PR_CLIST_IS_EMPTY(g_automember_config)) { + list = PR_LIST_HEAD(g_automember_config); + while (list != g_automember_config) { + config_entry = (struct configEntry *) list; + + /* If the config entry we are adding has a scope that is + * a child of the scope of the current list item, we insert + * the entry before that list item. */ + if (slapi_dn_issuffix(entry->scope, config_entry->scope)) { + PR_INSERT_BEFORE(&(entry->list), list); + slapi_log_error(SLAPI_LOG_CONFIG, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "store [%s] before [%s] \n", entry->dn, + config_entry->dn); + + entry_added = 1; + break; + } + + list = PR_NEXT_LINK(list); + + /* If we hit the end of the list, add to the tail. */ + if (g_automember_config == list) { + PR_INSERT_BEFORE(&(entry->list), list); + slapi_log_error(SLAPI_LOG_CONFIG, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "store [%s] at tail\n", entry->dn); + + entry_added = 1; + break; + } + } + } else { + /* first entry */ + PR_INSERT_LINK(&(entry->list), g_automember_config); + slapi_log_error(SLAPI_LOG_CONFIG, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "store [%s] at head \n", entry->dn); + + entry_added = 1; + } + + bail: + if (0 == entry_added) { + /* Don't log error if we weren't asked to apply config */ + if ((apply != 0) && (entry != NULL)) { + slapi_log_error(SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_parse_config_entry: Invalid config entry " + "[%s] skipped\n", entry->dn); + } + automember_free_config_entry(&entry); + } else { + ret = 0; + } + + slapi_log_error(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "<-- automember_parse_config_entry\n"); + + return ret; +} + +static void +automember_free_config_entry(struct configEntry ** entry) +{ + struct configEntry *e = *entry; + + if (e == NULL) + return; + + if (e->dn) { + slapi_log_error(SLAPI_LOG_CONFIG, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "freeing config entry [%s]\n", e->dn); + slapi_ch_free_string(&e->dn); + } + + if (e->scope) { + slapi_ch_free_string(&e->scope); + } + + if (e->filter) { + slapi_filter_free(e->filter, 1); + } + + if (e->exclusive_rules) { + PRCList *list; + + /* Clear out the list contents. */ + while (!PR_CLIST_IS_EMPTY((PRCList *)e->exclusive_rules)) { + list = PR_LIST_HEAD((PRCList *)e->exclusive_rules); + PR_REMOVE_LINK(list); + automember_free_regex_rule((struct automemberRegexRule *)list); + } + + /* Free the list itself. */ + slapi_ch_free((void **)&(e->exclusive_rules)); + } + + /* Clear out the list contents. */ + if (e->inclusive_rules) { + PRCList *list; + + while (!PR_CLIST_IS_EMPTY((PRCList *)e->inclusive_rules)) { + list = PR_LIST_HEAD((PRCList *)e->inclusive_rules); + PR_REMOVE_LINK(list); + automember_free_regex_rule((struct automemberRegexRule *)list); + } + + /* Free the list itself. */ + slapi_ch_free((void **)&(e->inclusive_rules)); + } + + if (e->default_groups) { + slapi_ch_array_free(e->default_groups); + } + + if (e->grouping_attr) { + slapi_ch_free_string(&e->grouping_attr); + } + + if (e->grouping_value) { + slapi_ch_free_string(&e->grouping_value); + } + + slapi_ch_free((void **)entry); +} + +static void +automember_delete_configEntry(PRCList *entry) +{ + PR_REMOVE_LINK(entry); + automember_free_config_entry((struct configEntry **) &entry); +} + +static void +automember_delete_config() +{ + PRCList *list; + + /* Delete the config cache. */ + while (!PR_CLIST_IS_EMPTY(g_automember_config)) { + list = PR_LIST_HEAD(g_automember_config); + automember_delete_configEntry(list); + } + + return; +} + + +/* + * Helper functions + */ +static char * +automember_get_dn(Slapi_PBlock * pb) +{ + char *dn = 0; + slapi_log_error(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "--> automember_get_dn\n"); + + if (slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn)) { + slapi_log_error(SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_get_dn: failed to get dn of changed entry"); + goto bail; + } + + bail: + slapi_log_error(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "<-- automember_get_dn\n"); + + return dn; +} + +void +automember_set_config_area(char *dn) +{ + _ConfigAreaDN = dn; +} + +char * +automember_get_config_area() +{ + return _ConfigAreaDN; +} + +/* + * automember_dn_is_config() + * + * Checks if dn is an auto membership config entry. + */ +static int +automember_dn_is_config(char *dn) +{ + int ret = 0; + + slapi_log_error(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "--> automember_dn_is_config\n"); + + /* Return 1 if the passed in dn is a child of the main + * plugin config entry, or if it is a child of the + * container defined as a config area. */ + if ((slapi_dn_issuffix(dn, automember_get_plugin_dn()) && + strcasecmp(dn, automember_get_plugin_dn())) || + (automember_get_config_area() && slapi_dn_issuffix(dn, + automember_get_config_area()))) { + ret = 1; + } + + slapi_log_error(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "<-- automember_dn_is_config\n"); + + return ret; +} + +/* + * automember_oktodo() + * + * Check if we want to process this operation. We need to be + * sure that the operation succeeded. + */ +static int +automember_oktodo(Slapi_PBlock *pb) +{ + int ret = 1; + int oprc = 0; + + slapi_log_error( SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "--> automember_oktodo\n" ); + + if(slapi_pblock_get(pb, SLAPI_PLUGIN_OPRETURN, &oprc) != 0) { + slapi_log_error( SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_oktodo: could not get parameters\n" ); + ret = -1; + } + + /* This plugin should only execute if the operation succeeded. */ + if(oprc != 0) { + ret = 0; + } + + slapi_log_error( SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "<-- automember_oktodo\n" ); + + return ret; +} + +/* + * automember_isrepl() + * + * Returns 1 if the operation associated with pb + * is a replicated op. Returns 0 otherwise. + */ +static int +automember_isrepl(Slapi_PBlock *pb) +{ + int is_repl = 0; + + slapi_log_error( SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "--> automember_isrepl\n" ); + + slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_repl); + + slapi_log_error( SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "<-- automember_isrepl\n" ); + + return is_repl; +} + +/* + * automember_parse_regex_rule() + * + * Parses a regex rule and returns a regex rule struct. The caller + * will need to free this struct when it is finished with it. If + * there is a problem parsing the regex rule, an error will be + * logged and NULL will be returned. + */ +static struct automemberRegexRule * +automember_parse_regex_rule(char *rule_string) +{ + struct automemberRegexRule *rule = NULL; + Slapi_DN *target_group_dn = NULL; + char *desc = NULL; + char *attr = NULL; + Slapi_Regex *regex = NULL; + const char *recomp_result = NULL; + char *dn_string = NULL; + char *p = NULL; + char *p2 = NULL; + + /* A rule is in the form "target:desc:attr=regex" */ + /* Find the target group DN. */ + if ((p = strchr(rule_string, ':')) == NULL) { + slapi_log_error(SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_parse_regex_rule: Unable to parse " + "regex rule (missing first ':' delimeter).\n"); + goto bail; + } + + /* Ensure the target group DN is not empty. */ + if (p == rule_string) { + slapi_log_error(SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_parse_regex_rule: Unable to parse " + " regex rule (missing target group DN).\n"); + goto bail; + } + + if ((dn_string = strndup(rule_string, p - rule_string)) == NULL) { + slapi_log_error(SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_parse_regex_rule: Error allocating " + "memory.\n"); + goto bail; + } + + /* Ensure that the DN is valid. */ + if (slapi_dn_syntax_check(NULL, dn_string, 1) != 0) { + slapi_log_error(SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_parse_regex_rule: Unable to parse " + " regex rule (invalid target group DN).\n"); + slapi_cn_free_string(&dn_string); + goto bail; + } + + /* Create a Slapi_DN. */ + target_group_dn = slapi_sdn_new_dn_passin(dn_string); + + /* Find the description. */ + p++; + if (*p == '\0') { + slapi_log_error(SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_parse_regex_rule: Unable to parse " + "regex rule (missing description).\n"); + goto bail; + } + + p2 = p; + if ((p = strchr(p2, ':')) == NULL) { + slapi_log_error(SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_parse_regex_rule: Unable to parse " + "regex rule (missing second ':' delimeter).\n"); + goto bail; + } + + /* We allow an empty description. */ + if (p == p2) { + desc = slapi_ch_strdup(""); + } else { + if ((desc = strndup(p2, p - p2)) == NULL) { + slapi_log_error(SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_parse_regex_rule: Unable to allocate " + "memory.\n"); + goto bail; + } + } + + /* Find the comparison attribute name. */ + p++; + if (*p == '\0') { + slapi_log_error(SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_parse_regex_rule: Unable to parse " + "regex rule (missing comparison attribute).\n"); + goto bail; + } + + p2 = p; + if ((p = strchr(p2, '=')) == NULL) { + slapi_log_error(SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_parse_regex_rule: Unable to parse " + "regex rule (missing '=' delimeter).\n"); + goto bail; + } + + + if ((attr = strndup(p2, p - p2)) == NULL) { + slapi_log_error(SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_parse_regex_rule: Unable to allocate " + "memory.\n"); + goto bail; + } + + /* Validate the attribute. */ + for (p2 = attr; p2 && (*p2 != '\0'); p2++) { + if (!IS_ATTRDESC_CHAR(*p2)) { + slapi_log_error( SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_parse_regex_rule: Invalid comparison " + "attribute name \"%s\".\n", attr); + goto bail; + } + } + + /* Find the regex. */ + p++; + if (*p == '\0') { + slapi_log_error(SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_parse_regex_rule: Unable to parse " + "regex rule (missing regex).\n"); + goto bail; + } + + /* Compile the regex to validate it. */ + regex = slapi_re_comp(p, &recomp_result); + if (!regex) { + slapi_log_error(SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_parse_regex_rule: Unable to parse " + "regex rule (invalid regex). Error \"%s\".\n", + recomp_result?recomp_result:"unknown"); + } + + /* Validation has passed, so create the regex rule struct and fill it in. + * We hand off everything we have allocated. All of this will be free'd + * when the rule struct itself is freed. */ + rule = (struct automemberRegexRule *)slapi_ch_calloc(1, sizeof(struct automemberRegexRule)); + rule->target_group_dn = target_group_dn; + rule->desc = desc; + rule->attr = attr; + rule->regex_str = slapi_ch_strdup(p); + rule->regex = regex; + +bail: + /* Cleanup if we didn't successfully create a rule. */ + if (!rule) { + slapi_sdn_free(&target_group_dn); + slapi_ch_free_string(&desc); + slapi_ch_free_string(&attr); + slapi_re_free(regex); + } + + return rule; +} + +/* + * automember_free_regex_rule() + * + * Frees a regex rule and all of it's contents from memory. + */ +static void +automember_free_regex_rule(struct automemberRegexRule *rule) +{ + if (rule) { + if (rule->target_group_dn) { + slapi_sdn_free(&(rule->target_group_dn)); + } + + if (rule->desc) { + slapi_ch_free_string(&(rule->desc)); + } + + if (rule->attr) { + slapi_ch_free_string(&(rule->attr)); + } + + if (rule->regex_str) { + slapi_ch_free_string(&(rule->regex_str)); + } + + if (rule->regex) { + slapi_re_free(rule->regex); + } + } + + slapi_ch_free((void **)&rule); +} + +/* + * automember_parse_grouping_attr() + * + * Parses a grouping attribute and grouping value from + * the passed in config string. Memory will be allocated + * for grouping_attr and grouping_value, so it is up to + * the called to free them when they are no longer needed. + * Returns 0 upon success and 1 upon failure. + */ +static int +automember_parse_grouping_attr(char *value, char **grouping_attr, char **grouping_value) +{ + int ret = 0; + char *p = NULL; + + /* Clear out any existing type or value. */ + slapi_ch_free_string(grouping_attr); + slapi_ch_free_string(grouping_value); + + /* split out the type from the value (use the first ':') */ + if ((p = strchr(value, ':')) == NULL) { + slapi_log_error( SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_parse_grouping_attr: Value for grouping attribute " + "is not in the correct format. (value: \"%s\").\n", value); + ret = 1; + goto bail; + } + + /* Ensure the type is not empty. */ + if (p == value) { + slapi_log_error( SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_parse_grouping_attr: Value for grouping attribute " + "is not in the correct format. The grouping attribute is missing. " + "(value: \"%s\").\n", value); + ret = 1; + goto bail; + } + + /* Duplicate the type to be returned. */ + *grouping_attr = strndup(value, p - value); + + /* Advance p to point to the beginning of the value. */ + p++; + while (*p == ' ') { + p++; + } + + /* Ensure the value is not empty. */ + if (*p == '\0') { + slapi_log_error( SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_parse_grouping_attr: Value for grouping attribute " + "is not in the correct format. The grouping value is " + "missing. (value: \"%s\").\n", value); + ret = 1; + goto bail; + } + + /* Duplicate the value to be returned. */ + *grouping_value = slapi_ch_strdup(p); + + /* Ensure that memory was allocated successfully. */ + if ((*grouping_attr == NULL) || (*grouping_value == NULL)) { + slapi_log_error( SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_parse_grouping_attr: Error allocating memory.\n"); + ret = 1; + goto bail; + } + + /* Ensure that the grouping attr is a legal attr name. */ + for (p = *grouping_attr; p && (*p != '\0'); p++) { + if (!IS_ATTRDESC_CHAR(*p)) { + slapi_log_error( SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_parse_grouping_attr: Invalid value for " + "grouping attribute. The grouping attribute type is " + "illegal. (type: \"%s\").\n", *grouping_attr); + ret = 1; + goto bail; + } + } + + /* Ensure that the grouping value type is a legal attr name. */ + for (p = *grouping_value; p && (*p != '\0'); p++) { + if (!IS_ATTRDESC_CHAR(*p)) { + slapi_log_error( SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_parse_grouping_attr: Invalid value for " + "grouping attribute. The grouping value type is " + "illegal. (type: \"%s\").\n", *grouping_value); + ret = 1; + goto bail; + } + } + + bail: + if (ret != 0) { + slapi_ch_free_string(grouping_attr); + slapi_ch_free_string(grouping_value); + } + + return ret; +} + +/* + * automember_update_membership() + * + * Determines which target groups need to be updated according to + * the rules in config, then performs the updates. + */ +static void +automember_update_membership(struct configEntry *config, Slapi_Entry *e) +{ + PRCList *rule = NULL; + struct automemberRegexRule *curr_rule = NULL; + PRCList exclusions; + PRCList targets; + struct automemberDNListItem *dnitem = NULL; + Slapi_DN *last = NULL; + PRCList *curr_exclusion = NULL; + char **vals = NULL; + int i = 0; + + if (!config || !e) { + return; + } + + slapi_log_error(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_update_membership: Processing \"%s\" " + "definition entry for candidate entry \"%s\".\n", + config->dn, slapi_entry_get_dn(e)); + + /* Initialize our lists that keep track of targets. */ + PR_INIT_CLIST(&exclusions); + PR_INIT_CLIST(&targets); + + /* Go through exclusive rules and build an exclusion list. */ + if (config->exclusive_rules) { + if (!PR_CLIST_IS_EMPTY((PRCList *)config->exclusive_rules)) { + rule = PR_LIST_HEAD((PRCList *)config->exclusive_rules); + while (rule != (PRCList *)config->exclusive_rules) { + curr_rule = (struct automemberRegexRule *)rule; + + /* Regex rules are sorted by the target group DN. This means + * we can skip all rules for the last target group DN that we + * added to the exclusions list. */ + if ((last == NULL) || slapi_sdn_compare(last, curr_rule->target_group_dn) != 0) { + /* Get comparison attr and loop through values. */ + vals = slapi_entry_attr_get_charray(e, curr_rule->attr); + for (i = 0; vals && vals[i]; ++i) { + /* Evaluate the regex. */ + if (slapi_re_exec(curr_rule->regex, vals[i], -1) == 1) { + /* Found a match. Add to end of the exclusion list + * and set last as a hint to ourselves. */ + slapi_log_error(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_update_membership: Adding \"%s\" " + "to list of excluded groups for \"%s\" " + "(matched: \"%s=%s\").\n", + slapi_sdn_get_dn(curr_rule->target_group_dn), + slapi_entry_get_dn(e), curr_rule->attr, + curr_rule->regex_str); + dnitem = (struct automemberDNListItem *)slapi_ch_calloc(1, + sizeof(struct automemberDNListItem)); + /* We are just referencing the dn from the regex rule. We + * will not free it when we clean up this list. This list + * is more short-lived than the regex rule list, so we can + * get away with this optimization. */ + dnitem->dn = curr_rule->target_group_dn; + PR_APPEND_LINK(&(dnitem->list), &exclusions); + last = curr_rule->target_group_dn; + } + } + + slapi_ch_array_free(vals); + vals = NULL; + } + + rule = PR_NEXT_LINK(rule); + } + } + } + + /* Go through inclusive rules and build the target list. */ + if (config->inclusive_rules) { + if (!PR_CLIST_IS_EMPTY((PRCList *)config->inclusive_rules)) { + /* Clear out our last hint from processing exclusions. */ + last = NULL; + + /* Get the first excluded target for exclusion checking. */ + if (!PR_CLIST_IS_EMPTY(&exclusions)) { + curr_exclusion = PR_LIST_HEAD(&exclusions); + } + + rule = PR_LIST_HEAD((PRCList *)config->inclusive_rules); + while (rule != (PRCList *)config->inclusive_rules) { + curr_rule = (struct automemberRegexRule *)rule; + + /* The excluded targets are stored in alphabetical order. Instead + * of going through the entire exclusion list for each inclusive + * rule, we can simply go through the exclusion list once and keep + * track of our position. If the curent exclusion comes after + * the target DN used in the current inclusive rule, it can't be + * excluded. If the current exclusion comes before the target + * in the current rule, we need to go through the exclusion list + * until we find a target that is the same or comes after the + * current rule. */ + if (curr_exclusion) { + while ((curr_exclusion != &exclusions) && (strcmp(slapi_sdn_get_ndn( + ((struct automemberDNListItem *)curr_exclusion)->dn), + slapi_sdn_get_ndn(curr_rule->target_group_dn)) < 0)) { + curr_exclusion = PR_NEXT_LINK(curr_exclusion); + } + } + + /* Regex rules are sorted by the target group DN. This means + * we can skip all rules for the last target group DN that we + * added to the targets list. We also skip any rules for + * target groups that have been excluded by an exclusion rule. */ + if (((curr_exclusion == NULL) || (curr_exclusion == &exclusions) || + slapi_sdn_compare(((struct automemberDNListItem *)curr_exclusion)->dn, + curr_rule->target_group_dn) != 0) && ((last == NULL) || + (slapi_sdn_compare(last, curr_rule->target_group_dn) != 0))) { + /* Get comparison attr and loop through values. */ + vals = slapi_entry_attr_get_charray(e, curr_rule->attr); + for (i = 0; vals && vals[i]; ++i) { + /* Evaluate the regex. */ + if (slapi_re_exec(curr_rule->regex, vals[i], -1) == 1) { + /* Found a match. Add to the end of the targets list + * and set last as a hint to ourselves. */ + slapi_log_error(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_update_membership: Adding \"%s\" " + "to list of target groups for \"%s\" " + "(matched: \"%s=%s\").\n", + slapi_sdn_get_dn(curr_rule->target_group_dn), + slapi_entry_get_dn(e), curr_rule->attr, + curr_rule->regex_str); + dnitem = (struct automemberDNListItem *)slapi_ch_calloc(1, + sizeof(struct automemberDNListItem)); + /* We are just referencing the dn from the regex rule. We + * will not free it when we clean up this list. This list + * is more short-lived than the regex rule list, so we can + * get away with this optimization. */ + dnitem->dn = curr_rule->target_group_dn; + PR_APPEND_LINK(&(dnitem->list), &targets); + last = curr_rule->target_group_dn; + } + } + + slapi_ch_array_free(vals); + vals = NULL; + } + + rule = PR_NEXT_LINK(rule); + } + } + } + + /* If no targets, update default groups if set. Otherwise, update + * targets. Use a helper to do the actual updates. We can just pass an + * array of target group DNs along with our entry DN, the grouping attr, + * and the grouping value. */ + if (PR_CLIST_IS_EMPTY(&targets)) { + /* Add to each default group. */ + for (i = 0; config->default_groups && config->default_groups[i]; i++) { + automember_add_member_value(e, config->default_groups[i], + config->grouping_attr, config->grouping_value); + } + } else { + /* Update the target groups. */ + dnitem = (struct automemberDNListItem *)PR_LIST_HEAD(&targets); + while ((PRCList *)dnitem != &targets) { + automember_add_member_value(e, slapi_sdn_get_dn(dnitem->dn), + config->grouping_attr, config->grouping_value); + dnitem = (struct automemberDNListItem *)PR_NEXT_LINK((PRCList *)dnitem); + } + } + + /* Free the exclusions and targets lists. Remember that the DN's + * are not ours, so don't free them! */ + while (!PR_CLIST_IS_EMPTY(&exclusions)) { + dnitem = (struct automemberDNListItem *)PR_LIST_HEAD(&exclusions); + PR_REMOVE_LINK((PRCList *)dnitem); + slapi_ch_free((void**)&dnitem); + } + + while (!PR_CLIST_IS_EMPTY(&targets)) { + dnitem = (struct automemberDNListItem *)PR_LIST_HEAD(&targets); + PR_REMOVE_LINK((PRCList *)dnitem); + slapi_ch_free((void**)&dnitem); + } + +} + +/* + * automember_add_member_value() + * + * Adds a member entry to a group. + */ +static void +automember_add_member_value(Slapi_Entry *member_e, const char *group_dn, + char *grouping_attr, char *grouping_value) +{ + Slapi_PBlock *mod_pb = slapi_pblock_new(); + int result = LDAP_SUCCESS; + LDAPMod mod; + LDAPMod *mods[2]; + char *vals[2]; + char *member_value = NULL; + int freeit = 0; + + /* If grouping_value is dn, we need to fetch the dn instead. */ + if (slapi_attr_type_cmp(grouping_value, "dn", SLAPI_TYPE_CMP_EXACT) == 0) { + member_value = slapi_entry_get_ndn(member_e); + } else { + member_value = slapi_entry_attr_get_charptr(member_e, grouping_value); + freeit = 1; + } + + if (member_value) { + /* Set up the operation. */ + vals[0] = member_value; + vals[1] = 0; + mod.mod_op = LDAP_MOD_ADD; + mod.mod_type = grouping_attr; + mod.mod_values = vals; + mods[0] = &mod; + mods[1] = 0; + + /* Perform the modify operation. */ + slapi_log_error(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_add_member_value: Adding \"%s\" as " + "a \"%s\" value to group \"%s\".\n", + member_value, grouping_attr, group_dn); + + slapi_modify_internal_set_pb(mod_pb, group_dn, + mods, 0, 0, automember_get_plugin_id(), 0); + slapi_modify_internal_pb(mod_pb); + slapi_pblock_get(mod_pb, SLAPI_PLUGIN_INTOP_RESULT, &result); + + if ((result != LDAP_SUCCESS) && (result != LDAP_TYPE_OR_VALUE_EXISTS)) { + slapi_log_error(SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_add_member_value: Unable to add \"%s\" as " + "a \"%s\" value to group \"%s\" (%s).\n", + member_value, grouping_attr, group_dn, + ldap_err2string(result)); + } + } else { + slapi_log_error(SLAPI_LOG_FATAL, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_add_member_value: Unable to find grouping " + "value attribute \"%s\" in entry \"%s\".\n", + grouping_value, slapi_entry_get_dn(member_e)); + } + + /* Cleanup */ + if (freeit) { + slapi_ch_free_string(&member_value); + } + + slapi_pblock_destroy(mod_pb); +} + + +/* + * Operation callback functions + */ + +/* + * automember_pre_op() + * + * Checks if an operation affects the auto membership + * config and validates the config changes. + */ +static int +automember_pre_op(Slapi_PBlock * pb, int modop) +{ + char *dn = 0; + Slapi_Entry *e = 0; + Slapi_Mods *smods = 0; + LDAPMod **mods; + int free_entry = 0; + char *errstr = NULL; + int ret = 0; + + slapi_log_error(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "--> automember_pre_op\n"); + + /* Just bail if we aren't ready to service requests yet. */ + if (!g_plugin_started) + goto bail; + + if (0 == (dn = automember_get_dn(pb))) + goto bail; + + if (automember_dn_is_config(dn)) { + /* Validate config changes, but don't apply them. + * This allows us to reject invalid config changes + * here at the pre-op stage. Applying the config + * needs to be done at the post-op stage. */ + + if (LDAP_CHANGETYPE_ADD == modop) { + slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &e); + + } else if (LDAP_CHANGETYPE_MODIFY == modop) { + /* Fetch the entry being modified so we can + * create the resulting entry for validation. */ + Slapi_DN *tmp_dn = slapi_sdn_new_dn_byref(dn); + if (tmp_dn) { + slapi_search_internal_get_entry(tmp_dn, 0, &e, automember_get_plugin_id()); + slapi_sdn_free(&tmp_dn); + free_entry = 1; + } + + /* If the entry doesn't exist, just bail and + * let the server handle it. */ + if (e == NULL) { + goto bail; + } + + /* Grab the mods. */ + slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods); + smods = slapi_mods_new(); + slapi_mods_init_byref(smods, mods); + + /* Apply the mods to create the resulting entry. */ + if (mods && (slapi_entry_apply_mods(e, mods) != LDAP_SUCCESS)) { + /* The mods don't apply cleanly, so we just let this op go + * to let the main server handle it. */ + goto bailmod; + } + } + + if (automember_parse_config_entry(e, 0) != 0) { + /* Refuse the operation if config parsing failed. */ + ret = LDAP_UNWILLING_TO_PERFORM; + if (LDAP_CHANGETYPE_ADD == modop) { + errstr = slapi_ch_smprintf("Not a valid auto membership configuration entry."); + } else { + errstr = slapi_ch_smprintf("Changes result in an invalid " + "auto membership configuration."); + } + } + } + + bailmod: + /* Clean up smods. */ + if (LDAP_CHANGETYPE_MODIFY == modop) { + slapi_mods_free(&smods); + } + + bail: + if (free_entry && e) + slapi_entry_free(e); + + if (ret) { + slapi_log_error(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_pre_op: operation failure [%d]\n", ret); + slapi_send_ldap_result(pb, ret, NULL, errstr, 0, NULL); + slapi_ch_free((void **)&errstr); + ret = -1; + } + + slapi_log_error(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "<-- automember_pre_op\n"); + + return ret; +} + +static int +automember_add_pre_op(Slapi_PBlock * pb) +{ + return automember_pre_op(pb, LDAP_CHANGETYPE_ADD); +} + +static int +automember_mod_pre_op(Slapi_PBlock * pb) +{ + return automember_pre_op(pb, LDAP_CHANGETYPE_MODIFY); +} + +/* + * automember_mod_post_op() + * + * Reloads the auto membership config + * if config changes were made. + */ +static int +automember_mod_post_op(Slapi_PBlock *pb) +{ + Slapi_Mods *smods = NULL; + char *dn = NULL; + + slapi_log_error(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "--> automember_mod_post_op\n"); + + /* Just bail if we aren't ready to service requests yet. */ + if (!g_plugin_started) + return 0; + + if (automember_oktodo(pb) && (dn = automember_get_dn(pb))) { + /* Check if the config is being modified and reload if so. */ + if (automember_dn_is_config(dn)) { + automember_load_config(); + } + } + + bail: + slapi_log_error(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "<-- automember_mod_post_op\n"); + + return 0; +} + +static int +automember_add_post_op(Slapi_PBlock *pb) +{ + Slapi_Entry *e = NULL; + char *dn = NULL; + struct configEntry *config = NULL; + PRCList *list = NULL; + + slapi_log_error(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "--> automember_add_post_op\n"); + + /* Just bail if we aren't ready to service requests yet. */ + if (!g_plugin_started || !automember_oktodo(pb)) + return 0; + + /* Reload config if a config entry was added. */ + if ((dn = automember_get_dn(pb))) { + if (automember_dn_is_config(dn)) { + automember_load_config(); + } + } else { + slapi_log_error(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_add_post_op: Error " + "retrieving dn\n"); + } + + /* If replication, just bail. */ + if (automember_isrepl(pb)) { + return 0; + } + + /* Get the newly added entry. */ + slapi_pblock_get(pb, SLAPI_ENTRY_POST_OP, &e); + + if (e) { + /* If the entry is a tombstone, just bail. */ + if (slapi_entry_attr_hasvalue(e, SLAPI_ATTR_OBJECTCLASS, + SLAPI_ATTR_VALUE_TOMBSTONE)) { + return 0; + } + + /* Check if a config entry applies + * to the entry being added. */ + automember_config_read_lock(); + + if (!PR_CLIST_IS_EMPTY(g_automember_config)) { + list = PR_LIST_HEAD(g_automember_config); + while (list != g_automember_config) { + config = (struct configEntry *)list; + + /* Does the entry meet scope and filter requirements? */ + if (slapi_dn_issuffix(dn, config->scope) && + (slapi_filter_test_simple(e, config->filter) == 0)) { + /* Find out what membership changes are needed and make them. */ + automember_update_membership(config, e); + } + + list = PR_NEXT_LINK(list); + } + } + + automember_config_unlock(); + } else { + slapi_log_error(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_add_post_op: Error " + "retrieving post-op entry %s\n", dn); + } + + slapi_log_error(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "<-- automember_add_post_op\n"); + + return 0; +} + +/* + * automember_del_post_op() + * + * Removes deleted config. + */ +static int +automember_del_post_op(Slapi_PBlock *pb) +{ + char *dn = NULL; + + slapi_log_error(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "--> automember_del_post_op\n"); + + /* Just bail if we aren't ready to service requests yet. */ + if (!g_plugin_started || !automember_oktodo(pb)) { + return 0; + } + + /* Reload config if a config entry was deleted. */ + if ((dn = automember_get_dn(pb))) { + if (automember_dn_is_config(dn)) + automember_load_config(); + } else { + slapi_log_error(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_del_post_op: Error " + "retrieving dn\n"); + } + + slapi_log_error(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "<-- automember_del_post_op\n"); + + return 0; +} + +/* + * automember_modrdn_post_op() + * + * Reloads config if config entries move + * into or out of our config scope. + */ +static int +automember_modrdn_post_op(Slapi_PBlock *pb) +{ + char *old_dn = NULL; + char *new_dn = NULL; + Slapi_Entry *post_e = NULL; + + slapi_log_error(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "--> automember_modrdn_post_op\n"); + + /* Just bail if we aren't ready to service requests yet. */ + if (!g_plugin_started || !automember_oktodo(pb)) + return 0;; + + /* Reload config if an existing config entry was renamed, + * or if the new dn brings an entry into the scope of the + * config entries. */ + slapi_pblock_get(pb, SLAPI_ENTRY_POST_OP, &post_e); + if (post_e) { + new_dn = slapi_entry_get_ndn(post_e); + } else { + slapi_log_error(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_modrdn_post_op: Error " + "retrieving post-op entry\n"); + return 0; + } + + if ((old_dn = automember_get_dn(pb))) { + if (automember_dn_is_config(old_dn) || automember_dn_is_config(new_dn)) + automember_load_config(); + } else { + slapi_log_error(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "automember_modrdn_post_op: Error " + "retrieving dn\n"); + } + + slapi_log_error(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM, + "<-- automember_modrdn_post_op\n"); + + return 0; +} + diff --git a/ldap/servers/plugins/automember/automember.h b/ldap/servers/plugins/automember/automember.h new file mode 100644 index 0000000..979de92 --- /dev/null +++ b/ldap/servers/plugins/automember/automember.h @@ -0,0 +1,128 @@ +/** BEGIN COPYRIGHT BLOCK + * This Program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; version 2 of the License. + * + * This Program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * this Program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, Boston, MA 02111-1307 USA. + * + * In addition, as a special exception, Red Hat, Inc. gives You the additional + * right to link the code of this Program with code not covered under the GNU + * General Public License ("Non-GPL Code") and to distribute linked combinations + * including the two, subject to the limitations in this paragraph. Non-GPL Code + * permitted under this exception must only link to the code of this Program + * through those well defined interfaces identified in the file named EXCEPTION + * found in the source code files (the "Approved Interfaces"). The files of + * Non-GPL Code may instantiate templates or use macros or inline functions from + * the Approved Interfaces without causing the resulting work to be covered by + * the GNU General Public License. Only Red Hat, Inc. may make changes or + * additions to the list of Approved Interfaces. You must obey the GNU General + * Public License in all respects for all of the Program code and other code used + * in conjunction with the Program except the Non-GPL Code covered by this + * exception. If you modify this file, you may extend this exception to your + * version of the file, but you are not obligated to do so. If you do not wish to + * provide this exception without modification, you must delete this exception + * statement from your version and license this file solely under the GPL without + * exception. + * + * + * Copyright (C) 2011 Red Hat, Inc. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* + * Auto Membership plug-in header file + */ +#include +#include +#include +#include +#include "portable.h" +#include "nspr.h" +#include "slapi-plugin.h" +#include "prclist.h" +#include "ldif.h" + +/* + * Plug-in defines + */ +#define AUTOMEMBER_PLUGIN_SUBSYSTEM "auto-membership-plugin" +#define AUTOMEMBER_FEATURE_DESC "Auto Membership" +#define AUTOMEMBER_PLUGIN_DESC "Auto Membership plugin" +#define AUTOMEMBER_INT_POSTOP_DESC "Auto Membership internal postop plugin" +#define AUTOMEMBER_POSTOP_DESC "Auto Membership postop plugin" + +/* + * Config type defines + */ +#define AUTOMEMBER_SCOPE_TYPE "autoMemberScope" +#define AUTOMEMBER_FILTER_TYPE "autoMemberFilter" +#define AUTOMEMBER_EXC_REGEX_TYPE "autoMemberExclusiveRegex" +#define AUTOMEMBER_INC_REGEX_TYPE "autoMemberInclusiveRegex" +#define AUTOMEMBER_DEFAULT_GROUP_TYPE "autoMemberDefaultGroup" +#define AUTOMEMBER_GROUPING_ATTR_TYPE "autoMemberGroupingAttr" +#define AUTOMEMBER_DISABLED_TYPE "autoMemberDisabled" + +/* + * Helper defines + */ +#define IS_ATTRDESC_CHAR(c) ( isalnum(c) || (c == '.') || (c == ';') || (c == '-') ) + +struct automemberRegexRule { + PRCList list; + Slapi_DN *target_group_dn; + char *desc; + char *attr; + char *regex_str; + Slapi_Regex *regex; +}; + +struct automemberDNListItem { + PRCList list; + Slapi_DN *dn; +}; + +/* + * Linked list of config entries. + */ +struct configEntry { + PRCList list; + char *dn; + char *scope; + Slapi_Filter *filter; + struct automemberRegexRule *exclusive_rules; + struct automemberRegexRule *inclusive_rules; + char **default_groups; + char *grouping_attr; + char *grouping_value; +}; + +/* + * Config fetch function + */ +PRCList *automember_get_config(); + +/* + * Config cache locking functions + */ +void automember_config_read_lock(); +void automember_config_write_lock(); +void automember_config_unlock(); + +/* + * Plugin identity functions + */ +void automember_set_plugin_id(void *pluginID); +void *automember_get_plugin_id(); +void automember_set_plugin_dn(char *pluginDN); +char *automember_get_plugin_dn(); + diff --git a/ldap/servers/slapd/pblock.c b/ldap/servers/slapd/pblock.c index 6bc2375..215b7df 100644 --- a/ldap/servers/slapd/pblock.c +++ b/ldap/servers/slapd/pblock.c @@ -499,6 +499,9 @@ slapi_pblock_get( Slapi_PBlock *pblock, int arg, void *value ) case SLAPI_PLUGIN_IDENTITY: (*(void**)value) = pblock->pb_plugin_identity; break; + case SLAPI_PLUGIN_CONFIG_AREA: + (*(char **)value) = pblock->pb_plugin_config_area; + break; case SLAPI_PLUGIN_INTOP_RESULT: (*(int *)value) = pblock->pb_internal_op_result; break; @@ -1931,6 +1934,9 @@ slapi_pblock_set( Slapi_PBlock *pblock, int arg, void *value ) case SLAPI_PLUGIN_IDENTITY: pblock->pb_plugin_identity = (void*)value; break; + case SLAPI_PLUGIN_CONFIG_AREA: + pblock->pb_plugin_config_area = (char *) value; + break; case SLAPI_PLUGIN_DESTROY_FN: pblock->pb_destroy_fn = (IFP) value; break; diff --git a/ldap/servers/slapd/plugin.c b/ldap/servers/slapd/plugin.c index cc88dc4..fae1f8e 100644 --- a/ldap/servers/slapd/plugin.c +++ b/ldap/servers/slapd/plugin.c @@ -724,6 +724,7 @@ typedef struct _plugin_dep_config { int total_type; char **depends_named_list; int total_named; + char *config_area; } plugin_dep_config; /* list of plugins which should be shutdown in reverse order */ @@ -994,6 +995,16 @@ plugin_dependency_startall(int argc, char** argv, char *errmsg, int operation) slapi_pblock_set(&(config[plugin_index].pb), SLAPI_ADD_ENTRY, plugin_entry ); + /* Pass the plugin alternate config area DN in SLAPI_PLUGIN_CONFIG_AREA. */ + value = slapi_entry_attr_get_charptr(plugin_entry, ATTR_PLUGIN_CONFIG_AREA); + if(value) + { + config[plugin_index].config_area = value; + value = NULL; + slapi_pblock_set(&(config[plugin_index].pb), SLAPI_PLUGIN_CONFIG_AREA, + config[plugin_index].config_area); + } + value = slapi_entry_attr_get_charptr(plugin_entry, "nsslapd-plugintype"); if(value) { diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h index b3728d3..1af2eec 100644 --- a/ldap/servers/slapd/slap.h +++ b/ldap/servers/slapd/slap.h @@ -709,6 +709,7 @@ struct matchingRuleList { #define ATTR_PLUGIN_DESC "nsslapd-pluginDescription" #define ATTR_PLUGIN_ENABLED "nsslapd-pluginEnabled" #define ATTR_PLUGIN_ARG "nsslapd-pluginArg" +#define ATTR_PLUGIN_CONFIG_AREA "nsslapd-pluginConfigArea" #define ATTR_PLUGIN_BACKEND "nsslapd-backend" #define ATTR_PLUGIN_SCHEMA_CHECK "nsslapd-schemaCheck" #define ATTR_PLUGIN_LOG_ACCESS "nsslapd-logAccess" @@ -1515,6 +1516,7 @@ typedef struct slapi_pblock { Slapi_Entry **pb_plugin_internal_search_op_entries; char **pb_plugin_internal_search_op_referrals; void *pb_plugin_identity; /* identifies plugin for internal operation */ + char *pb_plugin_config_area; /* optional config area */ void *pb_parent_txn; /* parent transaction ID */ void *pb_txn; /* transaction ID */ IFP pb_txn_ruv_mods_fn; /* Function to fetch RUV mods for txn */ diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h index 8531fbe..4ec8c70 100644 --- a/ldap/servers/slapd/slapi-plugin.h +++ b/ldap/servers/slapd/slapi-plugin.h @@ -5904,6 +5904,8 @@ typedef struct slapi_plugindesc { #define SLAPI_PLUGIN_ENTRY_STORE_FUNC 814 #define SLAPI_PLUGIN_ENABLED 815 +#define SLAPI_PLUGIN_CONFIG_AREA 816 + /* * Defined values of SLAPI_PLUGIN_SYNTAX_FLAGS: */ -- 1.7.4