From b80b688459204d2513a2864c60c70c3312d44465 Mon Sep 17 00:00:00 2001
From: Noriko Hosoi <nhosoi@redhat.com>
Date: Mon, 26 Oct 2015 22:11:43 -0700
Subject: [PATCH] Ticket #548 - RFE: Allow AD password sync to update
 shadowLastChange

Shadow Account Support Design - http://www.port389.org/docs/389ds/design/shadow-account-support.html
---
 ldap/servers/slapd/libglobs.c   |   2 +-
 ldap/servers/slapd/log.c        |   2 +-
 ldap/servers/slapd/opshared.c   |   2 +
 ldap/servers/slapd/proto-slap.h |   1 +
 ldap/servers/slapd/pw.c         | 167 ++++++++++++++++++++++++++++++++++------
 ldap/servers/slapd/pw_mgmt.c    |  45 ++++-------
 ldap/servers/slapd/slap.h       |   2 +
 7 files changed, 166 insertions(+), 55 deletions(-)

diff --git a/ldap/servers/slapd/libglobs.c b/ldap/servers/slapd/libglobs.c
index a3c4243..fde3885 100644
--- a/ldap/servers/slapd/libglobs.c
+++ b/ldap/servers/slapd/libglobs.c
@@ -1447,7 +1447,7 @@ FrontendConfig_init () {
   cfg->pw_policy.pw_mintokenlength = 3;
   cfg->pw_policy.pw_maxage = 8640000; /* 100 days     */
   cfg->pw_policy.pw_minage = 0;
-  cfg->pw_policy.pw_warning = 86400; /* 1 day        */
+  cfg->pw_policy.pw_warning = _SEC_PER_DAY; /* 1 day */
   init_pw_history = cfg->pw_policy.pw_history = LDAP_OFF;
   cfg->pw_policy.pw_inhistory = 6;
   init_pw_lockout = cfg->pw_policy.pw_lockout = LDAP_OFF;
diff --git a/ldap/servers/slapd/log.c b/ldap/servers/slapd/log.c
index 2ec90de..78701c4 100644
--- a/ldap/servers/slapd/log.c
+++ b/ldap/servers/slapd/log.c
@@ -200,7 +200,7 @@ void g_log_init(int log_enabled)
 	loginfo.log_access_rotationsyncclock = -1;
 	loginfo.log_access_rotationtime = 1;                  /* default: 1 */
 	loginfo.log_access_rotationunit = LOG_UNIT_DAYS;      /* default: day */
-	loginfo.log_access_rotationtime_secs = 86400;         /* default: 1 day */
+	loginfo.log_access_rotationtime_secs = _SEC_PER_DAY;  /* default: 1 day */
 	loginfo.log_access_maxdiskspace =  -1;
 	loginfo.log_access_minfreespace =  -1;
 	loginfo.log_access_exptime =  -1;                     /* default: -1 */
diff --git a/ldap/servers/slapd/opshared.c b/ldap/servers/slapd/opshared.c
index dcdbb04..4fe7969 100644
--- a/ldap/servers/slapd/opshared.c
+++ b/ldap/servers/slapd/opshared.c
@@ -1439,6 +1439,8 @@ iterate(Slapi_PBlock *pb, Slapi_Backend *be, int send_result,
                 done = 1;
                 continue;
             }
+            /* Adding shadow password attrs. */
+            add_shadow_ext_password_attrs(pb, e);
             if (process_entry(pb, e, send_result)) 
             {
                 /* shouldn't  send this entry */
diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h
index b10c1eb..c49f1d1 100644
--- a/ldap/servers/slapd/proto-slap.h
+++ b/ldap/servers/slapd/proto-slap.h
@@ -917,6 +917,7 @@ void add_password_attrs( Slapi_PBlock *pb, Operation *op, Slapi_Entry *e );
 void mod_allowchange_aci(char *val);
 void pw_mod_allowchange_aci(int pw_prohibit_change);
 void pw_add_allowchange_aci(Slapi_Entry *e, int pw_prohibit_change);
+void add_shadow_ext_password_attrs(Slapi_PBlock *pb, Slapi_Entry *e);
 
 /*
  * pw_retry.c
diff --git a/ldap/servers/slapd/pw.c b/ldap/servers/slapd/pw.c
index 4e222d7..3985c2b 100644
--- a/ldap/servers/slapd/pw.c
+++ b/ldap/servers/slapd/pw.c
@@ -611,6 +611,13 @@ update_pw_info ( Slapi_PBlock *pb , char *old_pw)
 	cur_time = current_time();
 	slapi_mods_init(&smods, 0);
 	
+	if (slapi_entry_attr_hasvalue(e, SLAPI_ATTR_OBJECTCLASS, "shadowAccount")) {
+		time_t ctime = cur_time / _SEC_PER_DAY;
+		timestr = slapi_ch_smprintf("%ld", ctime);
+		slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "shadowLastChange", timestr);
+		slapi_ch_free_string(&timestr);
+	}
+
 	/* update passwordHistory */
 	if ( old_pw != NULL && pwpolicy->pw_history == 1 ) {
 		(void)update_pw_history(pb, sdn, old_pw);
@@ -699,7 +706,7 @@ update_pw_info ( Slapi_PBlock *pb , char *old_pw)
 
 	timestr = format_genTime ( pw_exp_date );
 	slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordExpirationTime", timestr);
-	slapi_ch_free((void **)&timestr);
+	slapi_ch_free_string(&timestr);
 	
 	slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordExpWarned", "0");
 
@@ -722,8 +729,7 @@ check_pw_minage ( Slapi_PBlock *pb, const Slapi_DN *sdn, struct berval **vals)
 	pwpolicy = new_passwdPolicy(pb, dn);
 	slapi_pblock_get ( pb, SLAPI_PWPOLICY, &pwresponse_req );
 
-	if ( !pb->pb_op->o_isroot && 
-		pwpolicy->pw_minage != 0 ) {
+	if (!pb->pb_op->o_isroot && !pwpolicy->pw_minage) {
 
 		Slapi_Entry     *e;
 		char *passwordAllowChangeTime;
@@ -753,9 +759,7 @@ check_pw_minage ( Slapi_PBlock *pb, const Slapi_DN *sdn, struct berval **vals)
 					slapi_pwpolicy_make_response_control ( pb, -1, -1,
 							LDAP_PWPOLICY_PWDTOOYOUNG );
 				}
-				pw_send_ldap_result ( pb,
-                        LDAP_CONSTRAINT_VIOLATION, NULL,
-                        "within password minimum age", 0, NULL );
+				pw_send_ldap_result(pb, LDAP_CONSTRAINT_VIOLATION, NULL, "within password minimum age", 0, NULL);
 				slapi_entry_free( e );
 				slapi_ch_free((void **) &cur_time_str );
 				return ( 1 );
@@ -1379,17 +1383,23 @@ add_password_attrs( Slapi_PBlock *pb, Operation *op, Slapi_Entry *e )
 	const char *dn = slapi_entry_get_ndn(e);
 	int has_allowchangetime = 0, has_expirationtime = 0;
 	time_t existing_exptime = 0;
+	time_t exptime = 0;
+	int isShadowAccount = 0;
+	int has_shadowLastChange = 0;
 
-	LDAPDebug( LDAP_DEBUG_TRACE, "add_password_attrs\n", 0, 0, 0 );
+	LDAPDebug0Args(LDAP_DEBUG_TRACE, "add_password_attrs\n");
 
 	bvals[0] = &bv;
 	bvals[1] = NULL;
-		
+
+	if (slapi_entry_attr_hasvalue(e, SLAPI_ATTR_OBJECTCLASS, "shadowAccount")) {
+		isShadowAccount = 1;
+	}
+
 	/* If passwordexpirationtime is specified by the user, don't 
 	   try to assign the initial value */
-	for ( a = &e->e_attrs; *a != NULL; a = next ) {
-		if ( strcasecmp( (*a)->a_type, 
-			"passwordexpirationtime" ) == 0) {
+	for (a = &e->e_attrs; a && *a; a = next) {
+		if (!strcasecmp((*a)->a_type, "passwordexpirationtime")) {
 			Slapi_Value *sval;
 			if (slapi_attr_first_value(*a, &sval) == 0) {
 				const struct berval *bv = slapi_value_get_berval(sval);
@@ -1397,32 +1407,44 @@ add_password_attrs( Slapi_PBlock *pb, Operation *op, Slapi_Entry *e )
 			}
 			has_expirationtime = 1;
 			
-		} else if ( strcasecmp( (*a)->a_type,
-			"passwordallowchangetime" ) == 0) {
+		} else if (!strcasecmp((*a)->a_type, "passwordallowchangetime")) {
 			has_allowchangetime = 1;
+		} else if (isShadowAccount && !strcasecmp((*a)->a_type, "shadowlastchange")) {
+			has_shadowLastChange = 1;
 		}
 		next = &(*a)->a_next;
 	}
 
-	if ( has_allowchangetime && has_expirationtime ) {
+	if (has_allowchangetime && has_expirationtime && has_shadowLastChange) {
 		return;
 	}
 
 	pwpolicy = new_passwdPolicy(pb, dn);
 
-	if ( !has_expirationtime && 
-		( pwpolicy->pw_exp || pwpolicy->pw_must_change ) ) {
-		if ( pwpolicy->pw_must_change) {
+	if (!has_expirationtime && (pwpolicy->pw_exp || pwpolicy->pw_must_change)) {
+		if (pwpolicy->pw_must_change) {
 			/* must change password when first time logon */
 			bv.bv_val = format_genTime ( NO_TIME );
 		} else if ( pwpolicy->pw_exp ) {
-			bv.bv_val = format_genTime ( time_plus_sec ( current_time (),
-       	                 pwpolicy->pw_maxage ) );
+			exptime = time_plus_sec(current_time(), pwpolicy->pw_maxage);
+			bv.bv_val = format_genTime(exptime);
 		}
 		bv.bv_len = strlen( bv.bv_val );
 		slapi_entry_attr_merge( e, "passwordexpirationtime", bvals );
 		slapi_ch_free_string( &bv.bv_val );
 	}
+	if (isShadowAccount && !has_shadowLastChange) {
+		if (pwpolicy->pw_must_change) {
+			/* must change password when first time logon */
+			bv.bv_val = slapi_ch_smprintf("0");
+		} else {
+			exptime = current_time() / _SEC_PER_DAY;
+			bv.bv_val = slapi_ch_smprintf("%ld", exptime);
+		}
+		bv.bv_len = strlen(bv.bv_val);
+		slapi_entry_attr_merge(e, "shadowLastChange", bvals);
+		slapi_ch_free_string(&bv.bv_val);
+	}
 
 	/* 
 	 * If the password minimum age is not 0, calculate when the password 
@@ -1434,12 +1456,11 @@ add_password_attrs( Slapi_PBlock *pb, Operation *op, Slapi_Entry *e )
 	 */
 	if ( !has_allowchangetime && pwpolicy->pw_minage != 0 && 
 		(has_expirationtime && existing_exptime > current_time()) ) {
-		bv.bv_val = format_genTime ( time_plus_sec ( current_time (),
-                        pwpolicy->pw_minage ) );
+		bv.bv_val = format_genTime ( time_plus_sec ( current_time (), pwpolicy->pw_minage ) );
 		bv.bv_len = strlen( bv.bv_val );
-	
+
 		slapi_entry_attr_merge( e, "passwordallowchangetime", bvals );
-		slapi_ch_free((void **) &bv.bv_val );
+		slapi_ch_free_string( &bv.bv_val );
 	}
 }
 
@@ -2800,3 +2821,103 @@ pw_get_ext_size(Slapi_Entry *entry, size_t *size)
     }
     return LDAP_SUCCESS;
 }
+
+void
+add_shadow_ext_password_attrs(Slapi_PBlock *pb, Slapi_Entry *e)
+{
+    const char *dn = NULL;
+    passwdPolicy *pwpolicy = NULL;
+    time_t shadowval = 0;
+    time_t exptime = 0;
+    struct berval bv;
+    struct berval *bvals[2];
+
+    if (!e) {
+        return;
+    }
+    dn = slapi_entry_get_ndn(e);
+    if (!dn) {
+        return;
+    }
+    if (!slapi_entry_attr_hasvalue(e, SLAPI_ATTR_OBJECTCLASS, "shadowAccount")) {
+        /* Not a shadowAccount; nothing to do. */
+        return;
+    }
+    if (operation_is_flag_set(pb->pb_op, OP_FLAG_INTERNAL)) {
+        /* external only */
+        return;
+    }
+    pwpolicy = new_passwdPolicy(pb, dn);
+    if (!pwpolicy) {
+        return;
+    }
+
+    LDAPDebug0Args(LDAP_DEBUG_TRACE, "--> add_shadow_password_attrs\n");
+
+    bvals[0] = &bv;
+    bvals[1] = NULL;
+
+    /* shadowMin - the minimum number of days required between password changes. */
+    if (pwpolicy->pw_minage > 0) {
+        shadowval = pwpolicy->pw_minage / _SEC_PER_DAY;
+    } else {
+        shadowval = 0;
+    }
+    bv.bv_val = slapi_ch_smprintf("%ld", shadowval);
+    bv.bv_len = strlen(bv.bv_val);
+    slapi_entry_attr_replace(e, "shadowMin", bvals);
+    slapi_ch_free_string(&bv.bv_val);
+
+    /* shadowMax - the maximum number of days for which the user password remains valid. */
+    if (pwpolicy->pw_maxage > 0) {
+        shadowval = pwpolicy->pw_maxage / _SEC_PER_DAY;
+        exptime = time_plus_sec(current_time(), pwpolicy->pw_maxage);
+    } else {
+        shadowval = 99999;
+    }
+    bv.bv_val = slapi_ch_smprintf("%ld", shadowval);
+    bv.bv_len = strlen(bv.bv_val);
+    slapi_entry_attr_replace(e, "shadowMax", bvals);
+    slapi_ch_free_string(&bv.bv_val);
+
+    /* shadowWarning - the number of days of advance warning given to the user before the user password expires. */
+    if (pwpolicy->pw_warning > 0) {
+        shadowval = pwpolicy->pw_warning / _SEC_PER_DAY;
+    } else {
+        shadowval = 0;
+    }
+    bv.bv_val = slapi_ch_smprintf("%ld", shadowval);
+    bv.bv_len = strlen(bv.bv_val);
+    slapi_entry_attr_replace(e, "shadowWarning", bvals);
+    slapi_ch_free_string(&bv.bv_val);
+
+    /* shadowExpire - the date on which the user login will be disabled. */
+    if (exptime) {
+        exptime /= _SEC_PER_DAY;
+        bv.bv_val = slapi_ch_smprintf("%ld", exptime);
+        bv.bv_len = strlen(bv.bv_val);
+        slapi_entry_attr_replace(e, "shadowExpire", bvals);
+        slapi_ch_free_string(&bv.bv_val);
+    }
+
+#if 0 /* These 2 attributes are no need (or not able) to auto-fill. */
+    /* 
+     * shadowInactive - the number of days of inactivity allowed for the user.
+     * Password Policy does not have the corresponding parameter.
+     */
+    shadowval = 0;
+    bv.bv_val = slapi_ch_smprintf("%ld", shadowval);
+    bv.bv_len = strlen(bv.bv_val);
+    slapi_entry_attr_replace(e, "shadowInactive", bvals);
+    slapi_ch_free_string(&bv.bv_val);
+
+    /* shadowFlag - not currently in use. */
+    bv.bv_val = slapi_ch_smprintf("%d", 0);
+    bv.bv_len = strlen(bv.bv_val);
+    slapi_entry_attr_replace(e, "shadowFlag", bvals);
+    slapi_ch_free_string(&bv.bv_val);
+#endif
+
+    LDAPDebug0Args(LDAP_DEBUG_TRACE, "<-- add_shadow_password_attrs\n");
+    return;
+}
diff --git a/ldap/servers/slapd/pw_mgmt.c b/ldap/servers/slapd/pw_mgmt.c
index 8f33751..a650b1c 100644
--- a/ldap/servers/slapd/pw_mgmt.c
+++ b/ldap/servers/slapd/pw_mgmt.c
@@ -27,11 +27,10 @@ int
 need_new_pw( Slapi_PBlock *pb, long *t, Slapi_Entry *e, int pwresponse_req )
 {
 	time_t 		cur_time, pw_exp_date;
- 	LDAPMod 	*mod;
 	Slapi_Mods smods;
 	double		diff_t = 0;
 	char 		*cur_time_str = NULL;
-	char *passwordExpirationTime;
+	char *passwordExpirationTime = NULL;
 	char *timestring;
 	char *dn;
 	const Slapi_DN *sdn;
@@ -67,13 +66,12 @@ need_new_pw( Slapi_PBlock *pb, long *t, Slapi_Entry *e, int pwresponse_req )
 		 * This is ok for data that has been loaded via ldif2ldbm
 		 * Set expiration time if needed,
 		 * don't do further checking and return 0 */
-		if ( pwpolicy->pw_exp == 1) {
-			pw_exp_date = time_plus_sec ( cur_time, 
-				pwpolicy->pw_maxage );
+		if (pwpolicy->pw_exp == 1) {
+			pw_exp_date = time_plus_sec(cur_time, pwpolicy->pw_maxage);
 
 			timestring = format_genTime (pw_exp_date);
 			slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordExpirationTime", timestring);
-			slapi_ch_free((void **)&timestring);
+			slapi_ch_free_string(&timestring);
 			slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordExpWarned", "0");
 			
 			pw_apply_mods(sdn, &smods);
@@ -86,7 +84,7 @@ need_new_pw( Slapi_PBlock *pb, long *t, Slapi_Entry *e, int pwresponse_req )
 
 	pw_exp_date = parse_genTime(passwordExpirationTime);
 
-	slapi_ch_free((void**)&passwordExpirationTime);
+	slapi_ch_free_string(&passwordExpirationTime);
 
 	/* Check if password has been reset */
 	if ( pw_exp_date == NO_TIME ) {
@@ -110,7 +108,7 @@ need_new_pw( Slapi_PBlock *pb, long *t, Slapi_Entry *e, int pwresponse_req )
 		pw_exp_date = NOT_FIRST_TIME;
 		timestring = format_genTime(pw_exp_date);
 		slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordExpirationTime", timestring);
-		slapi_ch_free((void **)&timestring);
+		slapi_ch_free_string(&timestring);
 	}
 
 skip:
@@ -130,11 +128,8 @@ skip:
 
 	/* check if password expired.  If so, abort bind. */
 	cur_time_str = format_genTime ( cur_time );
-	if ( pw_exp_date != NO_TIME  && 
-		 pw_exp_date != NOT_FIRST_TIME && 
-		 (diff_t = difftime ( pw_exp_date, 
-			parse_genTime ( cur_time_str ))) <= 0 ) {
-	
+	if ((pw_exp_date != NO_TIME) && (pw_exp_date != NOT_FIRST_TIME) &&
+	    (diff_t = difftime(pw_exp_date, parse_genTime(cur_time_str))) <= 0) {
 		slapi_ch_free_string(&cur_time_str); /* only need this above */
 		/* password has expired. Check the value of 
 		 * passwordGraceUserTime and compare it
@@ -144,7 +139,7 @@ skip:
 			pwdGraceUserTime++;
 			sprintf ( graceUserTime, "%d", pwdGraceUserTime );
 			slapi_mods_add_string(&smods, LDAP_MOD_REPLACE,
-				"passwordGraceUserTime", graceUserTime);	
+				"passwordGraceUserTime", graceUserTime);
 			pw_apply_mods(sdn, &smods);
 			slapi_mods_done(&smods);
 			if (pwresponse_req) {
@@ -190,40 +185,30 @@ skip:
 		pw_apply_mods(sdn, &smods);
 		slapi_mods_done(&smods);
 		return (-1);
-	} 
+	}
 	slapi_ch_free((void **) &cur_time_str );
 
 	/* check if password is going to expire within "passwordWarning" */
 	/* Note that if pw_exp_date is NO_TIME or NOT_FIRST_TIME,
 	 * we must send warning first and this changes the expiration time.
 	 * This is done just below since diff_t is 0 
-  	 */
+	 */
 	if ( diff_t <= pwpolicy->pw_warning ) {
 		int pw_exp_warned = 0;
 		
-		pw_exp_warned= slapi_entry_attr_get_int( e, "passwordExpWarned");
+		pw_exp_warned = slapi_entry_attr_get_int( e, "passwordExpWarned");
 		if ( !pw_exp_warned ){
 			/* first time send out a warning */
 			/* reset the expiration time to current + warning time 
 			 * and set passwordExpWarned to true
 			 */
 			if (pb->pb_conn->c_needpw != 1) {
-				pw_exp_date = time_plus_sec ( cur_time, 
-					pwpolicy->pw_warning );
+				pw_exp_date = time_plus_sec(cur_time, pwpolicy->pw_warning);
 			}
 			
 			timestring = format_genTime(pw_exp_date);
-			/* At this time passwordExpirationTime may already be
-			 * in the list of mods: Remove it */
-			for (mod = slapi_mods_get_first_mod(&smods); mod != NULL; 
-				 mod = slapi_mods_get_next_mod(&smods))
-			{
-				if (!strcmp(mod->mod_type, "passwordExpirationTime"))
-					slapi_mods_remove(&smods);
-			}
-
 			slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordExpirationTime", timestring);
-			slapi_ch_free((void **)&timestring);
+			slapi_ch_free_string(&timestring);
 
 			slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordExpWarned", "1");
 			
@@ -232,7 +217,7 @@ skip:
 		} else {
 			*t = (long)diff_t; /* jcm: had to cast double to long */
 		}
-			
+
 		pw_apply_mods(sdn, &smods);
 		slapi_mods_done(&smods);
 		if (pwresponse_req) {
diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h
index 823568d..f78bc46 100644
--- a/ldap/servers/slapd/slap.h
+++ b/ldap/servers/slapd/slap.h
@@ -2507,4 +2507,6 @@ extern char	*attr_dataversion;
 /* copied from replication/repl5.h */
 #define RUV_STORAGE_ENTRY_UNIQUEID "ffffffff-ffffffff-ffffffff-ffffffff"
 
+#define _SEC_PER_DAY 86400
+
 #endif /* _slap_h_ */
-- 
2.4.3

