From 04efbf04c46a84429c60bd6c624ab6282d9dd913 Mon Sep 17 00:00:00 2001
From: Nathan Kinder <nkinder@redhat.com>
Date: Thu, 25 Jun 2009 14:28:56 -0700
Subject: [PATCH] Add linked attributes plug-in.

This patch implements a linked attribute plug-in.  Details of the
plug-in features and design are available on the 389 wiki at:

  http://directory.fedoraproject.org/wiki/Linked_Attributes_Design

In addition, I encountered a memory leak when testing the new plug-in
with valgrind.  There was a fix to the dse add code for a double free
a few months back, which causes a leak in certain situations.  This
previous fix was for bz#489763.  The proper thing to do is to make
the dse backend add function consume the passed in entry upon success
and leave it for the caller to deal with upon failure.  This is the
way the back-ldbm add function works.
---
 Makefile.am                                        |   83 +-
 Makefile.in                                        |  128 +-
 .../src/scripts/template-fixup-linkedattrs.pl.in   |  152 ++
 ldap/ldif/template-dse.ldif.in                     |   12 +
 ldap/servers/plugins/linkedattrs/fixup_task.c      |  401 ++++
 ldap/servers/plugins/linkedattrs/linked_attrs.c    | 2024 ++++++++++++++++++++
 ldap/servers/plugins/linkedattrs/linked_attrs.h    |  141 ++
 ldap/servers/slapd/add.c                           |    4 +-
 ldap/servers/slapd/attr.c                          |    2 +-
 ldap/servers/slapd/dse.c                           |   13 +-
 10 files changed, 2886 insertions(+), 74 deletions(-)
 create mode 100644 ldap/admin/src/scripts/template-fixup-linkedattrs.pl.in
 create mode 100644 ldap/servers/plugins/linkedattrs/fixup_task.c
 create mode 100644 ldap/servers/plugins/linkedattrs/linked_attrs.c
 create mode 100644 ldap/servers/plugins/linkedattrs/linked_attrs.h

diff --git a/Makefile.am b/Makefile.am
index 27869a9..4dd8973 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -148,12 +148,13 @@ enable_presence = off
 endif
 
 serverplugin_LTLIBRARIES = libacl-plugin.la libattr-unique-plugin.la \
-	libback-ldbm.la libchainingdb-plugin.la libcos-plugin.la libdes-plugin.la \
-	libdistrib-plugin.la libhttp-client-plugin.la libcollation-plugin.la \
+	libback-ldbm.la libchainingdb-plugin.la libcollation-plugin.la \
+	libcos-plugin.la libdes-plugin.la libdistrib-plugin.la \
+	libhttp-client-plugin.la liblinkedattrs-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 \
-	libviews-plugin.la libschemareload-plugin.la $(LIBPAM_PASSTHRU_PLUGIN) \
+	libroles-plugin.la libschemareload-plugin.la libstatechange-plugin.la \
+	libsyntax-plugin.la libviews-plugin.la $(LIBPAM_PASSTHRU_PLUGIN) \
 	$(LIBDNA_PLUGIN) $(LIBBITWISE_PLUGIN) $(LIBPRESENCE_PLUGIN)
 
 nodist_property_DATA = ns-slapd.properties
@@ -299,6 +300,7 @@ task_SCRIPTS = ldap/admin/src/scripts/template-bak2db \
 	ldap/admin/src/scripts/template-db2bak.pl \
 	ldap/admin/src/scripts/template-db2index.pl \
 	ldap/admin/src/scripts/template-db2ldif.pl \
+        ldap/admin/src/scripts/template-fixup-linkedattrs.pl \
 	ldap/admin/src/scripts/template-fixup-memberof.pl \
 	ldap/admin/src/scripts/template-ldif2db.pl \
 	ldap/admin/src/scripts/template-ns-accountstatus.pl \
