myaac/system/libs/pot/OTS_Admin.php
SRNT-GG 76bfab1303 WIP - Removing unneccessary closing tags to prevent potential issues. (#223)
* Part 1

Removing closing tags when no HTML or other output comes after the last PHP codeblock.

* Further removals

* nothing

---------

Co-authored-by: slawkens <slawkens@gmail.com>
2023-06-15 21:06:22 +02:00

738 lines
18 KiB
PHP

<?php
/**#@+
* @version 0.1.2
* @since 0.1.2
*/
/**
* @package POT
* @version 0.1.4
* @author Wrzasq <wrzasq@gmail.com>
* @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.
*
* <p>
* This method automaticly handles RSA and XTEA encryption if such method is required by server including keys negotiations.
* </p>
*
* <p>
* After connecting you should check {@link OTS_Admin::requiresLogin() if server requires login}.
* </p>
*
* @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.
*
* <p>
* Call this method if after connection is established login required flag is set.
* </p>
*
* @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.
*
* <p>
* 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.
* </p>
*
* @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.
*
* <p>
* This method wraps another buffer within command byte and also checks for command success.
* </p>
*
* @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.
*
* <p>
* Sends broadcast message to all players.
* </p>
*
* @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.
*
* <p>
* Closes server. This command closes server for connections to enable maintenance but doesn't shut it down.
* </p>
*
* @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.
*
* <p>
* Takes fees for all rented houses.
* </p>
*
* @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.
*
* <p>
* Shutdowns server. This command closes server thread.
* </p>
*
* @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.
*
* <p>
* Kicks given player from server.
* </p>
*
* @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.
*
* <p>
* Allows object importing from {@link http://www.php.net/manual/en/function.var-export.php var_export()}.
* </p>
*
* @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.
*
* <p>
* Creates new socket connection to server.
* </p>
*
* @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.
*
* <p>
* Allows object serialisation.
* </p>
*
* @return array List of properties that should be saved.
*/
public function __sleep()
{
return array('host', 'port');
}
/**
* Magic PHP5 method.
*
* <p>
* Allows object unserialisation.
* </p>
*
* @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);
}
}
/**#@-*/