diff --git a/include/class_ldap.inc b/include/class_ldap.inc index 07f07b0f16505bfee8cee985f7966597b20f8a83..df4418e12f1c2de7b13f3f5667e518f147bc5bb4 100644 --- a/include/class_ldap.inc +++ b/include/class_ldap.inc @@ -121,6 +121,16 @@ class LDAP return ldap_escape_f($dn); } + /*! + * \brief Error text that must be returned for invalid user or password + * + * This is useful to make sure the same error text is shown whether a user exists or not, when the password is not correct. + */ + static function invalidCredentialsError (): string + { + return _(ldap_err2str(49)); + } + /*! * \brief Create a connection to LDAP server * @@ -151,11 +161,41 @@ class LDAP if (@ldap_parse_result($this->cid, $result, $errcode, $matcheddn, $errmsg, $referrals, $ctrls)) { if (isset($ctrls[LDAP_CONTROL_PASSWORDPOLICYRESPONSE]['value']['error'])) { $this->hascon = FALSE; - $this->error = $ctrls[LDAP_CONTROL_PASSWORDPOLICYRESPONSE]['value']['error']; + switch ($ctrls[LDAP_CONTROL_PASSWORDPOLICYRESPONSE]['value']['error']) { + case 0: + /* passwordExpired - password has expired and must be reset */ + $this->error = _('It seems your user password has expired. Please use <a href="recovery.php">password recovery</a> to change it.'); + break; + case 1: + /* accountLocked */ + $this->error = _('Account locked. Please contact your system administrator!'); + break; + case 2: + /* changeAfterReset - password must be changed before the user will be allowed to perform any other operation */ + $this->error = 'changeAfterReset'; + break; + case 3: + /* passwordModNotAllowed */ + case 4: + /* mustSupplyOldPassword */ + case 5: + /* insufficientPasswordQuality */ + case 6: + /* passwordTooShort */ + case 7: + /* passwordTooYoung */ + case 8: + /* passwordInHistory */ + default: + $this->error = sprintf(_('Unexpected ppolicy error "%s", please contact the administrator'), $ctrls[LDAP_CONTROL_PASSWORDPOLICYRESPONSE]['value']['error']); + break; + } // Note: Also available: expire, grace } else { $this->hascon = ($errcode == 0); - if (empty($errmsg)) { + if ($errcode == 49) { + $this->error = static::invalidCredentialsError(); + } elseif (empty($errmsg)) { $this->error = ldap_err2str($errcode); } else { $this->error = $errmsg; @@ -171,10 +211,10 @@ class LDAP } else { if ($this->reconnect) { if ($this->error != 'Success') { - $this->error = 'Could not rebind to ' . $this->binddn; + $this->error = static::invalidCredentialsError(); } } else { - $this->error = 'Could not bind to ' . $this->binddn; + $this->error = static::invalidCredentialsError(); } } } else { diff --git a/include/class_userinfo.inc b/include/class_userinfo.inc index 61995447703f1d60a843f7d6518d36d82f20a126..61a781246be1a2b5a0adfdc76d2c64016aab5740 100644 --- a/include/class_userinfo.inc +++ b/include/class_userinfo.inc @@ -62,6 +62,9 @@ class userinfo /*! \brief Current management base */ protected $currentBase; + /*! \brief Password change should be forced */ + protected $forcePasswordChange = FALSE; + /* get acl's an put them into the userinfo object attr subtreeACL (userdn:components, userdn:component1#sub1#sub2,component2,...) */ function __construct ($userdn) @@ -903,6 +906,10 @@ class userinfo { global $config; + if ($this->forcePasswordChange) { + return POSIX_FORCE_PASSWORD_CHANGE; + } + // Skip this for the admin account, we do not want to lock him out. if ($this->is_user_admin()) { return 0; @@ -1209,7 +1216,7 @@ class userinfo $ui = static::getLdapUser($username); if ($ui === FALSE) { - throw new LoginFailureException('User not found'); + throw new LoginFailureException(ldap::invalidCredentialsError()); } elseif (is_string($ui)) { msg_dialog::display(_('Internal error'), $ui, FATAL_ERROR_DIALOG); exit(); @@ -1222,14 +1229,18 @@ class userinfo ); $ldap = new ldapMultiplexer($ldapObj); if (!$ldap->success()) { - throw new LoginFailureException($ldap->get_error()); + if ($ldap->get_error() == 'changeAfterReset') { + $ui->forcePasswordChange = TRUE; + } else { + throw new LoginFailureException($ldap->get_error()); + } } - if (class_available('ppolicyAccount')) { + if (class_available('ppolicyAccount') && !function_exists('ldap_bind_ext')) { $ldap->cd($config->current['BASE']); $ldap->search('(objectClass=*)', [], 'one'); if (!$ldap->success()) { - throw new LoginFailurePpolicyException(_('It seems your user password has expired. Please use <a href="recovery.php">password recovery</a> to change it.')); + $ui->forcePasswordChange = TRUE; } } diff --git a/include/login/class_LoginMethod.inc b/include/login/class_LoginMethod.inc index 92797de643ac95e5610ef366b2aefe4018cf4e85..5f5e8f68a27e09a2f78065bdd9a3c8b12d2144d4 100644 --- a/include/login/class_LoginMethod.inc +++ b/include/login/class_LoginMethod.inc @@ -97,12 +97,6 @@ class LoginMethod /* Login as user, initialize user ACL's */ try { $ui = userinfo::loginUser(static::$username, static::$password); - } catch (LoginFailurePpolicyException $e) { - msg_dialog::display(_('Authentication error'), $e->getMessage(), ERROR_DIALOG); - logging::log('security', 'login', '', [], 'Account for user "'.static::$username.'" has expired (ppolicy)'); - $message = _('Password expired'); - $smarty->assign('focusfield', 'username'); - return FALSE; } catch (LoginFailureException $e) { if (isset($_SERVER['REMOTE_ADDR'])) { logging::log('security', 'login', '', [], 'Authentication failed for user "'.static::$username.'" [from '.$_SERVER['REMOTE_ADDR'].']: '.$e->getMessage()); @@ -110,7 +104,7 @@ class LoginMethod logging::log('security', 'login', '', [], 'Authentication failed for user "'.static::$username.'": '.$e->getMessage()); } /* Show the same message whether the user exists or not to avoid information leak */ - $message = _('Please check the username/password combination.'); + $message = $e->getMessage(); $smarty->assign('focusfield', 'password'); return FALSE; }