PocketMine-MP 5.15.1 git-fb9a74e8799c71ed8292cfa53abe7a4c9204629d
QueryHandler.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20 */
21
22declare(strict_types=1);
23
29
36use function chr;
37use function hash;
38use function random_bytes;
39use function strlen;
40use function substr;
41
43 private string $lastToken;
44 private string $token;
45
46 private \Logger $logger;
47
48 public const HANDSHAKE = 9;
49 public const STATISTICS = 0;
50
51 public function __construct(
52 private Server $server
53 ){
54 $this->logger = new \PrefixedLogger($this->server->getLogger(), "Query Handler");
55
56 /*
57 The Query protocol is built on top of the existing Minecraft PE UDP network stack.
58 Because the 0xFE packet does not exist in the MCPE protocol,
59 we can identify Query packets and remove them from the packet queue.
60
61 Then, the Query class handles itself sending the packets in raw form, because
62 packets can conflict with the MCPE ones.
63 */
64
65 $this->token = $this->generateToken();
66 $this->lastToken = $this->token;
67 }
68
69 public function getPattern() : string{
70 return '/^\xfe\xfd.+$/s';
71 }
72
73 private function generateToken() : string{
74 return random_bytes(16);
75 }
76
77 public function regenerateToken() : void{
78 $this->lastToken = $this->token;
79 $this->token = $this->generateToken();
80 }
81
82 public static function getTokenString(string $token, string $salt) : int{
83 return Binary::readInt(substr(hash("sha512", $salt . ":" . $token, true), 7, 4));
84 }
85
86 public function handle(AdvancedNetworkInterface $interface, string $address, int $port, string $packet) : bool{
87 try{
88 $stream = new BinaryStream($packet);
89 $header = $stream->get(2);
90 if($header !== "\xfe\xfd"){ //TODO: have this filtered by the regex filter we installed above
91 return false;
92 }
93 $packetType = $stream->getByte();
94 $sessionID = $stream->getInt();
95
96 switch($packetType){
97 case self::HANDSHAKE: //Handshake
98 $reply = chr(self::HANDSHAKE);
99 $reply .= Binary::writeInt($sessionID);
100 $reply .= self::getTokenString($this->token, $address) . "\x00";
101
102 $interface->sendRawPacket($address, $port, $reply);
103
104 return true;
105 case self::STATISTICS: //Stat
106 $token = $stream->getInt();
107 if($token !== ($t1 = self::getTokenString($this->token, $address)) && $token !== ($t2 = self::getTokenString($this->lastToken, $address))){
108 $this->logger->debug("Bad token $token from $address $port, expected $t1 or $t2");
109
110 return true;
111 }
112 $reply = chr(self::STATISTICS);
113 $reply .= Binary::writeInt($sessionID);
114
115 $remaining = $stream->getRemaining();
116 if(strlen($remaining) === 4){ //TODO: check this! according to the spec, this should always be here and always be FF FF FF 01
117 $reply .= $this->server->getQueryInformation()->getLongQuery();
118 }else{
119 $reply .= $this->server->getQueryInformation()->getShortQuery();
120 }
121 $interface->sendRawPacket($address, $port, $reply);
122
123 return true;
124 default:
125 return false;
126 }
127 }catch(BinaryDataException $e){
128 $this->logger->debug("Bad packet from $address $port: " . $e->getMessage());
129 return false;
130 }
131 }
132}
handle(AdvancedNetworkInterface $interface, string $address, int $port, string $packet)
sendRawPacket(string $address, int $port, string $payload)