Location: PHPKode > projects > Kbot > kbot_1.3/rconBots/includes/sourceRcon.class.php
<?PHP
/*
PHP Source Rcon Class
Written by Mathew Rodley (hide@address.com)

This was written as soon as i found out about the Source Rcon Protocol
So im sure there are a few things that i can improve
Please email suggestions+bugs to me

However, this code does work, and at the moment it seems to be stable.
It does however need --enable-sockets, though im positive an fsockopen
version could be written into this aswell.

This is pretty much the alpha version 0.1

Hope it helps you, Khaless

Thanks to
http://wikki.kquery.net/index.php/Other:SourceRcon
and vanja|b
*/
// public methods available
// connect()
// rconCommand($command)
// setReadTimeout($timeout)
// getLastSocketErr()
// getLastSocketErrno()
// packetFragmentsRead()
// sendCommand($command)

class sourceRcon
{
	private $_rconAddress;
	private $_rconPort;
	private $_rconPassword;
	private $_socket;
	private $_connected       = FALSE;
	private $_requestID       = 0;
	// time out in usec.
	private $_readTimeout     = 150000;
	private $_socketLastErrno = 0;
	private $_socketLastErr   = '';
	
	function __construct($rconAddress, $rconPort, $rconPassword)
	{
		$this->_rconAddress  = $rconAddress;
		$this->_rconPort     = (int) $rconPort;
		$this->_rconPassword = $rconPassword;
	}
	
	public function setReadTimeout($timeout)
	{
		$this->_readTimeout = $timeout*1000;
	}
	
	public function getLastSocketErr()
	{
		return $this->_socketLastErr;
	}
	
	public function getLastSocketErrno()
	{
		return $this->_socketLastErrno;
	}
	
	public function connect()
	{
		
		// create and attempt to connect our socket.
		if(($this->socket = socket_create(AF_INET,SOCK_STREAM, SOL_TCP)) === FALSE) {
			$this->_connected = FALSE;
			$this->_socketLastErrno = socket_last_error($this->socket);
			$this->_socketLastErr   = socket_strerror($this->_socketLastErrno);
			return FALSE;
		}	
		if(socket_connect($this->socket,$this->_rconAddress, $this->_rconPort) !== TRUE) {
			$this->_connected = FALSE;
			$this->_socketLastErrno = socket_last_error($this->socket);
			$this->_socketLastErr   = socket_strerror($this->_socketLastErrno);
			return FALSE;
		}
		// ok thats our socket connected, send the first Rcon challenge string. 
		$this->_rawSendPacket($this->_rconPassword,NULL,3);

		// now read the data into an array
		$authResponse = $this->_rawPacketRead();
		// the first authResponse packet [0] is JUNK so ignore it.
		if($authResponse[1]['CommandResponse'] == 2) {
			
			// apparently even on invalid password CommandsResponse is 2
			// however the request id will be -1
			// so we should also check that
			// however, at the present time this is not done
			// thanks Clog^Work
			
			$this->_connected = TRUE;
			return TRUE;
		}
		else 
		{
			$this->_connected = FALSE;
			return FALSE;
		}
	}
	
	private function _rawSendPacket($string1, $string2 = NULL, $command = 2)
	{
		// build the packet backwards
		$packet = $string1 . "\x00" . $string2 . "\x00";
		// build the Request ID and Command into the Packet
		$packet = pack('VV',++$this->requestID, $command) . $packet;
		// ok now add the length
		$packet = pack('V',strlen($packet)) . $packet;
		// ok send the packet.
		socket_send($this->socket,$packet,strlen($packet),0x00);
	}
	
	private function _rawPacketRead()
	{
		// check if there is anything on the other side of the socket.
		$packets = array();
		$read = array($this->socket);
		while(socket_select($read,$write = NULL, $except = NULL, 0, $this->_readTimeout))
		{
			// read the first 4 bytes (will give us packet length)
			$packetLength = socket_read($read[0],4);
			$packetLength = unpack('V1PacketLength', $packetLength);
			
			// this is a bad hack.. it seems that if the rcon reply is
			// spanned over more then 1 packet
			// the format is not followed and PacketLength and RequestID are NOT INCLUDED
			// gg valve!.
			if($packetLength['PacketLength'] > 4096)
			{
				// go back to reading the entire thing, add 8 null bytes at the beginning
				// because valve is STUPID
				$packet = "\x00\x00\x00\x00\x00\x00\x00\x00" . socket_read($read[0], 4096);
			}
			else 
			{
				// now read the remainder of the RCON Packet. 
				// THIS IS HOW IT SHOULD BE!
				$packet = socket_read($read[0],$packetLength['PacketLength']);
			}
			$packets[] = unpack ('V1RequestID/V1CommandResponse/a*String1/a*String2',$packet);
		}
		return $packets;
	}
	
	public function packetFragmentsRead()
	{
		// check connection
		if($this->_connected !== TRUE) return FALSE;
		$rconRequest = array();
		$packets = $this->_rawPacketRead(); // will be in this form Array ( Array ( Packet Size, RequestID, CommandResponse, String1, String2 ) )
		foreach($packets as $packet)
		{
			//echo $packet['String1'];
			// this will build a full packet if we got it in fragments.
			if(array_key_exists($packet['RequestID'], $rconRequest))
			{
				$rconRequest[$packet['RequestID']]['String1'] .= $packet['String1'];
				$rconRequest[$packet['RequestID']]['String2'] .= $packet['String2'];
			}
			else {
				$rconRequest[$packet['RequestID']] = array ('CommandResponse' => $packet['CommandResponse'],
															'String1'         => $packet['String1'],
															'String2'         => $packet['String2']);	
			}
		}
		return $rconRequest;
	}
	
	public function sendCommand($command)
	{
		// check connection
		if($this->_connected !== TRUE) return FALSE;
		$this->_rawSendPacket($command); 
	}
	
	public function rconCommand($command)
	{
		// check connection
		if($this->_connected !== TRUE) return FALSE;
		 
		$this->sendCommand($command);
		// now read what we get back.
		$result = $this->packetFragmentsRead();
		if(array_key_exists($this->requestID, $result))
		return $result[$this->requestID]['String1'];
	}
}
?>
Return current item: Kbot