From 82e7a917ab3d92d96f63ae839dac41bd96129eec Mon Sep 17 00:00:00 2001 From: Guangcong Luo Date: Thu, 20 Oct 2016 12:53:53 -0400 Subject: [PATCH] Move session library inside repository For too long, ntbb-session and ntbb-database have been maintained outside of this repo, but no longer! All these files are now part of the repository, making it significantly more self-contained. If I had to say why it took this long, I think it was mostly inertia. It was easier leaving them where they were than having to audit them for private keys in the wrong places, etc. I'm starting to think of PS more as sim first, website secondary than the other way around, now. Especially now that we don't have a forum, the website itself isn't really important... Maybe one day I'll get rid of the landing page and make the sim itself the first thing you see when you hit pokemonshowdown.com... but today is not that day! The repo is still not "batteries-included" since I am not going to teach anyone how to set up PHP and MySQL or even get the config files working. But for anyone who wanted their own client, well, it gets a lot easier to do now. --- README.md | 11 +- action.php | 2 +- config/config-example.inc.php | 46 +++ config/servers-example.inc.php | 11 + lib/ntbb-database.lib.php | 65 ++++ lib/ntbb-ladder.lib.php | 2 +- lib/ntbb-session.lib.php | 640 +++++++++++++++++++++++++++++++++ lib/ntbb-users.sql | 51 +++ lib/ntbb_sessions.sql | 41 +++ 9 files changed, 860 insertions(+), 9 deletions(-) create mode 100644 config/config-example.inc.php create mode 100644 config/servers-example.inc.php create mode 100644 lib/ntbb-database.lib.php mode change 100755 => 100644 lib/ntbb-ladder.lib.php create mode 100644 lib/ntbb-session.lib.php create mode 100644 lib/ntbb-users.sql create mode 100644 lib/ntbb_sessions.sql diff --git a/README.md b/README.md index 478fd00f4..6500136d6 100644 --- a/README.md +++ b/README.md @@ -42,19 +42,16 @@ Everything else can be tested, though. Warning ------------------------------------------------------------------------ -This repository is not "batteries included". It does NOT include everything -necessary to run a full Pokémon Showdown client. - -In particular, it doesn't include a login/authentication server, nor does it -include the database abstraction library used by the ladder library (although -it's similar enough to `mysqli` that you can use that with minimal changes). +This repository is not "batteries included". It does NOT include instructions +to run a full Pokémon Showdown client, and we will not provide them. Please +do not ask for help on this; you will be turned away. It also doesn't include several resource files (namely, the `/audio/` and `/sprites/` directories) for size reasons. In other words, this repository is incomplete and NOT intended for people who wish to serve their own Pokémon Showdown client (you can, but it'll -require you to rewrite some things). Rather, it's intended for people who +require you figure it out yourself). Rather, it's intended for people who wish to contribute and submit pull requests to Pokémon Showdown's client. License diff --git a/action.php b/action.php index fee0ab2a4..d47e543d0 100644 --- a/action.php +++ b/action.php @@ -24,7 +24,7 @@ if (preg_match('/^http\\:\\/\\/[a-z0-9]+\\.psim\\.us\\//', @$_SERVER['HTTP_REFER } // header("X-Debug: " . @$_SERVER['HTTP_REFERER']); -include_once '../pokemonshowdown.com/lib/ntbb-session.lib.php'; +include_once 'lib/ntbb-session.lib.php'; include_once '../pokemonshowdown.com/config/servers.inc.php'; include_once 'lib/dispatcher.lib.php'; diff --git a/config/config-example.inc.php b/config/config-example.inc.php new file mode 100644 index 000000000..ed291e0e3 --- /dev/null +++ b/config/config-example.inc.php @@ -0,0 +1,46 @@ + ['zarel'], + +// password and SID hashing settings + + 'password_cost' => 12, + 'sid_length' => 15, + 'sid_cost' => 4, + +// database + + 'server' => 'localhost', + 'username' => '', + 'password' => '', + 'database' => '', + 'prefix' => 'ps_', + 'charset' => 'utf8', + +// CORS requests + + 'cors' => [ + '/^http:\/\/smogon\.com$/' => 'smogon.com_', + '/^http:\/\/www\.smogon\.com$/' => 'www.smogon.com_', + '/^http:\/\/logs\.psim\.us$/' => 'logs.psim.us_', + '/^http:\/\/logs\.psim\.us:8080$/' => 'logs.psim.us_', + '/^http:\/\/[a-z0-9]+\.psim\.us$/' => '', + '/^http:\/\/play\.pokemonshowdown\.com$/' => '', + ], + +// key signing for SSO + + 'privatekeys' => [ + +1 => '-----BEGIN PRIVATE KEY----- +[insert RSA private key] +-----END PRIVATE KEY----- +', + + ] + +]; diff --git a/config/servers-example.inc.php b/config/servers-example.inc.php new file mode 100644 index 000000000..453b925ad --- /dev/null +++ b/config/servers-example.inc.php @@ -0,0 +1,11 @@ + + array ( + 'name' => 'Smogon University', + 'id' => 'showdown', + 'server' => 'sim.psim.us', + 'port' => 8000, + ), +); diff --git a/lib/ntbb-database.lib.php b/lib/ntbb-database.lib.php new file mode 100644 index 000000000..4a18a9e74 --- /dev/null +++ b/lib/ntbb-database.lib.php @@ -0,0 +1,65 @@ +server = $server; + $this->username = $username; + $this->password = $password; + $this->database = $database; + $this->prefix = $prefix; + $this->charset = $charset; + } + + function connect() { + if (!$this->db) { + $this->db = mysqli_connect($this->server, $this->username, $this->password, $this->database); + if ($this->charset) { + mysqli_set_charset($this->db, $this->charset); + } + } + } + function query($query) { + $this->connect(); + //$this->queries[] = $query; + return mysqli_query($this->db, $query); + } + function fetch_assoc($resource) { + return mysqli_fetch_assoc($resource); + } + function fetch($resource) { + return mysqli_fetch_assoc($resource); + } + function escape($data) { + $this->connect(); + return mysqli_real_escape_string($this->db, $data); + } + function error() { + if ($this->db) { + return mysqli_error($this->db); + } + } + function insert_id() { + if ($this->db) { + return mysqli_insert_id($this->db); + } + } +} + +$psdb = new NTBBDatabase($psconfig['server'], + $psconfig['username'], + $psconfig['password'], + $psconfig['database'], + $psconfig['prefix'], + $psconfig['charset']); diff --git a/lib/ntbb-ladder.lib.php b/lib/ntbb-ladder.lib.php old mode 100755 new mode 100644 index ab0da2b5c..f888b0c31 --- a/lib/ntbb-ladder.lib.php +++ b/lib/ntbb-ladder.lib.php @@ -4,7 +4,7 @@ error_reporting(E_ALL); // An implementation of the Glicko2 rating system. -@include_once dirname(__FILE__).'/../../pokemonshowdown.com/lib/ntbb-database.lib.php'; +@include_once dirname(__FILE__).'/ntbb-database.lib.php'; // connect to the ladder database (if we aren't already connected) if (empty($ladderdb)) { diff --git a/lib/ntbb-session.lib.php b/lib/ntbb-session.lib.php new file mode 100644 index 000000000..1da9cd2cf --- /dev/null +++ b/lib/ntbb-session.lib.php @@ -0,0 +1,640 @@ +getGuest(); + + // see if we're logged in + $osid = @$_COOKIE['sid']; + if (!$osid) { + // nope, not logged in + return; + } + $osidsplit = explode('x', $osid, 2); + if (count($osidsplit) !== 2) { + // malformed `sid` cookie + $this->killCookie(); + return; + } + $session = intval($osidsplit[0]); + $res = $psdb->query( + "SELECT sid, timeout, `{$psdb->prefix}users`.* " . + "FROM `{$psdb->prefix}sessions`, `{$psdb->prefix}users` " . + "WHERE `session` = $session " . + "AND `{$psdb->prefix}sessions`.`userid` = `{$psdb->prefix}users`.`userid` " . + "LIMIT 1"); + if (!$res) { + // query problem? + $this->killCookie(); + return; + } + $sess = $psdb->fetch_assoc($res); + if (!$sess || !password_verify(base64_decode($osidsplit[1]), $sess['sid'])) { + // invalid session ID + $this->killCookie(); + return; + } + if (intval($sess['timeout'])<$ctime) { + // session expired + // delete all sessions that will expire within 30 minutes + $ctime += 60 * 30; + $psdb->query("DELETE FROM `{$psdb->prefix}sessions` WHERE `timeout` < $ctime"); + $this->killCookie(); + return; + } + + // okay, legit session ID - you're logged in now. + $curuser = $sess; + $curuser['loggedin'] = true; + // unset these values to avoid them being leaked accidentally + $curuser['outdatedpassword'] = !!$curuser['password']; + unset($curuser['password']); + unset($curuser['nonce']); + unset($curuser['passwordhash']); + + $this->sid = $osid; + $this->session = $session; + } + + // taken from http://stackoverflow.com/questions/594112/matching-an-ip-to-a-cidr-mask-in-php5 + function cidr_match($ip, $range) { + list($subnet, $bits) = explode('/', $range); + $ip = ip2long($ip); + $subnet = ip2long($subnet); + $mask = -1 << (32 - $bits); + $subnet &= $mask; # nb: in case the supplied subnet wasn't correctly aligned + return ($ip & $mask) == $subnet; + } + + function getIp() { + $ip = $_SERVER['REMOTE_ADDR']; + foreach ($this->trustedproxies as &$proxyip) { + if ($this->cidr_match($ip, $proxyip)) { + $parts = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); + $ip = array_pop($parts); + break; + } + } + return $ip; + } + + function userid($username) { + if (!$username) $username = ''; + $username = strtr($username, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz"); + return preg_replace('/[^A-Za-z0-9]+/','',$username); + } + function getGuest($username='') { + $userid = $this->userid($username); + if (!$userid) + { + $username = 'Guest'; + $userid = 'guest'; + } + return array( + 'username' => $username, + 'userid' => $userid, + 'group' => 0, + 'loggedin' => false + ); + } + + function qsid() { return $this->sid ? '?sid='.$this->sid : ''; } + function asid() { return $this->sid ? '&sid='.$this->sid : ''; } + function hasid() { return $this->sid ? '&sid='.$this->sid : ''; } + function fsid() { return $this->sid ? '' : ''; } + + /** + * New SID and password hashing functions. + */ + function mksid() { + global $psconfig; + if (!function_exists('mcrypt_create_iv')) { + error_log('mcrypt_create_iv is not defined!'); + die; + } + return mcrypt_create_iv($psconfig['sid_length'], MCRYPT_DEV_URANDOM); + } + function sidHash($sid) { + global $psconfig; + return password_hash($sid, PASSWORD_DEFAULT, array('cost' => $psconfig['sid_cost'])); + } + function passwordNeedsRehash($hash) { + global $psconfig; + return password_needs_rehash($hash, PASSWORD_DEFAULT, + array('cost' => $psconfig['password_cost']) + ); + } + function passwordHash($pass) { + global $psconfig; + return password_hash($pass, PASSWORD_DEFAULT, + array('cost' => $psconfig['password_cost']) + ); + } + + public function passwordVerify($name, $pass) { + global $psdb; + + $userid = $this->userid($name); + + $res = $psdb->query( + "SELECT `password`, `nonce`, `passwordhash` " . + "FROM `{$psdb->prefix}users` " . + "WHERE `userid` = '".$psdb->escape($userid)."' " . + "LIMIT 1" + ); + if (!$res) return false; + + $user = $psdb->fetch_assoc($res); + return $this->passwordVerifyInner($userid, $pass, $user); + } + + private function passwordVerifyInner($userid, $pass, $user) { + global $psdb; + + // throttle + $ip = $this->getIp(); + $res = $psdb->query( + "SELECT `count`, `time` " . + "FROM `{$psdb->prefix}loginthrottle` " . + "WHERE `ip` = '".$ip."' " . + "LIMIT 1" + ); + $loginthrottle = null; + if ($res) $loginthrottle = $psdb->fetch_assoc($res); + if ($loginthrottle) { + if ($loginthrottle['count'] > 500) { + $loginthrottle['count']++; + $psdb->query("UPDATE `{$psdb->prefix}loginthrottle` SET count = {$loginthrottle['count']}, lastuserid = '".$userid."', `time` = '".time()."' WHERE ip = '".$ip."'"); + return false; + } else if ($loginthrottle['time'] + 24 * 60 * 60 < time()) { + $loginthrottle = [ + 'count' => 0, + 'time' => time(), + ]; + } + } + + $rehash = false; + if ($user['passwordhash']) { + // new password hashes + if (!password_verify($pass, $user['passwordhash'])) { + // wrong password + if ($loginthrottle) { + $loginthrottle['count']++; + $psdb->query("UPDATE `{$psdb->prefix}loginthrottle` SET count = {$loginthrottle['count']}, lastuserid = '".$userid."', `time` = '".time()."' WHERE ip = '".$ip."'"); + } else { + $psdb->query("INSERT INTO `{$psdb->prefix}loginthrottle` (ip, count, lastuserid, `time`) VALUES ('".$ip."', 1, '".$userid."', '".time()."')"); + } + return false; + } + $rehash = $this->passwordNeedsRehash($user['passwordhash']); + } else if ($user['password'] && $user['nonce']) { + // original ntbb-session password hashes + return false; + } else { + // error + return false; + } + if ($rehash) { + // create a new password hash for the user + $hash = $this->passwordHash($pass); + if ($hash) { + $psdb->query("UPDATE `{$psdb->prefix}users` SET `passwordhash`='" . $psdb->escape($hash) . "', `password`=NULL, `nonce`=NULL WHERE `userid`='" . $psdb->escape($userid) . "'"); + } + } + return true; + } + + function login($name, $pass, $timeout = false, $debug = false) { + global $psdb, $curuser; + $ctime = time(); + + $this->logout(); + $userid = $this->userid($name); + $res = $psdb->query("SELECT * FROM `{$psdb->prefix}users` WHERE `userid` = '".$psdb->escape($userid)."' LIMIT 1"); + if (!$res) { + if ($debug) error_log('no such user'); + return $curuser; + } + $user = $psdb->fetch_assoc($res); + if ($user['banstate'] >= 100) { + return $curuser; + } + if (!$this->passwordVerifyInner($userid, $pass, $user)) { + if ($debug) error_log('wrong password'); + return $curuser; + } + if (!$timeout) { + // expire in a week and 30 minutes + $timeout = (14*24*60+30)*60; + } + $timeout += $ctime; + + $nsid = $this->mksid(); + $nsidhash = $this->sidHash($nsid); + $res = $psdb->query("INSERT INTO `{$psdb->prefix}sessions` (`userid`,`sid`,`time`,`timeout`,`ip`) VALUES ('".$psdb->escape($user['userid'])."', '" . $psdb->escape($nsidhash) . "',$ctime,$timeout,'".$psdb->escape($this->getIp())."')"); + if (!$res) die; + + $this->session = $psdb->insert_id(); + $this->sid = $this->session . 'x' . base64_encode($nsid); + + $curuser = $user; + $curuser['loggedin'] = true; + // unset these values to avoid them being leaked accidentally + $curuser['outdatedpassword'] = !!$curuser['password']; + unset($curuser['password']); + unset($curuser['nonce']); + unset($curuser['passwordhash']); + + setcookie('sid', $this->sid, $timeout, '/', $this->cookiedomain, false, true); + + return $curuser; + } + + function killCookie() { + setcookie('sid', '', time()-60*60*24*2, + '/', $this->cookiedomain, false, true); + } + + function csrfData() { + echo ''; + return ''; + } + + function csrfCheck() { + if (empty($_POST['csrf'])) return false; + $csrf = $_POST['csrf']; + if ($csrf === @$_COOKIE['sid']) return true; + return false; + } + + function logout() { + global $psdb,$curuser; + + if (!$this->session) return $curuser; + + $curuser = $this->getGuest(); + $psdb->query("DELETE FROM `{$psdb->prefix}sessions` WHERE `session` = '{$this->session}' LIMIT 1"); + $this->sid = ''; + $this->session = 0; + + $this->killCookie(); + + return $curuser; + } + + function createPasswordResetToken($name, $timeout=false) { + global $psdb, $curuser; + $ctime = time(); + + $userid = $this->userid($name); + $res = $psdb->query("SELECT * FROM `{$psdb->prefix}users` WHERE `userid` = '".$psdb->escape($userid)."' LIMIT 1"); + if (!$res) // user doesn't exist + return false; + $user = $psdb->fetch_assoc($res); + if (!$timeout) { + $timeout = 7*24*60*60; + } + $timeout += $ctime; + { + $modlogentry = "Password reset token generated"; + $psdb->query("INSERT INTO `{$psdb->prefix}usermodlog` (`userid`,`actorid`,`date`,`ip`,`entry`) VALUES ('".$psdb->escape($user['userid'])."','".$psdb->escape($curuser['userid'])."','" . $psdb->escape(time()) . "','".$psdb->escape($this->getIp())."','".$psdb->escape($modlogentry)."')"); + + // magical character string... + $nsid = bin2hex($this->mksid()); + $res = $psdb->query("INSERT INTO `{$psdb->prefix}sessions` (`userid`,`sid`,`time`,`timeout`,`ip`) VALUES ('".$psdb->escape($user['userid'])."', '$nsid',$ctime,$timeout,'".$psdb->escape($this->getIp())."')"); + if (!$res) die($psdb->error()); + } + + return $nsid; + } + + function validatePasswordResetToken($token) { + global $psdb, $psconfig; + if (strlen($token) !== ($psconfig['sid_length'] * 2)) return false; + $res = $psdb->query("SELECT * FROM `{$psdb->prefix}sessions` WHERE `sid` = '".$psdb->escape($token)."' LIMIT 1"); + $session = $psdb->fetch_assoc($res); + if (!$session) return false; + + if (intval($session['timeout'])userid($userid); + if (!$userid || + (($userid === $curuser['userid']) && !empty($user['loggedin']))) { + return $curuser; + } + + $res = $psdb->query("SELECT * FROM `{$psdb->prefix}users` WHERE `userid` = '".$psdb->escape($userid)."' LIMIT 1"); + if (!$res) // query failed for weird reason + return false; + $user = $psdb->fetch_assoc($res); + + if (!$user) + { + return false; + } + + // unset these values to avoid them being leaked accidentally + $user['outdatedpassword'] = !!$user['password']; + unset($user['password']); + unset($user['nonce']); + unset($user['passwordhash']); + + return $user; + } + + function getGroupName($user=false) { + global $ntbb_cache; + $user = $this->getUser($user); + return @$ntbb_cache['groups'][$user['group']]['name']; + } + + function getGroupSymbol($user=false) { + global $ntbb_cache; + $user = $this->getUser($user); + return @$ntbb_cache['groups'][$user['group']]['symbol']; + } + + function getUserData($username) { + $userdata = $this->getUser($username); + + if ($userdata) return $userdata; + + $userdata = $this->getGuest($username); + + return $userdata; + } + + function getAssertion($userid, $serverhostname, $user = null, $challengekeyid = -1, $challenge = '', $challengeprefix = '') { + global $psdb, $curuser, $psconfig; + + if (substr($userid, 0, 5) === 'guest') { + return ';;Your username cannot start with \'guest\'.'; + } else if (strlen($userid) > 18) { + return ';;Your username must be less than 19 characters long.'; + } else if (!$this->isUseridAllowed($userid)) { + return ';;Your username contains disallowed text.'; + } + + $data = ''; + if (!$user) { + $user = $curuser; + } + $ip = $this->getIp(); + $forceUsertype = false; + + if (($user['userid'] === $userid) && !empty($user['loggedin'])) { + // already logged in + $usertype = '2'; + if (in_array($userid, $psconfig->sysops, true)) { + $usertype = '3'; + } else { + // check autoconfirmed + if ($forceUsertype) { + $usertype = $forceUsertype; + } else if (intval(@$user['banstate']) <= -10) { + $usertype = '4'; + } else if (@$user['banstate'] >= 100) { + return ';;Your account is disabled.'; + } else if (@$user['banstate'] >= 40) { + if ($serverhostname === 'sim.psim.us') { + $usertype = '40'; + } else { + $usertype = '2'; + } + } else if (@$user['banstate'] >= 30) { + $usertype = '6'; + } else if (@$user['banstate'] >= 20) { + $usertype = '5'; + } else if (@$user['banstate'] == 0) { + if (@$user['registertime'] && time() - $user['registertime'] > 7*24*60*60) { + $res = $psdb->query("SELECT formatid FROM ntbb_ladder WHERE userid = '".$psdb->escape($userid)."' LIMIT 1"); + if ($psdb->fetch_assoc($res)) { + $usertype = '4'; + $psdb->query("UPDATE ntbb_users SET banstate = -10 WHERE userid = '".$psdb->escape($userid)."' LIMIT 1"); + } + } + } + } + if (!@$user['logintime'] || time() - $user['logintime'] > 24*60*60) { + $psdb->query("UPDATE ntbb_users SET logintime = " . time() . ", loginip = '".$psdb->escape($ip)."' WHERE userid = '".$psdb->escape($userid)."' LIMIT 1"); + } + $data = $user['userid'].',' . $usertype . ','.time().','.$serverhostname; + } else { + if ((strlen($userid) < 1) || ctype_digit($userid)) { + return ';;Your username must contain at least one letter.'; + } + $res = $psdb->query("SELECT * FROM `{$psdb->prefix}users` WHERE `userid` = '".$psdb->escape($userid)."' LIMIT 1"); + if (!$res) { + // query failed for weird reason + return ';;The login server is under heavy load, please try logging in again later'; + } else if ($row = $psdb->fetch_assoc($res)) { + // Username exists, but the user isn't logged in: require authentication. + if ($row['banstate'] >= 100) { + return ';;Your account is disabled.'; + } + if ($row['password'] && $row['nonce']) { + return ';;Your account is disabled.'; + } + return ';'; + } else { + // Unregistered username. + $usertype = '1'; + if ($forceUsertype) $usertype = $forceUsertype; + $data = $userid.','.$usertype.','.time().','.$serverhostname; + } + } + + $splitChallenge = explode(';', $challenge); + if (count($splitChallenge) == 1) { + $splitChallenge = explode('|', $challenge); + } + if (count($splitChallenge) == 1) { + $splitChallenge = explode('%7C', $challenge); + } + $challengetoken = @$_REQUEST['challengetoken']; + if (count($splitChallenge) > 1) { + $challengekeyid = intval($splitChallenge[0]); + $challenge = $splitChallenge[1]; + if (@$splitChallenge[2] && !$challengetoken) $challengetoken = $splitChallenge[2]; + } + + if ($challengekeyid < 1) { + return ';;This server is requesting an invalid login key. This probably means that either you are not connected to a server, or the server is set up incorrectly.'; + } else if ($challengekeyid < 2) { + // Compromised keys - no longer supported. + return ';;This server is using login key ' . $challengekeyid . ', which is no longer supported. Please tell the server operator to update their config.js file.'; + } else if (empty($psconfig['privatekeys'][$challengekeyid])) { + // Bogus key id. + return ';;Unknown key ID'; + } else if (!preg_match('/^[0-9a-f]*$/', $challenge)) { + // Bogus challenge. + return ';;Corrupt challenge'; + } else { + // Include the challenge in the assertion. + $data = $challengeprefix . $challenge . ',' . $data; + } + + if (strpos($challengeprefix . $challenge, ',') !== false) { + trigger_error("challenge contains comma? ".$challengeprefix." // ".$challenge, E_USER_ERROR); + } + + if (function_exists('psconfig_validate_assertion')) { + psconfig_validate_assertion($data, $serverhostname); + } + + $sig = ''; + openssl_sign($data, $sig, openssl_get_privatekey($psconfig['privatekeys'][$challengekeyid])); + return $data.';'.bin2hex($sig); + } + + function modifyUser($user, $changes) { + global $psdb, $curuser; + $userid = $user; + if (is_array($user)) $userid = $user['userid']; + + $res = $psdb->query("SELECT * FROM `{$psdb->prefix}users` WHERE `userid` = '".$psdb->escape($userid)."' LIMIT 1"); + if (!$res) // query failed for weird reason + return false; + $user = $psdb->fetch_assoc($res); + if (!$user['userid']) return false; + + if (@$changes['password']) { + $modlogentry = "Password changed from: {$user['passwordhash']}"; + $psdb->query("INSERT INTO `{$psdb->prefix}usermodlog` (`userid`,`actorid`,`date`,`ip`,`entry`) VALUES ('".$psdb->escape($user['userid'])."','".$psdb->escape($curuser['userid'])."','" . $psdb->escape(time()) . "','".$psdb->escape($this->getIp())."','".$psdb->escape($modlogentry)."')"); + $passwordhash = $this->passwordHash($changes['password']); + $psdb->query("UPDATE `{$psdb->prefix}users` SET `passwordhash`='" . $psdb->escape($passwordhash) . "', `password`=NULL, `nonce`=NULL WHERE `userid` = '".$psdb->escape($user['userid'])."'"); + if ($psdb->error()) { + return false; + } + $psdb->query("DELETE FROM `{$psdb->prefix}sessions` WHERE `userid` = '".$psdb->escape($user['userid'])."'"); + if ($curuser['userid'] === $userid) { + $this->login($userid, $changes['password']); + } + } + if (!empty($changes['group'])) { + $group = intval($changes['group']); + $psdb->query("UPDATE `{$psdb->prefix}users` SET `group` = $group WHERE `userid` = '".$psdb->escape($user['userid'])."'"); + if ($psdb->error()) { + return false; + } + } + if (!empty($changes['username'])) { + $newUsername = $changes['username']; + if (strlen($newUsername) > 18) return false; + $newUserid = $this->userid($newUsername); + if ($userid !== $newUserid) return false; + $psdb->query("UPDATE `{$psdb->prefix}users` SET `username` = '".$psdb->escape($newUsername)."' WHERE `userid` = '".$psdb->escape($userid)."'"); + if ($psdb->error()) { + return false; + } + } + if (!empty($changes['userdata'])) { + $psdb->query("UPDATE `{$psdb->prefix}users` SET `userdata` = '".$psdb->escape($changes['userdata'])."' WHERE `userid` = '".$psdb->escape($user['userid'])."'"); + if ($psdb->error()) { + return false; + } + $user['userdata'] = $changes['userdata']; + } + return true; + } + + function getRecentRegistrationCount($ip = '', $timeperiod = 7200 /* 2 hours */) { + global $psdb; + if ($ip === '') { + $ip = $this->getIp(); + } + $timestamp = time() - $timeperiod; + $res = $psdb->query("SELECT COUNT(*) AS `registrationcount` FROM `{$psdb->prefix}users` WHERE `ip` = '" . $psdb->escape($ip) . "' AND `registertime` > '" . $timestamp . "'"); + if (!$res) { + return false; + } + $user = $psdb->fetch_assoc($res); + if ($user === NULL) { // Should be impossible. + return 0; + } + return $user['registrationcount']; + } + + function addUser($user, $password) + { + global $psdb, $curuser; + $ctime = time(); + + $user['userid'] = $this->userid($user['username']); + $user['passwordhash'] = $this->passwordHash($password); + + if (!$this->isUseridAllowed($user['userid'])) { + return false; + } + + $psdb->query("INSERT INTO `{$psdb->prefix}users` (`userid`,`username`,`passwordhash`,`email`,`registertime`,`ip`) VALUES ('".$psdb->escape($user['userid'])."','".$psdb->escape($user['username'])."','" . $psdb->escape($user['passwordhash']) . "','".$psdb->escape(@$user['email'])."',$ctime,'".$psdb->escape($this->getIp())."')"); + if ($psdb->error()) + { + return false; + } + + $user['usernum'] = $psdb->insert_id(); + $user['loggedin'] = true; + $this->login($user['username'], $password); + return $user; + } + + function wordfilter($text) { + $text = str_ireplace('lolicon', '*', $text); + $text = str_ireplace('roricon', '*', $text); + return $text; + } + + function isUseridAllowed($userid) { + if (strpos($userid, 'nigger') !== false) return false; + if (strpos($userid, 'nigga') !== false) return false; + if (strpos($userid, 'faggot') !== false) return false; + if (strpos($userid, 'lolicon') !== false) return false; + if (strpos($userid, 'roricon') !== false) return false; + if (strpos($userid, 'lazyafrican') !== false) return false; + return true; + } +} + +$users = new NTBBSession(); + diff --git a/lib/ntbb-users.sql b/lib/ntbb-users.sql new file mode 100644 index 000000000..856e46dc3 --- /dev/null +++ b/lib/ntbb-users.sql @@ -0,0 +1,51 @@ +-- Database: showdown + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `ntbb_users` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `ntbb_users` ( + `userid` varbinary(255) NOT NULL, + `usernum` int(11) NOT NULL AUTO_INCREMENT, + `username` varbinary(255) NOT NULL, + `password` varbinary(255) DEFAULT NULL, + `nonce` varbinary(255) DEFAULT NULL, + `passwordhash` varbinary(255) DEFAULT NULL, + `email` varbinary(255) DEFAULT NULL, + `registertime` bigint(20) NOT NULL, + `group` int(11) NOT NULL DEFAULT '1', + `banstate` int(11) NOT NULL DEFAULT '0', + `ip` varchar(255) NOT NULL DEFAULT '', + `avatar` int(11) NOT NULL DEFAULT '0', + `account` varbinary(255) DEFAULT NULL, + `logintime` bigint(20) NOT NULL DEFAULT '0', + `loginip` varbinary(255) DEFAULT NULL, + PRIMARY KEY (`userid`), + UNIQUE KEY `usernum` (`usernum`), + KEY `ip` (`ip`), + KEY `loginip` (`loginip`), + KEY `account` (`account`) +) ENGINE=InnoDB AUTO_INCREMENT=7379773 DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; diff --git a/lib/ntbb_sessions.sql b/lib/ntbb_sessions.sql new file mode 100644 index 000000000..6ddf1061b --- /dev/null +++ b/lib/ntbb_sessions.sql @@ -0,0 +1,41 @@ +-- Database: showdown + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `ntbb_sessions` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `ntbb_sessions` ( + `session` bigint(20) NOT NULL AUTO_INCREMENT, + `sid` varchar(255) NOT NULL, + `userid` varchar(255) NOT NULL, + `time` int(11) NOT NULL, + `timeout` int(11) NOT NULL, + `ip` varchar(255) NOT NULL, + PRIMARY KEY (`session`), + KEY `userid` (`userid`), + KEY `sid` (`sid`), + KEY `timeout` (`timeout`) +) ENGINE=InnoDB AUTO_INCREMENT=1700523 DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;