Oppaitime's version of Gazelle
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.php 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. <?php
  2. /*-- TODO ---------------------------//
  3. Add the JavaScript validation into the display page using the class
  4. //-----------------------------------*/
  5. // Allow users to reset their password while logged in
  6. if (!empty($LoggedUser['ID']) && $_REQUEST['act'] != 'recover') {
  7. header('Location: index.php');
  8. die();
  9. }
  10. if (BLOCK_OPERA_MINI && isset($_SERVER['HTTP_X_OPERAMINI_PHONE'])) {
  11. error('Opera Mini is banned. Please use another browser.');
  12. }
  13. // Check if IP is banned
  14. if (Tools::site_ban_ip($_SERVER['REMOTE_ADDR'])) {
  15. error('Your IP address has been banned.');
  16. }
  17. require_once SERVER_ROOT.'/classes/twofa.class.php';
  18. require_once SERVER_ROOT.'/classes/validate.class.php';
  19. $Validate = new VALIDATE;
  20. $TwoFA = new TwoFactorAuth(SITE_NAME);
  21. if (array_key_exists('action', $_GET) && $_GET['action'] == 'disabled') {
  22. require('disabled.php');
  23. die();
  24. }
  25. if (isset($_REQUEST['act']) && $_REQUEST['act'] == 'recover') {
  26. // Recover password
  27. if (!empty($_REQUEST['key'])) {
  28. // User has entered a new password, use step 2
  29. $DB->query("
  30. SELECT
  31. m.ID,
  32. m.Email,
  33. m.ipcc,
  34. i.ResetExpires
  35. FROM users_main AS m
  36. INNER JOIN users_info AS i ON i.UserID = m.ID
  37. WHERE i.ResetKey = '".db_string($_REQUEST['key'])."'
  38. AND i.ResetKey != ''");
  39. list($UserID, $Email, $Country, $Expires) = $DB->next_record();
  40. if (!apc_exists('DBKEY')) {
  41. error('Database not fully decrypted. Please wait for staff to fix this and try again later');
  42. }
  43. $Email = DBCrypt::decrypt($Email);
  44. if ($UserID && strtotime($Expires) > time()) {
  45. // If the user has requested a password change, and his key has not expired
  46. $Validate->SetFields('password', '1', 'regex', 'You entered an invalid password. Any password at least 6 characters long is accepted, but a strong password is 8 characters or longer, contains at least 1 lowercase and uppercase letter, contains at least a number or symbol', array('regex' => '/(?=^.{6,}$).*$/'));
  47. $Validate->SetFields('verifypassword', '1', 'compare', 'Your passwords did not match.', array('comparefield' => 'password'));
  48. if (!empty($_REQUEST['password'])) {
  49. // If the user has entered a password.
  50. // If the user has not entered a password, $PassWasReset is not set to 1, and the success message is not shown
  51. $Err = $Validate->ValidateForm($_REQUEST);
  52. if ($Err == '') {
  53. // Form validates without error, set new secret and password.
  54. $DB->query("
  55. UPDATE
  56. users_main AS m,
  57. users_info AS i
  58. SET
  59. m.PassHash = '".db_string(Users::make_sec_hash($_REQUEST['password']))."',
  60. i.ResetKey = '',
  61. m.LastLogin = NOW(),
  62. i.ResetExpires = NULL
  63. WHERE m.ID = '$UserID'
  64. AND i.UserID = m.ID");
  65. $DB->query("
  66. INSERT INTO users_history_passwords
  67. (UserID, ChangerIP, ChangeTime)
  68. VALUES
  69. ('$UserID', '".DBCrypt::encrypt($_SERVER['REMOTE_ADDR'])."', '".sqltime()."')");
  70. $PassWasReset = true;
  71. $LoggedUser['ID'] = $UserID; // Set $LoggedUser['ID'] for logout_all_sessions() to work
  72. logout_all_sessions();
  73. }
  74. }
  75. // Either a form asking for them to enter the password
  76. // Or a success message if $PassWasReset is 1
  77. require('recover_step2.php');
  78. } else {
  79. // Either his key has expired, or he hasn't requested a pass change at all
  80. if (strtotime($Expires) < time() && $UserID) {
  81. // If his key has expired, clear all the reset information
  82. $DB->query("
  83. UPDATE users_info
  84. SET ResetKey = '',
  85. ResetExpires = NULL
  86. WHERE UserID = '$UserID'");
  87. $_SESSION['reseterr'] = 'The link you were given has expired.'; // Error message to display on form
  88. }
  89. // Show him the first form (enter email address)
  90. header('Location: login.php?act=recover');
  91. }
  92. } // End step 2
  93. // User has not clicked the link in his email, use step 1
  94. else {
  95. $Validate->SetFields('email', '1', 'email', 'You entered an invalid email address.');
  96. if (!empty($_REQUEST['email'])) {
  97. // User has entered email and submitted form
  98. $Err = $Validate->ValidateForm($_REQUEST);
  99. if (!apc_exists('DBKEY')) {
  100. $Err = 'Database not fully decrypted. Please wait for staff to fix this and try again.';
  101. }
  102. if (!$Err) {
  103. // Form validates correctly
  104. $DB->query("
  105. SELECT
  106. Email
  107. FROM users_main");
  108. while(list($EncEmail) = $DB->next_record()) {
  109. if (strtolower($_REQUEST['email']) == strtolower(DBCrypt::decrypt($EncEmail))) {
  110. break; // $EncEmail is now the encrypted form of the given email from the database
  111. }
  112. }
  113. $DB->query("
  114. SELECT
  115. ID,
  116. Username,
  117. Email
  118. FROM users_main
  119. WHERE Email = '$EncEmail'");
  120. list($UserID, $Username, $Email) = $DB->next_record();
  121. $Email = DBCrypt::decrypt($Email);
  122. if ($UserID) {
  123. // Email exists in the database
  124. // Set ResetKey, send out email, and set $Sent to 1 to show success page
  125. Users::reset_password($UserID, $Username, $Email);
  126. $Sent = 1; // If $Sent is 1, recover_step1.php displays a success message
  127. //Log out all of the users current sessions
  128. $Cache->delete_value("user_info_$UserID");
  129. $Cache->delete_value("user_info_heavy_$UserID");
  130. $Cache->delete_value("user_stats_$UserID");
  131. $Cache->delete_value("enabled_$UserID");
  132. $DB->query("
  133. SELECT SessionID
  134. FROM users_sessions
  135. WHERE UserID = '$UserID'");
  136. while (list($SessionID) = $DB->next_record()) {
  137. $Cache->delete_value("session_$UserID"."_$SessionID");
  138. }
  139. $DB->query("
  140. UPDATE users_sessions
  141. SET Active = 0
  142. WHERE UserID = '$UserID'
  143. AND Active = 1");
  144. } else {
  145. $Err = 'There is no user with that email address.';
  146. }
  147. }
  148. } elseif (!empty($_SESSION['reseterr'])) {
  149. // User has not entered email address, and there is an error set in session data
  150. // This is typically because their key has expired.
  151. // Stick the error into $Err so recover_step1.php can take care of it
  152. $Err = $_SESSION['reseterr'];
  153. unset($_SESSION['reseterr']);
  154. }
  155. // Either a form for the user's email address, or a success message
  156. require('recover_step1.php');
  157. }
  158. } // End password recovery
  159. else if (isset($_REQUEST['act']) && $_REQUEST['act'] == 'newlocation') {
  160. if (isset($_REQUEST['key'])) {
  161. if ($ASNCache = $Cache->get_value('new_location_'.$_REQUEST['key'])) {
  162. $Cache->cache_value('new_location_'.$ASNCache['UserID'].'_'.$ASNCache['ASN'], true);
  163. require('newlocation.php');
  164. die();
  165. } else {
  166. error(403);
  167. }
  168. } else {
  169. error(403);
  170. }
  171. } // End new location
  172. // Normal login
  173. else {
  174. $Validate->SetFields('username', true, 'regex', 'You did not enter a valid username.', array('regex' => USERNAME_REGEX));
  175. $Validate->SetFields('password', '1', 'string', 'You entered an invalid password.', array('minlength' => '6', 'maxlength' => '307200'));
  176. list($Attempts, $Banned) = $Cache->get_value('login_attempts_'.db_string($_SERVER['REMOTE_ADDR']));
  177. // Function to log a user's login attempt
  178. function log_attempt() {
  179. global $Cache, $Attempts;
  180. $Attempts = ($Attempts ?? 0) + 1;
  181. $Cache->cache_value('login_attempts_'.db_string($_SERVER['REMOTE_ADDR']), array($Attempts, ($Attempts > 5)), 60*60*$Attempts);
  182. $AllAttempts = $Cache->get_value('login_attempts');
  183. $AllAttempts[$_SERVER['REMOTE_ADDR']] = time()+(60*60*$Attempts);
  184. foreach($AllAttempts as $IP => $Time) {
  185. if ($Time < time()) { unset($AllAttempts[$IP]); }
  186. }
  187. $Cache->cache_value('login_attempts', $AllAttempts, 0);
  188. }
  189. // If user has submitted form
  190. if (isset($_POST['username']) && !empty($_POST['username']) && isset($_POST['password']) && !empty($_POST['password'])) {
  191. if ($Banned) {
  192. header("Location: login.php");
  193. die();
  194. }
  195. $Err = $Validate->ValidateForm($_POST);
  196. if (!$Err) {
  197. // Passes preliminary validation (username and password "look right")
  198. $DB->query("
  199. SELECT
  200. ID,
  201. PermissionID,
  202. CustomPermissions,
  203. PassHash,
  204. TwoFactor,
  205. Enabled
  206. FROM users_main
  207. WHERE Username = '".db_string($_POST['username'])."'
  208. AND Username != ''");
  209. list($UserID, $PermissionID, $CustomPermissions, $PassHash, $TwoFactor, $Enabled) = $DB->next_record(MYSQLI_NUM, array(2));
  210. if (!$Banned) {
  211. if ($UserID && Users::check_password($_POST['password'], $PassHash)) {
  212. // Update hash if better algorithm available
  213. if (password_needs_rehash($PassHash, PASSWORD_DEFAULT)) {
  214. $DB->query("
  215. UPDATE users_main
  216. SET PassHash = '".make_sec_hash($_POST['password'])."'
  217. WHERE Username = '".db_string($_POST['username'])."'");
  218. }
  219. if (empty($TwoFactor) || $TwoFA->verifyCode($TwoFactor, $_POST['twofa'])) {
  220. if ($Enabled == 1) {
  221. // Check if the current login attempt is from a location previously logged in from
  222. if (apc_exists('DBKEY')) {
  223. $DB->query("
  224. SELECT IP
  225. FROM users_history_ips
  226. WHERE UserID = $UserID");
  227. $IPs = $DB->to_array(false, MYSQLI_NUM);
  228. $QueryParts = array();
  229. foreach ($IPs as $i => $IP) {
  230. $IPs[$i] = DBCrypt::decrypt($IP[0]);
  231. }
  232. $IPs = array_unique($IPs);
  233. if (count($IPs) > 0) { // Always allow first login
  234. foreach ($IPs as $IP) {
  235. $QueryParts[] = "(StartIP<=INET6_ATON('$IP') AND EndIP>=INET6_ATON('$IP'))";
  236. }
  237. $DB->query('SELECT ASN FROM geoip_asn WHERE '.implode(' OR ', $QueryParts));
  238. $PastASNs = array_column($DB->to_array(false, MYSQLI_NUM), 0);
  239. $DB->query("SELECT ASN FROM geoip_asn WHERE StartIP<=INET6_ATON('$_SERVER[REMOTE_ADDR]') AND EndIP>=INET6_ATON('$_SERVER[REMOTE_ADDR]')");
  240. list($CurrentASN) = $DB->next_record();
  241. // if FEATURE_ENFORCE_LOCATIONS is enabled, require users to confirm new logins
  242. if (!in_array($CurrentASN, $PastASNs) && FEATURE_ENFORCE_LOCATIONS) {
  243. // Never logged in from this location before
  244. if ($Cache->get_value('new_location_'.$UserID.'_'.$CurrentASN) !== true) {
  245. $DB->query("
  246. SELECT
  247. UserName,
  248. Email
  249. FROM users_main
  250. WHERE ID = $UserID");
  251. list($Username, $Email) = $DB->next_record();
  252. Users::auth_location($UserID, $Username, $CurrentASN, DBCrypt::decrypt($Email));
  253. require('newlocation.php');
  254. die();
  255. }
  256. }
  257. }
  258. }
  259. $SessionID = Users::make_secret(64);
  260. $KeepLogged = ($_POST['keeplogged'] ?? false) ? 1 : 0;
  261. setcookie('session', $SessionID, (time()+60*60*24*365)*$KeepLogged, '/', '', true, true);
  262. setcookie('userid', $UserID, (time()+60*60*24*365)*$KeepLogged, '/', '', true, true);
  263. // Because we <3 our staff
  264. $Permissions = Permissions::get_permissions($PermissionID);
  265. $CustomPermissions = unserialize($CustomPermissions);
  266. if (isset($Permissions['Permissions']['site_disable_ip_history'])
  267. || isset($CustomPermissions['site_disable_ip_history'])
  268. ) {
  269. $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
  270. }
  271. $DB->query("
  272. INSERT INTO users_sessions
  273. (UserID, SessionID, KeepLogged, Browser, OperatingSystem, IP, LastUpdate, FullUA)
  274. VALUES
  275. ('$UserID', '".db_string($SessionID)."', '$KeepLogged', '$Browser', '$OperatingSystem', '".db_string(apc_exists('DBKEY')?DBCrypt::encrypt($_SERVER['REMOTE_ADDR']):'0.0.0.0')."', '".sqltime()."', '".db_string($_SERVER['HTTP_USER_AGENT'])."')");
  276. $Cache->begin_transaction("users_sessions_$UserID");
  277. $Cache->insert_front($SessionID, array(
  278. 'SessionID' => $SessionID,
  279. 'Browser' => $Browser,
  280. 'OperatingSystem' => $OperatingSystem,
  281. 'IP' => (apc_exists('DBKEY')?DBCrypt::encrypt($_SERVER['REMOTE_ADDR']):'0.0.0.0'),
  282. 'LastUpdate' => sqltime()
  283. ));
  284. $Cache->commit_transaction(0);
  285. $Sql = "
  286. UPDATE users_main
  287. SET
  288. LastLogin = '".sqltime()."',
  289. LastAccess = '".sqltime()."'
  290. WHERE ID = '".db_string($UserID)."'";
  291. $DB->query($Sql);
  292. if (!empty($_COOKIE['redirect'])) {
  293. $URL = $_COOKIE['redirect'];
  294. setcookie('redirect', '', time() - 60 * 60 * 24, '/', '', false);
  295. header("Location: $URL");
  296. die();
  297. } else {
  298. header('Location: index.php');
  299. die();
  300. }
  301. } else {
  302. log_attempt();
  303. if ($Enabled == 2) {
  304. // Save the username in a cookie for the disabled page
  305. setcookie('username', db_string($_POST['username']), time() + 60 * 60, '/', '', false);
  306. header('Location: login.php?action=disabled');
  307. } elseif ($Enabled == 0) {
  308. $Err = 'Your account has not been confirmed.<br />Please check your email.';
  309. }
  310. setcookie('keeplogged', '', time() + 60 * 60 * 24 * 365, '/', '', false);
  311. }
  312. } else {
  313. log_attempt();
  314. $Err = 'Two-factor authentication failed.';
  315. setcookie('keeplogged', '', time() + 60 * 60 * 24 * 365, '/', '', false);
  316. }
  317. } else {
  318. log_attempt();
  319. $Err = 'Your username or password was incorrect.';
  320. setcookie('keeplogged', '', time() + 60 * 60 * 24 * 365, '/', '', false);
  321. }
  322. } else {
  323. log_attempt();
  324. setcookie('keeplogged', '', time() + 60 * 60 * 24 * 365, '/', '', false);
  325. }
  326. } else {
  327. log_attempt();
  328. setcookie('keeplogged', '', time() + 60 * 60 * 24 * 365, '/', '', false);
  329. }
  330. }
  331. require('sections/login/login.php');
  332. }