This repository has been archived on 2024-07-18. You can view files and clone it, but cannot push or open issues or pull requests.
lwb5/ircbot/socket.php

1010 lines
20 KiB
PHP

<?php
/*
+---------------------------------------------------------------------------
| PHP-IRC v2.2.1 Service Release
| ========================================================
| by Manick
| (c) 2001-2005 by http://www.phpbots.org/
| Contact: manick@manekian.com
| irc: #manekian@irc.rizon.net
| ========================================
+---------------------------------------------------------------------------
| > socket module
| > Module written by Manick
| > Module Version Number: 2.2.0
+---------------------------------------------------------------------------
| > This program is free software; you can redistribute it and/or
| > modify it under the terms of the GNU General Public License
| > as published by the Free Software Foundation; either version 2
| > of the License, or (at your option) any later version.
| >
| > This program is distributed in the hope that it will be useful,
| > but WITHOUT ANY WARRANTY; without even the implied warranty of
| > MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
| > GNU General Public License for more details.
| >
| > You should have received a copy of the GNU General Public License
| > along with this program; if not, write to the Free Software
| > Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+---------------------------------------------------------------------------
| Changes
| =======-------
| > If you wish to suggest or submit an update/change to the source
| > code, email me at manick@manekian.com with the change, and I
| > will look to adding it in as soon as I can.
+---------------------------------------------------------------------------
*/
class socket {
private $rawSockets; //array of raw sockets to be used by select
private $socketInfo; //index by intval($rawSockets[socket])
private $numSockets; //number of sockets currently in use
private $writeSocks; //sockets that have write buffers queued
private $numWriteSocks;
private $readQueueSize = 0;
private $tcpRangeStart = 1025;
private $timeoutSeconds = 0;
private $timeoutMicroSeconds = 0;
private $myTimeout = 0;
private $procQueue;
public function __construct()
{
$this->connectSockets = array();
$this->rawSockets = array();
$this->socketInfo = array();
$this->writeSocks = array();
$this->readQueueSize = 0;
$this->numSockets = 0;
$this->numWriteSocks = 0;
}
public function setProcQueue($class)
{
$this->procQueue = $class;
}
public function getNumSockets()
{
return $this->numSockets;
}
public function setTcpRange($range)
{
if (intval($range) != 0)
{
$this->tcpRangeStart = $range;
}
}
public function getHost($sockInt)
{
$status = socket_getsockname($this->socketInfo[$sockInt]->socket, $addr);
if ($status == false)
{
return false;
}
return $addr;
}
public function getRemoteHost($sockInt)
{
$status = socket_getpeername($this->socketInfo[$sockInt]->socket, $addr);
if ($status == false)
{
return false;
}
return $addr;
}
public function setTimeout($time)
{
$sec = intval($time);
$msec = intval(($time - $sec)*1e6);
if ($sec == 0)
{
$msec = $msec < $this->myTimeout ? $this->myTimeout : $msec;
}
if ($sec < $this->timeoutSeconds)
{
$this->timeoutSeconds = $sec;
$this->timeoutMicroSeconds = $msec;
}
else if ($sec == $this->timeoutSeconds)
{
if ($msec < $this->timeoutMicroSeconds)
{
$this->timeoutMicroSeconds = $msec;
}
}
}
public function setHandler($sockInt, $owner, $class, $function)
{
if (!isset($this->socketInfo[$sockInt]))
{
return false;
}
$sock = $this->socketInfo[$sockInt];
$sock->owner = $owner;
$sock->class = $class;
$sock->func = $function;
return true;
}
/* For debug... */
public function showSocks($read, $write)
{
echo "\n\nRead:\n";
if (is_array($read))
{
foreach($read AS $sock)
{
echo $sock . "\n";
}
}
echo "\nWrite:\n";
if (is_array($write))
{
foreach($write AS $sock)
{
echo $sock . "\n";
}
}
echo "\n";
}
public function handle()
{
//For debug
//echo "Read: " . $this->readQueueSize . " Write: " . $this->writeQueueSize . "\n";
//echo "timeout: " . $this->timeoutSeconds . "-" . $this->timeoutMicroSeconds . "\n";
if ($this->numSockets < 1)
{
if ($this->timeoutSeconds > 0)
{
sleep($this->timeoutSeconds);
}
if ($this->timeoutMicroSeconds > 0)
{
usleep($this->timeoutMicroSeconds);
}
$this->timeoutSeconds = 1000;
return;
}
if ($this->numSockets < 1)
{
$sockArray = NULL;
$except = NULL;
}
else
{
$sockArray = $this->rawSockets;
$except = $this->rawSockets;
}
if ($this->numWriteSocks < 1)
{
$writeArray = NULL;
}
else
{
$writeArray = $this->writeSocks;
}
//For debug
//$this->showSocks($sockArray, $writeArray);
$newData = socket_select($sockArray, $writeArray, $except, $this->timeoutSeconds, $this->timeoutMicroSeconds);
$this->timeoutSeconds = 1000;
if ($newData === false)
{
die("socket_select error"); // need to change this to handle errors
return;
}
if (!$newData)
{
return;
}
if (count($sockArray) != 0)
{
foreach($sockArray AS $socket)
{
$sockIntval = intval($socket);
switch($this->socketInfo[$sockIntval]->status)
{
case SOCK_CONNECTED:
$this->readSocket($sockIntval);
break;
case SOCK_LISTENING:
$this->acceptSocket($sockIntval);
break;
case SOCK_CONNECTING:
$this->connectSocket($sockIntval);
break;
default:
break;
}
}
}
if (count($writeArray) != 0)
{
foreach ($writeArray AS $socket)
{
$sockIntval = intval($socket);
$this->sendSocketQueue($sockIntval, 1);
}
}
if (count($except) != 0)
{
foreach($except AS $socket)
{
$sockIntval = intval($socket);
$this->markDead($sockIntval);
}
}
}
private function callBack($sockIntval, $msg)
{
//Schedule the callback to run
if ($this->socketInfo[$sockIntval]->func != "" &&
$this->socketInfo[$sockIntval]->func != null)
{
$this->procQueue->addQueue( $this->socketInfo[$sockIntval]->owner,
$this->socketInfo[$sockIntval]->class,
$this->socketInfo[$sockIntval]->func,
$msg,
.01);
}
}
private function readSocket($sockIntval)
{
if ($this->isDead($sockIntval))
{
return;
}
if ($this->socketInfo[$sockIntval]->status != SOCK_CONNECTED)
{
return;
}
$dataRead = false;
$socket = $this->socketInfo[$sockIntval]->socket;
//Read in 4096*30 bytes
for ($i = 0; $i < 30; $i++)
{
$response = @socket_read($socket, 8192, PHP_BINARY_READ);
$respLength = strlen($response);
if ($response === false)
{
$err = socket_last_error($this->socketInfo[$sockIntval]->socket);
if ($err != EALREADY && $err != EAGAIN && $err != EINPROGRESS)
{
$this->markDead($sockIntval);
}
break;
}
else if ($respLength === 0)
{
if ($i == 0)
{
$this->markDead($sockIntval);
}
break;
}
$dataRead = true;
$this->readQueueSize += $respLength;
$this->socketInfo[$sockIntval]->readLength += $respLength;
$this->socketInfo[$sockIntval]->readQueue .= $response;
}
if ($dataRead == true)
{
if ($this->socketInfo[$sockIntval]->readScheduled == false)
{
$this->callBack($sockIntval, CONN_READ);
$this->socketInfo[$sockIntval]->readScheduled = true;
}
}
}
private function sendSocketQueue($sockIntval, $queued = 0)
{
$socket = $this->socketInfo[$sockIntval]->socket;
if ($this->isDead($sockIntval))
{
return;
}
if (($bytesWritten = @socket_write($socket, $this->socketInfo[$sockIntval]->writeQueue)) === false)
{
$socketError = socket_last_error($socket);
switch ($socketError)
{
case EAGAIN:
case EALREADY:
case EINPROGRESS:
break;
default:
$this->markDead($sockIntval);
break;
}
}
else
{
$this->socketInfo[$sockIntval]->writeQueue = substr($this->socketInfo[$sockIntval]->writeQueue, $bytesWritten);
$this->socketInfo[$sockIntval]->writeLength -= $bytesWritten;
//Queue Empty, Remove socket from write
if ($this->socketInfo[$sockIntval]->writeLength == 0 && $queued == 1)
{
$this->removeWriteSocketFromArray($sockIntval);
}
if ($this->socketInfo[$sockIntval]->writeLength == 0)
{
unset($this->socketInfo[$sockIntval]->writeQueue);
$this->socketInfo[$sockIntval]->writeQueue = "";
}
//Callback after we wrote to socket
if ($this->socketInfo[$sockIntval]->writeScheduled == false)
{
$this->callBack($sockIntval, CONN_WRITE);
$this->socketInfo[$sockIntval]->writeScheduled = true;
}
}
}
private function createSocket()
{
$socket = socket_create(AF_INET, SOCK_STREAM, 0);
socket_set_option($socket,SOL_SOCKET,SO_REUSEADDR,1);
if ($socket == false)
{
return false;
}
socket_clear_error($socket);
if (socket_set_nonblock($socket) == false)
{
@socket_close($socket);
return false;
}
return $socket;
}
public function bindIP($sockInt, $ip)
{
if (!isset($this->socketInfo[$sockInt]))
{
return;
}
$sockData = $this->socketInfo[$sockInt];
if ($sockData->status != SOCK_CONNECTING)
{
return;
}
socket_bind($sockData->socket, $ip);
}
public function addSocket($host, $port)
{
$listening = false;
if ($host == null)
{
$host = false;
$listening = true;
}
$socket = $this->createSocket();
if ($socket == false)
{
return false;
}
if ($listening == true)
{
$boundError = false;
$currentPort = $this->tcpRangeStart;
if ($port !== null)
{
$boundError = @socket_bind($socket, 0, $port);
if ($boundError === false)
{
return false;
}
}
else
{
while ($boundError === false)
{
$boundError = @socket_bind($socket, 0, $currentPort);
$currentPort++;
if ($currentPort > $this->tcpRangeStart + HIGHEST_PORT)
{
return false;
}
}
$port = $currentPort - 1;
}
if (socket_listen($socket) === false)
{
return false;
}
if (DEBUG == 1)
{
echo "Socket Listening: " . intval($socket) . "\n";
}
}
else
{
if (DEBUG == 1)
{
echo "Socket Opened: " . intval($socket) . "\n";
}
}
$newSock = new socketInfo;
$newSock->socket = $socket;
$newSock->owner = null;
$newSock->class = null;
$newSock->func = null;
$newSock->readQueue = "";
$newSock->writeQueue = "";
$newSock->readLength = 0;
$newSock->writeLength = 0;
$newSock->host = $host;
$newSock->port = $port;
$newSock->newSockInt = array();
$newSock->readScheduled = false;
$newSock->writeScheduled = false;
$this->socketInfo[intval($socket)] = $newSock;
if ($listening == true)
{
$newSock->status = SOCK_LISTENING;
}
else
{
$newSock->status = SOCK_CONNECTING;
}
$this->numSockets++;
$this->rawSockets[] = $socket;
return intval($socket);
}
public function clearReadSchedule($sockInt)
{
if (!isset($this->socketInfo[$sockInt]))
{
return false;
}
$this->socketInfo[$sockInt]->readScheduled = false;
return true;
}
public function clearWriteSchedule($sockInt)
{
if (!isset($this->socketInfo[$sockInt]))
{
return false;
}
$this->socketInfo[$sockInt]->writeScheduled = false;
return true;
}
/*
public function beginConnect($sockInt)
{
$this->procQueue->addQueue(null, $this, "connectSocketProcess", $sockInt, 0);
}
*/
//process to connect the socket $sockInt
public function connectSocketTimer($sockInt)
{
if (!isset($this->socketInfo[$sockInt]))
{
return false;
}
if ($this->socketInfo[$sockInt]->status != SOCK_CONNECTING)
{
return false;
}
$this->connectSocket($sockInt);
return true;
}
//Remove all sockets from a specific irc bot
public function removeOwner($class)
{
foreach($this->socketInfo AS $sockInt => $data)
{
if ($class === $data->owner)
{
$this->killSocket($sockInt);
$this->removeSocket($sockInt);
}
}
}
public function killSocket($sockInt)
{
if (DEBUG == 1)
{
echo "Killing socket: " . $sockInt . "\n";
}
if ($this->socketInfo[$sockInt]->status == SOCK_ACCEPTED)
{
$this->acceptedSockets--;
}
if ($this->socketInfo[$sockInt]->status != SOCK_DEAD)
{
$this->removeReadSocketFromArray($sockInt);
$this->removeWriteSocketFromArray($sockInt);
$this->socketInfo[$sockInt]->status = SOCK_DEAD;
}
if (is_resource($this->socketInfo[$sockInt]->socket))
{
if (DEBUG == 1)
{
echo "Closed socket: " . $sockInt . "\n";
}
socket_clear_error($this->socketInfo[$sockInt]->socket);
socket_close($this->socketInfo[$sockInt]->socket);
}
else
{
if (DEBUG == 1)
{
echo "Socket already closed: " . $sockInt . "\n";
}
}
}
public function removeSocket($socketIntval)
{
$this->readQueueSize -= $this->socketInfo[$socketIntval]->readLength;
unset($this->socketInfo[$socketIntval]->class);
unset($this->socketInfo[$socketIntval]->owner);
unset($this->socketInfo[$socketIntval]);
}
private function removeReadSocketFromArray($socketIntval)
{
foreach ($this->rawSockets AS $index => $socket)
{
if ($socket === $this->socketInfo[$socketIntval]->socket)
{
unset($this->rawSockets[$index]);
$this->numSockets--;
break;
}
}
}
private function removeWriteSocketFromArray($socketIntval)
{
foreach ($this->writeSocks AS $index => $rawSocket)
{
if ($rawSocket === $this->socketInfo[$socketIntval]->socket)
{
unset($this->writeSocks[$index]);
$this->numWriteSocks--;
break;
}
}
}
public function sendSocket($sockInt, $data)
{
if ($this->isDead($sockInt))
{
return;
}
if ($this->socketInfo[$sockInt]->status != SOCK_CONNECTED)
{
return;
}
$inQueue = $this->socketInfo[$sockInt]->writeLength > 0 ? true : false;
$len = strlen($data);
$this->socketInfo[$sockInt]->writeQueue .= $data;
$this->socketInfo[$sockInt]->writeLength += $len;
if (!$inQueue)
{
$this->sendSocketQueue($sockInt, 0);
if ($this->socketInfo[$sockInt]->status == SOCK_CONNECTED)
{
if ($this->socketInfo[$sockInt]->writeLength > 0)
{
$this->writeSocks[] = $this->socketInfo[$sockInt]->socket;
$this->numWriteSocks++;
}
}
}
return $len;
}
private function acceptSocket($sockInt)
{
$sockData = $this->socketInfo[$sockInt];
$newSock = @socket_accept($sockData->socket);
socket_set_nonblock($newSock);
if ($newSock === false)
{
return false;
}
$newSockInt = intval($newSock);
if (DEBUG == 1)
{
echo "Accepted new connection on: " . $newSockInt . "\n";
}
$this->socketInfo[$newSockInt] = clone $sockData;
$this->socketInfo[$newSockInt]->socket = $newSock;
$this->socketInfo[$newSockInt]->status = SOCK_ACCEPTING; /* fix a onRead done before onAccept */
$this->numSockets++;
$this->rawSockets[] = $newSock;
$this->socketInfo[$sockInt]->newSockInt[] = $newSockInt;
//Schedule the callback to run
$this->callBack($sockInt, CONN_ACCEPT);
return true;
}
private function connectSocket($sockInt)
{
if (!isset($this->socketInfo[$sockInt]))
{
return;
}
if ($this->socketInfo[$sockInt]->status == SOCK_CONNECTED)
{
return;
}
if (@socket_connect($this->socketInfo[$sockInt]->socket,
$this->socketInfo[$sockInt]->host,
$this->socketInfo[$sockInt]->port) === true)
{
$this->socketInfo[$sockInt]->status = SOCK_CONNECTED;
$this->callBack($sockInt, CONN_CONNECT);
}
else
{
$socketError = socket_last_error($this->socketInfo[$sockInt]->socket);
switch ($socketError)
{
case 10022:
if (OS != 'windows')
{
$this->markDead($sockInt);
}
break;
case EISCONN:
$this->socketInfo[$sockInt]->status = SOCK_CONNECTED;
$this->callBack($sockInt, CONN_CONNECT);
break;
case EAGAIN:
case EALREADY:
case EINPROGRESS:
break;
default:
$this->markDead($sockInt);
break;
}
}
return;
}
public function getSockStatus($sockInt)
{
return (isset($this->socketInfo[$sockInt]) ? $this->socketInfo[$sockInt]->status : false);
}
public function getSockData($sockInt)
{
return (isset($this->socketInfo[$sockInt]) ? $this->socketInfo[$sockInt] : false);
}
public function alterSocket($sockInt, $level, $opt, $val)
{
return socket_set_option($this->socketInfo[$sockInt]->socket, $level, $opt, $val);
}
public function getSockError($sockInt)
{
return socket_last_error($this->socketInfo[$sockInt]->socket);
}
public function getSockStringError($sockInt)
{
$strErr = "[" . self::getSockError($sockInt) . "]:" . socket_strerror(socket_last_error($this->socketInfo[$sockInt]->socket));
$strErr = str_replace("\n", "", $strErr);
return $strErr;
}
public function hasAccepted($sockInt)
{
if (!isset($this->socketInfo[$sockInt]))
{
return false;
}
$newSockInt = array_shift($this->socketInfo[$sockInt]->newSockInt);
$this->socketInfo[$newSockInt]->status = SOCK_CONNECTED;
return $newSockInt;
}
private function markDead($sockInt)
{
if (DEBUG == 1)
{
echo "Marking socket dead: " . $sockInt . "\n";
}
$this->removeReadSocketFromArray($sockInt);
$this->removeWriteSocketFromArray($sockInt);
$this->socketInfo[$sockInt]->status = SOCK_DEAD;
$this->callBack($sockInt, CONN_DEAD);
}
public function isDead($sockInt)
{
$socket = $this->socketInfo[$sockInt]->socket;
if (!is_resource($socket))
{
$this->markDead($sockIntval);
return true;
}
if (!isset($this->socketInfo[$sockInt]))
{
return true;
}
switch ($this->socketInfo[$sockInt]->status)
{
case SOCK_DEAD:
return true;
break;
default:
return false;
break;
}
}
public function hasWriteQueue($sockInt)
{
if ($this->socketInfo[$sockInt]->writeLength > 0)
{
return $this->socketInfo[$sockInt]->writeLength;
}
else
{
return false;
}
}
public function getQueue($sockInt)
{
$this->readQueueSize -= $this->socketInfo[$sockInt]->readLength;
$queue = $this->socketInfo[$sockInt]->readQueue;
unset($this->socketInfo[$sockInt]->readQueue);
$this->socketInfo[$sockInt]->readQueue = "";
$this->socketInfo[$sockInt]->readLength = 0;
return $queue;
}
public function hasQueue($sockInt)
{
if ($this->socketInfo[$sockInt]->readLength > 0)
{
return true;
}
return false;
}
public function hasLine($sockInt)
{
if (strpos($this->socketInfo[$sockInt]->readQueue, "\n") !== false)
{
return true;
}
return false;
}
public function getQueueLine($sockInt)
{
$readQueue =& $this->socketInfo[$sockInt]->readQueue;
if (!$this->hasLine($sockInt))
{
return false;
}
$crlf = "\r\n";
$crlfLen = 2;
$lineEnds = strpos($readQueue, $crlf);
if ($lineEnds === false)
{
$crlf = "\n";
$crlfLen = 1;
$lineEnds = strpos($readQueue, $crlf);
}
$line = substr($readQueue, 0, $lineEnds);
$readQueue = substr($readQueue, $lineEnds + $crlfLen);
$this->readQueueSize -= ($lineEnds + $crlfLen);
$this->socketInfo[$sockInt]->readLength -= ($lineEnds + $crlfLen);
if ($readQueue == "")
{
unset($this->socketInfo[$sockInt]->readQueue);
$this->socketInfo[$sockInt]->readQueue = "";
}
return $line;
}
/* Misc HTTP Functions */
public static function generatePostQuery($query, $host, $path, $httpVersion = "1.0")
{
if ($query != "" && substr($query, 0, 1) != "?")
{
$query = "?" . $query;
}
if ($path == "")
{
$path = "/";
}
$postQuery = "POST " . $path . " HTTP/".$httpVersion."\r\n";
$postQuery .= "Host: " . $host . "\r\n";
$postQuery .= "Content-type: application/x-www-form-urlencoded\r\n";
$postQuery .= "Content-length: " . strlen($query) . "\r\n\r\n";
$postQuery .= $query;
return $postQuery;
}
public static function generateGetQuery($query, $host, $path, $httpVersion = "1.0")
{
if ($path == "")
{
$path = "/";
}
if ($query != "" && substr($query, 0, 1) != "?")
{
$query = "?" . $query;
}
$getQuery = "GET " . $path . $query . " HTTP/".$httpVersion."\r\n";
$getQuery .= "Host: " . $host . "\r\n";
$getQuery .= "Connection: close\r\n";
$getQuery .= "\r\n";
return $getQuery;
}
}
?>