Mercurial > hg > udpmsg3
diff multiclientrelay/multiclientrelay.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/multiclientrelay/multiclientrelay.php Mon Feb 28 00:49:07 2011 +0100 @@ -0,0 +1,465 @@ +<?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.*/ + +if (defined('app_loaded')) return; +define('app_loaded', TRUE); + +print("UCIS UDPMSG3 Multi client IRC relay (c) 2010 Ivo Smits <Ivo@UCIS.nl>\n"); +print("More information: http://wiki.ucis.nl/UDPMSG3\n"); +print("\n"); + +$irc_socket = NULL; +$irc_nick = NULL; +$irc_channels = array(); +$irc_buffer = ''; +$irc_connected = FALSE; +$irc_users = array(); +$irc_clients = array(); + +$udpmsg_channels = array(); +$udpmsg_buffer = ''; + +class Channel { + public $uname = NULL; + public $iname = NULL; +} +class User { + public $iname = NULL; + public $channels = array(); +} +class Client { + public $uname = NULL; + public $iname = NULL; + public $channels = array(); + public $socket = NULL; + public $buffer = ''; + public $connected = FALSE; + public $identified = FALSE; + public $joinchannels = array(); +} + +srand(); + +print("Loading configuration...\n"); +if (!isset($config)) require './config.php'; +$udpmsg_socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); +socket_connect($udpmsg_socket, $config['udpmsg']['host'], $config['udpmsg']['port']); + +$irc_socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); +socket_connect($irc_socket, $config['irc']['host'], $config['irc']['port']); +irc_send('USER '.$config['irc']['ident'].' host server :'.$config['irc']['realname']); +$irc_nick = $config['irc']['nick']; +irc_send('NICK '.$irc_nick); +foreach ($config['channels'] as $iname => $uname) { + $ch = new Channel(); + $ch->iname = $iname; + $ch->uname = $uname; + $irc_channels[strtoupper($ch->iname)] = $ch; + $udpmsg_channels[$ch->uname] = $ch; +} + +print("Starting IRC Relay...\n"); +print("Running\n"); +$atime = 0; +while (TRUE) { + $selread = array($udpmsg_socket, $irc_socket); + $selwrite = array(); + $selerror = array($udpmsg_socket, $irc_socket); + foreach ($irc_clients as $client) { + if ($client->connected) { + $selread[] = $client->socket; + $selerror[] = $client->socket; + } else { + $selwrite[] = $client->socket; + $selerror[] = $client->socket; + } + } + socket_select(&$selread, &$selwrite, &$selerror, 120); + if (in_array($udpmsg_socket, $selerror)) die("UDPMSG: Error: select error.\n"); + if (in_array($irc_socket, $selerror)) die("IRC: Error: select error.\n"); + if (in_array($udpmsg_socket, $selread) && !udpmsg_read()) die("UDPMSG: Error: closing connection.\n"); + if (in_array($irc_socket, $selread) && !irc_read()) die("IRC: read error.\n"); + foreach ($irc_clients as $client) { + if (in_array($client->socket, $selerror)) { + irc_client_close($client); + } else if ($client->connected && in_array($client->socket, $selread)) { + if (!ircc_read($client)) { + print("IRCC: read error.\n"); + irc_client_close($client); + } + } else if (!$client->connected && in_array($client->socket, $selwrite)) { + $client->connected = TRUE; + ircc_send($client, 'USER '.$client->iname.' host server :'.$client->iname); + ircc_send($client, 'NICK '.$client->iname); + } + } + $ctime = time(); + if ($atime < $ctime) { + foreach ($irc_users as $usr) if (is_a($usr, 'User')) irc_userchannels($usr, array('CMD' => 'ALIVE')); + $atime = $ctime + 1800; + } +} + +function udpmsg_read() { + global $udpmsg_socket, $udpmsg_buffer; + $msg = socket_read($udpmsg_socket, 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]; + udpmsg_process($ret); + } + return TRUE; +} + +function udpmsg_send($arr) { + global $udpmsg_socket, $config; + $tmp = array(); + $arr['DUMMY'] = rand(0, 999999); + $arr['NET'] = $config['udpmsg']['netname']; + 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_socket, $lens.$msg); +} + +function ircc_send($client, $line) { + print('ICW: '.$line."\n"); + $line .= "\r\n"; + socket_send($client->socket, $line, strlen($line), 0); +} +function ircc_read($client) { + $newdata = socket_read($client->socket, 1024); + if ($newdata === FALSE || !strlen($newdata)) return FALSE; + $irc_buffer = $client->buffer . $newdata; + $offset = 0; + $len = strlen($irc_buffer); + while ($offset < $len) { + $posa = strpos($irc_buffer, "\n", $offset); + $posb = strpos($irc_buffer, "\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($irc_buffer, $offset, $pos - $offset); + if (strlen($line) && !ircc_process($client, $line)) return FALSE; + $offset = $pos + 1; + } + $client->buffer = ($offset == $len) ? '' : substr($irc_buffer, $offset); + return TRUE; +} +function ircc_process($client, $line) { + global $irc_channels, $irc_users; + print('ICR: '.$line."\n"); + $partsa = explode(' :', $line, 2); + $parts = explode(' ', $partsa[0]); + $sender = ($parts[0][0] == ':') ? substr(array_shift(&$parts), 1) : 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 + $client->iname = $parts[0]; + $client->identified = TRUE; + $irc_users[strtoupper($client->iname)] = $client; + foreach ($client->joinchannels as $ch) ircc_send($client, "JOIN :".$ch->iname); + break; + case '433': //Nickname in use + ircc_send($client, "NICK ".$client->iname.rand(100,999)); + break; + case 'PING': + ircc_send($client, 'PONG :'.$parts[0]); + break; + case 'NICK': //Nickname change + case 'JOIN': + case '353': //:server 353 me = channel :nickname nickname nickname + case 'PART': + case 'KICK': + case 'QUIT': + case 'PRIVMSG': + case 'NOTICE': + //These messages are also received and processed by the master relay bot, so we ignore them here + break; + } + return TRUE; +} +function ircc_channel_send($client, $ch, $msg) { + ircc_send($client, 'PRIVMSG '.$ch->iname.' :'.$msg); +} + +function irc_read() { + global $irc_socket, $irc_buffer; + $newdata = socket_read($irc_socket, 1024); + if ($newdata === FALSE || !strlen($newdata)) return FALSE; + $irc_buffer .= $newdata; + $offset = 0; + $len = strlen($irc_buffer); + while ($offset < $len) { + $posa = strpos($irc_buffer, "\n", $offset); + $posb = strpos($irc_buffer, "\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($irc_buffer, $offset, $pos - $offset); + if (strlen($line) && !irc_process($line)) return FALSE; + $offset = $pos + 1; + } + $irc_buffer = ($offset == $len) ? '' : substr($irc_buffer, $offset); + return TRUE; +} + +function irc_send($line) { + global $irc_socket; + print('IW: '.$line."\n"); + $line .= "\r\n"; + socket_send($irc_socket, $line, strlen($line), 0); +} + +function get_irc_client($name, $ch) { + if (!isset($irc_clients[$name])) { + if (count($irc_clients) < $config['irc']['clientconnections']) { + $client = new Client(); + $client->uname = $client->iname = $name; + $client->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); + socket_connect($client->socket, $config['irc']['host'], $config['irc']['port']); + $client->joinchannels[$ch->iname] = $ch; + $irc_clients[$name] = $client; + } + return NULL; + } + $client = $irc_clients[$name]; + if (!$client->connected || !$client->identified) return NULL; + if (!isset($client->channels[$ch->iname])) { + $client->joinchannels[$ch->iname] = $ch; + ircc_send($client, 'JOIN :'.$ch->iname); + return NULL; + } + return $client; +} +function irc_client_part($name, $ch) { + if (!isset($irc_clients[$name])) return FALSE; + $client = $irc_clients[$name]; + unset($client->channels[$ch->iname]); + unset($client->joinchannels[$ch->iname]); + if (!count($client->channels) && !count($client->joinchannels)) { + ircc_send($client, 'QUIT :No remaining channels.'); + irc_client_close($client); + } else { + ircc_send($client, 'PART :'.$ch->iname); + } +} +function irc_client_close($client) { + global $irc_clients, $irc_users; + unset($irc_clients[$client->uname]); + if ($client->identified) unset($irc_users[strtoupper($client->iname)]); + socket_close($client->socket); +} + +function udpmsg_process($ret) { + global $udpmsg_channels, $irc_clients, $config; + if (!isset($ret['CHN']) || !isset($ret['CMD']) || !isset($ret['USR'])) return; + if (!array_key_exists($ret['CHN'], $udpmsg_channels)) return; + $ch = $udpmsg_channels[$ret['CHN']]; + $net = isset($ret['NET']) ? preg_replace('/[\x00\x10-\x13]/', '', $ret['NET']) : NULL; + $usr = preg_replace('/[\x00\x10-\x13]/', '', $ret['USR']); + switch ($ret['CMD']) { + case 'MSG': + if (!isset($ret['MSG'])) break; + $msg = preg_replace('/[\x00\x10-\x13]/', '', $ret['MSG']); + $client = NULL; + $client = get_irc_client($usr, $ch); + if ($client === NULL) { + $pfx = '['.$usr.($net?' @ '.$net:'').'] '; + if (ord($msg[0]) == 1) { + if (substr($message, 1, 6) != 'ACTION') break; + $msg = chr(1).'ACTION '.$pfx.substr($msg, 8, -1).chr(1); + } else { + $msg = $pfx.$msg; + } + irc_channel_send($ch, $msg); + } else { + ircc_channel_send($client, $ch, $msg); + } + break; + case 'JOIN': + if (get_irc_client($usr, $ch) === NULL) irc_channel_send($ch, '* '.$usr.' has joined'.($net?' on network '.$net:'')); + break; + case 'PART': + if (!irc_client_part($usr, $ch)) irc_channel_send($ch, '* '.$usr.' has left'.($net?' on network '.$net:'')); + break; + case 'NICK': + if (!isset($ret['NEWNICK'])) break; + $newnick = preg_replace('/[\x00\x10-\x13]/', '', $ret['NEWNICK']); + if (irc_client_part($usr, $ch)) { + if (get_irc_client($usr, $ch) === NULL) irc_channel_send($ch, '* '.$usr.' has joined'.($net?' on network '.$net:'')); + } else { + irc_channel_send($ch, '* '.$usr.' has changed their nickname to '.$newnick.' '.($net?' on network '.$net:'')); + } + break; + } +} + +function irc_process($line) { + global $irc_nick, $irc_connected, $irc_channels, $config, $irc_users; + print('IR: '.$line."\n"); + $partsa = explode(' :', $line, 2); + $parts = explode(' ', $partsa[0]); + $sender = ($parts[0][0] == ':') ? substr(array_shift(&$parts), 1) : 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 + $irc_nick = $parts[0]; + $irc_connected = TRUE; + foreach ($irc_channels as $ch) irc_send("JOIN :".$ch->iname); + break; + case '433': //Nickname in use + irc_send("NICK ".$config['irc']['nick'].rand(100,999)); + break; + case 'PING': + irc_send('PONG :'.$parts[0]); + break; + case 'NICK': //Nickname change + if (strcasecmp($sendernick, $irc_nick)) { + $irc_nick = $parts[0]; + } else { + $usr = irc_getuser($sendernick, FALSE); + if ($usr === NULL) break; + irc_userchannels($usr, array('CMD' => 'NICK', 'NEWNICK' => $parts[0])); + unset($irc_users[strtoupper($usr->iname)]); + $usr->iname = $parts[0]; + $irc_users[strtoupper($usr->iname)] = $usr; + } + break; + case 'JOIN': + if (!strcasecmp($sendernick, $irc_nick)) break; + $chs = explode(',', $parts[0]); + $usr = NULL; + foreach ($chs as $chn) { + $ch = irc_getchannel($chn); + if ($ch === NULL) continue; + if ($usr === NULL) $usr = irc_getuser($sendernick, TRUE); + $usr->channels[$ch->iname] = $ch; + if (!is_a($usr, 'Client')) udpmsg_send(array('CMD' => 'JOIN', 'CHN' => $ch->uname, 'USR' => $usr->iname)); + } + break; + case '353': //:server 353 me = channel :nickname nickname nickname + $ch = irc_getchannel($parts[2]); + if ($ch === NULL) break; + $partsa = explode(' ', $parts[3]); + foreach ($partsa as $un) { + if (!strlen($un)) continue; + $usr = irc_getuser(ltrim($un, '~&@%+'), TRUE); + $usr->channels[$ch->iname] = $ch; + if (!is_a($usr, 'Client')) udpmsg_send(array('CMD' => 'JOIN', 'CHN' => $ch->uname, 'USR' => $usr->iname)); + } + break; + case 'PART': + $usr = irc_getuser($sendernick, FALSE); + if ($usr === NULL) break; + $chs = explode(',', $parts[0]); + foreach ($chs as $chn) irc_part($usr, $chn); + break; + case 'KICK': + $usr = irc_getuser($parts[1], FALSE); + if (is_a($usr, 'Client')) break; + irc_part($usr, $parts[0]); + break; + case 'QUIT': + $usr = irc_getuser($sendernick, FALSE); + if ($usr === NULL) break; + if (is_a($usr, 'Client')) break; + irc_userchannels($usr, array('CMD' => 'PART')); + unset($irc_users[strtoupper($usr->iname)]); + break; + case 'PRIVMSG': + case 'NOTICE': + $ch = irc_getchannel($parts[0]); + if (is_a(irc_getuser($sendernick, FALSE), 'Client')) break; + if ($ch === NULL) break; + udpmsg_send(array('CMD' => 'MSG', 'CHN' => $ch->uname, 'MSG' => $parts[1], 'USR' => $sendernick)); + break; + } + return TRUE; +} + +function irc_getchannel($chn) { + global $irc_channels; + $chn = strtoupper($chn); + if (!array_key_exists($chn, $irc_channels)) return NULL; + return $irc_channels[$chn]; +} +function irc_getuser($nick, $create = TRUE) { + global $irc_users; + $snick = strtoupper($nick); + if (array_key_exists($snick, $irc_users)) return $irc_users[$snick]; + if (!$create) return NULL; + $usr = new User(); + $usr->iname = $nick; + $irc_users[$snick] = $usr; + return $usr; +} +function irc_part($usr, $chn) { + $ch = irc_getchannel($chn); + if ($ch === NULL || $usr === NULL) return; + unset($usr->channels[$ch->name]); + if (!is_a($usr, 'Client')) udpmsg_send(array('CMD' => 'PART', 'CHN' => $ch->uname, 'USR' => $usr->iname)); +} +function irc_userchannels($usr, $msg) { + $msg['USR'] = $usr->iname; + foreach ($usr->channels as $ch) { + $msg['CHN'] = $ch->uname; + udpmsg_send($msg); + } +} +function irc_channel_send($ch, $msg) { + global $irc_connected; + if (!$irc_connected) return; + irc_send('PRIVMSG '.$ch->iname.' :'.$msg); +}