pokemon-showdown-client/action.php
Cathy J. Fitzpatrick 1208dee10f Verify that source IP is valid for server requests
Currently, server authentication for updating the ladder and for
uploading replays is done by comparing the hash of the token provided
by the server to the hash on record. This commit adds a second layer
of authentication by also verifying that the request actually
originates from the Pokemon Showdown server in question.

For now, I have also maintained the server token check as a form of
two-factor authentication.
2013-01-31 11:31:35 -07:00

301 lines
8.8 KiB
PHP

<?php
/*
License: GPLv2 or later
<http://www.gnu.org/licenses/gpl-2.0.html>
*/
error_reporting(E_ALL);
include_once '../pokemonshowdown.com/lib/ntbb-session.lib.php';
//include_once 'lib/ntbb-ladder.lib.php';
include_once '../pokemonshowdown.com/config/servers.inc.php';
$reqs = array($_REQUEST);
$multiReqs = false;
if (@$_REQUEST['json']) {
$reqs = json_decode($_REQUEST['json'], true);
$multiReqs = true;
}
$outArray = array();
foreach ($reqs as $reqData) {
$reqData = array_merge($_REQUEST, $reqData);
if (!ctype_alnum(@$reqData['act'])) die('{"error":"invalid action"}');
$out = array(
'action' => @$reqData['act']
);
// if ($_REQUEST['debug']) {
// var_export($reqData);
// }
switch (@$reqData['act'])
{
case 'login':
if (!$_POST) die();
$users->login($reqData['name'], $reqData['pass']);
unset($curuser['userdata']);
$out['curuser'] = $curuser;
$out['actionsuccess'] = !!$curuser;
if (empty($reqData['servertoken'])) {
die('Bogus request.');
}
if ($curuser && $reqData['servertoken'])
{
$out['sessiontoken'] = $users->getSessionToken($reqData['servertoken']) . '::' . $reqData['servertoken'];
}
$out['assertion'] = $users->getAssertion($curuser['userid'], $reqData['servertoken']);
break;
case 'register':
if (empty($reqData['servertoken'])) {
die('Bogus request.');
}
$user = array();
$user['username'] = @$_POST['username'];
if (strlen($users->userid($user['username'])) < 1)
{
$out['actionerror'] = 'Your username must contain at least one letter or number.';
}
else if (strlen($user['username']) > 64)
{
$out['actionerror'] = 'Your username must be less than 64 characters long.';
}
else if (strlen(@$_POST['password']) < 5)
{
$out['actionerror'] = 'Your password must be at least 5 characters long.';
}
else if (@$_POST['password'] !== @$_POST['cpassword'])
{
$out['actionerror'] = 'Your passwords do not match.';
}
else if (trim(strtolower(@$_POST['captcha'])) !== 'pikachu')
{
$out['actionerror'] = 'Please answer the anti-spam question given.';
}
else if ($user = $users->addUser($user, $_POST['password']))
{
$out['curuser'] = $user;
$out['assertion'] = $users->getAssertion($user['userid'],
$reqData['servertoken'], $user);
$out['actionsuccess'] = true;
if ($curuser && @$reqData['servertoken'])
{
$out['sessiontoken'] = $users->getSessionToken($reqData['servertoken']) . '::' . $reqData['servertoken'];
}
}
else
{
$out['actionerror'] = 'Your username is already taken.';
}
break;
case 'upkeep':
unset($curuser['userdata']);
$out['curuser'] = $curuser;
if (empty($reqData['name'])) $userid = $curuser['userid'];
else $userid = $users->userid($reqData['name']);
if (empty($reqData['servertoken'])) {
die('Bogus request.'); // Will not happen with official client.
}
$out['assertion'] = $users->getAssertion($userid, $reqData['servertoken']);
break;
case 'checklogin':
// direct
header('Content-type: text/plain');
die(!!$users->getUser($reqData['userid']));
break;
case 'logout':
if (!$_POST) die();
$users->logout();
$out['curuser'] = $curuser;
$out['actionsuccess'] = true;
break;
case 'savedata':
if (!$_POST) die();
$userdata = $curuser['userdata'];
$userdata['psclient'] = $reqData['userdata'];
$out['actionsuccess'] = $users->modifyUser($curuser, array(
'userdata' => $userdata
));
break;
case 'getsessiontoken':
// direct
header('Content-type: text/plain');
die($users->getSessionToken($reqData['servertoken']));
break;
case 'getassertion':
// direct
if (empty($reqData['servertoken']) || empty($reqData['userid'])) {
die('servertoken and userid required');
}
header('Content-type: text/plain');
$userid = $users->userid($reqData['userid']);
$servertoken = htmlspecialchars($reqData['servertoken']);
die($users->getAssertion($userid, $servertoken));
break;
case 'verifysessiontoken':
// direct
if (!$users->getUser(@$reqData['userid']))
{
// ok
die('1');
}
$user = $users->verifySessionToken(@$reqData['token'], @$reqData['servertoken']);
if (!$user) die('');
if ($reqData['userid'] !== $user['userid']) die('');
$serveruserdata = array(
'userid' => $user['userid'],
'username' => $user['username'],
'group' => $user['group']
// 'userserverdata' => @$user['userdata']['psserver'][@$reqData['servertoken']]
);
header('Content-type: application/json');
die(json_encode($serveruserdata));
break;
case 'ladderupdate':
include_once 'lib/ntbb-ladder.lib.php';
$server = @$PokemonServers[@$reqData['serverid']];
//var_export($users->getUserData($reqData['p1']));
if (!$server ||
($_SERVER['REMOTE_ADDR'] !== gethostbyname($server['server'])) ||
($server['token'] !== md5($reqData['servertoken']))) {
$out = 0;
break;
}
$ladder = new NTBBLadder($server['id'], $reqData['format']);
$p1 = $users->getUserData($reqData['p1']);
$p2 = $users->getUserData($reqData['p2']);
$ladder->updateRating($p1, $p2, floatval($reqData['score']));
$out['actionsuccess'] = true;
$out['p1rating'] = $p1['rating'];
$out['p2rating'] = $p2['rating'];
unset($out['p1rating']['rpdata']);
unset($out['p2rating']['rpdata']);
break;
case 'prepreplay':
include_once 'lib/ntbb-ladder.lib.php';
$server = @$PokemonServers[@$reqData['serverid']];
//var_export($users->getUserData($reqData['p1']));
if (!$server ||
($_SERVER['REMOTE_ADDR'] !== gethostbyname($server['server'])) ||
($server['token'] !== md5($reqData['servertoken']))) {
$out = 0;
break;
}
if (@$reqData['serverid'] !== 'showdown') break; // let's not think about other servers yet
$res = $db->query("SELECT * FROM `ntbb_replays` WHERE `id`='".$db->escape($reqData['id'])."','".$db->escape($reqData['loghash'])."'");
$replay = $db->fetch_assoc($res);
if ($replay && !$replay['loghash']) {
$out = !!$db->query("UPDATE `ntbb_replays` SET `loghash` = '".$db->escape($reqData['loghash'])."' WHERE `id`='".$db->escape($reqData['id'])."','".$db->escape($reqData['loghash'])."'");
} else {
$out = !!$db->query("INSERT INTO `ntbb_replays` (`id`,`loghash`,`p1`,`p2`,`format`,`date`) VALUES ('".$db->escape($reqData['id'])."','".$db->escape($reqData['loghash'])."','".$db->escape($reqData['p1'])."','".$db->escape($reqData['p2'])."','".$db->escape($reqData['format'])."',".time().")");
}
break;
case 'uploadreplay':
function stripNonAscii($str) { return preg_replace('/[^(\x20-\x7F)]+/','', $str); }
if (!$_POST['id']) die('ID needed');
$id = $_POST['id'];
$res = $db->query("SELECT * FROM `ntbb_replays` WHERE `id` = '".$db->escape($id)."'");
$replay = $db->fetch_assoc($res);
if (!$replay) die('not found');
if (md5(stripNonAscii($_POST['log'])) !== $replay['loghash']) {
$_POST['log'] = str_replace("\r",'', $_POST['log']);
if (md5(stripNonAscii($_POST['log'])) !== $replay['loghash']) {
die('hash mismatch');
}
}
$db->query("UPDATE `ntbb_replays` SET `log` = '".$db->escape($_POST['log'])."', `loghash` = '' WHERE `id` = '".$db->escape($id)."'");
die('success');
break;
case 'ladderget':
include_once 'lib/ntbb-ladder.lib.php';
$server = $PokemonServers[$reqData['serverid']];
if (!$server) die('');
$ladder = new NTBBLadder($server['id'], @$reqData['format']);
$user = $users->getUserData($reqData['user']);
$ladder->getAllRatings($user);
header('Content-type: application/json');
die(json_encode($user['ratings']));
break;
case 'ladderformatget':
include_once 'lib/ntbb-ladder.lib.php';
$server = $PokemonServers[$reqData['serverid']];
if (!$server) die('');
$ladder = new NTBBLadder($server['id'], @$reqData['format']);
$user = $users->getUserData($reqData['user']);
$ladder->getRating($user);
if (!@$user['rating']) die();
unset($user['rating']['serverid']);
unset($user['rating']['formatid']);
unset($user['rating']['rpdata']);
header('Content-type: application/json');
die(json_encode($user['rating']));
break;
case 'ladderformatgetmmr':
case 'mmr':
include_once 'lib/ntbb-ladder.lib.php';
$server = $PokemonServers[$reqData['serverid']];
if (!$server) die('');
$ladder = new NTBBLadder($server['id'], @$reqData['format']);
$user = $users->getUserData($reqData['user']);
$ladder->getRating($user);
if (!@$user['rating']) {
$out = 1500;
} else {
$out = ($user['rating']['r']+$user['rating']['rpr'])/2;
}
break;
case 'laddertop':
include_once 'lib/ntbb-ladder.lib.php';
$server = $PokemonServers[$reqData['serverid']];
if (!$server)
{
die('');
}
$ladder = new NTBBLadder($server['id'], $reqData['format']);
$out = $ladder->getTop();
break;
default:
break;
}
if ($multiReqs) $outArray[] = $out;
}
// json output
if ($multiReqs) {
header('Content-type: application/json');
die(json_encode($outArray));
} else {
header('Content-type: application/json');
die(json_encode($out));
}