From 3ab8a78cd27cc8d2ad7a2b322a4fe73c43a3db08 Mon Sep 17 00:00:00 2001 From: Mark Reynolds Date: Thu, 14 Sep 2017 15:47:53 -0400 Subject: [PATCH] Ticket 49327 - password expired control not sent during grace logins Bug Description: When a password is expired, but within the grace login limit, we should still send the expired control even though we allowed the bind. Fix Description: new_new_passwd() returned a variety of result codes that required the caller to set the response controls. This was hard to read and process. Instead I added all the controls inside the function, and return success or failure to the caller. https://pagure.io/389-ds-base/issue/49327 Reviewed by: gparente & tbordaz (Thanks!!) (cherry picked from commit fbd32c4e27af9f331ee3a42dec944895a6efe2ad) --- ldap/servers/plugins/replication/repl_extop.c | 5 +- ldap/servers/slapd/bind.c | 18 +- ldap/servers/slapd/proto-slap.h | 3 +- ldap/servers/slapd/pw_mgmt.c | 453 +++++++++++++------------- ldap/servers/slapd/saslbind.c | 20 +- 5 files changed, 238 insertions(+), 261 deletions(-) diff --git a/ldap/servers/plugins/replication/repl_extop.c b/ldap/servers/plugins/replication/repl_extop.c index a39d918..96ad7dd 100644 --- a/ldap/servers/plugins/replication/repl_extop.c +++ b/ldap/servers/plugins/replication/repl_extop.c @@ -1173,8 +1173,9 @@ send_response: * On the supplier, we need to close the connection so * that the RA will restart a new session in a clear state */ - slapi_log_err(SLAPI_LOG_REPL, repl_plugin_name, "multimaster_extop_StartNSDS50ReplicationRequest - " - "already acquired replica: disconnect conn=%d\n", connid); + slapi_log_err(SLAPI_LOG_REPL, repl_plugin_name, + "multimaster_extop_StartNSDS50ReplicationRequest - " + "already acquired replica: disconnect conn=%" PRIu64 "\n", connid); slapi_disconnect_server(conn); } diff --git a/ldap/servers/slapd/bind.c b/ldap/servers/slapd/bind.c index d6c7668..e6cad7f 100644 --- a/ldap/servers/slapd/bind.c +++ b/ldap/servers/slapd/bind.c @@ -673,8 +673,7 @@ do_bind( Slapi_PBlock *pb ) slapi_entry_free(referral); goto free_and_return; } else if (auto_bind || rc == SLAPI_BIND_SUCCESS || rc == SLAPI_BIND_ANONYMOUS) { - long t; - char* authtype = NULL; + char *authtype = NULL; /* rc is SLAPI_BIND_SUCCESS or SLAPI_BIND_ANONYMOUS */ if(auto_bind) { rc = SLAPI_BIND_SUCCESS; @@ -761,19 +760,8 @@ do_bind( Slapi_PBlock *pb ) slapi_ch_strdup(slapi_sdn_get_ndn(sdn)), NULL, NULL, NULL, bind_target_entry); if (!slapi_be_is_flag_set(be, SLAPI_BE_FLAG_REMOTE_DATA)) { - /* check if need new password before sending - the bind success result */ - myrc = need_new_pw(pb, &t, bind_target_entry, pw_response_requested); - switch (myrc) { - case 1: - (void)slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRED, 0); - break; - case 2: - (void)slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRING, t); - break; - default: - break; - } + /* check if need new password before sending the bind success result */ + myrc = need_new_pw(pb, bind_target_entry, pw_response_requested); } } if (auth_response_requested) { diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h index 9696ead..0ba61d7 100644 --- a/ldap/servers/slapd/proto-slap.h +++ b/ldap/servers/slapd/proto-slap.h @@ -972,7 +972,7 @@ int plugin_call_acl_verify_syntax ( Slapi_PBlock *pb, Slapi_Entry *e, char **err * pw_mgmt.c */ void pw_init( void ); -int need_new_pw( Slapi_PBlock *pb, long *t, Slapi_Entry *e, int pwresponse_req ); +int need_new_pw(Slapi_PBlock *pb, Slapi_Entry *e, int pwresponse_req); int update_pw_info( Slapi_PBlock *pb , char *old_pw ); int check_pw_syntax( Slapi_PBlock *pb, const Slapi_DN *sdn, Slapi_Value **vals, char **old_pw, Slapi_Entry *e, int mod_op ); @@ -982,7 +982,6 @@ void get_old_pw( Slapi_PBlock *pb, const Slapi_DN *sdn, char **old_pw); int check_account_lock( Slapi_PBlock *pb, Slapi_Entry * bind_target_entry, int pwresponse_req, int account_inactivation_only /*no wire/no pw policy*/); int check_pw_minage( Slapi_PBlock *pb, const Slapi_DN *sdn, struct berval **vals) ; void add_password_attrs( Slapi_PBlock *pb, Operation *op, Slapi_Entry *e ); - int add_shadow_ext_password_attrs(Slapi_PBlock *pb, Slapi_Entry **e); /* diff --git a/ldap/servers/slapd/pw_mgmt.c b/ldap/servers/slapd/pw_mgmt.c index 7252c08..b06e3f1 100644 --- a/ldap/servers/slapd/pw_mgmt.c +++ b/ldap/servers/slapd/pw_mgmt.c @@ -22,234 +22,239 @@ /* prototypes */ /****************************************************************************/ -/* need_new_pw() is called when non rootdn bind operation succeeds with authentication */ +/* + * need_new_pw() is called when non rootdn bind operation succeeds with authentication + * + * Return 0 - password is okay + * Return -1 - password is expired, abort bind + */ int -need_new_pw( Slapi_PBlock *pb, long *t, Slapi_Entry *e, int pwresponse_req ) +need_new_pw(Slapi_PBlock *pb, Slapi_Entry *e, int pwresponse_req) { - time_t cur_time, pw_exp_date; - Slapi_Mods smods; - double diff_t = 0; - char *cur_time_str = NULL; - char *passwordExpirationTime = NULL; - char *timestring; - char *dn; - const Slapi_DN *sdn; - passwdPolicy *pwpolicy = NULL; - int pwdGraceUserTime = 0; - char graceUserTime[8]; - - if (NULL == e) { - return (-1); - } - slapi_mods_init (&smods, 0); - sdn = slapi_entry_get_sdn_const( e ); - dn = slapi_entry_get_ndn( e ); - pwpolicy = new_passwdPolicy(pb, dn); - - /* after the user binds with authentication, clear the retry count */ - if ( pwpolicy->pw_lockout == 1) - { - if(slapi_entry_attr_get_int( e, "passwordRetryCount") > 0) - { - slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordRetryCount", "0"); - } - } - - cur_time = current_time(); - - /* get passwordExpirationTime attribute */ - passwordExpirationTime= slapi_entry_attr_get_charptr(e, "passwordExpirationTime"); - - if (passwordExpirationTime == NULL) - { - /* password expiration date is not set. - * 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); - - timestring = format_genTime (pw_exp_date); - slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordExpirationTime", timestring); - slapi_ch_free_string(×tring); - slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordExpWarned", "0"); - - pw_apply_mods(sdn, &smods); - } else if (pwpolicy->pw_lockout == 1) { - pw_apply_mods(sdn, &smods); - } - slapi_mods_done(&smods); - return ( 0 ); - } - - pw_exp_date = parse_genTime(passwordExpirationTime); - - slapi_ch_free_string(&passwordExpirationTime); - - /* Check if password has been reset */ - if ( pw_exp_date == NO_TIME ) { - - /* check if changing password is required */ - if ( pwpolicy->pw_must_change ) { - /* set c_needpw for this connection to be true. this client - now can only change its own password */ - pb->pb_conn->c_needpw = 1; - *t=0; - /* We need to include "changeafterreset" error in - * passwordpolicy response control. So, we will not be - * done here. We remember this scenario by (c_needpw=1) - * and check it before sending the control from various - * places. We will also add LDAP_CONTROL_PWEXPIRED control - * as the return value used to be (1). - */ - goto skip; - } - /* Mark that first login occured */ - 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_string(×tring); - } + time_t cur_time, pw_exp_date; + Slapi_Mods smods; + double diff_t = 0; + char *cur_time_str = NULL; + char *passwordExpirationTime = NULL; + char *timestring; + char *dn; + const Slapi_DN *sdn; + passwdPolicy *pwpolicy = NULL; + int pwdGraceUserTime = 0; + char graceUserTime[16] = {0}; + Connection *pb_conn = NULL; + long t; + + if (NULL == e) { + return (-1); + } + slapi_mods_init(&smods, 0); + sdn = slapi_entry_get_sdn_const(e); + dn = slapi_entry_get_ndn(e); + pwpolicy = new_passwdPolicy(pb, dn); + + /* after the user binds with authentication, clear the retry count */ + if (pwpolicy->pw_lockout == 1) { + if (slapi_entry_attr_get_int(e, "passwordRetryCount") > 0) { + slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordRetryCount", "0"); + } + } + + cur_time = current_time(); + + /* get passwordExpirationTime attribute */ + passwordExpirationTime = slapi_entry_attr_get_charptr(e, "passwordExpirationTime"); + + if (passwordExpirationTime == NULL) { + /* password expiration date is not set. + * 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); + + timestring = format_genTime(pw_exp_date); + slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordExpirationTime", timestring); + slapi_ch_free_string(×tring); + slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordExpWarned", "0"); + + pw_apply_mods(sdn, &smods); + } else if (pwpolicy->pw_lockout == 1) { + pw_apply_mods(sdn, &smods); + } + slapi_mods_done(&smods); + return (0); + } + + pw_exp_date = parse_genTime(passwordExpirationTime); + + slapi_ch_free_string(&passwordExpirationTime); + + slapi_pblock_get(pb, SLAPI_CONNECTION, &pb_conn); + + /* Check if password has been reset */ + if (pw_exp_date == NO_TIME) { + + /* check if changing password is required */ + if (pwpolicy->pw_must_change) { + /* set c_needpw for this connection to be true. this client + now can only change its own password */ + pb_conn->c_needpw = 1; + t = 0; + /* We need to include "changeafterreset" error in + * passwordpolicy response control. So, we will not be + * done here. We remember this scenario by (c_needpw=1) + * and check it before sending the control from various + * places. We will also add LDAP_CONTROL_PWEXPIRED control + * as the return value used to be (1). + */ + goto skip; + } + /* Mark that first login occured */ + 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_string(×tring); + } skip: - /* if password never expires, don't need to go on; return 0 */ - if ( pwpolicy->pw_exp == 0 ) { - /* check for "changeafterreset" condition */ - if (pb->pb_conn->c_needpw == 1) { - if (pwresponse_req) { - slapi_pwpolicy_make_response_control ( pb, -1, -1, LDAP_PWPOLICY_CHGAFTERRESET ); - } - slapi_add_pwd_control ( pb, LDAP_CONTROL_PWEXPIRED, 0); - } - pw_apply_mods(sdn, &smods); - slapi_mods_done(&smods); - return ( 0 ); - } - - /* 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) { - slapi_ch_free_string(&cur_time_str); /* only need this above */ - /* password has expired. Check the value of - * passwordGraceUserTime and compare it - * against the value of passwordGraceLimit */ - pwdGraceUserTime = slapi_entry_attr_get_int( e, "passwordGraceUserTime"); - if ( pwpolicy->pw_gracelimit > pwdGraceUserTime ) { - pwdGraceUserTime++; - sprintf ( graceUserTime, "%d", pwdGraceUserTime ); - slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, - "passwordGraceUserTime", graceUserTime); - pw_apply_mods(sdn, &smods); - slapi_mods_done(&smods); - if (pwresponse_req) { - /* check for "changeafterreset" condition */ - if (pb->pb_conn->c_needpw == 1) { - slapi_pwpolicy_make_response_control( pb, -1, - ((pwpolicy->pw_gracelimit) - pwdGraceUserTime), - LDAP_PWPOLICY_CHGAFTERRESET); - } else { - slapi_pwpolicy_make_response_control( pb, -1, - ((pwpolicy->pw_gracelimit) - pwdGraceUserTime), - -1); - } - } - - if (pb->pb_conn->c_needpw == 1) { - slapi_add_pwd_control ( pb, LDAP_CONTROL_PWEXPIRED, 0); - } - return ( 0 ); - } - - /* password expired and user exceeded limit of grace attemps. - * Send result and also the control */ - slapi_add_pwd_control ( pb, LDAP_CONTROL_PWEXPIRED, 0); - if (pwresponse_req) { - slapi_pwpolicy_make_response_control ( pb, -1, -1, LDAP_PWPOLICY_PWDEXPIRED ); - } - slapi_send_ldap_result ( pb, LDAP_INVALID_CREDENTIALS, NULL, - "password expired!", 0, NULL ); - - /* abort bind */ - /* pass pb to do_unbind(). pb->pb_op->o_opid and - pb->pb_op->o_tag are not right but I don't see - do_unbind() checking for those. We might need to - create a pb for unbind operation. Also do_unbind calls - pre and post ops. Maybe we don't want to call them */ - if (pb->pb_conn && (LDAP_VERSION2 == pb->pb_conn->c_ldapversion)) { - /* We close the connection only with LDAPv2 connections */ - disconnect_server( pb->pb_conn, pb->pb_op->o_connid, - pb->pb_op->o_opid, SLAPD_DISCONNECT_UNBIND, 0); - } - /* Apply current modifications */ - 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"); - 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); - } - - timestring = format_genTime(pw_exp_date); - slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordExpirationTime", timestring); - slapi_ch_free_string(×tring); - - slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordExpWarned", "1"); - - *t = pwpolicy->pw_warning; - - } else { - *t = (long)diff_t; /* jcm: had to cast double to long */ - } - - pw_apply_mods(sdn, &smods); - slapi_mods_done(&smods); - if (pwresponse_req) { - /* check for "changeafterreset" condition */ - if (pb->pb_conn->c_needpw == 1) { - slapi_pwpolicy_make_response_control( pb, *t, -1, - LDAP_PWPOLICY_CHGAFTERRESET); - } else { - slapi_pwpolicy_make_response_control( pb, *t, -1, - -1); - } - } - - if (pb->pb_conn->c_needpw == 1) { - slapi_add_pwd_control ( pb, LDAP_CONTROL_PWEXPIRED, 0); - } - return (2); - } else { - if (pwresponse_req && pwpolicy->pw_send_expiring) { - slapi_pwpolicy_make_response_control( pb, diff_t, -1, -1); - slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRING, diff_t); - } - } - - pw_apply_mods(sdn, &smods); - slapi_mods_done(&smods); - /* Leftover from "changeafterreset" condition */ - if (pb->pb_conn->c_needpw == 1) { - slapi_add_pwd_control ( pb, LDAP_CONTROL_PWEXPIRED, 0); - } - /* passes checking, return 0 */ - return( 0 ); + /* if password never expires, don't need to go on; return 0 */ + if (pwpolicy->pw_exp == 0) { + /* check for "changeafterreset" condition */ + if (pb_conn->c_needpw == 1) { + if (pwresponse_req) { + slapi_pwpolicy_make_response_control(pb, -1, -1, LDAP_PWPOLICY_CHGAFTERRESET); + } + slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRED, 0); + } + pw_apply_mods(sdn, &smods); + slapi_mods_done(&smods); + return (0); + } + + /* 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) { + slapi_ch_free_string(&cur_time_str); /* only need this above */ + /* password has expired. Check the value of + * passwordGraceUserTime and compare it + * against the value of passwordGraceLimit */ + pwdGraceUserTime = slapi_entry_attr_get_int(e, "passwordGraceUserTime"); + if (pwpolicy->pw_gracelimit > pwdGraceUserTime) { + pwdGraceUserTime++; + sprintf(graceUserTime, "%d", pwdGraceUserTime); + slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, + "passwordGraceUserTime", graceUserTime); + pw_apply_mods(sdn, &smods); + slapi_mods_done(&smods); + if (pwresponse_req) { + /* check for "changeafterreset" condition */ + if (pb_conn->c_needpw == 1) { + slapi_pwpolicy_make_response_control(pb, -1, + ((pwpolicy->pw_gracelimit) - pwdGraceUserTime), + LDAP_PWPOLICY_CHGAFTERRESET); + } else { + slapi_pwpolicy_make_response_control(pb, -1, + ((pwpolicy->pw_gracelimit) - pwdGraceUserTime), + -1); + } + } + slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRED, 0); + return (0); + } + + /* password expired and user exceeded limit of grace attemps. + * Send result and also the control */ + slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRED, 0); + if (pwresponse_req) { + slapi_pwpolicy_make_response_control(pb, -1, -1, LDAP_PWPOLICY_PWDEXPIRED); + } + slapi_send_ldap_result(pb, LDAP_INVALID_CREDENTIALS, NULL, + "password expired!", 0, NULL); + + /* abort bind */ + /* pass pb to do_unbind(). pb->pb_op->o_opid and + pb->pb_op->o_tag are not right but I don't see + do_unbind() checking for those. We might need to + create a pb for unbind operation. Also do_unbind calls + pre and post ops. Maybe we don't want to call them */ + if (pb_conn && (LDAP_VERSION2 == pb_conn->c_ldapversion)) { + Operation *pb_op = NULL; + slapi_pblock_get(pb, SLAPI_OPERATION, &pb_op); + /* We close the connection only with LDAPv2 connections */ + disconnect_server(pb_conn, pb_op->o_connid, + pb_op->o_opid, SLAPD_DISCONNECT_UNBIND, 0); + } + /* Apply current modifications */ + 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"); + 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_conn->c_needpw != 1) { + pw_exp_date = time_plus_sec(cur_time, pwpolicy->pw_warning); + } + + timestring = format_genTime(pw_exp_date); + slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordExpirationTime", timestring); + slapi_ch_free_string(×tring); + + slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordExpWarned", "1"); + + t = pwpolicy->pw_warning; + + } else { + t = (long)diff_t; /* jcm: had to cast double to long */ + } + + pw_apply_mods(sdn, &smods); + slapi_mods_done(&smods); + if (pwresponse_req) { + /* check for "changeafterreset" condition */ + if (pb_conn->c_needpw == 1) { + slapi_pwpolicy_make_response_control(pb, t, -1, LDAP_PWPOLICY_CHGAFTERRESET); + } else { + slapi_pwpolicy_make_response_control(pb, t, -1, -1); + } + } + + if (pb_conn->c_needpw == 1) { + slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRED, 0); + } else { + slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRING, t); + } + return (0); + } else { + if (pwresponse_req && pwpolicy->pw_send_expiring) { + slapi_pwpolicy_make_response_control(pb, diff_t, -1, -1); + slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRING, diff_t); + } + } + + pw_apply_mods(sdn, &smods); + slapi_mods_done(&smods); + /* Leftover from "changeafterreset" condition */ + if (pb_conn->c_needpw == 1) { + slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRED, 0); + } + /* passes checking, return 0 */ + return (0); } /* Called once from main */ diff --git a/ldap/servers/slapd/saslbind.c b/ldap/servers/slapd/saslbind.c index dd0c4fb..134f5aa 100644 --- a/ldap/servers/slapd/saslbind.c +++ b/ldap/servers/slapd/saslbind.c @@ -859,7 +859,6 @@ ids_sasl_mech_supported(Slapi_PBlock *pb, const char *mech) void ids_sasl_check_bind(Slapi_PBlock *pb) { int rc, isroot; - long t; sasl_conn_t *sasl_conn; struct propctx *propctx; sasl_ssf_t *ssfp; @@ -1096,23 +1095,8 @@ sasl_check_result: set_db_default_result_handlers(pb); /* check password expiry */ - if (!isroot) { - int pwrc; - - pwrc = need_new_pw(pb, &t, bind_target_entry, pwresponse_requested); - - switch (pwrc) { - case 1: - slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRED, 0); - break; - case 2: - slapi_add_pwd_control(pb, LDAP_CONTROL_PWEXPIRING, t); - break; - case -1: - goto out; - default: - break; - } + if (!isroot && need_new_pw(pb, bind_target_entry, pwresponse_requested) == -1) { + goto out; } /* attach the sasl data */ -- 2.9.5