/** * Called whenever there is data available on the stream * @param $data String the raw data */ public function onData($data) { //if we're in encryption stage decrypt it first if ($this->secret !== null) { mcrypt_generic_init($this->encryptor, $this->secret, $this->secret); $data = mdecrypt_generic($this->encryptor, $data); mcrypt_generic_deinit($this->encryptor); } //attempt to parse the data as a packet try { //add the data to the current buffer $this->packetBuffer .= $data; //for as long as we have data left in the buffer do { //read the packet length from the stream $packetLengthVarInt = VarInt::readUnsignedVarInt($this->packetBuffer); //not enough data to read the packet length, wait for more data if ($packetLengthVarInt === false) { return; } //total length of the packet is the length of the varint + it's value $totalLength = $packetLengthVarInt->getDataLength() + $packetLengthVarInt->getValue(); //if we don't have enough data to read the entire packet wait for more data to enter $bufferLength = strlen($this->packetBuffer); if ($bufferLength < $totalLength) { return; } //remove the packet length varint from the buffer $this->packetBuffer = substr($this->packetBuffer, $packetLengthVarInt->getDataLength()); //read the packet ID from the buffer $packetDataWithPacketID = substr($this->packetBuffer, 0, $packetLengthVarInt->getValue()); //remove the rest of the packet from the buffer $this->packetBuffer = substr($this->packetBuffer, $packetLengthVarInt->getValue()); //read the packet ID $packetID = VarInt::readUnsignedVarInt($packetDataWithPacketID); //get the raw packet data $packetData = substr($packetDataWithPacketID, $packetID->getDataLength()); $this->lastPacket = $this->currentTime(); //trigger packet processing $this->processPacket($packetID->getValue(), $packetData); //if we have buffer left run again } while (strlen($this->packetBuffer) > 0); //if any exceptions are thrown (error parsing the packets e.t.c.) send a disconnect packet } catch (Exception $ex) { echo "EXCEPTION IN PACKET PARSING {$ex->getMessage()}\n"; echo $ex->getTraceAsString(); $dis = new DisconnectPacket(); $dis->setReason('Internal Server Error: ' . $ex->getMessage()); $this->end($dis->encodePacket()); } }
/** * Called on event LOGIN.EncryptionResponsePacket * @param EncryptionResponsePacket $packet */ public function onEncryptionResponsePacket(EncryptionResponsePacket $packet) { //if we don't have a verifiyToken sent yet then the packet has been sent without us sending a request, disconnect client if (null === $this->verifyToken) { $this->disconnectClient((new DisconnectPacket())->setReason('Packet received out of order')); return; } //get the verify token from the packet it and decrypt it using our key $verifyToken = $this->certificate->getPrivateKey()->decrypt($packet->getToken()); //if it isn't the same as our token then encryption failed, disconnect the client if ($verifyToken != $this->verifyToken) { $this->disconnectClient((new DisconnectPacket())->setReason('Invalid validation token')); return; } //decrypt the shared secret from the packet and decrypt it using our key $secret = $this->certificate->getPrivateKey()->decrypt($packet->getSecret()); //enable encryption using the client generated secret $this->enableAES($secret); //generate a login hash the same as the client for the session server request $publicKey = $this->certificate->getPublicKey()->getPublicKey(); $publicKey = base64_decode(substr($publicKey, 28, -26)); //cut out the start and end lines of the key $loginHash = $this->serverID . $secret . $publicKey; $loginHash = self::sha1($loginHash); //create a new Yggdrasil for checking against Mojang $yggdrasil = new DefaultYggdrasil(); try { //ask Mojang if the user has sent a join request, throws exception on failure $response = $yggdrasil->hasJoined($this->username, $loginHash); //get and set the clients UUID from the response $this->uuid = $response->getUuid(); //trigger the login success with the disconnect packet $disconnect = new DisconnectPacket(); $this->emit('login_success', [$this, $disconnect]); //if no reason was set after the event then set a default if ($disconnect->getReasonJSON() === null) { $disconnect->setReason("No kick reason supplied"); } //disconnect the client $this->disconnectClient($disconnect); } catch (Exception $ex) { echo "{$this->username} failed authentication with Mojang session servers\n"; //exception occured checking against Mojang $this->disconnectClient((new DisconnectPacket())->setReason("Error Authenticating with Mojang servers")); } }