BioTorrents.de’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 18KB

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