/** * Check the passed id_member/password. * If $is_username is true, treats $id as a username. * * @param int|null $id * @param string|null $password * @param bool $is_username */ function ssi_checkPassword($id = null, $password = null, $is_username = false) { // If $id is null, this was most likely called from a query string and should do nothing. if ($id === null) { return; } require_once SUBSDIR . '/Auth.subs.php'; $member = loadExistingMember($id, !$is_username); return validateLoginPassword($password, $member['passwd'], $member['member_name']) && $member['is_activated'] == 1; }
/** * Actually logs you in. * * What it does: * - checks credentials and checks that login was successful. * - it employs protection against a specific IP or user trying to brute force * a login to an account. * - upgrades password encryption on login, if necessary. * - after successful login, redirects you to $_SESSION['login_url']. * - accessed from ?action=login2, by forms. * * On error, uses the same templates action_login() uses. */ public function action_login2() { global $txt, $scripturl, $user_info, $user_settings, $modSettings, $context, $sc; // Load cookie authentication and all stuff. require_once SUBSDIR . '/Auth.subs.php'; // Beyond this point you are assumed to be a guest trying to login. if (!$user_info['is_guest']) { redirectexit(); } // Are you guessing with a script? checkSession('post'); validateToken('login'); spamProtection('login'); // Set the login_url if it's not already set (but careful not to send us to an attachment). if (empty($_SESSION['login_url']) && isset($_SESSION['old_url']) && strpos($_SESSION['old_url'], 'dlattach') === false && preg_match('~(board|topic)[=,]~', $_SESSION['old_url']) != 0 || isset($_GET['quicklogin']) && isset($_SESSION['old_url']) && strpos($_SESSION['old_url'], 'login') === false) { $_SESSION['login_url'] = $_SESSION['old_url']; } // Been guessing a lot, haven't we? if (isset($_SESSION['failed_login']) && $_SESSION['failed_login'] >= $modSettings['failed_login_threshold'] * 3) { fatal_lang_error('login_threshold_fail', 'critical'); } // Set up the cookie length. (if it's invalid, just fall through and use the default.) if (isset($_POST['cookieneverexp']) || !empty($_POST['cookielength']) && $_POST['cookielength'] == -1) { $modSettings['cookieTime'] = 3153600; } elseif (!empty($_POST['cookielength']) && ($_POST['cookielength'] >= 1 || $_POST['cookielength'] <= 525600)) { $modSettings['cookieTime'] = (int) $_POST['cookielength']; } loadLanguage('Login'); // Load the template stuff loadTemplate('Login'); loadJavascriptFile('sha256.js', array('defer' => true)); $context['sub_template'] = 'login'; // Set up the default/fallback stuff. $context['default_username'] = isset($_POST['user']) ? preg_replace('~&#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', htmlspecialchars($_POST['user'], ENT_COMPAT, 'UTF-8')) : ''; $context['default_password'] = ''; $context['never_expire'] = $modSettings['cookieTime'] == 525600 || $modSettings['cookieTime'] == 3153600; $context['login_errors'] = array($txt['error_occurred']); $context['page_title'] = $txt['login']; // Add the login chain to the link tree. $context['linktree'][] = array('url' => $scripturl . '?action=login', 'name' => $txt['login']); // This is an OpenID login. Let's validate... if (!empty($_POST['openid_identifier']) && !empty($modSettings['enableOpenID'])) { require_once SUBSDIR . '/OpenID.subs.php'; $open_id = new OpenID(); if ($open_id->validate($_POST['openid_identifier']) !== 'no_data') { return $open_id; } else { $context['login_errors'] = array($txt['openid_not_found']); return; } } // You forgot to type your username, dummy! if (!isset($_POST['user']) || $_POST['user'] == '') { $context['login_errors'] = array($txt['need_username']); return; } // No one needs a username that long, plus we only support 80 chars in the db if (Util::strlen($_POST['user']) > 80) { $_POST['user'] = Util::substr($_POST['user'], 0, 80); } // Can't use a password > 64 characters sorry, to long and only good for a DoS attack // Plus we expect a 64 character one from SHA-256 if (isset($_POST['passwrd']) && strlen($_POST['passwrd']) > 64 || isset($_POST['hash_passwrd']) && strlen($_POST['hash_passwrd']) > 64) { $context['login_errors'] = array($txt['improper_password']); return; } // Hmm... maybe 'admin' will login with no password. Uhh... NO! if ((!isset($_POST['passwrd']) || $_POST['passwrd'] == '') && (!isset($_POST['hash_passwrd']) || strlen($_POST['hash_passwrd']) != 64)) { $context['login_errors'] = array($txt['no_password']); return; } // No funky symbols either. if (preg_match('~[<>&"\'=\\\\]~', preg_replace('~(&#(\\d{1,7}|x[0-9a-fA-F]{1,6});)~', '', $_POST['user'])) != 0) { $context['login_errors'] = array($txt['error_invalid_characters_username']); return; } // Are we using any sort of integration to validate the login? if (in_array('retry', call_integration_hook('integrate_validate_login', array($_POST['user'], isset($_POST['hash_passwrd']) && strlen($_POST['hash_passwrd']) == 40 ? $_POST['hash_passwrd'] : null, $modSettings['cookieTime'])), true)) { $context['login_errors'] = array($txt['login_hash_error']); $context['disable_login_hashing'] = true; return; } // Find them... if we can $user_settings = loadExistingMember($_POST['user']); // Let them try again, it didn't match anything... if (empty($user_settings)) { $context['login_errors'] = array($txt['username_no_exist']); return; } // Figure out if the password is using Elk's encryption - if what they typed is right. if (isset($_POST['hash_passwrd']) && strlen($_POST['hash_passwrd']) === 64) { // Challenge what was passed $valid_password = validateLoginPassword($_POST['hash_passwrd'], $user_settings['passwd']); // Let them in if ($valid_password) { $sha_passwd = $_POST['hash_passwrd']; $valid_password = true; } elseif (preg_match('/^[0-9a-f]{40}$/i', $user_settings['passwd']) && isset($_POST['old_hash_passwrd']) && $_POST['old_hash_passwrd'] === hash('sha1', $user_settings['passwd'] . $sc)) { // Old password passed, turn off hashing and ask for it again so we can update the db to something more secure. $context['login_errors'] = array($txt['login_hash_error']); $context['disable_login_hashing'] = true; unset($user_settings); return; } else { // Don't allow this! validatePasswordFlood($user_settings['id_member'], $user_settings['passwd_flood']); $_SESSION['failed_login'] = isset($_SESSION['failed_login']) ? $_SESSION['failed_login'] + 1 : 1; // To many tries, maybe they need a reminder if ($_SESSION['failed_login'] >= $modSettings['failed_login_threshold']) { redirectexit('action=reminder'); } else { log_error($txt['incorrect_password'] . ' - <span class="remove">' . $user_settings['member_name'] . '</span>', 'user'); // Wrong password, lets enable plain text responses in case form hashing is causing problems $context['disable_login_hashing'] = true; $context['login_errors'] = array($txt['incorrect_password']); unset($user_settings); return; } } } else { // validateLoginPassword will hash this like the form normally would and check its valid $sha_passwd = $_POST['passwrd']; $valid_password = validateLoginPassword($sha_passwd, $user_settings['passwd'], $user_settings['member_name']); } // Bad password! Thought you could fool the database?! if ($valid_password === false) { // Let's be cautious, no hacking please. thanx. validatePasswordFlood($user_settings['id_member'], $user_settings['passwd_flood']); // Maybe we were too hasty... let's try some other authentication methods. $other_passwords = $this->_other_passwords($user_settings); // Whichever encryption it was using, let's make it use ElkArte's now ;). if (in_array($user_settings['passwd'], $other_passwords)) { $user_settings['passwd'] = validateLoginPassword($sha_passwd, '', '', true); $user_settings['password_salt'] = substr(md5(mt_rand()), 0, 4); // Update the password hash and set up the salt. updateMemberData($user_settings['id_member'], array('passwd' => $user_settings['passwd'], 'password_salt' => $user_settings['password_salt'], 'passwd_flood' => '')); } else { // They've messed up again - keep a count to see if they need a hand. $_SESSION['failed_login'] = isset($_SESSION['failed_login']) ? $_SESSION['failed_login'] + 1 : 1; // Hmm... don't remember it, do you? Here, try the password reminder ;). if ($_SESSION['failed_login'] >= $modSettings['failed_login_threshold']) { redirectexit('action=reminder'); } else { // Log an error so we know that it didn't go well in the error log. log_error($txt['incorrect_password'] . ' - <span class="remove">' . $user_settings['member_name'] . '</span>', 'user'); $context['login_errors'] = array($txt['incorrect_password']); return; } } } elseif (!empty($user_settings['passwd_flood'])) { // Let's be sure they weren't a little hacker. validatePasswordFlood($user_settings['id_member'], $user_settings['passwd_flood'], true); // If we got here then we can reset the flood counter. updateMemberData($user_settings['id_member'], array('passwd_flood' => '')); } // Correct password, but they've got no salt; fix it! if ($user_settings['password_salt'] == '') { $user_settings['password_salt'] = substr(md5(mt_rand()), 0, 4); updateMemberData($user_settings['id_member'], array('password_salt' => $user_settings['password_salt'])); } // Check their activation status. if (!checkActivation()) { return; } doLogin(); }