Files
cacti/auth_resetpassword.php
T

392 lines
13 KiB
PHP

<?php
/*
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2026 The Cacti Group |
| |
| 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. |
+-------------------------------------------------------------------------+
| Cacti: The Complete RRDtool-based Graphing Solution |
+-------------------------------------------------------------------------+
| This code is designed, written, and maintained by the Cacti Group. See |
| about.php and/or the AUTHORS file for specific developer information. |
+-------------------------------------------------------------------------+
| http://www.cacti.net/ |
+-------------------------------------------------------------------------+
*/
require('./include/global.php');
set_default_action('formidentity');
$action = grv('action');
$errorMessage = '';
$return = CACTI_PATH_URL . 'index.php';
switch ($action) {
case 'checkpass': // check if inserted password match password complexivity only
$error = secpass_check_pass(gnrv('password'));
if ($error != '') {
print $error;
} else {
print 'ok';
}
exit;
case 'formreset': // check correct hash, if incorrect lets start again
$user_hash = gfrv('hash', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '/^[a-zA-Z0-9]+$/']]);
$hash = db_fetch_row_prepared('SELECT *
FROM user_auth_reset_hashes
WHERE hash = ?
AND expiry > NOW()
LIMIT 1',
[$user_hash]);
if (!$hash || $hash['hash'] != $user_hash) {
$errorMessage = "<span class='badpassword_message'>" . __('Incorrect resetlink hash') . '</span>';
$action = 'formidentity';
}
break;
case 'resetrequest': // try to find user in db. If yes and has email, send resetlink
$user = [];
$identity = gnrv('identity');
if (filter_var($identity, FILTER_VALIDATE_EMAIL) ||
gfrv('identity', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '/^[a-zA-Z0-9]+$/']])) {
$user = db_fetch_row_prepared('SELECT id, username, email_address
FROM user_auth
WHERE (username = ? OR email_address = ?)
AND realm = 0
AND enabled = "on"
AND password_change = "on"
LIMIT 1',
[$identity, $identity]);
}
if (cacti_sizeof($user) && $user['email_address'] != '') {
$hash = generate_hash();
if (auth_reset_token_replace($user['id'], $hash, (int) read_config_option('secnotify_resetlink_timeout'))) {
$replacement = [
read_config_option('base_url') . CACTI_PATH_URL,
$user['username'],
read_config_option('base_url') . CACTI_PATH_URL . 'auth_resetpassword.php?action=formreset&hash=' . $hash
];
$search = ['<CACTIURL>', '<USERNAME>', '<PWDRESETLINK>'];
$body = str_replace($search, $replacement, read_config_option('secnotify_chpass_message'));
send_mail($user['email_address'], null, read_config_option('secnotify_chpass_subject'), $body, [], [], true);
cacti_log(sprintf('NOTE: Reset password request for user %s from IP %s', $user['username'], get_client_addr()), false, 'SYSTEM');
} else {
cacti_log(sprintf('ERROR: Unable to replace password reset token for user %s', $user['username']), false, 'AUTH');
}
} else {
cacti_log(sprintf('NOTE: Reset password request for unknown user "%s" from IP %s', db_qstr($identity), get_client_addr()), false, 'SYSTEM');
}
$errorMessage = "<span class='badpassword_message'>" . __('Reset password token was sent. Check your mailbox') . '</span>';
$action = 'formidentity';
break;
case 'resetpassword': // check and save new password
$user_hash = gfrv('hash', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '/^[a-zA-Z0-9]+$/']]);
$hash = db_fetch_row_prepared('SELECT *
FROM user_auth_reset_hashes
WHERE hash = ?
AND expiry > NOW()
LIMIT 1',
[$user_hash]);
if (!$hash || $hash['hash'] != $user_hash) {
$errorMessage = "<span class='badpassword_message'>" . __('Incorrect resetlink hash') . '</span>';
$action = 'formidentity';
break;
}
$user = db_fetch_row_prepared('SELECT *
FROM user_auth
WHERE id = ?
AND realm = 0
AND enabled = "on"',
[$hash['user_id']]);
if (!cacti_sizeof($user)) {
$errorMessage = "<span class='badpassword_message'>" . __('Password reset is not available for this account.') . '</span>';
$action = 'formidentity';
break;
}
// Get passwords entered for change
$password = gnrv('password');
$password_confirm = gnrv('password_confirm');
// Secpass checking
$error = secpass_check_pass($password);
// Check new password passes basic checks
if ($error != 'ok') {
$errorMessage = "<span class='badpassword_message'>$error</span>";
$action = 'formreset';
break;
}
// Check user password history
if (!secpass_check_history($user['id'], $password)) {
$action = 'formreset';
break;
}
// Password and Confirmed password checks
if ($password !== $password_confirm) {
$errorMessage = "<span class='badpassword_message'>" . __('Your new passwords do not match, please retype.') . '</span>';
$action = 'formreset';
break;
}
// Check new password does not match stored password
if (compat_password_verify($password, $user['password'])) {
$errorMessage = "<span class='badpassword_message'>" . __('Your new password cannot be the same as the old password. Please try again.') . '</span>';
$action = 'formreset';
break;
}
// If password isn't blank, password change is good to go
if ($password != '') {
db_check_password_length();
if (!db_begin_transaction()) {
$errorMessage = "<span class='badpassword_message'>" . __('Unable to securely consume the password reset link.') . '</span>';
$action = 'formidentity';
break;
}
$locked_user = db_fetch_cell_prepared('SELECT id
FROM user_auth
WHERE id = ?
AND realm = 0
AND enabled = "on"
FOR UPDATE',
[$user['id']]);
$locked_token_user = db_fetch_cell_prepared('SELECT user_id
FROM user_auth_reset_hashes
WHERE user_id = ?
AND hash = ?
AND expiry > NOW()
FOR UPDATE',
[$user['id'], $user_hash]);
if ((int) $locked_user !== (int) $user['id'] ||
(int) $locked_token_user !== (int) $user['id']) {
db_rollback_transaction();
$errorMessage = "<span class='badpassword_message'>" . __('Incorrect resetlink hash') . '</span>';
$action = 'formidentity';
break;
}
$reset_ok = true;
if (read_config_option('secpass_expirepass') > 0) {
$reset_ok = db_execute_prepared("UPDATE user_auth
SET lastchange = ?
WHERE id = ?
AND realm = 0
AND enabled = 'on'",
[time(), $user['id']]) !== false;
}
$history = intval(read_config_option('secpass_history'));
if ($history > 0) {
$h = db_fetch_row_prepared("SELECT password, password_history
FROM user_auth
WHERE id = ?
AND realm = 0
AND enabled = 'on'",
[$user['id']]);
$op = $h['password'];
$h = explode('|', $h['password_history']);
while (cacti_count($h) > $history - 1) {
array_shift($h);
}
$h[] = $op;
$h = implode('|', $h);
$reset_ok = $reset_ok && db_execute_prepared("UPDATE user_auth
SET password_history = ?
WHERE id = ?
AND realm = 0
AND enabled = 'on'",
[$h, $user['id']]) !== false;
}
$reset_ok = $reset_ok && db_execute_prepared('INSERT IGNORE INTO user_log
(username, result, time, ip)
VALUES (?, 3, NOW(), ?)',
[$user['username'], get_client_addr()]) !== false;
$reset_ok = $reset_ok && db_execute_prepared("UPDATE user_auth
SET must_change_password = '', password = ?
WHERE id = ?",
[compat_password_hash($password, PASSWORD_DEFAULT), $user['id']]) !== false;
$reset_ok = $reset_ok && db_execute_prepared('DELETE FROM user_auth_reset_hashes
WHERE user_id = ?',
[$user['id']]) !== false;
$reset_ok = $reset_ok && db_execute_prepared('DELETE FROM user_auth_cache WHERE user_id = ?', [$user['id']]) !== false;
$reset_ok = $reset_ok && db_execute_prepared('DELETE FROM sessions WHERE user_id = ?', [$user['id']]) !== false;
if (!$reset_ok || !db_commit_transaction()) {
db_rollback_transaction();
$errorMessage = "<span class='badpassword_message'>" . __('Unable to securely consume the password reset link.') . '</span>';
$action = 'formidentity';
break;
}
raise_message('password_success');
header('Location: index.php');
exit;
} else {
$action = 'resetpassword';
$return = CACTI_PATH_URL . 'resetpassword.php';
}
break;
}
// show identity form or reset password form
if (api_plugin_hook_function('custom_password', OPER_MODE_NATIVE) == OPER_MODE_RESKIN) {
exit;
}
// Create tooltip for password complexity
$secpass_tooltip = "<span style='font-weight:normal;'>" . __('Password requirements include:') . '</span><br>';
$secpass_body = '';
if (read_config_option('secpass_minlen') > 0) {
$secpass_body .= __('Must be at least %d characters in length', read_config_option('secpass_minlen'));
}
if (read_config_option('secpass_reqmixcase') == 'on') {
$secpass_body .= ($secpass_body != '' ? '<br>' : '') . __('Must include mixed case');
}
if (read_config_option('secpass_reqnum') == 'on') {
$secpass_body .= ($secpass_body != '' ? '<br>' : '') . __('Must include at least 1 number');
}
if (read_config_option('secpass_reqspec') == 'on') {
$secpass_body .= ($secpass_body != '' ? '<br>' : '') . __('Must include at least 1 special character');
}
if (read_config_option('secpass_history') != '0') {
$secpass_body .= ($secpass_body != '' ? '<br>' : '') . __('Cannot be reused for %d password changes', read_config_option('secpass_history') + 1);
}
$secpass_tooltip .= $secpass_body;
$selectedTheme = get_selected_theme();
if ($action == 'formidentity') {
$title_message = __('Please enter your Cacti username or email address.');
} elseif ($action == 'formreset') {
$title_message = __('Please enter your new Cacti password.');
} else {
$title_message = __('Reset password problem.');
}
html_auth_header('reset_password', __('Reset Password'), __('Reset Password'), $title_message);
?>
<?php if ($action == 'formidentity') { ?>
<tr>
<td class='nowrap' colspan='2'>
<input type='text' class='ui-state-default ui-corner-all' id='identity' name='identity' size='25' maxlength='50'></td>
</tr>
<tr>
<td colspan='2' class='nowrap'>
<button type='submit' class='ui-button ui-corner-all ui-widget ui-state-active' value='send'><?php print __esc('Send reset token by email'); ?></button>
<button type='button' class='ui-button ui-corner-all ui-widget' onClick='document.location=<?php print json_encode((string) $return); ?>' value='return'><?php print __esc('Return'); ?></button>
</td>
</tr>
<tr>
<td colspan='2'>
<input type='hidden' name='action' value='resetrequest'>
<input type='hidden' name='hash' value='<?php print htmlerv('hash'); ?>'>
</td>
</tr>
<?php }
if ($action == 'formreset') {?>
<tr>
<td><?php print __('New password'); ?></td>
<td class='nowrap'>
<input type='password' class='ui-state-default ui-corner-all' id='password' name='password' autocomplete='off' size='15' maxlength='25' placeholder='********'><?php print display_tooltip($secpass_tooltip); ?>
</td>
</tr>
<tr>
<td><?php print __('Confirm password'); ?></td>
<td class='nowrap'>
<input type='password' class='ui-state-default ui-corner-all' id='password_confirm' name='password_confirm' autocomplete='off' size='15' maxlength='25' placeholder='********'>
</td>
</tr>
<tr>
<td colspan='2' class='nowrap'>
<button type='submit' class='ui-button ui-corner-all ui-widget ui-state-active' value='save'><?php print __esc('Save'); ?></button>
<button type='button' class='ui-button ui-corner-all ui-widget' onClick='document.location=<?php print json_encode((string) $return); ?>' value='return'><?php print __esc('Return'); ?></button>
</td>
</tr>
<tr>
<td colspan='2'>
<input type='hidden' name='action' value='resetpassword'>
<input type='hidden' name='hash' value='<?php print htmlerv('hash'); ?>'>
</td>
</tr>
<?php
}
$secpass_minlen = read_config_option('secpass_minlen');
html_auth_footer('reset_password', $errorMessage, "
<script type='text/javascript'>
$(function() {
if ($('#identity').length) {
$('#identity').focus();
} else {
$('#password').focus();
}
/* clear passwords */
$('#password').val('');
$('#password_confirm').val('');
});
</script>");