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 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  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(SERVER_ROOT.'/classes/validate.class.php');
  18. $Validate = NEW VALIDATE;
  19. if (array_key_exists('action', $_GET) && $_GET['action'] == 'disabled') {
  20. require('disabled.php');
  21. die();
  22. }
  23. if (isset($_REQUEST['act']) && $_REQUEST['act'] == 'recover') {
  24. // Recover password
  25. if (!empty($_REQUEST['key'])) {
  26. // User has entered a new password, use step 2
  27. $DB->query("
  28. SELECT
  29. m.ID,
  30. m.Email,
  31. m.ipcc,
  32. i.ResetExpires
  33. FROM users_main AS m
  34. INNER JOIN users_info AS i ON i.UserID = m.ID
  35. WHERE i.ResetKey = '".db_string($_REQUEST['key'])."'
  36. AND i.ResetKey != ''
  37. AND m.Enabled = '1'");
  38. list($UserID, $Email, $Country, $Expires) = $DB->next_record();
  39. if (!apc_exists('DBKEY')) {
  40. error('Database not fully decrypted. Please wait for staff to fix this and try again later');
  41. }
  42. $Email = DBCrypt::decrypt($Email);
  43. if ($UserID && strtotime($Expires) > time()) {
  44. // If the user has requested a password change, and his key has not expired
  45. $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,}$).*$/'));
  46. $Validate->SetFields('verifypassword', '1', 'compare', 'Your passwords did not match.', array('comparefield' => 'password'));
  47. if (!empty($_REQUEST['password'])) {
  48. // If the user has entered a password.
  49. // If the user has not entered a password, $Reset is not set to 1, and the success message is not shown
  50. $Err = $Validate->ValidateForm($_REQUEST);
  51. if ($Err == '') {
  52. // Form validates without error, set new secret and password.
  53. $DB->query("
  54. UPDATE
  55. users_main AS m,
  56. users_info AS i
  57. SET
  58. m.PassHash = '".db_string(Users::make_sec_hash($_REQUEST['password']))."',
  59. i.ResetKey = '',
  60. m.LastLogin = NOW(),
  61. i.ResetExpires = '0000-00-00 00:00:00'
  62. WHERE m.ID = '$UserID'
  63. AND i.UserID = m.ID");
  64. $DB->query("
  65. INSERT INTO users_history_passwords
  66. (UserID, ChangerIP, ChangeTime)
  67. VALUES
  68. ('$UserID', '".DBCrypt::encrypt($_SERVER['REMOTE_ADDR'])."', '".sqltime()."')");
  69. $Reset = true; // Past tense form of "to reset", meaning that password has now been reset
  70. $LoggedUser['ID'] = $UserID; // Set $LoggedUser['ID'] for logout_all_sessions() to work
  71. logout_all_sessions();
  72. }
  73. }
  74. // Either a form asking for them to enter the password
  75. // Or a success message if $Reset is 1
  76. require('recover_step2.php');
  77. } else {
  78. // Either his key has expired, or he hasn't requested a pass change at all
  79. if (strtotime($Expires) < time() && $UserID) {
  80. // If his key has expired, clear all the reset information
  81. $DB->query("
  82. UPDATE users_info
  83. SET ResetKey = '',
  84. ResetExpires = '0000-00-00 00:00:00'
  85. WHERE UserID = '$UserID'");
  86. $_SESSION['reseterr'] = 'The link you were given has expired.'; // Error message to display on form
  87. }
  88. // Show him the first form (enter email address)
  89. header('Location: login.php?act=recover');
  90. }
  91. } // End step 2
  92. // User has not clicked the link in his email, use step 1
  93. else {
  94. $Validate->SetFields('email', '1', 'email', 'You entered an invalid email address.');
  95. if (!empty($_REQUEST['email'])) {
  96. // User has entered email and submitted form
  97. $Err = $Validate->ValidateForm($_REQUEST);
  98. if (!apc_exists('DBKEY')) {
  99. $Err = 'Database not fully decrypted. Please wait for staff to fix this and try again.';
  100. }
  101. if (!$Err) {
  102. // Form validates correctly
  103. $DB->query("
  104. SELECT
  105. Email
  106. FROM users_main
  107. WHERE Enabled = '1'");
  108. while(list($EncEmail) = $DB->next_record()) {
  109. if ($_REQUEST['email'] == 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. AND Enabled = '1'");
  121. list($UserID, $Username, $Email) = $DB->next_record();
  122. $Email = DBCrypt::decrypt($Email);
  123. if ($UserID) {
  124. // Email exists in the database
  125. // Set ResetKey, send out email, and set $Sent to 1 to show success page
  126. Users::resetPassword($UserID, $Username, $Email);
  127. $Sent = 1; // If $Sent is 1, recover_step1.php displays a success message
  128. //Log out all of the users current sessions
  129. $Cache->delete_value("user_info_$UserID");
  130. $Cache->delete_value("user_info_heavy_$UserID");
  131. $Cache->delete_value("user_stats_$UserID");
  132. $Cache->delete_value("enabled_$UserID");
  133. $DB->query("
  134. SELECT SessionID
  135. FROM users_sessions
  136. WHERE UserID = '$UserID'");
  137. while (list($SessionID) = $DB->next_record()) {
  138. $Cache->delete_value("session_$UserID"."_$SessionID");
  139. }
  140. $DB->query("
  141. UPDATE users_sessions
  142. SET Active = 0
  143. WHERE UserID = '$UserID'
  144. AND Active = 1");
  145. } else {
  146. $Err = 'There is no user with that email address.';
  147. }
  148. }
  149. } elseif (!empty($_SESSION['reseterr'])) {
  150. // User has not entered email address, and there is an error set in session data
  151. // This is typically because their key has expired.
  152. // Stick the error into $Err so recover_step1.php can take care of it
  153. $Err = $_SESSION['reseterr'];
  154. unset($_SESSION['reseterr']);
  155. }
  156. // Either a form for the user's email address, or a success message
  157. require('recover_step1.php');
  158. } // End if (step 1)
  159. } // End password recovery
  160. // Normal login
  161. else {
  162. $Validate->SetFields('username', true, 'regex', 'You did not enter a valid username.', array('regex' => USERNAME_REGEX));
  163. $Validate->SetFields('password', '1', 'string', 'You entered an invalid password.', array('minlength' => '6', 'maxlength' => '307200'));
  164. $DB->query("
  165. SELECT ID, Attempts, Bans, BannedUntil
  166. FROM login_attempts
  167. WHERE IP = '".db_string($_SERVER['REMOTE_ADDR'])."'");
  168. // Todo: handle IP encryption
  169. list($AttemptID, $Attempts, $Bans, $BannedUntil) = $DB->next_record();
  170. // Function to log a user's login attempt
  171. function log_attempt($UserID) {
  172. global $DB, $Cache, $AttemptID, $Attempts, $Bans, $BannedUntil;
  173. if (!apc_exists('DBKEY')) { return; }
  174. $IPStr = $_SERVER['REMOTE_ADDR'];
  175. $IPA = substr($IPStr, 0, strcspn($IPStr, '.'));
  176. $IP = Tools::ip_to_unsigned($IPStr);
  177. if ($AttemptID) { // User has attempted to log in recently
  178. $Attempts++;
  179. if ($Attempts > 5) { // Only 6 allowed login attempts, ban user's IP
  180. $BannedUntil = time_plus(60 * 60 * 6);
  181. $DB->query("
  182. UPDATE login_attempts
  183. SET
  184. LastAttempt = '".sqltime()."',
  185. Attempts = '".db_string($Attempts)."',
  186. BannedUntil = '".db_string($BannedUntil)."',
  187. Bans = Bans + 1
  188. WHERE ID = '".db_string($AttemptID)."'");
  189. if ($Bans > 9) { // Automated bruteforce prevention
  190. $DB->query("
  191. SELECT Reason
  192. FROM ip_bans
  193. WHERE $IP BETWEEN FromIP AND ToIP");
  194. if ($DB->has_results()) {
  195. //Ban exists already, only add new entry if not for same reason
  196. list($Reason) = $DB->next_record(MYSQLI_BOTH, false);
  197. if ($Reason != 'Automated ban per >60 failed login attempts') {
  198. $DB->query("
  199. UPDATE ip_bans
  200. SET Reason = CONCAT('Automated ban per >60 failed login attempts AND ', Reason)
  201. WHERE FromIP = $IP
  202. AND ToIP = $IP");
  203. }
  204. } else {
  205. //No ban
  206. $DB->query("
  207. INSERT IGNORE INTO ip_bans
  208. (FromIP, ToIP, Reason)
  209. VALUES
  210. ('".$IP."','".$IP."', 'Automated ban per >60 failed login attempts')");
  211. $Cache->delete_value("ip_bans_$IPA");
  212. }
  213. }
  214. } else {
  215. // User has attempted fewer than 6 logins
  216. $DB->query("
  217. UPDATE login_attempts
  218. SET
  219. LastAttempt = '".sqltime()."',
  220. Attempts = '".db_string($Attempts)."',
  221. BannedUntil = '0000-00-00 00:00:00'
  222. WHERE ID = '".db_string($AttemptID)."'");
  223. }
  224. } else { // User has not attempted to log in recently
  225. $Attempts = 1;
  226. $DB->query("
  227. INSERT INTO login_attempts
  228. (UserID, IP, LastAttempt, Attempts)
  229. VALUES
  230. ('".db_string($UserID)."', '".db_string(DBCrypt::encrypt($IPStr))."', '".sqltime()."', 1)");
  231. }
  232. } // end log_attempt function
  233. // If user has submitted form
  234. if (isset($_POST['username']) && !empty($_POST['username']) && isset($_POST['password']) && !empty($_POST['password'])) {
  235. if (strtotime($BannedUntil) > time()) {
  236. header("Location: login.php");
  237. die();
  238. }
  239. $Err = $Validate->ValidateForm($_POST);
  240. if (!$Err) {
  241. // Passes preliminary validation (username and password "look right")
  242. $DB->query("
  243. SELECT
  244. ID,
  245. PermissionID,
  246. CustomPermissions,
  247. PassHash,
  248. Enabled
  249. FROM users_main
  250. WHERE Username = '".db_string($_POST['username'])."'
  251. AND Username != ''");
  252. list($UserID, $PermissionID, $CustomPermissions, $PassHash, $Enabled) = $DB->next_record(MYSQLI_NUM, array(2));
  253. if (strtotime($BannedUntil) < time()) {
  254. if ($UserID && Users::check_password($_POST['password'], $PassHash)) {
  255. // Update hash if better algorithm available
  256. if (password_needs_rehash($PassHash, PASSWORD_DEFAULT)) {
  257. $DB->query("
  258. UPDATE users_main
  259. SET PassHash = '".make_sec_hash($_POST['password'])."'
  260. WHERE Username = '".db_string($_POST['username'])."'");
  261. }
  262. if ($Enabled == 1) {
  263. $SessionID = Users::make_secret();
  264. $Cookie = $Enc->encrypt($Enc->encrypt($SessionID.'|~|'.$UserID));
  265. if (isset($_POST['keeplogged']) && $_POST['keeplogged']) {
  266. $KeepLogged = 1;
  267. setcookie('session', $Cookie, time() + 60 * 60 * 24 * 365, '/', '', true, true);
  268. } else {
  269. $KeepLogged = 0;
  270. setcookie('session', $Cookie, 0, '/', '', true, true);
  271. }
  272. //TODO: another tracker might enable this for donors, I think it's too stupid to bother adding that
  273. // Because we <3 our staff
  274. $Permissions = Permissions::get_permissions($PermissionID);
  275. $CustomPermissions = unserialize($CustomPermissions);
  276. if (isset($Permissions['Permissions']['site_disable_ip_history'])
  277. || isset($CustomPermissions['site_disable_ip_history'])
  278. ) {
  279. $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
  280. }
  281. $DB->query("
  282. INSERT INTO users_sessions
  283. (UserID, SessionID, KeepLogged, Browser, OperatingSystem, IP, LastUpdate, FullUA)
  284. VALUES
  285. ('$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'])."')");
  286. $Cache->begin_transaction("users_sessions_$UserID");
  287. $Cache->insert_front($SessionID, array(
  288. 'SessionID' => $SessionID,
  289. 'Browser' => $Browser,
  290. 'OperatingSystem' => $OperatingSystem,
  291. 'IP' => (apc_exists('DBKEY')?DBCrypt::encrypt($_SERVER['REMOTE_ADDR']):'0.0.0.0'),
  292. 'LastUpdate' => sqltime()
  293. ));
  294. $Cache->commit_transaction(0);
  295. $Sql = "
  296. UPDATE users_main
  297. SET
  298. LastLogin = '".sqltime()."',
  299. LastAccess = '".sqltime()."'
  300. WHERE ID = '".db_string($UserID)."'";
  301. $DB->query($Sql);
  302. if (!empty($_COOKIE['redirect'])) {
  303. $URL = $_COOKIE['redirect'];
  304. setcookie('redirect', '', time() - 60 * 60 * 24, '/', '', false);
  305. header("Location: $URL");
  306. die();
  307. } else {
  308. header('Location: index.php');
  309. die();
  310. }
  311. } else {
  312. log_attempt($UserID);
  313. if ($Enabled == 2) {
  314. // Save the username in a cookie for the disabled page
  315. setcookie('username', db_string($_POST['username']), time() + 60 * 60, '/', '', false);
  316. header('Location: login.php?action=disabled');
  317. } elseif ($Enabled == 0) {
  318. $Err = 'Your account has not been confirmed.<br />Please check your email.';
  319. }
  320. setcookie('keeplogged', '', time() + 60 * 60 * 24 * 365, '/', '', false);
  321. }
  322. } else {
  323. log_attempt($UserID);
  324. $Err = 'Your username or password was incorrect.';
  325. setcookie('keeplogged', '', time() + 60 * 60 * 24 * 365, '/', '', false);
  326. }
  327. } else {
  328. log_attempt($UserID);
  329. setcookie('keeplogged', '', time() + 60 * 60 * 24 * 365, '/', '', false);
  330. }
  331. } else {
  332. log_attempt('0');
  333. setcookie('keeplogged', '', time() + 60 * 60 * 24 * 365, '/', '', false);
  334. }
  335. }
  336. require('sections/login/login.php');
  337. }