diff --git a/contrib/ConfigOIDs b/contrib/ConfigOIDs
index a04675a3d786862ce510cb020e65237c35228c73..6dd4a9a4a10f7a96558a4db6e8b8b4a8bbb73418 100644
--- a/contrib/ConfigOIDs
+++ b/contrib/ConfigOIDs
@@ -5,3 +5,4 @@ OLcfgCt{Oc|At}:2	autogroup
 OLcfgCt{Oc|At}:3	nssov
 OLcfgCt{Oc|At}:4	cloak
 OLcfgCt{Oc|At}:5	lastbind
+OLcfgCt{Oc|At}:6	adremap
diff --git a/contrib/slapd-modules/adremap/Makefile b/contrib/slapd-modules/adremap/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..e4cab3bdbe94db7aac141fd0b12d1bb6124be472
--- /dev/null
+++ b/contrib/slapd-modules/adremap/Makefile
@@ -0,0 +1,56 @@
+# $OpenLDAP$
+# Copyright 2015 Howard Chu <hyc@symas.com>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted only as authorized by the OpenLDAP
+# Public License.
+#
+# A copy of this license is available in the file LICENSE in the
+# top-level directory of the distribution or, alternatively, at
+# <http://www.OpenLDAP.org/license.html>.
+
+LDAP_SRC = ../../..
+LDAP_BUILD = $(LDAP_SRC)
+LDAP_INC = -I$(LDAP_BUILD)/include -I$(LDAP_SRC)/include -I$(LDAP_SRC)/servers/slapd
+LDAP_LIB = $(LDAP_BUILD)/libraries/libldap_r/libldap_r.la \
+	$(LDAP_BUILD)/libraries/liblber/liblber.la
+
+LIBTOOL = $(LDAP_BUILD)/libtool
+CC = gcc
+OPT = -g -O2 -Wall
+DEFS = -DSLAPD_OVER_ADREMAP=SLAPD_MOD_DYNAMIC
+INCS = $(LDAP_INC)
+LIBS = $(LDAP_LIB)
+
+PROGRAMS = adremap.la
+LTVER = 0:0:0
+
+prefix=/usr/local
+exec_prefix=$(prefix)
+ldap_subdir=/openldap
+
+libdir=$(exec_prefix)/lib
+libexecdir=$(exec_prefix)/libexec
+moduledir = $(libexecdir)$(ldap_subdir)
+
+.SUFFIXES: .c .o .lo
+
+.c.lo:
+	$(LIBTOOL) --mode=compile $(CC) $(OPT) $(DEFS) $(INCS) -c $<
+
+all: $(PROGRAMS)
+
+adremap.la: adremap.lo
+	$(LIBTOOL) --mode=link $(CC) $(OPT) -version-info $(LTVER) \
+	-rpath $(moduledir) -module -o $@ $? $(LIBS)
+
+clean:
+	rm -rf *.o *.lo *.la .libs
+
+install: $(PROGRAMS)
+	mkdir -p $(DESTDIR)$(moduledir)
+	for p in $(PROGRAMS) ; do \
+		$(LIBTOOL) --mode=install cp $$p $(DESTDIR)$(moduledir) ; \
+	done
+
diff --git a/contrib/slapd-modules/adremap/adremap.c b/contrib/slapd-modules/adremap/adremap.c
new file mode 100644
index 0000000000000000000000000000000000000000..38c95aafcef560aaaf5f1a5155789b67c0231a23
--- /dev/null
+++ b/contrib/slapd-modules/adremap/adremap.c
@@ -0,0 +1,352 @@
+/* adremap.c - Case-folding and DN-value remapping for AD proxies */
+/* $OpenLDAP$ */
+/*
+ * Copyright 2015 Howard Chu <hyc@symas.com>.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+/*
+ * This file implements an overlay that performs two remapping functions
+ * to allow older POSIX clients to use Microsoft AD:
+ * 1: downcase the values of a configurable list of attributes
+ * 2: dereference some DN-valued attributes and convert to their simple names
+ *	   e.g. generate memberUid based on member
+ */
+
+#ifdef SLAPD_OVER_ADREMAP
+
+#include <ldap.h>
+#include "lutil.h"
+#include "slap.h"
+#include <ac/errno.h>
+#include <ac/time.h>
+#include <ac/string.h>
+#include <ac/ctype.h>
+#include "config.h"
+
+typedef struct adremap_dnv {
+	struct adremap_dnv *ad_next;
+	AttributeDescription *ad_dnattr;	/* DN-valued attr to deref */
+	AttributeDescription *ad_deref;		/* target attr's value to retrieve */
+	AttributeDescription *ad_newattr;	/* New attr to collect new values */
+} adremap_dnv;
+/* example: member uid memberUid */
+
+typedef struct adremap_case {
+	struct adremap_case *ac_next;
+	AttributeDescription *ac_attr;
+} adremap_case;
+
+/* Per-instance configuration information */
+typedef struct adremap_info {
+	adremap_case *ai_case;	/* attrs to downcase */
+	adremap_dnv *ai_dnv;	/* DN attrs to remap */
+} adremap_info;
+
+enum {
+	ADREMAP_CASE = 1,
+	ADREMAP_DNV
+};
+
+static ConfigDriver adremap_cf_case;
+static ConfigDriver adremap_cf_dnv;
+
+/* configuration attribute and objectclass */
+static ConfigTable adremapcfg[] = {
+	{ "adremap-downcase", "attrs", 2, 0, 0,
+	  ARG_MAGIC|ADREMAP_CASE, adremap_cf_case,
+	  "( OLcfgCtAt:6.1 "
+	  "NAME 'olcADremapDowncase' "
+	  "DESC 'List of attributes to casefold to lower case' "
+	  "SYNTAX OMsDirectoryString )", NULL, NULL },
+	{ "adremap-dnmap", "dnattr simpleattr newattr", 4, 4, 0,
+	  ARG_MAGIC|ADREMAP_DNV, adremap_cf_dnv,
+	  "( OLcfgCtAt:6.2 "
+	  "NAME 'olcADremapDNmap' "
+	  "DESC 'DN attr to map, attr from target to use, attr to generate' "
+	  "SYNTAX OMsDirectoryString )", NULL, NULL },
+	{ NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs adremapocs[] = {
+	{ "( OLcfgCtOc:6.1 "
+	  "NAME 'olcADremapConfig' "
+	  "DESC 'AD remap configuration' "
+	  "SUP olcOverlayConfig "
+	  "MAY ( olcADremapDowncase $ olcADremapDNmap ) )",
+	  Cft_Overlay, adremapcfg, NULL, NULL },
+	{ NULL, 0, NULL }
+};
+
+static int
+adremap_cf_case(ConfigArgs *c)
+{
+	BackendDB *be = (BackendDB *)c->be;
+	slap_overinst *on = (slap_overinst *)c->bi;
+	adremap_info *ai = on->on_bi.bi_private;
+	adremap_case *ac, **a2;
+	int rc = ARG_BAD_CONF;
+
+	switch(c->op) {
+	case SLAP_CONFIG_EMIT:
+		for (ac = ai->ai_case; ac; ac=ac->ac_next) {
+			rc = value_add_one(&c->rvalue_vals, &ac->ac_attr->ad_cname);
+			if (rc) break;
+		}
+		break;
+	case LDAP_MOD_DELETE:
+		if (c->valx < 0) {
+			for (ac = ai->ai_case; ac; ac=ai->ai_case) {
+				ai->ai_case = ac->ac_next;
+				ch_free(ac);
+			}
+		} else {
+			int i;
+			for (i=0, a2 = &ai->ai_case; i<c->valx; i++, a2 = &(*a2)->ac_next);
+			ac = *a2;
+			*a2 = ac->ac_next;
+			ch_free(ac);
+		}
+		rc = 0;
+		break;
+	default: {
+		const char *text;
+		adremap_case ad;
+		ad.ac_attr = NULL;
+		rc = slap_str2ad(c->argv[1], &ad.ac_attr, &text);
+		if (rc) break;
+		for (a2 = &ai->ai_case; *a2; a2 = &(*a2)->ac_next);
+		ac = ch_malloc(sizeof(adremap_case));
+		ac->ac_next = NULL;
+		ac->ac_attr = ad.ac_attr;
+		*a2 = ac;
+		break;
+		}
+	}
+	return rc;
+}
+
+static int
+adremap_cf_dnv(ConfigArgs *c)
+{
+	BackendDB *be = (BackendDB *)c->be;
+	slap_overinst *on = (slap_overinst *)c->bi;
+	adremap_info *ai = on->on_bi.bi_private;
+	adremap_dnv *ad, **a2;
+	int rc = ARG_BAD_CONF;
+
+	switch(c->op) {
+	case SLAP_CONFIG_EMIT:
+		for (ad = ai->ai_dnv; ad; ad=ad->ad_next) {
+			char *ptr;
+			struct berval bv;
+			bv.bv_len = ad->ad_dnattr->ad_cname.bv_len + ad->ad_deref->ad_cname.bv_len + ad->ad_newattr->ad_cname.bv_len + 2;
+			bv.bv_val = ch_malloc(bv.bv_len + 1);
+			ptr = lutil_strcopy(bv.bv_val, ad->ad_dnattr->ad_cname.bv_val);
+			*ptr++ = ' ';
+			ptr = lutil_strcopy(ptr, ad->ad_deref->ad_cname.bv_val);
+			*ptr++ = ' ';
+			strcpy(ptr, ad->ad_newattr->ad_cname.bv_val);
+			ber_bvarray_add(&c->rvalue_vals, &bv);
+		}
+		if (ai->ai_dnv) rc = 0;
+		break;
+	case LDAP_MOD_DELETE:
+		if (c->valx < 0) {
+			for (ad = ai->ai_dnv; ad; ad=ai->ai_dnv) {
+				ai->ai_dnv = ad->ad_next;
+				ch_free(ad);
+			}
+		} else {
+			int i;
+			for (i=0, a2 = &ai->ai_dnv; i<c->valx; i++, a2 = &(*a2)->ad_next);
+			ad = *a2;
+			*a2 = ad->ad_next;
+			ch_free(ad);
+		}
+		rc = 0;
+		break;
+	default: {
+		const char *text;
+		adremap_dnv av = {0};
+		rc = slap_str2ad(c->argv[1], &av.ad_dnattr, &text);
+		if (rc) break;
+		if (av.ad_dnattr->ad_type->sat_syntax != slap_schema.si_syn_distinguishedName) {
+			rc = 1;
+			snprintf(c->cr_msg, sizeof(c->cr_msg), "<%s> not a DN-valued attribute",
+				c->argv[0]);
+			Debug(LDAP_DEBUG_ANY, "%s: %s(%s)\n", c->log, c->cr_msg, c->argv[1]);
+			break;
+		}
+		rc = slap_str2ad(c->argv[2], &av.ad_deref, &text);
+		if (rc) break;
+		rc = slap_str2ad(c->argv[3], &av.ad_newattr, &text);
+		if (rc) break;
+
+		for (a2 = &ai->ai_dnv; *a2; a2 = &(*a2)->ad_next);
+		ad = ch_malloc(sizeof(adremap_dnv));
+		ad->ad_next = NULL;
+		ad->ad_dnattr = av.ad_dnattr;
+		ad->ad_deref = av.ad_deref;
+		ad->ad_newattr = av.ad_newattr;
+		*a2 = ad;
+		break;
+		}
+	}
+	return rc;
+}
+
+static int
+adremap_search_resp(
+	Operation *op,
+	SlapReply *rs
+)
+{
+	slap_overinst *on = op->o_callback->sc_private;
+	adremap_info *ai = on->on_bi.bi_private;
+	adremap_case *ac;
+	adremap_dnv *ad;
+	Attribute *a;
+	Entry *e;
+
+	if (rs->sr_type != REP_SEARCH)
+		return SLAP_CB_CONTINUE;
+
+	e = rs->sr_entry;
+	for (ac = ai->ai_case; ac; ac = ac->ac_next) {
+		a = attr_find(e->e_attrs, ac->ac_attr);
+		if (a) {
+			int i, j;
+			if (!(rs->sr_flags & REP_ENTRY_MODIFIABLE)) {
+				e = entry_dup(e);
+				rs_replace_entry(op, rs, on, e);
+				rs->sr_flags |= REP_ENTRY_MODIFIABLE|REP_ENTRY_MUSTBEFREED;
+				a = attr_find(e->e_attrs, ac->ac_attr);
+			}
+			for (i=0; i<a->a_numvals; i++) {
+				unsigned char *c = a->a_vals[i].bv_val;
+				for (j=0; j<a->a_vals[i].bv_len; j++)
+					if (isupper(c[j]))
+						c[j] = tolower(c[j]);
+			}
+		}
+	}
+	for (ad = ai->ai_dnv; ad; ad = ad->ad_next) {
+		a = attr_find(e->e_attrs, ad->ad_dnattr);
+		if (a) {
+			Entry *n;
+			Attribute *dr;
+			int i, rc;
+			if (!(rs->sr_flags & REP_ENTRY_MODIFIABLE)) {
+				e = entry_dup(e);
+				rs_replace_entry(op, rs, on, e);
+				rs->sr_flags |= REP_ENTRY_MODIFIABLE|REP_ENTRY_MUSTBEFREED;
+				a = attr_find(e->e_attrs, ad->ad_dnattr);
+			}
+			for (i=0; i<a->a_numvals; i++) {
+				n = NULL;
+				rc = be_entry_get_rw(op, &a->a_nvals[i], NULL, ad->ad_deref, 0, &n);
+				if (!rc && n) {
+					dr = attr_find(n->e_attrs, ad->ad_deref);
+					if (dr)
+						attr_merge_one(e, ad->ad_newattr, dr->a_vals, dr->a_nvals);
+					be_entry_release_r(op, n);
+				}
+			}
+		}
+	}
+	return SLAP_CB_CONTINUE;
+}
+
+static int
+adremap_search(
+	Operation *op,
+	SlapReply *rs
+)
+{
+	slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+	slap_callback *cb;
+
+	cb = op->o_tmpcalloc(1, sizeof(slap_callback), op->o_tmpmemctx);
+	cb->sc_response = adremap_search_resp;
+	cb->sc_private = on;
+	cb->sc_next = op->o_callback;
+	op->o_callback = cb;
+	return SLAP_CB_CONTINUE;
+}
+
+static int
+adremap_db_init(
+	BackendDB *be,
+	ConfigReply *cr
+)
+{
+	slap_overinst *on = (slap_overinst *) be->bd_info;
+
+	/* initialize private structure to store configuration */
+	on->on_bi.bi_private = ch_calloc( 1, sizeof(adremap_info) );
+
+	return 0;
+}
+
+static int
+adremap_db_destroy(
+	BackendDB *be,
+	ConfigReply *cr
+)
+{
+	slap_overinst *on = (slap_overinst *) be->bd_info;
+	adremap_info *ai = (adremap_info *) on->on_bi.bi_private;
+	adremap_case *ac;
+	adremap_dnv *ad;
+
+	/* free config */
+	for (ac = ai->ai_case; ac; ac = ai->ai_case) {
+		ai->ai_case = ac->ac_next;
+		ch_free(ac);
+	}
+	for (ad = ai->ai_dnv; ad; ad = ai->ai_dnv) {
+		ai->ai_dnv = ad->ad_next;
+		ch_free(ad);
+	}
+	free( ai );
+
+	return 0;
+}
+
+static slap_overinst adremap;
+
+int adremap_initialize()
+{
+	int i, code;
+
+	adremap.on_bi.bi_type = "adremap";
+	adremap.on_bi.bi_db_init = adremap_db_init;
+	adremap.on_bi.bi_db_destroy = adremap_db_destroy;
+	adremap.on_bi.bi_op_search = adremap_search;
+
+	/* register configuration directives */
+	adremap.on_bi.bi_cf_ocs = adremapocs;
+	code = config_register_schema( adremapcfg, adremapocs );
+	if ( code ) return code;
+
+	return overlay_register( &adremap );
+}
+
+#if SLAPD_OVER_ADREMAP == SLAPD_MOD_DYNAMIC
+int init_module(int argc, char *argv[]) {
+	return adremap_initialize();
+}
+#endif
+
+#endif	/* defined(SLAPD_OVER_ADREMAP) */
diff --git a/contrib/slapd-modules/adremap/slapo-adremap.5 b/contrib/slapd-modules/adremap/slapo-adremap.5
new file mode 100644
index 0000000000000000000000000000000000000000..4a8d7515b4fe3aff6e75dd1e769062df43bd6cf4
--- /dev/null
+++ b/contrib/slapd-modules/adremap/slapo-adremap.5
@@ -0,0 +1,94 @@
+.TH SLAPO-ADREMAP 5 "RELEASEDATE" "OpenLDAP LDVERSION"
+.\" Copyright 2015 Howard Chu, All Rights Reserved.
+.\" $OpenLDAP$
+.SH NAME
+slapo-adremap \- AD Remap overlay to slapd
+.SH SYNOPSIS
+ETCDIR/slapd.conf
+.SH DESCRIPTION
+The
+.B adremap
+overlay to
+.BR slapd (8)
+remaps some attribute values for compatibility between Microsoft AD
+and older POSIX systems' PAM/NSS clients. It can be configured to
+convert values of given attributes to lower case, and it can be
+configured to generate RFC2307-compliant group memberships based
+on RFC2307bis groups. All mapping is only performed on entries
+returned as search responses.
+
+.SH CONFIGURATION
+The config directives that are specific to the
+.B adremap
+overlay must be prefixed by
+.BR adremap\- ,
+to avoid potential conflicts with directives specific to the underlying 
+database or to other stacked overlays.
+
+.TP
+.B overlay adremap
+This directive adds the
+.B adremap
+overlay to the current database, see
+.BR slapd.conf (5)
+for details.
+
+.LP
+These
+.B slapd.conf
+configuration options are defined for the adremap overlay. They must
+appear after the
+.B overlay
+directive. They can each be specified multiple times:
+.TP
+.B adremap-downcase <attr>
+Specify an attributeType whose values will all be mapped to lowercase
+when returned in search responses.
+.TP
+.B adremap-dnmap <dnattr> <derefattr> <newattr>
+Specify a DN-valued attributeType whose values will be dereferenced. The
+.B <derefattr>
+of the target entry will be retrieved and its value will be added to the
+.B <newattr>
+in the entry.
+
+.SH EXAMPLE
+This example configures the
+.B adremap
+overlay to map all
+.B uid
+attributes to lowercase, and create
+.B memberUid
+values for group entries.
+Add the following to
+.BR slapd.conf (5):
+
+.LP
+.nf
+    database <database>
+    # ...
+
+    overlay adremap
+    adremap-downcase uid
+	adremap-dnmap member uid memberUid
+.fi
+.LP
+.B slapd
+must also load
+.B adremap.la,
+if compiled as a run-time module;
+
+.SH FILES
+.TP
+ETCDIR/slapd.conf
+default slapd configuration file
+.SH SEE ALSO
+.BR slapd.conf (5),
+.BR slapd (8).
+The
+.BR slapo-adremap (5)
+overlay supports dynamic configuration via
+.BR back-config.
+.SH ACKNOWLEDGEMENTS
+.P
+This module was written in 2015 by Howard Chu.