@@ -664,6 +666,14 @@ libattr_unique_plugin_la_CPPFLAGS = -I$(srcdir)/ldap/servers/plugins/shared $(PL
 libattr_unique_plugin_la_LDFLAGS = -avoid-version
 
 #------------------------
+# libbitwise-plugin
+#------------------------
+libbitwise_plugin_la_SOURCES = ldap/servers/plugins/bitwise/bitwise.c
+
+libbitwise_plugin_la_CPPFLAGS = $(PLUGIN_CPPFLAGS)
+libbitwise_plugin_la_LDFLAGS = -avoid-version
+
+#------------------------
 # libchainingdb-plugin
 #------------------------
 libchainingdb_plugin_la_SOURCES = ldap/servers/plugins/chainingdb/cb_abandon.c \
@@ -696,6 +706,18 @@ libchainingdb_plugin_la_CPPFLAGS = $(PLUGIN_CPPFLAGS)
 libchainingdb_plugin_la_LDFLAGS = -avoid-version
 
 #------------------------
+# libcollation-plugin
+#------------------------
+libcollation_plugin_la_SOURCES = ldap/servers/plugins/collation/collate.c \
+        ldap/servers/plugins/collation/config.c \
+        ldap/servers/plugins/collation/orfilter.c
+
+libcollation_plugin_la_CPPFLAGS = @icu_inc@ $(PLUGIN_CPPFLAGS)
+libcollation_plugin_la_LIBADD = $(ICU_LINK) $(LIBCSTD) $(LIBCRUN)
+libcollation_plugin_la_LDFLAGS = -avoid-version
+libcollation_plugin_la_LINK = $(CXXLINK) -avoid-version
+
+#------------------------
 # libcos-plugin
 #------------------------
 libcos_plugin_la_SOURCES = ldap/servers/plugins/cos/cos.c \
@@ -722,6 +744,14 @@ libdistrib_plugin_la_CPPFLAGS = $(PLUGIN_CPPFLAGS)
 libdistrib_plugin_la_LDFLAGS = -avoid-version
 
 #------------------------
+# libdna-plugin
+#------------------------
+libdna_plugin_la_SOURCES = ldap/servers/plugins/dna/dna.c
+
+libdna_plugin_la_CPPFLAGS = $(PLUGIN_CPPFLAGS)
+libdna_plugin_la_LDFLAGS = -avoid-version
+
+#------------------------
 # libhttp-client-plugin
 #------------------------
 libhttp_client_plugin_la_SOURCES = ldap/servers/plugins/http/http_client.c \
@@ -731,16 +761,13 @@ libhttp_client_plugin_la_CPPFLAGS = $(PLUGIN_CPPFLAGS)
 libhttp_client_plugin_la_LDFLAGS = -avoid-version
 
 #------------------------
-# libcollation-plugin
+# liblinkedattrs-plugin
 #------------------------
-libcollation_plugin_la_SOURCES = ldap/servers/plugins/collation/collate.c \
-	ldap/servers/plugins/collation/config.c \
-	ldap/servers/plugins/collation/orfilter.c
+liblinkedattrs_plugin_la_SOURCES = ldap/servers/plugins/linkedattrs/fixup_task.c \
+	ldap/servers/plugins/linkedattrs/linked_attrs.c
 
-libcollation_plugin_la_CPPFLAGS = @icu_inc@ $(PLUGIN_CPPFLAGS)
-libcollation_plugin_la_LIBADD = $(ICU_LINK) $(LIBCSTD) $(LIBCRUN)
-libcollation_plugin_la_LDFLAGS = -avoid-version
-libcollation_plugin_la_LINK = $(CXXLINK) -avoid-version
+liblinkedattrs_plugin_la_CPPFLAGS = $(PLUGIN_CPPFLAGS)
+liblinkedattrs_plugin_la_LDFLAGS = -avoid-version
 
 #------------------------
 # libmemberof-plugin
@@ -892,6 +919,14 @@ libroles_plugin_la_CPPFLAGS = $(PLUGIN_CPPFLAGS)
 libroles_plugin_la_LDFLAGS = -avoid-version
 
 #------------------------
+# libschemareload-plugin
+#------------------------
+libschemareload_plugin_la_SOURCES = ldap/servers/plugins/schema_reload/schema_reload.c
+
+libschemareload_plugin_la_CPPFLAGS = $(PLUGIN_CPPFLAGS)
+libschemareload_plugin_la_LDFLAGS = -avoid-version
+
+#------------------------
 # libstatechange-plugin
 #------------------------
 libstatechange_plugin_la_SOURCES = ldap/servers/plugins/statechange/statechange.c
@@ -928,30 +963,6 @@ libviews_plugin_la_SOURCES = ldap/servers/plugins/views/views.c
 libviews_plugin_la_CPPFLAGS = $(PLUGIN_CPPFLAGS)
 libviews_plugin_la_LDFLAGS = -avoid-version
 
-#------------------------
-# libschemareload-plugin
-#------------------------
-libschemareload_plugin_la_SOURCES = ldap/servers/plugins/schema_reload/schema_reload.c
-
-libschemareload_plugin_la_CPPFLAGS = $(PLUGIN_CPPFLAGS)
-libschemareload_plugin_la_LDFLAGS = -avoid-version
-
-#------------------------
-# libdna-plugin
-#------------------------
-libdna_plugin_la_SOURCES = ldap/servers/plugins/dna/dna.c
-
-libdna_plugin_la_CPPFLAGS = $(PLUGIN_CPPFLAGS)
-libdna_plugin_la_LDFLAGS = -avoid-version
-
-#------------------------
-# libbitwise-plugin
-#------------------------
-libbitwise_plugin_la_SOURCES = ldap/servers/plugins/bitwise/bitwise.c
-
-libbitwise_plugin_la_CPPFLAGS = $(PLUGIN_CPPFLAGS)
-libbitwise_plugin_la_LDFLAGS = -avoid-version
-
 
 #////////////////////////////////////////////////////////////////
 #
diff --git a/Makefile.in b/Makefile.in
index 1e42369..e82a1e6 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -279,6 +279,14 @@ libhttp_client_plugin_la_OBJECTS =  \
 libhttp_client_plugin_la_LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) \
 	$(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
 	$(libhttp_client_plugin_la_LDFLAGS) $(LDFLAGS) -o $@
+liblinkedattrs_plugin_la_LIBADD =
+am_liblinkedattrs_plugin_la_OBJECTS = ldap/servers/plugins/linkedattrs/liblinkedattrs_plugin_la-fixup_task.lo \
+	ldap/servers/plugins/linkedattrs/liblinkedattrs_plugin_la-linked_attrs.lo
+liblinkedattrs_plugin_la_OBJECTS =  \
+	$(am_liblinkedattrs_plugin_la_OBJECTS)
+liblinkedattrs_plugin_la_LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) \
+	$(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+	$(liblinkedattrs_plugin_la_LDFLAGS) $(LDFLAGS) -o $@
 libmemberof_plugin_la_LIBADD =
 am_libmemberof_plugin_la_OBJECTS = ldap/servers/plugins/memberof/libmemberof_plugin_la-memberof.lo \
 	ldap/servers/plugins/memberof/libmemberof_plugin_la-memberof_config.lo
@@ -872,6 +880,7 @@ SOURCES = $(libavl_a_SOURCES) $(libldaputil_a_SOURCES) \
 	$(libdes_plugin_la_SOURCES) $(libdistrib_plugin_la_SOURCES) \
 	$(libdna_plugin_la_SOURCES) \
 	$(libhttp_client_plugin_la_SOURCES) \
+	$(liblinkedattrs_plugin_la_SOURCES) \
 	$(libmemberof_plugin_la_SOURCES) $(libns_dshttpd_la_SOURCES) \
 	$(libpam_passthru_plugin_la_SOURCES) \
 	$(libpassthru_plugin_la_SOURCES) \
@@ -898,6 +907,7 @@ DIST_SOURCES = $(libavl_a_SOURCES) $(libldaputil_a_SOURCES) \
 	$(libdes_plugin_la_SOURCES) $(libdistrib_plugin_la_SOURCES) \
 	$(libdna_plugin_la_SOURCES) \
 	$(libhttp_client_plugin_la_SOURCES) \
+	$(liblinkedattrs_plugin_la_SOURCES) \
 	$(libmemberof_plugin_la_SOURCES) $(libns_dshttpd_la_SOURCES) \
 	$(libpam_passthru_plugin_la_SOURCES) \
 	$(libpassthru_plugin_la_SOURCES) \
@@ -1222,12 +1232,13 @@ server_LTLIBRARIES = libslapd.la libns-dshttpd.la
 @enable_presence_FALSE@enable_presence = off
 @enable_presence_TRUE@enable_presence = on
 serverplugin_LTLIBRARIES = libacl-plugin.la libattr-unique-plugin.la \
-	libback-ldbm.la libchainingdb-plugin.la libcos-plugin.la libdes-plugin.la \
-	libdistrib-plugin.la libhttp-client-plugin.la libcollation-plugin.la \
+	libback-ldbm.la libchainingdb-plugin.la libcollation-plugin.la \
+	libcos-plugin.la libdes-plugin.la libdistrib-plugin.la \
+	libhttp-client-plugin.la liblinkedattrs-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 \
-	libviews-plugin.la libschemareload-plugin.la $(LIBPAM_PASSTHRU_PLUGIN) \
+	libroles-plugin.la libschemareload-plugin.la libstatechange-plugin.la \
+	libsyntax-plugin.la libviews-plugin.la $(LIBPAM_PASSTHRU_PLUGIN) \
 	$(LIBDNA_PLUGIN) $(LIBBITWISE_PLUGIN) $(LIBPRESENCE_PLUGIN)
 
 nodist_property_DATA = ns-slapd.properties
@@ -1372,6 +1383,7 @@ task_SCRIPTS = ldap/admin/src/scripts/template-bak2db \
 	ldap/admin/src/scripts/template-db2bak.pl \
 	ldap/admin/src/scripts/template-db2index.pl \
 	ldap/admin/src/scripts/template-db2ldif.pl \
+        ldap/admin/src/scripts/template-fixup-linkedattrs.pl \
 	ldap/admin/src/scripts/template-fixup-memberof.pl \
 	ldap/admin/src/scripts/template-ldif2db.pl \
 	ldap/admin/src/scripts/template-ns-accountstatus.pl \
@@ -1684,6 +1696,13 @@ libattr_unique_plugin_la_CPPFLAGS = -I$(srcdir)/ldap/servers/plugins/shared $(PL
 libattr_unique_plugin_la_LDFLAGS = -avoid-version
 
 #------------------------
+# libbitwise-plugin
+#------------------------
+libbitwise_plugin_la_SOURCES = ldap/servers/plugins/bitwise/bitwise.c
+libbitwise_plugin_la_CPPFLAGS = $(PLUGIN_CPPFLAGS)
+libbitwise_plugin_la_LDFLAGS = -avoid-version
+
+#------------------------
 # libchainingdb-plugin
 #------------------------
 libchainingdb_plugin_la_SOURCES = ldap/servers/plugins/chainingdb/cb_abandon.c \
@@ -1716,6 +1735,18 @@ libchainingdb_plugin_la_CPPFLAGS = $(PLUGIN_CPPFLAGS)
 libchainingdb_plugin_la_LDFLAGS = -avoid-version
 
 #------------------------
+# libcollation-plugin
+#------------------------
+libcollation_plugin_la_SOURCES = ldap/servers/plugins/collation/collate.c \
+        ldap/servers/plugins/collation/config.c \
+        ldap/servers/plugins/collation/orfilter.c
+
+libcollation_plugin_la_CPPFLAGS = @icu_inc@ $(PLUGIN_CPPFLAGS)
+libcollation_plugin_la_LIBADD = $(ICU_LINK) $(LIBCSTD) $(LIBCRUN)
+libcollation_plugin_la_LDFLAGS = -avoid-version
+libcollation_plugin_la_LINK = $(CXXLINK) -avoid-version
+
+#------------------------
 # libcos-plugin
 #------------------------
 libcos_plugin_la_SOURCES = ldap/servers/plugins/cos/cos.c \
@@ -1741,6 +1772,13 @@ libdistrib_plugin_la_CPPFLAGS = $(PLUGIN_CPPFLAGS)
 libdistrib_plugin_la_LDFLAGS = -avoid-version
 
 #------------------------
+# libdna-plugin
+#------------------------
+libdna_plugin_la_SOURCES = ldap/servers/plugins/dna/dna.c
+libdna_plugin_la_CPPFLAGS = $(PLUGIN_CPPFLAGS)
+libdna_plugin_la_LDFLAGS = -avoid-version
+
+#------------------------
 # libhttp-client-plugin
 #------------------------
 libhttp_client_plugin_la_SOURCES = ldap/servers/plugins/http/http_client.c \
@@ -1750,16 +1788,13 @@ libhttp_client_plugin_la_CPPFLAGS = $(PLUGIN_CPPFLAGS)
 libhttp_client_plugin_la_LDFLAGS = -avoid-version
 
 #------------------------
-# libcollation-plugin
+# liblinkedattrs-plugin
 #------------------------
-libcollation_plugin_la_SOURCES = ldap/servers/plugins/collation/collate.c \
-	ldap/servers/plugins/collation/config.c \
-	ldap/servers/plugins/collation/orfilter.c
+liblinkedattrs_plugin_la_SOURCES = ldap/servers/plugins/linkedattrs/fixup_task.c \
+	ldap/servers/plugins/linkedattrs/linked_attrs.c
 
-libcollation_plugin_la_CPPFLAGS = @icu_inc@ $(PLUGIN_CPPFLAGS)
-libcollation_plugin_la_LIBADD = $(ICU_LINK) $(LIBCSTD) $(LIBCRUN)
-libcollation_plugin_la_LDFLAGS = -avoid-version
-libcollation_plugin_la_LINK = $(CXXLINK) -avoid-version
+liblinkedattrs_plugin_la_CPPFLAGS = $(PLUGIN_CPPFLAGS)
+liblinkedattrs_plugin_la_LDFLAGS = -avoid-version
 
 #------------------------
 # libmemberof-plugin
@@ -1909,6 +1944,13 @@ libroles_plugin_la_CPPFLAGS = $(PLUGIN_CPPFLAGS)
 libroles_plugin_la_LDFLAGS = -avoid-version
 
 #------------------------
+# libschemareload-plugin
+#------------------------
+libschemareload_plugin_la_SOURCES = ldap/servers/plugins/schema_reload/schema_reload.c
+libschemareload_plugin_la_CPPFLAGS = $(PLUGIN_CPPFLAGS)
+libschemareload_plugin_la_LDFLAGS = -avoid-version
+
+#------------------------
 # libstatechange-plugin
 #------------------------
 libstatechange_plugin_la_SOURCES = ldap/servers/plugins/statechange/statechange.c
@@ -1943,27 +1985,6 @@ libviews_plugin_la_SOURCES = ldap/servers/plugins/views/views.c
 libviews_plugin_la_CPPFLAGS = $(PLUGIN_CPPFLAGS)
 libviews_plugin_la_LDFLAGS = -avoid-version
 
-#------------------------
-# libschemareload-plugin
-#------------------------
-libschemareload_plugin_la_SOURCES = ldap/servers/plugins/schema_reload/schema_reload.c
-libschemareload_plugin_la_CPPFLAGS = $(PLUGIN_CPPFLAGS)
-libschemareload_plugin_la_LDFLAGS = -avoid-version
-
-#------------------------
-# libdna-plugin
-#------------------------
-libdna_plugin_la_SOURCES = ldap/servers/plugins/dna/dna.c
-libdna_plugin_la_CPPFLAGS = $(PLUGIN_CPPFLAGS)
-libdna_plugin_la_LDFLAGS = -avoid-version
-
-#------------------------
-# libbitwise-plugin
-#------------------------
-libbitwise_plugin_la_SOURCES = ldap/servers/plugins/bitwise/bitwise.c
-libbitwise_plugin_la_CPPFLAGS = $(PLUGIN_CPPFLAGS)
-libbitwise_plugin_la_LDFLAGS = -avoid-version
-
 #////////////////////////////////////////////////////////////////
 #
 #   Programs
@@ -2814,6 +2835,20 @@ ldap/servers/plugins/http/libhttp_client_plugin_la-http_impl.lo:  \
 	ldap/servers/plugins/http/$(DEPDIR)/$(am__dirstamp)
 libhttp-client-plugin.la: $(libhttp_client_plugin_la_OBJECTS) $(libhttp_client_plugin_la_DEPENDENCIES) 
 	$(libhttp_client_plugin_la_LINK) -rpath $(serverplugindir) $(libhttp_client_plugin_la_OBJECTS) $(libhttp_client_plugin_la_LIBADD) $(LIBS)
+ldap/servers/plugins/linkedattrs/$(am__dirstamp):
+	@$(MKDIR_P) ldap/servers/plugins/linkedattrs
+	@: > ldap/servers/plugins/linkedattrs/$(am__dirstamp)
+ldap/servers/plugins/linkedattrs/$(DEPDIR)/$(am__dirstamp):
+	@$(MKDIR_P) ldap/servers/plugins/linkedattrs/$(DEPDIR)
+	@: > ldap/servers/plugins/linkedattrs/$(DEPDIR)/$(am__dirstamp)
+ldap/servers/plugins/linkedattrs/liblinkedattrs_plugin_la-fixup_task.lo:  \
+	ldap/servers/plugins/linkedattrs/$(am__dirstamp) \
+	ldap/servers/plugins/linkedattrs/$(DEPDIR)/$(am__dirstamp)
+ldap/servers/plugins/linkedattrs/liblinkedattrs_plugin_la-linked_attrs.lo:  \
+	ldap/servers/plugins/linkedattrs/$(am__dirstamp) \
+	ldap/servers/plugins/linkedattrs/$(DEPDIR)/$(am__dirstamp)
+liblinkedattrs-plugin.la: $(liblinkedattrs_plugin_la_OBJECTS) $(liblinkedattrs_plugin_la_DEPENDENCIES) 
+	$(liblinkedattrs_plugin_la_LINK) -rpath $(serverplugindir) $(liblinkedattrs_plugin_la_OBJECTS) $(liblinkedattrs_plugin_la_LIBADD) $(LIBS)
 ldap/servers/plugins/memberof/$(am__dirstamp):
 	@$(MKDIR_P) ldap/servers/plugins/memberof
 	@: > ldap/servers/plugins/memberof/$(am__dirstamp)
@@ -4180,6 +4215,10 @@ mostlyclean-compile:
 	-rm -f ldap/servers/plugins/http/libhttp_client_plugin_la-http_client.lo
 	-rm -f ldap/servers/plugins/http/libhttp_client_plugin_la-http_impl.$(OBJEXT)
 	-rm -f ldap/servers/plugins/http/libhttp_client_plugin_la-http_impl.lo
+	-rm -f ldap/servers/plugins/linkedattrs/liblinkedattrs_plugin_la-fixup_task.$(OBJEXT)
+	-rm -f ldap/servers/plugins/linkedattrs/liblinkedattrs_plugin_la-fixup_task.lo
+	-rm -f ldap/servers/plugins/linkedattrs/liblinkedattrs_plugin_la-linked_attrs.$(OBJEXT)
+	-rm -f ldap/servers/plugins/linkedattrs/liblinkedattrs_plugin_la-linked_attrs.lo
 	-rm -f ldap/servers/plugins/memberof/libmemberof_plugin_la-memberof.$(OBJEXT)
 	-rm -f ldap/servers/plugins/memberof/libmemberof_plugin_la-memberof.lo
 	-rm -f ldap/servers/plugins/memberof/libmemberof_plugin_la-memberof_config.$(OBJEXT)
@@ -4937,6 +4976,8 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@ldap/servers/plugins/dna/$(DEPDIR)/libdna_plugin_la-dna.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@ldap/servers/plugins/http/$(DEPDIR)/libhttp_client_plugin_la-http_client.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@ldap/servers/plugins/http/$(DEPDIR)/libhttp_client_plugin_la-http_impl.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@ldap/servers/plugins/linkedattrs/$(DEPDIR)/liblinkedattrs_plugin_la-fixup_task.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@ldap/servers/plugins/linkedattrs/$(DEPDIR)/liblinkedattrs_plugin_la-linked_attrs.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@ldap/servers/plugins/memberof/$(DEPDIR)/libmemberof_plugin_la-memberof.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@ldap/servers/plugins/memberof/$(DEPDIR)/libmemberof_plugin_la-memberof_config.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@ldap/servers/plugins/pam_passthru/$(DEPDIR)/libpam_passthru_plugin_la-pam_ptconfig.Plo@am__quote@
@@ -6285,6 +6326,20 @@ ldap/servers/plugins/http/libhttp_client_plugin_la-http_impl.lo: ldap/servers/pl
 @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) $(libhttp_client_plugin_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ldap/servers/plugins/http/libhttp_client_plugin_la-http_impl.lo `test -f 'ldap/servers/plugins/http/http_impl.c' || echo '$(srcdir)/'`ldap/servers/plugins/http/http_impl.c
 
+ldap/servers/plugins/linkedattrs/liblinkedattrs_plugin_la-fixup_task.lo: ldap/servers/plugins/linkedattrs/fixup_task.c
+@am__fastdepCC_TRUE@	$(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblinkedattrs_plugin_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ldap/servers/plugins/linkedattrs/liblinkedattrs_plugin_la-fixup_task.lo -MD -MP -MF ldap/servers/plugins/linkedattrs/$(DEPDIR)/liblinkedattrs_plugin_la-fixup_task.Tpo -c -o ldap/servers/plugins/linkedattrs/liblinkedattrs_plugin_la-fixup_task.lo `test -f 'ldap/servers/plugins/linkedattrs/fixup_task.c' || echo '$(srcdir)/'`ldap/servers/plugins/linkedattrs/fixup_task.c
+@am__fastdepCC_TRUE@	mv -f ldap/servers/plugins/linkedattrs/$(DEPDIR)/liblinkedattrs_plugin_la-fixup_task.Tpo ldap/servers/plugins/linkedattrs/$(DEPDIR)/liblinkedattrs_plugin_la-fixup_task.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='ldap/servers/plugins/linkedattrs/fixup_task.c' object='ldap/servers/plugins/linkedattrs/liblinkedattrs_plugin_la-fixup_task.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) $(liblinkedattrs_plugin_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ldap/servers/plugins/linkedattrs/liblinkedattrs_plugin_la-fixup_task.lo `test -f 'ldap/servers/plugins/linkedattrs/fixup_task.c' || echo '$(srcdir)/'`ldap/servers/plugins/linkedattrs/fixup_task.c
+
+ldap/servers/plugins/linkedattrs/liblinkedattrs_plugin_la-linked_attrs.lo: ldap/servers/plugins/linkedattrs/linked_attrs.c
+@am__fastdepCC_TRUE@	$(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblinkedattrs_plugin_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ldap/servers/plugins/linkedattrs/liblinkedattrs_plugin_la-linked_attrs.lo -MD -MP -MF ldap/servers/plugins/linkedattrs/$(DEPDIR)/liblinkedattrs_plugin_la-linked_attrs.Tpo -c -o ldap/servers/plugins/linkedattrs/liblinkedattrs_plugin_la-linked_attrs.lo `test -f 'ldap/servers/plugins/linkedattrs/linked_attrs.c' || echo '$(srcdir)/'`ldap/servers/plugins/linkedattrs/linked_attrs.c
+@am__fastdepCC_TRUE@	mv -f ldap/servers/plugins/linkedattrs/$(DEPDIR)/liblinkedattrs_plugin_la-linked_attrs.Tpo ldap/servers/plugins/linkedattrs/$(DEPDIR)/liblinkedattrs_plugin_la-linked_attrs.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='ldap/servers/plugins/linkedattrs/linked_attrs.c' object='ldap/servers/plugins/linkedattrs/liblinkedattrs_plugin_la-linked_attrs.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) $(liblinkedattrs_plugin_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ldap/servers/plugins/linkedattrs/liblinkedattrs_plugin_la-linked_attrs.lo `test -f 'ldap/servers/plugins/linkedattrs/linked_attrs.c' || echo '$(srcdir)/'`ldap/servers/plugins/linkedattrs/linked_attrs.c
+
 ldap/servers/plugins/memberof/libmemberof_plugin_la-memberof.lo: ldap/servers/plugins/memberof/memberof.c
 @am__fastdepCC_TRUE@	$(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmemberof_plugin_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ldap/servers/plugins/memberof/libmemberof_plugin_la-memberof.lo -MD -MP -MF ldap/servers/plugins/memberof/$(DEPDIR)/libmemberof_plugin_la-memberof.Tpo -c -o ldap/servers/plugins/memberof/libmemberof_plugin_la-memberof.lo `test -f 'ldap/servers/plugins/memberof/memberof.c' || echo '$(srcdir)/'`ldap/servers/plugins/memberof/memberof.c
 @am__fastdepCC_TRUE@	mv -f ldap/servers/plugins/memberof/$(DEPDIR)/libmemberof_plugin_la-memberof.Tpo ldap/servers/plugins/memberof/$(DEPDIR)/libmemberof_plugin_la-memberof.Plo
@@ -8962,6 +9017,7 @@ clean-libtool:
 	-rm -rf ldap/servers/plugins/distrib/.libs ldap/servers/plugins/distrib/_libs
 	-rm -rf ldap/servers/plugins/dna/.libs ldap/servers/plugins/dna/_libs
 	-rm -rf ldap/servers/plugins/http/.libs ldap/servers/plugins/http/_libs
+	-rm -rf ldap/servers/plugins/linkedattrs/.libs ldap/servers/plugins/linkedattrs/_libs
 	-rm -rf ldap/servers/plugins/memberof/.libs ldap/servers/plugins/memberof/_libs
 	-rm -rf ldap/servers/plugins/pam_passthru/.libs ldap/servers/plugins/pam_passthru/_libs
 	-rm -rf ldap/servers/plugins/passthru/.libs ldap/servers/plugins/passthru/_libs
@@ -9459,6 +9515,8 @@ distclean-generic:
 	-rm -f ldap/servers/plugins/dna/$(am__dirstamp)
 	-rm -f ldap/servers/plugins/http/$(DEPDIR)/$(am__dirstamp)
 	-rm -f ldap/servers/plugins/http/$(am__dirstamp)
+	-rm -f ldap/servers/plugins/linkedattrs/$(DEPDIR)/$(am__dirstamp)
+	-rm -f ldap/servers/plugins/linkedattrs/$(am__dirstamp)
 	-rm -f ldap/servers/plugins/memberof/$(DEPDIR)/$(am__dirstamp)
 	-rm -f ldap/servers/plugins/memberof/$(am__dirstamp)
 	-rm -f ldap/servers/plugins/pam_passthru/$(DEPDIR)/$(am__dirstamp)
@@ -9529,7 +9587,7 @@ clean-am: clean-binPROGRAMS clean-generic clean-libtool \
 
 distclean: distclean-am
 	-rm -f $(am__CONFIG_DISTCLEAN_FILES)
-	-rm -rf ldap/libraries/libavl/$(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/distrib/$(DEPDIR) ldap/servers/plugins/dna/$(DEPDIR) ldap/servers/plugins/http/$(DEPDIR) ldap/servers/plugins/memberof/$(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/shared/$(DEPDIR) ldap/servers/plugins/statechange/$(DEPDIR) ldap/servers/plugins/syntaxes/$(DEPDIR) ldap/servers/plugins/uiduniq/$(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/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/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/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/shared/$(DEPDIR) ldap/servers/plugins/statechange/$(DEPDIR) ldap/servers/plugins/syntaxes/$(DEPDIR) ldap/servers/plugins/uiduniq/$(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
@@ -9571,7 +9629,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/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/distrib/$(DEPDIR) ldap/servers/plugins/dna/$(DEPDIR) ldap/servers/plugins/http/$(DEPDIR) ldap/servers/plugins/memberof/$(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/shared/$(DEPDIR) ldap/servers/plugins/statechange/$(DEPDIR) ldap/servers/plugins/syntaxes/$(DEPDIR) ldap/servers/plugins/uiduniq/$(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/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/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/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/shared/$(DEPDIR) ldap/servers/plugins/statechange/$(DEPDIR) ldap/servers/plugins/syntaxes/$(DEPDIR) ldap/servers/plugins/uiduniq/$(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/template-fixup-linkedattrs.pl.in b/ldap/admin/src/scripts/template-fixup-linkedattrs.pl.in
new file mode 100644
index 0000000..48a8926
--- /dev/null
+++ b/ldap/admin/src/scripts/template-fixup-linkedattrs.pl.in
@@ -0,0 +1,152 @@
+#{{PERL-EXEC}}
+#
+# 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) 2001 Sun Microsystems, Inc. Used by permission.
+# Copyright (C) 2009 Red Hat, Inc.
+# All rights reserved.
+# END COPYRIGHT BLOCK
+#
+
+sub usage {
+    print(STDERR "Usage: $0 [-v] -D rootdn { -w password | -w - | -j filename } \n");
+    print(STDERR "        [-l linkDN]\n");
+    print(STDERR " Opts: -D rootdn           - Directory Manager\n");
+    print(STDERR "     : -w password         - Directory Manager's password\n");
+    print(STDERR "     : -w -                - Prompt for Directory Manager's password\n");
+    print(STDERR "     : -j filename         - Read Directory Manager's password from file\n");
+    print(STDERR "     : -l linkDN           - DN of link config entry that you want to fix\n");
+    print(STDERR "                             up the links for.  If omitted, all configured\n");
+    print(STDERR "                             linked attributes will be fixed up.\n");
+    print(STDERR "     : -v                  - verbose\n");
+}
+
+$rootdn = "";
+$passwd = "";
+$passwdfile = "";
+$linkdn_arg = "";
+$verbose = 0;
+
+$prefix = "{{DS-ROOT}}";
+
+$ENV{'PATH'} = "$prefix@ldapsdk_bindir@:$prefix/usr/bin:@ldapsdk_bindir@:/usr/bin";
+$ENV{'LD_LIBRARY_PATH'} = "$prefix@nss_libdir@:$prefix/usr/lib:@nss_libdir@:/usr/lib";
+$ENV{'SHLIB_PATH'} = "$prefix@nss_libdir@:$prefix/usr/lib:@nss_libdir@:/usr/lib";
+
+$i = 0;
+while ($i <= $#ARGV) 
+{
+    if ("$ARGV[$i]" eq "-l")
+    {
+        # link DN
+        $i++; $linkdn_arg = $ARGV[$i];
+    }
+    elsif ("$ARGV[$i]" eq "-D") 
+    {    
+        # Directory Manager
+        $i++; $rootdn = $ARGV[$i];
+    }
+    elsif ("$ARGV[$i]" eq "-w") 
+    {    
+        # Directory Manager's password
+        $i++; $passwd = $ARGV[$i];
+    } 
+    elsif ("$ARGV[$i]" eq "-j")
+    {
+         # Read Directory Manager's password from a file
+        $i++; $passwdfile = $ARGV[$i];
+    }
+    elsif ("$ARGV[$i]" eq "-v") 
+    {    
+        # verbose
+        $verbose = 1;
+    }
+    else
+    {
+        &usage; exit(1);
+    }
+    $i++;
+}
+
+if ($passwdfile ne ""){
+# Open file and get the password
+    unless (open (RPASS, $passwdfile)) {
+        die "Error, cannot open password file $passwdfile\n";
+    }
+    $passwd = <RPASS>;
+    chomp($passwd);
+    close(RPASS);
+} elsif ($passwd eq "-"){
+# Read the password from terminal
+	print "Bind Password: ";
+	# Disable console echo
+	system("stty -echo");
+	# read the answer
+	$passwd = <STDIN>;
+	# Enable console echo
+	system("stty echo");
+	print "\n";
+	chop($passwd); # trim trailing newline
+}
+
+if ( $rootdn eq "" || $passwd eq "" ) 
+{ 
+    &usage; 
+    exit(1); 
+}
+
+$vstr = "";
+if ($verbose != 0) 
+{ 
+    $vstr = "-v"; 
+}
+
+# Use a timestamp as part of the task entry name
+($s, $m, $h, $dy, $mn, $yr, $wdy, $ydy, $r) = localtime(time);
+$mn++; $yr += 1900;
+$taskname = "linked_attrs_fixup_${yr}_${mn}_${dy}_${h}_${m}_${s}";
+
+# Build the task entry to add
+$dn = "dn: cn=$taskname, cn=fixup linked attributes, cn=tasks, cn=config\n";
+$misc = "changetype: add\nobjectclass: top\nobjectclass: extensibleObject\n";
+$cn =  "cn: $taskname\n";
+if ($linkdn_arg ne "")
+{
+    $linkdn = "linkdn: $linkdn_arg\n";
+}
+
+$entry = "${dn}${misc}${cn}${basedn}${linkdn}";
+open(FOO, "| ldapmodify $vstr -h {{SERVER-NAME}} -p {{SERVER-PORT}} -D \"$rootdn\" -w \"$passwd\" -a" );
+print(FOO "$entry");
+close(FOO);
diff --git a/ldap/ldif/template-dse.ldif.in b/ldap/ldif/template-dse.ldif.in
index 82326d5..2694f0f 100644
--- a/ldap/ldif/template-dse.ldif.in
+++ b/ldap/ldif/template-dse.ldif.in
@@ -554,6 +554,18 @@ nsslapd-plugintype: preoperation
 nsslapd-pluginenabled: on
 nsslapd-plugin-depends-on-type: database
 
+dn: cn=Linked Attributes,cn=plugins,cn=config
+objectclass: top
+objectclass: nsSlapdPlugin
+objectclass: extensibleObject
+objectclass: nsContainer
+cn: Linked Attributes
+nsslapd-pluginpath: liblinkedattrs-plugin
+nsslapd-plugininitfunc: linked_attrs_init
+nsslapd-plugintype: preoperation
+nsslapd-pluginenabled: on
+nsslapd-plugin-depends-on-type: database
+
 dn: cn=Pass Through Authentication,cn=plugins,cn=config
 objectclass: top
 objectclass: nsSlapdPlugin
diff --git a/ldap/servers/plugins/linkedattrs/fixup_task.c b/ldap/servers/plugins/linkedattrs/fixup_task.c
new file mode 100644
index 0000000..50d35d0
--- /dev/null
+++ b/ldap/servers/plugins/linkedattrs/fixup_task.c
@@ -0,0 +1,401 @@
+/** 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) 2009 Red Hat, Inc.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/* fixup_task.c - linked attributes fixup task */
+
+#include "linked_attrs.h"
+
+/*
+ * Function Prototypes
+ */
+static void linked_attrs_fixup_task_destructor(Slapi_Task *task);
+static void linked_attrs_fixup_task_thread(void *arg);
+static void linked_attrs_fixup_links(struct configEntry *config);
+static int linked_attrs_remove_backlinks_callback(Slapi_Entry *e, void *callback_data);
+static int linked_attrs_add_backlinks_callback(Slapi_Entry *e, void *callback_data);
+static const char *fetch_attr(Slapi_Entry *e, const char *attrname,
+                              const char *default_val);
+
+/*
+ * Function Implementations
+ */
+int
+linked_attrs_fixup_task_add(Slapi_PBlock *pb, Slapi_Entry *e,
+                Slapi_Entry *eAfter, int *returncode,
+                char *returntext, void *arg)
+{
+	PRThread *thread = NULL;
+	int rv = SLAPI_DSE_CALLBACK_OK;
+	task_data *mytaskdata = NULL;
+	Slapi_Task *task = NULL;
+	const char *linkdn = NULL;
+
+	*returncode = LDAP_SUCCESS;
+	/* get arg(s) */
+	linkdn = fetch_attr(e, "linkdn", 0);
+
+	/* setup our task data */
+	mytaskdata = (task_data*)slapi_ch_calloc(1, sizeof(task_data));
+	if (mytaskdata == NULL) {
+		*returncode = LDAP_OPERATIONS_ERROR;
+		rv = SLAPI_DSE_CALLBACK_ERROR;
+		goto out;
+	}
+
+        if (linkdn) {
+	    mytaskdata->linkdn = slapi_dn_normalize(slapi_ch_strdup(linkdn));
+        }
+
+	/* allocate new task now */
+	task = slapi_new_task(slapi_entry_get_ndn(e));
+
+	/* register our destructor for cleaning up our private data */
+	slapi_task_set_destructor_fn(task, linked_attrs_fixup_task_destructor);
+
+	/* Stash a pointer to our data in the task */
+	slapi_task_set_data(task, mytaskdata);
+
+	/* start the sample task as a separate thread */
+	thread = PR_CreateThread(PR_USER_THREAD, linked_attrs_fixup_task_thread,
+		(void *)task, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
+		PR_UNJOINABLE_THREAD, SLAPD_DEFAULT_THREAD_STACKSIZE);
+	if (thread == NULL) {
+		slapi_log_error( SLAPI_LOG_FATAL, LINK_PLUGIN_SUBSYSTEM,
+			"unable to create task thread!\n");
+		*returncode = LDAP_OPERATIONS_ERROR;
+		rv = SLAPI_DSE_CALLBACK_ERROR;
+		slapi_task_finish(task, *returncode);
+	} else {
+		rv = SLAPI_DSE_CALLBACK_OK;
+	}
+
+out:
+	return rv;
+}
+
+static void
+linked_attrs_fixup_task_destructor(Slapi_Task *task)
+{
+	if (task) {
+		task_data *mydata = (task_data *)slapi_task_get_data(task);
+		if (mydata) {
+			slapi_ch_free_string(&mydata->linkdn);
+			/* Need to cast to avoid a compiler warning */
+			slapi_ch_free((void **)&mydata);
+		}
+	}
+}
+
+static void
+linked_attrs_fixup_task_thread(void *arg)
+{
+	int rc = 0;
+	Slapi_Task *task = (Slapi_Task *)arg;
+	task_data *td = NULL;
+        PRCList *main_config = NULL;
+        int found_config = 0;
+
+	/* Fetch our task data from the task */
+	td = (task_data *)slapi_task_get_data(task);
+
+	/* Log started message. */
+	slapi_task_begin(task, 1);
+	slapi_task_log_notice(task, "Linked attributes fixup task starting (link dn: \"%s\") ...\n",
+	                      td->linkdn ? td->linkdn : "");
+	slapi_log_error(SLAPI_LOG_FATAL, LINK_PLUGIN_SUBSYSTEM,
+	                "Syntax validate task starting (link dn: \"%s\") ...\n",
+                        td->linkdn ? td->linkdn : "");
+
+        linked_attrs_read_lock();
+        main_config = linked_attrs_get_config();
+        if (!PR_CLIST_IS_EMPTY(main_config)) {
+            struct configEntry *config_entry = NULL;
+            PRCList *list = PR_LIST_HEAD(main_config);
+
+            while (list != main_config) {
+                config_entry = (struct configEntry *) list;
+
+                /* See if this is the requested config and fix up if so. */
+                if (td->linkdn) {
+                    if  (strcasecmp(td->linkdn, config_entry->dn) == 0) {
+                        found_config = 1;
+                        slapi_task_log_notice(task, "Fixing up linked attribute pair (%s)\n",
+                               config_entry->dn);
+                        slapi_log_error(SLAPI_LOG_FATAL, LINK_PLUGIN_SUBSYSTEM,
+                               "Fixing up linked attribute pair (%s)\n",
+                               config_entry->dn);
+
+                        linked_attrs_fixup_links(config_entry);
+                        break;
+                    }
+                } else {
+                    /* No config DN was supplied, so fix up all configured links. */
+                    slapi_task_log_notice(task, "Fixing up linked attribute pair (%s)\n",
+                            config_entry->dn);
+                    slapi_log_error(SLAPI_LOG_FATAL, LINK_PLUGIN_SUBSYSTEM,
+                           "Fixing up linked attribute pair (%s)\n", config_entry->dn);
+
+                    linked_attrs_fixup_links(config_entry);
+                }
+
+                list = PR_NEXT_LINK(list);
+            }
+        }
+
+        /* Log a message if we didn't find the requested attribute pair. */
+        if (td->linkdn && !found_config) {
+            slapi_task_log_notice(task, "Requested link config DN not found (%s)\n",
+                    td->linkdn);
+            slapi_log_error(SLAPI_LOG_FATAL, LINK_PLUGIN_SUBSYSTEM,
+                    "Requested link config DN not found (%s)\n", td->linkdn);
+        }
+
+        linked_attrs_unlock();
+
+	/* Log finished message. */
+	slapi_task_log_notice(task, "Linked attributes fixup task complete.\n");
+	slapi_task_log_status(task, "Linked attributes fixup task complete.\n");
+	slapi_log_error(SLAPI_LOG_FATAL, LINK_PLUGIN_SUBSYSTEM, "Linked attributes fixup task complete.\n");
+	slapi_task_inc_progress(task);
+
+	/* this will queue the destruction of the task */
+	slapi_task_finish(task, rc);
+}
+
+static void 
+linked_attrs_fixup_links(struct configEntry *config)
+{
+    Slapi_PBlock *pb = slapi_pblock_new();
+    char *del_filter = NULL;
+    char *add_filter = NULL;
+
+    del_filter = slapi_ch_smprintf("%s=*", config->managedtype);
+    add_filter = slapi_ch_smprintf("%s=*", config->linktype);
+
+    /* Lock the attribute pair. */
+    slapi_lock_mutex(config->lock);
+
+    if (config->scope) {
+        /* Find all entries with the managed type present
+         * within the scope and remove the managed type. */
+        slapi_search_internal_set_pb(pb, config->scope, LDAP_SCOPE_SUBTREE,
+                del_filter, 0, 0, 0, 0, linked_attrs_get_plugin_id(), 0);
+
+        slapi_search_internal_callback_pb(pb, config->managedtype, 0,
+                linked_attrs_remove_backlinks_callback, 0);
+
+        /* Clean out pblock for reuse. */
+        slapi_pblock_init(pb);
+
+        /* Find all entries with the link type present within the
+         * scope and add backlinks to the entries they point to. */
+        slapi_search_internal_set_pb(pb, config->scope, LDAP_SCOPE_SUBTREE,
+                add_filter, 0, 0, 0, 0, linked_attrs_get_plugin_id(), 0);
+
+        slapi_search_internal_callback_pb(pb, config, 0,
+                linked_attrs_add_backlinks_callback, 0);
+    } else {
+        /* Loop through all non-private backend suffixes and
+         * remove the managed type from any entry that has it.
+         * We then find any entry with the linktype present and
+         * generate the proper backlinks. */
+        void *node = NULL;
+        Slapi_DN * suffix = slapi_get_first_suffix (&node, 0);
+
+        while (suffix) {
+            slapi_search_internal_set_pb(pb, slapi_sdn_get_dn(suffix),
+                                         LDAP_SCOPE_SUBTREE, del_filter,
+                                         0, 0, 0, 0,
+                                         linked_attrs_get_plugin_id(), 0);
+
+            slapi_search_internal_callback_pb(pb, config->managedtype, 0,
+                    linked_attrs_remove_backlinks_callback, 0);
+
+            /* Clean out pblock for reuse. */
+            slapi_pblock_init(pb);
+
+            slapi_search_internal_set_pb(pb, slapi_sdn_get_dn(suffix),
+                                         LDAP_SCOPE_SUBTREE, add_filter,
+                                         0, 0, 0, 0,
+                                         linked_attrs_get_plugin_id(), 0);
+
+            slapi_search_internal_callback_pb(pb, config, 0,
+                    linked_attrs_add_backlinks_callback, 0);
+
+            /* Clean out pblock for reuse. */
+            slapi_pblock_init(pb);
+
+            suffix = slapi_get_next_suffix (&node, 0);
+        }
+    }
+
+    /* Unlock the attribute pair. */
+    slapi_unlock_mutex(config->lock);
+
+    slapi_ch_free_string(&del_filter);
+    slapi_ch_free_string(&add_filter);
+    slapi_pblock_destroy(pb);
+}
+
+static int
+linked_attrs_remove_backlinks_callback(Slapi_Entry *e, void *callback_data)
+{
+    int rc = 0;
+    char *dn = slapi_entry_get_dn(e);
+    char *type = (char *)callback_data;
+    Slapi_PBlock *pb = slapi_pblock_new();
+    char *val[1];
+    LDAPMod mod;
+    LDAPMod *mods[2];
+
+    /* Remove all values of the passed in type. */
+    val[0] = 0;
+
+    mod.mod_op = LDAP_MOD_DELETE;
+    mod.mod_type = type;
+    mod.mod_values = val;
+
+    mods[0] = &mod;
+    mods[1] = 0;
+
+    slapi_log_error(SLAPI_LOG_PLUGIN, LINK_PLUGIN_SUBSYSTEM,
+                    "Removing backpointer attribute (%s) from entry (%s)\n",
+                    type, dn);
+
+    /* Perform the operation. */
+    slapi_modify_internal_set_pb(pb, dn, mods, 0, 0,
+                                 linked_attrs_get_plugin_id(), 0);
+    slapi_modify_internal_pb(pb);
+
+    slapi_pblock_destroy(pb);
+
+    return rc;
+}
+
+static int
+linked_attrs_add_backlinks_callback(Slapi_Entry *e, void *callback_data)
+{
+    int rc = 0;
+    char *linkdn = slapi_entry_get_dn(e);
+    struct configEntry *config = (struct configEntry *)callback_data;
+    Slapi_PBlock *pb = slapi_pblock_new();
+    int i = 0;
+    char **targets = NULL;
+    char *val[2];
+    LDAPMod mod;
+    LDAPMod *mods[2];
+
+    /* Setup the modify operation.  Only the target will
+     * change, so we only need to do this once. */
+    val[0] = linkdn;
+    val[1] = 0;
+
+    mod.mod_op = LDAP_MOD_ADD;
+    mod.mod_type = config->managedtype;
+    mod.mod_values = val;
+
+    mods[0] = &mod;
+    mods[1] = 0;
+
+    targets = slapi_entry_attr_get_charray(e, config->linktype);
+    for (i = 0; targets && targets[i]; ++i) {
+        char *targetdn = (char *)targets[i];
+        int perform_update = 0;
+
+        if (config->scope) {
+            /* Check if the target is within the scope. */
+            perform_update = slapi_dn_issuffix(targetdn, config->scope);
+        } else {
+            /* Find out the root suffix that the linkdn is in
+             * and see if the target is in the same backend. */
+            Slapi_Backend *be = NULL;
+            Slapi_DN *linksdn = slapi_sdn_new_dn_byref(linkdn);
+            Slapi_DN *targetsdn = slapi_sdn_new_dn_byref(targetdn);
+
+            if ((be = slapi_be_select(linksdn))) {
+                perform_update = slapi_sdn_issuffix(targetsdn, slapi_be_getsuffix(be, 0));
+            }
+
+            slapi_sdn_free(&linksdn);
+            slapi_sdn_free(&targetsdn);
+        }
+
+        if (perform_update) {
+            slapi_log_error(SLAPI_LOG_PLUGIN, LINK_PLUGIN_SUBSYSTEM,
+                            "Adding backpointer (%s) in entry (%s)\n",
+                            linkdn, targetdn);
+
+            /* Perform the modify operation. */
+            slapi_modify_internal_set_pb(pb, targetdn, mods, 0, 0,
+                                         linked_attrs_get_plugin_id(), 0);
+            slapi_modify_internal_pb(pb);
+
+            /* Initialize the pblock so we can reuse it. */
+            slapi_pblock_init(pb);
+        }
+    }
+
+    slapi_ch_array_free(targets);
+    slapi_pblock_destroy(pb);
+
+    return rc;
+}
+
+/* extract a single value from the entry (as a string) -- if it's not in the
+ * entry, the default will be returned (which can be NULL).
+ * you do not need to free anything returned by this.
+ */
+static const char *
+fetch_attr(Slapi_Entry *e, const char *attrname,
+           const char *default_val)
+{
+Slapi_Attr *attr;
+Slapi_Value *val = NULL;
+
+	if (slapi_entry_attr_find(e, attrname, &attr) != 0) {
+		return default_val;
+	}
+
+	slapi_attr_first_value(attr, &val);
+
+	return slapi_value_get_string(val);
+}
+
diff --git a/ldap/servers/plugins/linkedattrs/linked_attrs.c b/ldap/servers/plugins/linkedattrs/linked_attrs.c
new file mode 100644
index 0000000..5126e35
--- /dev/null
+++ b/ldap/servers/plugins/linkedattrs/linked_attrs.c
@@ -0,0 +1,2024 @@
+/** 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) 2009 Red Hat, Inc.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
+/*
+ * Linked attributes plug-in
+ */
+#include "linked_attrs.h"
+
+
+/*
+ * Plug-in globals
+ */
+static PRCList *g_link_config = NULL;
+static PRCList *g_managed_config_index = NULL;
+static PRRWLock *g_config_lock;
+
+static void *_PluginID = NULL;
+static char *_PluginDN = NULL;
+static int g_plugin_started = 0;
+
+static Slapi_PluginDesc pdesc = { LINK_FEATURE_DESC,
+                                  PLUGIN_MAGIC_VENDOR_STR,
+                                  PRODUCTTEXT,
+                                  LINK_PLUGIN_DESC };
+
+/*
+ * Plug-in management functions
+ */
+int linked_attrs_init(Slapi_PBlock * pb);
+static int linked_attrs_start(Slapi_PBlock * pb);
+static int linked_attrs_close(Slapi_PBlock * pb);
+static int linked_attrs_postop_init(Slapi_PBlock * pb);
+static int linked_attrs_internal_postop_init(Slapi_PBlock *pb);
+
+/*
+ * Operation callbacks (where the real work is done)
+ */
+static int linked_attrs_mod_post_op(Slapi_PBlock *pb);
+static int linked_attrs_add_post_op(Slapi_PBlock *pb);
+static int linked_attrs_del_post_op(Slapi_PBlock *pb);
+static int linked_attrs_modrdn_post_op(Slapi_PBlock *pb);
+static int linked_attrs_pre_op(Slapi_PBlock *pb, int modop);
+static int linked_attrs_mod_pre_op(Slapi_PBlock *pb);
+static int linked_attrs_add_pre_op(Slapi_PBlock *pb);
+
+/*
+ * Config cache management functions
+ */
+static int linked_attrs_load_config();
+static void linked_attrs_delete_config();
+static int linked_attrs_parse_config_entry(Slapi_Entry * e, int apply);
+static void linked_attrs_insert_config_index(struct configEntry *entry);
+static void linked_attrs_free_config_entry(struct configEntry ** entry);
+
+/*
+ * helpers
+ */
+static char *linked_attrs_get_dn(Slapi_PBlock * pb);
+static int linked_attrs_dn_is_config(char *dn);
+static void linked_attrs_find_config(const char *dn, const char *type,
+    struct configEntry **config);
+static void linked_attrs_find_config_reverse(const char *dn,
+    const char *type, struct configEntry **config);
+static int linked_attrs_config_index_has_type(char *type);
+static int linked_attrs_config_exists(struct configEntry *entry);
+static int linked_attrs_config_exists_reverse(struct configEntry *entry);
+static int linked_attrs_oktodo(Slapi_PBlock *pb);
+void linked_attrs_load_array(Slapi_Value **array, Slapi_Attr *attr);
+int linked_attrs_compare(const void *a, const void *b);
+static void linked_attrs_add_backpointers(char *linkdn, struct configEntry *config,
+    Slapi_Mod *smod);
+static void linked_attrs_del_backpointers(Slapi_PBlock *pb, char *linkdn,
+    struct configEntry *config, Slapi_Mod *smod);
+static void linked_attrs_replace_backpointers(Slapi_PBlock *pb, char *linkdn,
+    struct configEntry *config, Slapi_Mod *smod);
+static void linked_attrs_mod_backpointers(char *linkdn, char *type, char *scope,
+    int modop, Slapi_ValueSet *targetvals);
+
+/*
+ * Config cache locking functions
+ */
+void
+linked_attrs_read_lock()
+{
+    PR_RWLock_Rlock(g_config_lock);
+}
+
+void
+linked_attrs_write_lock()
+{
+    PR_RWLock_Wlock(g_config_lock);
+}
+
+void
+linked_attrs_unlock()
+{
+    PR_RWLock_Unlock(g_config_lock);
+}
+
+
+/*
+ * Plugin identity functions
+ */
+void
+linked_attrs_set_plugin_id(void *pluginID)
+{
+    _PluginID = pluginID;
+}
+
+void *
+linked_attrs_get_plugin_id()
+{
+    return _PluginID;
+}
+
+void
+linked_attrs_set_plugin_dn(char *pluginDN)
+{
+    _PluginDN = pluginDN;
+}
+
+char *
+linked_attrs_get_plugin_dn()
+{
+    return _PluginDN;
+}
+
+
+/*
+ * Plug-in initialization functions
+ */
+int
+linked_attrs_init(Slapi_PBlock *pb)
+{
+    int status = 0;
+    char *plugin_identity = NULL;
+
+    slapi_log_error(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
+                    "--> linked_attrs_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);
+    linked_attrs_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 *) linked_attrs_start) != 0 ||
+        slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN,
+                         (void *) linked_attrs_close) != 0 ||
+        slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION,
+                         (void *) &pdesc) != 0 ||
+        slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODIFY_FN,
+                         (void *) linked_attrs_mod_pre_op) != 0 ||
+        slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_ADD_FN,
+                         (void *) linked_attrs_add_pre_op) != 0 ||
+        slapi_register_plugin("internalpostoperation",  /* op type */
+                              1,        /* Enabled */
+                              "linked_attrs_init",   /* this function desc */
+                              linked_attrs_internal_postop_init,  /* init func */
+                              LINK_INT_POSTOP_DESC,      /* plugin desc */
+                              NULL,     /* ? */
+                              plugin_identity   /* access control */
+        ) ||
+        slapi_register_plugin("postoperation",  /* op type */
+                              1,        /* Enabled */
+                              "linked_attrs_init",   /* this function desc */
+                              linked_attrs_postop_init,  /* init func for post op */
+                              LINK_POSTOP_DESC,      /* plugin desc */
+                              NULL,     /* ? */
+                              plugin_identity   /* access control */
+        )
+        ) {
+        slapi_log_error(SLAPI_LOG_FATAL, LINK_PLUGIN_SUBSYSTEM,
+                        "linked_attrs_init: failed to register plugin\n");
+        status = -1;
+    }
+
+    slapi_log_error(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
+                    "<-- linked_attrs_init\n");
+    return status;
+}
+
+static int
+linked_attrs_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 *) linked_attrs_add_post_op) != 0 ||
+        slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_POST_DELETE_FN,
+                         (void *) linked_attrs_del_post_op) != 0 ||
+        slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_POST_MODIFY_FN,
+                         (void *) linked_attrs_mod_post_op) != 0 ||
+        slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_POST_MODRDN_FN,
+                         (void *) linked_attrs_modrdn_post_op) != 0) {
+        slapi_log_error(SLAPI_LOG_FATAL, LINK_PLUGIN_SUBSYSTEM,
+                        "linked_attrs_internal_postop_init: failed to register plugin\n");
+        status = -1;
+    }
+ 
+    return status;
+}
+
+static int
+linked_attrs_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 *) linked_attrs_add_post_op) != 0 ||
+        slapi_pblock_set(pb, SLAPI_PLUGIN_POST_DELETE_FN,
+                         (void *) linked_attrs_del_post_op) != 0 ||
+        slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODIFY_FN,
+                         (void *) linked_attrs_mod_post_op) != 0 ||
+        slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODRDN_FN,
+                         (void *) linked_attrs_modrdn_post_op) != 0) {
+        slapi_log_error(SLAPI_LOG_FATAL, LINK_PLUGIN_SUBSYSTEM,
+                        "linked_attrs_postop_init: failed to register plugin\n");
+        status = -1;
+    }
+
+    return status;
+}
+
+
+/*
+ * linked_attrs_start()
+ *
+ * Creates config lock and loads config cache.
+ */
+static int
+linked_attrs_start(Slapi_PBlock * pb)
+{
+    char *plugindn = NULL;
+
+    slapi_log_error(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
+                    "--> linked_attrs_start\n");
+
+    /* Check if we're already started */
+    if (g_plugin_started) {
+        goto done;
+    }
+
+    g_config_lock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "linkedattrs");
+
+    if (!g_config_lock) {
+        slapi_log_error(SLAPI_LOG_FATAL, LINK_PLUGIN_SUBSYSTEM,
+                        "linked_attrs_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, LINK_PLUGIN_SUBSYSTEM,
+                        "linked_attrs_start: unable to retrieve plugin dn\n");
+        return -1;
+    }
+
+    linked_attrs_set_plugin_dn(plugindn);
+
+    /*
+     * Load the config cache
+     */
+    g_link_config = (PRCList *)slapi_ch_calloc(1, sizeof(struct configEntry));
+    PR_INIT_CLIST(g_link_config);
+    g_managed_config_index = (PRCList *)slapi_ch_calloc(1, sizeof(struct configIndex));
+    PR_INIT_CLIST(g_managed_config_index);
+
+    if (linked_attrs_load_config() != 0) {
+        slapi_log_error(SLAPI_LOG_FATAL, LINK_PLUGIN_SUBSYSTEM,
+                        "linked_attrs_start: unable to load plug-in configuration\n");
+        return -1;
+    }
+
+    /*
+     * Register our task callback
+     */
+    slapi_task_register_handler("fixup linked attributes", linked_attrs_fixup_task_add);
+
+    g_plugin_started = 1;
+    slapi_log_error(SLAPI_LOG_PLUGIN, LINK_PLUGIN_SUBSYSTEM,
+                    "linked attributes plug-in: ready for service\n");
+    slapi_log_error(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
+                    "<-- linked_attrs_start\n");
+
+done:
+    return 0;
+}
+
+/*
+ * linked_attrs_close()
+ *
+ * Cleans up the config cache.
+ */
+static int
+linked_attrs_close(Slapi_PBlock * pb)
+{
+    slapi_log_error(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
+                    "--> linked_attrs_close\n");
+
+    linked_attrs_delete_config();
+
+    slapi_ch_free((void **)&g_link_config);
+    slapi_ch_free((void **)&g_managed_config_index);
+
+    slapi_log_error(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
+                    "<-- linked_attrs_close\n");
+
+    return 0;
+}
+
+PRCList *
+linked_attrs_get_config()
+{
+    return g_link_config;
+}
+
+/*
+ * config looks like this
+ * - cn=myplugin
+ * --- cn=manager link
+ * --- cn=owner link
+ * --- cn=etc
+ */
+static int
+linked_attrs_load_config()
+{
+    int status = 0;
+    int result;
+    int i;
+    Slapi_PBlock *search_pb;
+    Slapi_Entry **entries = NULL;
+
+    slapi_log_error(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
+                    "--> linked_attrs_load_config\n");
+
+    /* Clear out any old config. */
+    linked_attrs_write_lock();
+    linked_attrs_delete_config();
+
+    /* Find the config entries beneath our plugin entry. */
+    search_pb = slapi_pblock_new();
+    slapi_search_internal_set_pb(search_pb, linked_attrs_get_plugin_dn(),
+                                 LDAP_SCOPE_SUBTREE, "objectclass=*",
+                                 NULL, 0, NULL, NULL, linked_attrs_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);
+    if (NULL == entries || NULL == entries[0]) {
+        /* If there are no config entries, we're done. */
+        goto cleanup;
+    }
+
+    /* Loop through all of the entries we found and parse them. */
+    for (i = 0; (entries[i] != NULL); 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. */
+        linked_attrs_parse_config_entry(entries[i], 1);
+    }
+
+  cleanup:
+    slapi_free_search_results_internal(search_pb);
+    slapi_pblock_destroy(search_pb);
+    linked_attrs_unlock();
+    slapi_log_error(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
+                    "<-- linked_attrs_load_config\n");
+
+    return status;
+}
+
+/*
+ * linked_attrs_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
+linked_attrs_parse_config_entry(Slapi_Entry * e, int apply)
+{
+    char *value;
+    struct configEntry *entry = NULL;
+    struct configEntry *config_entry;
+    PRCList *list;
+    int entry_added = 0;
+    int ret = 0;
+
+    slapi_log_error(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
+                    "--> linked_attrs_parse_config_entry\n");
+
+    /* If this is the main plug-in
+     * config entry, just bail. */
+    if (strcasecmp(linked_attrs_get_plugin_dn(), slapi_entry_get_ndn(e)) == 0) {
+        ret = -1;
+        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, LINK_PLUGIN_SUBSYSTEM,
+                        "linked_attrs_parse_config_entry: Error "
+                        "reading dn from config entry\n");
+        ret = -1;
+        goto bail;
+    }
+
+    slapi_log_error(SLAPI_LOG_CONFIG, LINK_PLUGIN_SUBSYSTEM,
+                    "----------> dn [%s]\n", entry->dn);
+
+    value = slapi_entry_attr_get_charptr(e, LINK_LINK_TYPE);
+    if (value) {
+        int not_dn_syntax = 0;
+        char *syntaxoid = NULL;
+        Slapi_Attr *attr = slapi_attr_new();
+
+        /* Set this first so we free it if we encounter an error */
+        entry->linktype = value;
+
+        /* Gather some information about this attribute. */
+        slapi_attr_init(attr, value);
+        slapi_attr_get_syntax_oid_copy(attr, &syntaxoid );
+        not_dn_syntax = strcmp(syntaxoid, DN_SYNTAX_OID);
+        slapi_ch_free_string(&syntaxoid);
+        slapi_attr_free(&attr);
+
+        /* Check if the link type's syntax is Distinguished Name.
+         * We only treat this as a warning. */
+        if (not_dn_syntax) {
+            slapi_log_error(SLAPI_LOG_FATAL, LINK_PLUGIN_SUBSYSTEM,
+                            "linked_attrs_parse_config_entry: The %s config "
+                            "setting must be set to an attribute with the "
+                            "Distinguished Name syntax for linked attribute "
+                            "pair \"%s\".\n", LINK_LINK_TYPE, entry->dn);
+        }
+    } else {
+        slapi_log_error(SLAPI_LOG_FATAL, LINK_PLUGIN_SUBSYSTEM,
+                        "linked_attrs_parse_config_entry: The %s config "
+                        "setting is required for linked attribute pair \"%s\".\n",
+                        LINK_LINK_TYPE, entry->dn);
+        ret = -1;
+        goto bail;
+    }
+
+    slapi_log_error(SLAPI_LOG_CONFIG, LINK_PLUGIN_SUBSYSTEM,
+                    "----------> %s [%s]\n", LINK_LINK_TYPE, entry->linktype);
+
+    value = slapi_entry_attr_get_charptr(e, LINK_MANAGED_TYPE);
+    if (value) {
+        int single_valued = 0;
+        int not_dn_syntax = 0;
+        char *syntaxoid = NULL;
+        Slapi_Attr *attr = slapi_attr_new();
+
+	/* Set this first so we free it if we encounter an error */
+        entry->managedtype = value;
+
+        /* Gather some information about this attribute. */
+        slapi_attr_init(attr, value);
+        slapi_attr_get_syntax_oid_copy(attr, &syntaxoid );
+        not_dn_syntax = strcmp(syntaxoid, DN_SYNTAX_OID);
+        single_valued = slapi_attr_flag_is_set(attr, SLAPI_ATTR_FLAG_SINGLE);
+        slapi_ch_free_string(&syntaxoid);
+        slapi_attr_free(&attr);
+
+        /* Ensure that the managed type is a multi-valued attribute. */
+        if (single_valued) {
+            slapi_log_error(SLAPI_LOG_FATAL, LINK_PLUGIN_SUBSYSTEM,
+                            "linked_attrs_parse_config_entry: The %s config "
+                            "setting must be set to a multi-valued attribute "
+                            "for linked attribute pair \"%s\".\n",
+                            LINK_MANAGED_TYPE, entry->dn);
+            ret = -1;
+            goto bail;
+        /* Check if the link type's syntax is Distinguished Name.
+         * We only treat this as a warning. */
+        } else if (not_dn_syntax) {
+            slapi_log_error(SLAPI_LOG_FATAL, LINK_PLUGIN_SUBSYSTEM,
+                            "linked_attrs_parse_config_entry: The %s config "
+                            "setting must be set to an attribute with the "
+                            "Distinguished Name syntax for linked attribute "
+                            "pair \"%s\".\n", LINK_MANAGED_TYPE, entry->dn);
+        }
+    } else {
+        slapi_log_error(SLAPI_LOG_FATAL, LINK_PLUGIN_SUBSYSTEM,
+                        "linked_attrs_parse_config_entry: The %s config "
+                        "setting is required for linked attribute pair \"%s\".\n",
+                        LINK_MANAGED_TYPE, entry->dn);
+        ret = -1;
+        goto bail;
+    }
+
+    slapi_log_error(SLAPI_LOG_CONFIG, LINK_PLUGIN_SUBSYSTEM,
+                    "----------> %s [%s]\n", LINK_MANAGED_TYPE,
+                    entry->managedtype);
+
+    /* A scope is not required.  No scope means it
+     * applies to any part of the DIT. */
+    value = slapi_entry_attr_get_charptr(e, LINK_SCOPE);
+    if (value) {
+        entry->scope = value;
+    }
+
+    slapi_log_error(SLAPI_LOG_CONFIG, LINK_PLUGIN_SUBSYSTEM,
+                    "----------> %s [%s]\n", LINK_SCOPE,
+                    entry->scope ? entry->scope : "NULL");
+
+    /* Check if config already exists for
+     * the link type at the same scope. */
+    if (linked_attrs_config_exists(entry)) {
+        slapi_log_error(SLAPI_LOG_FATAL, LINK_PLUGIN_SUBSYSTEM,
+                        "linked_attrs_parse_config_entry: A config "
+                        "entry for the link attribute %s already "
+                        "exists at a scope of \"%s\".\n", entry->linktype,
+                        entry->scope);
+        ret = -1;
+        goto bail;
+    }
+
+    /* Check if config already exists for
+     * the managed type at the same scope. */
+    if (linked_attrs_config_exists_reverse(entry)) {
+        slapi_log_error(SLAPI_LOG_FATAL, LINK_PLUGIN_SUBSYSTEM,
+                        "linked_attrs_parse_config_entry: A config "
+                        "entry for the managed attribute %s already "
+                        "exists at a scope of \"%s\".\n", entry->managedtype,
+                        entry->scope);
+        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;
+    }
+
+    /* Create a lock for this attribute pair. */
+    entry->lock = slapi_new_mutex();
+    if (!entry->lock) {
+        slapi_log_error(SLAPI_LOG_FATAL, LINK_PLUGIN_SUBSYSTEM,
+                        "linked_attrs_parse_config_entry: Unable to create "
+                        "lock for linked attribute pair \"%s\".\n", entry->dn);
+        ret = -1;
+        goto bail;
+    }
+
+    /* Add the entry to the list.  We group by link type. We
+     * also maintain a reverse list grouped by managed type. */
+    if (!PR_CLIST_IS_EMPTY(g_link_config)) {
+        list = PR_LIST_HEAD(g_link_config);
+        while (list != g_link_config) {
+            config_entry = (struct configEntry *) list;
+
+            /* See if the types match.  We want to group
+             * entries for the same link type together. */
+            if (slapi_attr_type_cmp(config_entry->linktype, entry->linktype, 1) == 0) {
+                PR_INSERT_BEFORE(&(entry->list), list);
+                slapi_log_error(SLAPI_LOG_CONFIG, LINK_PLUGIN_SUBSYSTEM,
+                                "store [%s] before [%s] \n", entry->dn,
+                                config_entry->dn);
+
+                /* add to managed type index */
+                linked_attrs_insert_config_index(entry);
+
+                entry_added = 1;
+                break;
+            }
+
+            list = PR_NEXT_LINK(list);
+
+            if (g_link_config == list) {
+                /* add to tail */
+                PR_INSERT_BEFORE(&(entry->list), list);
+                slapi_log_error(SLAPI_LOG_CONFIG, LINK_PLUGIN_SUBSYSTEM,
+                                "store [%s] at tail\n", entry->dn);
+
+                /* add to managed type index */
+                linked_attrs_insert_config_index(entry);
+
+                entry_added = 1;
+                break;
+            }
+        }
+    } else {
+        /* first entry */
+        PR_INSERT_LINK(&(entry->list), g_link_config);
+        slapi_log_error(SLAPI_LOG_CONFIG, LINK_PLUGIN_SUBSYSTEM,
+                        "store [%s] at head \n", entry->dn);
+
+        /* add to managed type index */
+        linked_attrs_insert_config_index(entry);
+
+        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, LINK_PLUGIN_SUBSYSTEM,
+                            "linked_attrs_parse_config_entry: Invalid config entry "
+                            "[%s] skipped\n", entry->dn);
+        }
+        linked_attrs_free_config_entry(&entry);
+    } else {
+        ret = 0;
+    }
+
+    slapi_log_error(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
+                    "<-- linked_attrs_parse_config_entry\n");
+
+    return ret;
+}
+
+/*
+ * linked_attrs_insert_config_index()
+ *
+ * Adds an entry to the ordered config index.  We maintain
+ * an list of pointers to the cached config entries that is
+ * grouped by managed type. We use this index to find the
+ * appropriate config entry when given a backpointer.  This
+ * is useful for the case where an entry with backpointers
+ * is renamed and we need to updated the forward link.
+ */
+static void 
+linked_attrs_insert_config_index(struct configEntry *entry)
+{
+    struct configEntry *config_entry = NULL;
+    struct configIndex *index_entry = NULL;
+    PRCList *list = PR_LIST_HEAD(g_managed_config_index);
+
+    index_entry = (struct configIndex *)slapi_ch_calloc(1, sizeof(struct configIndex));
+    index_entry->config = entry;
+
+    if (!PR_CLIST_IS_EMPTY(g_managed_config_index)) {
+        while (list != g_managed_config_index) {
+            config_entry = ((struct configIndex *)list)->config;
+    
+            /* See if the types match. */
+            if (slapi_attr_type_cmp(config_entry->managedtype, entry->managedtype, 1) == 0) {
+                PR_INSERT_BEFORE(&(index_entry->list), list);
+                slapi_log_error(SLAPI_LOG_CONFIG, LINK_PLUGIN_SUBSYSTEM,
+                                "store [%s] before [%s] \n", entry->dn,
+                                config_entry->dn);
+                break;
+            }
+    
+            list = PR_NEXT_LINK(list);
+
+            if (g_managed_config_index == list) {
+                /* add to tail */
+                PR_INSERT_BEFORE(&(index_entry->list), list);
+                slapi_log_error(SLAPI_LOG_CONFIG, LINK_PLUGIN_SUBSYSTEM,
+                                "store [%s] at tail\n", entry->dn);
+                break;
+            }
+        }
+    } else {
+        /* first entry */
+        slapi_log_error(SLAPI_LOG_CONFIG, LINK_PLUGIN_SUBSYSTEM,
+                        "store [%s] at head \n", entry->dn);
+        PR_INSERT_LINK(&(index_entry->list), g_managed_config_index);
+    }
+}
+
+static void
+linked_attrs_free_config_entry(struct configEntry ** entry)
+{
+    struct configEntry *e = *entry;
+
+    if (e == NULL)
+        return;
+
+    if (e->dn) {
+        slapi_log_error(SLAPI_LOG_CONFIG, LINK_PLUGIN_SUBSYSTEM,
+                        "freeing config entry [%s]\n", e->dn);
+        slapi_ch_free_string(&e->dn);
+    }
+
+    if (e->linktype)
+        slapi_ch_free_string(&e->linktype);
+
+    if (e->managedtype)
+        slapi_ch_free_string(&e->managedtype);
+
+    if (e->scope)
+        slapi_ch_free_string(&e->scope);
+
+    if (e->lock)
+        slapi_destroy_mutex(e->lock);
+
+    slapi_ch_free((void **) entry);
+}
+
+static void
+linked_attrs_delete_configEntry(PRCList *entry)
+{
+    PR_REMOVE_LINK(entry);
+    linked_attrs_free_config_entry((struct configEntry **) &entry);
+}
+
+static void
+linked_attrs_delete_config()
+{
+    PRCList *list;
+
+    /* Delete the config cache. */
+    while (!PR_CLIST_IS_EMPTY(g_link_config)) {
+        list = PR_LIST_HEAD(g_link_config);
+        linked_attrs_delete_configEntry(list);
+    }
+
+    /* Delete the reverse index. */
+    while (!PR_CLIST_IS_EMPTY(g_managed_config_index)) {
+        list = PR_LIST_HEAD(g_managed_config_index);
+        PR_REMOVE_LINK(list);
+        slapi_ch_free((void **)&list);
+    }
+
+    return;
+}
+
+
+/*
+ * Helper functions
+ */
+static char *
+linked_attrs_get_dn(Slapi_PBlock * pb)
+{
+    char *dn = 0;
+    slapi_log_error(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
+                    "--> linked_attrs_get_dn\n");
+
+    if (slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn)) {
+        slapi_log_error(SLAPI_LOG_FATAL, LINK_PLUGIN_SUBSYSTEM,
+                        "linked_attrs_get_dn: failed to get dn of changed entry");
+        goto bail;
+    }
+
+  bail:
+    slapi_log_error(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
+                    "<-- linked_attrs_get_dn\n");
+
+    return dn;
+}
+
+/*
+ * linked_attrs_dn_is_config()
+ *
+ * Checks if dn is a linked attribute config entry.
+ */
+static int
+linked_attrs_dn_is_config(char *dn)
+{
+    int ret = 0;
+
+    slapi_log_error(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
+                    "--> linked_attrs_dn_is_config\n");
+
+    /* Return 1 if the passed in dn is a child of the main
+     * plugin config entry. */
+    if (slapi_dn_issuffix(dn, linked_attrs_get_plugin_dn()) &&
+        strcasecmp(dn, linked_attrs_get_plugin_dn())) {
+        ret = 1;
+    }
+
+    slapi_log_error(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
+                    "<-- linked_attrs_dn_is_config\n");
+
+    return ret;
+}
+
+/*
+ * linked_attrs_find_config()
+ *
+ * Finds the appropriate config entry for a given dn and
+ * link type.  A read lock must be held on the config
+ * before calling this function.  The configEntry that is
+ * returned is a pointer to the actual config entry in
+ * the config cache.  It should not be modified in any
+ * way.  The read lock should not be released until you
+ * are finished with the config entry that is returned.
+
+ * Returns NULL if no applicable config entry is found.
+ */
+static void
+linked_attrs_find_config(const char *dn,
+    const char *type, struct configEntry **config)
+{
+    int found_type = 0;
+    PRCList *list = NULL;
+
+    *config = NULL;
+
+    if (!PR_CLIST_IS_EMPTY(g_link_config)) {
+        list = PR_LIST_HEAD(g_link_config);
+        while (list != g_link_config) {
+            if (slapi_attr_type_cmp(((struct configEntry *)list)->linktype,
+                type, 1) == 0) {
+                /* Set a flag indicating that we found a config entry
+                 * for this type. We use this flag so we can stop
+                 * processing early if we don't find a matching scope. */
+                found_type = 1;
+
+                /* Check if the dn is in the scope of this config
+                 * entry.  If the config entry doesn't have a scope
+                 * (global), consider it a match.  If we have a match,
+                 * we can stop processing the config. */
+                if ((((struct configEntry *)list)->scope == NULL) ||
+                    (slapi_dn_issuffix(dn, ((struct configEntry *)list)->scope))) {
+                    *config = (struct configEntry *)list;
+                    break;
+                }
+            } else {
+                /* If flag is set, we're done.  We have configured links
+                 * for this type, but none of the scopes match. */
+                if (found_type) {
+                    break;
+                }
+            }
+
+            list = PR_NEXT_LINK(list);
+        }
+    }
+}
+
+/*
+ * linked_attrs_find_config_reverse()
+ *
+ * Finds the appropriate config entry for a given dn and
+ * managed type.  A read lock must be held on the config 
+ * before calling this function.  The configEntry that is
+ * returned is a pointer to the actual config entry in
+ * the config cache.  It should not be modified in any
+ * way.  The read lock should not be released until you
+ * are finished with the config entry that is returned.
+
+ * Returns NULL if no applicable config entry is found.
+ */ 
+static void
+linked_attrs_find_config_reverse(const char *dn,
+    const char *type, struct configEntry **config)
+{   
+    int found_type = 0;
+    PRCList *list = NULL;
+
+    *config = NULL;
+
+    if (!PR_CLIST_IS_EMPTY(g_managed_config_index)) {
+        list = PR_LIST_HEAD(g_managed_config_index);
+        while (list != g_managed_config_index) {
+            if (slapi_attr_type_cmp(((struct configIndex *)list)->config->managedtype,
+                                    type, 1) == 0) {
+                /* Set a flag indicating that we found a config entry
+                 * for this type. We use this flag so we can stop
+                 * processing early if we don't find a matching scope. */
+                found_type = 1;
+
+                /* Check if the dn is in the scope of this config
+                 * entry.  If the config entry doesn't have a scope
+                 * (global), consider it a match.  If we have a match,
+                 * we can stop processing the config. */
+                if ((((struct configIndex *)list)->config->scope == NULL) ||
+                    (slapi_dn_issuffix(dn, ((struct configIndex *)list)->config->scope))) {
+                    *config = ((struct configIndex *)list)->config;
+                    break;
+                }
+            } else {
+                /* If flag is set, we're done.  We have configured links
+                 * for this type, but none of the scopes match. */
+                if (found_type) {
+                    break;
+                }
+            }
+
+            list = PR_NEXT_LINK(list);
+        }
+    }
+}
+
+/*
+ * linked_attrs_config_index_has_type()
+ *
+ * Returns 1 if a config entry exists with the passed
+ * in managed type.
+ *
+ * A read lock on the config must be held before calling
+ * this function.
+ */
+static int
+linked_attrs_config_index_has_type(char *type)
+{
+    int rc = 0;
+    PRCList *list = NULL;
+
+    if (!PR_CLIST_IS_EMPTY(g_managed_config_index)) {
+        list = PR_LIST_HEAD(g_managed_config_index);
+        while (list != g_managed_config_index) {
+            if (slapi_attr_type_cmp(((struct configIndex *)list)->config->managedtype,
+                                    type, 1) == 0) {
+                rc = 1;
+                break;
+            }
+
+            list = PR_NEXT_LINK(list);
+        }
+    }
+
+    return rc;
+}
+
+/*
+ * linked_attrs_config_exists()
+ *
+ * Returns 1 if a config entry exists in the cache
+ * already for the given link type at the given scope.
+ * This will detect if the cached config entry is really
+ * the same one as the passed in entry by comparing the
+ * dn of the config entry.  We will still return 0 in
+ * this case as it's one and the same config entry. We
+ * really want to use this to prevent multiple config
+ * entries for the same link type at the same scope.
+ *
+ * A read lock on the config must be held before calling
+ * this function.
+ */
+static int
+linked_attrs_config_exists(struct configEntry *entry)
+{
+    int rc = 0;
+    int found_type = 0;
+    PRCList *list = NULL;
+
+    if (!PR_CLIST_IS_EMPTY(g_link_config)) {
+        list = PR_LIST_HEAD(g_link_config);
+        while (list != g_link_config) {
+            if (slapi_attr_type_cmp(((struct configEntry *)list)->linktype,
+                                    entry->linktype, 1) == 0) {
+                found_type = 1;
+                /* We don't allow nested config for the same type.  We
+                 * need to check for nesting in both directions here. 
+                 * If no scope is set, we consider the entry global. */
+                if ((((struct configEntry *)list)->scope == NULL) ||
+                    slapi_dn_issuffix(entry->scope, ((struct configEntry *)list)->scope) ||
+                    slapi_dn_issuffix(((struct configEntry *)list)->scope, entry->scope)) {
+                    /* Make sure that this isn't the same exact entry
+                     * in the list already.  This can happen if a config
+                     * entry is being modified.  Both of these were already
+                     * normalized when the config struct was filled in. */
+                    if (strcasecmp(entry->dn, ((struct configEntry *)list)->dn) != 0) {
+                        rc = 1;
+                        break;
+                    }
+                }
+            } else {
+                if (found_type) {
+                    /* Since the list is sorted by link type, we
+                     * are finished if we get here since we found
+                     * the type but didn't match the scope. */
+                    break;
+                }
+            }
+
+            list = PR_NEXT_LINK(list);
+        }
+    }
+
+    return rc;
+}
+
+/*
+ * linked_attrs_config_exists_reverse()
+ *
+ * Returns 1 if a config entry exists in the cache
+ * already for the given managed type at the given scope.
+ * This will detect if the cached config entry is really
+ * the same one as the passed in entry by comparing the
+ * dn of the config entry.  We will still return 0 in
+ * this case as it's one and the same config entry. We
+ * really want to use this to prevent multiple config
+ * entries for the same managed type at the same scope.
+ *
+ * A read lock on the config must be held before calling
+ * this function.
+ */
+static int
+linked_attrs_config_exists_reverse(struct configEntry *entry)
+{
+    int rc = 0;
+    int found_type = 0;
+    PRCList *list = NULL;
+
+    if (!PR_CLIST_IS_EMPTY(g_managed_config_index)) {
+        list = PR_LIST_HEAD(g_managed_config_index);
+        while (list != g_managed_config_index) {
+            if (slapi_attr_type_cmp(((struct configIndex *)list)->config->managedtype,
+                                    entry->managedtype, 1) == 0) {
+                found_type = 1;
+                /* We don't allow nested config for the same type.  We
+                 * need to check for nesting in both directions here. */
+                if ((((struct configIndex *)list)->config->scope == NULL) ||
+                    slapi_dn_issuffix(entry->scope,
+                    ((struct configIndex *)list)->config->scope) ||
+                    slapi_dn_issuffix(((struct configIndex *)list)->config->scope,
+                    entry->scope)) {
+                    /* Make sure that this isn't the same exact entry
+                     * in the list already.  This can happen if a config
+                     * entry is being modified.  Both of these were already
+                     * normalized when the config struct was filled in. */
+                    if (strcasecmp(entry->dn, ((struct configIndex *)list)->config->dn) != 0) {
+                        rc = 1;
+                        break;
+                    }
+                }
+            } else {
+                if (found_type) {
+                    /* Since the list is sorted by link type, we
+                     * are finished if we get here since we found
+                     * the type but didn't match the scope. */
+                    break;
+                }
+            }
+
+            list = PR_NEXT_LINK(list);
+        }
+    }
+
+    return rc;
+}
+
+/*
+ * linked_attrs_oktodo()
+ *
+ * Check if we want to process this operation.  We need to be
+ * sure that the operation succeeded.  We also respond to replicated
+ * ops so we don't test for that.  This does require that the managed
+ * attributes not be replicated.
+ */
+static int
+linked_attrs_oktodo(Slapi_PBlock *pb)
+{
+        int ret = 1;
+        int oprc = 0;
+
+        slapi_log_error( SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
+                     "--> linked_attrs_oktodo\n" );
+
+        if(slapi_pblock_get(pb, SLAPI_PLUGIN_OPRETURN, &oprc) != 0)
+        {
+                slapi_log_error( SLAPI_LOG_FATAL, LINK_PLUGIN_SUBSYSTEM,
+                        "linked_attrs_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, LINK_PLUGIN_SUBSYSTEM,
+                     "<-- linked_attrs_oktodo\n" );
+
+        return ret;
+}
+
+/* linked_attrs_load_array()
+ * 
+ * put attribute values in array structure
+ */
+void
+linked_attrs_load_array(Slapi_Value **array, Slapi_Attr *attr)
+{
+        Slapi_Value *val = 0;
+        int hint = slapi_attr_first_value(attr, &val);
+
+        while(val)
+        {
+                *array = val;
+                array++;
+                hint = slapi_attr_next_value(attr, hint, &val);
+        }
+}
+
+/* linked_attrs_compare()
+ * 
+ * Compare two attr values using the DN syntax.
+ */
+int
+linked_attrs_compare(const void *a, const void *b)
+{
+        int rc = 0;
+        Slapi_Value *val1 = *((Slapi_Value **)a);
+        Slapi_Value *val2 = *((Slapi_Value **)b);
+        Slapi_Attr *linkattr = slapi_attr_new();
+
+        slapi_attr_init(linkattr, "distinguishedName");
+
+        rc = slapi_attr_value_cmp(linkattr,
+                slapi_value_get_berval(val1),
+                slapi_value_get_berval(val2));
+
+        slapi_attr_free(&linkattr);
+
+        return rc;
+}
+
+/*
+ * linked_attrs_add_backpointers()
+ *
+ * Adds backpointers pointing to dn to the entries referred to
+ * by the values in smod.
+ */
+static void
+linked_attrs_add_backpointers(char *linkdn, struct configEntry *config,
+    Slapi_Mod *smod)
+{
+    Slapi_ValueSet *vals = slapi_valueset_new();
+
+    slapi_valueset_set_from_smod(vals, smod);
+    linked_attrs_mod_backpointers(linkdn, config->managedtype, config->scope,
+                                  LDAP_MOD_ADD, vals);
+
+    slapi_valueset_free(vals);
+}
+
+/*
+ * linked_attrs_del_backpointers()
+ *
+ * Remove backpointers pointing to linkdn in the entries referred
+ * to by the values in smod.
+ */
+static void
+linked_attrs_del_backpointers(Slapi_PBlock *pb, char *linkdn,
+    struct configEntry *config, Slapi_Mod *smod)
+{
+    Slapi_ValueSet *vals = NULL;
+
+    /* If no values are listed in the smod, we need to get
+     * a list of all of the values that were deleted by
+     * looking at the pre-op copy of the entry. */
+    if (slapi_mod_get_num_values(smod) == 0) {
+        Slapi_Entry *pre_e = NULL;
+        Slapi_Attr *pre_attr = NULL;
+
+        slapi_pblock_get( pb, SLAPI_ENTRY_PRE_OP, &pre_e );
+        slapi_entry_attr_find( pre_e, config->linktype, &pre_attr );
+        slapi_attr_get_valueset(pre_attr, &vals);
+    } else {
+        vals = slapi_valueset_new();
+        slapi_valueset_set_from_smod(vals, smod);
+    }
+
+    linked_attrs_mod_backpointers(linkdn, config->managedtype, config->scope,
+                                  LDAP_MOD_DELETE, vals);
+
+    slapi_valueset_free(vals);
+}
+
+/*
+ * linked_attrs_replace_backpointers()
+ *
+ * Remove backpointers pointing to linkdn from the entries
+ * whose values were deleted in smod and add backpointers
+ * for any new values that were added as a part of the
+ * replace operation.
+ */
+static void
+linked_attrs_replace_backpointers(Slapi_PBlock *pb, char *linkdn,
+    struct configEntry *config, Slapi_Mod *smod)
+{
+    Slapi_Entry *pre_e = NULL;
+    Slapi_Entry *post_e = NULL;
+    Slapi_Attr *pre_attr = 0;
+    Slapi_Attr *post_attr = 0;
+
+    /* Get the pre and post copy of the entry to see
+     * what values have been added and removed. */
+    slapi_pblock_get(pb, SLAPI_ENTRY_PRE_OP, &pre_e);
+    slapi_pblock_get(pb, SLAPI_ENTRY_POST_OP, &post_e);
+
+    if(pre_e && post_e) {
+        slapi_entry_attr_find(pre_e, config->linktype, &pre_attr);
+        slapi_entry_attr_find(post_e, config->linktype, &post_attr);
+    }
+
+    if(pre_attr || post_attr) {
+        int pre_total = 0;
+        int post_total = 0;
+        Slapi_Value **pre_array = 0;
+        Slapi_Value **post_array = 0;
+        int pre_index = 0;
+        int post_index = 0;
+        Slapi_ValueSet *addvals = NULL;
+        Slapi_ValueSet *delvals = NULL;
+
+        /* create arrays of values */
+        if(pre_attr) {
+            slapi_attr_get_numvalues(pre_attr, &pre_total);
+        }
+
+        if(post_attr) {
+            slapi_attr_get_numvalues(post_attr, &post_total);
+        }
+
+        if(pre_total) {
+            pre_array = (Slapi_Value**) slapi_ch_malloc(sizeof(Slapi_Value*)*pre_total);
+            linked_attrs_load_array(pre_array, pre_attr);
+            qsort(pre_array, pre_total, sizeof(Slapi_Value*), linked_attrs_compare);
+        }
+
+        if(post_total) {
+            post_array = (Slapi_Value**) slapi_ch_malloc(sizeof(Slapi_Value*)*post_total);
+            linked_attrs_load_array(post_array, post_attr);
+            qsort(post_array, post_total, sizeof(Slapi_Value*), linked_attrs_compare);
+        }
+
+        /* Work through arrays, following these rules:
+         *   - in pre, in post, do nothing
+         *   - in pre, not in post, delete from entry
+         *   - not in pre, in post, add to entry
+         */
+        while(pre_index < pre_total || post_index < post_total) {
+            if(pre_index == pre_total) {
+                /* add the rest of post */
+                if (addvals == NULL) {
+                    addvals = slapi_valueset_new();
+                }
+
+                slapi_valueset_add_value(addvals, post_array[post_index]);
+                post_index++;
+            } else if(post_index == post_total) {
+                /* delete the rest of pre */
+                if (delvals == NULL) {
+                    delvals = slapi_valueset_new();
+                }
+
+                slapi_valueset_add_value(delvals, pre_array[pre_index]);
+                pre_index++;
+            } else {
+                /* decide what to do */
+                int cmp = linked_attrs_compare(&(pre_array[pre_index]),
+                                               &(post_array[post_index]));
+
+                if(cmp < 0) {
+                    /* delete pre array */
+                    if (delvals == NULL) {
+                        delvals = slapi_valueset_new();
+                    }
+
+                    slapi_valueset_add_value(delvals, pre_array[pre_index]);
+                    pre_index++;
+                } else if(cmp > 0) {
+                    /* add post array */
+                    if (addvals == NULL) {
+                        addvals = slapi_valueset_new();
+                    }
+
+                    slapi_valueset_add_value(addvals, post_array[post_index]);
+                    post_index++;
+                } else {
+                    /* do nothing, advance */
+                    pre_index++;
+                    post_index++;
+                }
+            }
+        }
+
+        /* Perform the actual updates to the target entries. */
+        if (delvals) {
+            linked_attrs_mod_backpointers(linkdn, config->managedtype,
+                                          config->scope, LDAP_MOD_DELETE, delvals);
+            slapi_valueset_free(delvals);
+        }
+
+        if (addvals) {
+            linked_attrs_mod_backpointers(linkdn, config->managedtype,
+                                          config->scope, LDAP_MOD_ADD, addvals);
+            slapi_valueset_free(addvals);
+        }
+
+        slapi_ch_free((void **)&pre_array);
+        slapi_ch_free((void **)&post_array);
+    }
+}
+
+/*
+ * linked_attrs_mod_backpointers()
+ *
+ * Performs backpointer management.
+ */
+static void
+linked_attrs_mod_backpointers(char *linkdn, char *type,
+    char *scope, int modop, Slapi_ValueSet *targetvals)
+{
+    char *val[2];
+    int i = 0;
+    Slapi_PBlock *mod_pb = slapi_pblock_new();
+    LDAPMod mod;
+    LDAPMod *mods[2];
+    Slapi_Value *targetval = NULL;
+
+    /* Setup the modify operation.  Only the target will
+     * change, so we only need to do this once. */
+    val[0] = linkdn;
+    val[1] = 0;
+
+    mod.mod_op = modop;
+    mod.mod_type = type;
+    mod.mod_values = val;
+
+    mods[0] = &mod;
+    mods[1] = 0;
+
+    i = slapi_valueset_first_value(targetvals, &targetval);
+    while(targetval)
+    {
+        int perform_update = 0;
+        const char *targetdn = slapi_value_get_string(targetval);
+
+        /* If we have a scope, only update the target if it is within
+         * the scope.  If we don't have a scope, only update the target
+         * if it is in the same backend as the linkdn. */
+        if (scope) {
+            perform_update = slapi_dn_issuffix(targetdn, scope);
+        } else {
+            Slapi_Backend *be = NULL;
+            Slapi_DN *linksdn = slapi_sdn_new_dn_byref(linkdn);
+            Slapi_DN *targetsdn = slapi_sdn_new_dn_byref(targetdn);
+
+            if ((be = slapi_be_select(linksdn))) {
+                perform_update = slapi_sdn_issuffix(targetsdn, slapi_be_getsuffix(be, 0));
+            }
+
+            slapi_sdn_free(&linksdn);
+            slapi_sdn_free(&targetsdn);
+        }
+
+        if (perform_update) {
+            slapi_log_error(SLAPI_LOG_PLUGIN, LINK_PLUGIN_SUBSYSTEM,
+                            "%s backpointer (%s) in entry (%s)\n",
+                            (modop == LDAP_MOD_ADD) ? "Adding" : "Removing",
+                            linkdn, targetdn);
+
+            /* Perform the modify operation. */
+            slapi_modify_internal_set_pb(mod_pb, targetdn, mods, 0, 0,
+                                         linked_attrs_get_plugin_id(), 0);
+            slapi_modify_internal_pb(mod_pb);
+
+            /* Initialize the pblock so we can reuse it. */
+            slapi_pblock_init(mod_pb);
+        }
+
+        i = slapi_valueset_next_value(targetvals, i, &targetval);
+    }
+
+    slapi_pblock_destroy(mod_pb);
+}
+
+
+/*
+ * Operation callback functions
+ */
+
+/*
+ * linked_attrs_pre_op()
+ *
+ * Checks if an operation is modifying the linked
+ * attribute config and validates the config changes.
+ */
+static int
+linked_attrs_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, LINK_PLUGIN_SUBSYSTEM,
+                    "--> linked_attrs_pre_op\n");
+
+    /* Just bail if we aren't ready to service requests yet. */
+    if (!g_plugin_started)
+        goto bail;
+
+    if (0 == (dn = linked_attrs_get_dn(pb)))
+        goto bail;
+
+    if (linked_attrs_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 {
+            /* 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, linked_attrs_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_passin(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 (linked_attrs_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 linked attribute configuration entry.");
+            } else {
+                errstr = slapi_ch_smprintf("Changes result in an invalid "
+                                           "linked attribute configuration.");
+            }
+        }
+
+      bailmod:
+        /* Clean up smods. */
+        if (LDAP_CHANGETYPE_MODIFY == modop) {
+            mods = slapi_mods_get_ldapmods_passout(smods);
+            slapi_pblock_set(pb, SLAPI_MODIFY_MODS, mods);
+            slapi_mods_free(&smods);
+        }
+    }
+
+  bail:
+    if (free_entry && e)
+        slapi_entry_free(e);
+
+    if (ret) {
+        slapi_log_error(SLAPI_LOG_PLUGIN, LINK_PLUGIN_SUBSYSTEM,
+                        "linked_attrs_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, LINK_PLUGIN_SUBSYSTEM,
+                    "<-- linked_attrs_pre_op\n");
+
+    return ret;
+}
+
+static int
+linked_attrs_add_pre_op(Slapi_PBlock * pb)
+{
+    return linked_attrs_pre_op(pb, LDAP_CHANGETYPE_ADD);
+}
+
+static int
+linked_attrs_mod_pre_op(Slapi_PBlock * pb)
+{
+    return linked_attrs_pre_op(pb, LDAP_CHANGETYPE_MODIFY);
+}
+
+static int
+linked_attrs_mod_post_op(Slapi_PBlock *pb)
+{
+    Slapi_Mods *smods = NULL;
+    Slapi_Mod *smod = NULL;
+    LDAPMod **mods;
+    Slapi_Mod *next_mod = NULL;
+    char *dn = NULL;
+    struct configEntry *config = NULL;
+    void *caller_id = NULL;
+
+    slapi_log_error(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
+                    "--> linked_attrs_mod_post_op\n");
+
+    /* Just bail if we aren't ready to service requests yet. */
+    if (!g_plugin_started)
+        return 0;
+
+    /* We don't want to process internal modify
+     * operations that originate from this plugin.
+     * Doing so could cause a deadlock. */
+    slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &caller_id);
+
+    if (caller_id == linked_attrs_get_plugin_id()) {
+        /* Just return without processing */
+        return 0;
+    }
+
+    if (linked_attrs_oktodo(pb) &&
+        (dn = linked_attrs_get_dn(pb))) {
+        /* First check if the config is being modified. */
+        if (linked_attrs_dn_is_config(dn)) {
+            linked_attrs_load_config();
+        } else {
+            slapi_log_error(SLAPI_LOG_PLUGIN, LINK_PLUGIN_SUBSYSTEM,
+                            "linked_attrs_mod_post_op: Error "
+                            "retrieving dn\n");
+        }
+
+        /* get the mod set */
+        slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods);
+        smods = slapi_mods_new();
+        slapi_mods_init_byref(smods, mods);
+
+        next_mod = slapi_mod_new();
+        smod = slapi_mods_get_first_smod(smods, next_mod);
+        while(smod) {
+            char *type = (char *)slapi_mod_get_type(smod);
+
+            /* See if there is an applicable link configured. */
+            linked_attrs_read_lock();
+            linked_attrs_find_config(dn, type, &config);
+
+            /* If we have a matching config entry, we have
+             * work to do. If not, we can go to the next smod. */
+            if (config) {
+                int op = slapi_mod_get_operation(smod);
+
+                /* Prevent other threads from managing 
+                 * this specific link at the same time. */
+                slapi_lock_mutex(config->lock);
+
+                switch(op & ~LDAP_MOD_BVALUES) {
+                case LDAP_MOD_ADD:
+                    /* Find the entries pointed to by the new
+                     * values and add the backpointers. */
+                    linked_attrs_add_backpointers(dn, config, smod);
+                    break;
+                case LDAP_MOD_DELETE:
+                    /* Find the entries pointed to by the deleted
+                     * values and remove the backpointers. */
+                    linked_attrs_del_backpointers(pb, dn, config, smod);
+                    break;
+                case LDAP_MOD_REPLACE:
+                    /* Find the entries pointed to by the deleted
+                     * values and remove the backpointers.  If
+                     * any new values are being added, find those
+                     * entries and add the backpointers. */
+                    linked_attrs_replace_backpointers(pb, dn, config, smod);
+                    break;
+                default:
+                    slapi_log_error(SLAPI_LOG_PLUGIN, LINK_PLUGIN_SUBSYSTEM,
+                                    "linked_attrs_mod_post_op: unknown mod type\n" );
+                    break;
+                }
+
+                slapi_unlock_mutex(config->lock);
+            }
+
+            config = NULL;
+            linked_attrs_unlock();
+            slapi_mod_done(next_mod);
+            smod = slapi_mods_get_next_smod(smods, next_mod);
+        }
+
+        slapi_mod_free(&next_mod);
+        slapi_mods_free(&smods);
+    }
+
+    slapi_log_error(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
+                    "<-- linked_attrs_mod_post_op\n");
+
+    return 0;
+}
+
+static int
+linked_attrs_add_post_op(Slapi_PBlock *pb)
+{
+    Slapi_Entry *e = NULL;
+    char *dn = NULL;
+
+    slapi_log_error(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
+                    "--> linked_attrs_add_post_op\n");
+
+    /* Just bail if we aren't ready to service requests yet. */
+    if (!g_plugin_started || !linked_attrs_oktodo(pb))
+        return 0;
+
+    /* Reload config if a config entry was added. */
+    if ((dn = linked_attrs_get_dn(pb))) {
+        if (linked_attrs_dn_is_config(dn))
+            linked_attrs_load_config();
+    } else {
+        slapi_log_error(SLAPI_LOG_PLUGIN, LINK_PLUGIN_SUBSYSTEM,
+                        "linked_attrs_add_post_op: Error "
+                        "retrieving dn\n");
+    }
+
+    /* Get the newly added entry. */
+    slapi_pblock_get(pb, SLAPI_ENTRY_POST_OP, &e);
+
+    if (e) {
+        Slapi_Attr *attr = NULL;
+        char *type = NULL;
+        struct configEntry *config = NULL;
+
+        slapi_entry_first_attr(e, &attr);
+        while (attr) {
+            slapi_attr_get_type(attr, &type);
+
+            /* See if there is an applicable link configured. */
+            linked_attrs_read_lock();
+            linked_attrs_find_config(dn, type, &config);
+
+            /* If config was found, add the backpointers to this entry. */
+            if (config) {
+                Slapi_ValueSet *vals = NULL;
+
+                slapi_attr_get_valueset(attr, &vals);
+
+                slapi_lock_mutex(config->lock);
+
+                linked_attrs_mod_backpointers(dn, config->managedtype,
+                                              config->scope, LDAP_MOD_ADD, vals);
+
+                slapi_unlock_mutex(config->lock);
+
+                slapi_valueset_free(vals);
+            }
+
+            config = NULL;
+            linked_attrs_unlock();
+
+            slapi_entry_next_attr(e, attr, &attr);
+        }
+    } else {
+        slapi_log_error(SLAPI_LOG_PLUGIN, LINK_PLUGIN_SUBSYSTEM,
+                        "linked_attrs_add_post_op: Error "
+                        "retrieving post-op entry %s\n", dn);
+    }
+
+    slapi_log_error(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
+                    "<-- linked_attrs_add_post_op\n");
+
+    return 0;
+}
+
+static int
+linked_attrs_del_post_op(Slapi_PBlock *pb)
+{
+    char *dn = NULL;
+    Slapi_Entry *e = NULL;
+
+    slapi_log_error(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
+                    "--> linked_attrs_del_post_op\n");
+
+    /* Just bail if we aren't ready to service requests yet. */
+    if (!g_plugin_started || !linked_attrs_oktodo(pb))
+        return 0;
+
+    /* Reload config if a config entry was deleted. */
+    if ((dn = linked_attrs_get_dn(pb))) {
+        if (linked_attrs_dn_is_config(dn))
+            linked_attrs_load_config();
+    } else {
+        slapi_log_error(SLAPI_LOG_PLUGIN, LINK_PLUGIN_SUBSYSTEM,
+                        "linked_attrs_del_post_op: Error "
+                        "retrieving dn\n");
+    }
+
+    /* Get deleted entry, then go through types to find config. */
+    slapi_pblock_get( pb, SLAPI_ENTRY_PRE_OP, &e );
+
+    if (e) {
+        Slapi_Attr *attr = NULL;
+        char *type = NULL;
+        struct configEntry *config = NULL;
+
+        slapi_entry_first_attr(e, &attr);
+        while (attr) {
+            slapi_attr_get_type(attr, &type);
+
+            /* See if there is an applicable link configured. */
+            linked_attrs_read_lock();
+            linked_attrs_find_config(dn, type, &config);
+
+            /* If config was found, delete the backpointers to this entry. */
+            if (config) {
+                Slapi_ValueSet *vals = NULL;
+
+                slapi_attr_get_valueset(attr, &vals);
+
+                slapi_lock_mutex(config->lock);
+
+                linked_attrs_mod_backpointers(dn, config->managedtype,
+                                              config->scope, LDAP_MOD_DELETE, vals);
+
+                slapi_unlock_mutex(config->lock);
+
+                slapi_valueset_free(vals);
+            }
+
+            config = NULL;
+
+            /* See if any of the values for this attribute are managed
+             * backpointers.  We need to remove the forward link if so. */
+            if (linked_attrs_config_index_has_type(type)) {
+                int hint = 0;
+                Slapi_Value *val = NULL;
+
+                /* Loop through values and see if we have matching config */
+                hint = slapi_attr_first_value(attr, &val);
+                while (val) {
+                    linked_attrs_find_config_reverse(slapi_value_get_string(val),
+                                                      type, &config);
+
+                    if (config) {
+                        Slapi_ValueSet *vals = slapi_valueset_new();
+                        slapi_valueset_add_value(vals, val);
+
+                        slapi_lock_mutex(config->lock);
+
+                        /* Delete forward link value. */
+                        linked_attrs_mod_backpointers(dn, config->linktype,
+                                                      config->scope, LDAP_MOD_DELETE, vals);
+
+                        slapi_unlock_mutex(config->lock);
+
+                        slapi_valueset_free(vals);
+                        config = NULL;
+                    }
+
+                    hint = slapi_attr_next_value(attr, hint, &val);
+                }
+            }
+
+            linked_attrs_unlock();
+
+            slapi_entry_next_attr(e, attr, &attr);
+        }
+    } else {
+        slapi_log_error(SLAPI_LOG_PLUGIN, LINK_PLUGIN_SUBSYSTEM,
+                        "linked_attrs_del_post_op: Error "
+                        "retrieving pre-op entry %s\n", dn);
+    }
+
+    slapi_log_error(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
+                    "<-- linked_attrs_del_post_op\n");
+
+    return 0;
+}
+
+static int
+linked_attrs_modrdn_post_op(Slapi_PBlock *pb)
+{
+    char *old_dn = NULL;
+    char *new_dn = NULL;
+    Slapi_Entry *post_e = NULL;
+    Slapi_Attr *attr = NULL;
+    char *type = NULL;
+    struct configEntry *config = NULL;
+
+    slapi_log_error(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
+                    "--> linked_attrs_modrdn_post_op\n");
+
+    /* Just bail if we aren't ready to service requests yet. */
+    if (!g_plugin_started || !linked_attrs_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, LINK_PLUGIN_SUBSYSTEM,
+                        "linked_attrs_modrdn_post_op: Error "
+                        "retrieving post-op entry\n");
+    }
+
+    if ((old_dn = linked_attrs_get_dn(pb))) {
+        if (linked_attrs_dn_is_config(old_dn) || linked_attrs_dn_is_config(new_dn))
+            linked_attrs_load_config();
+    } else {
+        slapi_log_error(SLAPI_LOG_PLUGIN, LINK_PLUGIN_SUBSYSTEM,
+                        "linked_attrs_modrdn_post_op: Error "
+                        "retrieving dn\n");
+    }
+
+    /* Check if this operation requires any updates to links. */
+    slapi_entry_first_attr(post_e, &attr);
+    while (attr) {
+        slapi_attr_get_type(attr, &type);
+
+        /* See if there is an applicable link configured. */
+        linked_attrs_read_lock();
+        linked_attrs_find_config(old_dn, type, &config);
+
+        /* If config was found for the old dn, delete the backpointers
+         * to this entry. */
+        if (config) {
+            Slapi_ValueSet *vals = NULL;
+
+            slapi_attr_get_valueset(attr, &vals);
+
+            slapi_lock_mutex(config->lock);
+
+            /* Delete old dn value. */
+            linked_attrs_mod_backpointers(old_dn, config->managedtype,
+                                          config->scope, LDAP_MOD_DELETE, vals);
+
+            slapi_unlock_mutex(config->lock);
+
+            slapi_valueset_free(vals);
+            config = NULL;
+        }
+
+        linked_attrs_find_config(new_dn, type, &config);
+
+        /* If config was found for the new dn, add the backpointers
+         * to this entry.  We do this separate check for both dn's
+         * to catch an entry that comes into or goes out of scope
+         * from the MODRDN operation. */
+        if (config) {
+            Slapi_ValueSet *vals = NULL;
+
+            slapi_attr_get_valueset(attr, &vals);
+
+            slapi_lock_mutex(config->lock);
+
+            /* Add new dn value. */
+            linked_attrs_mod_backpointers(new_dn, config->managedtype,
+                                          config->scope, LDAP_MOD_ADD, vals);
+
+            slapi_unlock_mutex(config->lock);
+
+            slapi_valueset_free(vals);
+            config = NULL;
+        }
+
+        /* See if any of the values for this attribute are managed
+         * backpointers.  We need to update the forward link if so. */
+        if (linked_attrs_config_index_has_type(type)) {
+            int hint = 0;
+            Slapi_Value *val = NULL;
+
+            /* Loop through values and see if we have matching config */
+            hint = slapi_attr_first_value(attr, &val);
+            while (val) {
+                linked_attrs_find_config_reverse(slapi_value_get_string(val),
+                                                  type, &config);
+
+                if (config) {
+                    Slapi_ValueSet *vals = slapi_valueset_new();
+                    slapi_valueset_add_value(vals, val);
+
+                    slapi_lock_mutex(config->lock);
+
+                    /* Delete old dn value. */
+                    linked_attrs_mod_backpointers(old_dn, config->linktype,
+                                                  config->scope, LDAP_MOD_DELETE, vals);
+
+                    /* Add new dn value. */
+                    linked_attrs_mod_backpointers(new_dn, config->linktype,
+                                                  config->scope, LDAP_MOD_ADD, vals);
+
+                    slapi_unlock_mutex(config->lock);
+
+                    slapi_valueset_free(vals);
+                    config = NULL;
+                }
+
+                hint = slapi_attr_next_value(attr, hint, &val);
+            }
+        }
+
+        linked_attrs_unlock();
+
+        slapi_entry_next_attr(post_e, attr, &attr);
+    }
+
+    slapi_log_error(SLAPI_LOG_TRACE, LINK_PLUGIN_SUBSYSTEM,
+                    "<-- linked_attrs_modrdn_post_op\n");
+
+    return 0;
+}
+
+
+/*
+ * Debug functions to print config
+ */
+void
+linked_attrs_dump_config()
+{
+    PRCList *list;
+
+    linked_attrs_read_lock();
+
+    if (!PR_CLIST_IS_EMPTY(g_link_config)) {
+        list = PR_LIST_HEAD(g_link_config);
+        while (list != g_link_config) {
+            linked_attrs_dump_config_entry((struct configEntry *)list);
+            list = PR_NEXT_LINK(list);
+        }
+    }
+
+    linked_attrs_unlock();
+}
+
+void
+linked_attrs_dump_config_index()
+{
+    PRCList *list;
+
+    linked_attrs_read_lock();
+
+    if (!PR_CLIST_IS_EMPTY(g_managed_config_index)) {
+        list = PR_LIST_HEAD(g_managed_config_index);
+        while (list != g_managed_config_index) {
+            linked_attrs_dump_config_entry(((struct configIndex *)list)->config);
+            list = PR_NEXT_LINK(list);
+        }
+    }
+
+    linked_attrs_unlock();
+}
+
+
+void
+linked_attrs_dump_config_entry(struct configEntry * entry)
+{
+    slapi_log_error(SLAPI_LOG_FATAL, LINK_PLUGIN_SUBSYSTEM,
+                    "<==== Linked Attribute Pair =====>\n");
+    slapi_log_error(SLAPI_LOG_FATAL, LINK_PLUGIN_SUBSYSTEM,
+                    "<---- config entry dn -----> %s\n", entry->dn);
+    slapi_log_error(SLAPI_LOG_FATAL, LINK_PLUGIN_SUBSYSTEM,
+                    "<---- link type -----------> %s\n", entry->linktype);
+    slapi_log_error(SLAPI_LOG_FATAL, LINK_PLUGIN_SUBSYSTEM,
+                    "<---- managed type --------> %s\n", entry->managedtype);
+    slapi_log_error(SLAPI_LOG_FATAL, LINK_PLUGIN_SUBSYSTEM,
+                    "<---- scope ---------------> %s\n", entry->scope);
+}
diff --git a/ldap/servers/plugins/linkedattrs/linked_attrs.h b/ldap/servers/plugins/linkedattrs/linked_attrs.h
new file mode 100644
index 0000000..8a3b262
--- /dev/null
+++ b/ldap/servers/plugins/linkedattrs/linked_attrs.h
@@ -0,0 +1,141 @@
+/** 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) 2009 Red Hat, Inc.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
+/*
+ * Linked attributes plug-in header file
+ */
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+#include "portable.h"
+#include "nspr.h"
+#include "slapi-plugin.h"
+#include "dirlite_strings.h"
+#include "dirver.h"
+#include "prclist.h"
+#include "ldif.h"
+
+/*
+ * Plug-in defines
+ */
+#define LINK_PLUGIN_SUBSYSTEM  "linkedattrs-plugin"
+#define LINK_FEATURE_DESC      "Linked Attributes"
+#define LINK_PLUGIN_DESC       "Linked Attributes plugin"
+#define LINK_INT_POSTOP_DESC   "Linked Attributes internal postop plugin"
+#define LINK_POSTOP_DESC       "Linked Attributes postop plugin"
+
+/*
+ * Config type defines
+ */
+#define LINK_LINK_TYPE         "linkType"
+#define LINK_MANAGED_TYPE      "managedType"
+#define LINK_SCOPE             "linkScope"
+
+/*
+ * Other defines
+ */
+#define DN_SYNTAX_OID          "1.3.6.1.4.1.1466.115.121.1.12"
+
+/*
+ * Linked list of config entries.
+ */
+struct configEntry {
+    PRCList list;
+    char *dn;
+    char *linktype;
+    char *managedtype;
+    char *scope;
+    Slapi_Mutex *lock;
+};
+
+/*
+ * Linked list used for indexing config entries
+ * by managed type.
+ */
+struct configIndex {
+    PRCList list;
+    struct configEntry *config;
+};
+
+/*
+ * Fixup task private data.
+ */
+typedef struct _task_data
+{
+    char *linkdn;
+} task_data;
+
+
+/*
+ * Debug functions - global, for the debugger
+ */
+void linked_attrs_dump_config();
+void linked_attrs_dump_config_index();
+void linked_attrs_dump_config_entry(struct configEntry *);
+
+/*
+ * Config fetch function
+ */
+PRCList *linked_attrs_get_config();
+
+/*
+ * Config cache locking functions
+ */
+void linked_attrs_read_lock();
+void linked_attrs_write_lock();
+void linked_attrs_unlock();
+
+/*
+ * Plugin identity functions
+ */
+void linked_attrs_set_plugin_id(void *pluginID);
+void *linked_attrs_get_plugin_id();
+void linked_attrs_set_plugin_dn(char *pluginDN);
+char *linked_attrs_get_plugin_dn();
+
+/*
+ * Fixup task callback
+ */
+int linked_attrs_fixup_task_add(Slapi_PBlock *pb, Slapi_Entry *e,
+                           Slapi_Entry *eAfter, int *returncode,
+                           char *returntext, void *arg);
+
diff --git a/ldap/servers/slapd/add.c b/ldap/servers/slapd/add.c
index 6607eff..1bd2d2f 100644
--- a/ldap/servers/slapd/add.c
+++ b/ldap/servers/slapd/add.c
@@ -641,7 +641,9 @@ static void op_shared_add (Slapi_PBlock *pb)
 				slapi_pblock_get(pb, SLAPI_ENTRY_POST_OP, &pse);
 				do_ps_service(pse, NULL, LDAP_CHANGETYPE_ADD, 0);
 
