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.
pull/79/head
Sylvain Tissot 8 years ago committed by Adrien Crivelli
parent 8bb6000072
commit ffb84283c2
No known key found for this signature in database
GPG Key ID: B182FD79DC6DE92E

@ -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
// 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'] = '';
$CONF['forgotten_admin_password_reset'] = false;
// 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: */

@ -154,7 +154,7 @@ $PALANG['pCreate_mailbox_mail'] = 'Send Welcome mail';
$PALANG['pCreate_mailbox_result_error'] = 'Creating the mailbox %s failed!';
$PALANG['pCreate_mailbox_result_success'] = 'The mailbox %s has been added to the mailbox table.';
$PALANG['pCreate_mailbox_result_succes_nosubfolders'] = 'The mailbox %s has been added to the mailbox table, but none (or only some) of the predefined sub-folders could be created.';
$PALANG['mailbox_updated'] = "The mailbox %s has been updated.";
$PALANG['mailbox_updated'] = "The mailbox %s has been updated.";
$PALANG['mailbox_update_failed'] = "Updating the mailbox %s failed!";
$PALANG['pEdit_mailbox_welcome'] = 'Edit a mailbox for your domain.';
@ -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>

@ -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 {

@ -1,16 +1,16 @@
<?php
/**
* Postfix Admin
*
* LICENSE
* This source file is subject to the GPL license that is bundled with
* this package in the file LICENSE.TXT.
*
* Further details on the project are available at http://postfixadmin.sf.net
*
* @version $Id$
* @license GNU GPL v2 or later.
*
/**
* Postfix Admin
*
* LICENSE
* This source file is subject to the GPL license that is bundled with
* this package in the file LICENSE.TXT.
*
* Further details on the project are available at http://postfixadmin.sf.net
*
* @version $Id$
* @license GNU GPL v2 or later.
*
* File: password-change.php
* Used by users and admins to change their forgotten login password.
* Template File: password-change.tpl
@ -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;
} else {
$_SESSION['sessid']['roles'][] = 'admin';
$handler = new AdminHandler;
}
if (!$handler->init($tUsername)) {
flash_error($handler->errormsg);
$handler = $context === 'admin' ? new AdminHandler : new MailboxHandler;
if (!$handler->checkPasswordRecoveryCode($tUsername, $tCode)) {
flash_error(Config::lang('pPassword_code_text_error'));
} 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);
$result = file_get_contents($url);
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;
}
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);

Loading…
Cancel
Save