dirsrvtests/tickets/ticket548_test.py | 339 ++++++++++++++++++++++++++++++++++
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
8 files changed, 505 insertions(+), 55 deletions(-)
New commits:
commit cb209be7ca39b9064e7ce2c89a94715c258cd72b
Author: Noriko Hosoi <nhosoi(a)redhat.com>
Date: Mon Oct 19 15:14:18 2015 -0700
Ticket #548 - CI test: added test cases for ticket 548
Description: RFE: Allow AD password sync to update shadowLastChange
https://fedorahosted.org/389/ticket/548
Reviwed by spichugi(a)redhat.com and wibrown(a)redhat.com (Thank you, Simon and William!)
diff --git a/dirsrvtests/tickets/ticket548_test.py
b/dirsrvtests/tickets/ticket548_test.py
new file mode 100644
index 0000000..030ff4f
--- /dev/null
+++ b/dirsrvtests/tickets/ticket548_test.py
@@ -0,0 +1,339 @@
+# --- BEGIN COPYRIGHT BLOCK ---
+# Copyright (C) 2015 Red Hat, Inc.
+# All rights reserved.
+#
+# License: GPL (version 3 or any later version).
+# See LICENSE for details.
+# --- END COPYRIGHT BLOCK ---
+#
+import os
+import sys
+import time
+import ldap
+import logging
+import pytest
+from lib389 import DirSrv, Entry, tools, tasks
+from lib389.tools import DirSrvTools
+from lib389._constants import *
+from lib389.properties import *
+from lib389.tasks import *
+from lib389.utils import *
+
+log = logging.getLogger(__name__)
+
+installation_prefix = None
+
+# Assuming DEFAULT_SUFFIX is "dc=example,dc=com", otherwise it does not work...
:(
+SUBTREE_CONTAINER = 'cn=nsPwPolicyContainer,' + DEFAULT_SUFFIX
+SUBTREE_PWPDN = 'cn=nsPwPolicyEntry,' + DEFAULT_SUFFIX
+SUBTREE_PWP = 'cn=cn\3DnsPwPolicyEntry\2Cdc\3Dexample\2Cdc\3Dcom,' +
SUBTREE_CONTAINER
+SUBTREE_COS_TMPLDN = 'cn=nsPwTemplateEntry,' + DEFAULT_SUFFIX
+SUBTREE_COS_TMPL = 'cn=cn\3DnsPwTemplateEntry\2Cdc\3Dexample\2Cdc\3Dcom,' +
SUBTREE_CONTAINER
+SUBTREE_COS_DEF = 'cn=nsPwPolicy_CoS,' + DEFAULT_SUFFIX
+
+USER1_DN = 'uid=user1,' + DEFAULT_SUFFIX
+USER2_DN = 'uid=user2,' + DEFAULT_SUFFIX
+USER3_DN = 'uid=user3,' + DEFAULT_SUFFIX
+USER_PW = 'password'
+
+class TopologyStandalone(object):
+ def __init__(self, standalone):
+ standalone.open()
+ self.standalone = standalone
+
+
+(a)pytest.fixture(scope="module")
+def topology(request):
+ global installation_prefix
+ if installation_prefix:
+ args_instance[SER_DEPLOYED_DIR] = installation_prefix
+
+ # Creating standalone instance ...
+ standalone = DirSrv(verbose=False)
+ args_instance[SER_HOST] = HOST_STANDALONE
+ args_instance[SER_PORT] = PORT_STANDALONE
+ args_instance[SER_SERVERID_PROP] = SERVERID_STANDALONE
+ args_instance[SER_CREATION_SUFFIX] = DEFAULT_SUFFIX
+ args_standalone = args_instance.copy()
+ standalone.allocate(args_standalone)
+ instance_standalone = standalone.exists()
+ if instance_standalone:
+ standalone.delete()
+ standalone.create()
+ standalone.open()
+
+ # Delete each instance in the end
+ def fin():
+ standalone.delete()
+ request.addfinalizer(fin)
+
+ # Clear out the tmp dir
+ standalone.clearTmpDir(__file__)
+
+ return TopologyStandalone(standalone)
+
+
+def set_global_pwpolicy(topology):
+ log.info(" +++++ Enable global password policy +++++\n")
+ # Enable password policy
+ try:
+ topology.standalone.modify_s(DN_CONFIG, [(ldap.MOD_REPLACE,
'nsslapd-pwpolicy-local', 'on')])
+ except ldap.LDAPError as e:
+ log.error('Failed to set pwpolicy-local: error ' +
e.message['desc'])
+ assert False
+
+ log.info(" Set global password Min Age -- 1 day\n")
+ try:
+ topology.standalone.modify_s(DN_CONFIG, [(ldap.MOD_REPLACE,
'passwordMinAge', '86400')])
+ except ldap.LDAPError as e:
+ log.error('Failed to set passwordMinAge: error ' +
e.message['desc'])
+ assert False
+
+ log.info(" Set global password Max Age -- 10 days\n")
+ try:
+ topology.standalone.modify_s(DN_CONFIG, [(ldap.MOD_REPLACE,
'passwordMaxAge', '864000')])
+ except ldap.LDAPError as e:
+ log.error('Failed to set passwordMaxAge: error ' +
e.message['desc'])
+ assert False
+
+ log.info(" Set global password Warning -- 3 days\n")
+ try:
+ topology.standalone.modify_s(DN_CONFIG, [(ldap.MOD_REPLACE,
'passwordWarning', '259200')])
+ except ldap.LDAPError as e:
+ log.error('Failed to set passwordWarning: error ' +
e.message['desc'])
+ assert False
+
+
+def set_subtree_pwpolicy(topology):
+ log.info(" +++++ Enable subtree level password policy +++++\n")
+ log.info(" Add the container")
+ try:
+ topology.standalone.add_s(Entry((SUBTREE_CONTAINER, {'objectclass':
'top nsContainer'.split(),
+ 'cn':
'nsPwPolicyContainer'})))
+ except ldap.LDAPError as e:
+ log.error('Failed to add subtree container: error ' +
e.message['desc'])
+ assert False
+
+ log.info(" Add the password policy subentry {passwordMustChange: on,
passwordMinAge: 2, passwordMaxAge: 20, passwordWarning: 6}")
+ try:
+ topology.standalone.add_s(Entry((SUBTREE_PWP, {'objectclass': 'top
ldapsubentry passwordpolicy'.split(),
+ 'cn': SUBTREE_PWPDN,
+ 'passwordMustChange':
'on',
+ 'passwordExp':
'on',
+ 'passwordMinAge':
'172800',
+ 'passwordMaxAge':
'1728000',
+ 'passwordWarning':
'518400',
+ 'passwordChange':
'on',
+ 'passwordStorageScheme':
'clear'})))
+ except ldap.LDAPError as e:
+ log.error('Failed to add passwordpolicy: error ' +
e.message['desc'])
+ assert False
+
+ log.info(" Add the COS template")
+ try:
+ topology.standalone.add_s(Entry((SUBTREE_COS_TMPL, {'objectclass':
'top ldapsubentry costemplate extensibleObject'.split(),
+ 'cn': SUBTREE_PWPDN,
+ 'cosPriority':
'1',
+ 'cn':
SUBTREE_COS_TMPLDN,
+ 'pwdpolicysubentry':
SUBTREE_PWP})))
+ except ldap.LDAPError as e:
+ log.error('Failed to add COS template: error ' +
e.message['desc'])
+ assert False
+
+ log.info(" Add the COS definition")
+ try:
+ topology.standalone.add_s(Entry((SUBTREE_COS_DEF, {'objectclass':
'top ldapsubentry cosSuperDefinition cosPointerDefinition'.split(),
+ 'cn': SUBTREE_PWPDN,
+ 'costemplatedn':
SUBTREE_COS_TMPL,
+ 'cosAttribute':
'pwdpolicysubentry default operational-default'})))
+ except ldap.LDAPError as e:
+ log.error('Failed to add COS def: error ' + e.message['desc'])
+ assert False
+
+ time.sleep(1)
+
+
+def update_passwd(topology, user, passwd, newpasswd):
+ log.info(" Bind as {%s,%s}" % (user, passwd))
+ topology.standalone.simple_bind_s(user, passwd)
+ try:
+ topology.standalone.modify_s(user, [(ldap.MOD_REPLACE, 'userpassword',
newpasswd)])
+ except ldap.LDAPError as e:
+ log.fatal('test_ticket548: Failed to update the password ' + cpw + '
of user ' + user + ': error ' + e.message['desc'])
+ assert False
+
+ time.sleep(1)
+
+
+def check_shadow_attr_value(entry, attr_type, expected, dn):
+ if entry.hasAttr(attr_type):
+ actual = entry.getValue(attr_type)
+ if int(actual) == expected:
+ log.info('%s of entry %s has expected value %s' % (attr_type, dn,
actual))
+ assert True
+ else:
+ log.fatal('%s %s of entry %s does not have expected value %s' %
(attr_type, actual, dn, expected))
+ assert False
+ else:
+ log.fatal('entry %s does not have %s attr' % (dn, attr_type))
+ assert False
+
+
+def test_ticket548_test_with_no_policy(topology):
+ """
+ Check shadowAccount under no password policy
+ """
+ log.info("Case 1. No password policy")
+
+ log.info("Bind as %s" % DN_DM)
+ topology.standalone.simple_bind_s(DN_DM, PASSWORD)
+
+ log.info('Add an entry' + USER1_DN)
+ try:
+ topology.standalone.add_s(Entry((USER1_DN, {'objectclass': "top
person organizationalPerson inetOrgPerson shadowAccount".split(),
+ 'sn': '1',
+ 'cn': 'user 1',
+ 'uid': 'user1',
+ 'givenname': 'user',
+ 'mail': 'user1(a)example.com',
+ 'userpassword': USER_PW})))
+ except ldap.LDAPError as e:
+ log.fatal('test_ticket548: Failed to add user' + USER1_DN + ': error
' + e.message['desc'])
+ assert False
+
+ edate = int(time.time() / (60 * 60 * 24))
+ log.info('Search entry %s' % USER1_DN)
+
+ log.info("Bind as %s" % USER1_DN)
+ topology.standalone.simple_bind_s(USER1_DN, USER_PW)
+ entry = topology.standalone.getEntry(USER1_DN, ldap.SCOPE_BASE,
"(objectclass=*)", ['shadowLastChange'])
+ check_shadow_attr_value(entry, 'shadowLastChange', edate, USER1_DN)
+
+ log.info("Check shadowAccount with no policy was successfully verified.")
+
+
+def test_ticket548_test_global_policy(topology):
+ """
+ Check shadowAccount with global password policy
+ """
+
+ log.info("Case 2. Check shadowAccount with global password policy")
+
+ log.info("Bind as %s" % DN_DM)
+ topology.standalone.simple_bind_s(DN_DM, PASSWORD)
+
+ set_global_pwpolicy(topology)
+
+ log.info('Add an entry' + USER2_DN)
+ try:
+ topology.standalone.add_s(Entry((USER2_DN, {'objectclass': "top
person organizationalPerson inetOrgPerson shadowAccount".split(),
+ 'sn': '2',
+ 'cn': 'user 2',
+ 'uid': 'user2',
+ 'givenname': 'user',
+ 'mail': 'user2(a)example.com',
+ 'userpassword': USER_PW})))
+ except ldap.LDAPError as e:
+ log.fatal('test_ticket548: Failed to add user' + USER2_DN + ': error
' + e.message['desc'])
+ assert False
+
+ log.info("Bind as %s" % USER2_DN)
+ topology.standalone.simple_bind_s(USER2_DN, USER_PW)
+
+ edate = int(time.time() / (60 * 60 * 24))
+ log.info('Search entry %s' % USER2_DN)
+ entry = topology.standalone.getEntry(USER2_DN, ldap.SCOPE_BASE,
"(objectclass=*)")
+ check_shadow_attr_value(entry, 'shadowLastChange', edate, USER2_DN)
+
+ # passwordMinAge -- 1 day
+ check_shadow_attr_value(entry, 'shadowMin', 1, USER2_DN)
+
+ # passwordMaxAge -- 10 days
+ check_shadow_attr_value(entry, 'shadowMax', 10, USER2_DN)
+
+ # passwordWarning -- 3 days
+ check_shadow_attr_value(entry, 'shadowWarning', 3, USER2_DN)
+
+ log.info("Check shadowAccount with global policy was successfully
verified.")
+
+
+def test_ticket548_test_subtree_policy(topology):
+ """
+ Check shadowAccount with subtree level password policy
+ """
+
+ log.info("Case 3. Check shadowAccount with subtree level password
policy")
+
+ log.info("Bind as %s" % DN_DM)
+ topology.standalone.simple_bind_s(DN_DM, PASSWORD)
+
+ set_subtree_pwpolicy(topology)
+
+ log.info('Add an entry' + USER3_DN)
+ try:
+ topology.standalone.add_s(Entry((USER3_DN, {'objectclass': "top
person organizationalPerson inetOrgPerson shadowAccount".split(),
+ 'sn': '3',
+ 'cn': 'user 3',
+ 'uid': 'user3',
+ 'givenname': 'user',
+ 'mail': 'user3(a)example.com',
+ 'userpassword': USER_PW})))
+ except ldap.LDAPError as e:
+ log.fatal('test_ticket548: Failed to add user' + USER3_DN + ': error
' + e.message['desc'])
+ assert False
+
+ log.info('Search entry %s' % USER3_DN)
+ entry0 = topology.standalone.getEntry(USER3_DN, ldap.SCOPE_BASE,
"(objectclass=*)")
+
+ log.info('Expecting shadowLastChange 0 since passwordMustChange is on')
+ check_shadow_attr_value(entry0, 'shadowLastChange', 0, USER3_DN)
+
+ # passwordMinAge -- 2 day
+ check_shadow_attr_value(entry0, 'shadowMin', 2, USER3_DN)
+
+ # passwordMaxAge -- 20 days
+ check_shadow_attr_value(entry0, 'shadowMax', 20, USER3_DN)
+
+ # passwordWarning -- 6 days
+ check_shadow_attr_value(entry0, 'shadowWarning', 6, USER3_DN)
+
+ log.info("Bind as %s" % USER3_DN)
+ topology.standalone.simple_bind_s(USER3_DN, USER_PW)
+
+ log.info('Search entry %s' % USER3_DN)
+ try:
+ entry1 = topology.standalone.getEntry(USER3_DN, ldap.SCOPE_BASE,
"(objectclass=*)")
+ except ldap.UNWILLING_TO_PERFORM:
+ log.info('test_ticket548: Search by' + USER3_DN + ' failed by
UNWILLING_TO_PERFORM as expected')
+ except ldap.LDAPError as e:
+ log.fatal('test_ticket548: Failed to serch user' + USER3_DN + ' by
self: error ' + e.message['desc'])
+ assert False
+
+ log.info("Bind as %s and updating the password with a new one" % USER3_DN)
+ topology.standalone.simple_bind_s(USER3_DN, USER_PW)
+
+ newpasswd = USER_PW + '0'
+ update_passwd(topology, USER3_DN, USER_PW, newpasswd)
+
+ log.info("Re-bind as %s with new password" % USER3_DN)
+ topology.standalone.simple_bind_s(USER3_DN, newpasswd)
+
+ try:
+ entry2 = topology.standalone.getEntry(USER3_DN, ldap.SCOPE_BASE,
"(objectclass=*)")
+ except ldap.LDAPError as e:
+ log.fatal('test_ticket548: Failed to serch user' + USER3_DN + ' by
self: error ' + e.message['desc'])
+ assert False
+
+ edate = int(time.time() / (60 * 60 * 24))
+
+ log.info('Expecting shadowLastChange %d once userPassword is updated',
edate)
+ check_shadow_attr_value(entry2, 'shadowLastChange', edate, USER3_DN)
+
+ log.info("Check shadowAccount with subtree level policy was successfully
verified.")
+
+
+if __name__ == '__main__':
+ # Run isolated
+ # -s for DEBUG mode
+ CURRENT_FILE = os.path.realpath(__file__)
+ pytest.main("-s %s" % CURRENT_FILE)
commit 17f3624c19929ffa1d37a567b7a889fd397cca59
Author: Noriko Hosoi <nhosoi(a)redhat.com>
Date: Mon Oct 26 22:11:43 2015 -0700
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
https://fedorahosted.org/389/ticket/548
Reviewed by mreynolds(a)redhat.com (Thank you, Mark!!)
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(×tr);
+ }
+
/* 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 **)×tr);
+ slapi_ch_free_string(×tr);
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 **)×tring);
+ slapi_ch_free_string(×tring);
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 **)×tring);
+ slapi_ch_free_string(×tring);
}
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 **)×tring);
+ slapi_ch_free_string(×tring);
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_ */