Fixed resource packs/login sequence fail, added basic safety restrictions for packet sending before clients are logged in

close #452
This commit is contained in:
Dylan K. Taylor 2017-03-26 14:40:59 +01:00
parent 01440fb659
commit 788bd6fc20
9 changed files with 71 additions and 25 deletions

View File

@ -1063,6 +1063,11 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade
return false; return false;
} }
//Basic safety restriction. TODO: improve this
if(!$this->loggedIn and !$packet->canBeSentBeforeLogin()){
throw new \InvalidArgumentException("Attempted to send " . get_class($packet) . " to " . $this->getName() . " too early");
}
$timings = Timings::getSendDataPacketTimings($packet); $timings = Timings::getSendDataPacketTimings($packet);
$timings->startTiming(); $timings->startTiming();
@ -1096,6 +1101,11 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade
return false; return false;
} }
//Basic safety restriction. TODO: improve this
if(!$this->loggedIn and !$packet->canBeSentBeforeLogin()){
throw new \InvalidArgumentException("Attempted to send " . get_class($packet) . " to " . $this->getName() . " too early");
}
$timings = Timings::getSendDataPacketTimings($packet); $timings = Timings::getSendDataPacketTimings($packet);
$timings->startTiming(); $timings->startTiming();
$this->server->getPluginManager()->callEvent($ev = new DataPacketSendEvent($this, $packet)); $this->server->getPluginManager()->callEvent($ev = new DataPacketSendEvent($this, $packet));
@ -1734,38 +1744,40 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade
if($p !== $this and strtolower($p->getName()) === strtolower($this->getName())){ if($p !== $this and strtolower($p->getName()) === strtolower($this->getName())){
if($p->kick("logged in from another location") === false){ if($p->kick("logged in from another location") === false){
$this->close($this->getLeaveMessage(), "Logged in from another location"); $this->close($this->getLeaveMessage(), "Logged in from another location");
return; return;
} }
}elseif($p->loggedIn and $this->getUniqueId()->equals($p->getUniqueId())){ }elseif($p->loggedIn and $this->getUniqueId()->equals($p->getUniqueId())){
if($p->kick("logged in from another location") === false){ if($p->kick("logged in from another location") === false){
$this->close($this->getLeaveMessage(), "Logged in from another location"); $this->close($this->getLeaveMessage(), "Logged in from another location");
return; return;
} }
} }
} }
$nbt = $this->server->getOfflinePlayerData($this->username); $this->namedtag = $this->server->getOfflinePlayerData($this->username);
$this->playedBefore = ($nbt["lastPlayed"] - $nbt["firstPlayed"]) > 1; // microtime(true) - microtime(true) may have less than one millisecond difference $this->playedBefore = ($this->namedtag["lastPlayed"] - $this->namedtag["firstPlayed"]) > 1; // microtime(true) - microtime(true) may have less than one millisecond difference
if(!isset($nbt->NameTag)){ if(!isset($this->namedtag->NameTag)){
$nbt->NameTag = new StringTag("NameTag", $this->username); $this->namedtag->NameTag = new StringTag("NameTag", $this->username);
}else{ }else{
$nbt["NameTag"] = $this->username; $this->namedtag["NameTag"] = $this->username;
} }
$this->gamemode = $nbt["playerGameType"] & 0x03; $this->gamemode = $this->namedtag["playerGameType"] & 0x03;
if($this->server->getForceGamemode()){ if($this->server->getForceGamemode()){
$this->gamemode = $this->server->getGamemode(); $this->gamemode = $this->server->getGamemode();
$nbt->playerGameType = new IntTag("playerGameType", $this->gamemode); $this->namedtag->playerGameType = new IntTag("playerGameType", $this->gamemode);
} }
$this->allowFlight = (bool) ($this->gamemode & 0x01); $this->allowFlight = (bool) ($this->gamemode & 0x01);
if(($level = $this->server->getLevelByName($nbt["Level"])) === null){ if(($level = $this->server->getLevelByName($this->namedtag["Level"])) === null){
$this->setLevel($this->server->getDefaultLevel()); $this->setLevel($this->server->getDefaultLevel());
$nbt["Level"] = $this->level->getName(); $this->namedtag["Level"] = $this->level->getName();
$nbt["Pos"][0] = $this->level->getSpawnLocation()->x; $this->namedtag["Pos"][0] = $this->level->getSpawnLocation()->x;
$nbt["Pos"][1] = $this->level->getSpawnLocation()->y; $this->namedtag["Pos"][1] = $this->level->getSpawnLocation()->y;
$nbt["Pos"][2] = $this->level->getSpawnLocation()->z; $this->namedtag["Pos"][2] = $this->level->getSpawnLocation()->z;
}else{ }else{
$this->setLevel($level); $this->setLevel($level);
} }
@ -1773,19 +1785,29 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade
$this->achievements = []; $this->achievements = [];
/** @var ByteTag $achievement */ /** @var ByteTag $achievement */
foreach($nbt->Achievements as $achievement){ foreach($this->namedtag->Achievements as $achievement){
$this->achievements[$achievement->getName()] = $achievement->getValue() > 0 ? true : false; $this->achievements[$achievement->getName()] = $achievement->getValue() > 0 ? true : false;
} }
$nbt->lastPlayed = new LongTag("lastPlayed", floor(microtime(true) * 1000)); $this->namedtag->lastPlayed = new LongTag("lastPlayed", floor(microtime(true) * 1000));
if($this->server->getAutoSave()){ if($this->server->getAutoSave()){
$this->server->saveOfflinePlayerData($this->username, $nbt, true); $this->server->saveOfflinePlayerData($this->username, $this->namedtag, true);
} }
parent::__construct($this->level, $nbt); $this->sendPlayStatus(PlayStatusPacket::LOGIN_SUCCESS);
$this->loggedIn = true; $this->loggedIn = true;
$this->server->addOnlinePlayer($this); $this->server->addOnlinePlayer($this);
$pk = new ResourcePacksInfoPacket();
$manager = $this->server->getResourceManager();
$pk->resourcePackEntries = $manager->getResourceStack();
$pk->mustAccept = $manager->resourcePacksRequired();
$this->dataPacket($pk);
}
protected function completeLoginSequence(){
parent::__construct($this->level, $this->namedtag);
$this->server->getPluginManager()->callEvent($ev = new PlayerLoginEvent($this, "Plugin reason")); $this->server->getPluginManager()->callEvent($ev = new PlayerLoginEvent($this, "Plugin reason"));
if($ev->isCancelled()){ if($ev->isCancelled()){
$this->close($this->getLeaveMessage(), $ev->getKickMessage()); $this->close($this->getLeaveMessage(), $ev->getKickMessage());
@ -1910,13 +1932,7 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade
//TODO: add JWT verification, add encryption //TODO: add JWT verification, add encryption
$this->sendPlayStatus(PlayStatusPacket::LOGIN_SUCCESS); $this->processLogin();
$pk = new ResourcePacksInfoPacket();
$manager = $this->server->getResourceManager();
$pk->resourcePackEntries = $manager->getResourceStack();
$pk->mustAccept = $manager->resourcePacksRequired();
$this->dataPacket($pk);
return true; return true;
} }
@ -1989,7 +2005,7 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade
$this->dataPacket($pk); $this->dataPacket($pk);
break; break;
case ResourcePackClientResponsePacket::STATUS_COMPLETED: case ResourcePackClientResponsePacket::STATUS_COMPLETED:
$this->processLogin(); $this->completeLoginSequence();
break; break;
} }

