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@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@redhat.com and wibrown@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 + + +@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@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@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@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@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@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_ */
389-commits@lists.fedoraproject.org