Commit 277158dd authored by Côme Bernigaud's avatar Côme Bernigaud Committed by Benoit Mortier
Browse files

Fixes #3383 Cleaning password modification system

parent ec0157d0
......@@ -178,6 +178,7 @@ attributetype ( 1.3.6.1.4.1.38414.8.13.3 NAME 'fdPasswordMinDiffer'
attributetype ( 1.3.6.1.4.1.38414.8.13.4 NAME 'fdPasswordHook'
DESC 'FusionDirectory - Password hook (external command)'
OBSOLETE
EQUALITY caseExactIA5Match
SUBSTR caseExactIA5SubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
......
......@@ -604,16 +604,6 @@ class passwordRecovery {
return;
}
/* Passed quality check, just try to change the password now */
if ($this->config->get_cfg_value("passwordHook") != "") {
exec($this->config->get_cfg_value("passwordHook")." ".
escapeshellarg($this->uid)." ".escapeshellarg($_POST['new_password']), $resarr);
if (count($resarr) > 0) {
$this->message[] = _("External password changer reported a problem: ".join('\n', $resarr));
msg_dialog::displayChecks($this->message);
return;
}
}
if ($this->method != "") {
change_password($dn, $_POST['new_password'], 0, $this->method);
} else {
......
......@@ -2901,7 +2901,7 @@ function get_correct_class_name($cls)
* \param int $mode if not 0 doesn't create the samba password hash
*
* \param string $hash which hash to use to encrypt it, default is empty
* for cleartext storage.
* for reusing existing hash method for this password (or use the default one).
*
* \return boolean TRUE on success FALSE on error
*/
......@@ -2916,24 +2916,24 @@ function change_password ($dn, $password, $mode = 0, $hash = "")
// Get all available encryption Methods
// NON STATIC CALL :)
$methods = new passwordMethod(session::get('config'), $dn);
$methods = new passwordMethod($config, $dn);
$available = $methods->get_available_methods();
// read current password entry for $dn, to detect the encryption Method
$ldap = $config->get_ldap_link();
$ldap->cat ($dn, array("shadowLastChange", "userPassword", "uid"));
$attrs = $ldap->fetch ();
$ldap = $config->get_ldap_link();
$ldap->cat($dn, array('shadowLastChange', 'userPassword', 'uid'));
$attrs = $ldap->fetch ();
/* Is ensure that clear passwords will stay clear */
if ($hash == "" && isset($attrs['userPassword'][0]) && !preg_match ("/^{([^}]+)}(.+)/", $attrs['userPassword'][0])) {
$hash = "clear";
if ($hash == '' && isset($attrs['userPassword'][0]) && !preg_match ("/^{([^}]+)}(.+)/", $attrs['userPassword'][0])) {
$hash = 'clear';
}
// Detect the encryption Method
if ($config->get_cfg_value("forcePasswordDefaultHash", "FALSE") == "TRUE") {
if ($config->get_cfg_value('forcePasswordDefaultHash', 'FALSE') == 'TRUE') {
// if forcePasswordDefaultHash is TRUE we use the passwordDefaultHash
// hash and if it is not defined we use 'ssha' as default
$hash = $config->get_cfg_value("passwordDefaultHash", "ssha");
$hash = $config->get_cfg_value('passwordDefaultHash', 'ssha');
$test = new $available[$hash]($config, $dn);
} elseif ((isset($attrs['userPassword'][0]) && preg_match ("/^{([^}]+)}(.+)/", $attrs['userPassword'][0])) || $hash != "") {
......@@ -2950,103 +2950,106 @@ function change_password ($dn, $password, $mode = 0, $hash = "")
} else {
// Use SSHA by default
$hash = $config->get_cfg_value("passwordDefaultHash", "ssha");
$test = new $available[$hash]($config, $dn);
$hash = $config->get_cfg_value('passwordDefaultHash', 'ssha');
$test = new $available[$hash]($config, $dn);
}
if ($test instanceOf passwordMethod) {
if (!($test instanceOf passwordMethod)) {
return FALSE;
}
$deactivated = $test->is_locked($config, $dn);
$deactivated = $test->is_locked($config, $dn);
/* Feed password backends with information */
$test->dn = $dn;
$test->attrs = $attrs;
$newpass = $test->generate_hash($password);
/* Feed password backends with information */
$test->dn = $dn;
$test->attrs = $attrs;
$newpass = $test->generate_hash($password);
// Update shadow timestamp?
if (isset($attrs["shadowLastChange"][0])) {
$shadow = (int)(date("U") / 86400);
} else {
$shadow = 0;
}
// Update shadow timestamp?
if (isset($attrs['shadowLastChange'][0])) {
$shadow = (int)(date('U') / 86400);
} else {
$shadow = 0;
}
// Write back modified entry
$ldap->cd($dn);
$attrs = array();
// Write back modified entry
$ldap->cd($dn);
$attrs = array();
// Not for groups
if ($mode == 0) {
if ($test->need_password()) {
// Create SMB Password
$attrs = generate_smb_nt_hash($password);
} else {
$attrs['sambaLMPassword'] = array();
$attrs['sambaNTPassword'] = array();
$attrs['sambaPwdLastSet'] = array();
$attrs['sambaBadPasswordCount'] = array();
$attrs['sambaBadPasswordTime'] = array();
}
if ($shadow != 0) {
$attrs['shadowLastChange'] = $shadow;
}
// Not for groups
if ($mode == 0) {
if ($test->need_password()) {
// Create SMB Password
$attrs = generate_smb_nt_hash($password);
} else {
$attrs['sambaLMPassword'] = array();
$attrs['sambaNTPassword'] = array();
$attrs['sambaPwdLastSet'] = array();
$attrs['sambaBadPasswordCount'] = array();
$attrs['sambaBadPasswordTime'] = array();
}
$attrs['userPassword'] = array();
$attrs['userPassword'] = $newpass;
$ldap->modify($attrs);
/* Read ! if user was deactivated */
if ($deactivated) {
$test->lock_account($config, $dn);
if ($shadow != 0) {
$attrs['shadowLastChange'] = $shadow;
}
}
new log("modify", "user/passwordMethod", $dn, array_keys($attrs), $ldap->get_error());
$attrs['userPassword'] = array();
$attrs['userPassword'] = $newpass;
if (!$ldap->success()) {
msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $dn, LDAP_MOD), LDAP_ERROR);
} else {
$ldap->modify($attrs);
/* Run backend method for change/create */
if (!$test->set_password($password)) {
return FALSE;
}
/* Read ! if user was deactivated */
if ($deactivated) {
$test->lock_account($config, $dn);
}
/* Find postmodify entries for this class */
$command = $config->search("password", "POSTMODIFY", array('menu', 'hooks'));
if ($command != "") {
/* Walk through attribute list */
$addAttrs = array(
'userPassword' => escapeshellarg($password),
'dn' => escapeshellarg($dn)
);
$addAttrsStars = array(
'userPassword' => '******',
'dn' => escapeshellarg($dn)
);
$commandHiddenPwd = plugin::tpl_parse_string($command, $addAttrsStars);
$command = plugin::tpl_parse_string($command, $addAttrs);
new log('modify', 'user/passwordMethod', $dn, array_keys($attrs), $ldap->get_error());
@DEBUG (DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__, $command, "Execute");
exec($command, $arr, $returnCode);
if ($returnCode != 0) {
$str = implode("\n", $arr);
@DEBUG(DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__, $commandHiddenPwd, "Execution failed code: ".$returnCode);
$message = msgPool::cmdexecfailed('POSTMODIFY', $commandHiddenPwd, 'password');
if (!empty($str)) {
$message .= "Result: ".$str;
}
msg_dialog::display(_("Error"), $message, ERROR_DIALOG);
} elseif (is_array($arr)) {
$str = implode("\n", $arr);
@DEBUG(DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__, $commandHiddenPwd, "Result: ".$str);
if (!$ldap->success()) {
msg_dialog::display(_('LDAP error'), msgPool::ldaperror($ldap->get_error(), $dn, LDAP_MOD), LDAP_ERROR);
} else {
/* Run backend method for change/create */
if (!$test->set_password($password)) {
return FALSE;
}
/* Find postmodify entries for this class */
$command = $config->search('password', 'POSTMODIFY', array('menu', 'hooks'));
if ($command != '') {
/* Walk through attribute list */
$addAttrs = array(
'userPassword' => escapeshellarg($password),
'dn' => escapeshellarg($dn),
'passwordHash' => $hash,
);
$addAttrsStars = array(
'userPassword' => '******',
'dn' => escapeshellarg($dn),
'passwordHash' => $hash,
);
$commandHiddenPwd = plugin::tpl_parse_string($command, $addAttrsStars);
$command = plugin::tpl_parse_string($command, $addAttrs);
@DEBUG (DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__, $command, 'Execute');
exec($command, $arr, $returnCode);
if ($returnCode != 0) {
$str = implode("\n", $arr);
@DEBUG(DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__, $commandHiddenPwd, 'Execution failed code: '.$returnCode);
$message = msgPool::cmdexecfailed('POSTMODIFY', $commandHiddenPwd, 'password');
if (!empty($str)) {
$message .= 'Result: '.$str;
}
msg_dialog::display(_('Error'), $message, ERROR_DIALOG);
} elseif (is_array($arr)) {
$str = implode("\n", $arr);
@DEBUG(DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__, $commandHiddenPwd, 'Result: '.$str);
}
}
return TRUE;
}
return TRUE;
}
......
......@@ -79,6 +79,7 @@ class configInLdap extends simplePlugin
$plugins = array_keys(session::global_get('plist')->info);
}
sort($plugins);
array_unshift($plugins, 'password');
return array(
'look_n_feel' => array(
'name' => _('Look n feel'),
......@@ -141,10 +142,6 @@ class configInLdap extends simplePlugin
'fdPasswordMinDiffer', FALSE,
0 /*min*/, FALSE /*no max*/
),
new StringAttribute (
_('Password hook'), _('External script to handle password settings'),
'fdPasswordHook'
),
new BooleanAttribute (
_('Use account expiration'),
_('Enables shadow attribute tests during the login to FusionDirectory and forces password renewal or account locking'),
......
......@@ -385,6 +385,25 @@ class user extends simplePlugin
return parent::execute();
}
function prepare_save()
{
parent::prepare_save();
unset($this->attrs['userPassword']);
}
function ldap_save($cleanup = TRUE)
{
parent::ldap_save($cleanup);
if ($this->attributesAccess['userPassword']->attributes[1]->getValue() != '') {
change_password(
$this->dn,
$this->attributesAccess['userPassword']->attributes[1]->getValue(), /*password*/
0,
$this->attributesAccess['userPassword']->attributes[0]->getValue() /*hash*/
);
}
}
function save()
{
parent::save();
......
<p>
<b>{t}You've successfully changed your password. Remember to change all programms configured to use it as well.{/t}</b>
</p>
<br>
<p class="plugbottom">
<input type=submit name="password_back" value="{msgPool type=backButton}">
</p>
<input type="hidden" name="ignore">
<?php
/*
This code is part of FusionDirectory (http://www.fusiondirectory.org/)
Copyright (C) 2003 Cajus Pollmeier
Copyright (C) 2011-2013 FusionDirectory
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
class password extends plugin
{
var $proposal = "";
var $proposalEnabled = FALSE;
var $proposalSelected = FALSE;
var $forcedHash = NULL;
function password(&$config, $dn = NULL, $parent = NULL)
{
parent::__construct($config, $dn, $parent);
// Try to generate a password proposal, if this is successfull
// then preselect the proposal usage.
$this->refreshProposal();
if ($this->proposal != "") {
$this->proposalSelected = TRUE;
}
}
function forceHash($hash)
{
$this->forcedHash = $hash;
}
function refreshProposal()
{
$this->proposal = passwordMethod::getPasswordProposal($this->config);
$this->proposalEnabled = (!empty($this->proposal));
}
function execute()
{
plugin::execute();
$smarty = get_smarty();
$smarty->assign("usePrototype", "true");
$ui = get_userinfo();
/* Get acls */
$password_ACLS = $ui->get_permissions($ui->dn, "user/password");
$smarty->assign("ChangeACL", $password_ACLS);
$smarty->assign("NotAllowed", !preg_match("/w/i", $password_ACLS));
/* Display expiration template */
$smarty->assign("passwordExpired", FALSE);
if ($this->config->get_cfg_value("handleExpiredAccounts") == "TRUE") {
$expired = $ui->expired_status();
$smarty->assign("passwordExpired", ($expired == POSIX_FORCE_PASSWORD_CHANGE));
if ($expired == POSIX_DISALLOW_PASSWORD_CHANGE) {
return $smarty->fetch(get_template_path("nochange.tpl", TRUE));
}
}
// Refresh proposal if requested
if (isset($_POST['refreshProposal'])) {
$this->refreshProposal();
}
if (isset($_POST['proposalSelected'])) {
$this->proposalSelected = get_post('proposalSelected') == 1;
}
$smarty->assign("proposal", $this->proposal);
$smarty->assign("proposalEnabled", $this->proposalEnabled);
$smarty->assign("proposalSelected", $this->proposalSelected);
/* Pwd change requested */
if (isset($_POST['password_finish'])) {
if ($this->proposalSelected) {
$current_password = $_POST['current_password'];
$new_password = $this->proposal;
$repeated_password = $this->proposal;
} else {
$current_password = $_POST['current_password'];
$new_password = $_POST['new_password'];
$repeated_password = $_POST['repeated_password'];
}
/* Should we check different characters in new password */
$check_differ = ($this->config->get_cfg_value("passwordMinDiffer") != "");
$differ = $this->config->get_cfg_value("passwordMinDiffer", 0);
/* Enable length check ? */
$check_length = ($this->config->get_cfg_value("passwordMinLength") != "");
$length = $this->config->get_cfg_value("passwordMinLength", 0);
// Perform FusionDirectory password policy checks
$message = array();
if (empty($current_password)) {
$message[] = _("You need to specify your current password in order to proceed.");
} elseif ($new_password != $repeated_password) {
$message[] = _("The passwords you've entered as 'New password' and 'Repeated new password' do not match.");
} elseif ($new_password == "") {
$message[] = _("The password you've entered as 'New password' is empty.");
} elseif ($check_differ && (substr($current_password, 0, $differ) == substr($new_password, 0, $differ))) {
$message[] = _("The password used as new and current are too similar.");
} elseif ($check_length && (strlen($new_password) < $length)) {
$message[] = _("The password used as new is to short.");
} elseif (!passwordMethod::is_harmless($new_password)) {
$message[] = _("The password contains possibly problematic Unicode characters!");
}
/* Call external password quality hook ?*/
if (!count($message)) {
$check_hook = $this->config->get_cfg_value("passwordHook") != "";
$hook = $this->config->get_cfg_value("passwordHook")." ".
escapeshellarg($ui->username)." ".escapeshellarg($new_password)." ".escapeshellarg($current_password);
if ($check_hook) {
exec($hook, $resarr);
$check_hook_output = "";
if (count($resarr) > 0) {
$check_hook_output = join('\n', $resarr);
}
if (!empty($check_hook_output)) {
$message[] = sprintf(_("Check-hook reported a problem: %s. Password change canceled!"), $check_hook_output);
}
}
}
// Some errors/warning occured, display them and abort password change.
if (count($message)) {
msg_dialog::displayChecks($message);
} else {
/* Try to connect via current password */
$tldap = new LDAP(
$ui->dn,
$current_password,
$this->config->current['SERVER'],
$this->config->get_cfg_value("ldapFollowReferrals") == "TRUE",
$this->config->get_cfg_value("ldapTLS") == "TRUE"
);
/* connection Successfull ? */
if (!$tldap->success()) {
msg_dialog::display(_("Password change"),
_("The password you've entered as your current password doesn't match the real one."), WARNING_DIALOG);
} else {
/* Check FusionDirectory permissions */
if (!preg_match("/w/i", $password_ACLS)) {
msg_dialog::display(_("Password change"),
_("You have no permission to change your password."), WARNING_DIALOG);
} else {
$this->change_password($ui->dn, $new_password, $this->forcedHash);
fusiondirectory_log("User/password has been changed");
$ui->password = $new_password;
session::set('ui', $ui);
return $smarty->fetch(get_template_path("changed.tpl", TRUE));
}
}
}
}
return $smarty->fetch(get_template_path("password.tpl", TRUE));
}
function change_password($dn, $pwd, $hash)
{
if ($hash) {
change_password ($dn, $pwd, 0, $hash);
} else {
change_password ($dn, $pwd);
}
}
function remove_from_parent()
{
$this->handle_post_events("remove");
}
function save()
{
}
static function plInfo()
{
return array(
"plShortName" => _("Password"),
"plDescription" => _("Change user password"),
"plSelfModify" => TRUE,
"plPriority" => 10,
"plCategory" => array("user"),
"plSection" => "personal",
"plIcon" => 'geticon.php?context=status&icon=dialog-password&size=48',
"plProvidedAcls" => array(
"userPassword" => _("Change user password"),
)
);
}
}
?>
<?php
/*
This code is part of FusionDirectory (http://www.fusiondirectory.org/)
Copyright (C) 2003 Cajus Pollmeier
Copyright (C) 2011-2013 FusionDirectory
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/* Remove locks created by this plugin */
$lock_msg = "";
if ($remove_lock) {
if (session::is_set('password')) {
//Nothing to do here
}
}
/* Remove this plugin from session */
if ($cleanup) {
session::un_set('password');
session::un_set('edit');
} else {
/* Reset requested? */
if (isset($_POST['edit_cancel'])) {
session::un_set('edit');
session::un_set('password');
}
/* Create password object on demand */
if (!session::is_set('password') || (isset($_GET['reset']) && $_GET['reset'] == 1)) {
session::set('password', new password ($config, $ui->dn));
}
$password = session::get('password');
/* Execute formular */
$display .= $password->execute();
/* Page header*/
$display = print_header('geticon.php?context=status&icon=dialog-password&size=48',
_("Password settings"), "").$display;
}
?>
<p>
<b>{t}You have no permission to change your password at this time{/t}</b>
</p>
<input type="hidden" name="ignore">
<script type="text/javascript" src="include/pwdStrength.js"></script>
<p>
{t}To change your personal password use the fields below. The changes take effect immediately. Please memorize the new password, because you wouldn't be able to login without it.{/t}
</p>
<p>
{t}Changing the password affects your authentification on mail, proxy, samba and unix services.{/t}