View File

@ -2207,7 +2207,9 @@ class Server{
private function checkTickUpdates($currentTick, $tickTime){ private function checkTickUpdates($currentTick, $tickTime){
foreach($this->players as $p){ foreach($this->players as $p){
if($this->alwaysTickPlayers){ if(!$p->loggedIn and ($tickTime - $p->creationTime) >= 10){
$p->close("", "Login timeout");
}elseif($this->alwaysTickPlayers){
$p->onUpdate($currentTick); $p->onUpdate($currentTick);
} }
} }

View File

@ -35,6 +35,10 @@ class BatchPacket extends DataPacket{
return false; return false;
} }
public function canBeSentBeforeLogin() : bool{
return true;
}
public function decode(){ public function decode(){
$this->payload = $this->getString(); $this->payload = $this->getString();
} }

View File

@ -29,6 +29,10 @@ use pocketmine\network\mcpe\NetworkSession;
class ClientToServerHandshakePacket extends DataPacket{ class ClientToServerHandshakePacket extends DataPacket{
const NETWORK_ID = ProtocolInfo::CLIENT_TO_SERVER_HANDSHAKE_PACKET; const NETWORK_ID = ProtocolInfo::CLIENT_TO_SERVER_HANDSHAKE_PACKET;
public function canBeSentBeforeLogin() : bool{
return true;
}
public function decode(){ public function decode(){
//No payload //No payload
} }

View File

@ -44,6 +44,10 @@ abstract class DataPacket extends BinaryStream{
return true; return true;
} }
public function canBeSentBeforeLogin() : bool{
return false;
}
abstract public function encode(); abstract public function encode();
abstract public function decode(); abstract public function decode();

View File

@ -32,6 +32,10 @@ class DisconnectPacket extends DataPacket{
public $hideDisconnectionScreen = false; public $hideDisconnectionScreen = false;
public $message; public $message;
public function canBeSentBeforeLogin() : bool{
return true;
}
public function decode(){ public function decode(){
$this->hideDisconnectionScreen = $this->getBool(); $this->hideDisconnectionScreen = $this->getBool();
$this->message = $this->getString(); $this->message = $this->getString();

View File

@ -48,6 +48,10 @@ class LoginPacket extends DataPacket{
return false; return false;
} }
public function canBeSentBeforeLogin() : bool{
return true;
}
public function decode(){ public function decode(){
$this->protocol = $this->getInt(); $this->protocol = $this->getInt();

View File

@ -42,6 +42,10 @@ class PlayStatusPacket extends DataPacket{
} }
public function canBeSentBeforeLogin() : bool{
return true;
}
public function encode(){ public function encode(){
$this->reset(); $this->reset();
$this->putInt($this->status); $this->putInt($this->status);

View File

@ -32,6 +32,10 @@ class ServerToClientHandshakePacket extends DataPacket{
public $publicKey; public $publicKey;
public $serverToken; public $serverToken;
public function canBeSentBeforeLogin() : bool{
return true;
}
public function decode(){ public function decode(){
$this->publicKey = $this->getString(); $this->publicKey = $this->getString();
$this->serverToken = $this->getString(); $this->serverToken = $this->getString();