parser 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 parser { private $cmd; private $args; private $timers = array(); private $cmdList = array(); private $cmdTypes = array(); private $fileModified = array(); private $loadDefError = false; //Classes private $ircClass; private $dccClass; private $timerClass; private $socketClass; private $db; /* Part of easy alias idea I've been working on..., look below for large bit of commented out code for info... private $aliasArray = array( "notice" => "ircClass", "privMsg" => "ircClass", "action" => "ircClass", "sendRaw" => "ircClass", "getNick" => "ircClass", "isOnline" => "ircClass", "isMode" => "ircClass", "isChanMode" => "ircClass", "sendFile" => "dccClass", "dccInform" => "dccClass", "addTimer" => "timerClass", "addListener" => "socketClass", "removeTimer" => "timerClass", ); */ public function __construct() { $this->fileModified = array(); } public function init() { if ($this->ircClass->getClientConf('functionfile') != "") { $this->loadFuncs($this->ircClass->getClientConf('functionfile')); } } public function setDccClass($class) { $this->dccClass = $class; } public function setIrcClass($class) { $this->ircClass = $class; } public function setTimerClass($class) { $this->timerClass = $class; } public function setSocketClass($class) { $this->socketClass = $class; } public function setDatabase($class) { $this->db = $class; } public function getCmdList($type = "") { if ($type == "") { return $this->cmdList; } else { if (isset($this->cmdList[$type])) { return $this->cmdList[$type]; } else { return false; } } } public function destroyModules() { if (is_array($this->cmdList) && count($this->cmdList) > 0) { if (isset($this->cmdList['file']) && is_array($this->cmdList['file'])) { foreach ($this->cmdList['file'] AS $index => $data) { if (is_object($data['class'])) { $data['class']->destroy(); } } } } } private function readFile($file) { $configRaw = file_get_contents($file); if ($configRaw === false) { if (DEBUG == 1) { echo "Could not find function file '$file' or error.\n"; } $this->dccClass->dccInform("Could not find function file '$file' or error."); return false; } return $configRaw; } public function include_recurse($file) { $configRaw = $this->readFile($file); if ($configRaw === false) { return false; } $configRaw = preg_replace("/;(.*\n?)?/", "\n", $configRaw); $configRaw = preg_replace("/~.*\n/", "", $configRaw); $configRaw = trim($configRaw); $lines = explode("\n", $configRaw); $num = 0; $lineNo = 1; $extra = 0; $fullLine = ""; foreach ($lines as $line) { $line = trim($line); if ($line == "") { $lineNo++; continue; } $line = trim($fullLine . " " . $line); $newLine = $this->parseFunc($file, $lineNo, $line); $lineNo += $extra + 1; $extra = 0; $fullLine = ""; if ($newLine === false) { continue; } if ($newLine['type'] == "type") { $this->cmdTypes[$newLine['typeArray']['name']]['numArgs'] = count($newLine['typeArray']); $this->cmdTypes[$newLine['typeArray']['name']]['args'] = $newLine['typeArray']; } else if ($newLine['type'] == "include") { if (isset($newLine['typeArray'][0])) { $num += $this->include_recurse($newLine['typeArray'][0]); } else { if (DEBUG == 1) { echo "Malformed include line on line " . $lineNo . " of function file: " . $file . "\n"; } $this->dccClass->dccInform("Malformed include line on line " . $lineNo . " of function file: " . $file); } } else { if (!isset($newLine['typeArray']['name'])) { $name = irc::randomHash() . "_" . rand(1,1024); } else { $name = $newLine['typeArray']['name']; } unset($newLine['typeArray']['name']); $this->cmdList[$newLine['type']][$name] = $newLine['typeArray']; if (isset($this->cmdList[$newLine['type']][$name]['usage'])) { if (isset($oldCmdList[$newLine['type']][$name]['usage'])) { $this->cmdList[$newLine['type']][$name]['usage'] = $oldCmdList[$newLine['type']][$name]['usage']; } } } $num++; } return $num; } public function loadFuncs($file) { $error = false; clearstatcache(); $this->destroyModules(); $oldCmdList = $this->cmdList; if (!is_array($oldCmdList)) { $oldCmdList = array(); } unset($this->cmdList); unset($this->cmdTypes); $this->cmdList = array(); $this->cmdTypes = array(); $this->dccClass->dccInform("Rehashing Function File, please wait..."); if (DEBUG == 1) { echo "Rehashing Function File, please wait...\n"; } //Read in main function file $num = $this->include_recurse($file); //Book keeping foreach ($this->cmdList AS $cmd => $data) { ksort($this->cmdList[$cmd]); } if (isset($this->cmdList['file'])) { foreach ($this->cmdList['file'] AS $file => $data) { $classDef = $this->loadClassDef($data['filename'], $file); if ($this->loadDefError == true) { $error = true; } $this->loadDefError = false; if ($classDef === false) { continue; } //require_once($data['filename']); $this->cmdList['file'][$file]['class'] = new $classDef; $this->cmdList['file'][$file]['class']->__setClasses( $this->ircClass, $this->dccClass, $this->timerClass, $this, $this->socketClass, $this->db ); $this->cmdList['file'][$file]['class']->init(); } } $this->dccClass->dccInform("Successfully loaded " . $num . " functions into memory."); if (DEBUG == 1) { echo "Successfully loaded " . $num . " functions into memory.\n"; } return $error; } private function loadClassError($filename, $msg) { $this->ircClass->log("Error loading $filename: $msg"); $this->dccClass->dccInform("Error loading $filename: $msg"); } /* Okay.. * This method is really freaking cool. It loads a module file with fopen, then * it finds the classname, and gives it a random name (changes it) so that you can * include functions you've changed multiple times without having to restart the bot! * Also, easier names than having to use $this->ircClass->blah() all the time, you can * just use blah()... These are defined in an array somewhere... I'm not sure where yet... * because I haven't written that yet!!!!!!!!!!!!111oneone. */ private function loadClassDef($filename, $classname) { $stat = stat($filename); if ($stat === false) { $this->loadClassError($filename, "Could not find function file"); return false; } $modified = $stat['mtime']; if (isset($this->fileModified[$filename])) { if ($modified == $this->fileModified[$filename]['modified']) { return $this->fileModified[$filename]['classdef']; } } $fileData = file_get_contents($filename); if ($fileData === false) { $this->loadClassError($filename, "Could not find function file"); return false; } //Okay, we have the module now.. now we need to find some stuff. if (!preg_match("/class[\s]+?".$classname."[\s]+?extends[\s]+?module[\s]+?{/", $fileData)) { $this->loadClassError($filename, "Could not find valid classdef in function file"); return false; } //Okay, our module is in the file... replace it with random hash. $newHash = irc::randomHash(); $newClassDef = $classname . "_" . $newHash; $fileData = preg_replace("/(class[\s]+?)".$classname."([\s]+?extends[\s]+?module[\s]+?{)/", "\\1" . $newClassDef . "\\2", $fileData); /* Interesting idea, but lets leave it out for now foreach($this->aliasArray AS $func => $class) { $fileData = preg_replace("/([=\n\(\t\s]+?)".$func."[\s\t\n\r]*?\(/s", "\\1\$this->" . $class . "->" . $func . "(", $fileData); } */ $success = eval("?>" . $fileData . "loadClassError($filename, "Error in function file"); /* Attempt to fallback on a previous revision that worked! */ if (isset($this->fileModified[$filename])) { $this->loadClassError($filename, "Using a cached version of the class definition"); $this->loadDefError = true; return $this->fileModified[$filename]['classdef']; } return false; } $this->fileModified[$filename]['modified'] = $modified; $this->fileModified[$filename]['classdef'] = $newClassDef; return $newClassDef; } //Used to show array, as there seems to be some crazy bug in var_dump/print_r that //shows EVERY variable in my program when I do var_dump($this->cmdList) or use print_r the same way //This isn't used anywhere in the production copy of this script. (DEBUG ONLY!) private function show_all($title, $array, $level) { echo $title . " = array(" . "\r\n"; foreach($array AS $index => $val) { for ($i = 0; $i < $level; $i++) { echo " "; } if (is_array($val)) { $this->show_all($index, $val, $level + 1); } else if (is_object($val)) { echo "[$index] => [object]\r\n"; } else { echo "[$index] => [$val]\r\n"; } } for ($i = 0; $i < $level; $i++) { echo " "; } echo ")\r\n"; } public function setCmdListValue($type, $cmd, $var, $value) { if (isset($this->cmdList[$type][$cmd][$var])) { $this->cmdList[$type][$cmd][$var] = $value; return true; } return false; } private function parseFunc($file, $lineNo, $line) { $strings = array(); $line = str_replace("\t", " ", $line); $quotes = array("'", "\""); foreach($quotes AS $quote) { $currPos = 0; $extraPos = 0; while (($firstPos = strpos($line, $quote, $currPos)) !== false && substr($line, strpos($line, $quote, $currPos)-1, 1) != "\\") { while (($secondPos = strpos($line, $quote, $firstPos + 1 + $extraPos)) !== false && substr($line, strpos($line, $quote, $firstPos + 1 + $extraPos)-1, 1) == "\\") { $extraPos = $secondPos; } if ($secondPos === false) { if (DEBUG == 1) { echo "Syntax Error on line " . $lineNo . " of function file: " . $file . ". Expected '".$quote."', got end of line.\n"; } $this->dccClass->dccInform("Syntax Error on line " . $lineNo . " of function file: " . $file . ". Expected '".$quote."', got end of line."); return false; } $strings[$quote][] = substr($line, $firstPos + 1, $secondPos - $firstPos - 1); $currPos = $secondPos + 1; } } foreach ($strings AS $string) { $line = str_replace($string, "", $line); } $lineElements = explode(chr(32), $line); $type = ""; $currElement = 0; $typeArray = array(); foreach($lineElements AS $element) { if (trim($element) == "") { continue; } $currElement++; if ($currElement == 1) { $element = irc::myStrToLower($element); if ($element == "type") { $type = "type"; } else if ($element == "include") { $type = "include"; } else { if (isset($this->cmdTypes[$element])) { $type = $element; } else { if (DEBUG == 1) { echo "Error: Undefined type, '" . $element . "' on line " . $lineNo . " of function file: " . $file . "\n"; } $this->dccClass->dccInform("Error: Undefined type, '" . $element . "' on line " . $lineNo . " of function file: " . $file . ""); return false; } } continue; } if ($element == "\"\"") { $element = array_shift($strings["\""]); } else if ($element == "''") { $element = array_shift($strings["'"]); } $element = str_replace("\\" . "'", "'", $element); $element = str_replace("\\" . '"', '"', $element); if ($type == "type") { if ($currElement == 2) { $typeArray['name'] = $element; } else { $typeArray[] = $element; } } else if ($type == "include") { $typeArray[] = $element; } else { if ($currElement > $this->cmdTypes[$type]['numArgs']) { if (DEBUG == 1) { echo "Error on line " . $lineNo . " of function file: " . $file . ", too many arguments\n"; } $this->dccClass->dccInform("Error on line " . $lineNo . " of function file: " . $file . ", too many arguments"); return false; } $element = (irc::myStrToLower($element) == "true" ? true : $element); $element = (irc::myStrToLower($element) == "false" ? false : $element); $typeArray[$this->cmdTypes[$type]['args'][ $currElement - 2 ]] = $element; } } if ($type != "type" && $type != "include") { if ($currElement < $this->cmdTypes[$type]['numArgs']) { if (DEBUG == 1) { echo "Error on line " . $lineNo . " of function file: " . $file . ", not enough arguments\n"; } $this->dccClass->dccInform("Error on line " . $lineNo . " of function file: " . $file . ", not enough arguments"); return false; } } return array('type' => $type, 'typeArray' => $typeArray); } public function parseDcc($chat, $handler) { $chat->readQueue = str_replace("\r", "", $chat->readQueue); // if (!($offSet = strpos($chat->readQueue, "\n"))) // { // return false; // } // $rawLine = trim(substr($chat->readQueue, 0, $offSet)); // $chat->readQueue = substr($chat->readQueue, $offSet + 1); $rawLine = $chat->readQueue; $chat->readQueue = ""; $this->ircClass->log("DCC Chat(" . $chat->nick . "): " . $rawLine); $line = $this->createLine($rawLine); if ($line == false) { return; } if ($handler != false) { if (is_object($handler)) { $handler->handle($chat, $line); return; } } if ($chat->isAdmin == true && $chat->verified == false) { if (md5($line['cmd']) == $this->ircClass->getClientConf('dccadminpass')) { $this->dccClass->dccInform("DCC: " . $chat->nick . " has successfully logged in."); $chat->verified = true; $chat->dccSend("You have successfully logged in."); } else { $chat->dccSend("Invalid password, bye bye."); $this->dccClass->disconnect($chat); } return; } $cmdLower = irc::myStrToLower($line['cmd']); if (isset($this->cmdList['dcc'][$cmdLower])) { if ($this->cmdList['dcc'][$cmdLower]['admin'] == 1 && !$chat->isAdmin) { $chat->dccSend("Request Denied. You must have admin access to use this function."); return; } if ($line['nargs'] < $this->cmdList['dcc'][$cmdLower]['numArgs']) { $chat->dccSend("Usage: " . $cmdLower . " " . $this->cmdList['dcc'][$cmdLower]['usage']); return; } $module = $this->cmdList['dcc'][$cmdLower]['module']; $class = $this->cmdList['file'][$module]['class']; $func = $this->cmdList['dcc'][$cmdLower]['function']; $class->$func($chat, $line); if ($chat->isAdmin) { $chat->dccSend("ADMIN " . irc::myStrToUpper($cmdLower) . " Requested"); } else { $chat->dccSend("CLIENT " . irc::myStrToUpper($cmdLower) . " Requested"); } } else { $chat->dccSend("Invalid Command: " . $line['cmd']); } } public static function createLine($rawLine) { $line = array(); $rawLineArray = explode(chr(32), $rawLine); $lineCount = count($rawLineArray); if ($lineCount < 1) { return false; } else if ($lineCount == 1) { $line['cmd'] = irc::myStrToLower($rawLine); $line['nargs'] = 0; $line['query'] = ""; } else { $line['nargs'] = 0; $line['cmd'] = irc::myStrToLower(array_shift($rawLineArray)); while (($arg = array_shift($rawLineArray)) !== NULL) // NULL fixed contributed by cortex, 05/01/05 { if (trim($arg) == "") { continue; } $line['arg' . ++$line['nargs']] = $arg; if ($line['nargs'] > MAX_ARGS-1) { break; } } $line['query'] = trim(substr($rawLine, strlen($line['cmd']) + 1)); } return $line; } public function parseLine($line) { if (DEBUG==1) { //print_r($line); } if ($this->ircClass->checkIgnore($line['from'])) { return; } switch($line['cmd']) { case "PRIVMSG": $args = $this->createLine($line['text']); $cmdLower = irc::myStrToLower($args['cmd']); if (isset($this->cmdList['priv'][$cmdLower])) { if ($this->cmdList['priv'][$cmdLower]['active'] == true) { $theCase = $this->ircClass->floodCheck($line); switch ($theCase) { case STATUS_NOT_BANNED: if ($this->cmdList['priv'][$cmdLower]['inform'] == true) { $this->dccClass->dccInform("Sending " . irc::myStrToUpper($line['text']) . " to " . $line['fromNick']); } $this->cmdList['priv'][$cmdLower]['usage']++; $func = $this->cmdList['priv'][$cmdLower]['function']; $module = $this->cmdList['priv'][$cmdLower]['module']; $class = $this->cmdList['file'][$module]['class']; if ($this->ircClass->getTextQueueLength() > 5) { $this->ircClass->notice($line['fromNick'], "Request Queued. Please wait " . $this->ircClass->getTextQueueLength() . " seconds for your data.", 0); } $class->$func($line, $args); break; case STATUS_JUST_BANNED: $this->ircClass->notice($line['fromNick'], "Flood Detected. All of your queues have been discarded and you have been banned from using this bot for ". $this->ircClass->getClientConf('floodtime') . " seconds."); $this->dccClass->dccInform("BAN: (*!". irc::myStrToUpper($line['fromHost']) . "): " . $line['fromNick'] . " is on ignore for " . $this->ircClass->getClientConf('floodtime') . " seconds."); break; case STATUS_ALREADY_BANNED: break; } } else { $this->dccClass->dccInform("FUNCTION: " . $line['fromNick'] . " attempted to use deactivated command '" . $cmdLower . "'"); } } else { if ($line['to'] == $this->ircClass->getNick()) { if (strpos($line['text'], chr(1)) !== false) { $this->ircClass->floodCheck($line); $this->parseCtcp($line); } else { $this->dccClass->dccInform("PRIVMSG: <" . $line['fromNick'] . "> " . $line['text']); } } else { if (strpos($line['text'], chr(1)) !== false) { $this->parseCtcp($line, "CHAN: " . $line['to']); } else { $chanData = $this->ircClass->getChannelData($line['to']); if ($chanData == NULL) { $this->dccClass->dccInform("CHAN PRIVMSG [".$line['to']."]: <" . $line['fromNick'] . "> " . $line['text']); } } } } break; case "MODE": break; case "NOTICE": $chan = $line['to'] != $this->ircClass->getNick() ? ":" . $line['to'] : ""; $this->dccClass->dccInform("NOTICE: <" . ($line['fromNick'] == "" ? $line['from'] : $line['fromNick']) . $chan . "> " . $line['text']); break; case "JOIN": if ($line['fromNick'] == $this->ircClass->getNick()) { $this->dccClass->dccInform("Joined: " . irc::myStrToUpper($line['text'])); } break; case "PART": if ($line['fromNick'] == $this->ircClass->getNick()) { $this->dccClass->dccInform("Parted: " . irc::myStrToUpper($line['to'])); } break; case "KICK": if ($line['params'] == $this->ircClass->getNick()) { $this->dccClass->dccInform("Kicked: " . $line['fromNick'] . " kicked you from " . $line['to']); } break; case "ERROR": $this->dccClass->dccInform("Server Error: " . $line['text']); break; case "366": $params = explode(chr(32), $line['params']); $channel = $params[0]; $this->dccClass->dccInform("Finished receiving NAMES list for " . $channel); break; case "005": if ($this->ircClass->getServerConf("NETWORK") != "") { //Only show this once... if (strpos($line['params'],"NETWORK") !== false) { $this->dccClass->dccInform("Parsing IRC-Server specific configuration..."); $this->dccClass->dccInform("Network has been identified as " . $this->ircClass->getServerConf("NETWORK") . "(" . $line['from'] . ")"); } } break; case "315": $params = explode(chr(32), $line['params']); $this->dccClass->dccInform("Finished receiving WHO list for: " . $params[0]); break; case "368": $params = explode(chr(32), $line['params']); $this->dccClass->dccInform("Finished receiving ban list for: " . $params[0]); break; case "433": $this->dccClass->dccInform("Nick collision! Unable to change your nick. Nickname already in use!"); break; default: break; } // Lets alias 004 to CONNECT, for the n00bs if ($line['cmd'] == "004") { $line['cmd'] = "connect"; } if ($line['cmd'] == "error") { $line['cmd'] = "disconnect"; } // Action type handler if (isset($this->cmdList['action']) && strtolower($line['cmd']) == "privmsg") { if (substr($line['text'], 0, 8) == chr(1) . "ACTION ") { $newLine = $line; $newLine['text'] = substr($line['text'], 8, strlen($line['text']) - 9); $sArgs = $this->createLine($newLine['text']); foreach($this->cmdList['action'] AS $item) { $func = $item['function']; $class = $this->cmdList['file'][$item['module']]['class']; $class->$func($newLine, $sArgs); } } } // Raw type handler if (isset($this->cmdList['raw'])) { if (!isset($args)) { $args = $this->createLine($line['text']); } foreach($this->cmdList['raw'] AS $item) { $func = $item['function']; $class = $this->cmdList['file'][$item['module']]['class']; $class->$func($line, $args); } } // Here we will call any type if (isset($this->cmdList[irc::myStrToLower($line['cmd'])])) { if (!isset($args)) { $args = $this->createLine($line['text']); } foreach($this->cmdList[irc::myStrToLower($line['cmd'])] AS $item) { $func = $item['function']; $class = $this->cmdList['file'][$item['module']]['class']; $class->$func($line, $args); } } if (isset($args)) { unset($args); } } /* Misc Functions */ private function parseCtcp($line, $msgs = "PRIVMSG") { $cmd = str_replace(chr(1), "", $line['text']) . " "; $query = trim(substr($cmd, strpos($cmd, chr(32)) + 1)); $cmd = substr(irc::myStrToLower($cmd), 0, strpos($cmd, chr(32))); $msg = ""; switch($cmd) { case "version": $this->dccClass->dccInform("CTCP VERSION: " . $line['fromNick'] . " versioned us."); break; case "time": $this->dccClass->dccInform("CTCP TIME: " . $line['fromNick'] . " requested the time."); break; case "uptime": $this->dccClass->dccInform("CTCP UPTIME: " . $line['fromNick'] . " requested our uptime."); break; case "ping": $this->dccClass->dccInform("CTCP PING: " . $line['fromNick'] . " pinged us."); break; case "dcc": $vars = explode(chr(32), $query); $this->dccParse($line, $vars, $query); break; } if ($msg != "") { $this->notice($this->lVars['fromNick'], chr(1) . $msg . chr(1)); } if (isset($this->cmdList['ctcp'][$cmd])) { $func = $this->cmdList['ctcp'][$cmd]['function']; $class = $this->cmdList['file'][$this->cmdList['ctcp'][$cmd]['module']]['class']; $args = $this->createLine($cmd . " " . $query); $class->$func($line, $args); } } function dccParse($line, $vars, $txt) { $cVars = count($vars); if ($cVars < 1) { return; } $cmd = irc::myStrToUpper($vars[0]); switch ($cmd) { case "CHAT": if ($cVars == 4) { $iplong = long2ip( (double) $vars[2]); $port = $vars[3]; $this->dccClass->addChat($line['fromNick'], $iplong, (int) $port, false, null); } break; case "SEND": if ($this->ircClass->getClientConf('upload') != 'yes') { $this->ircClass->notice($line['fromNick'], "DCC: I do not accept dcc transfers at this time.", 0); break; } if ($cVars >= 5) { //Some bastard sent a file with spaces. Shit. Ass. if (strpos($query, chr(34)) !== false) { $first = strpos($query, chr(34)); $second = strpos($query, chr(34), $first + 1); $filename = substr($query, $first + 1, $second - $first - 1); $query = str_replace("\"".$filename."\"", "file.ext", $query); $vars = explode(chr(32), $query); } else { $filename = $vars[1]; } $iplong = long2ip( (double) $vars[2]); $port = $vars[3]; $filesize = $vars[4]; $this->dccClass->addFile($line['fromNick'], $iplong, (int) $port, DOWNLOAD, $filename, $filesize); } break; case "ACCEPT": if ($cVars == 4) { $port = $vars[2]; $bytes = $vars[3]; $this->dccClass->dccAccept($port, $bytes); } break; case "RESUME": if ($cVars == 4) { $port = $vars[2]; $bytes = $vars[3]; $this->dccClass->dccResume($port, $bytes); } break; } } } /* Used to access dcc admin commands via private message */ class chat_wrapper { public $nick; private $ircClass; public $isAdmin; public function __construct($nick, $ircClass) { $this->nick = $nick; $this->ircClass = $ircClass; $this->isAdmin = 1; } public function dccSend($data, $to = null) { $this->ircClass->privMsg($this->nick, "--> " . $data); } public function disconnect($msg = "") { $this->ircClass->privMsg($this->nick, "Right........"); } } ?>