* @copyright 2007 - 2008 (C) by Wrzasq * @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU Lesser General Public License, Version 3 */ /** * OTAdmin protocol client. * * @package POT * @version 0.1.4 * @property-read bool $requiresLogin {@link OTS_Admin::requiresLogin() requiresLogin()} wrapper. * @property-read bool $requiresEncryption {@link OTS_Admin::requiresEncryption() requiresEncryption()} wrapper. * @property-read bool $usesRSA1024XTEA {@link OTS_Admin::usesRSA1024XTEA() usesRSA1024XTEA()} wrapper. * @property-read int $ping Ping time. * @property-write string $login Logs in with given password. * @property-write string $broadcast Sends given broadcast message. * @property-write string $kick Kicks player with given name from server. * @tutorial POT/OTAdmin.pkg * @example examples/admin.php admin.php */ class OTS_Admin { /** * User login. */ const REQUEST_LOGIN = 1; /** * Encryption packet. */ const REQUEST_ENCRYPTION = 2; /** * RSA key exchange. */ const REQUEST_KEY_EXCHANGE = 3; /** * OTAdmin commnd. */ const REQUEST_COMMAND = 4; /** * Ping. */ const REQUEST_PING = 5; /** * Hello respond. */ const RESPOND_HELLO = 1; /** * Keys exchange success. */ const RESPOND_KEY_EXCHANGE_OK = 2; /** * Keys exchange failed. */ const RESPOND_KEY_EXCHANGE_FAILED = 3; /** * Login success. */ const RESPOND_LOGIN_OK = 4; /** * Login incorrect. */ const RESPOND_LOGIN_FAILED = 5; /** * Command success. */ const RESPOND_COMMAND_OK = 6; /** * Command failed. */ const RESPOND_COMMAND_FAILED = 7; /** * Encryption initialization success. */ const RESPOND_ENCRYPTION_OK = 8; /** * Encryption initialization failed. */ const RESPOND_ENCRYPTION_FAILED = 9; /** * Ping success. */ const RESPOND_PING_OK = 10; /** * Message. */ const RESPOND_MESSAGE = 11; /** * Error. */ const RESPOND_ERROR = 12; /** * Broadcast message. */ const COMMAND_BROADCAST = 1; /** * Closes server. */ const COMMAND_CLOSE_SERVER = 2; /** * Pays all rented shouses. */ const COMMAND_PAY_HOUSES = 3; /** * Not supported in current OTAdmin imlpementation. */ const COMMAND_OPEN_SERVER = 4; /** * Shutdowns the server. */ const COMMAND_SHUTDOWN_SERVER = 5; /** * Not supported in current OTAdmin imlpementation. */ const COMMAND_RELOAD_SCRIPTS = 6; /** * Not supported in current OTAdmin imlpementation. */ const COMMAND_PLAYER_INFO = 7; /** * Not supported in current OTAdmin imlpementation. */ const COMMAND_GETONLINE = 8; /** * Not supported in current OTAdmin imlpementation. */ const COMMAND_KICK = 9; /** * Not supported in current OTAdmin imlpementation. */ const COMMAND_BAN_MANAGER = 10; /** * Not supported in current OTAdmin imlpementation. */ const COMMAND_SERVER_INFO = 11; /** * Not supported in current OTAdmin imlpementation. */ const COMMAND_GETHOUSE = 12; /** * Server requires login. */ const REQUIRE_LOGIN = 1; /** * Server requires encryption. */ const REQUIRE_ENCRYPTION = 2; /** * Server uses XTEA encryption, XTEA key is being sent in 1024bit RSA encrypted packet. */ const ENCRYPTION_RSA1024XTEA = 1; /** * Socket handle. * * @var resource */ private $socket; /** * Server security policy. * * @var int */ private $policy; /** * Protocol options. * * @var int */ private $options; /** * Packets cipher. * * @var IOTS_Cipher */ private $cipher; /** * Host address for session sleep/wakeup. * * @var string */ private $host; /** * Port number for session sleep/wakeup. * * @var int */ private $port; /** * Creates new connection to OTServ administration backend. * *

* This method automaticly handles RSA and XTEA encryption if such method is required by server including keys negotiations. *

* *

* After connecting you should check {@link OTS_Admin::requiresLogin() if server requires login}. *

* * @param string $host Target server. * @param int $port Port (7171 by default). * @throws E_OTS_ErrorCode When receive failed respond or unexpected message. * @throws E_OTS_OutOfBuffer When there is read attemp after end of packet stream. */ public function __construct($host, $port = 7171) { // saves connection sessint $this->host = $host; $this->port = $port; // opens connection $this->socket = fsockopen($this->host, $this->port); // 254 = OTAdmin protocol identifier $message = new OTS_Buffer( chr(254) ); // sends initial packet $respond = $this->send($message); $message->reset(); $byte = $respond->getChar(); // checks if it's HELLO packet if($byte != self::RESPOND_HELLO) { throw new E_OTS_ErrorCode($byte); } // skips respond signature and protocol version $respond->getLong(); $respond->getString(); // saves protocol settings $this->policy = $respond->getShort(); $this->options = $respond->getLong(); // handles encryption initialisation if required if( $this->requiresEncryption() && $this->usesRSA1024XTEA() ) { // requests public key $message->putChar(self::REQUEST_KEY_EXCHANGE); $message->putChar(self::ENCRYPTION_RSA1024XTEA); $respond = $this->send($message); $message->reset(); $byte = $respond->getChar(); // checks if it succeded if($byte != self::RESPOND_KEY_EXCHANGE_OK) { throw new E_OTS_ErrorCode($byte, $respond->getString() ); } // we support only RSA 1024bit encryption for XTEA key sending if( $respond->getChar() != self::ENCRYPTION_RSA1024XTEA) { throw new E_OTS_ErrorCode($byte); } // reads binary form of public key (128 bytes) $key = OTS_BinaryTools::bin2Int( strrev( $respond->getString(128) ) ); // creates RSA cipher // as we have ready N computer here and we don't compute it by ourselves we can use a little hack, to save N as P * Q we will use P = N and Q = 1 $rsa = new OTS_RSA($key, '1'); $key = ''; // generates random XTEA key for($i = 0; $i < 4; $i++) { $key .= pack('L', rand(0, 4294967295) ); } $xtea = new OTS_XTEA($key); // sends XTEA key $message->putChar(self::REQUEST_ENCRYPTION); $message->putChar(self::ENCRYPTION_RSA1024XTEA); $message->putString( $rsa->encrypt( chr(0) . $key), false); // we can't bind cipher yet since only respnd will be XTEA encrypted $respond = new OTS_Buffer( $xtea->decrypt( $this->send($message)->getBuffer() ) ); $byte = $respond->getChar(); // checks if encryption negotation succeeded if($byte != self::RESPOND_ENCRYPTION_OK) { throw new E_OTS_ErrorCode($byte, $respond->getString()); } // saves encryption/decryption cipher $this->cipher = $xtea; } } /** * Checks if protocol requires login. * * @return bool True if protocol requires user login. */ public function requiresLogin() { return ($this->policy & self::REQUIRE_LOGIN) == self::REQUIRE_LOGIN; } /** * Checks if protocol requires encryption. * * @return bool True if protocol requires encryption. */ public function requiresEncryption() { return ($this->policy & self::REQUIRE_ENCRYPTION) == self::REQUIRE_ENCRYPTION; } /** * Checks if protocol requires XTEA encryption with RSA-encrypted key. * * @return bool True if protocol requires that encryption. */ public function usesRSA1024XTEA() { return ($this->options & self::ENCRYPTION_RSA1024XTEA) == self::ENCRYPTION_RSA1024XTEA; } /** * Sends OTAdmin packet. * * @param OTS_Buffer $message Packet to be sent. * @return OTS_Buffer Server respond. * @throws E_OTS_ErrorCode When receive RESPOND_ERROR message. * @throws E_OTS_OutOfBuffer When there is read attemp after end of packet stream. */ public function send(OTS_Buffer $message) { $message = $message->getBuffer(); // encrypts message if required if( isset($this->cipher) ) { $message = $this->cipher->encrypt($message); } $message = pack('v', strlen($message) ) . $message; fputs($this->socket, $message); // reads respond length $length = unpack('v', fgets($this->socket, 3) ); $buffer = fgets($this->socket, $length[1] + 1); // decrypts buffer if required if( isset($this->cipher) ) { $buffer = $this->cipher->decrypt($buffer); } // checks for error code $respond = new OTS_Buffer($buffer); if( $respond->getChar() == self::RESPOND_ERROR) { throw new E_OTS_ErrorCode(self::RESPOND_ERROR, $respond->getString() ); } // returns respond $respond->setPos(0); return $respond; } /** * Closes connection. */ public function __destruct() { fclose($this->socket); } /** * Magic PHP5 method. * * @version 0.1.3 * @since 0.1.3 * @param string $name Property name. * @param mixed $value Property value. * @throws OutOfBoundsException For non-supported properties. * @throws E_OTS_ErrorCode When receive failed respond or unexpected message. * @throws E_OTS_OutOfBuffer When there is read attemp after end of packet stream. */ public function __get($name) { switch($name) { case 'requiresLogin': return $this->requiresLogin(); case 'requiresEncryption': return $this->requiresEncryption(); case 'usesRSA1024XTEA': return $this->usesRSA1024XTEA(); case 'ping': return $this->ping(); default: throw new OutOfBoundsException(); } } /** * Magic PHP5 method. * * @version 0.1.4 * @since 0.1.3 * @param string $name Property name. * @param mixed $value Property value. * @throws OutOfBoundsException For non-supported properties. * @throws E_OTS_ErrorCode When receive failed respond or unexpected message. * @throws E_OTS_OutOfBuffer When there is read attemp after end of packet stream. */ public function __set($name, $value) { switch($name) { case 'login': $this->login($value); break; case 'broadcast': $this->broadcast($value); break; case 'kick': $this->kick($value); break; default: throw new OutOfBoundsException(); } } /** * Logs into server. * *

* Call this method if after connection is established login required flag is set. *

* * @param string $password Admin password. * @throws E_OTS_ErrorCode When receive failed respond or unexpected message. * @throws E_OTS_OutOfBuffer When there is read attemp after end of packet stream. */ public function login($password) { // password packet $message = new OTS_Buffer(); $message->putChar(self::REQUEST_LOGIN); $message->putString($password); // reads respond $message = $this->send($message); $byte = $message->getChar(); // checks respond if($byte != self::RESPOND_LOGIN_OK) { throw new E_OTS_ErrorCode($byte, $message->getString()); } } /** * Ping command. * *

* Note: This methods calculates ping time based on {@link OTS_Admin::send() OTS_Admin::send()} sub-call. This means ping time will be time used for entire seding operation including packet encryption, packing, unpacking and decryption. *

* * @return int Ping time. * @throws E_OTS_ErrorCode When receive failed respond or unexpected message. * @throws E_OTS_OutOfBuffer When there is read attemp after end of packet stream. */ public function ping() { // constructs message $message = new OTS_Buffer(); $message->putChar(self::REQUEST_PING); // start $ping = microtime(true); $message = $this->send($message); // stop $ping = microtime(true) - $ping; $byte = $message->getChar(); // checks if command succeeded if($byte != self::RESPOND_PING_OK) { throw new E_OTS_ErrorCode($byte, $respond->getString()); } return $ping; } /** * Sends command message. * *

* This method wraps another buffer within command byte and also checks for command success. *

* * @param OTS_Buffer $message Command to be send. * @return OTS_Buffer Respond. * @throws E_OTS_ErrorCode If failure respond received. * @throws E_OTS_OutOfBuffer When there is read attemp after end of packet stream. */ private function sendCommand(OTS_Buffer $message) { // prepends command byte $buffer = new OTS_Buffer(); $buffer->putChar(self::REQUEST_COMMAND); $buffer->putString( $message->getBuffer(), false); // sends command $buffer = $this->send($buffer); $byte = $buffer->getChar(); // checks for error code if($byte != self::RESPOND_COMMAND_OK) { throw new E_OTS_ErrorCode($byte, $buffer->getString() ); } // returns respond with reseted position $buffer->setPos(0); return $buffer; } /** * Sends COMMAND_BROADCAST command with given parameter. * *

* Sends broadcast message to all players. *

* * @param string $message Broadcast to be sent. * @throws E_OTS_ErrorCode If failure respond received. * @throws E_OTS_OutOfBuffer When there is read attemp after end of packet stream. */ public function broadcast($message) { // sends message $buffer = new OTS_Buffer(); $buffer->putChar(self::COMMAND_BROADCAST); $buffer->putString($message); $this->sendCommand($buffer); } /** * Sends COMMAND_CLOSE_SERVER command. * *

* Closes server. This command closes server for connections to enable maintenance but doesn't shut it down. *

* * @throws E_OTS_ErrorCode If failure respond received. * @throws E_OTS_OutOfBuffer When there is read attemp after end of packet stream. */ public function close() { // sends message $buffer = new OTS_Buffer(); $buffer->putChar(self::COMMAND_CLOSE_SERVER); $this->sendCommand($buffer); } public function open() { // sends message $buffer = new OTS_Buffer(); $buffer->putChar(self::COMMAND_OPEN_SERVER); $this->sendCommand($buffer); } public function reloadScripts($type) { // sends message $buffer = new OTS_Buffer(); $buffer->putChar(self::COMMAND_RELOAD_SCRIPTS); $buffer->putChar($type); $this->sendCommand($buffer); } /** * Sends COMMAND_PAY_HOUSES command. * *

* Takes fees for all rented houses. *

* * @throws E_OTS_ErrorCode If failure respond received. * @throws E_OTS_OutOfBuffer When there is read attemp after end of packet stream. */ public function payHouses() { // sends message $buffer = new OTS_Buffer(); $buffer->putChar(self::COMMAND_PAY_HOUSES); $this->sendCommand($buffer); } /** * Sends COMMAND_SHUTDOWN_SERVER command. * *

* Shutdowns server. This command closes server thread. *

* * @throws E_OTS_ErrorCode If failure respond received. * @throws E_OTS_OutOfBuffer When there is read attemp after end of packet stream. */ public function shutdown() { // sends message $buffer = new OTS_Buffer(); $buffer->putChar(self::COMMAND_SHUTDOWN_SERVER); $this->sendCommand($buffer); } /** * Sends COMMAND_KICK command with given parameter. * *

* Kicks given player from server. *

* * @version 0.1.4 * @since 0.1.4 * @param string $name Name of player to be kicked. * @throws E_OTS_ErrorCode If failure respond received. * @throws E_OTS_OutOfBuffer When there is read attemp after end of packet stream. */ public function kick($name) { // sends message $buffer = new OTS_Buffer(); $buffer->putChar(self::COMMAND_KICK); $buffer->putString($name); $this->sendCommand($buffer); } /** * Magic PHP5 method. * *

* Allows object importing from {@link http://www.php.net/manual/en/function.var-export.php var_export()}. *

* * @param array $properties List of object properties. * @throws E_OTS_ErrorCode When receive failed respond or unexpected message. * @throws E_OTS_OutOfBuffer When there is read attemp after end of packet stream. */ public static function __set_state($properties) { return new self($properties['host'], $properties['port']); } /** * Magic PHP5 method. * *

* Creates new socket connection to server. *

* * @throws E_OTS_ErrorCode When receive failed respond or unexpected message. * @throws E_OTS_OutOfBuffer When there is read attemp after end of packet stream. */ public function __clone() { $this->__construct($this->host, $this->port); } /** * Magic PHP5 method. * *

* Allows object serialisation. *

* * @return array List of properties that should be saved. */ public function __sleep() { return array('host', 'port'); } /** * Magic PHP5 method. * *

* Allows object unserialisation. *

* * @throws E_OTS_ErrorCode When receive failed respond or unexpected message. * @throws E_OTS_OutOfBuffer When there is read attemp after end of packet stream. */ public function __wakeup() { $this->__construct($this->host, $this->port); } } /**#@-*/ ?>