You've already forked postfixadmin
mirror of
https://github.com/postfixadmin/postfixadmin.git
synced 2025-07-31 10:04:20 +03:00
Harden password reset process
The improvements are: - Die with an explicit message when a user is trying to reset his lost password and the option is disabled in config - Redirect user to main page after password change using relative URL - Don't leak info whether user exists or has recovery info defined - Throttle password reset requests to prevent brute force attacks - Show phone/alt email fields in mailbox/admin edit form only when the password reset option is enabled - Make database upgrade code compatible with other databases types - Use the existing password generator to generate OTP. It is now stored in database, unique to each user, valid only for 1 hour and can only by used once.
This commit is contained in:
committed by
Adrien Crivelli
parent
8bb6000072
commit
ffb84283c2
@ -129,10 +129,6 @@ $CONF['admin_email'] = '';
|
||||
// This will be used as signature in notification messages
|
||||
$CONF['admin_name'] = 'Postmaster';
|
||||
|
||||
// Site admin phone number
|
||||
// This will be used if a user cannot access his/her email and needs support
|
||||
$CONF['admin_phone'] = '';
|
||||
|
||||
// Mail Server
|
||||
// Hostname (FQDN) of your mail server.
|
||||
// This is used to send email to Postfix in order to create mailboxes.
|
||||
@ -588,16 +584,34 @@ $CONF['create_mailbox_subdirs_hostoptions'] = array();
|
||||
|
||||
// Optional:
|
||||
// Allows a user to reset his forgotten password with a code sent by email/SMS
|
||||
$CONF['forgotten_user_password_reset'] = false; # INSECURE, DO NOT ENABLE! See https://github.com/postfixadmin/postfixadmin/pull/18 for details
|
||||
$CONF['forgotten_user_password_reset'] = true;
|
||||
// Allows an admin to reset his forgotten password with a code sent by email/SMS
|
||||
$CONF['forgotten_admin_password_reset'] = false; # INSECURE, DO NOT ENABLE! see https://github.com/postfixadmin/postfixadmin/pull/18 for details
|
||||
$CONF['forgotten_admin_password_reset'] = false;
|
||||
|
||||
// Clickatell gateway to send SMS code for password reset
|
||||
// API type: HTTP
|
||||
$CONF['clickatell_api_id'] = '';
|
||||
$CONF['clickatell_user'] = '';
|
||||
$CONF['clickatell_password'] = '';
|
||||
$CONF['clickatell_sender'] = '';
|
||||
// Name of the function to send a SMS
|
||||
// Please use a name that begins with "x_" to prevent collisions
|
||||
// This function must accept 2 parameters: phone number and message,
|
||||
// and return true on success or false on failure
|
||||
$CONF['sms_send_function'] = '';
|
||||
|
||||
/*
|
||||
// Example of send SMS function using Clickatell HTTP API
|
||||
function x_send_sms_clickatell($to, $message) {
|
||||
|
||||
$clickatell_api_id = 'CHANGEME';
|
||||
$clickatell_user = 'CHANGEME';
|
||||
$clickatell_password = 'CHANGEME';
|
||||
$clickatell_sender = 'CHANGEME';
|
||||
|
||||
$url = 'https://api.clickatell.com/http/sendmsg?api_id=%s&user=%s&password=%s&to=%s&from=%s&text=%s';
|
||||
|
||||
$url = sprintf($url, $clickatell_api_id, $clickatell_user, $clickatell_password, $to, $clickatell_sender, urlencode($message));
|
||||
|
||||
$result = file_get_contents($url);
|
||||
|
||||
return $result !== false;
|
||||
}
|
||||
*/
|
||||
|
||||
// Theme Config
|
||||
// Specify your own logo and CSS file
|
||||
|
@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
$version = '3.1';
|
||||
$min_db_version = 1836; # update (at least) before a release with the latest function numbrer in upgrade.php
|
||||
$min_db_version = 1837; # update (at least) before a release with the latest function numbrer in upgrade.php
|
||||
|
||||
/**
|
||||
* check_session
|
||||
@ -89,6 +89,23 @@ function authentication_require_role($role) {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a user or admin session
|
||||
*
|
||||
* @param String $username the user or admin name
|
||||
* @param boolean $is_admin true if the user is an admin, false otherwise
|
||||
* @return boolean true on success
|
||||
*/
|
||||
function init_session($username, $is_admin = false) {
|
||||
$status = session_regenerate_id(true);
|
||||
$_SESSION['sessid'] = array();
|
||||
$_SESSION['sessid']['roles'] = array();
|
||||
$_SESSION['sessid']['roles'][] = $is_admin ? 'admin' : 'user';
|
||||
$_SESSION['sessid']['username'] = $username;
|
||||
$_SESSION['PFA_token'] = md5(uniqid(rand(), true));
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an error message for display on the next page that is rendered.
|
||||
@ -806,7 +823,7 @@ function encode_header ($string, $default_charset = "utf-8") {
|
||||
//
|
||||
function generate_password () {
|
||||
// length of the generated password
|
||||
$length = 8;
|
||||
$length = 12;
|
||||
|
||||
// define possible characters
|
||||
$possible = "2345678923456789abcdefghijkmnpqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ"; # skip 0 and 1 to avoid confusion with O and l
|
||||
@ -1942,21 +1959,4 @@ function getRemoteAddr() {
|
||||
return $REMOTE_ADDR;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a hash for a username valid for one day
|
||||
*
|
||||
* @param String $username user name
|
||||
* @return String password recovery code
|
||||
*/
|
||||
function getPasswordRecoveryCode($username)
|
||||
{
|
||||
$username = trim(strtolower($username));
|
||||
$date = date('Y-m-d');
|
||||
|
||||
$code = substr(strtoupper(md5('SECRET SALTING PHRASE' . $username . $date)), 0, 6);
|
||||
|
||||
return $code;
|
||||
}
|
||||
|
||||
/* vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */
|
||||
|
@ -180,11 +180,9 @@ $PALANG['pPassword_result_success'] = 'The password for %s has been changed.';
|
||||
|
||||
$PALANG['pPassword_recovery_title'] = 'Follow the instructions to reset your password.';
|
||||
$PALANG['pPassword_recovery_button'] = 'Send me the code';
|
||||
$PALANG['pPassword_recovery_email_body'] = "Hello,\n\nUse the following link to change your email password:\n%s\n\nRegards,\n\n" . $CONF['admin_name'];
|
||||
$PALANG['pPassword_recovery_email_sent'] = 'An email was sent to:';
|
||||
$PALANG['pPassword_recovery_email_body'] = "Hello,\n\nUse the following link to change your email password :\n%s\n\nRegards,\n\n" . $CONF['admin_name'];
|
||||
$PALANG['pPassword_recovery_sms_body'] = "Hello,\nThe code to change your password is: %s\n" . $CONF['admin_name'];
|
||||
$PALANG['pPassword_recovery_sms_sent'] = 'An SMS was sent to:';
|
||||
$PALANG['pPassword_recovery_no_alternative'] = 'No alternative contact info were found. Please contact the support at ' . $CONF['admin_email'] . 'or by phone to ' . $CONF['admin_phone'];
|
||||
$PALANG['pPassword_recovery_processed'] = "We processed your request. If you entered a valid username, you'll receive an email/SMS with a password code.";
|
||||
$PALANG['pPassword_password_code'] = 'Code sent by email/SMS';
|
||||
$PALANG['pPassword_code_text_error'] = 'Invalid code';
|
||||
|
||||
|
@ -179,10 +179,8 @@ $PALANG['pPassword_result_success'] = 'Le mot de passe de %s a été changé !';
|
||||
$PALANG['pPassword_recovery_title'] = 'Suivez les instructions pour réinitialiser votre mot de passe.';
|
||||
$PALANG['pPassword_recovery_button'] = 'Envoyez-moi le code';
|
||||
$PALANG['pPassword_recovery_email_body'] = "Bonjour,\n\nUtilisez le lien suivant pour modifier votre mot de passe :\n%s\n\nSalutations,\n\n" . $CONF['admin_name'];
|
||||
$PALANG['pPassword_recovery_email_sent'] = 'Un code a été envoyé à :';
|
||||
$PALANG['pPassword_recovery_sms_body'] = "Bonjour,\nLe code pour modifier votre mot de passe: %s\n" . $CONF['admin_name'];
|
||||
$PALANG['pPassword_recovery_sms_sent'] = 'Un code a été envoyé par SMS à :';
|
||||
$PALANG['pPassword_recovery_no_alternative'] = "Aucun moyen de contact alternatif n'a été trouvé. Contactez le support à " . $CONF['admin_email'] . ' ou par téléphone ' . $CONF['admin_phone'];
|
||||
$PALANG['pPassword_recovery_processed'] = "Nous avons traité votre demande. Si le nom d'utilisateur que vous avez saisi est valide, vous recevrez par e-mail/SMS un code de réinitialisation du mot de passe.";
|
||||
$PALANG['pPassword_password_code'] = 'Code reçu par email/SMS';
|
||||
$PALANG['pPassword_code_text_error'] = 'Code invalide';
|
||||
|
||||
@ -339,7 +337,7 @@ $PALANG['pBroadcast_title'] = 'Envoyer un message général';
|
||||
$PALANG['pBroadcast_name'] = 'Votre nom';
|
||||
$PALANG['pBroadcast_success'] = 'Votre message général a été envoyé.';
|
||||
$PALANG['pAdminMenu_broadcast_message'] = 'message général';
|
||||
$PALANG['pBroadcast_error_empty'] = 'Les champs "Nom", "Sujet" et "Message" ne peuvent pas être vides!';
|
||||
$PALANG['pBroadcast_error_empty'] = 'Les champs "Nom", "Sujet" et "Message" ne peuvent pas être vides !';
|
||||
$PALANG['broadcast_mailboxes_only'] = 'Only send to mailboxes'; # XXX
|
||||
$PALANG['broadcast_to_domains'] = 'Send to domains:'; # XXX
|
||||
$PALANG['pStatus_undeliverable'] = 'Non délivrable ';
|
||||
|
@ -52,13 +52,8 @@ if ($_SERVER['REQUEST_METHOD'] == "POST")
|
||||
|
||||
$h = new AdminHandler;
|
||||
if ( $h->login($fUsername, $fPassword) ) {
|
||||
session_regenerate_id(true);
|
||||
$_SESSION['sessid'] = array();
|
||||
$_SESSION['sessid']['roles'] = array();
|
||||
$_SESSION['sessid']['roles'][] = 'admin';
|
||||
$_SESSION['sessid']['username'] = $fUsername;
|
||||
|
||||
$_SESSION['PFA_token'] = md5(uniqid(rand(), true));
|
||||
init_session($fUsername, true);
|
||||
|
||||
# they've logged in, so see if they are a domain admin, as well.
|
||||
|
||||
|
@ -35,6 +35,8 @@ class AdminHandler extends PFAHandler {
|
||||
$domains_grouped = 'group_concat(domain)';
|
||||
}
|
||||
|
||||
$passwordReset = Config::read('forgotten_admin_password_reset');
|
||||
|
||||
$this->struct=array(
|
||||
# field name allow display in... type $PALANG label $PALANG description default / options / ...
|
||||
# editing? form list
|
||||
@ -47,10 +49,6 @@ class AdminHandler extends PFAHandler {
|
||||
/*select*/ 'password as password2'
|
||||
),
|
||||
|
||||
'phone' => pacol( 1, 1, 0, 'text', 'pCreate_mailbox_phone', 'pCreate_mailbox_phone_desc', ''),
|
||||
|
||||
'email_other' => pacol( 1, 1, 0, 'mail', 'pCreate_mailbox_email', 'pCreate_mailbox_email_desc', ''),
|
||||
|
||||
'superadmin' => pacol( 1, 1, 0, 'bool', 'super_admin' , 'super_admin_desc' , 0
|
||||
# TODO: (finally) replace the ALL domain with a column in the admin table
|
||||
# TODO: current status: 'superadmin' column exists and is written when storing an admin with AdminHandler,
|
||||
@ -78,6 +76,10 @@ class AdminHandler extends PFAHandler {
|
||||
' ) AS __domain on username = __domain_username'),
|
||||
|
||||
'active' => pacol( 1, 1, 1, 'bool', 'active' , '' , 1 ),
|
||||
'phone' => pacol( 1, $passwordReset, 0, 'text', 'pCreate_mailbox_phone', 'pCreate_mailbox_phone_desc', ''),
|
||||
'email_other' => pacol( 1, $passwordReset, 0, 'mail', 'pCreate_mailbox_email', 'pCreate_mailbox_email_desc', ''),
|
||||
'token' => pacol( 1, 0, 0, 'text', '' , '' ),
|
||||
'token_validity' => pacol( 1, 0, 0, 'ts', '' , '' ),
|
||||
'created' => pacol( 0, 0, 0, 'ts', 'created' , '' ),
|
||||
'modified' => pacol( 0, 0, 1, 'ts', 'last_modified' , '' ),
|
||||
);
|
||||
|
@ -13,6 +13,7 @@ class MailboxHandler extends PFAHandler {
|
||||
|
||||
# init $this->struct, $this->db_table and $this->id_field
|
||||
protected function initStruct() {
|
||||
$passwordReset = Config::read('forgotten_user_password_reset');
|
||||
$this->struct=array(
|
||||
# field name allow display in... type $PALANG label $PALANG description default / options / ...
|
||||
# editing? form list
|
||||
@ -35,11 +36,13 @@ class MailboxHandler extends PFAHandler {
|
||||
# read_from_db_postprocess() also sets 'quotabytes' for use in init()
|
||||
# TODO: read used quota from quota/quota2 table
|
||||
'active' => pacol( 1, 1, 1, 'bool', 'active' , '' , 1 ),
|
||||
'phone' => pacol( 1, 1, 0, 'text', 'pCreate_mailbox_phone' , 'pCreate_mailbox_phone_desc' , ''),
|
||||
'email_other' => pacol( 1, 1, 0, 'mail', 'pCreate_mailbox_email' , 'pCreate_mailbox_email_desc' , ''),
|
||||
'welcome_mail' => pacol( $this->new, $this->new, 0, 'bool', 'pCreate_mailbox_mail' , '' , 1,
|
||||
/*options*/ '',
|
||||
/*not_in_db*/ 1 ),
|
||||
'phone' => pacol( 1, $passwordReset, 0, 'text', 'pCreate_mailbox_phone' , 'pCreate_mailbox_phone_desc' , ''),
|
||||
'email_other' => pacol( 1, $passwordReset, 0, 'mail', 'pCreate_mailbox_email' , 'pCreate_mailbox_email_desc' , ''),
|
||||
'token' => pacol( 1, 0, 0, 'text', '' , '' ),
|
||||
'token_validity'=> pacol( 1, 0, 0, 'ts', '' , '' ),
|
||||
'created' => pacol( 0, 0, 1, 'ts', 'created' , '' ),
|
||||
'modified' => pacol( 0, 0, 1, 'ts', 'last_modified' , '' ),
|
||||
# TODO: add virtual 'notified' column and allow to display who received a vacation response?
|
||||
|
@ -780,6 +780,55 @@ abstract class PFAHandler {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate and store a unique password reset token valid for one hour
|
||||
* @param string $username
|
||||
* @return false|string
|
||||
*/
|
||||
function getPasswordRecoveryCode($username) {
|
||||
if ($this->init($username)) {
|
||||
$token = generate_password();
|
||||
$table = table_by_key($this->db_table);
|
||||
$updatedRows = db_update($table, $this->id_field, $username, array(
|
||||
'token' => pacrypt($token),
|
||||
'token_validity' => date("Y-m-d H:i:s", strtotime('+ 1 hour')),
|
||||
));
|
||||
|
||||
if ($updatedRows == 1) {
|
||||
return $token;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify user's one time password reset token
|
||||
* @param string $username
|
||||
* @param string $token
|
||||
* @return boolean true on success (i.e. code matches etc)
|
||||
*/
|
||||
public function checkPasswordRecoveryCode($username, $token) {
|
||||
$username = escape_string($username);
|
||||
|
||||
$table = table_by_key($this->db_table);
|
||||
$active = db_get_boolean(True);
|
||||
$query = "SELECT token FROM $table WHERE " . $this->id_field . "='$username' AND token <> '' AND active='$active' AND NOW() < token_validity";
|
||||
|
||||
$result = db_query ($query);
|
||||
if ($result['rows'] == 1) {
|
||||
$row = db_array($result['result']);
|
||||
$crypt_token = pacrypt($token, $row['token']);
|
||||
|
||||
if($row['token'] == $crypt_token) {
|
||||
db_update($table, $this->id_field, $username, array(
|
||||
'token' => '',
|
||||
'token_validity' => '2000-01-01 00:00:00',
|
||||
));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**************************************************************************
|
||||
* functions to read protected variables
|
||||
|
@ -15,8 +15,11 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label>{$PALANG.password}:</label></td>
|
||||
<td><input class="flat" type="password" name="fPassword" />{if $forgotten_password_reset}<br/>
|
||||
<a href="password-recover.php">{$PALANG.pUsersLogin_password_recover}</a>{/if}</td>
|
||||
<td><input class="flat" type="password" name="fPassword" />
|
||||
{if $forgotten_password_reset}
|
||||
<br/><a href="password-recover.php">{$PALANG.pUsersLogin_password_recover}</a>
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><label>{$PALANG.pLogin_language}:</label></td>
|
||||
|
12
upgrade.php
12
upgrade.php
@ -1684,16 +1684,14 @@ function upgrade_1836_mysql() {
|
||||
|
||||
}
|
||||
|
||||
function upgrade_1837_mysql() {
|
||||
function upgrade_1837() {
|
||||
# alternative contact means to reset a forgotten password
|
||||
foreach(array('admin', 'mailbox') as $table_to_change) {
|
||||
$table = table_by_key($table_to_change);
|
||||
if(!_mysql_field_exists($table, 'phone')) {
|
||||
db_query_parsed("ALTER TABLE `$table` ADD COLUMN `phone` varchar(30) NOT NULL DEFAULT ''");
|
||||
}
|
||||
if(!_mysql_field_exists($table, 'email_other')) {
|
||||
db_query_parsed("ALTER TABLE `$table` ADD COLUMN `email_other` varchar(255) NOT NULL DEFAULT ''");
|
||||
}
|
||||
_db_add_field($table, 'phone', "varchar(30) {UTF-8} NOT NULL DEFAULT ''", 'active');
|
||||
_db_add_field($table, 'email_other', "varchar(255) {UTF-8} NOT NULL DEFAULT ''", 'phone');
|
||||
_db_add_field($table, 'token', "varchar(255) {UTF-8} NOT NULL DEFAULT ''", 'email_other');
|
||||
_db_add_field($table, 'token_validity', '{DATETIME}', 'token');
|
||||
}
|
||||
}
|
||||
# TODO MySQL:
|
||||
|
@ -48,12 +48,9 @@ if ($_SERVER['REQUEST_METHOD'] == "POST")
|
||||
|
||||
$h = new MailboxHandler();
|
||||
if($h->login($fUsername, $fPassword)) {
|
||||
session_regenerate_id(true);
|
||||
$_SESSION['sessid'] = array();
|
||||
$_SESSION['sessid']['roles'] = array();
|
||||
$_SESSION['sessid']['roles'][] = 'user';
|
||||
$_SESSION['sessid']['username'] = $fUsername;
|
||||
$_SESSION['PFA_token'] = md5(uniqid(rand(), true));
|
||||
|
||||
init_session($fUsername, false);
|
||||
|
||||
header("Location: main.php");
|
||||
exit;
|
||||
} else {
|
||||
|
@ -34,20 +34,16 @@ if (preg_match('/\/users\//', $_SERVER['REQUEST_URI'])) {
|
||||
}
|
||||
require_once($rel_path . 'common.php');
|
||||
|
||||
if ($context == 'admin' && !Config::read('forgotten_admin_password_reset') || $context == 'users' && !Config::read('forgotten_user_password_reset'))
|
||||
{
|
||||
header('HTTP/1.0 403 Forbidden');
|
||||
exit(0);
|
||||
if ($context === 'admin' && !Config::read('forgotten_admin_password_reset') || $context === 'users' && !Config::read('forgotten_user_password_reset')) {
|
||||
die('Password reset is disabled by configuration option: forgotten_admin_password_reset');
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'GET')
|
||||
{
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
$tUsername = safeget('username');
|
||||
$tCode = safeget('code');
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'POST')
|
||||
{
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if(safepost('fCancel')) {
|
||||
header('Location: main.php');
|
||||
exit(0);
|
||||
@ -57,37 +53,33 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST')
|
||||
$fPassword2 = safepost('fPassword2');
|
||||
|
||||
$tUsername = safepost('fUsername');
|
||||
$tCode = trim(strtoupper(safepost('fCode')));
|
||||
$tCode = trim(safepost('fCode'));
|
||||
|
||||
if (empty($fPassword) or ($fPassword != $fPassword2)) {
|
||||
$error = true;
|
||||
flash_error(Config::lang('pPassword_password_text_error'));
|
||||
} elseif (trim(strtoupper($tCode) != getPasswordRecoveryCode($tUsername))) {
|
||||
flash_error(Config::lang('pPassword_code_text_error'));
|
||||
} else {
|
||||
session_regenerate_id();
|
||||
$_SESSION['sessid']['username'] = $tUsername;
|
||||
if ($context == 'users') {
|
||||
$_SESSION['sessid']['roles'][] = 'user';
|
||||
$handler = new MailboxHandler;
|
||||
|
||||
$handler = $context === 'admin' ? new AdminHandler : new MailboxHandler;
|
||||
if (!$handler->checkPasswordRecoveryCode($tUsername, $tCode)) {
|
||||
flash_error(Config::lang('pPassword_code_text_error'));
|
||||
} else {
|
||||
$_SESSION['sessid']['roles'][] = 'admin';
|
||||
$handler = new AdminHandler;
|
||||
}
|
||||
if (!$handler->init($tUsername)) {
|
||||
flash_error($handler->errormsg);
|
||||
} else {
|
||||
$values = $handler->result;
|
||||
$values[$handler->getId_field()] = $tUsername;
|
||||
$values['password'] = $fPassword;
|
||||
$values['password2'] = $fPassword2;
|
||||
if ($handler->set($values) && $handler->store()) {
|
||||
flash_info(Config::lang_f('pPassword_result_success', $tUsername));
|
||||
header('Location: ' . dirname($_SERVER['REQUEST_URI']) . '/main.php');
|
||||
exit(0);
|
||||
|
||||
init_session($tUsername, $context === 'admin');
|
||||
if (!$handler->init($tUsername)) {
|
||||
flash_error($handler->errormsg);
|
||||
} else {
|
||||
foreach($handler->errormsg as $msg) {
|
||||
flash_error($msg);
|
||||
$values = $handler->result;
|
||||
$values['password'] = $fPassword;
|
||||
$values['password2'] = $fPassword2;
|
||||
if ($handler->set($values) && $handler->store()) {
|
||||
flash_info(Config::lang_f('pPassword_result_success', $tUsername));
|
||||
header('Location: main.php');
|
||||
exit(0);
|
||||
} else {
|
||||
foreach($handler->errormsg as $msg) {
|
||||
flash_error($msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,86 +34,62 @@ if (preg_match('/\/users\//', $_SERVER['REQUEST_URI'])) {
|
||||
}
|
||||
require_once($rel_path . 'common.php');
|
||||
|
||||
if ($context == 'admin' && !Config::read('forgotten_admin_password_reset') || $context == 'users' && !Config::read('forgotten_user_password_reset'))
|
||||
{
|
||||
header('HTTP/1.0 403 Forbidden');
|
||||
exit(0);
|
||||
if ($context === 'admin' && !Config::read('forgotten_admin_password_reset') || $context === 'users' && !Config::read('forgotten_user_password_reset')) {
|
||||
die('Password reset is disabled by configuration option: forgotten_admin_password_reset');
|
||||
}
|
||||
|
||||
function sendCodebyEmail($to, $username, $code)
|
||||
{
|
||||
$fHeaders = "To: " . $to . PHP_EOL;
|
||||
$fHeaders .= "From: " . Config::read('admin_email') . PHP_EOL;
|
||||
$fHeaders .= "Subject: " . encode_header(Config::Lang('pPassword_welcome')) . PHP_EOL;
|
||||
$fHeaders .= "MIME-Version: 1.0" . PHP_EOL;
|
||||
$fHeaders .= "Content-Type: text/plain; charset=utf-8" . PHP_EOL;
|
||||
$fHeaders .= "Content-Transfer-Encoding: 8bit" . PHP_EOL . PHP_EOL;
|
||||
|
||||
function sendCodebyEmail($to, $username, $code) {
|
||||
$url = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'] . dirname($_SERVER['REQUEST_URI']) . '/password-change.php?username=' . urlencode($username) . '&code=' . $code;
|
||||
$fHeaders .= Config::lang_f('pPassword_recovery_email_body', $url);
|
||||
|
||||
return smtp_mail($to, Config::read('admin_email') , $fHeaders);
|
||||
return smtp_mail($to, Config::read('admin_email'), Config::Lang('pPassword_welcome'), Config::lang_f('pPassword_recovery_email_body', $url));
|
||||
}
|
||||
|
||||
function sendCodebySMS($to, $username, $code)
|
||||
{
|
||||
function sendCodebySMS($to, $username, $code) {
|
||||
$text = Config::lang_f('pPassword_recovery_sms_body', $code);
|
||||
|
||||
$url = 'https://api.clickatell.com/http/sendmsg?api_id=' . Config::read('clickatell_api_id') . '&user=' . Config::read('clickatell_user') . '&password=' . Config::read('clickatell_password') . "&to=$to" . '&from=' . Config::read('clickatell_sender') . '&text=' . urlencode($text);
|
||||
if (Config::read('sms_send_function') && is_callable(Config::read('sms_send_function'))) {
|
||||
$result = call_user_func(Config::read('sms_send_function'), $to, $text);
|
||||
return $result !== false;
|
||||
}
|
||||
|
||||
$result = file_get_contents($url);
|
||||
|
||||
return $result !== false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] == "POST")
|
||||
{
|
||||
if ($_SERVER['REQUEST_METHOD'] === "POST") {
|
||||
$start_time = microtime();
|
||||
$tUsername = escape_string (safepost('fUsername'));
|
||||
$table = table_by_key($context == 'users' ? 'mailbox' : 'admin');
|
||||
$result = db_query("SELECT * FROM `$table` WHERE username='$tUsername'");
|
||||
$eMessage = '';
|
||||
if ($result['rows'] == 1)
|
||||
{
|
||||
$handler = $context === 'admin' ? new AdminHandler : new MailboxHandler;
|
||||
$token = $handler->getPasswordRecoveryCode($tUsername);
|
||||
if ($token !== false) {
|
||||
|
||||
$table = table_by_key($context === 'users' ? 'mailbox' : 'admin');
|
||||
$result = db_query("SELECT * FROM `$table` WHERE username='$tUsername'");
|
||||
$row = db_array($result['result']);
|
||||
$code = getPasswordRecoveryCode($tUsername);
|
||||
|
||||
$email_other = trim($row['email_other']);
|
||||
$phone = trim($row['phone']);
|
||||
|
||||
// An active session is required to propagate flash messages to redirected page
|
||||
if ($email_other)
|
||||
{
|
||||
// send email
|
||||
if (sendCodeByEmail($email_other, $tUsername, $code))
|
||||
{
|
||||
flash_info(Config::Lang('pPassword_recovery_email_sent') . ' ' . $email_other);
|
||||
}
|
||||
if ($email_other) {
|
||||
sendCodeByEmail($email_other, $tUsername, $token);
|
||||
}
|
||||
|
||||
if ($phone)
|
||||
{
|
||||
// send phone
|
||||
if (sendCodeBySMS($phone, $tUsername, $code))
|
||||
{
|
||||
flash_info(Config::Lang('pPassword_recovery_sms_sent') . ' ' . $phone);
|
||||
}
|
||||
if ($phone) {
|
||||
sendCodeBySMS($phone, $tUsername, $token);
|
||||
}
|
||||
|
||||
if ($email_other || $phone)
|
||||
{
|
||||
// session_regenerate_id();
|
||||
if ($email_other || $phone) {
|
||||
header("Location: password-change.php?username=" . $tUsername);
|
||||
exit(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
flash_error(Config::Lang('pPassword_recovery_no_alternative'));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
flash_error(Config::Lang('pCreate_mailbox_username_text_error1'));
|
||||
|
||||
// throttle password reset requests to prevent brute force attack
|
||||
$elapsed_time = microtime() - $start_time;
|
||||
if ($elapsed_time < 2 * pow(10, 6)) {
|
||||
usleep(2 * pow(10, 6) - $elapsed_time);
|
||||
}
|
||||
|
||||
flash_info(Config::Lang('pPassword_recovery_processed'));
|
||||
}
|
||||
|
||||
$smarty->assign ('language_selector', language_selector(), false);
|
||||
|
Reference in New Issue
Block a user