<?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'];
}
}
?>