-				e = NULL; /* if be_add succeeded, then e is consumed. Must prevent e from being free'd. */
+				/* If be_add succeeded, then e is consumed.  We
+				 * set e to NULL to prevent freeing it ourselves. */
+				e = NULL;
 			}
 			else
 			{
diff --git a/ldap/servers/slapd/attr.c b/ldap/servers/slapd/attr.c
index 6c3a6a0..d78342e 100644
--- a/ldap/servers/slapd/attr.c
+++ b/ldap/servers/slapd/attr.c
@@ -835,7 +835,7 @@ attr_add_valuearray(Slapi_Attr *a, Slapi_Value **vals, const char *dn)
             }
         }
 
-        slapi_log_error( SLAPI_LOG_FATAL, NULL, "add value \"%s\" to "
+        slapi_log_error( SLAPI_LOG_TRACE, NULL, "add value \"%s\" to "
                 "attribute type \"%s\" in entry \"%s\" failed: %s\n", 
                 duplicate_string,
                 a->a_type,
diff --git a/ldap/servers/slapd/dse.c b/ldap/servers/slapd/dse.c
index c48d1f2..04a1b35 100644
--- a/ldap/servers/slapd/dse.c
+++ b/ldap/servers/slapd/dse.c
@@ -1065,6 +1065,11 @@ dse_write_entry( caddr_t data, caddr_t arg )
     return 0;
 }
   
+/*
+ * Adds an entry to the dse backend.  The passed in entry will be
+ * free'd upon success.  If we don't return 0, the caller is responsible
+ * for freeing the entry.
+ */
 static int
 dse_add_entry_pb(struct dse* pdse, Slapi_Entry *e, Slapi_PBlock *pb)
 {
@@ -1139,7 +1144,13 @@ dse_add_entry_pb(struct dse* pdse, Slapi_Entry *e, Slapi_PBlock *pb)
 		slapi_entry_free(schemacheckentry);
 	}
 
-    return rc;
+	/* Callers expect e (SLAPI_ADD_ENTRY) to be freed or otherwise
+	 * consumed if the add was successful. */
+	if (rc == 0) {
+		slapi_entry_free(e);
+	}
+
+	return rc;
 }
 
 /*
-- 
1.6.0.6

