PageRenderTime 49ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/sources/controllers/LogInOut.controller.php

https://github.com/Arantor/Elkarte
PHP | 699 lines | 434 code | 108 blank | 157 comment | 140 complexity | 73308c5ac460ffe1788f0b7e4e5cab58 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-3.0
  1. <?php
  2. /**
  3. * @name ElkArte Forum
  4. * @copyright ElkArte Forum contributors
  5. * @license BSD http://opensource.org/licenses/BSD-3-Clause
  6. *
  7. * This software is a derived product, based on:
  8. *
  9. * Simple Machines Forum (SMF)
  10. * copyright: 2011 Simple Machines (http://www.simplemachines.org)
  11. * license: BSD, See included LICENSE.TXT for terms and conditions.
  12. *
  13. * @version 1.0 Alpha
  14. *
  15. * This file is concerned pretty entirely, as you see from its name, with
  16. * logging in and out members, and the validation of that.
  17. *
  18. */
  19. if (!defined('ELKARTE'))
  20. die('No access...');
  21. /**
  22. * Ask them for their login information. (shows a page for the user to type
  23. * in their username and password.)
  24. * It caches the referring URL in $_SESSION['login_url'].
  25. * It is accessed from ?action=login.
  26. * @uses Login template and language file with the login sub-template.
  27. * @uses the protocol_login sub-template in the Wireless template,
  28. * if you are using a wireless device
  29. */
  30. function action_login()
  31. {
  32. global $txt, $context, $scripturl, $user_info;
  33. // You are already logged in, go take a tour of the boards
  34. if (!empty($user_info['id']))
  35. redirectexit();
  36. // Load the Login template/language file.
  37. loadLanguage('Login');
  38. loadTemplate('Login');
  39. $context['sub_template'] = 'login';
  40. // Get the template ready.... not really much else to do.
  41. $context['page_title'] = $txt['login'];
  42. $context['default_username'] = &$_REQUEST['u'];
  43. $context['default_password'] = '';
  44. $context['never_expire'] = false;
  45. // Add the login chain to the link tree.
  46. $context['linktree'][] = array(
  47. 'url' => $scripturl . '?action=login',
  48. 'name' => $txt['login'],
  49. );
  50. // Set the login URL - will be used when the login process is done (but careful not to send us to an attachment).
  51. if (isset($_SESSION['old_url']) && strpos($_SESSION['old_url'], 'dlattach') === false && preg_match('~(board|topic)[=,]~', $_SESSION['old_url']) != 0)
  52. $_SESSION['login_url'] = $_SESSION['old_url'];
  53. else
  54. unset($_SESSION['login_url']);
  55. // Create a one time token.
  56. createToken('login');
  57. }
  58. /**
  59. * Actually logs you in.
  60. * What it does:
  61. * - checks credentials and checks that login was successful.
  62. * - it employs protection against a specific IP or user trying to brute force
  63. * a login to an account.
  64. * - upgrades password encryption on login, if necessary.
  65. * - after successful login, redirects you to $_SESSION['login_url'].
  66. * - accessed from ?action=login2, by forms.
  67. * On error, uses the same templates Login() uses.
  68. */
  69. function action_login2()
  70. {
  71. global $txt, $scripturl, $user_info, $user_settings, $smcFunc;
  72. global $cookiename, $maintenance, $modSettings, $context, $sc;
  73. // Load cookie authentication stuff.
  74. require_once(SUBSDIR . '/Auth.subs.php');
  75. if (isset($_GET['sa']) && $_GET['sa'] == 'salt' && !$user_info['is_guest'])
  76. {
  77. if (isset($_COOKIE[$cookiename]) && preg_match('~^a:[34]:\{i:0;(i:\d{1,6}|s:[1-8]:"\d{1,8}");i:1;s:(0|40):"([a-fA-F0-9]{40})?";i:2;[id]:\d{1,14};(i:3;i:\d;)?\}$~', $_COOKIE[$cookiename]) === 1)
  78. list (, , $timeout) = @unserialize($_COOKIE[$cookiename]);
  79. elseif (isset($_SESSION['login_' . $cookiename]))
  80. list (, , $timeout) = @unserialize($_SESSION['login_' . $cookiename]);
  81. else
  82. trigger_error('Login2(): Cannot be logged in without a session or cookie', E_USER_ERROR);
  83. $user_settings['password_salt'] = substr(md5(mt_rand()), 0, 4);
  84. updateMemberData($user_info['id'], array('password_salt' => $user_settings['password_salt']));
  85. setLoginCookie($timeout - time(), $user_info['id'], sha1($user_settings['passwd'] . $user_settings['password_salt']));
  86. redirectexit('action=login2;sa=check;member=' . $user_info['id'], $context['server']['needs_login_fix']);
  87. }
  88. // Double check the cookie...
  89. elseif (isset($_GET['sa']) && $_GET['sa'] == 'check')
  90. {
  91. // Strike! You're outta there!
  92. if ($_GET['member'] != $user_info['id'])
  93. fatal_lang_error('login_cookie_error', false);
  94. $user_info['can_mod'] = allowedTo('access_mod_center') || (!$user_info['is_guest'] && ($user_info['mod_cache']['gq'] != '0=1' || $user_info['mod_cache']['bq'] != '0=1' || ($modSettings['postmod_active'] && !empty($user_info['mod_cache']['ap']))));
  95. if ($user_info['can_mod'] && isset($user_settings['openid_uri']) && empty($user_settings['openid_uri']))
  96. {
  97. $_SESSION['moderate_time'] = time();
  98. unset($_SESSION['just_registered']);
  99. }
  100. // Some whitelisting for login_url...
  101. if (empty($_SESSION['login_url']))
  102. redirectexit();
  103. elseif (!empty($_SESSION['login_url']) && (strpos('http://', $_SESSION['login_url']) === false && strpos('https://', $_SESSION['login_url']) === false))
  104. {
  105. unset ($_SESSION['login_url']);
  106. redirectexit();
  107. }
  108. else
  109. {
  110. // Best not to clutter the session data too much...
  111. $temp = $_SESSION['login_url'];
  112. unset($_SESSION['login_url']);
  113. redirectexit($temp);
  114. }
  115. }
  116. // Beyond this point you are assumed to be a guest trying to login.
  117. if (!$user_info['is_guest'])
  118. redirectexit();
  119. // Are you guessing with a script?
  120. checkSession('post');
  121. $tk = validateToken('login');
  122. spamProtection('login');
  123. // Set the login_url if it's not already set (but careful not to send us to an attachment).
  124. 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))
  125. $_SESSION['login_url'] = $_SESSION['old_url'];
  126. // Been guessing a lot, haven't we?
  127. if (isset($_SESSION['failed_login']) && $_SESSION['failed_login'] >= $modSettings['failed_login_threshold'] * 3)
  128. fatal_lang_error('login_threshold_fail', 'critical');
  129. // Set up the cookie length. (if it's invalid, just fall through and use the default.)
  130. if (isset($_POST['cookieneverexp']) || (!empty($_POST['cookielength']) && $_POST['cookielength'] == -1))
  131. $modSettings['cookieTime'] = 3153600;
  132. elseif (!empty($_POST['cookielength']) && ($_POST['cookielength'] >= 1 || $_POST['cookielength'] <= 525600))
  133. $modSettings['cookieTime'] = (int) $_POST['cookielength'];
  134. loadLanguage('Login');
  135. // Load the template stuff
  136. loadTemplate('Login');
  137. $context['sub_template'] = 'login';
  138. // Set up the default/fallback stuff.
  139. $context['default_username'] = isset($_POST['user']) ? preg_replace('~&amp;#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', htmlspecialchars($_POST['user'])) : '';
  140. $context['default_password'] = '';
  141. $context['never_expire'] = $modSettings['cookieTime'] == 525600 || $modSettings['cookieTime'] == 3153600;
  142. $context['login_errors'] = array($txt['error_occured']);
  143. $context['page_title'] = $txt['login'];
  144. // Add the login chain to the link tree.
  145. $context['linktree'][] = array(
  146. 'url' => $scripturl . '?action=login',
  147. 'name' => $txt['login'],
  148. );
  149. if (!empty($_POST['openid_identifier']) && !empty($modSettings['enableOpenID']))
  150. {
  151. require_once(SUBSDIR . '/OpenID.subs.php');
  152. if (($open_id = openID_validate($_POST['openid_identifier'])) !== 'no_data')
  153. return $open_id;
  154. }
  155. // You forgot to type your username, dummy!
  156. if (!isset($_POST['user']) || $_POST['user'] == '')
  157. {
  158. $context['login_errors'] = array($txt['need_username']);
  159. return;
  160. }
  161. // Hmm... maybe 'admin' will login with no password. Uhh... NO!
  162. if ((!isset($_POST['passwrd']) || $_POST['passwrd'] == '') && (!isset($_POST['hash_passwrd']) || strlen($_POST['hash_passwrd']) != 40))
  163. {
  164. $context['login_errors'] = array($txt['no_password']);
  165. return;
  166. }
  167. // No funky symbols either.
  168. if (preg_match('~[<>&"\'=\\\]~', preg_replace('~(&#(\\d{1,7}|x[0-9a-fA-F]{1,6});)~', '', $_POST['user'])) != 0)
  169. {
  170. $context['login_errors'] = array($txt['error_invalid_characters_username']);
  171. return;
  172. }
  173. // Are we using any sort of integration to validate the login?
  174. 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))
  175. {
  176. $context['login_errors'] = array($txt['login_hash_error']);
  177. $context['disable_login_hashing'] = true;
  178. return;
  179. }
  180. // Load the data up!
  181. $request = $smcFunc['db_query']('', '
  182. SELECT passwd, id_member, id_group, lngfile, is_activated, email_address, additional_groups, member_name, password_salt,
  183. openid_uri, passwd_flood
  184. FROM {db_prefix}members
  185. WHERE ' . ($smcFunc['db_case_sensitive'] ? 'LOWER(member_name) = LOWER({string:user_name})' : 'member_name = {string:user_name}') . '
  186. LIMIT 1',
  187. array(
  188. 'user_name' => $smcFunc['db_case_sensitive'] ? strtolower($_POST['user']) : $_POST['user'],
  189. )
  190. );
  191. // Probably mistyped or their email, try it as an email address. (member_name first, though!)
  192. if ($smcFunc['db_num_rows']($request) == 0 && strpos($_POST['user'], '@') !== false)
  193. {
  194. $smcFunc['db_free_result']($request);
  195. $request = $smcFunc['db_query']('', '
  196. SELECT passwd, id_member, id_group, lngfile, is_activated, email_address, additional_groups, member_name, password_salt, openid_uri,
  197. passwd_flood
  198. FROM {db_prefix}members
  199. WHERE email_address = {string:user_name}
  200. LIMIT 1',
  201. array(
  202. 'user_name' => $_POST['user'],
  203. )
  204. );
  205. }
  206. // Let them try again, it didn't match anything...
  207. if ($smcFunc['db_num_rows']($request) == 0)
  208. {
  209. $context['login_errors'] = array($txt['username_no_exist']);
  210. return;
  211. }
  212. $user_settings = $smcFunc['db_fetch_assoc']($request);
  213. $smcFunc['db_free_result']($request);
  214. // Figure out the password using ELKARTE's encryption - if what they typed is right.
  215. if (isset($_POST['hash_passwrd']) && strlen($_POST['hash_passwrd']) == 40)
  216. {
  217. // Needs upgrading?
  218. if (strlen($user_settings['passwd']) != 40)
  219. {
  220. $context['login_errors'] = array($txt['login_hash_error']);
  221. $context['disable_login_hashing'] = true;
  222. unset($user_settings);
  223. return;
  224. }
  225. // Challenge passed.
  226. elseif ($_POST['hash_passwrd'] == sha1($user_settings['passwd'] . $sc . $tk))
  227. $sha_passwd = $user_settings['passwd'];
  228. else
  229. {
  230. // Don't allow this!
  231. validatePasswordFlood($user_settings['id_member'], $user_settings['passwd_flood']);
  232. $_SESSION['failed_login'] = isset($_SESSION['failed_login']) ? ($_SESSION['failed_login'] + 1) : 1;
  233. if ($_SESSION['failed_login'] >= $modSettings['failed_login_threshold'])
  234. redirectexit('action=reminder');
  235. else
  236. {
  237. log_error($txt['incorrect_password'] . ' - <span class="remove">' . $user_settings['member_name'] . '</span>', 'user');
  238. $context['disable_login_hashing'] = true;
  239. $context['login_errors'] = array($txt['incorrect_password']);
  240. unset($user_settings);
  241. return;
  242. }
  243. }
  244. }
  245. else
  246. $sha_passwd = sha1(strtolower($user_settings['member_name']) . un_htmlspecialchars($_POST['passwrd']));
  247. // Bad password! Thought you could fool the database?!
  248. if ($user_settings['passwd'] != $sha_passwd)
  249. {
  250. // Let's be cautious, no hacking please. thanx.
  251. validatePasswordFlood($user_settings['id_member'], $user_settings['passwd_flood']);
  252. // Maybe we were too hasty... let's try some other authentication methods.
  253. $other_passwords = array();
  254. // None of the below cases will be used most of the time (because the salt is normally set.)
  255. if (!empty($modSettings['enable_password_conversion']) && $user_settings['password_salt'] == '')
  256. {
  257. // YaBB SE, Discus, MD5 (used a lot), SHA-1 (used some), SMF 1.0.x, IkonBoard, and none at all.
  258. $other_passwords[] = crypt($_POST['passwrd'], substr($_POST['passwrd'], 0, 2));
  259. $other_passwords[] = crypt($_POST['passwrd'], substr($user_settings['passwd'], 0, 2));
  260. $other_passwords[] = md5($_POST['passwrd']);
  261. $other_passwords[] = sha1($_POST['passwrd']);
  262. $other_passwords[] = md5_hmac($_POST['passwrd'], strtolower($user_settings['member_name']));
  263. $other_passwords[] = md5($_POST['passwrd'] . strtolower($user_settings['member_name']));
  264. $other_passwords[] = md5(md5($_POST['passwrd']));
  265. $other_passwords[] = $_POST['passwrd'];
  266. // This one is a strange one... MyPHP, crypt() on the MD5 hash.
  267. $other_passwords[] = crypt(md5($_POST['passwrd']), md5($_POST['passwrd']));
  268. // Snitz style - SHA-256. Technically, this is a downgrade, but most PHP configurations don't support sha256 anyway.
  269. if (strlen($user_settings['passwd']) == 64 && function_exists('mhash') && defined('MHASH_SHA256'))
  270. $other_passwords[] = bin2hex(mhash(MHASH_SHA256, $_POST['passwrd']));
  271. // phpBB3 users new hashing. We now support it as well ;).
  272. $other_passwords[] = phpBB3_password_check($_POST['passwrd'], $user_settings['passwd']);
  273. // APBoard 2 Login Method.
  274. $other_passwords[] = md5(crypt($_POST['passwrd'], 'CRYPT_MD5'));
  275. }
  276. // The hash should be 40 if it's SHA-1, so we're safe with more here too.
  277. elseif (!empty($modSettings['enable_password_conversion']) && strlen($user_settings['passwd']) == 32)
  278. {
  279. // vBulletin 3 style hashing? Let's welcome them with open arms \o/.
  280. $other_passwords[] = md5(md5($_POST['passwrd']) . stripslashes($user_settings['password_salt']));
  281. // Hmm.. p'raps it's Invision 2 style?
  282. $other_passwords[] = md5(md5($user_settings['password_salt']) . md5($_POST['passwrd']));
  283. // Some common md5 ones.
  284. $other_passwords[] = md5($user_settings['password_salt'] . $_POST['passwrd']);
  285. $other_passwords[] = md5($_POST['passwrd'] . $user_settings['password_salt']);
  286. }
  287. elseif (strlen($user_settings['passwd']) == 40)
  288. {
  289. // Maybe they are using a hash from before the password fix.
  290. $other_passwords[] = sha1(strtolower($user_settings['member_name']) . un_htmlspecialchars($_POST['passwrd']));
  291. // BurningBoard3 style of hashing.
  292. if (!empty($modSettings['enable_password_conversion']))
  293. $other_passwords[] = sha1($user_settings['password_salt'] . sha1($user_settings['password_salt'] . sha1($_POST['passwrd'])));
  294. // Perhaps we converted to UTF-8 and have a valid password being hashed differently.
  295. if (!empty($modSettings['previousCharacterSet']) && $modSettings['previousCharacterSet'] != 'utf8')
  296. {
  297. // Try iconv first, for no particular reason.
  298. if (function_exists('iconv'))
  299. $other_passwords['iconv'] = sha1(strtolower(iconv('UTF-8', $modSettings['previousCharacterSet'], $user_settings['member_name'])) . un_htmlspecialchars(iconv('UTF-8', $modSettings['previousCharacterSet'], $_POST['passwrd'])));
  300. // Say it aint so, iconv failed!
  301. if (empty($other_passwords['iconv']) && function_exists('mb_convert_encoding'))
  302. $other_passwords[] = sha1(strtolower(mb_convert_encoding($user_settings['member_name'], 'UTF-8', $modSettings['previousCharacterSet'])) . un_htmlspecialchars(mb_convert_encoding($_POST['passwrd'], 'UTF-8', $modSettings['previousCharacterSet'])));
  303. }
  304. }
  305. // ELKARTE's sha1 function can give a funny result on Linux (Not our fault!). If we've now got the real one let the old one be valid!
  306. if (stripos(PHP_OS, 'win') !== 0)
  307. {
  308. require_once(SUBSDIR . '/Compat.subs.php');
  309. $other_passwords[] = sha1_smf(strtolower($user_settings['member_name']) . un_htmlspecialchars($_POST['passwrd']));
  310. }
  311. // Allows mods to easily extend the $other_passwords array
  312. call_integration_hook('integrate_other_passwords', array($other_passwords));
  313. // Whichever encryption it was using, let's make it use ELKARTE's now ;).
  314. if (in_array($user_settings['passwd'], $other_passwords))
  315. {
  316. $user_settings['passwd'] = $sha_passwd;
  317. $user_settings['password_salt'] = substr(md5(mt_rand()), 0, 4);
  318. // Update the password and set up the hash.
  319. updateMemberData($user_settings['id_member'], array('passwd' => $user_settings['passwd'], 'password_salt' => $user_settings['password_salt'], 'passwd_flood' => ''));
  320. }
  321. // Okay, they for sure didn't enter the password!
  322. else
  323. {
  324. // They've messed up again - keep a count to see if they need a hand.
  325. $_SESSION['failed_login'] = isset($_SESSION['failed_login']) ? ($_SESSION['failed_login'] + 1) : 1;
  326. // Hmm... don't remember it, do you? Here, try the password reminder ;).
  327. if ($_SESSION['failed_login'] >= $modSettings['failed_login_threshold'])
  328. redirectexit('action=reminder');
  329. // We'll give you another chance...
  330. else
  331. {
  332. // Log an error so we know that it didn't go well in the error log.
  333. log_error($txt['incorrect_password'] . ' - <span class="remove">' . $user_settings['member_name'] . '</span>', 'user');
  334. $context['login_errors'] = array($txt['incorrect_password']);
  335. return;
  336. }
  337. }
  338. }
  339. elseif (!empty($user_settings['passwd_flood']))
  340. {
  341. // Let's be sure they weren't a little hacker.
  342. validatePasswordFlood($user_settings['id_member'], $user_settings['passwd_flood'], true);
  343. // If we got here then we can reset the flood counter.
  344. updateMemberData($user_settings['id_member'], array('passwd_flood' => ''));
  345. }
  346. // Correct password, but they've got no salt; fix it!
  347. if ($user_settings['password_salt'] == '')
  348. {
  349. $user_settings['password_salt'] = substr(md5(mt_rand()), 0, 4);
  350. updateMemberData($user_settings['id_member'], array('password_salt' => $user_settings['password_salt']));
  351. }
  352. // Check their activation status.
  353. if (!checkActivation())
  354. return;
  355. DoLogin();
  356. }
  357. /**
  358. * Check activation status of the current user.
  359. */
  360. function checkActivation()
  361. {
  362. global $context, $txt, $scripturl, $user_settings, $modSettings;
  363. if (!isset($context['login_errors']))
  364. $context['login_errors'] = array();
  365. // What is the true activation status of this account?
  366. $activation_status = $user_settings['is_activated'] > 10 ? $user_settings['is_activated'] - 10 : $user_settings['is_activated'];
  367. // Check if the account is activated - COPPA first...
  368. if ($activation_status == 5)
  369. {
  370. $context['login_errors'][] = $txt['coppa_no_concent'] . ' <a href="' . $scripturl . '?action=coppa;member=' . $user_settings['id_member'] . '">' . $txt['coppa_need_more_details'] . '</a>';
  371. return false;
  372. }
  373. // Awaiting approval still?
  374. elseif ($activation_status == 3)
  375. fatal_lang_error('still_awaiting_approval', 'user');
  376. // Awaiting deletion, changed their mind?
  377. elseif ($activation_status == 4)
  378. {
  379. if (isset($_REQUEST['undelete']))
  380. {
  381. updateMemberData($user_settings['id_member'], array('is_activated' => 1));
  382. updateSettings(array('unapprovedMembers' => ($modSettings['unapprovedMembers'] > 0 ? $modSettings['unapprovedMembers'] - 1 : 0)));
  383. }
  384. else
  385. {
  386. $context['disable_login_hashing'] = true;
  387. $context['login_errors'][] = $txt['awaiting_delete_account'];
  388. $context['login_show_undelete'] = true;
  389. return false;
  390. }
  391. }
  392. // Standard activation?
  393. elseif ($activation_status != 1)
  394. {
  395. log_error($txt['activate_not_completed1'] . ' - <span class="remove">' . $user_settings['member_name'] . '</span>', false);
  396. $context['login_errors'][] = $txt['activate_not_completed1'] . ' <a href="' . $scripturl . '?action=activate;sa=resend;u=' . $user_settings['id_member'] . '">' . $txt['activate_not_completed2'] . '</a>';
  397. return false;
  398. }
  399. return true;
  400. }
  401. /**
  402. * Perform the logging in. (set cookie, call hooks, etc)
  403. */
  404. function DoLogin()
  405. {
  406. global $txt, $scripturl, $user_info, $user_settings, $smcFunc;
  407. global $cookiename, $maintenance, $modSettings, $context;
  408. // Load cookie authentication stuff.
  409. require_once(SUBSDIR . '/Auth.subs.php');
  410. // Call login integration functions.
  411. call_integration_hook('integrate_login', array($user_settings['member_name'], isset($_POST['hash_passwrd']) && strlen($_POST['hash_passwrd']) == 40 ? $_POST['hash_passwrd'] : null, $modSettings['cookieTime']));
  412. // Get ready to set the cookie...
  413. $username = $user_settings['member_name'];
  414. $user_info['id'] = $user_settings['id_member'];
  415. // Bam! Cookie set. A session too, just in case.
  416. setLoginCookie(60 * $modSettings['cookieTime'], $user_settings['id_member'], sha1($user_settings['passwd'] . $user_settings['password_salt']));
  417. // Reset the login threshold.
  418. if (isset($_SESSION['failed_login']))
  419. unset($_SESSION['failed_login']);
  420. $user_info['is_guest'] = false;
  421. $user_settings['additional_groups'] = explode(',', $user_settings['additional_groups']);
  422. $user_info['is_admin'] = $user_settings['id_group'] == 1 || in_array(1, $user_settings['additional_groups']);
  423. // Are you banned?
  424. is_not_banned(true);
  425. // An administrator, set up the login so they don't have to type it again.
  426. if ($user_info['is_admin'] && isset($user_settings['openid_uri']) && empty($user_settings['openid_uri']))
  427. {
  428. $_SESSION['admin_time'] = time();
  429. unset($_SESSION['just_registered']);
  430. }
  431. // Don't stick the language or theme after this point.
  432. unset($_SESSION['language'], $_SESSION['id_theme']);
  433. // First login?
  434. $request = $smcFunc['db_query']('', '
  435. SELECT last_login
  436. FROM {db_prefix}members
  437. WHERE id_member = {int:id_member}
  438. AND last_login = 0',
  439. array(
  440. 'id_member' => $user_info['id'],
  441. )
  442. );
  443. if ($smcFunc['db_num_rows']($request) == 1)
  444. $_SESSION['first_login'] = true;
  445. else
  446. unset($_SESSION['first_login']);
  447. $smcFunc['db_free_result']($request);
  448. // You've logged in, haven't you?
  449. updateMemberData($user_info['id'], array('last_login' => time(), 'member_ip' => $user_info['ip'], 'member_ip2' => $_SERVER['BAN_CHECK_IP']));
  450. // Get rid of the online entry for that old guest....
  451. $smcFunc['db_query']('', '
  452. DELETE FROM {db_prefix}log_online
  453. WHERE session = {string:session}',
  454. array(
  455. 'session' => 'ip' . $user_info['ip'],
  456. )
  457. );
  458. $_SESSION['log_time'] = 0;
  459. // Log this entry, only if we have it enabled.
  460. if (!empty($modSettings['loginHistoryDays']))
  461. $smcFunc['db_insert']('insert',
  462. '{db_prefix}member_logins',
  463. array(
  464. 'id_member' => 'int', 'time' => 'int', 'ip' => 'string', 'ip2' => 'string',
  465. ),
  466. array(
  467. $user_info['id'], time(), $user_info['ip'], $user_info['ip2']
  468. ),
  469. array(
  470. 'id_member', 'time'
  471. )
  472. );
  473. // Just log you back out if it's in maintenance mode and you AREN'T an admin.
  474. if (empty($maintenance) || allowedTo('admin_forum'))
  475. redirectexit('action=login2;sa=check;member=' . $user_info['id'], $context['server']['needs_login_fix']);
  476. else
  477. redirectexit('action=logout;' . $context['session_var'] . '=' . $context['session_id'], $context['server']['needs_login_fix']);
  478. }
  479. /**
  480. * Logs the current user out of their account.
  481. * It requires that the session hash is sent as well, to prevent automatic logouts by images or javascript.
  482. * It redirects back to $_SESSION['logout_url'], if it exists.
  483. * It is accessed via ?action=logout;session_var=...
  484. *
  485. * @param bool $internal if true, it doesn't check the session
  486. * @param $redirect
  487. */
  488. function action_logout($internal = false, $redirect = true)
  489. {
  490. global $user_info, $user_settings, $context, $modSettings, $smcFunc;
  491. // Make sure they aren't being auto-logged out.
  492. if (!$internal)
  493. checkSession('get');
  494. require_once(SUBSDIR . '/Auth.subs.php');
  495. if (isset($_SESSION['pack_ftp']))
  496. $_SESSION['pack_ftp'] = null;
  497. // They cannot be open ID verified any longer.
  498. if (isset($_SESSION['openid']))
  499. unset($_SESSION['openid']);
  500. // It won't be first login anymore.
  501. unset($_SESSION['first_login']);
  502. // Just ensure they aren't a guest!
  503. if (!$user_info['is_guest'])
  504. {
  505. // Pass the logout information to integrations.
  506. call_integration_hook('integrate_logout', array($user_settings['member_name']));
  507. // If you log out, you aren't online anymore :P.
  508. $smcFunc['db_query']('', '
  509. DELETE FROM {db_prefix}log_online
  510. WHERE id_member = {int:current_member}',
  511. array(
  512. 'current_member' => $user_info['id'],
  513. )
  514. );
  515. }
  516. $_SESSION['log_time'] = 0;
  517. // Empty the cookie! (set it in the past, and for id_member = 0)
  518. setLoginCookie(-3600, 0);
  519. // Off to the merry board index we go!
  520. if ($redirect)
  521. {
  522. if (empty($_SESSION['logout_url']))
  523. redirectexit('', $context['server']['needs_login_fix']);
  524. elseif (!empty($_SESSION['logout_url']) && (strpos('http://', $_SESSION['logout_url']) === false && strpos('https://', $_SESSION['logout_url']) === false))
  525. {
  526. unset ($_SESSION['logout_url']);
  527. redirectexit();
  528. }
  529. else
  530. {
  531. $temp = $_SESSION['logout_url'];
  532. unset($_SESSION['logout_url']);
  533. redirectexit($temp, $context['server']['needs_login_fix']);
  534. }
  535. }
  536. }
  537. /**
  538. * MD5 Encryption used for older passwords. (SMF 1.0.x/YaBB SE 1.5.x hashing)
  539. *
  540. * @param string $data
  541. * @param string $key
  542. * @return string, the HMAC MD5 of data with key
  543. */
  544. function md5_hmac($data, $key)
  545. {
  546. $key = str_pad(strlen($key) <= 64 ? $key : pack('H*', md5($key)), 64, chr(0x00));
  547. return md5(($key ^ str_repeat(chr(0x5c), 64)) . pack('H*', md5(($key ^ str_repeat(chr(0x36), 64)) . $data)));
  548. }
  549. /**
  550. * Custom encryption for phpBB3 based passwords.
  551. *
  552. * @param string $passwd
  553. * @param string $passwd_hash
  554. * @return string
  555. */
  556. function phpBB3_password_check($passwd, $passwd_hash)
  557. {
  558. // Too long or too short?
  559. if (strlen($passwd_hash) != 34)
  560. return;
  561. // Range of characters allowed.
  562. $range = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
  563. // Tests
  564. $strpos = strpos($range, $passwd_hash[3]);
  565. $count = 1 << $strpos;
  566. $count2 = $count;
  567. $salt = substr($passwd_hash, 4, 8);
  568. $hash = md5($salt . $passwd, true);
  569. for (; $count != 0; --$count)
  570. $hash = md5($hash . $passwd, true);
  571. $output = substr($passwd_hash, 0, 12);
  572. $i = 0;
  573. while ($i < 16)
  574. {
  575. $value = ord($hash[$i++]);
  576. $output .= $range[$value & 0x3f];
  577. if ($i < 16)
  578. $value |= ord($hash[$i]) << 8;
  579. $output .= $range[($value >> 6) & 0x3f];
  580. if ($i++ >= 16)
  581. break;
  582. if ($i < 16)
  583. $value |= ord($hash[$i]) << 16;
  584. $output .= $range[($value >> 12) & 0x3f];
  585. if ($i++ >= 16)
  586. break;
  587. $output .= $range[($value >> 18) & 0x3f];
  588. }
  589. // Return now.
  590. return $output;
  591. }