diff --git a/CHANGES b/CHANGES
index 1d5d79b9fc1f42f91afbac2a39312a714e499bdd..2d8586cbd667fca34fe2c7119ddf473cab9d3ed9 100644
--- a/CHANGES
+++ b/CHANGES
@@ -7,6 +7,7 @@ OpenLDAP 2.4.24 Engineering
 	Added libldap x500UniqueIdentifier handling (ITS#6741)
 	Added slapadd attribute value checking (ITS#6592)
 	Added slapcat continue mode for problematic DBs (ITS#6482)
+	Added slapd-meta control fowarding (ITS#6664)
 	Added slapd-null back-config support (ITS#6624)
 	Added slapd-sql autocommit support (ITS#6612)
 	Added slapd-sql support for long long keys (ITS#6617)
diff --git a/doc/man/man5/slapd-meta.5 b/doc/man/man5/slapd-meta.5
index 92a694ea72c059fb6b658545bd90d4487354afbf..1af67466f32f330216f67d9597ecbd2b40475198 100644
--- a/doc/man/man5/slapd-meta.5
+++ b/doc/man/man5/slapd-meta.5
@@ -311,6 +311,18 @@ underlying libldap, with rebinding eventually performed if the
 If set before any target specification, it affects all targets, unless
 overridden by any per-target directive.
 
+.TP
+.B client\-pr {accept-unsolicited|DISABLE|<size>}
+This feature allows to use RFC 2696 Paged Results control when performing
+search operations with a specific target.
+When set to a numeric value, Paged Results control is always
+used with \fIsize\fP as the page size.
+When set to \fIaccept-unsolicited\fP, unsolicited Paged Results
+control responses are accepted and honored.
+By default, Paged Results control is not used and responses are not accepted.
+If set before any target specification, it affects all targets, unless
+overridden by any per-target directive.
+
 .TP
 .B default\-target [<target>]
 The "default\-target" directive can also be used during target specification.
diff --git a/servers/slapd/back-meta/back-meta.h b/servers/slapd/back-meta/back-meta.h
index a1b9476007b731ef2ce79e2ef3b5b0a04fa38355..c8b847d7d431ab85b33e5ae9972854fe2da74988 100644
--- a/servers/slapd/back-meta/back-meta.h
+++ b/servers/slapd/back-meta/back-meta.h
@@ -27,6 +27,10 @@
 #ifndef SLAPD_META_H
 #define SLAPD_META_H
 
+#ifdef LDAP_DEVEL
+#define SLAPD_META_CLIENT_PR 1
+#endif /* LDAP_DEVEL */
+
 #include "proto-meta.h"
 
 /* String rewrite library */
@@ -335,6 +339,19 @@ typedef struct metatarget_t {
 	slap_mask_t		mt_rep_flags;
 
 	int			mt_version;
+
+#ifdef SLAPD_META_CLIENT_PR
+	/*
+	 * client-side paged results:
+	 * -1: accept unsolicited paged results responses
+	 *  0: off
+	 * >0: always request paged results with size == mt_ps
+	 */
+#define META_CLIENT_PR_DISABLE			(0)
+#define META_CLIENT_PR_ACCEPT_UNSOLICITED	(-1)
+	ber_int_t		mt_ps;
+#endif /* SLAPD_META_CLIENT_PR */
+
 	time_t			mt_network_timeout;
 	struct timeval		mt_bind_timeout;
 #define META_BIND_TIMEOUT	LDAP_BACK_RESULT_UTIMEOUT
@@ -411,6 +428,12 @@ typedef struct metainfo_t {
 #define META_BACK_QUARANTINE(mi)	LDAP_BACK_ISSET( (mi), LDAP_BACK_F_QUARANTINE )
 
 	int			mi_version;
+
+#ifdef SLAPD_META_CLIENT_PR
+	ber_int_t		mi_ps;
+#endif /* SLAPD_META_CLIENT_PR */
+
+
 	time_t			mi_network_timeout;
 	time_t			mi_conn_ttl;
 	time_t			mi_idle_timeout;
diff --git a/servers/slapd/back-meta/config.c b/servers/slapd/back-meta/config.c
index e6de08a8ebec085754efaf606f7b7ebbac0e5d8c..ae87023b3d3586b56d19ce1c548b172e4429451e 100644
--- a/servers/slapd/back-meta/config.c
+++ b/servers/slapd/back-meta/config.c
@@ -168,6 +168,9 @@ meta_back_db_config(
 		}
 		mt->mt_flags = mi->mi_flags;
 		mt->mt_version = mi->mi_version;
+#ifdef SLAPD_META_CLIENT_PR
+		mt->mt_ps = mi->mi_ps;
+#endif /* SLAPD_META_CLIENT_PR */
 		mt->mt_network_timeout = mi->mi_network_timeout;
 		mt->mt_bind_timeout = mi->mi_bind_timeout;
 		for ( c = 0; c < SLAP_OP_LAST; c++ ) {
@@ -1532,6 +1535,33 @@ idassert-authzFrom	"dn:<rootdn>"
 			return( 1 );
 		}
 
+#ifdef SLAPD_META_CLIENT_PR
+	} else if ( strcasecmp( argv[ 0 ], "client-pr" ) == 0 ) {
+		int *ps = mi->mi_ntargets ?
+				&mi->mi_targets[ mi->mi_ntargets - 1 ]->mt_ps
+				: &mi->mi_ps;
+
+		if ( argc != 2 ) {
+			Debug( LDAP_DEBUG_ANY,
+	"%s: line %d: \"client-pr {accept-unsolicited|disable|<size>}\" needs 1 argument.\n",
+				fname, lineno, 0 );
+			return( 1 );
+		}
+
+		if ( strcasecmp( argv[ 1 ], "accept-unsolicited" ) == 0 ) {
+			*ps = META_CLIENT_PR_ACCEPT_UNSOLICITED;
+
+		} else if ( strcasecmp( argv[ 1 ], "disable" ) == 0 ) {
+			*ps = META_CLIENT_PR_DISABLE;
+
+		} else if ( lutil_atoi( ps, argv[ 1 ] ) || *ps < -1 ) {
+			Debug( LDAP_DEBUG_ANY,
+	"%s: line %d: \"client-pr {accept-unsolicited|disable|<size>}\" invalid arg \"%s\".\n",
+				fname, lineno, argv[ 1 ] );
+			return( 1 );
+		}
+#endif /* SLAPD_META_CLIENT_PR */
+
 	/* anything else */
 	} else {
 		return SLAP_CONF_UNKNOWN;
diff --git a/servers/slapd/back-meta/search.c b/servers/slapd/back-meta/search.c
index b0594605f7db97ad0fd72c0e940c6fbcc0a3be22..6f01dbefd9176e2942840943e2bac37eb060581f 100644
--- a/servers/slapd/back-meta/search.c
+++ b/servers/slapd/back-meta/search.c
@@ -446,7 +446,9 @@ meta_back_search_start(
 	dncookie		*dc,
 	metaconn_t		**mcp,
 	int			candidate,
-	SlapReply		*candidates )
+	SlapReply		*candidates,
+	struct berval		*prcookie,
+	ber_int_t		prsize )
 {
 	metainfo_t		*mi = ( metainfo_t * )op->o_bd->be_private;
 	metatarget_t		*mt = mi->mi_targets[ candidate ];
@@ -461,6 +463,9 @@ meta_back_search_start(
 	struct timeval		tv, *tvp = NULL;
 	int			nretries = 1;
 	LDAPControl		**ctrls = NULL;
+#ifdef SLAPD_META_CLIENT_PR
+	LDAPControl		**save_ctrls = NULL;
+#endif /* SLAPD_META_CLIENT_PR */
 
 	/* this should not happen; just in case... */
 	if ( msc->msc_ld == NULL ) {
@@ -615,6 +620,85 @@ meta_back_search_start(
 		tvp = &tv;
 	}
 
+#ifdef SLAPD_META_CLIENT_PR
+	save_ctrls = op->o_ctrls;
+	{
+		LDAPControl *pr_c = NULL;
+		int i = 0, nc = 0;
+
+		if ( save_ctrls ) {
+			for ( ; save_ctrls[i] != NULL; i++ );
+			nc = i;
+			pr_c = ldap_control_find( LDAP_CONTROL_PAGEDRESULTS, save_ctrls, NULL );
+		}
+
+		if ( pr_c != NULL ) nc--;
+		if ( mt->mt_ps > 0 || prcookie != NULL ) nc++;
+
+		if ( mt->mt_ps > 0 || prcookie != NULL || pr_c != NULL ) {
+			int src = 0, dst = 0;
+			BerElementBuffer berbuf;
+			BerElement *ber = (BerElement *)&berbuf;
+			struct berval val = BER_BVNULL;
+			ber_len_t len;
+
+			len = sizeof( LDAPControl * )*( nc + 1 ) + sizeof( LDAPControl );
+
+			if ( mt->mt_ps > 0 || prcookie != NULL ) {
+				struct berval nullcookie = BER_BVNULL;
+				ber_tag_t tag;
+
+				if ( prsize == 0 && mt->mt_ps > 0 ) prsize = mt->mt_ps;
+				if ( prcookie == NULL ) prcookie = &nullcookie;
+
+				ber_init2( ber, NULL, LBER_USE_DER );
+				tag = ber_printf( ber, "{iO}", prsize, prcookie ); 
+				if ( tag == LBER_ERROR ) {
+					/* error */
+					(void) ber_free_buf( ber );
+					goto done_pr;
+				}
+
+				tag = ber_flatten2( ber, &val, 0 );
+				if ( tag == LBER_ERROR ) {
+					/* error */
+					(void) ber_free_buf( ber );
+					goto done_pr;
+				}
+
+				len += val.bv_len + 1;
+			}
+
+			op->o_ctrls = op->o_tmpalloc( len, op->o_tmpmemctx );
+			if ( save_ctrls ) {
+				for ( ; save_ctrls[ src ] != NULL; src++ ) {
+					if ( save_ctrls[ src ] != pr_c ) {
+						op->o_ctrls[ dst ] = save_ctrls[ src ];
+						dst++;
+					}
+				}
+			}
+
+			if ( mt->mt_ps > 0 || prcookie != NULL ) {
+				op->o_ctrls[ dst ] = (LDAPControl *)&op->o_ctrls[ nc + 1 ];
+
+				op->o_ctrls[ dst ]->ldctl_oid = LDAP_CONTROL_PAGEDRESULTS;
+				op->o_ctrls[ dst ]->ldctl_iscritical = 1;
+
+				op->o_ctrls[ dst ]->ldctl_value.bv_val = (char *)&op->o_ctrls[ dst ][ 1 ];
+				AC_MEMCPY( op->o_ctrls[ dst ]->ldctl_value.bv_val, val.bv_val, val.bv_len + 1 );
+				op->o_ctrls[ dst ]->ldctl_value.bv_len = val.bv_len;
+				dst++;
+
+				(void)ber_free_buf( ber );
+			}
+
+			op->o_ctrls[ dst ] = NULL;
+		}
+done_pr:;
+	}
+#endif /* SLAPD_META_CLIENT_PR */
+
 retry:;
 	ctrls = op->o_ctrls;
 	if ( meta_back_controls_add( op, rs, *mcp, candidate, &ctrls )
@@ -661,6 +745,12 @@ retry:;
 
 done:;
 	(void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+#ifdef SLAPD_META_CLIENT_PR
+	if ( save_ctrls != op->o_ctrls ) {
+		op->o_tmpfree( op->o_ctrls, op->o_tmpmemctx );
+		op->o_ctrls = save_ctrls;
+	}
+#endif /* SLAPD_META_CLIENT_PR */
 
 	if ( mapped_attrs ) {
 		ber_memfree_x( mapped_attrs, op->o_tmpmemctx );
@@ -740,6 +830,7 @@ getconn:;
 		candidates[ i ].sr_text = NULL;
 		candidates[ i ].sr_ref = NULL;
 		candidates[ i ].sr_ctrls = NULL;
+		candidates[ i ].sr_nentries = 0;
 
 		/* get largest timeout among candidates */
 		if ( mi->mi_targets[ i ]->mt_timeout[ SLAP_OP_SEARCH ]
@@ -756,7 +847,7 @@ getconn:;
 			continue;
 		}
 
-		switch ( meta_back_search_start( op, rs, &dc, &mc, i, candidates ) )
+		switch ( meta_back_search_start( op, rs, &dc, &mc, i, candidates, NULL, 0 ) )
 		{
 		case META_SEARCH_NOT_CANDIDATE:
 			candidates[ i ].sr_msgid = META_MSGID_IGNORE;
@@ -973,7 +1064,7 @@ getconn:;
 
 				case META_SEARCH_CANDIDATE:
 					candidates[ i ].sr_msgid = META_MSGID_IGNORE;
-					switch ( meta_back_search_start( op, rs, &dc, &mc, i, candidates ) )
+					switch ( meta_back_search_start( op, rs, &dc, &mc, i, candidates, NULL, 0 ) )
 					{
 					case META_SEARCH_CANDIDATE:
 						assert( candidates[ i ].sr_msgid >= 0 );
@@ -1060,7 +1151,7 @@ really_bad:;
 
 					if ( meta_back_retry( op, rs, &mc, i, LDAP_BACK_DONTSEND ) ) {
 						candidates[ i ].sr_msgid = META_MSGID_IGNORE;
-						switch ( meta_back_search_start( op, rs, &dc, &mc, i, candidates ) )
+						switch ( meta_back_search_start( op, rs, &dc, &mc, i, candidates, NULL, 0 ) )
 						{
 							/* means that failed but onerr == continue */
 						case META_SEARCH_NOT_CANDIDATE:
@@ -1140,6 +1231,9 @@ really_bad:;
 						candidates[ i ].sr_type = REP_RESULT;
 					}
 
+					/* count entries returned by target */
+					candidates[ i ].sr_nentries++;
+
 					is_ok++;
 
 					e = ldap_first_entry( msc->msc_ld, msg );
@@ -1283,6 +1377,7 @@ really_bad:;
 				} else if ( rc == LDAP_RES_SEARCH_RESULT ) {
 					char		buf[ SLAP_TEXT_BUFLEN ];
 					char		**references = NULL;
+					LDAPControl	**ctrls = NULL;
 
 					if ( candidates[ i ].sr_type == REP_INTERMEDIATE ) {
 						/* don't retry any more... */
@@ -1306,7 +1401,7 @@ really_bad:;
 						(char **)&candidates[ i ].sr_matched,
 						(char **)&candidates[ i ].sr_text,
 						&references,
-						NULL /* &candidates[ i ].sr_ctrls (unused) */ ,
+						&ctrls /* &candidates[ i ].sr_ctrls (unused) */ ,
 						0 );
 					if ( rs->sr_err != LDAP_SUCCESS ) {
 						candidates[ i ].sr_err = rs->sr_err;
@@ -1401,7 +1496,7 @@ really_bad:;
 
 					/* cleanup */
 					ber_memvfree( (void **)references );
-	
+
 					sres = slap_map_api2result( rs );
 	
 					if ( LogTest( LDAP_DEBUG_TRACE | LDAP_DEBUG_ANY ) ) {
@@ -1434,6 +1529,93 @@ really_bad:;
 						break;
 	
 					case LDAP_SUCCESS:
+						if ( ctrls != NULL && ctrls[0] != NULL ) {
+#ifdef SLAPD_META_CLIENT_PR
+							LDAPControl *pr_c;
+
+							pr_c = ldap_control_find( LDAP_CONTROL_PAGEDRESULTS, ctrls, NULL );
+							if ( pr_c != NULL ) {
+								BerElementBuffer berbuf;
+								BerElement *ber = (BerElement *)&berbuf;
+								ber_tag_t tag;
+								ber_int_t prsize;
+								struct berval prcookie;
+
+								/* unsolicited, do not accept */
+								if ( mi->mi_targets[i]->mt_ps == 0 ) {
+									rs->sr_err = LDAP_OTHER;
+									goto err_pr;
+								}
+
+								ber_init2( ber, &pr_c->ldctl_value, LBER_USE_DER );
+
+								tag = ber_scanf( ber, "{im}", &prsize, &prcookie );
+								if ( tag == LBER_ERROR ) {
+									rs->sr_err = LDAP_OTHER;
+									goto err_pr;
+								}
+
+								/* more pages? new search request */
+								if ( !BER_BVISNULL( &prcookie ) && !BER_BVISEMPTY( &prcookie ) ) {
+									if ( mi->mi_targets[i]->mt_ps > 0 ) {
+										/* ignore size if specified */
+										prsize = 0;
+
+									} else if ( prsize == 0 ) {
+										/* guess the page size from the entries returned so far */
+										prsize = candidates[ i ].sr_nentries;
+									}
+
+									candidates[ i ].sr_nentries = 0;
+									candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+									candidates[ i ].sr_type = REP_INTERMEDIATE;
+								
+									assert( candidates[ i ].sr_matched == NULL );
+									assert( candidates[ i ].sr_text == NULL );
+									assert( candidates[ i ].sr_ref == NULL );
+
+									switch ( meta_back_search_start( op, rs, &dc, &mc, i, candidates, &prcookie, prsize ) )
+									{
+									case META_SEARCH_CANDIDATE:
+										assert( candidates[ i ].sr_msgid >= 0 );
+										ldap_controls_free( ctrls );
+										goto free_message;
+
+									case META_SEARCH_ERR:
+err_pr:;
+										candidates[ i ].sr_err = rs->sr_err;
+										if ( META_BACK_ONERR_STOP( mi ) ) {
+											savepriv = op->o_private;
+											op->o_private = (void *)i;
+											send_ldap_result( op, rs );
+											op->o_private = savepriv;
+											ldap_controls_free( ctrls );
+											goto finish;
+										}
+										/* fallthru */
+
+									case META_SEARCH_NOT_CANDIDATE:
+										/* means that meta_back_search_start()
+										 * failed but onerr == continue */
+										candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+										assert( ncandidates > 0 );
+										--ncandidates;
+										break;
+
+									default:
+										/* impossible */
+										assert( 0 );
+										break;
+									}
+									break;
+								}
+							}
+#endif /* SLAPD_META_CLIENT_PR */
+
+							ldap_controls_free( ctrls );
+						}
+						/* fallthru */
+
 					case LDAP_REFERRAL:
 						is_ok++;
 						break;
@@ -1451,7 +1633,7 @@ really_bad:;
 						if ( rs->sr_nentries == op->ors_slimit
 							|| META_BACK_ONERR_STOP( mi ) )
 						{
-							char *save_text = rs->sr_text;
+							const char *save_text = rs->sr_text;
 							savepriv = op->o_private;
 							op->o_private = (void *)i;
 							rs->sr_text = candidates[ i ].sr_text;
@@ -1467,7 +1649,7 @@ really_bad:;
 					default:
 						candidates[ i ].sr_err = rs->sr_err;
 						if ( META_BACK_ONERR_STOP( mi ) ) {
-							char *save_text = rs->sr_text;
+							const char *save_text = rs->sr_text;
 							savepriv = op->o_private;
 							op->o_private = (void *)i;
 							rs->sr_text = candidates[ i ].sr_text;
@@ -1497,7 +1679,7 @@ really_bad:;
 					retcode = meta_search_dobind_result( op, rs, &mc, i, candidates, msg );
 					if ( retcode == META_SEARCH_CANDIDATE ) {
 						candidates[ i ].sr_msgid = META_MSGID_IGNORE;
-						retcode = meta_back_search_start( op, rs, &dc, &mc, i, candidates );
+						retcode = meta_back_search_start( op, rs, &dc, &mc, i, candidates, NULL, 0 );
 					}
 	
 					switch ( retcode ) {