andykimpe / rpms / 389-ds-base

Forked from rpms/389-ds-base 5 months ago
Clone
Blob Blame History Raw
From fa6fba4d7716cb2ce4cd5446e0af790d765d71e2 Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Wed, 5 Mar 2014 15:55:31 -0500
Subject: [PATCH] Ticket 417, 458, 47522 - Password Administrator Backport

Description:  Backported all the fixes needed to implement the feature.

https://fedorahosted.org/389/ticket/417
https://fedorahosted.org/389/ticket/458
https://fedorahosted.org/389/ticket/47522

Bug 985270 - [RFE] Add Password adminstrators to RHDS 9 as in
http://directory.fedoraproject.org/wiki/Password_Administrator (edit)

Reviewed by: nhosoi(Thanks!)
(cherry picked from commit 096d8958a81ee57d3486d8260430cbfab81a0bbc)
---
 ldap/schema/02common.ldif         |   3 +-
 ldap/servers/slapd/entry.c        |  45 ++++++++++----
 ldap/servers/slapd/libglobs.c     |  24 +++++++-
 ldap/servers/slapd/modify.c       |  25 ++++----
 ldap/servers/slapd/pblock.c       |   6 ++
 ldap/servers/slapd/proto-slap.h   |   1 +
 ldap/servers/slapd/pw.c           | 123 ++++++++++++++++++++++++++++++++++++--
 ldap/servers/slapd/pw.h           |   1 +
 ldap/servers/slapd/slap.h         |   3 +
 ldap/servers/slapd/slapi-plugin.h |  32 ++++++++++
 10 files changed, 233 insertions(+), 30 deletions(-)

