Помимо смены пароля необходимо предусмотреть часто возникающую ситуацию, когда пользователь попросту забывает пароль. Обратите внимание, что титульная страница, login.php, содержит ссылку Забыли пароль?, предназначенную для подобных случаев. Ссылка запускает сценарий forgot_form.php, который использует функции вывода для отображения формы.
forgot_form.php — форма, в которой пользователь может запросить переустановку и отправку пароля по электронной почте
<?php
require_once('bookmark_fns.php');
$title = "Переустановка пароля";
$description = "Переустановка пароля";
$keywords = "переустановка пароля";
do_html_header($title, $description, $keywords);
blok_left();
echo'<div id="blok_right">';
echo '<div class="blok_text">';
echo'<div class="text_top" align=left>';
do_html_heading($title);
display_forgot_form();
echo '</div></div></div>';
do_html_footer();
?>
Нажав на ссылку Забыли пароль? в веб-браузере отобразится вот такая форма.
Этот сценарий очень прост. В нем используются лишь функции вывода. После отправки формы вызывается сценарий forgotjasswd.php, который заслуживает специального рассмотрения.
forgot_passwd.php — этот сценарий переустанавливает пароль, выбирая для него случайное значение, и отправляет новую версию пользователю по электронной почте.
<?php
require_once('bookmark_fns.php');
$title = "Переустановка пароля";
$description = "Переустановка пароля";
$keywords = "переустановка пароля";
do_html_header($title, $description, $keywords);
blok_left();
echo'<div id="blok_right">';
echo '<div class="blok_text">';
echo'<div class="text_top" align=left>';
do_html_heading($title);
// Создать короткие имена переменных
$username = $_POST['username'];
try
{
$password = reset_password($username);
notify_password($username, $password);
echo 'Новый пароль отправлен по адресу электронной почты, '
.'который вы указали при регистрации.';
}
catch (Exception $e)
{
echo 'Пароль не может быть переустановлен. '
.'Пожалуйста, повторите попытку позже.';
}
do_html_url('login.php', 'Вход');
echo '</div></div></div>';
do_html_footer();
?>
В сценарии задействованы две основных функции: reset_password() и notily_password(). Рассмотрим их по очереди.
Функция reset_password() генерирует случайный пароль и помещает его в базу данных.
Функция reset_password() библиотеки user_auth_fns.php — этот сценарий присваивает паролю случайное значение и отправляет его пользователю по электронной почте
function reset_password($username)
// Устанавливает случайное значение для пароля.
// Возвращает новый пароль либо значение false в случае ошибки
{
// Получить случайное слово из словаря длиной от 6 до 13 символов
$new_password = get_random_word(6, 13);
if($new_password == false)
throw new Exception('Невозможно сгенерировать новый пароль.');
// Добавить к нему число от 0 до 999
// с целью небольшого улучшения
srand ((double) microtime() * 1000000);
$rand_number = rand(0, 999);
$new_password .= $rand_number;
// Изменить пароль в базе данных или вернуть значение false
$conn = db_connect();
$result = $conn->query( "update user
set passwd = sha1('$new_password')
where username = '$username'");
if (!$result)
throw new Exception('Невозможно изменить пароль.'); // Пароль не изменен
else
return $new_password; // Пароль успешно изменен
}
Эта функция генерирует случайный пароль, получив случайное слово из словаря с помощью функции get_random_word() и добавив к нему случайное число от 0 до 999. Функция get_random_word() также содержится в библиотеке user_auth_fns.php.
Функция get_random_word() библиотеки user_auth_fns.php — получает случайное слово из словаря, используемое для генерации пароля
function get_random_word($min_length, $max_length)
//Извлекает случайное слово из словаря в заданном диапазоне
// длины и возвращает его в качестве результата
{
// Сгенерировать случайное слово
$word = '';
// Не забудте изменить этот путь на тот,
//который будет соответствовать вашей системе
$dictionary = '/usr/dict/words'; // словарь ispell
$fp = @fopen($dictionary, 'r');
if(!$fp)
return false;
$size = filesize($dictionary);
// перейти на случайную позицию в словаре
srand ((double) microtime() * 1000000);
$rand_location = rand(0, $size);
fseek($fp, $rand_location);
// Получить из файла словаря следующее полное слово допустимой длины
while (strlen($word)< $min_length || strlen($word)>$max_length || strstr($word, "'"))
{
if (feof($fp))
fseek($fp, 0); // если достигнут конец файла словря, перейти на его начало
$word = fgets($fp, 80); // пропуская первое слово, поскольку оно может оказаться неполным
$word = fgets($fp, 80); //потенциальный пароль
};
$word=trim($word); // выполнить усеченное завершение символов \n в результате функции fgets
return $word;
}
Функция работает только при наличии словаря. В системе UNIX встроенная программа проверки орфографии ispell укомплектована словарем, который обычно содержится в каталоге /usr/dist/words/, как в нашем примере, или же в каталоге /usr/share/dict/words. Если вам не удалось отыскать файл словаря ни в одном из упомянутых мест, в большинстве систем имеется возможность поискать его, выбрав следующую команду:
$ locate dist/words
Если используется другая операционная система либо нет желания устанавливать словарь, не стоит беспокоиться. Список слов, используемой программой ispell, можно загрузить из следующего сайта:
http://wordlist.sourceforge.net/
Этот сайт содержит словари на многих языках (в т.ч. на английском и отличных от английского). Поэтому для получения случайного слова, скажем, на норвежском языке или эсперанто, можно загрузить один из соответствующих словарей. В файлах словарей каждое слово содержится в отдельной строке, а разделителями служат символы перевода строки.
Чтобы получить случайное слово из файла, выбирается случайная позиция в диапазоне от 0 до значения размера файла, из которого выполняется чтение. В таком случае, вероятнее всего, будет прочитана часть слова, поэтому текущая строка пропускается и выбирается следующее слово путем двухкратного обращения к функции fgets().
Эта функция имеет две интересных особенности. Во-первых, если в процессе поиска слова достигается конец файла, выполняется переход на его начало:
if (feof($fp))
fseek($fp, 0); // если достигнут конец файла словря, перейти на его начало
Во-вторых, выполняется поиск слова определенной длины. Проверяется длина каждого слова, извлекаемого из словаря. Если она находится в диапазоне значений от $min_length до $max_length, поиск продолжается. В тоже время, выполняется анализ слова вместе с апострофами (одинарными кавычками), которые оно может содержать. Конечно, мы моглибы отменить их перед использованием слова, однако тем самым несколько усложнили бы процесс получения следующего слова.
Вернемся к функции reset_password(). После того как будет сгенерирован новый пароль, произойдет обновление базы данных и возврат нового пароля в главный сценарий. Затем он передается в функцию notify_password(), которая отправит его пользователю по электронной почте.
Функция notify_password() из библиотеки user_auth_fns.php — отправляет пользователю новый пароль по электронной почте
function notify_password($username, $password)
// Уведомляет пользователя о том, что его пароль изменен
{
$conn = db_connect();
$result = $conn->query("select email from user
where username='$username'");
if (!$result)
{
throw new Exception('Адрес электронной почты не найден.');
}
else if ($result->num_rows == 0)
{
throw new Exception('Адрес электронной почты '
.'не найден.'); // имя пользователя отсутствует в БД
}
else
{
$row = $result->fetch_object();
$email = $row->email;
$from = "From: support@phpbookmark \r\n";
$mesg = "Ваш пароль для входа в систему PHPBookmark изменен на $password \r\n"
."Пожалуйста, учтите это при будущем входе в систему. \r\n";
if (mail($email, 'Информация о входе в систему PHPBookmark', $mesg, $from))
return true;
else
throw new Exception('Не удается отправить электронную почту.');
}
}
Эта функция по имени пользователя и новому паролю выполняет поиск адреса электронной почты в базе данных и применяет PHP-функцию mail() для отправки пароля пользователю.
Безопаснее предоставить пользователю действительно случайный пароль, составленный из комбинации букв верхнего и нижнего регистра, чисел и знаков пунктуации, вместо случайного слова и числа. Однако пароль вроде "zigzag487" пользователю будет проще читать и печатать, чем случайный набор символов. В таком наборе часто труд но различить 0 и О (нуль от прописного "О"), либо 1 и l (единицу от строчной "L").
В нашей системе файл словаря содержит приблизительно 45000 слов. Если даже взломщик знает способ создания пароля и имя пользователя, то ему и тогда придется угадать, в среднем, один вариант из 22500000. Этот уровень защищенности достаточен для приложений данного типа, даже если пользователи оставляют без внимания сообщения электронной почты с предложением смены пароля.