irc 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 irc { // Config Vars private $clientConfig = array(); private $configFilename = ""; // nick, realname, localhost, remotehost, ident, host, port private $serverConfig = array(); private $nick = ""; private $tempNick = ""; // only used if our nick is taken initially private $clientIP; private $clientLongIP; // Above address related determined on runtime.. private $startTime = 0; //when the bot was started // Status Vars private $status = STATUS_IDLE; private $exception; private $reportedStatus = STATUS_IDLE; //Classes private $timerClass = null; private $socketClass = null; private $dccClass = null; private $parserClass = null; //Socket Vars private $sockInt; //old pre 2.1.2 method private $conn; //new 2.1.2 method private $connectStartTime = 0; private $lagTime = 0; private $timeConnected = 0; //Queue Vars private $textQueueTime = 0; private $textQueue = array(); private $textQueueLength = 0; /* This variable will be set when new text is sent to be sent to the irc server, its so we don't have to call addQueue() more than once. */ private $textQueueAdded = false; private $modeQueueAdded = false; private $modeQueue = array(); private $modeQueueLength = 0; private $modeTimerAdded = false; //Stats Var private $stats = array(); //Parsing Vars private $lVars = array(); private $modeArray; private $prefixArray; //Channel and User Linked Lists private $chanData = array(); private $maintainChannels = array(); /* private $chanData = null; */ private $usageList = array(); //Timers private $timeoutTimer = 0; private $lastPing = 0; private $lastPingTime = 0; private $nickTimer = 0; private $checkChansTimer = 0; //Kill private $setKill = false; //My process id private $pid; //Process Queue private $procQueue; function __construct() { $this->startTime = time(); // Initialize the stats array $this->stats = array( 'BYTESUP' => 0, 'BYTESDOWN' => 0, ); $this->pid = getmypid(); return; } public function init() { $this->socketClass->setTcpRange( $this->getClientConf('dccrangestart') ); /* Add other timers */ $this->timerClass->addTimer("check_nick_timer", $this, "checkNick", "", NICK_CHECK_TIMEOUT); $this->timerClass->addTimer("check_channels_timer", $this, "checkChans", "", CHAN_CHECK_TIMEOUT); $this->timerClass->addTimer("check_ping_timeout_timer", $this, "checkPingTimeout", "", PING_TIMEOUT+1); /* Timer that makes sure we're connected every 1:15 minutes */ $this->reconnect(); //$this->timerClass->addTimer("check_connection", $this, "checkConnection", "", 75, true); } public function pid() { return $this->pid; } public function setConfig($config, $filename = "") { $this->clientConfig = $config; $this->configFilename = $filename; $this->nick = $config['nick']; } public function setProcQueue($class) { $this->procQueue = $class; } public function setDccClass($class) { $this->dccClass = $class; $this->dccClass->setIrcClass($this); } public function setParserClass($class) { $this->parserClass = $class; $this->parserClass->setIrcClass($this); } public function setSocketClass($class) { $this->socketClass = $class; } public function setTimerClass($class) { $this->timerClass = $class; } public function setClientConfigVar($var, $value) { $this->clientConfig[$this->myStrToLower($var)] = $value; } public function getNick() { return $this->nick; } public function getMaintainedChannels() { return $this->maintainChannels; } public function getServerConf($var) { if (isset($this->serverConfig[$this->myStrToUpper($var)])) { return $this->serverConfig[$this->myStrToUpper($var)]; } return ""; } public function getConfigFilename() { return $this->configFilename; } public function getClientConf($var = "") { if ($var != "") { if (isset($this->clientConfig[$var])) { return $this->clientConfig[$var]; } } else { return $this->clientConfig; } return ""; } //debug only! public function displayUsers() { $toReturn = ""; foreach($this->chanData AS $chanPtr) { $toReturn .= $chanPtr->name . "\n"; foreach($chanPtr->memberList AS $memPtr) { $toReturn .= $memPtr->realNick . " -- "; $toReturn .= $memPtr->status . "\n"; } } return trim($toReturn); } private function addMember($channel, $nick, $ident, $status, $host = "") { $channel = $this->myStrToLower($channel); $realNick = trim($nick); $nick = trim($this->myStrToLower($nick)); $newMember = new memberLink; if ($host != "") { $newMember->host = $host; } $newMember->nick = $nick; $newMember->realNick = $realNick; $newMember->ident = $ident; $newMember->status = $status; if (!isset($this->chanData[$channel])) { $chanPtr = new channelLink; $chanPtr->count = 1; $chanPtr->memberList = NULL; $chanPtr->banComplete = 1; $chanPtr->name = $channel; $this->chanData[$channel] = $chanPtr; } else { $chanPtr = $this->chanData[$channel]; if (!isset($chanPtr->memberList[$nick])) { $chanPtr->count++; } } $chanPtr->memberList[$nick] = $newMember; return $chanPtr->count; } public function &getChannelData($channel = "") { if ($channel == "") { return $this->chanData; } $channel = $this->myStrToLower($channel); if (isset($this->chanData[$channel])) { return $this->chanData[$channel]; } return NULL; } public function setMemberData($nick, $ident, $host) { $nick = $this->myStrToLower($nick); foreach($this->chanData AS $chanPtr) { if (isset($chanPtr->memberList[$nick])) { $chanPtr->memberList[$nick]->ident = $ident; $chanPtr->memberList[$nick]->host = $host; } } } public function getUserData($user, $channel = "") { if ($user == "") { return NULL; } $channel = $this->myStrToLower($channel); $user = $this->myStrToLower($user); if ($channel == "") { foreach ($this->chanData AS $chanPtr) { if (isset($chanPtr->memberList[$user])) { return $chanPtr->memberList[$user]; } } return NULL; } if (isset($this->chanData[$channel])) { if (isset($this->chanData[$channel]->memberList[$user])) { return $this->chanData[$channel]->memberList[$user]; } return NULL; } } private function changeMember( $channel, $oldNick, $newNick, $ident, $newStatus, $action, $newHost = "") { $channel = $this->myStrToLower($channel); $ident = trim($ident); $oldNick = trim($this->myStrToLower($oldNick)); $realNick = trim($newNick); $newNick = trim($this->myStrToLower($newNick)); // See if we have a valid usermode if ($newStatus != "") { if (strpos($this->getServerConf('PREFIX'), $newStatus) === false) { $newStatus = ""; $action = ""; } } //Find our channel, also change user name if no channel name given if ($channel == "") { foreach($this->chanData AS $chanPtr) { if (isset($chanPtr->memberList[$oldNick])) { $memPtr = $chanPtr->memberList[$oldNick]; if ($newHost != "") { $memPtr->host = $newHost; } if ($ident != "") { $memPtr->ident = $ident; } $memPtr->nick = $newNick; $memPtr->realNick = $realNick; $chanPtr->memberList[$newNick] = $memPtr; unset($chanPtr->memberList[$oldNick]); } } return; } if (isset($this->chanData[$channel])) { $chanPtr = $this->chanData[$channel]; if (isset($chanPtr->memberList[$oldNick])) { $memPtr = $chanPtr->memberList[$oldNick]; if ($newHost != "") { $memPtr->host = $newHost; } if ($ident != "") { $memPtr->ident = $ident; } if ($newStatus != "") { if ($action == "+") { if (strpos($memPtr->status, $newStatus) === false) { $memPtr->status .= $newStatus; } } else { $memPtr->status = str_replace($newStatus, "", $memPtr->status); } } } } } private function setChannelData($channel, $item, $val) { $channel = $this->myStrToLower($channel); $item = $this->myStrToLower($item); if (isset($this->chanData[$channel])) { $chanPtr = $this->chanData[$channel]; switch ($item) { case "topicby": $chanPtr->topicBy = $val; break; case "topic": $chanPtr->topic = $val; break; case "bancomplete": $chanPtr->banComplete = $val; break; case "created": $chanPtr->created = $val; break; case "modes": unset($chanPtr->modes); $chanPtr->modes = array(); $chanPtr->modes['MODE_STRING'] = substr($val, 1); break; } } } private function clearBanList($channel) { $channel = $this->myStrToLower($channel); if (isset($this->chanData[$channel])) { unset($this->chanData[$channel]->banList); $this->chanData[$channel]->banList = array(); } } private function changeChannel($channel, $action, $newStatus, $extraStatus = "") { $channel = $this->myStrToLower($channel); $newStatus = trim($newStatus); // i.e, l, b if (!is_array($extraStatus)) { $extraStatus = trim($extraStatus); // i.e, 50, *!*@* } //ex. CHANMODES=eIb,k,l,cimnpstMNORZ if ($newStatus == "" || $action == "") { return; } if (!isset($this->modeArray[$newStatus])) { return; } $type = $this->modeArray[$newStatus]; if ($type != BY_NONE && $extraStatus == "") { return; } if (strpos($this->getServerConf('CHANMODES'), $newStatus) === false) { return; } //Find our channel if (isset($this->chanData[$channel])) { $chanPtr = $this->chanData[$channel]; if (!is_array($chanPtr->modes)) { unset($chanPtr->modes); $chanPtr->modes = array(); } if (!isset($chanPtr->modes['MODE_STRING'])) { $chanPtr->modes['MODE_STRING'] = ""; } if ($type == BY_MASK) { if ($newStatus == "b") { if ($action == "+") { if (is_array($extraStatus)) { $ban = $extraStatus[0]; $time = $extraStatus[1]; } else { $ban = $extraStatus; $time = time(); } $chanPtr->banList[] = array( 'HOST' => $ban, 'TIME' => $time, ); } else { foreach ($chanPtr->banList AS $index => $data) { if ($data['HOST'] == $extraStatus) { unset($chanPtr->banList[$index]); break; } } } } } else { if ($action == "+") { if (strpos($chanPtr->modes['MODE_STRING'], $newStatus) === false) { $chanPtr->modes['MODE_STRING'] .= $newStatus; if ($type != BY_NONE) { $chanPtr->modes[$newStatus] = $extraStatus; } } } else { $chanPtr->modes['MODE_STRING'] = str_replace($newStatus, "", $chanPtr->modes['MODE_STRING']); if ($type != BY_NONE) { unset($chanPtr->modes[$newStatus]); } } } } } private function purgeChanList() { foreach ($this->chanData AS $cIndex => $chanPtr) { foreach ($chanPtr->memberList AS $mIndex => $memPtr) { unset($chanPtr->memberList[$mIndex]); } unset($this->chanData[$cIndex]); } unset($this->chanData); $this->chanData = array(); } private function removeMember($channel, $nick) { $channel = $this->myStrToLower($channel); $nick = trim($this->myStrToLower($nick)); if ($channel == "") { foreach($this->chanData AS $chanPtr) { if (isset($chanPtr->memberList[$nick])) { unset($chanPtr->memberList[$nick]); $chanPtr->count--; } } } else { if (isset($this->chanData[$channel])) { if (isset($this->chanData[$channel]->memberList[$nick])) { unset($this->chanData[$channel]->memberList[$nick]); $this->chanData[$channel]->count--; } } } } private function removeChannel($channel) { $channel = $this->myStrToLower($channel); if (isset($this->chanData[$channel])) { $chanPtr = $this->chanData[$channel]; foreach ($chanPtr->memberList AS $mIndex => $memPtr) { $this->removeMember($channel, $mIndex); } unset($chanPtr->banList); unset($chanPtr->modes); unset($this->chanData[$channel]); } } public function isChanMode($channel, $mode, $extra = "") { $channel = $this->myStrToLower($channel); $extra = $this->myStrToLower($extra); if (!isset($this->chanData[$channel])) { return; } $chanPtr = $this->chanData[$channel]; if (!isset($this->modeArray[$mode])) { return false; } $type = $this->modeArray[$mode]; if ($type == BY_MASK) { if ($mode == "b") { foreach ($chanPtr->banList AS $index => $ban) { if ($this->hostMasksMatch($ban['HOST'], $extra)) { return true; } } } } else { if (strpos($chanPtr->modes['MODE_STRING'], $mode) !== false) { return true; } } return false; } public function hasModeSet($chan, $nick, $modes) { $channel = $this->myStrToLower($chan); $nick = $this->myStrToLower($nick); if (isset($this->chanData[$channel])) { if (isset($this->chanData[$channel]->memberList[$nick])) { $memPtr = $this->chanData[$channel]->memberList[$nick]; while ($modes != "") { $mode = substr($modes, 0, 1); $modes = substr($modes, 1); if (strpos($memPtr->status, $mode) !== false) { return true; } } } } return false; } public function isMode($nick, $channel, $mode) { $channel = $this->myStrToLower($channel); $nick = $this->myStrToLower($nick); if (isset($this->chanData[$channel])) { if (isset($this->chanData[$channel]->memberList[$nick])) { $memPtr = $this->chanData[$channel]->memberList[$nick]; if ($mode == "online") { return true; } else { if (strpos($memPtr->status, $mode) !== false) { return true; } } } } return false; } private function getHostMask($mask) { $offsetA = strpos($mask, "!"); $offsetB = strpos($mask, "@"); $myMask = array(); $myMask['nick'] = $this->myStrToLower(substr($mask, 0, $offsetA)); $myMask['ident'] = $this->myStrToLower(substr($mask, $offsetA + 1, $offsetB - $offsetA - 1)); $myMask['host'] = $this->myStrToLower(substr($mask, $offsetB + 1)); return $myMask; } public function hostMasksMatch($mask1, $mask2) { $maskA = $this->getHostMask($mask1); $maskB = $this->getHostMask($mask2); $ident = false; $nick = false; $host = false; if ($maskA['ident'] == $maskB['ident'] || $maskA['ident'] == "*" || $maskB['ident'] == "*") { $ident = true; } if ($maskA['nick'] == $maskB['nick'] || $maskA['nick'] == "*" || $maskB['nick'] == "*") { $nick = true; } if ($maskA['host'] == $maskB['host'] || $maskA['host'] == "*" || $maskB['host'] == "*") { $host = true; } if ($host && $nick && $ident) { return true; } else { return false; } } public function setToIdleStatus() { $this->status = STATUS_IDLE; } private function getStatus() { $this->reportedStatus = $this->status; return $this->status; } private function getStatusChange() { return ($this->reportedStatus != $this->status); } private function getException() { return $this->exception; } private function updateContext() { if ($this->getStatusChange()) { $status = $this->getStatus(); $statusStr = $this->getStatusString($status); $this->log("STATUS: " . $statusStr); $this->dccClass->dccInform("Status: " . $statusStr); switch($status) { case STATUS_ERROR: $exception = $this->getException(); $this->log("Error: " . $exception->getMessage()); $this->dccClass->dccInform("Error: " . $exception->getMessage()); break; default: break; } } } public function reconnect() { $this->updateContext(); try { $this->connectStartTime = time(); $conn = new connection($this->getClientConf('server'), $this->getClientConf('port'), CONNECT_TIMEOUT); $this->conn = $conn; $conn->setSocketClass($this->socketClass); $conn->setIrcClass($this); $conn->setCallbackClass($this); $conn->setTimerClass($this->timerClass); $conn->init(); if ($conn->getError()) { throw new ConnectException($conn->getErrorMsg()); } //Bind socket... if ($this->getClientConf('bind') != "") { $conn->bind($this->getClientConf('bind')); } $this->sockInt = $conn->getSockInt(); $conn->connect(); } catch (ConnectException $e) { $this->beginReconnectTimer(); $this->status = STATUS_ERROR; $this->exception = $e; return; } $this->status = STATUS_CONNECTING; $this->updateContext(); return; } public function onConnect($conn) { $this->status = STATUS_CONNECTED; $this->updateContext(); $this->timeoutTimer = time(); $ip = ""; if ($this->getClientConf('natip') != "") { $ip = $this->getClientConf('natip'); } $this->setClientIP($ip); $this->register(); //TODO: Add registration timeout timer (with $conn to check identity) return false; } public function onRead($conn) { $this->updateContext(); return $this->readInput(); } public function onWrite($conn) { //Do nothing.. this function has no purpose for the ircClass return false; } public function onAccept($listenConn, $newConn) { //Do nothing.. this function has no purpose for the ircClass return false; } public function onDead($conn) { if ($conn->getError()) { $this->status = STATUS_ERROR; $this->exception = new ConnectException($conn->getErrorMsg()); } $this->disconnect(); return false; } public function onConnectTimeout($conn) { $this->status = STATUS_ERROR; $this->exception = new ConnectException("Connection attempt timed out"); $this->disconnect(); } private function beginReconnectTimer() { $this->timerClass->addTimer(self::randomHash(), $this, "reconnectTimer", $this->conn, ERROR_TIMEOUT); } public function reconnectTimer($conn) { //If curr connection is equal to the stored connection, then no forced //connect was attempted, so attempt another. if ($this->conn === $conn) { $this->reconnect(); } return false; } public function disconnect() { $this->conn->disconnect(); $this->updateContext(); $this->status = STATUS_ERROR; $this->exception = new ConnectException("Disconnected from server"); $this->updateContext(); //reset all vars $this->purgeChanList(); $this->timeoutTimer = 0; $this->lastPing = 0; $this->lastPingTime = 0; $this->nickTimer = 0; $this->connectStartTime = 0; $this->lagTime = 0; $this->checkChansTimer = 0; $this->purgeTextQueue(); $this->nick = $this->getClientConf('nick'); $this->tempNick = ""; unset($this->modeArray); unset($this->prefixArray); unset($this->serverConfig); $this->modeArray = array(); $this->prefixArray = array(); $this->serverConfig = array(); $this->beginReconnectTimer(); return; } private function register() { $login_string = "NICK ".$this->getClientConf('nick')."\r\n"."USER ".$this->getClientConf('ident')." "."localhost"." ".$this->getClientConf('server')." :".$this->getClientConf('realname'); if ($this->getClientConf('serverpassword') != "") $login_string = "PASS ".$this->getClientConf('serverpassword')."\r\n".$login_string; $validate = $this->clientFormat($login_string); $this->timeConnected = time(); $this->pushAfter($validate); $this->status = STATUS_CONNECTED_SENTREGDATA; $this->timerClass->addTimer($this->randomHash(), $this, "regTimeout", $this->sockInt, REGISTRATION_TIMEOUT); } public function regTimeout($sockInt) { if ($sockInt != $this->sockInt) { return false; } if ($this->status != STATUS_CONNECTED_SENTREGDATA) { return false; } $this->disconnect(); $this->status = STATUS_ERROR; $this->exception = new ConnectionTimeout("Session Authentication Timed out"); return false; } //The following function kills this irc object, but ONLY if there is no send queue. //A good way to use it is with a timer, say, ever second or so. public function shutDown() { if (!$this->conn->getError()) { return; } /* if ($this->socketClass->hasWriteQueue($this->sockInt)) { return true; } $this->disconnect(); */ $this->parserClass->destroyModules(); $this->dccClass->closeAll(); $this->procQueue->removeOwner($this); $date = date("h:i:s a, m-d-y"); $this->log("The bot successfully shutdown, $date"); //Okay.. now the bot is PSEUDO dead. It still exists, however it has no open sockets, it will not //attempt to reconnect to the server, and all dcc sessions, modules, timers, and processes related to it //have been destroyed. return false; } /* Some assorted timers */ public function checkNick() { if ($this->getStatusRaw() != STATUS_CONNECTED_REGISTERED) { return true; } if ($this->nick != $this->getClientConf('nick')) { $this->changeNick($this->getClientConf('nick')); } return true; } public function checkPingTimeout() { if ($this->getStatusRaw() != STATUS_CONNECTED_REGISTERED) { return true; } try { if ($this->lastPing == 1) { $this->lastPing = 0; throw new ConnectionTimeout("The connection with the server timed out."); } else { if (time() > $this->timeoutTimer + PING_TIMEOUT + $this->lagTime) { $this->pushBefore($this->clientFormat("PING :Lagtime")); $this->lastPingTime = time(); $this->lastPing = 1; } } } catch (ConnectionTimeout $e) { $this->disconnect(); $this->status = STATUS_ERROR; $this->exception = $e; } return true; } public function getStatusRaw() { return $this->status; } public function getLine() { return $this->lVars; } private function readInput() { if ($this->status != STATUS_CONNECTED_REGISTERED && $this->status != STATUS_CONNECTED_SENTREGDATA) { return false; } if ($this->socketClass->isDead($this->sockInt) && !$this->socketClass->hasLine($this->sockInt)) { $this->disconnect(); $this->status = STATUS_ERROR; $this->exception = new ReadException("Failed while reading from socket"); return false; } if ($this->socketClass->hasLine($this->sockInt)) { $this->timeoutTimer = time(); if ($this->lastPing == 1) { $this->lagTime = time() - $this->lastPingTime; } $this->lastPing = 0; $line = $this->socketClass->getQueueLine($this->sockInt); $this->stats['BYTESDOWN'] += strlen($line); $this->log($line); if (substr($line, 0, 1) != ":") { $line = ":Server " . $line; } $line = substr($line, 1); $parts = explode(chr(32), $line); $params = substr($line, strlen($parts[0]) + strlen($parts[1]) + strlen($parts[2]) + 3); if (strpos($params, " :")) { $params = substr($params, 0, strpos($params, " :")); } $offset1 = strpos($parts[0], '!'); $offset2 = $offset1 + 1; $offset3 = strpos($parts[0], '@') + 1; $offset4 = $offset3 - $offset2 - 1; $offset5 = strpos($line, " :") + 2; unset($this->lVars); $this->lVars = array( 'from' => $parts[0], 'fromNick' => substr($parts[0], 0, $offset1), 'fromIdent' => substr($parts[0], $offset2, $offset4), 'fromHost' => substr($parts[0], $offset3), 'cmd' => $parts[1], 'to' => $parts[2], 'text' => substr($line, $offset5), 'params' => trim($params), 'raw' => ":" . $line, ); if ($offset5 === false) { $line['text'] = ""; } if (intval($this->lVars['cmd']) > 0) { $this->parseServerMsgs($this->lVars['cmd']); } else { $this->parseMsgs(); } $this->parserClass->parseLine($this->lVars); } if ($this->socketClass->hasQueue($this->sockInt)) { return true; } return false; } private function parseServerMsgs($cmd) { switch ($cmd) { case 004: $this->status = STATUS_CONNECTED_REGISTERED; if ($this->tempNick != "") { $this->nick = $this->tempNick; } break; case 005: $this->parseServerConfig(); if (!isset($this->modeArray) || !is_array($this->modeArray) || count($this->modeArray) <= 0) { if ($this->getServerConf("CHANMODES") != "") { $this->createModeArray(); $this->checkChans(); } } break; case 311: $params = explode(chr(32), $this->lVars['params']); $this->setMemberData($params[0], $params[1], $params[2]); case 324: $params = explode(chr(32), $this->lVars['params']); $channel = $params[0]; $query = substr($this->lVars['params'], strlen($channel) + 1); $this->setChannelData($channel, "modes", $query); break; case 329: $params = explode(chr(32), $this->lVars['params']); $channel = $params[0]; $query = substr($this->lVars['params'], strlen($channel) + 1); $this->setChannelData($channel, "created", $query); break; case 332: $this->setChannelData(trim($this->lVars['params']), "topic", $this->lVars['text']); break; case 333: $params = explode(chr(32), $this->lVars['params']); $channel = $params[0]; $query = substr($this->lVars['params'], strlen($channel) + 1); $this->setChannelData($channel, "topicby", $query); break; case 352: $params = explode(chr(32), $this->lVars['params']); $this->changeMember($params[0], $params[4], $params[4], $params[1], "", "", $params[2]); break; case 353: $channel = substr($this->lVars['params'], 2); $this->updateOpList($channel); break; case 367: $params = explode(chr(32), $this->lVars['params']); $data = $this->getChannelData($params[0]); if ($data != NULL) { if ($data->banComplete == 1) { $this->clearBanList($params[0]); $data->banComplete = 0; } $this->changeChannel($params[0], "+", "b", array($params[1], $params[3])); } break; case 368: $params = explode(chr(32), $this->lVars['params']); $channel = $params[0]; $this->setChannelData($channel, "bancomplete", 1); break; case 401: $this->removeQueues($this->lVars['params']); break; case 433: if ($this->getStatusRaw() != STATUS_CONNECTED_REGISTERED) { if ($this->nick == $this->getClientConf('nick')) { $this->changeNick($this->nick . rand() % 1000); } $this->nickTimer = time(); } break; } } public function isOnline($nick, $chan) { return $this->isMode($nick, $chan, "online"); } private function updateOpList($channel) { $channel = $this->myStrToLower($channel); $users = explode(chr(32), $this->lVars['text']); if (!isset($this->prefixArray) || count($this->prefixArray) <= 0) { $this->createPrefixArray(); } foreach ($users AS $user) { if (trim($user) == "") { continue; } $userModes = ""; $userNick = ""; for ($currIndex = 0; $currIndex < strlen($user); $currIndex++) { $currChar = substr($user, $currIndex, 1); if (!isset($this->prefixArray[$currChar])) { $userNick = substr($user, $currIndex); break; } $userModes .= $currChar; } if ($userNick != $this->nick) { $this->addmember($channel, $userNick, "", $this->convertUserModes($userModes)); } } } private function convertUserModes($modes) { $newModes = ""; for ($index = 0; $index < strlen($modes); $index++) { $newModes .= $this->prefixArray[$modes[$index]]; } return $newModes; } private function createPrefixArray() { $modeSymbols = substr($this->getServerConf('PREFIX'), strpos($this->getServerConf('PREFIX'), ")") + 1); $leftParan = strpos($this->getServerConf('PREFIX'), "("); $rightParan = strpos($this->getServerConf('PREFIX'), ")"); $modeLetters = substr($this->getServerConf('PREFIX'), $leftParan + 1, $rightParan - $leftParan - 1); for ($index = 0; $index < strlen($modeLetters); $index++) { $this->prefixArray[$modeSymbols[$index]] = $modeLetters[$index]; } } public function doMode() { $this->modeQueueAdded = false; $currAct = ""; $currChan = ""; $modeLineModes = ""; $modeLineParams = ""; $maxModesPerLine = ($this->getServerConf('MODES') == "" ? 1 : $this->getServerConf('MODES')); $currLineModes = 0; foreach($this->modeQueue AS $modeChange) { if ($modeLineModes != "" && ($currChan != $modeChange['CHANNEL'] || $currLineModes >= $maxModesPerLine)) { $this->pushAfter($this->clientFormat("MODE " . $currChan . " " . $modeLineModes . " " . trim($modeLineParams))); $modeLineModes = ""; $currAct = ""; $currChan = ""; $modeLineParams = ""; $currLineModes = 0; } if ($currAct != $modeChange['ACT']) { $modeLineModes .= $modeChange['ACT']; } $modeLineModes .= $modeChange['MODE']; if ($modeChange['USER'] != "") { $modeLineParams .= $modeChange['USER'] . " "; } $currLineModes++; $currAct = $modeChange['ACT']; $currChan = $modeChange['CHANNEL']; } if ($modeLineModes != "") { $this->pushAfter($this->clientFormat("MODE " . $currChan . " " . $modeLineModes . " " . trim($modeLineParams))); } unset($this->modeQueue); $this->modeQueue = array(); $this->modeQueueLength = 0; return false; } public function changeMode($chan, $act, $mode, $user) { $user = trim($user); $chan = trim($chan); $act = trim($act); $mode = trim($mode); if ($chan == "" || $mode == "") { return false; } if (!($act == "+" || $act == "-")) { return false; } if (strlen($mode) > 1) { return false; } if (!isset($this->modeArray[$mode])) { if ($user == "") { return false; } } if ($this->modeQueueAdded != true) { $this->timerClass->addTimer("mode_timer", $this, "doMode", "", 0, true); $this->modeQueueAdded = true; } $this->modeQueue[] = array('USER' => $user, 'CHANNEL' => $chan, 'ACT' => $act, 'MODE' => $mode); $this->modeQueueLength++; return true; } public function parseModes($modeString) { $modeString .= " "; $offset = strpos($modeString, chr(32)); $modes = substr($modeString, 0, $offset); $users = substr($modeString, $offset + 1); $userArray = explode(chr(32), $users); if (count($this->modeArray) <= 0) { $this->createModeArray(); } $action = ""; $returnModes = array(); while (trim($modes) != "") { $thisMode = substr($modes, 0, 1); $modes = substr($modes, 1); if ($thisMode == "-" || $thisMode == "+") { $action = $thisMode; continue; } if (strpos($this->getServerConf('CHANMODES'), $thisMode) !== false) { if (!isset($this->modeArray[$thisMode])) { return false; } $type = $this->modeArray[$thisMode]; $extra = ""; if ($type != BY_NONE) { $extra = array_shift($userArray); } $type = CHANNEL_MODE; } else { $extra = array_shift($userArray); $type = USER_MODE; } $returnModes[] = array( 'ACTION' => $action, 'MODE' => $thisMode, 'EXTRA' => $extra, 'TYPE' => $type, ); } return $returnModes; } public static function intToSizeString($size) { $i = 20; while ($size > pow(2, $i)) { $i += 10; } switch ($i) { case 20: //kb $num = $size / 1000; $type = "KB"; break; case 30: //mb $num = $size / 1000000; $type = "MB"; break; case 40: //gb $num = $size / 1000000000; $type = "GB"; break; case 50: //tb $num = $size / 1000000000000; $type = "TB"; break; default: //pb $num = $size / 1000000000000000; $type = "PB"; break; } $stringSize = round($num, 2) . $type; return $stringSize; } public function checkIgnore($mask) { $ignore = $this->getClientConf('ignore'); if ($ignore == "") { return false; } if (!is_array($ignore)) { $ignore = array($ignore); } foreach($ignore AS $ig) { $case = $this->hostMasksMatch($mask, $ig); if ($case) { return true; } } return false; } private function parseMsgs() { switch($this->lVars['cmd']) { case "JOIN": $chan = $this->lVars['to']; if (substr($this->lVars['to'], 0, 1) == ":") { $chan = substr($this->lVars['to'], 1); } $this->addMember($chan, $this->lVars['fromNick'], $this->lVars['fromIdent'], "", $this->lVars['fromHost']); if ($this->lVars['fromNick'] == $this->getNick()) { $this->sendRaw("MODE " . $chan); if (isset($this->clientConfig['populatebans'])) { $this->sendRaw("MODE " . $chan . " +b"); } if (isset($this->clientConfig['populatewho'])) { $this->sendRaw("WHO " . $chan); } } break; case "PART": if ($this->lVars['fromNick'] == $this->nick) { $this->removeChannel($this->lVars['to']); } else { $this->removeMember($this->lVars['to'], $this->lVars['fromNick']); } break; case "QUIT": if ($this->lVars['fromNick'] == $this->nick) { $this->purgeChanList(); } else { $this->removeMember("", $this->lVars['fromNick']); } break; case "NICK": if ($this->lVars['fromNick'] == $this->nick) { $this->nick = $this->lVars['text']; } $this->changeMember("", $this->lVars['fromNick'], $this->lVars['text'], "", "", ""); break; case "KICK": if ($this->myStrToLower($this->lVars['params']) == $this->myStrToLower($this->nick)) { $this->removeChannel($this->lVars['to']); $this->joinChannel($this->lVars['to']); } else { $this->removeMember($this->lVars['to'], $this->lVars['params']); } break; case "MODE": $channel = $this->myStrToLower($this->lVars['to']); if ($channel == $this->myStrToLower($this->nick)) break; $modes = $this->parseModes($this->lVars['params']); foreach($modes AS $mode) { if ($mode['TYPE'] == CHANNEL_MODE) { $this->changeChannel($channel, $mode['ACTION'], $mode['MODE'], $mode['EXTRA']); } else { $this->changeMember($channel, $mode['EXTRA'], $mode['EXTRA'], "", $mode['MODE'], $mode['ACTION']); } unset($mode); } unset($modes); break; case "NOTICE": if ($this->checkIgnore($this->lVars['from'])) { return; } if ($this->myStrToLower($this->lVars['fromNick']) == "nickserv") { if (strpos($this->myStrToLower($this->lVars['text']), "identify") !== false) { if ($this->getClientConf('password') != "") { $this->pushBefore($this->clientFormat("PRIVMSG HostServ :ON")); $this->pushBefore($this->clientFormat("PRIVMSG NickServ :IDENTIFY " . $this->getClientConf('password'))); $this->pushBefore($this->clientFormat("MODE " . $this->getClientConf('nick') . " +B")); } } } break; case "PRIVMSG": if ($this->checkIgnore($this->lVars['from'])) { return; } if (strpos($this->lVars['text'], chr(1)) !== false) { $this->parseCtcp(); } break; case "TOPIC": $this->setChannelData($this->lVars['to'], "topic", $this->lVars['text']); break; case "PING": if ($this->lVars['from'] == "Server") { $this->pushBefore($this->clientFormat("PONG :" . $this->lVars['text'])); } default: break; } } private function parseCtcp() { $cmd = str_replace(chr(1), "", $this->lVars['text']) . " "; $query = trim(substr($cmd, strpos($cmd, chr(32)) + 1)); $cmd = substr($this->myStrToLower($cmd), 0, strpos($cmd, chr(32))); $msg = ""; switch($cmd) { case "version": // PLEASE DO NOT CHANGE THE FOLLOWING LINE OF CODE. It is the only way for people to know that this project // exists. If you would like to change it, please leave the project name/version or url in there somewhere, // so that others may find this project as you have. :) $msg = "PHP-iRC v" . VERSION . " [".VERSION_DATE."] by Manick (visit http://www.phpbots.org/ to download)"; $this->notice($this->lVars['fromNick'], chr(1) . $msg . chr(1)); $msg = ""; $this->showModules($this->lVars['fromNick']); break; case "time": $msg = "My current time is " . date("l, F jS, Y @ g:i a O", time()) . "."; break; case "uptime": $msg = "My uptime is " . $this->timeFormat($this->getRunTime(), "%d days, %h hours, %m minutes, and %s seconds."); break; case "ping": $msg = "PING " . $query; } if ($msg != "") { $this->notice($this->lVars['fromNick'], chr(1) . $msg . chr(1)); } } //Split huge lines up by spaces 255 by default public static function multiLine($text, $separator = " ") { $returnArray = array(); $text = trim($text); $strlen = strlen($text); $sepSize = strlen($separator); while ($strlen > 0) { if (256 > $strlen) { $returnArray[] = $text; break; } for ($i = 255; $i > 0; $i--) { if (substr($text, $i, $sepSize) == $separator) { break; } } if ($i <= 0) { $returnArray[] = substr($text, 0, 255); $text = substr($text, 254); $strlen -= 255; } else { $returnArray[] = substr($text, 0, $i); $text = substr($text, $i - 1); $strlen -= $i; } } return $returnArray; } private function showModules($nick) { $cmdList = $this->parserClass->getCmdList(); if (isset($cmdList['file'])) { $mod = ""; foreach($cmdList['file'] AS $module) { $class = $module['class']; if (isset($class->dontShow) && $class->dontShow == true) { continue; } $mod .= "[" . $class->title . " " . $class->version . "] "; } if ($mod != "") { $modArray = $this->multiLine("Running Modules: " . $mod); foreach ($modArray AS $myMod) { $this->notice($nick, chr(1) . $myMod . chr(1)); } } } unset($cmdList); } public function getRunTime() { return (time() - $this->startTime); } public static function timeFormat($time, $format) { $days = 0; $seconds = 0; $minutes = 0; $hours = 0; if (strpos($format, "%d") !== FALSE) { $days = (int) ($time / (3600 * 24)); $time -= ($days * (3600 * 24)); } if (strpos($format, "%h") !== FALSE) { $hours = (int) ($time / (3600)); $time -= ($hours * (3600)); } if (strpos($format, "%m") !== FALSE) { $minutes = (int) ( $time / (60)); $time -= ($minutes * (60)); } $seconds = $time; $format = str_replace("%d", $days, $format); $format = str_replace("%s", $seconds, $format); $format = str_replace("%m", $minutes, $format); $format = str_replace("%h", $hours, $format); return $format; } private function createModeArray() { $modeArray = explode(",", $this->getServerConf('CHANMODES')); for ($i = 0; $i < count($modeArray); $i++) { for ($j = 0; $j < strlen($modeArray[$i]); $j++) { $this->modeArray[$modeArray[$i][$j]] = $i; } } } public function checkChans() { if ($this->getStatusRaw() != STATUS_CONNECTED_REGISTERED) { return true; } foreach ($this->maintainChannels AS $index => $channel) { if ($this->isOnline($this->nick, $channel['CHANNEL']) === false) { if ($channel['KEY'] != "") { $this->joinChannel($channel['CHANNEL'] . " " . $channel['KEY']); } else { $this->joinChannel($channel['CHANNEL']); } } } return true; } public function getStatusString($status) { $msg = ""; switch ($status) { case STATUS_IDLE: $msg = "Idle"; break; case STATUS_ERROR: $msg = "Error"; break; case STATUS_CONNECTING: $msg = "Connecting to server..."; break; case STATUS_CONNECTED: $msg = "Connected to server: " . $this->getClientConf('server') . " " . $this->getClientConf('port'); break; case STATUS_CONNECTED_SENTREGDATA: $msg = "Sent registration data, awaiting reply..."; break; case STATUS_CONNECTED_REGISTERED: $msg = "Authenticated"; break; default: $msg = "Unknown"; } return $msg; } public function purgeMaintainList() { unset($this->maintainChannels); $this->maintainChannels = array(); } public function removeMaintain($channel) { $channel = $this->myStrToLower($channel); foreach ($this->maintainChannels AS $index => $chan) { if ($chan['CHANNEL'] == $channel) { unset($this->maintainChannels[$index]); break; } } } public function maintainChannel($channel, $key = "") { $channel = $this->myStrToLower($channel); $this->maintainChannels[] = array('CHANNEL' => $channel, 'KEY' => $key); } public function joinChannel($chan) { $this->pushBefore($this->clientFormat("JOIN " . $chan)); } public function changeNick($nick) { $this->pushBefore($this->clientFormat("NICK " . $nick)); $this->tempNick = $nick; } private function parseServerConfig() { $args = explode(chr(32), $this->lVars['params']); foreach ($args AS $arg) { if (strpos($arg, "=") === false) { $arg .= "=1"; } $argParts = explode("=", $arg); $this->serverConfig[$argParts[0]] = $argParts[1]; } } private function clientFormat($text) { return array("USER" => "*", "TEXT" => $text); } public function removeQueues($nick) { $nick = $this->myStrToLower($nick); foreach ($this->textQueue AS $index => $queue) { if ($this->myStrToLower($queue['USER']) == $nick) { unset($this->textQueue[$index]); $this->textQueueLength--; } } } public function getStats() { return $this->stats; } public function doQueue() { if ($this->status < STATUS_CONNECTED) { $this->textQueueAdded = false; return false; } if ($this->socketClass->hasWriteQueue($this->sockInt) !== false) { return true; } if ($this->textQueueLength < 0) { if (is_array($this->textQueue)) { unset($this->textQueue); $this->textQueue = array(); } $this->textQueueAdded = false; return false; } $bufferSize = $this->getClientConf("queuebuffer"); $bufferSize = $bufferSize <= 0 ? 0 : $bufferSize; $sendData = ""; $nextItem = array_shift($this->textQueue); if (trim($nextItem['TEXT']) != "") { $sendData .= $nextItem['TEXT'] . "\r\n"; } unset($nextItem); $this->textQueueLength--; while ($this->textQueueLength > 0 && ((strlen($this->textQueue[0]['TEXT']) + strlen($sendData)) < $bufferSize)) { $nextItem = array_shift($this->textQueue); if (trim($nextItem['TEXT']) != "") { $sendData .= $nextItem['TEXT'] . "\r\n"; unset($nextItem); } $this->textQueueLength--; } $this->stats['BYTESUP'] += strlen($sendData); $this->writeToSocket($sendData); unset($sendData); return true; } private function writeToSocket($sendData) { if (DEBUG == 1) { $this->log($sendData); } try { if ($this->socketClass->sendSocket($this->sockInt, $sendData) === false) { throw new SendDataException("Could not write to socket"); } } catch (SendDataException $e) { $this->disconnect(); $this->exception = $e; $this->status = STATUS_ERROR; } } private function pushAfter($data) { $this->textQueueLength++; $this->textQueue[] = $data; if ($this->textQueueAdded == false) { $this->timerClass->addTimer("queue_timer", $this, "doQueue", "", $this->getQueueTimeout(), true); $this->textQueueAdded = true; } } private function pushBefore($data) { $this->textQueueLength++; $this->textQueue = array_merge(array($data), $this->textQueue); if ($this->textQueueAdded == false) { $this->timerClass->addTimer("queue_timer", $this, "doQueue", "", $this->getQueueTimeout(), true); $this->textQueueAdded = true; } } public function sendRaw($text, $force = false) { if ($force == false) { $format = $this->clientFormat($text); $this->pushBefore($format); } else { $this->writeToSocket($text . "\r\n"); } } public function privMsg($who, $msg, $queue = 1) { $text = array( 'USER' => $who, 'TEXT' => 'PRIVMSG ' . $who . ' :' . $msg); if ($queue) { $this->pushAfter($text); } else { $this->pushBefore($text); } } public function action($who, $msg, $queue = 1) { $text = array( 'USER' => $who, 'TEXT' => 'PRIVMSG ' . $who . ' :' . chr(1) . 'ACTION ' .$msg . chr(1)); if ($queue) { $this->pushAfter($text); } else { $this->pushBefore($text); } } public function notice($who, $msg, $queue = 1) { $text = array( 'USER' => $who, 'TEXT' => 'NOTICE ' . $who . ' :' . $msg); if ($queue) { $this->pushAfter($text); } else { $this->pushBefore($text); } } public function getClientIP($long = 1) { if ($long == 1) { return $this->clientLongIP; } else { return $this->clientIP; } } public function setClientIP($ip = "") { if ($ip == "") { $ip = $this->socketClass->getHost($this->sockInt); } $this->clientIP = $ip; $this->clientLongIP = ip2long($this->clientIP); if ($this->clientLongIP <= 0) { $this->clientLongIP += pow(2,32); } } public function purgeTextQueue() { $this->textQueueTime = 0; unset($this->textQueue); $this->textQueue = array(); $this->textQueueLength = 0; } public function getTextQueueLength() { return $this->textQueueLength; } public function log($data) { $network = $this->getServerConf('Network') == "" ? $this->getClientConf('server') : $this->getServerConf('Network'); if (DEBUG == 1) { echo "[".date("h:i:s")."] " . "({$this->nick}@$network) > " . $data . "\n"; } else { if ($this->getClientConf('logfile') != "") { error_log("[".date("h:i:s")."] " . "({$this->nick}@$network) > " . $data . "\n", 3, $this->getClientConf('logfile')); } } } public function getUsageList() { return $this->usageList; } public function floodCheck($line) { $host = $line['fromHost']; if (!array_key_exists($host, $this->usageList)) { $this->usageList[$host] = new usageLink; $this->usageList[$host]->isBanned = false; $this->usageList[$host]->lastTimeUsed = time(); $this->usageList[$host]->timesUsed = 1; $user = $this->usageList[$host]; } else { $user = $this->usageList[$host]; $floodTime = intval($this->getClientConf('floodtime')); if ($floodTime <= 0) { $floodTime = 60; } if ($user->isBanned == true) { if ($user->timeBanned > time() - $floodTime) { return STATUS_ALREADY_BANNED; } $user->isBanned = false; } if ($user->lastTimeUsed < time() - 10) { $user->timesUsed = 0; } $user->lastTimeUsed = time(); $user->timesUsed++; } $numLines = intval($this->getClientConf('floodlines')); if ($numLines <= 0) { $numLines = 5; } if ($user->timesUsed > $numLines) { $user->isBanned = true; $user->timeBanned = time(); $user->timesUsed = 0; $user->lastTimeUsed = 0; $this->removeQueues($line['fromNick']); return STATUS_JUST_BANNED; } return STATUS_NOT_BANNED; } public static function myStrToLower($text) { $textA = strtolower($text); $textA = str_replace("\\", "|", $textA); $textA = str_replace("[", "{", $textA); $textA = str_replace("]", "}", $textA); $textA = str_replace("~", "^", $textA); return $textA; } public static function myStrToUpper($text) { $textA = strtoupper($text); $textA = str_replace("|", "\\", $textA); $textA = str_replace("{", "[", $textA); $textA = str_replace("}", "]", $textA); $textA = str_replace("^", "~", $textA); return $textA; } public static function randomHash() { return md5(uniqid(rand(), true)); } private function getQueueTimeout() { $timeout = $this->getClientConf("queuetimeout"); $timeout = $timeout <= 0 ? 0 : $timeout; return $timeout; } public function addQuery($host, $port, $query, $line, $class, $function) { $remote = new remote($host, $port, $query, $line, $class, $function, 8); $remote->setIrcClass($this); $remote->setTimerClass($this->timerClass); $remote->setSocketClass($this->socketClass); return $remote->connect(); } }