Mercurial > hg > udpmsg3
diff ircrelay/ircrelay.php @ 0:dd81c38b513a
Initial commit
author | Ivo Smits <Ivo@UCIS.nl> |
---|---|
date | Mon, 28 Feb 2011 00:49:07 +0100 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ircrelay/ircrelay.php Mon Feb 28 00:49:07 2011 +0100 @@ -0,0 +1,457 @@ +#!/usr/bin/php +<?php +/* Copyright 2010 Ivo Smits <Ivo@UCIS.nl>. All rights reserved. + Redistribution and use in source and binary forms, with or without modification, are + permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list + of conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + The views and conclusions contained in the software and documentation are those of the + authors and should not be interpreted as representing official policies, either expressed + or implied, of Ivo Smits.*/ + +print("UCIS IRC Relay bot (c) 2010 Ivo Smits <Ivo@UCIS.nl>\n"); +print("More information: http://wiki.ucis.nl/IRCRelay\n"); +print("\n"); + +if (defined('app_loaded')) return; +define('app_loaded', TRUE); +if (!isset($config)) include './config.php'; + +class Network { + public $key = NULL; + public $name = NULL; + public $socket = NULL; + public $server = NULL; + public $port = 6667; + public $nick = NULL; + public $realnick = NULL; + public $connected = FALSE; + public $connecting = FALSE; + public $timeout = 0; + public $channels = array(); + public $addressfamily = AF_INET; + public $active = FALSE; + public $ident = 'none'; + public $realname = 'Not set - http://wiki.qontrol.nl/IRCRelay'; + public $debug = FALSE; +} +class NetworkChannel { + public $network; + public $channel; + public $name; + public $display; +} +class Channel { + public $name = NULL; + public $udpmsg = FALSE; + public $links = array(); + public function __construct($name) { $this->name = $name; } +} + +$networks = array(); +$channels = array(); +$udpmsg = NULL; +$udpmsg_config = NULL; +$udpmsg_connected = FALSE; +$udpmsg_timer = 0; +$udpmsg_buffer = ''; +$udpmsg_channels = array(); +$status = NULL; +$adminpass = NULL; + +srand(); + +prepare(); +configure(); +init(); +readloop(); + +function getchannel($name) { + global $channels; + if (array_key_exists($name, $channels)) return $channels[$name]; + $ch = new Channel($name); + $channels[$name] = $ch; + return $ch; +} +function configure() { + global $networks, $config; + global $channels; + global $adminpass; + global $udpmsg, $udpmsg_config, $udpmsg_channels; + + print("Parsing configuration...\n"); + //require 'config.php'; + + if (isset($config['password'])) { + $adminpass = $config['password']; + require_once './ircrelay_admincmd.php'; + } + if (isset($config['udpmsg'])) { + $udpmsg_config = $config['udpmsg']; + $udpmsg = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); + socket_set_nonblock($udpmsg); + @socket_connect($udpmsg, $udpmsg_config['host'], $udpmsg_config['port']); + foreach ($udpmsg_config['channels'] as $chk => $chn) { + $ch = getchannel($chn); + $ch->udpmsg = $chk; + $udpmsg_channels[$chk] = $ch; + } + unset($udpmsg_config['channels']); + } + foreach ($config['networks'] as $key => $nc) { + $net = new Network(); + $net->key = $key; + $net->server = $nc['server']; + $net->nick = $nc['nick']; + $net->name = isset($nc['name']) ? $nc['name'] : $key; + if (isset($nc['port'])) $net->port = $nc['port']; + if (isset($nc['ident'])) $net->ident = $nc['ident']; + if (isset($nc['realname'])) $net->realname = $nc['realname']; + if (isset($nc['ipv6']) && $nc['ipv6']) $net->addressfamily = AF_INET6; + if (isset($nc['debug']) && $nc['debug']) $net->debug = TRUE; + foreach ($nc['channels'] as $chn => $cf) { + $ch = getchannel($cf['link']); + $nch = new NetworkChannel(); + $nch->name = $chn; + $nch->network = $net; + $nch->channel = $ch; + $nch->display = isset($cf['display']) ? $cf['display'] : $net->key; + $ch->links[] = $nch; + $net->channels[$chn] = $nch; + } + $networks[$key] = $net; + $net->active = TRUE; + print('NET '.$net->key.' create'."\n"); + } +} + +function prepare() { + global $channels, $status; + print("Starting IRC Relay...\n"); + $ch = new Channel('status'); + $channels[$ch->name] = $ch; + $status = $ch; +} + +function init() { + global $networks; + print("Initializing...\n"); +} + +function readloop() { + global $networks, $udpmsg, $listener, $udpmsg_connected, $udpmsg_config, $udpmsg_timer; + + $ltime = 0; + + print("Running\n"); + while (TRUE) { + $selread = array(); + $selwrite = array(); + $selerror = array(); + $selcount = 0; + + $ntime = time(); + $tdiff = $ntime - $ltime; + $ltime = $ntime; + + if ($udpmsg === NULL && $udpmsg_config['host'] !== NULL) { + $udpmsg_timer += $tdiff; + if ($udpmsg_timer >= 30) { + fprintf(STDERR, "UDPMSG: Reconnecting...\n"); + $udpmsg = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); + socket_set_nonblock($udpmsg); + @socket_connect($udpmsg, $udpmsg_config['host'], $udpmsg_config['port']); + $udpmsg_timer = 0; + } + } + + if ($udpmsg !== NULL) { + $selread[] = $udpmsg; + $selerror[] = $udpmsg; + if (!$udpmsg_connected) $selwrite[] = $udpmsg; + } + + foreach ($networks as $net) { + if ($net->connected) { + $selread[] = $net->socket; + $selerror[] = $net->socket; + $selcount++; + } else if ($net->connecting) { + $selwrite[] = $net->socket; + $selerror[] = $net->socket; + $selcount++; + } else if ($net->active) { + $net->timeout -= $tdiff; + if ($net->timeout <= 0) { + $net->connecting = TRUE; + print('NET '.$net->key.' connect '.$net->server.':'.$net->port."\n"); + $net->socket = socket_create($net->addressfamily, SOCK_STREAM, SOL_TCP); + socket_set_nonblock($net->socket); + socket_connect($net->socket, $net->server, $net->port); + $selwrite[] = $net->socket; + $selerror[] = $net->socket; + } + } + } + + if (!$selcount) { + sleep(1); + continue; + } + + socket_select(&$selread, &$selwrite, &$selerror, 10); + + if (in_array($udpmsg, $selerror)) { + fprintf(STDERR, "UDPMSG: Error: select error.\n"); + socket_close($udpmsg); + $udpmsg = NULL; + $udpmsg_connected = FALSE; + } else { + if (in_array($udpmsg, $selwrite) && !$udpmsg_connected) { + fprintf(STDERR, "UDPMSG: Connected.\n"); + $udpmsg_buffer = ''; + $udpmsg_connected = TRUE; + } + if (in_array($udpmsg, $selread)) { + if (!udpmsg_read($udpmsg)) { + fprintf(STDERR, "UDPMSG: Error: closing connection.\n"); + socket_close($udpmsg); + $udpmsg = NULL; + $udpmsg_connected = FALSE; + } + } + } + + foreach ($networks as $net) { + if (in_array($net->socket, $selerror)) { + print('NET '.$net->key.' socket error'."\n"); + network_reset($net); + } else if ($net->connected && in_array($net->socket, $selread)) { + if (!network_read($net)) { + print('NET '.$net->key.' read error'."\n"); + network_reset($net); + } + } else if ($net->connecting && in_array($net->socket, $selwrite)) { + $net->connected = TRUE; + $net->connecting = FALSE; + print('NET '.$net->key.' identify '.$net->nick."\n"); + network_send($net, 'USER '.$net->ident.' host server :'.$net->realname); + network_send($net, 'NICK '.$net->nick); + } + } + } +} + +function udpmsg_read() { + global $channels, $clients, $udpmsg, $udpmsg_buffer, $udpmsg_channels, $udpmsg_config; + $msg = socket_read($udpmsg, 1024); + if (!strlen($msg)) { + fprintf(STDERR, "UDPMSG: End of file\n"); + return FALSE; + } + $udpmsg_buffer .= $msg; + while (strlen($udpmsg_buffer) > 2) { + $len = ord($udpmsg_buffer[0]) * 256 + ord($udpmsg_buffer[1]); + if ($len <= 0 || $len > 1024) { + fprintf(STDERR, "UDPMSG: Error: protocol error\n"); + return FALSE; + } + if (strlen($udpmsg_buffer) < 2 + $len) break; + $msg = substr($udpmsg_buffer, 2, $len); + $udpmsg_buffer = substr($udpmsg_buffer, 2 + $len); + $parts = explode("\0", $msg); + $ret = array(); + for ($i = 0; $i < count($parts)-1; $i += 2) $ret[$parts[$i]] = $parts[$i+1]; + + if (!isset($ret['CHN']) || !isset($ret['CMD']) || !isset($ret['USR'])) continue; + if (!array_key_exists($ret['CHN'], $udpmsg_channels)) continue; + $ch = $udpmsg_channels[$ret['CHN']]; + if ($ret['CMD'] == 'MSG') { + if (!isset($ret['MSG'])) continue; + $from = preg_replace('/[^a-zA-Z0-9\-_]/', '', $ret['USR']); + if ($udpmsg_config['nicknetwork'] && isset($ret['NET'])) $from = preg_replace('/[^a-zA-Z0-9\-_]/', '', $ret['NET']).$udpmsg_config['nickseparator'].$from; + $from = $udpmsg_config['nickprefix'].$from; + $message = preg_replace('/[\x00\x10-\x13]/', '', $ret['MSG']); + $prefix = '['.$from.'] '; + if (ord($message[0]) == 1) { //CTCP + if (substr($message, 1, 6) != 'ACTION') continue; + $message = chr(1).'ACTION '.$prefix.substr($message, 8, -1).chr(1); + } else { + $message = $prefix.$message; + } + channel_send($ch, $message, $udpmsg); + } + } + return TRUE; +} + +function udpmsg_send($arr) { + global $udpmsg, $udpmsg_connected; + if (!$udpmsg || !$udpmsg_connected) return; + $tmp = array(); + foreach ($arr as $key => $value) { $tmp[] = $key; $tmp[] = $value; } + $tmp[] = ''; + $msg = implode("\0", $tmp); + $len = strlen($msg); + if ($len > 1024) { + fprintf(STDERR, "UDPMSG: Error: message too long!\n"); + return; + } + $lens = chr(floor($len / 256)).chr($len % 256); + socket_write($udpmsg, $lens.$msg); +} + +function network_reset($net) { + print('NET '.$net->key.' reset'."\n"); + socket_close($net->socket); + $net->connected = FALSE; + $net->connecting = FALSE; + $net->readbuffer = ''; + $net->timeout = 60; +} + +function network_read($net) { + $newdata = socket_read($net->socket, 1024); + if ($newdata === FALSE || !strlen($newdata)) return FALSE; + $net->readbuffer .= $newdata; + $offset = 0; + $len = strlen($net->readbuffer); + while ($offset < $len) { + $posa = strpos($net->readbuffer, "\n", $offset); + $posb = strpos($net->readbuffer, "\r", $offset); + if ($posa !== FALSE && $posb !== FALSE) { + $pos = min($posa, $posb); + } else if ($posa !== FALSE) { + $pos = $posa; + } else if ($posb !== FALSE) { + $pos = $posb; + } else { + break; + } + $line = substr($net->readbuffer, $offset, $pos - $offset); + if (strlen($line)) { + if (!network_process($net, $line)) return FALSE; + } + $offset = $pos + 1; + } + if ($offset == $len) { + $net->readbuffer = ''; + } else if ($offset != 0) { + $net->readbuffer = substr($net->readbuffer, $offset); + } + return TRUE; +} + +function network_process($net, $line) { + global $adminpass, $udpmsg, $udpmsg_connected; + if ($net->debug) print('NET '.$net->key.' recv '.$line."\n"); + $partsa = explode(' :', $line, 2); + $parts = explode(' ', $partsa[0]); + if ($parts[0][0] == ':') { + $sender = substr(array_shift(&$parts), 1); + } else { + $sender = NULL; + } + $command = array_shift(&$parts); + if (count($partsa) > 1) array_push(&$parts, $partsa[1]); + $partsa = explode('!', $sender); + $sendernick = $partsa[0]; + switch (strtoupper($command)) { + case '001': //Welcome + $net->realnick = $parts[0]; + print('NET '.$net->key.' welcome '.$net->realnick."\n"); + foreach ($net->channels as $ch) network_send($net, "JOIN :".$ch->name); + break; + case '433': //Nickname in use + print('NET '.$net->key.' nickname_in_use'."\n"); + network_send($net, "NICK ".$net->nick.rand(100,999)); + break; + case 'NICK': //Nickname change + if (strcasecmp($sendernick, $net->realnick)) { + $net->realnick = $parts[0]; + print('NET '.$net->key.' nickname '.$net->realnick."\n"); + } + break; + case 'PING': + network_send($net, 'PONG :'.$parts[0]); + break; + case 'PRIVMSG': + case 'NOTICE': + if ($parts[0][0] == '#') { + $to = $parts[0]; + } else { + $to = $sendernick; + if (!is_null($adminpass) && $parts[1][0] == '.' && substr($parts[1], 1, strlen($adminpass)) === $adminpass) { + $ret = admincmd(array_slice(explode(' ', $parts[1]), 1)); + network_send($net, 'PRIVMSG '.$to.' :'.$ret); + break; + } + } + if (!array_key_exists($to, $net->channels)) { + print('NET '.$net->key.' privmsg '.$sendernick.' @ '.$to.' - '.$parts[1]."\n"); + break; + } + $ch = $net->channels[$to]; + $prefix = '['.$sendernick.(is_null($ch->display) ? '' : ' @ '.$ch->display).'] '; + if (ord($parts[1][0]) == 1) { //CTCP + if (substr($parts[1], 1, 6) != 'ACTION') break; + $msg = chr(1).'ACTION '.$prefix.substr($parts[1], 8, -1).chr(1); + } else { + $msg = $prefix.$parts[1]; + } + channel_send($ch->channel, $msg, $ch, $command == 'NOTICE'); + if ($udpmsg && $udpmsg_connected && $ch->channel->udpmsg) { + $msg = array('CMD' => 'MSG', 'CHN' => $ch->channel->udpmsg, 'MSG' => $parts[1], 'USR' => $sendernick, 'DUMMY' => rand(10000, 99999)); + if (!is_null($ch->display)) $msg['NET'] = $ch->display; + udpmsg_send($msg); + } + break; + case '372': //Motd + case '376': //End of moth + case '002': //Your host + case '003': //This server + case '004': //Version + case '005': //Supported + case '422': //Motd missing + case '251': case '254': case '255': case '265': case '266': case '396': case '353': case '366': + case '252': case '375': + case 'MODE': + case 'JOIN': + case 'PART': + case 'QUIT': + break; + default: + print('NET '.$net->key.' unknown '.$line."\n"); + } + return TRUE; +} + +function network_send($net, $line) { + if ($net->debug) print('NET '.$net->key.' send '.$line."\n"); + $line .= "\r\n"; + socket_send($net->socket, $line, strlen($line), 0); +} + +function channel_send($ch, $message, $source = NULL, $notice = FALSE) { + foreach ($ch->links as $link) { + if ($link->network->connected && $link !== $source) { + network_send($link->network, ($notice ? 'NOTICE ' : 'PRIVMSG ').$link->name.' :'.$message); + } + } +}