diff --git a/ldap/schema/02common.ldif b/ldap/schema/02common.ldif
index ffec7ce..92feb49 100644
--- a/ldap/schema/02common.ldif
+++ b/ldap/schema/02common.ldif
@@ -95,6 +95,7 @@ attributeTypes: ( 2.16.840.1.113730.3.1.2081 NAME ( 'passwordMaxRepeats' 'pwdMax
 attributeTypes: ( 2.16.840.1.113730.3.1.2082 NAME ( 'passwordMinCategories' 'pwdMinCategories' ) DESC 'Netscape defined password policy attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' )
 attributeTypes: ( 2.16.840.1.113730.3.1.2083 NAME ( 'passwordMinTokenLength' 'pwdMinTokenLength' ) DESC 'Netscape defined password policy attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' )
 attributeTypes: ( 2.16.840.1.113730.3.1.2140 NAME ( 'passwordTrackUpdateTime' ) DESC 'Netscape defined password policy attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' )
+attributeTypes: ( 2.16.840.1.113730.3.1.2153 NAME ( 'passwordAdminDN' 'pwdAdminDN' ) DESC 'Netscape defined password policy attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' )
 attributeTypes: ( 2.16.840.1.113730.3.1.198 NAME 'memberURL' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 X-ORIGIN 'Netscape Directory Server' )
 attributeTypes: ( 2.16.840.1.113730.3.1.199 NAME 'memberCertificateDescription' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 X-ORIGIN 'Netscape Directory Server' )
 attributeTypes: ( 2.16.840.1.113730.3.1.207 NAME 'vlvBase' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'Netscape Directory Server' )
@@ -164,7 +165,7 @@ objectClasses: ( 2.16.840.1.113730.3.2.7 NAME 'nsLicenseUser' DESC 'Netscape def
 objectClasses: ( 2.16.840.1.113730.3.2.1 NAME 'changeLogEntry' DESC 'LDAP changelog objectclass' SUP top MUST ( targetdn $ changeTime $ changenumber $ changeType ) MAY ( changes $ newrdn $ deleteoldrdn $ newsuperior ) X-ORIGIN 'Changelog Internet Draft' )
 objectClasses: ( 2.16.840.1.113730.3.2.6 NAME 'referral' DESC 'LDAP referrals objectclass' SUP top MAY ( ref ) X-ORIGIN 'LDAPv3 referrals Internet Draft' )
 objectClasses: ( 2.16.840.1.113730.3.2.12 NAME 'passwordObject' DESC 'Netscape defined password policy objectclass' SUP top MAY ( pwdpolicysubentry $ passwordExpirationTime $ passwordExpWarned $ passwordRetryCount $ retryCountResetTime $ accountUnlockTime $ passwordHistory $ passwordAllowChangeTime $ passwordGraceUserTime ) X-ORIGIN 'Netscape Directory Server' )
-objectClasses: ( 2.16.840.1.113730.3.2.13 NAME 'passwordPolicy' DESC 'Netscape defined password policy objectclass' SUP top MAY ( passwordMaxAge $ passwordExp $ passwordMinLength $ passwordKeepHistory $ passwordInHistory $ passwordChange $ passwordWarning $ passwordLockout $ passwordMaxFailure $ passwordResetDuration $ passwordUnlock $ passwordLockoutDuration $ passwordCheckSyntax $ passwordMustChange $ passwordStorageScheme $ passwordMinAge $ passwordResetFailureCount $ passwordGraceLimit $ passwordMinDigits $ passwordMinAlphas $ passwordMinUppers $ passwordMinLowers $ passwordMinSpecials $ passwordMin8bit $ passwordMaxRepeats $ passwordMinCategories $ passwordMinTokenLength $ passwordTrackUpdateTime ) X-ORIGIN 'Netscape Directory Server' )
+objectClasses: ( 2.16.840.1.113730.3.2.13 NAME 'passwordPolicy' DESC 'Netscape defined password policy objectclass' SUP top MAY ( passwordMaxAge $ passwordExp $ passwordMinLength $ passwordKeepHistory $ passwordInHistory $ passwordChange $ passwordWarning $ passwordLockout $ passwordMaxFailure $ passwordResetDuration $ passwordUnlock $ passwordLockoutDuration $ passwordCheckSyntax $ passwordMustChange $ passwordStorageScheme $ passwordMinAge $ passwordResetFailureCount $ passwordGraceLimit $ passwordMinDigits $ passwordAdminDN $ passwordMinAlphas $ passwordMinUppers $ passwordMinLowers $ passwordMinSpecials $ passwordMin8bit $ passwordMaxRepeats $ passwordMinCategories $ passwordMinTokenLength $ passwordTrackUpdateTime ) X-ORIGIN 'Netscape Directory Server' )
 objectClasses: ( 2.16.840.1.113730.3.2.30 NAME 'glue' DESC 'Netscape defined objectclass' SUP top X-ORIGIN 'Netscape Directory Server' )
 objectClasses: ( 2.16.840.1.113730.3.2.32 NAME 'netscapeMachineData' DESC 'Netscape defined objectclass' SUP top X-ORIGIN 'Netscape Directory Server' )
 objectClasses: ( 2.16.840.1.113730.3.2.38 NAME 'vlvSearch' DESC 'Netscape defined objectclass' SUP top MUST ( cn $ vlvBase $ vlvScope $ vlvFilter ) MAY ( multiLineDescription ) X-ORIGIN 'Netscape Directory Server' )
diff --git a/ldap/servers/slapd/entry.c b/ldap/servers/slapd/entry.c
index 785256f..8b7766e 100644
--- a/ldap/servers/slapd/entry.c
+++ b/ldap/servers/slapd/entry.c
@@ -2630,25 +2630,46 @@ slapi_entry_delete_string(Slapi_Entry *e, const char *type, const char *value)
 char **
 slapi_entry_attr_get_charray( const Slapi_Entry* e, const char *type)
 {
-    char **parray = NULL;
-    Slapi_Attr* attr = NULL;
+	int ignore;
+	return slapi_entry_attr_get_charray_ext(e, type, &ignore);
+}
+
+/*
+ * The extension also gathers the number of values.
+ * The caller must free with slapi_ch_array_free
+ */
+char **
+slapi_entry_attr_get_charray_ext( const Slapi_Entry* e, const char *type, int *numVals)
+{
+	char **parray = NULL;
+	Slapi_Attr* attr = NULL;
+	int count = 0;
+
+	if(numVals == NULL){
+		return NULL;
+	}
+
 	slapi_entry_attr_find(e, type, &attr);
-	if(attr!=NULL)
-	{
+	if(attr!=NULL){
 		int hint;
 		Slapi_Value *v = NULL;
+
 		for (hint = slapi_attr_first_value(attr, &v);
 			 hint != -1;
 			 hint = slapi_attr_next_value(attr, hint, &v))
 		{
 			const struct berval *bvp = slapi_value_get_berval(v);
 			char *p = slapi_ch_malloc(bvp->bv_len + 1);
+
 			memcpy(p, bvp->bv_val, bvp->bv_len);
 			p[bvp->bv_len]= '\0';
 			charray_add(&parray, p);
+			count++;
 		}
 	}
-    return parray;
+	*numVals = count;
+
+	return parray;
 }
 
 char *
@@ -2660,16 +2681,18 @@ slapi_entry_attr_get_charptr( const Slapi_Entry* e, const char *type)
 	if(attr!=NULL)
 	{
 		Slapi_Value *v;
-                const struct berval *bvp;
+		const struct berval *bvp;
+
 		slapi_valueset_first_value( &attr->a_present_values, &v);
-                bvp = slapi_value_get_berval(v);
-        p= slapi_ch_malloc(bvp->bv_len + 1);
-        memcpy(p, bvp->bv_val, bvp->bv_len);
-        p[bvp->bv_len]= '\0';
+		bvp = slapi_value_get_berval(v);
+		p= slapi_ch_malloc(bvp->bv_len + 1);
+		memcpy(p, bvp->bv_val, bvp->bv_len);
+		p[bvp->bv_len]= '\0';
 	}
-    return p;
+	return p;
 }
 
+
 int
 slapi_entry_attr_get_int( const Slapi_Entry* e, const char *type)
 {
diff --git a/ldap/servers/slapd/libglobs.c b/ldap/servers/slapd/libglobs.c
index 8103133..b7dadcd 100644
--- a/ldap/servers/slapd/libglobs.c
+++ b/ldap/servers/slapd/libglobs.c
@@ -248,6 +248,9 @@ static struct config_get_and_set {
 	{CONFIG_PWPOLICY_LOCAL_ATTRIBUTE, config_set_pwpolicy_local,
 		NULL, 0,
 		(void**)&global_slapdFrontendConfig.pwpolicy_local, CONFIG_ON_OFF, NULL},
+	{CONFIG_PW_ADMIN_DN_ATTRIBUTE, config_set_pw_admin_dn,
+		NULL, 0,
+		(void**)&global_slapdFrontendConfig.pw_policy.pw_admin, CONFIG_STRING, NULL},
 	{CONFIG_AUDITLOG_MAXLOGDISKSPACE_ATTRIBUTE, NULL,
 		log_set_maxdiskspace, SLAPD_AUDIT_LOG,
 		(void**)&global_slapdFrontendConfig.auditlog_maxdiskspace, CONFIG_INT, NULL},
@@ -685,8 +688,7 @@ static struct config_get_and_set {
 	{CONFIG_DISK_THRESHOLD, config_set_disk_threshold,
 		NULL, 0,
 		(void**)&global_slapdFrontendConfig.disk_threshold,
-		CONFIG_LONG_LONG, (ConfigGetFunc)config_get_disk_threshold,
-		DEFAULT_DISK_THRESHOLD},
+		CONFIG_LONG_LONG, (ConfigGetFunc)config_get_disk_threshold},
 	{CONFIG_DISK_GRACE_PERIOD, config_set_disk_grace_period,
 		NULL, 0,
 		(void**)&global_slapdFrontendConfig.disk_grace_period,
@@ -1122,9 +1124,11 @@ FrontendConfig_init () {
   cfg->disk_grace_period = 60; /* 1 hour */
   cfg->disk_logging_critical = LDAP_OFF;
   cfg->sasl_max_bufsize = SLAPD_DEFAULT_SASL_MAXBUFSIZE;
-
+  cfg->pw_policy.pw_admin = NULL;
+  cfg->pw_policy.pw_admin_user = NULL;
   cfg->listen_backlog_size = DAEMON_LISTEN_SIZE;
   cfg->ignore_time_skew = LDAP_OFF;
+
 #if defined(LINUX)
   cfg->malloc_mxfast = DEFAULT_MALLOC_UNSET;
   cfg->malloc_trim_threshold = DEFAULT_MALLOC_UNSET;
@@ -2837,6 +2841,20 @@ config_set_dn_validate_strict( const char *attrname, char *value, char *errorbuf
 }
 
 int
+config_set_pw_admin_dn( const char *attrname, char *value, char *errorbuf, int apply ) {
+  int retVal =  LDAP_SUCCESS;
+  slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig();
+
+  if ( apply ) {
+        CFG_LOCK_WRITE(slapdFrontendConfig);
+        slapi_sdn_free(&slapdFrontendConfig->pw_policy.pw_admin);
+        slapdFrontendConfig->pw_policy.pw_admin = slapi_sdn_new_dn_byval(value);
+        CFG_UNLOCK_WRITE(slapdFrontendConfig);
+  }
+  return retVal;
+}
+
+int
 config_set_ds4_compatible_schema( const char *attrname, char *value, char *errorbuf, int apply ) {
   int retVal = LDAP_SUCCESS;
   slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig();
diff --git a/ldap/servers/slapd/modify.c b/ldap/servers/slapd/modify.c
index 817f17c..90c9f8c 100644
--- a/ldap/servers/slapd/modify.c
+++ b/ldap/servers/slapd/modify.c
@@ -1227,11 +1227,9 @@ static int op_shared_allow_pw_change (Slapi_PBlock *pb, LDAPMod *mod, char **old
 		slapi_pblock_set( pb, SLAPI_BACKEND, slapi_be_select( &sdn ) );
 
 		/* Check if ACIs allow password to be changed */
-		if ( (res = slapi_acl_check_mods(pb, e, mods, &errtxt)) != LDAP_SUCCESS) {
-			if (operation_is_flag_set(operation,OP_FLAG_ACTION_LOG_ACCESS))
-			{
-				if (proxydn)
-				{
+		if ( !pw_is_pwp_admin(pb, pwpolicy) && (res = slapi_acl_check_mods(pb, e, mods, &errtxt)) != LDAP_SUCCESS){
+			if (operation_is_flag_set(operation,OP_FLAG_ACTION_LOG_ACCESS)){
+				if (proxydn){
 					proxystr = slapi_ch_smprintf(" authzid=\"%s\"", proxydn);
 				}
 
@@ -1243,16 +1241,23 @@ static int op_shared_allow_pw_change (Slapi_PBlock *pb, LDAPMod *mod, char **old
 
 			/* Write access is denied to userPassword by ACIs */
 			if ( pwresponse_req == 1 ) {
-                               	slapi_pwpolicy_make_response_control ( pb, -1, -1,
-						LDAP_PWPOLICY_PWDMODNOTALLOWED );
-                       	}
-
-                       	send_ldap_result(pb, res, NULL, errtxt, 0, NULL);
+				slapi_pwpolicy_make_response_control ( pb, -1, -1, LDAP_PWPOLICY_PWDMODNOTALLOWED );
+            }
+            send_ldap_result(pb, res, NULL, errtxt, 0, NULL);
 			slapi_ch_free_string(&errtxt);
 			rc = -1;
 			goto done;
 		}
 
+		/*
+		 * If this mod is being performed by a password administrator/rootDN,
+		 * just return success.
+		 */
+		if(pw_is_pwp_admin(pb, pwpolicy)){
+			rc = 1;
+			goto done;
+		}
+
 		/* Check if password policy allows users to change their passwords.*/
 		if (!pb->pb_op->o_isroot && slapi_sdn_compare(&sdn, &pb->pb_op->o_sdn)==0 &&
 			!pb->pb_conn->c_needpw && !pwpolicy->pw_change)
diff --git a/ldap/servers/slapd/pblock.c b/ldap/servers/slapd/pblock.c
index 9b59e7b..849c20e 100644
--- a/ldap/servers/slapd/pblock.c
+++ b/ldap/servers/slapd/pblock.c
@@ -1803,6 +1803,12 @@ slapi_pblock_get( Slapi_PBlock *pblock, int arg, void *value )
 		}
 		break;
 
+	case SLAPI_REQUESTOR_SDN:
+		if(pblock->pb_op != NULL){
+			(*(Slapi_DN **)value) = &pblock->pb_op->o_sdn;
+		}
+		break;
+
 	case SLAPI_OPERATION_AUTHTYPE:
 		if (pblock->pb_op != NULL)
 		{
diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h
index 954cfd2..8c9f0fb 100644
--- a/ldap/servers/slapd/proto-slap.h
+++ b/ldap/servers/slapd/proto-slap.h
@@ -347,6 +347,7 @@ int config_set_return_exact_case(const char *attrname,  char *value, char *error
 int config_set_result_tweak(const char *attrname,  char *value, char *errorbuf, int apply );
 int config_set_referral_mode(const char *attrname, char *url, char *errorbuf, int apply);
 int config_set_conntablesize(const char *attrname, char *url, char *errorbuf, int apply);
+int config_set_pw_admin_dn( const char *attrname, char *value, char *errorbuf, int apply );
 int config_set_maxbersize(const char *attrname,  char *value, char *errorbuf, int apply );
 int config_set_maxsasliosize(const char *attrname,  char *value, char *errorbuf, int apply );
 int config_set_versionstring(const char *attrname,  char *versionstring, char *errorbuf, int apply );
diff --git a/ldap/servers/slapd/pw.c b/ldap/servers/slapd/pw.c
index 6f3d436..cf400a1 100644
--- a/ldap/servers/slapd/pw.c
+++ b/ldap/servers/slapd/pw.c
@@ -73,6 +73,8 @@ static int update_pw_history( Slapi_PBlock *pb, const Slapi_DN *sdn, char *old_p
 static int check_trivial_words (Slapi_PBlock *, Slapi_Entry *, Slapi_Value **,
 		char *attrtype, int toklen, Slapi_Mods *smods );
 static int pw_boolean_str2value (const char *str);
+static void pw_get_admin_users(passwdPolicy *pwp);
+
 /* static LDAPMod* pw_malloc_mod (char* name, char* value, int mod_op); */
 
 
@@ -588,7 +590,7 @@ update_pw_info ( Slapi_PBlock *pb , char *old_pw) {
 	char *timestr;
 	time_t 		pw_exp_date;
 	time_t      cur_time;
-	const char 	*dn;
+	const char 	*target_dn, *bind_dn;
 	Slapi_DN *sdn = NULL;
 	passwdPolicy *pwpolicy = NULL;
 	int internal_op = 0;
@@ -598,10 +600,11 @@ update_pw_info ( Slapi_PBlock *pb , char *old_pw) {
 	internal_op = slapi_operation_is_flag_set(operation, SLAPI_OP_FLAG_INTERNAL);
 
 	cur_time = current_time();
+	slapi_pblock_get( pb, SLAPI_REQUESTOR_NDN, &bind_dn);
 	slapi_pblock_get( pb, SLAPI_TARGET_SDN, &sdn );
-	dn = slapi_sdn_get_dn(sdn);
+	target_dn = slapi_sdn_get_dn(sdn);
 	
-	pwpolicy = new_passwdPolicy(pb, dn);
+	pwpolicy = new_passwdPolicy(pb, target_dn);
 
 	/* update passwordHistory */
 	if ( old_pw != NULL && pwpolicy->pw_history == 1 ) {
@@ -643,7 +646,8 @@ update_pw_info ( Slapi_PBlock *pb , char *old_pw) {
 	 * we stuff the actual user who initiated the password change in pb_conn.  We check
 	 * for this special case to ensure we reset the expiration date properly. */
 	if ((internal_op && pwpolicy->pw_must_change && (!pb->pb_conn || slapi_dn_isroot(pb->pb_conn->c_dn))) ||
-		(!internal_op && pwpolicy->pw_must_change && (pb->pb_requestor_isroot == 1))) {
+	    (!internal_op && pwpolicy->pw_must_change &&
+	    ((target_dn && bind_dn && strcasecmp(target_dn, bind_dn)) && pw_is_pwp_admin(pb, pwpolicy)))){
 		pw_exp_date = NO_TIME;
 	} else if ( pwpolicy->pw_exp == 1 ) {
 		Slapi_Entry *pse = NULL;
@@ -836,7 +840,7 @@ check_pw_syntax_ext ( Slapi_PBlock *pb, const Slapi_DN *sdn, Slapi_Value **vals,
 			 * case for the password modify extended operation. */
 			if (slapi_is_encoded((char *)slapi_value_get_string(vals[i]))) {
 				if ((!is_replication && ((internal_op && pb->pb_conn && !slapi_dn_isroot(pb->pb_conn->c_dn)) ||
-					(!internal_op && !pb->pb_requestor_isroot)))) {
+					(!internal_op && !pw_is_pwp_admin(pb, pwpolicy))))) {
 					PR_snprintf( errormsg, BUFSIZ,
 						"invalid password syntax - passwords with storage scheme are not allowed");
 					if ( pwresponse_req == 1 ) {
@@ -1527,6 +1531,97 @@ pw_add_allowchange_aci(Slapi_Entry *e, int pw_prohibit_change) {
 	slapi_ch_free((void **) &aci_pw);
 }
 
+int
+pw_is_pwp_admin(Slapi_PBlock *pb, passwdPolicy *pwp)
+{
+	Slapi_DN *bind_sdn = NULL;
+	int i;
+
+	/* first check if it's root */
+	if(pb->pb_requestor_isroot){
+			return 1;
+	}
+	/* now check if it's a Password Policy Administrator */
+	slapi_pblock_get(pb, SLAPI_REQUESTOR_SDN, &bind_sdn);
+	if(bind_sdn == NULL){
+			return 0;
+	}
+	for(i = 0; pwp->pw_admin_user && pwp->pw_admin_user[i]; i++){
+			if(slapi_sdn_compare(bind_sdn, pwp->pw_admin_user[i]) == 0){
+					return 1;
+			}
+	}
+
+	return 0;
+}
+
+static void
+pw_get_admin_users(passwdPolicy *pwp)
+{
+	Slapi_PBlock *pb = NULL;
+	const Slapi_DN *sdn = pwp->pw_admin;
+	char **uniquemember_vals = NULL;
+	char **member_vals = NULL;
+	const char *binddn = slapi_sdn_get_dn(sdn);
+	int uniquemember_count = 0;
+	int member_count = 0;
+	int nentries = 0;
+	int count = 0;
+	int res;
+	int i;
+
+	if(binddn == NULL){
+		return;
+	}
+	pb = slapi_pblock_new();
+	/*
+	 *  Check if the DN exists and has "group" objectclasses
+	 */
+	slapi_search_internal_set_pb(pb, binddn, LDAP_SCOPE_BASE,"(|(objectclass=groupofuniquenames)(objectclass=groupofnames))",
+		NULL, 0, NULL, NULL, (void *) plugin_get_default_component_id(), 0);
+	slapi_search_internal_pb(pb);
+	slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &res);
+	if (res != LDAP_SUCCESS) {
+		slapi_pblock_destroy(pb);
+		LDAPDebug(LDAP_DEBUG_ANY, "pw_get_admin_users: search failed for %s: error %d - Password Policy Administrators can not be set\n",
+				slapi_sdn_get_dn(sdn), res, 0);
+		return;
+	}
+	/*
+	 *  Ok, we know we have a valid DN, and nentries will tell us if its a group or a user
+	 */
+	slapi_pblock_get(pb, SLAPI_NENTRIES, &nentries);
+	if ( nentries > 0 ){
+		/*
+		 *  It's a group DN, gather all the members
+		 */
+		Slapi_Entry **entries = NULL;
+
+		slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
+		uniquemember_vals = slapi_entry_attr_get_charray_ext(entries[0], "uniquemember", &uniquemember_count);
+		member_vals = slapi_entry_attr_get_charray_ext(entries[0], "member", &member_count);
+		pwp->pw_admin_user = (Slapi_DN **)slapi_ch_calloc((uniquemember_count + member_count + 1), sizeof(Slapi_DN *));
+		if(uniquemember_count > 0){
+			for(i = 0; i < uniquemember_count; i++){
+				pwp->pw_admin_user[count++] = slapi_sdn_new_dn_passin(uniquemember_vals[i]);
+			}
+		}
+		if(member_count > 0){
+			for(i = 0; i < member_count; i++){
+				pwp->pw_admin_user[count++] = slapi_sdn_new_dn_passin(member_vals[i]);
+			}
+		}
+		slapi_ch_free((void**)&uniquemember_vals);
+		slapi_ch_free((void**)&member_vals);
+	} else {
+		/* It's a single user */
+		pwp->pw_admin_user = (Slapi_DN **)slapi_ch_calloc(2, sizeof(Slapi_DN *));
+		pwp->pw_admin_user[0] = slapi_sdn_dup(sdn);
+	}
+	slapi_free_search_results_internal(pb);
+	slapi_pblock_destroy(pb);
+}
+
 /* This function creates a passwdPolicy structure, loads it from either
  * slapdFrontendconfig or the entry pointed by pwdpolicysubentry and
  * returns the structure.
@@ -1831,6 +1926,13 @@ new_passwdPolicy(Slapi_PBlock *pb, const char *dn)
 						pw_boolean_str2value(slapi_value_get_string(*sval));
 					}
 				}
+				else
+				if (!strcasecmp(attr_name, "passwordAdminDN")) {
+						if ((sval = attr_get_present_values(attr))) {
+								pwdpolicy->pw_admin = slapi_sdn_new_dn_byval(slapi_value_get_string(*sval));
+								pw_get_admin_users(pwdpolicy);
+						}
+				}
 			} /* end of for() loop */
 			if (pw_entry) {
 				slapi_entry_free(pw_entry);
@@ -1851,6 +1953,8 @@ done:
 	*pwdscheme = *slapdFrontendConfig->pw_storagescheme;
 	pwdscheme->pws_name = strdup( slapdFrontendConfig->pw_storagescheme->pws_name );
 	pwdpolicy->pw_storagescheme = pwdscheme;
+	pwdpolicy->pw_admin = slapi_sdn_dup(slapdFrontendConfig->pw_policy.pw_admin);
+	pw_get_admin_users(pwdpolicy);
 
 	return pwdpolicy;
 
@@ -1861,6 +1965,15 @@ delete_passwdPolicy( passwdPolicy **pwpolicy)
 {
 	if (pwpolicy && *pwpolicy) {
 		free_pw_scheme( (*(*pwpolicy)).pw_storagescheme );
+		slapi_sdn_free(&(*(*pwpolicy)).pw_admin);
+		if((*(*pwpolicy)).pw_admin_user){
+				int i = 0;
+				while((*(*pwpolicy)).pw_admin_user[i]){
+						slapi_sdn_free(&(*(*pwpolicy)).pw_admin_user[i]);
+						i++;
+				}
+				slapi_ch_free((void **)&(*(*pwpolicy)).pw_admin_user);
+		}
 		slapi_ch_free((void **)pwpolicy);
 	}
 }
diff --git a/ldap/servers/slapd/pw.h b/ldap/servers/slapd/pw.h
index a470fdd..9bb5cc7 100644
--- a/ldap/servers/slapd/pw.h
+++ b/ldap/servers/slapd/pw.h
@@ -86,6 +86,7 @@ int pw_encodevals_ext( Slapi_PBlock *pb, const Slapi_DN *sdn, Slapi_Value **vals
 int checkPrefix(char *cipher, char *schemaName, char **encrypt);
 struct passwordpolicyarray *new_passwdPolicy ( Slapi_PBlock *pb, const char *dn );
 void delete_passwdPolicy( struct passwordpolicyarray **pwpolicy);
+int pw_is_pwp_admin(Slapi_PBlock *pb, struct passwordpolicyarray *pwp);
 
 /* function for checking the values of fine grained password policy attributes */
 int check_pw_duration_value( const char *attr_name, char *value, long minval, long maxval, char *errorbuf );
diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h
index ca30d2a..33cfeb4 100644
--- a/ldap/servers/slapd/slap.h
+++ b/ldap/servers/slapd/slap.h
@@ -2018,6 +2018,7 @@ typedef struct _slapdEntryPoints {
 #define CONFIG_SASL_MAXBUFSIZE "nsslapd-sasl-max-buffer-size"
 #define CONFIG_LISTEN_BACKLOG_SIZE	"nsslapd-listen-backlog-size"
 #define CONFIG_IGNORE_TIME_SKEW "nsslapd-ignore-time-skew"
+#define CONFIG_PW_ADMIN_DN_ATTRIBUTE "passwordAdminDN"
 
 /* getenv alternative */
 #define CONFIG_MALLOC_MXFAST "nsslapd-malloc-mxfast"
@@ -2081,6 +2082,8 @@ typedef struct passwordpolicyarray {
   int pw_is_legacy;
   int pw_track_update_time;
   struct pw_scheme *pw_storagescheme;
+  Slapi_DN *pw_admin;
+  Slapi_DN **pw_admin_user;
 } passwdPolicy;
 
 typedef struct _slapdFrontendConfig {
diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h
index 9e85dc0..c13a1a8 100644
--- a/ldap/servers/slapd/slapi-plugin.h
+++ b/ldap/servers/slapd/slapi-plugin.h
@@ -1618,6 +1618,38 @@ int slapi_entry_attr_delete( Slapi_Entry *e, const char *type );
  * be \c NULL terminated so that they can be used safely in a string context. If there
  * are no values, \c NULL will be returned. Because the array is \c NULL terminated,
  * the usage should be similar to the sample shown below:
+ *
+ * \code
+ *     char **ary = slapi_entry_attr_get_charray(e, someattr);
+ *     int ii;
+ *     for (ii = 0; ary && ary[ii]; ++ii) {
+ *        char *strval = ary[ii];
+ *        ...
+ *     }
+ *     slapi_ch_array_free(ary);
+ * \endcode
+ *
+ * \param e Entry from which you want to get the values.
+ * \param type Attribute type from which you want to get the values.
+ * \param numVals The number of attribute values will be stored in this variable.
+ * \return A copy of all the values of the attribute.
+ * \return \c NULL if the entry does not contain the attribute or if the attribute
+ *         has no values.
+ * \warning When you are done working with the values, free them from memory by calling
+ *          the slapi_ch_array_free() function.
+ * \see slapi_entry_attr_get_charptr()
+ */
+char **slapi_entry_attr_get_charray_ext( const Slapi_Entry* e, const char *type, int *numVals);
+
+/**
+ * Gets the values of a multi-valued attribute of an entry.
+ *
+ * This function is very similar to slapi_entry_attr_get_charptr(), except that it
+ * returns a <tt>char **</tt> array for multi-valued attributes. The array and all
+ * values are copies. Even if the attribute values are not strings, they will still
+ * be \c NULL terminated so that they can be used safely in a string context. If there
+ * are no values, \c NULL will be returned. Because the array is \c NULL terminated,
+ * the usage should be similar to the sample shown below:
  * 
  * \code
  *     char **ary = slapi_entry_attr_get_charray(e, someattr);
-- 
1.8.1.4