getGuest();
// see if we're logged in
$scookie = $_POST['sid'] ?? $_COOKIE['sid'] ?? null;
if (!$scookie) {
// nope, not logged in
return;
}
$sid = '';
$session = 0;
$scsplit = explode(',', $scookie);
if (count($scsplit) === 3) {
$this->cookiename = $scsplit[0];
$session = intval($scsplit[1]);
$sid = $scsplit[2];
$this->sid = $sid;
}
if (!$session) {
return;
}
$res = $psdb->query(
"SELECT sid, timeout, `{$psdb->prefix}users`.* " .
"FROM `{$psdb->prefix}sessions`, `{$psdb->prefix}users` " .
"WHERE `session` = ? " .
"AND `{$psdb->prefix}sessions`.`userid` = `{$psdb->prefix}users`.`userid` " .
"LIMIT 1", [$session]);
if (!$res) {
// query problem?
$this->killCookie();
return;
}
$sess = $psdb->fetch_assoc($res);
if (!$sess || !password_verify($sid, $sess['sid'])) {
// invalid session ID
$this->killCookie();
return;
}
if (intval($sess['timeout'])<$ctime) {
// session expired
$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'];
$curuser['password'] = null;
$curuser['nonce'] = null;
$curuser['passwordhash'] = null;
$this->scookie = $scookie;
$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): string {
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 [
'username' => $username,
'userid' => $userid,
'group' => 0,
'loggedin' => false,
'ip' => $this->getIp(),
];
}
function qsid() { return $this->scookie ? '?sid='.$this->scookie : ''; }
function asid() { return $this->scookie ? '&sid='.$this->scookie : ''; }
function hasid() { return $this->scookie ? '&sid='.$this->scookie : ''; }
function fsid() { return $this->scookie ? '' : ''; }
/**
* New SID and password hashing functions.
*/
function mksid(string $osid) {
if (function_exists('psconfig_mksid')) {
return psconfig_mksid($osid);
}
return substr(base64_encode(random_bytes(18)), 0, 24);
}
function sidHash(string $sid) {
global $psconfig;
return password_hash($sid, PASSWORD_DEFAULT, ['cost' => $psconfig['sid_cost']]);
}
function passwordNeedsRehash(string $hash) {
global $psconfig;
return password_needs_rehash($hash, PASSWORD_DEFAULT, ['cost' => $psconfig['password_cost']]);
}
function passwordHash(string $pass) {
global $psconfig;
return password_hash($pass, PASSWORD_DEFAULT, ['cost' => $psconfig['password_cost']]);
}
public function passwordVerify(string $name, string $pass) {
global $psdb;
$userid = $this->userid($name);
$res = $psdb->query(
"SELECT `password`, `nonce`, `passwordhash` " .
"FROM `{$psdb->prefix}users` " .
"WHERE `userid` = ? " .
"LIMIT 1",
[$userid]
);
if (!$res) return false;
$user = $psdb->fetch_assoc($res);
return $this->passwordVerifyInner($userid, $pass, $user);
}
private function passwordVerifyInner(string $userid, string $pass, $user) {
global $psdb, $psconfig;
// throttle
$ip = $this->getIp();
$res = $psdb->query(
"SELECT `count`, `time` FROM `{$psdb->prefix}loginthrottle` WHERE `ip` = ? LIMIT 1",
[$ip]
);
$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 = ?, lastuserid = ?, `time` = ? WHERE ip = ?", [$loginthrottle['count'], $userid, time(), $ip]);
return false;
} else if ($loginthrottle['time'] + 24 * 60 * 60 < time()) {
$loginthrottle = [
'count' => 0,
'time' => time(),
];
}
}
if (substr(@$user['email'], -1) === '@') {
// Forgive me, gods, for I have hardcoded way more than I really should have
$valResult = shell_exec("cd /var/www/html/play.pokemonshowdown.com && node lib/validate-token.js " . escapeshellarg($pass));
$payload = json_decode($valResult, true);
if (!$payload) return false;
if (strpos($payload['aud'], $psconfig['gapi_clientid']) === false) return false;
if ($payload['email'] === substr($user['email'], 0, -1)) {
return true;
}
return false;
}
$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 = ?, lastuserid = ?, `time` = ? WHERE ip = ?", [$loginthrottle['count'], $userid, time(), $ip]);
} else {
$psdb->query("INSERT INTO `{$psdb->prefix}loginthrottle` (ip, count, lastuserid, `time`) VALUES (?, 1, ?, ?)", [$ip, $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`=?, `password`=NULL, `nonce`=NULL WHERE `userid`=?",
[$hash, $userid]
);
}
}
return true;
}
function login(string $name, string $pass, $timeout = false, $debug = false) {
global $psdb, $curuser, $psconfig;
$ctime = time();
$this->logout();
$userid = $this->userid($name);
$res = $psdb->query("SELECT * FROM `{$psdb->prefix}users` WHERE `userid` = ? LIMIT 1", [$userid]);
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 two weeks
$timeout = 2*7*24*60*60;
}
$timeout += $ctime;
$this->sid = $this->mksid($this->sid);
$nsidhash = $this->sidHash($this->sid);
$res = $psdb->query(
"INSERT INTO `{$psdb->prefix}sessions` (`userid`,`sid`,`time`,`timeout`,`ip`) VALUES (?,?,?,?,?)",
[$user['userid'], $nsidhash, $ctime, $timeout, $this->getIp()]
);
if (!$res) die;
$this->session = $psdb->insert_id();
$this->scookie = $name . ',' . $this->session . ',' . $this->sid;
$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->scookie, ['expires' => time() + (363)*24*60*60, 'path' => '/', 'domain' => $psconfig['routes']['root'], 'secure' => true, 'httponly' => true, 'samesite' => 'None']);
$encodedcookie = rawurlencode($this->scookie);
header("Set-Cookie: sid=$encodedcookie; Max-Age=31363200; Domain={$psconfig['routes']['root']}; Path=/; Secure; SameSite=None");
return $curuser;
}
function updateCookie() {
global $psconfig;
if (!$this->sid) {
$this->sid = $this->mksid($this->sid);
$this->scookie = ',,' . $this->sid;
// setcookie('sid', $this->scookie, ['expires' => time() + (363)*24*60*60, 'path' => '/', 'domain' => $psconfig['routes']['root'], 'secure' => true, 'httponly' => true, 'samesite' => 'None']);
$encodedcookie = rawurlencode($this->scookie);
header("Set-Cookie: sid=$encodedcookie; Max-Age=31363200; Domain={$psconfig['routes']['root']}; Path=/; Secure; SameSite=None");
}
}
function killCookie() {
global $psconfig;
if ($this->sid) {
$this->scookie = ',,' . $this->sid;
// setcookie('sid', $this->scookie, ['expires' => time() + (363)*24*60*60, 'path' => '/', 'domain' => $psconfig['routes']['root'], 'secure' => true, 'httponly' => true, 'samesite' => 'None']);
$encodedcookie = rawurlencode($this->scookie);
header("Set-Cookie: sid=$encodedcookie; Max-Age=31363200; Domain={$psconfig['routes']['root']}; Path=/; Secure; SameSite=None");
} else {
// setcookie('sid', '', ['expires' => time() - 60*60*24*2, 'path' => '/', 'domain' => $psconfig['routes']['root'], 'secure' => true, 'httponly' => true, 'samesite' => 'None']);
header("Set-Cookie: sid=; Max-Age=0; Domain={$psconfig['routes']['root']}; Path=/; Secure; SameSite=None");
}
}
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` = ? LIMIT 1", [$this->session]);
$this->sid = '';
$this->scookie = '';
$this->session = 0;
$this->killCookie();
return $curuser;
}
function createPasswordResetToken(string $name, $timeout=false) {
global $psdb, $curuser;
$ctime = time();
$userid = $this->userid($name);
$res = $psdb->query("SELECT * FROM `{$psdb->prefix}users` WHERE `userid` = ? LIMIT 1", [$userid]);
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 (?, ?, ?, ?, ?)",
[$user['userid'], $curuser['userid'], time(), $this->getIp(), $modlogentry]
);
// magical character string...
$token = bin2hex(random_bytes(15));
$res = $psdb->query(
"INSERT INTO `{$psdb->prefix}sessions` (`userid`,`sid`,`time`,`timeout`,`ip`) VALUES (?, ?, ?, ?, ?)",
[$user['userid'], $token, $ctime, $timeout, $this->getIp()]
);
if (!$res) die($psdb->error());
}
return $token;
}
function validatePasswordResetToken(string $token) {
global $psdb, $psconfig;
if (strlen($token) !== ($psconfig['sid_length'] * 2)) return false;
$res = $psdb->query("SELECT * FROM `{$psdb->prefix}sessions` WHERE `sid` = ? LIMIT 1", [$token]);
$session = $psdb->fetch_assoc($res);
if (!$session) return false;
if (intval($session['timeout'])