# HG changeset patch # User Ivo Smits # Date 1298850547 -3600 # Node ID dd81c38b513a587dd499d9bf3e525369ac27c702 Initial commit diff -r 000000000000 -r dd81c38b513a client/client.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/client.php Mon Feb 28 00:49:07 2011 +0100 @@ -0,0 +1,82 @@ +. 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.*/ + +$server = '127.0.0.1:15387'; +$channel = 'chat/anonet'; +$name = 'user'; + +$socket = stream_socket_client('tcp://'.$server) or die("Could not create socket\n"); +$buffer = ''; +srand(); + +while (TRUE) { + $selread = array($socket, STDIN); + $selerror = array($socket); + $selwrite = NULL; + $count = stream_select(&$selread, &$selwrite, &$selerror, 10); + if (!$count) continue; + if (count($selerror)) die("Error: select error\n"); + if (count($selread)) { + if (in_array($socket, $selread)) { + $msg = fread($socket, 1024); + if (!strlen($msg)) die("Error: end of file\n"); + $buffer .= $msg; + while (strlen($buffer) > 2) { + $len = ord($buffer[0]) * 256 + ord($buffer[1]); + if ($len <= 0 || $len > 1024) die("Error: protocol error\n"); + if (strlen($buffer) < 2 + $len) break; + $msg = substr($buffer, 2, $len); + $buffer = substr($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['USR']) || !isset($ret['MSG']) || (isset($ret['CMD']) && $ret['CMD'] != 'MSG')) continue; + print(preg_replace('/[\x00\x07\x1B\x10-\x13]/', '', '<'.$ret['USR'].'> '.$ret['MSG'])."\n"); + } + } + if (in_array(STDIN, $selread)) { + $msg = rtrim(fgets(STDIN), "\r\n"); + udpmsg_send(array('DUMMY' => rand(10000, 99999), 'CMD' => 'MSG', 'CHN' => $channel, 'USR' => $name, 'MSG' => $msg)); + } + } + unset($msg, $len, $parts, $ret); +} + +function udpmsg_send($arr) { + global $socket; + $tmp = array(); + foreach ($arr as $key => $value) { $tmp[] = $key; $tmp[] = $value; } + $tmp[] = ''; + $msg = implode("\0", $tmp); + $len = strlen($msg); + if ($len > 1024) { + print("Error: message too long!\n"); + return FALSE; + } + $lens = chr(floor($len / 256)).chr($len % 256); + fwrite($socket, $lens.$msg); +} + diff -r 000000000000 -r dd81c38b513a client/debugclient.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/debugclient.php Mon Feb 28 00:49:07 2011 +0100 @@ -0,0 +1,49 @@ +. 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.*/ + +$server = '127.0.0.1:15387'; + +$socket = stream_socket_client('tcp://'.$server) or die("Could not create socket\n"); +$buffer = ''; + +while (TRUE) { + $msg = fread($socket, 1024); + if (!strlen($msg)) die("Error: end of file\n"); + $buffer .= $msg; + while (strlen($buffer) > 2) { + $len = ord($buffer[0]) * 256 + ord($buffer[1]); + if ($len <= 0 || $len > 1024) die("Error: protocol error\n"); + if (strlen($buffer) < 2 + $len) break; + $msg = substr($buffer, 2, $len); + $buffer = substr($buffer, 2 + $len); + printf('Received message: '); + $parts = explode("\0", $msg); + $ret = array(); + for ($i = 0; $i < count($parts)-1; $i += 2) $ret[$parts[$i]] = $parts[$i+1]; + print_r($ret); + } + unset($msg, $len, $parts, $ret); +} diff -r 000000000000 -r dd81c38b513a hub/config.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hub/config.php Mon Feb 28 00:49:07 2011 +0100 @@ -0,0 +1,12 @@ + array( + array(), + ), 'tcpconnect' => array( + ), 'udppeer' => array( + ), + 'logsize' => 1024, +); + +if (!defined('CONFIGFILE')) define('CONFIGFILE', __FILE__); +if (!defined('APP_LOADED')) include './hub.php'; diff -r 000000000000 -r dd81c38b513a hub/hub.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hub/hub.php Mon Feb 28 00:49:07 2011 +0100 @@ -0,0 +1,281 @@ +. 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); +if (!defined('CONFIGFILE')) define('CONFIGFILE', './config.php'); + +print("UCIS UDPMSG3 Hub (c) 2010 Ivo Smits \n"); +print("More information: http://wiki.ucis.nl/UDPMSG3\n"); +print("\n"); + +class StreamPeer { + public $socket = NULL; + public $buffer = ''; + public function select_read() { + if ($this->udpmsg_read()) return; + socket_delete($this); + socket_close($this->socket); + fprintf(STDERR, "UDPMSG: Error: closing connection.\n"); + peer_delete($this); + $this->closed(); + } + public function select_error() { + socket_delete($this); + socket_close($this->socket); + fprintf(STDERR, "UDPMSG: Error: select error.\n"); + peer_delete($this); + $this->closed(); + } + private function udpmsg_read() { + $msg = socket_read($this->socket, 1024); + if (!strlen($msg)) { + fprintf(STDERR, "UDPMSG: End of file\n"); + return FALSE; + } + $udpmsg_buffer = &$this->buffer; + $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; + udpmsg_receive($this, substr($udpmsg_buffer, 2, $len)); + $udpmsg_buffer = substr($udpmsg_buffer, 2 + $len); + } + return TRUE; + } + public function send($msg) { + $len = strlen($msg); + if ($len > 1024) { + fprintf(STDERR, "UDPMSG: Error: message too long!\n"); + return FALSE; + } + $lens = chr(floor($len / 256)).chr($len % 256); + socket_write($this->socket, $lens.$msg); + return TRUE; + } +} +class StreamServer { + public $host, $port, $bindhost, $bindport, $ipv6; + public $socket = NULL; + function __construct($host, $port, $ipv6 = FALSE) { + $this->host = $host; $this->port = $port; $this->ipv6 = $ipv6; + if (!$this->select_timeout()) socket_add_timeout($this); + } + public function select_timeout() { + socket_delete($this); + $this->socket = socket_create($this->ipv6 ? AF_INET6 : AF_INET, SOCK_STREAM, SOL_TCP); + socket_set_nonblock($this->socket); + if (!socket_bind($this->socket, $this->host, $this->port)) return FALSE; + if (!socket_listen($this->socket)) return FALSE; + socket_delete($this); + socket_add_connected($this); + fprintf(STDERR, "UDPMSG: Listening\n"); + return TRUE; + } + public function select_read() { + $client = socket_accept($this->socket); + if (!$client) return; + if (!socket_getpeername($client, &$addr, &$port)) $addr = 'unknown'; + fprintf(STDERR, 'UDPMSG: Accepted client '.$addr.':'.$port."\n"); + new StreamServerClient($client); + } + public function select_error() { + socket_close($this->socket); + fprintf(STDERR, "UDPMSG: Error: select error on listener. Closing listener.\n"); + socket_delete($this); + socket_add_timeout($this); + } +} +class StreamClient extends StreamPeer { + public $host, $port, $bindhost, $bindport, $ipv6; + function __construct($host, $port, $ipv6 = FALSE, $bindhost = NULL, $bindport = NULL) { + $this->host = $host; $this->port = $port; $this->ipv6 = $ipv6; $this->bindhost = $bindhost; $this->bindport = $bindport; + $this->select_timeout(); + } + public function closed() { + socket_add_timeout($this); + } + public function select_timeout() { + socket_delete($this); + $this->socket = socket_create($this->ipv6 ? AF_INET6 : AF_INET, SOCK_STREAM, SOL_TCP); + socket_set_nonblock($this->socket); + if ($this->bindhost || $this->bindport) socket_bind($this->socket, $this->bindhost, $this->bindport); + socket_connect($this->socket, $this->host, $this->port); + socket_add_connecting($this); + } + public function select_write() { + if (!socket_getpeername($this->socket, &$addr, &$port)) $addr = 'unknown'; + fprintf(STDERR, 'UDPMSG: Connected to '.$addr.':'.$port."\n"); + socket_delete($this); + socket_add_connected($this); + peer_add($this); + } +} +class StreamServerClient extends StreamPeer { + public function closed() { } + function __construct($socket) { + socket_set_nonblock($socket); + $this->socket = $socket; + socket_add_connected($this); + peer_add($this); + } +} +class UDPPeer { + public $socket = NULL; + function __construct($host = NULL, $port = NULL, $ipv6 = FALSE, $bindhost = NULL, $bindport = NULL) { + $this->socket = socket_create($this->ipv6 ? AF_INET6 : AF_INET, SOCK_STREAM, SOL_TCP); + socket_set_nonblock($this->socket); + if ($bindhost || $bindport) socket_bind($this->socket, $bindhost, $bindport); + socket_add_connected($this); + if ($host) { + socket_connect($this->socket, $host, $port); + peer_add($this); + } + } + public function select_read() { + if ($this->udpmsg_read()) return; + fprintf(STDERR, "UDPMSG: Error: closing connection.\n"); + } + public function select_error() { + fprintf(STDERR, "UDPMSG: Error: select error.\n"); + } + private function udpmsg_read() { + $msg = socket_read($this->socket, 1024 + 20); + if (!strlen($msg)) { + fprintf(STDERR, "UDPMSG: End of file\n"); + return FALSE; + } + if (strlen($msg) < 20 || substr($msg, 0, 20) != sha(substr($msg, 20), TRUE)) { + fprintf(STDERR, "UDPMSG: Short message or checksum mismatch\n"); + return FALSE; + } + udpmsg_receive($this, $msg); + return TRUE; + } + public function send($msg) { + socket_write($this->socket, sha1($msg, TRUE).$msg); + return TRUE; + } +} + +$msgi = 0; +$msglog = array(); + +$sock_fd_connecting = array(); +$sock_fd_connected = array(); +$sock_timeout = array(); +$sock_obj = array(); + +$peers = array(); + +function peer_add($obj) { + global $peers; + $peers[spl_object_hash($obj)] = $obj; +} +function peer_delete($obj) { + global $peers; + unset($peers[spl_object_hash($obj)]); +} + +function socket_add_timeout($obj) { + global $sock_timeout; + $sock_timeout[spl_object_hash($obj)] = $obj; +} +function socket_add_connecting($obj) { + global $sock_fd_connecting, $sock_obj; + $sock_fd_connecting[(int)$obj->socket] = $obj->socket; + $sock_obj[(int)$obj->socket] = $obj; +} +function socket_add_connected($obj) { + global $sock_fd_connected, $sock_obj; + $sock_fd_connected[(int)$obj->socket] = $obj->socket; + $sock_obj[(int)$obj->socket] = $obj; +} +function socket_delete($obj) { + global $sock_fd_connecting, $sock_fd_connected, $sock_obj, $sock_timeout; + unset($sock_fd_connecting[(int)$obj->socket]); + unset($sock_fd_connected[(int)$obj->socket]); + unset($sock_obj[(int)$obj->socket]); + unset($sock_timeout[spl_object_hash($obj)]); +} + +print("Loading configuration...\n"); +if (!isset($config)) require constant('CONFIGFILE'); + +$logsize = isset($config['logsize'])?$config['logsize']:512; +if (isset($config['tcplisten'])) + foreach ($config['tcplisten'] as $e) + new StreamServer( + isset($e['host']) ? $e['host'] : '0.0.0.0', + isset($e['port']) ? $e['port'] : 15387, + isset($e['ipv6']) && $e['ipv6']); +if (isset($config['tcpconnect'])) + foreach ($config['tcpconnect'] as $e) + new StreamClient( + isset($e['host']) ? $e['host'] : '127.0.0.1', + isset($e['port']) ? $e['port'] : 15387, + isset($e['ipv6']) && $e['ipv6'], + isset($e['bindhost']) ? $e['bindhost'] : NULL, + isset($e['bindport']) ? $e['bindport'] : NULL); +if (isset($config['udppeer'])) + foreach ($config['udppeer'] as $e) + new StreamClient( + isset($e['host']) ? $e['host'] : NULL, + isset($e['port']) ? $e['port'] : 15387, + isset($e['ipv6']) && $e['ipv6'], + isset($e['bindhost']) ? $e['bindhost'] : NULL, + isset($e['bindport']) ? $e['bindport'] : NULL); + +function udpmsg_receive($from, $msg) { + global $peers, $msgi, $msglog, $logsize; + $hash = md5($msg); + if (in_array($hash, $msglog)) return FALSE; + $msglog[$msgi] = $hash; + $msgi++; + if ($msgi >= $logsize) $msgi = 0; + $count = 0; + foreach ($peers as $peer) if ($peer !== $from) if ($peer->send($msg)) $count++; + return $count; +} + +while (TRUE) { + $selread = $sock_fd_connected; + $selwrite = $sock_fd_connecting; + $selerror = array_merge($selread, $selwrite); + if (count($selerror)) { + socket_select(&$selread, &$selwrite, &$selerror, 60); + foreach ($selread as $socket) if (isset($sock_obj[(int)$socket])) $sock_obj[(int)$socket]->select_read($socket); + foreach ($selwrite as $socket) if (isset($sock_obj[(int)$socket])) $sock_obj[(int)$socket]->select_write($socket); + foreach ($selerror as $socket) if (isset($sock_obj[(int)$socket])) $sock_obj[(int)$socket]->select_error($socket); + } else { + sleep(10); + } + foreach ($sock_timeout as $obj) $obj->select_timeout(); +} diff -r 000000000000 -r dd81c38b513a ircd/config.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ircd/config.php Mon Feb 28 00:49:07 2011 +0100 @@ -0,0 +1,18 @@ + array( + 'host' => '127.0.0.1', + 'port' => 15387, + 'nickprefix' => 'E\\', + 'nickseparator' => '', + 'nicknetwork' => FALSE, + 'netname' => 'IRCd', + ), + 'channels' => array( + '#anonet' => array( + 'udpmsg' => 'chat/anonet', + ), + ), +); + +include './ircd.php'; diff -r 000000000000 -r dd81c38b513a ircd/ircd.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ircd/ircd.php Mon Feb 28 00:49:07 2011 +0100 @@ -0,0 +1,470 @@ +. 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); +if (!isset($config)) include './config.php'; + +print("UCIS UDPMSG3 IRC server (c) 2010 Ivo Smits \n"); +print("More information: http://wiki.ucis.nl/UDPMSG3\n"); +print("\n"); + +srand(); + +class Channel { + public $name = NULL; + public $udpmsg = FALSE; + public $users = array(); + public $persistent = FALSE; + public function __construct($name) { $this->name = $name; } +} +class Client { + public $socket = NULL; + public $name = NULL; + public $channels = array(); + public $readbuffer = ''; +} + +$channels = array(); +$clients = array(); +$udpmsg = NULL; +$udpmsg_config = NULL; +$udpmsg_connected = FALSE; +$udpmsg_timer = 0; +$udpmsg_buffer = ''; +$udpmsg_channels = array(); +$listener = 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 $channels, $config; + global $udpmsg, $udpmsg_config, $udpmsg_channels; + + print("Parsing configuration...\n"); + //require 'config.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 ($config['channels'] as $key => $cf) { + $ch = getchannel($key); + $ch->persistent = TRUE; + if (isset($cf['udpmsg'])) { + $ch->udpmsg = $cf['udpmsg']; + $udpmsg_channels[$cf['udpmsg']] = $ch; + } + } +} + +function prepare() { + global $channels; + print("Starting IRC Relay...\n"); +} + +function init() { + global $listener; + print("Initializing...\n"); + $listener = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); + socket_bind($listener, '0.0.0.0', 7777) or die("Could not bind listening socket.\n"); + socket_listen($listener) or die("Could not start listening.\n"); +} + +function readloop() { + global $channels, $clients, $udpmsg, $listener, $udpmsg_connected, $udpmsg_config, $udpmsg_timer; + + $ltime = 0; + + print("Running\n"); + while (TRUE) { + $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; + } + } + + $selread = array(); + $selwrite = array(); + $selerror = array(); + + if ($udpmsg !== NULL) { + $selread[] = $udpmsg; + $selerror[] = $udpmsg; + if (!$udpmsg_connected) $selwrite[] = $udpmsg; + } + $selread[] = $listener; + + foreach ($clients as $client) { + $selread[] = $client->socket; + $selerror[] = $client->socket; + } + + socket_select(&$selread, &$selwrite, &$selerror, 10); + + if (in_array($listener, $selread)) { + client_accept($listener); + } + + 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 ($clients as $client) { + if (in_array($client->socket, $selerror)) { + client_close($client); + } else if (in_array($client->socket, $selread)) { + if (!client_read($client)) { + client_close($client); + } + } + } + } +} + +function client_accept($listener) { + global $clients; + $client = new Client(); + $client->socket = socket_accept($listener); + if (!$client->socket) return; + if (!socket_getpeername($client->socket, &$addr, &$port)) $addr = 'unknown'; + fprintf(STDERR, 'Accepted client '.$addr.':'.$port."\n"); + client_send($client, ':server NOTICE AUTH :*** Not looking up your hostname, we do not care.'); + client_send($client, ':server NOTICE AUTH :*** Not checking ident, are you serious?'); + $clients[(int)($client->socket)] = $client; +} + +function client_close($client, $reason = '') { + global $clients, $channels; + socket_close($client->socket); + array_remove($clients, $client); + $users = array(); + foreach ($client->channels as $ch) { + array_remove($ch->users, $client); + if (count($ch->users)) { + foreach ($ch->users as $user) if (!in_array($user, $users)) $users[] = $user; + } elseif (!$ch->persistent) { + array_remove($channels, $ch); + } + } + foreach ($users as $user) client_send($user, ':'.$client->name.' QUIT :'.$reason); +} + +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']); + channel_send($ch, NULL, ':'.$from.' PRIVMSG '.$ch->name.' :'.$message); + } + } + 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 array_remove(&$arr, $item) { + foreach ($arr as $key => $value) if ($value === $item) unset($arr[$key]); +} + +function find_nick($nick) { + global $clients; + $nick = strtoupper($nick); + foreach ($clients as $client) if (strtoupper($client->name) == $nick) return $client; + return FALSE; +} + +function client_read($client) { + $newdata = socket_read($client->socket, 1024); + if ($newdata === FALSE || !strlen($newdata)) return FALSE; + $client->readbuffer .= $newdata; + $offset = 0; + $len = strlen($client->readbuffer); + while ($offset < $len) { + $posa = strpos($client->readbuffer, "\n", $offset); + $posb = strpos($client->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($client->readbuffer, $offset, $pos - $offset); + if (strlen($line)) { + if (!client_process($client, $line)) return FALSE; + } + $offset = $pos + 1; + } + if ($offset == $len) { + $client->readbuffer = ''; + } else if ($offset != 0) { + $client->readbuffer = substr($client->readbuffer, $offset); + } + return TRUE; +} + +function client_process($client, $line) { + $partsa = explode(' :', $line, 2); + $parts = explode(' ', $partsa[0]); + $command = array_shift(&$parts); + if (count($partsa) > 1) array_push(&$parts, $partsa[1]); + $clientname = $client->name; + switch (strtoupper($command)) { + case 'NICK': //Nickname change: newnick = $parts[0] + if (find_nick($parts[0])) { + client_sendnumeric($client, 433, $parts[0].' :Nickname in use'); + } elseif (!preg_match('/^[a-zA-Z0-9\-_]+$/', $parts[0])) { + client_sendnumeric($client, 432, $parts[0].' :Erroneous nickname'); + } elseif ($client->name === NULL) { + $client->name = $parts[0]; + client_sendnumeric($client, 001, ':Welcome to the Internet Relay Chat Network'); + client_sendnumeric($client, 002, ':Your host is UDPMSG3-IRCD'); + client_sendnumeric($client, 003, ':This server was created some time ago'); + client_sendnumeric($client, 004, 'irc.udpmsg3.local 1.0 + +'); + client_sendnumeric($client, 005, 'NICKLEN=16 CHANNELLEN=16 CHANTYPES=# NETWORK=UDPMSG3 :are supported by this server'); + client_sendnumeric($client, 251, ':There are '.count($GLOBALS['clients']).' users and some invisible on a bunch of servers'); + client_sendnumeric($client, 252, '0 :operator(s) online'); + client_sendnumeric($client, 254, count($GLOBALS['channels']).' :channels formed'); + client_sendnumeric($client, 255, ':I have a bunch of clients and a bunch of servers'); + client_sendnumeric($client, 265, ':Current Local Users: undisclosed'); + client_sendnumeric($client, 266, ':Current Global Users: unknown'); + client_sendnumeric($client, 375, ':- server Message of the Day - '); + client_sendnumeric($client, 372, ':- No message, sorry.'); + client_sendnumeric($client, 376, ':End of /MOTD command.'); + } else { + $users = array($client); + foreach ($client->channels as $ch) foreach ($ch->users as $user) if (!in_array($user, $users)) $users[] = $user; + foreach ($users as $user) client_send($user, ':'.$client->name.' NICK :'.$parts[0]); + $client->name = $parts[0]; + } + break; + case 'USER': + case 'PASS': + break; + default: + if ($client->name === NULL) { + client_send($client, ':server 451 '.$command.' :You have not registered'); + break; + } + switch (strtoupper($command)) { + case 'PING': + client_send($client, 'PONG :'.$parts[0]); + break; + case 'PRIVMSG': + case 'NOTICE': + $channels = explode(',', $parts[0]); + foreach ($channels as $chn) { + if ($chn[0] == '#') { + if (!array_key_exists($chn, $client->channels)) { + client_sendnumeric($client, 404, $chn.' :Can not send to channel'); + } else { + channel_sendmsg($client->channels[$chn], $client, $parts[1]); + } + } else { + if ($ch = find_nick($chn)) { + client_send($ch, ':'.$client->name.' PRIVMSG '.$chn.' :'.$parts[1]); + } else { + client_sendnumeric($client, 401, $chn.' :No such nickname'); + } + } + } + break; + case 'JOIN': + $channels = explode(',', $parts[0]); + foreach ($channels as $chn) { + if (!preg_match('/^#[a-zA-Z0-9\-_]+$/', $chn)) { + client_sendnumeric($client, 403, $chn.' :No such channel'); + continue; + } + $ch = getchannel($chn); + if (!isset($client->channels[$chn])) { + $client->channels[$chn] = $ch; + $ch->users[] = $client; + print('JOIN '.$ch->name.': '.$client->name."\n"); + channel_send($ch, NULL, ':'.$client->name.'!user@host JOIN :'.$ch->name); + } + client_sendnames($ch, $client); + } + break; + case 'PART': + $channels = explode(',', $parts[0]); + foreach ($channels as $chn) { + if (!isset($client->channels[$chn])) continue; + $ch = $client->channels[$chn]; + print('PART '.$ch->name.': '.$client->name."\n"); + channel_send($ch, NULL, ':'.$client->name.' PART :'.$ch->name); + unset($client->channels[$chn]); + array_remove(&$ch->users, $client); + if (!count($ch->users) && !$ch->persistent) array_remove($GLOBALS['channels'], $ch); + } + break; + case 'QUIT': + client_close($client, isset($parts[0]) ? 'Quit: '.$parts[0] : 'Quit'); + break; + case 'NAMES': + $channels = explode(',', $parts[0]); + foreach ($channels as $chn) { + if (!isset($client->channels[$chn])) { + client_sendnumeric($client, 403, $chn.' :No such channel'); + } else { + client_sendnames($client->channels[$chn], $client); + } + } + break; + case 'LIST': + client_sendnumeric($client, 321, 'Channel :Users Name'); + foreach ($GLOBALS['channels'] as $ch) { + client_sendnumeric($client, 322, $ch->name.' '.count($ch->users).' :'); + } + client_sendnumeric($client, 323, 'End of /LIST'); + break; + case 'MODE': + case 'USER': + case 'USERHOST': + break; + default: + client_sendnumeric($client, 421, $command.' :Unknown command'); + } + } + return TRUE; +} + +function client_sendnames($ch, $client) { + $nicks = array(); + foreach ($ch->users as $u) if ($u->name !== NULL) $nicks[] = $u->name; + client_sendnumeric($client, 353, '= '.$ch->name.' :'.implode(' ', $nicks)); + client_sendnumeric($client, 366, $ch->name.' :End of /NAMES list.'); +} + +function client_sendnumeric($client, $num, $line) { + client_send($client, ':server '.str_pad($num, 3, '0', STR_PAD_LEFT).' '.$client->name.' '.$line); +} + +function client_send($client, $line) { + print('W: '.$line."\n"); + $line .= "\r\n"; + socket_send($client->socket, $line, strlen($line), 0); +} + +function channel_sendmsg($ch, $sender, $msg) { + global $udpmsg_config; + print('MESSAGE '.$ch->name.' <'.$sender->name.'> '.$msg."\n"); + channel_send($ch, $sender, ':'.$sender->name.' PRIVMSG '.$ch->name.' :'.$msg); + if (!$ch->udpmsg) return; + $umsg = array('CMD' => 'MSG', 'CHN' => $ch->udpmsg, 'MSG' => $msg, 'USR' => $sender->name, 'DUMMY' => rand(10000, 99999)); + if ($udpmsg_config['netname']) $umsg['NET'] = $udpmsg_config['netname']; + udpmsg_send($umsg); +} + +function channel_send($ch, $sender, $msg) { + foreach ($ch->users as $client) { + if ($client === $sender) continue; + client_send($client, $msg); + } +} diff -r 000000000000 -r dd81c38b513a ircrelay/config.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ircrelay/config.php Mon Feb 28 00:49:07 2011 +0100 @@ -0,0 +1,27 @@ + array( + 'host' => '127.0.0.1', + 'port' => 15387, + 'nickprefix' => 'E\\', + 'nickseparator' => '', + 'nicknetwork' => FALSE, + 'channels' => array( + 'chat/anonet' => 'channel1', + ), + ), + 'networks' => array( + 'NET1' => array( + 'name' => 'IRC network', + 'server' => 'irc.example.com', + 'port' => 6667, + 'nick' => 'Relay', + 'ident' => 'relay', + 'realname' => 'Relay bot', + 'channels' => array( + '#channel' => array('display' => 'NET1', 'link' => 'channel1'), + ), + ), +); + +include './relay.php'; diff -r 000000000000 -r dd81c38b513a ircrelay/ircrelay.php --- /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 +. 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 \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); + } + } +} diff -r 000000000000 -r dd81c38b513a ircrelay/ircrelay_admincmd.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ircrelay/ircrelay_admincmd.php Mon Feb 28 00:49:07 2011 +0100 @@ -0,0 +1,122 @@ +links as $link) $arr[] = $link->network->key.'/'.$link->name; + return 'OK '.implode('; ', $arr); + case 'join': + foreach ($chn->links as $link) if ($link->network->connected) network_send($link->network, 'JOIN '.$link->name); + return 'OK'; + case 'part': + foreach ($chn->links as $link) if ($link->network->connected) network_send($link->network, 'PART '.$link->name); + return 'OK'; + case 'rejoin': + foreach ($chn->links as $link) if ($link->network->connected) network_send($link->network, 'PART '.$link->name); + foreach ($chn->links as $link) if ($link->network->connected) network_send($link->network, 'JOIN '.$link->name); + return 'OK'; + case 'msg': + foreach ($chn->links as $link) if ($link->network->connected) network_send($link->network, 'PRIVMSG '.$link->name.' :'.implode(' ', array_slice($parts, 0))); + return 'OK'; + } + return 'ERR Unknown command'; +} +function admincmd_network($cmd, $net, $parts) { + global $channels; + switch (strtolower($cmd)) { + case 'disconnect': + network_reset($net); + return 'OK'; + case 'raw': + network_send($net, implode(' ', array_slice($parts, 0))); + return 'OK'; + case 'join': + network_send($net, 'JOIN '.$parts[0]); + return 'OK'; + case 'part': + network_send($net, 'PART '.$parts[0]); + return 'OK'; + case 'rejoin': + network_send($net, 'JOIN '.$parts[0]); + network_send($net, 'PART '.$parts[0]); + return 'OK'; + case 'msg': + network_send($net, 'PRIVMSG '.$parts[0].' :'.implode(' ', array_slice($parts, 1))); + return 'OK'; + case 'delete': + network_Reset($net); + unset($networks[$net->key]); + return 'OK'; + case 'info': + return 'OK key='.$net->key.'; name='.$net->name.'; server='.$net->server.'; port='.$net->port.'; nick='.$net->nick.'; active='.$net->active.'; af='.$net->addressfamily; + case 'status': + return 'OK connected='.$net->connected.'; connecting='.$net->connecting.'; timeout='.$net->timeout.'; nick='.$net->realnick; + case 'channels': + return 'OK '.implode('; ', array_keys($net->channels)); + case 'set': + $net->name = $parts[0]; + $net->server = $parts[1]; + $net->port = (int)$parts[2]; + $net->nick = $parts[3]; + return 'OK'; + case 'active': + $net->active = $parts[0]; + return 'OK'; + case 'ipv6': + $net->addressfamily = $parts[0] ? AF_INET6 : AF_INET; + return 'OK'; + case 'link': + if (isset($net->channels[$parts[0]])) return 'ERR Channel already linked'; + $link = new NetworkChannel(); + $link->name = $parts[0]; + $link->network = $net; + $link->channel = getchannel($parts[1]); + $link->display = implode(' ', array_slice($parts, 2)); + $link->channel->links[] = $link; + $net->channels[$link->name] = $link; + if ($net->connected && $link->name[0] == '#') network_send($net, 'JOIN '.$link->name); + return 'OK'; + case 'unlink': + if (!isset($net->channels[$parts[0]])) return 'ERR Channel not linked'; + $link = $net->channels[$parts[0]]; + unset($net->channels[$link->name]); + foreach ($link->channel->links as $k => $l) if ($l === $link) unset($link->channel->links[$k]); + if (!count($link->channel->links)) unset($channels[$link->channel->name]); + if ($net->connected && $link->name[0] == '#') network_send($net, 'PART '.$link->name); + return 'OK'; + case 'linkinfo': + if (!isset($net->channels[$parts[0]])) return 'ERR Channel not linked'; + $link = $net->channels[$parts[0]]; + return 'OK channel='.$link->channel->name.'; display='.$link->display; + } + return 'ERR Unknown command'; +} diff -r 000000000000 -r dd81c38b513a multiclientrelay/config.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/multiclientrelay/config.php Mon Feb 28 00:49:07 2011 +0100 @@ -0,0 +1,24 @@ + array( + 'host' => '127.0.0.1', + 'port' => 15387, + 'netname' => 'MyNet', + 'send_alive' => 1800, + ), + 'irc' => array( + 'host' => '127.0.0.1', + 'port' => 6667, + 'nick' => 'Relay', + 'ident' => 'relay', + 'realname' => 'Relay bot', + 'clientconnections' => 2, + 'joinpartmessages' => TRUE, + ), + 'channels' => array( + '#anonet2' => 'chat/anonet', + ), +); + +if (!defined('CONFIGFILE')) define('CONFIGFILE', __FILE__); +if (!defined('APP_LOADED')) include './multiclientrelay2.php'; diff -r 000000000000 -r dd81c38b513a multiclientrelay/multiclientrelay.php --- /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 @@ +. 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 \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); +} diff -r 000000000000 -r dd81c38b513a multiclientrelay/multiclientrelay2.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/multiclientrelay/multiclientrelay2.php Mon Feb 28 00:49:07 2011 +0100 @@ -0,0 +1,455 @@ +. 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); +if (!defined('CONFIGFILE')) define('CONFIGFILE', './config.php'); + +print("UCIS UDPMSG3 Multi client IRC relay (c) 2010 Ivo Smits \n"); +print("More information: http://wiki.ucis.nl/UDPMSG3\n"); +print("\n"); + +$irc_channels = array(); +$irc_users = array(); +$irc_clients = array(); + +$sock_fd_connecting = array(); +$sock_fd_connected = array(); +$sock_obj = array(); + +class Channel { + public $uname = NULL; + public $iname = NULL; +} +class User { + public $iname = NULL; + public $channels = array(); +} +class UDPMSGClient { + public $socket = NULL; + public $buffer = ''; + public $channels = array(); + public function select_read() { + if (!$this->udpmsg_read()) die("UDPMSG: Error: closing connection.\n"); + } + public function select_error() { die("UDPMSG: Error: select error.\n"); } + private function udpmsg_read() { + $msg = socket_read($this->socket, 1024); + if (!strlen($msg)) { + fprintf(STDERR, "UDPMSG: End of file\n"); + return FALSE; + } + $udpmsg_buffer = &$this->buffer; + $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]; + $this->udpmsg_process($ret); + } + return TRUE; + } + function udpmsg_process($ret) { + global $config, $ircbot; + if (!isset($ret['CHN']) || !isset($ret['CMD']) || !isset($ret['USR'])) return; + if (!array_key_exists($ret['CHN'], $this->channels)) return; + $ch = $this->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 = $this->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; + } + $client = $ircbot; + } + $client->irc_channel_send($ch, $msg); + break; + case 'JOIN': + if ($this->get_irc_client($usr, $ch) === NULL && $config['irc']['joinpartmessages']) $ircbot->irc_channel_send($ch, '* '.$usr.' has joined'.($net?' on network '.$net:'')); + break; + case 'PART': + if (!$this->irc_client_part($usr, $ch) && $config['irc']['joinpartmessages']) $ircbot->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 ($config['irc']['joinpartmessages']) $ircbot->irc_channel_send($ch, '* '.$usr.' has changed their nickname to '.$newnick.($net?' on network '.$net:'')); + $this->irc_client_part($usr, $ch); + $this->get_irc_client($usr, $ch); + break; + } + } + function send($arr) { + global $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($this->socket, $lens.$msg); + } + 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)) { + $client->irc_send('QUIT :No remaining channels.'); + $client->close(); + } else { + $client->irc_send('PART :'.$ch->iname); + } + } + function get_irc_client($name, $ch) { + global $irc_clients, $config; + 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; + $client->irc_send('JOIN :'.$ch->iname); + return NULL; + } + return $client; + } +} +class IRCClientBase { + public $iname = NULL; + public $channels = array(); + public $socket = NULL; + public $buffer = ''; + public $connected = FALSE; + public $identified = FALSE; + public function irc_read() { + $newdata = socket_read($this->socket, 1024); + if ($newdata === FALSE || !strlen($newdata)) return FALSE; + $irc_buffer = $this->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)) { + 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]; + if (!$this->process($sender, $sendernick, $command, $parts)) return FALSE; + } + $offset = $pos + 1; + } + $this->buffer = ($offset == $len) ? '' : substr($irc_buffer, $offset); + return TRUE; + } + public function irc_send($line) { + print('IW: '.$line."\n"); + $line .= "\r\n"; + socket_send($this->socket, $line, strlen($line), 0); + } + public function irc_channel_send($ch, $msg) { + if (!$this->identified) return; + $this->irc_send('PRIVMSG '.$ch->iname.' :'.$msg); + } +} +class MasterClient extends IRCClientBase { + public function select_error() { die("IRC: Error: select error.\n"); } + public function select_read() { + if (!$this->irc_read()) die("IRC: read error.\n"); + } + public function process($sender, $sendernick, $command, $parts) { + global $irc_channels, $config, $irc_users, $udpmsg; + switch (strtoupper($command)) { + case '001': //Welcome + $this->iname = $parts[0]; + $this->identified = TRUE; + irc_user_add($this, TRUE); + foreach ($irc_channels as $ch) $this->irc_send("JOIN :".$ch->iname); + break; + case '433': //Nickname in use + $this->irc_send("NICK ".$config['irc']['nick'].rand(100,999)); + break; + case 'PING': + $this->irc_send('PONG :'.$parts[0]); + break; + case 'NICK': //Nickname change + //if (strcasecmp($sendernick, $this->iname)) $this->iname = $parts[0]; + $usr = $this->irc_getuser($sendernick, FALSE); + if ($usr === NULL) break; + $this->irc_userchannels($usr, array('CMD' => 'NICK', 'NEWNICK' => $parts[0])); + irc_user_delete($usr); + $usr->iname = $parts[0]; + irc_user_add($usr); + break; + case 'JOIN': + if (!strcasecmp($sendernick, $this->iname)) break; + $chs = explode(',', $parts[0]); + $usr = NULL; + foreach ($chs as $chn) { + $ch = $this->irc_getchannel($chn); + if ($ch === NULL) continue; + if ($usr === NULL) $usr = $this->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 = $this->irc_getchannel($parts[2]); + if ($ch === NULL) break; + $partsa = explode(' ', $parts[3]); + foreach ($partsa as $un) { + if (!strlen($un)) continue; + $usr = $this->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 = $this->irc_getuser($sendernick, FALSE); + if ($usr === NULL) break; + $chs = explode(',', $parts[0]); + foreach ($chs as $chn) $this->irc_part($usr, $chn); + break; + case 'KICK': + $usr = $this->irc_getuser($parts[1], FALSE); + if (is_a($usr, 'Client')) break; + $this->irc_part($usr, $parts[0]); + break; + case 'QUIT': + $usr = $this->irc_getuser($sendernick, FALSE); + if ($usr === NULL) break; + if (is_a($usr, 'Client')) break; + $this->irc_userchannels($usr, array('CMD' => 'PART')); + irc_user_delete($usr, TRUE); + break; + case 'PRIVMSG': + case 'NOTICE': + $ch = $this->irc_getchannel($parts[0]); + if (is_a($this->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_userchannels($usr, $msg) { + global $udpmsg; + $msg['USR'] = $usr->iname; + foreach ($usr->channels as $ch) { + $msg['CHN'] = $ch->uname; + $udpmsg->send($msg); + } + } + function irc_part($usr, $chn) { + global $udpmsg; + $ch = $this->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)); + if (!count($usr->channels) && is_a($usr, 'User')) irc_user_delete($user, 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_user_add($usr, TRUE); + return $usr; + } +} +class Client extends IRCClientBase { + public $uname = NULL; + public $joinchannels = array(); + public function select_error() { + $this->close(); + } + public function select_write() { + $this->connected = TRUE; + $this->irc_send('USER '.$this->iname.' host server :'.$this->iname); + $this->irc_send('NICK '.$this->iname); + } + public function select_read() { + if (!$this->irc_read()) { + print("IRCC: read error.\n"); + $this->close(); + } + } + public function process($sender, $sendernick, $command, $parts) { + switch (strtoupper($command)) { + case '001': //Welcome + $this->iname = $parts[0]; + $this->identified = TRUE; + irc_user_add($this, TRUE); + foreach ($this->joinchannels as $ch) $this->irc_send("JOIN :".$ch->iname); + break; + case '433': //Nickname in use + $this->irc_send("NICK ".$this->iname.rand(100,999)); + break; + case 'PING': + $this->irc_send('PONG :'.$parts[0]); + break; + //Other messages are also received and processed by the master relay bot, so we ignore them here + } + return TRUE; + } + public function close() { + global $irc_clients; + unset($irc_clients[$this->uname]); + if ($this->identified) irc_user_delete($this, TRUE); + socket_close($this->socket); + socket_delete($this); + } +} + +srand(); + +print("Loading configuration...\n"); +if (!isset($config)) require constant('CONFIGFILE'); +if (!isset($config['irc']['joinpartmessages'])) $config['irc']['joinpartmessages'] = TRUE; +if (!isset($config['udpmsg']['send_alive'])) $config['udpmsg']['send_alive'] = 1800; + +$udpmsg = new UDPMSGClient(); +$udpmsg->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); +socket_connect($udpmsg->socket, $config['udpmsg']['host'], $config['udpmsg']['port']); +socket_add_connected($udpmsg); + +$ircbot = new MasterClient(); +$ircbot->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); +socket_connect($ircbot->socket, $config['irc']['host'], $config['irc']['port']); +socket_add_connected($ircbot); +$ircbot->irc_send('USER '.$config['irc']['ident'].' host server :'.$config['irc']['realname']); +$ircbot->irc_send('NICK '.$config['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"); + +function socket_add_connecting($obj) { + global $sock_fd_connecting, $sock_obj; + $sock_fd_connecting[(int)$obj->socket] = $obj->socket; + $sock_obj[(int)$obj->socket] = $obj; +} +function socket_add_connected($obj) { + global $sock_fd_connected, $sock_obj; + $sock_fd_connected[(int)$obj->socket] = $obj->socket; + $sock_obj[(int)$obj->socket] = $obj; +} +function socket_delete($obj) { + global $sock_fd_connecting, $sock_fd_connected, $sock_obj; + unset($sock_fd_connecting[(int)$obj->socket]); + unset($sock_fd_connected[(int)$obj->socket]); + unset($sock_obj[(int)$obj->socket]); +} + +function irc_user_add($user, $replace = FALSE) { + global $irc_users; + $name = strtoupper($user->iname); + if (!$replace && isset($irc_users[$name])) return FALSE; + $irc_users[$name] = $user; + return TRUE; +} +function irc_user_delete($user, $checkobject = FALSE) { + global $irc_users; + $name = strtoupper(is_object($user) ? $user->iname : $user); + if (!isset($irc_users[$name])) return TRUE; + if ($checkobject && $irc_users[$name] !== $user) return FALSE; + unset($irc_users[$name]); + return TRUE; +} + +$atime = 0; +print("Running\n"); +while (TRUE) { + $selread = $sock_fd_connected; + $selwrite = $sock_fd_connecting; + $selerror = array_merge($selread, $selwrite); + socket_select(&$selread, &$selwrite, &$selerror, 120); + foreach ($selread as $socket) if (isset($sock_obj[(int)$socket])) $sock_obj[(int)$socket]->select_read($socket); + foreach ($selwrite as $socket) if (isset($sock_obj[(int)$socket])) $sock_obj[(int)$socket]->select_write($socket); + foreach ($selerror as $socket) if (isset($sock_obj[(int)$socket])) $sock_obj[(int)$socket]->select_error($socket); + + $ctime = time(); + if ($atime < $ctime) { + foreach ($irc_users as $usr) if (is_a($usr, 'IUser')) $ircbot->irc_userchannels($usr, array('CMD' => 'ALIVE')); + $atime = $ctime + $config['udpmsg']['send_alive']; + } +} diff -r 000000000000 -r dd81c38b513a simpleircd/config.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/simpleircd/config.php Mon Feb 28 00:49:07 2011 +0100 @@ -0,0 +1,14 @@ + array( + 'host' => '1.3.3.8', + 'port' => 15387, + 'nickprefix' => 'E\\', + 'nickseparator' => '', + 'nicknetwork' => FALSE, + 'netname' => 'sIRCd', + 'chprefix' => 'chat/', + ), + 'channels' => array('#anonet'), +); + diff -r 000000000000 -r dd81c38b513a simpleircd/simpleircd.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/simpleircd/simpleircd.php Mon Feb 28 00:49:07 2011 +0100 @@ -0,0 +1,290 @@ +#!/usr/bin/php +. 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.*/ + +srand(); + +$listchannels = array(); +$channels = array(); + +$udpmsg = NULL; +$udpmsg_buffer = ''; + +$client_name = NULL; +$client_buffer = ''; + +srand(); + +require 'config.php'; + +client_send(':server NOTICE AUTH :*** Not looking up your hostname, we do not care.'); +client_send(':server NOTICE AUTH :*** Not checking ident, are you serious?'); + +while ($client_name === NULL) { + $line = client_read_line(); + $partsa = explode(' :', $line, 2); + $parts = explode(' ', $partsa[0]); + $command = array_shift(&$parts); + if (count($partsa) > 1) array_push(&$parts, $partsa[1]); + switch (strtoupper($command)) { + case 'NICK': //Nickname change: newnick = $parts[0] + if (!preg_match('/^[a-zA-Z0-9\-_]+$/', $parts[0])) { + client_sendnumeric(432, $parts[0].' :Erroneous nickname'); + } else { + $client_name = $parts[0]; + client_sendnumeric(001, ':Welcome to the Internet Relay Chat Network'); + client_sendnumeric(002, ':Your host is UDPMSG3-IRCD'); + client_sendnumeric(003, ':This server was created some time ago'); + client_sendnumeric(004, 'irc.udpmsg3.local 1.0 + +'); + client_sendnumeric(005, 'NICKLEN=16 CHANNELLEN=16 CHANTYPES=# NETWORK=UDPMSG3 :are supported by this server'); + client_sendnumeric(251, ':There are 1 users and some invisible on a bunch of servers'); + client_sendnumeric(252, '0 :operator(s) online'); + client_sendnumeric(254, count($config['channels']).' :channels formed'); + client_sendnumeric(255, ':I have a bunch of clients and a bunch of servers'); + client_sendnumeric(265, ':Current Local Users: undisclosed'); + client_sendnumeric(266, ':Current Global Users: unknown'); + client_sendnumeric(375, ':- server Message of the Day - '); + client_sendnumeric(372, ':- No message, sorry.'); + client_sendnumeric(376, ':End of /MOTD command.'); + } + case 'USER': + case 'PASS': + break; + default: + client_sendnumeric(421, $command.' :Unknown command'); + } +} + +$udpmsg = stream_socket_client('tcp://'.$config['udpmsg']['host'].':'.$config['udpmsg']['port']); +if (!$udpmsg) { + client_send('ERROR :Could not connect to exchange.'); + die(); +} + +while (TRUE) { + $selwrite = NULL; + $selread = array($udpmsg, STDIN); + $selerror = array($udpmsg, STDIN); + stream_select(&$selread, &$selwrite, &$selerror, 10); + if (in_array($udpmsg, $selerror)) { + fclose($udpmsg); + client_send('ERROR :Lost connection to exchange.'); + die(); + } else { + if (in_array($udpmsg, $selread)) { + if (!udpmsg_read($udpmsg)) { + fclose($udpmsg); + client_send('ERROR :Error in exchange protocol.'); + die(); + } + } + } + if (in_array(STDIN, $selerror)) { + fclose($udpmsg); + client_send('ERROR :Error while reading from client.'); + die(); + } else if (in_array(STDIN, $selread)) { + if (!client_read()) { + fclose($udpmsg); + client_send('ERROR :End of file.'); + die(); + } + } +} + +function udpmsg_read() { + global $channels, $udpmsg, $udpmsg_buffer, $config; + $msg = fread($udpmsg, 1024); + if (!strlen($msg)) 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) 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 (!substr($ret['CHN'], 0, strlen($config['udpmsg']['chprefix']))) continue; + $chn = '#'.substr($ret['CHN'], strlen($config['udpmsg']['chprefix'])); + if (!in_array($chn, $channels)) continue; + if ($ret['CMD'] == 'MSG') { + if (!isset($ret['MSG'])) continue; + $from = preg_replace('/[^a-zA-Z0-9\-_]/', '', $ret['USR']); + if ($config['udpmsg']['nicknetwork'] && isset($ret['NET'])) $from = preg_replace('/[^a-zA-Z0-9\-_]/', '', $ret['NET']).$config['udpmsg']['nickseparator'].$from; + $from = $config['udpmsg']['nickprefix'].$from; + $message = preg_replace('/[\x00\x10-\x13]/', '', $ret['MSG']); + client_send(':'.$from.' PRIVMSG '.$chn.' :'.$message); + } + } + return TRUE; +} + +function udpmsg_send($arr) { + global $udpmsg; + $tmp = array(); + foreach ($arr as $key => $value) { $tmp[] = $key; $tmp[] = $value; } + $tmp[] = ''; + $msg = implode("\0", $tmp); + $len = strlen($msg); + if ($len > 1024) return; + $lens = chr(floor($len / 256)).chr($len % 256); + fwrite($udpmsg, $lens.$msg); +} + +function array_remove(&$arr, $item) { + foreach ($arr as $key => $value) if ($value === $item) unset($arr[$key]); +} + +function client_read() { + global $client_buffer; + $newdata = fread(STDIN, 1024); + if ($newdata === FALSE || !strlen($newdata)) return FALSE; + $client_buffer .= $newdata; + $offset = 0; + $len = strlen($client_buffer); + while ($offset < $len) { + $posa = strpos($client_buffer, "\n", $offset); + $posb = strpos($client_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($client_buffer, $offset, $pos - $offset); + if (strlen($line)) { + if (!client_process($line)) return FALSE; + } + $offset = $pos + 1; + } + if ($offset == $len) { + $client_buffer = ''; + } else if ($offset != 0) { + $client_buffer = substr($client_buffer, $offset); + } + return TRUE; +} + +function client_process($line) { + global $client_name, $config; + $partsa = explode(' :', $line, 2); + $parts = explode(' ', $partsa[0]); + $command = array_shift(&$parts); + if (count($partsa) > 1) array_push(&$parts, $partsa[1]); + switch (strtoupper($command)) { + case 'NICK': + if (!preg_match('/^[a-zA-Z0-9\-_]+$/', $parts[0])) { + client_sendnumeric(432, $parts[0].' :Erroneous nickname'); + } else { + $client_name = $parts[0]; + } + break; + case 'PING': + client_send('PONG :'.$parts[0]); + break; + case 'PRIVMSG': + case 'NOTICE': + $channels = explode(',', $parts[0]); + foreach ($channels as $chn) { + if ($chn[0] == '#') { + if (!in_array($chn, $GLOBALS['channels'])) { + client_sendnumeric(404, $chn.' :Can not send to channel'); + } else { + $umsg = array('CMD' => 'MSG', 'CHN' => $config['udpmsg']['chprefix'].substr($chn, 1), 'MSG' => $parts[1], 'USR' => $client_name, 'DUMMY' => rand(10000, 99999)); + if ($config['udpmsg']['netname']) $umsg['NET'] = $config['udpmsg']['netname']; + udpmsg_send($umsg); + } + } else { + client_sendnumeric(401, $chn.' :No such nickname'); + } + } + break; + case 'JOIN': + $channels = explode(',', $parts[0]); + foreach ($channels as $chn) { + if (!preg_match('/^#[a-zA-Z0-9\-_]+$/', $chn)) { + client_sendnumeric($client, 403, $chn.' :No such channel'); + continue; + } + if (!in_array($chn, $GLOBALS['channels'])) { + client_send(':'.$client_name.'!user@host JOIN :'.$chn); + $GLOBALS['channels'][] = $chn; + } + client_sendnumeric(353, '= '.$chn.' :'.$client_name); + client_sendnumeric(366, $chn.' :End of /NAMES list.'); + } + break; + case 'PART': + client_send(':'.$client_name.'!user@host PART :'.$chn); + array_remove($GLOBALS['channels'], $chn); + break; + case 'QUIT': + return FALSE; + case 'NAMES': + client_sendnumeric(353, '= '.$parts[0].' :'.$client_name); + client_sendnumeric(366, $parts[0].' :End of /NAMES list.'); + break; + case 'LIST': + client_sendnumeric($client, 321, 'Channel :Users Name'); + foreach ($GLOBALS['listchannels'] as $chn) { + client_sendnumeric(322, $chn.' 0 :'); + } + client_sendnumeric(323, 'End of /LIST'); + break; + case 'MODE': + case 'USER': + case 'USERHOST': + break; + default: + client_sendnumeric(421, $command.' :Unknown command'); + } + return TRUE; +} + +function client_sendnumeric($num, $line) { + global $client_name; + client_send(':server '.str_pad($num, 3, '0', STR_PAD_LEFT).' '.$client_name.' '.$line); +} + +function client_send($line) { + print($line."\r\n"); +} + +function client_read_line() { + $line = fgets(STDIN); + if (!strlen($line)) { + client_send('ERROR :End of file.'); + die(); + } + return rtrim($line, "\r\n\x0B"); +} diff -r 000000000000 -r dd81c38b513a singlerelay/config.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/singlerelay/config.php Mon Feb 28 00:49:07 2011 +0100 @@ -0,0 +1,20 @@ + array( + 'host' => '127.0.0.1', + 'port' => 15387, + 'netname' => 'MyNet', + ), + 'irc' => array( + 'host' => '127.0.0.1', + 'port' => 6667, + 'nick' => 'Relay', + 'ident' => 'relay', + 'realname' => 'Relay bot', + ), + 'channels' => array( + '#anonet' => 'chat/anonet', + ), +); + +include './singlerelay.php'; diff -r 000000000000 -r dd81c38b513a singlerelay/singlerelay.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/singlerelay/singlerelay.php Mon Feb 28 00:49:07 2011 +0100 @@ -0,0 +1,306 @@ +. 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 Single server IRC relay (c) 2010 Ivo Smits \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(); + +$udpmsg_channels = array(); +$udpmsg_buffer = ''; + +class Channel { + public $uname = NULL; + public $iname = NULL; +} +class User { + public $iname = NULL; + public $channels = 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"); +while (TRUE) { + $selread = array($udpmsg_socket, $irc_socket); + $selwrite = NULL; + $selerror = array($udpmsg_socket, $irc_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"); +} + +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 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 udpmsg_process($ret) { + global $udpmsg_channels; + 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']); + $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); + break; + case 'JOIN': + irc_channel_send($ch, '* '.$usr.' has joined'.($net?' on network '.$net:'')); + break; + case 'PART': + 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']); + 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; + 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; + 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': + irc_part(irc_getuser($parts[1], FALSE), $parts[0]); + break; + case 'QUIT': + $usr = irc_getuser($sendernick, FALSE); + if ($usr === NULL) return; + irc_userchannels($usr, array('CMD' => 'PART')); + unset($irc_users[strtoupper($usr->iname)]); + break; + case 'PRIVMSG': + case 'NOTICE': + $ch = irc_getchannel($parts[0]); + 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]); + 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); +} diff -r 000000000000 -r dd81c38b513a unrealircdlink/config.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/unrealircdlink/config.php Mon Feb 28 00:49:07 2011 +0100 @@ -0,0 +1,28 @@ + array( + 'host' => '127.0.0.1', + 'port' => 6667, + 'password' => 'yoursecretpassword', + 'name' => 'UDPMSG3.Mynet.org', + 'desc' => 'UDPMSG3 gateway', + 'nick_format' => 'prefix', + 'nick_in_use' => '', + 'nick_prefix' => 'UDPMSG\\', + 'bot_nick' => 'UDPMSG', + ), + 'udpmsg' => array( + 'host' => '127.0.0.1', + 'port' => 15387, + 'timeout_implicit' => 600, + 'timeout_explicit' => 3600, + 'send_alive' => 1800, + 'netname' => 'Mynet', + ), + 'channels' => array( + '#anonet' => 'chat/anonet', + ), +); + +if (!defined('CONFIGFILE')) define('CONFIGFILE', __FILE__); +if (!defined('APP_LOADED')) include './unrealircdlink.php'; diff -r 000000000000 -r dd81c38b513a unrealircdlink/unrealircdlink.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/unrealircdlink/unrealircdlink.php Mon Feb 28 00:49:07 2011 +0100 @@ -0,0 +1,413 @@ +. 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); +if (!defined('CONFIGFILE')) define('CONFIGFILE', './config.php'); + +print("UCIS IRC Relay bot (c) 2010 Ivo Smits \n"); +print("More information: http://wiki.qontrol.nl/IRCRelay\n"); +print("\n"); + +class UUser { + public $iname = NULL; + public $unet = NULL; + public $uname = NULL; + public $channels = array(); + public $seen = 0; + public $explicit = FALSE; +} +class IUser { + public $iname = NULL; + public $channels = array(); +} +class Channel { + public $name = NULL; + public $iname = NULL; + public $uname = NULL; +} + +$iusers = array(); +$uusers = array(); +$ichannels = array(); +$uchannels = array(); + +$ircd_socket = NULL; +$ircd_buffer = ''; +$ircd_name = ''; + +$udpmsg_socket = NULL; +$udpmsg_buffer = ''; +$udpmsg_config = array(); + +$bot_nick = NULL; + +srand(); + +print("Loading configuration...\n"); +if (!isset($config)) require constant('CONFIGFILE'); + +$ircd_socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); +socket_connect($ircd_socket, $config['ircd']['host'], $config['ircd']['port']); +$ircd_name = $config['ircd']['name']; +$bot_nick = $config['ircd']['bot_nick']; +if (!strlen($bot_nick)) $bot_nick = NULL; +ircd_send('PROTOCTL'); +ircd_send('PASS :'.$config['ircd']['password']); +ircd_send('SERVER '.$ircd_name.' 0 0 :'.$config['ircd']['desc']); +ircd_send('AO 0 0 0 0 - 0 :Kwaaknet.org'); +if ($bot_nick) ircd_send('NICK '.$bot_nick.' 0 0 gateway '.$ircd_name.' '.$ircd_name.' 0 :'.$config['ircd']['desc']); +ircd_send('ES'); +$udpmsg_socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); +socket_connect($udpmsg_socket, $config['udpmsg']['host'], $config['udpmsg']['port']); +foreach ($config['channels'] as $iname => $uname) { + $ch = new Channel(); + $ch->iname = $iname; + $ch->uname = $uname; + $ch->name = $iname; + $uchannels[$ch->uname] = $ch; + $ichannels[strtoupper($ch->iname)] = $ch; + if ($bot_nick) ircd_send(':'.$bot_nick.' JOIN :'.$ch->iname); +} +$iusers[strtoupper($bot_nick)] = new IUser(); +$iusers[strtoupper($bot_nick)]->iname = $bot_nick; + +function reconfigure() { + global $config; + include constant('CONFIGFILE'); +} + +$ntime = 0; +$atime = 0; +print("Running\n"); +while (TRUE) { + $selread = array($udpmsg_socket, $ircd_socket); + $selwrite = NULL; + $selerror = array($udpmsg_socket, $ircd_socket); + socket_select(&$selread, &$selwrite, &$selerror, 60); + if (in_array($udpmsg_socket, $selerror)) die("UDPMSG: Error: select error.\n"); + if (in_array($ircd_socket, $selerror)) die("IRCd: Error: select error.\n"); + if (in_array($udpmsg_socket, $selread) && !udpmsg_read()) die("UDPMSG: Error: closing connection.\n"); + if (in_array($ircd_socket, $selread) && !ircd_read()) die("IRCd: read error.\n"); + $ctime = time(); + if ($ntime < $ctime) { + foreach ($uusers as $usr) { + if ($usr->seen > $ctime - $config['udpmsg'][$usr->explicit ? 'timeout_explicit' : 'timeout_implicit']) continue; + unset($uusers[$usr->uname]); + unset($iusers[strtoupper($usr->iname)]); + ircd_send(':'.$usr->iname.' QUIT :Inactivity'); + } + $ntime = $ctime + 120; //min($config['udpmsg']['timeout_explicit'], $config['udpmsg']['timeout_implicit']); + } + if ($atime < $ctime) { + foreach ($iusers as $usr) if (is_a($usr, 'IUser')) ircd_userchannels($usr, array('CMD' => 'ALIVE')); + $atime = $ctime + (isset($config['udpmsg']['send_alive']) ? $config['udpmsg']['send_alive'] : 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 ircd_read() { + global $ircd_socket, $ircd_buffer; + $newdata = socket_read($ircd_socket, 1024); + if ($newdata === FALSE || !strlen($newdata)) return FALSE; + $ircd_buffer .= $newdata; + $offset = 0; + $len = strlen($ircd_buffer); + while ($offset < $len) { + $posa = strpos($ircd_buffer, "\n", $offset); + $posb = strpos($ircd_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($ircd_buffer, $offset, $pos - $offset); + if (strlen($line) && !ircd_process($line)) { + fprintf(STDERR, "IRCd: process error\n"); + return FALSE; + } + $offset = $pos + 1; + } + $ircd_buffer = ($offset == $len) ? '' : substr($ircd_buffer, $offset); + return TRUE; +} + +function ircd_send($line) { + global $ircd_socket; + print('IW: '.$line."\n"); + $line .= "\r\n"; + socket_send($ircd_socket, $line, strlen($line), 0); +} + +function udpmsg_process($ret) { + global $uchannels; + if (!isset($ret['CHN']) || !isset($ret['CMD']) || !isset($ret['USR'])) return; + if (!array_key_exists($ret['CHN'], $uchannels)) return; + $ch = $uchannels[$ret['CHN']]; + $net = isset($ret['NET']) ? $ret['NET'] : NULL; + switch ($ret['CMD']) { + case 'MSG': + if (!isset($ret['MSG'])) break; + $usr = udpmsg_join($ret['USR'], $ch, $net); + ircd_send(':'.$usr->iname.' PRIVMSG '.$ch->iname.' :'.preg_replace('/[\x00\x10-\x13]/', '', $ret['MSG'])); + break; + case 'ALIVE': + case 'JOIN': + udpmsg_join($ret['USR'], $ch, $net, TRUE); + break; + case 'PART': + udpmsg_part($ret['USR'], $ch); + break; + case 'NICK': + udpmsg_part($ret['USR'], $ch); + if (!isset($ret['NEWNICK'])) break; + udpmsg_join($ret['NEWNICK'], $ch, $net, TRUE); + break; + } +} + +function udpmsg_join($name, $ch, $net = NULL, $explicit = FALSE) { + $usr = udpmsg_getuser($name, TRUE, $net, $explicit); + if (array_key_exists($ch->name, $usr->channels)) return $usr; + $usr->channels[$ch->name] = $ch; + ircd_send(':'.$usr->iname.' JOIN :'.$ch->iname); + return $usr; +} + +function udpmsg_part($name, $ch) { + global $uusers, $iusers; + $usr = udpmsg_getuser($name, FALSE); + if ($usr === NULL || !array_key_exists($ch->name, $usr->channels)) return; + unset($usr->channels[$ch->name]); + if (count($usr->channels)) { + ircd_send(':'.$usr->iname.' PART :'.$ch->iname); + } else { + unset($uusers[$usr->uname]); + unset($iusers[strtoupper($usr->iname)]); + ircd_send(':'.$usr->iname.' QUIT :No more channels'); + } +} + +function udpmsg_getuser($nick, $create = FALSE, $net = NULL, $explicit = FALSE) { + global $uusers, $iusers, $ircd_name; + if (array_key_exists($nick, $uusers)) { + $usr = $uusers[$nick]; + } else { + if (!$create) return NULL; + $usr = new UUser(); + $usr->uname = $nick; + $usr->unet = $net; + $usr->explicit = $explicit; + $usr->iname = udpmsg_getnick($usr, $nick); + $ident = preg_replace('/[^a-zA-Z0-9\-_]/', '', $usr->uname); + if (!strlen($ident)) $ident = 'unknown'; + $net = preg_replace('/[^a-zA-Z0-9\-_]/', '', $net); + if (!strlen($net)) $net = 'unknown'; + ircd_send('NICK '.$usr->iname.' 0 0 '.$ident.' '.$net.'.udpmsg3 '.$ircd_name.' 0 :UDPMSG3 user'); + $uusers[$nick] = $iusers[strtoupper($usr->iname)] = $usr; + } + $usr->seen = time(); + return $usr; +} + +function udpmsg_getnick($usr, $req) { + global $iusers, $config; + $nick = preg_replace('/[^a-zA-Z0-9\-_]/', '', $req); + if (!strlen($nick)) $nick = 'NoNick'; + switch ($config['ircd']['nick_format']) { + case 'plain': break; + case 'prefix': $nick = $config['ircd']['nick_prefix'].$nick; break; + case 'suffix': $nick = $nick.$config['ircd']['nick_suffix']; break; + } + if (isset($iusers[strtoupper($nick)])) { + switch ($config['ircd']['nick_in_use']) { + case 'prefix': $nick = $config['ircd']['nick_prefix'].$nick; break; + case 'suffix': $nick = $nick.$config['ircd']['nick_suffix']; break; + } + $bnick = $nick; + while (isset($iusers[strtoupper($nick)])) $nick = $bnick.rand(0, 999); + } + return $nick; +} + +function ircd_process($line) { + global $ichannels, $iusers, $ircd_name, $bot_nick; + 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 'REHASH': + if (strtoupper($parts[0]) != strtoupper($ircd_name)) break; + reconfigure(); + ircd_send('NOTICE '.$sendernick.' :Reloaded configuration.'); + break; + case 'PING': + ircd_send('PONG'.($sender?' '.$sender:'').' :'.$parts[0]); + break; + case 'SVSNICK': + $sendernick = array_shift($parts); + case 'NICK': + if ($sender === NULL) { + ircd_getuser($parts[0], TRUE); + } else { + $usr = ircd_getuser($sendernick, FALSE); + if ($usr === NULL) break; + if (is_a($usr, 'IUser')) ircd_userchannels($usr, array('CMD' => 'NICK', 'NEWNICK' => $parts[0])); + unset($iusers[strtoupper($usr->iname)]); + $usr->iname = $parts[0]; + $iusers[strtoupper($usr->iname)] = $usr; + } + break; + case 'PRIVMSG': case 'NOTICE': + if ($parts[1] == '--print-internals' && $parts[0][0] == '#') { + ircd_send(':'.$bot_nick.' PRIVMSG '.$parts[0].' :We have '.count($GLOBALS['iusers']).' IRC users of which '.count($GLOBALS['uusers']).' UDPMSG users, and '.count($GLOBALS['ichannels']).' channels.'); + break; + } + $ch = ircd_getchannel($parts[0]); + $usr = ircd_getuser($sendernick, FALSE); + if ($ch === NULL || $usr === NULL) break; + udpmsg_send(array('CMD' => 'MSG', 'CHN' => $ch->uname, 'MSG' => $parts[1], 'USR' => $usr->iname)); + break; + case 'SAJOIN': + case 'SVSJOIN': + $sendernick = array_shift($parts); + case 'JOIN': + $usr = ircd_getuser($sendernick, FALSE); + if ($usr === NULL) break; + $chs = explode(',', $parts[0]); + foreach ($chs as $chn) { + $ch = ircd_getchannel($chn); + if ($ch === NULL) break; + $usr->channels[$ch->name] = $ch; + if (is_a($usr, 'IUser')) udpmsg_send(array('CMD' => 'JOIN', 'CHN' => $ch->uname, 'USR' => $usr->iname)); + } + break; + case 'SAPART': + case 'SVSPART': + $sendernick = array_shift($parts); + case 'PART': + $usr = ircd_getuser($sendernick, FALSE); + if ($usr === NULL) break; + $chs = explode(',', $parts[0]); + foreach ($chs as $chn) ircd_part($usr, $chn); + break; + case 'KICK': + ircd_part(ircd_getuser($parts[1], FALSE), $parts[0]); + break; + case 'QUIT': + ircd_quit($sendernick); + break; + case 'KILL': + case 'SVSKILL': + ircd_quit($parts[0]); + break; + } + return TRUE; +} + +function ircd_quit($nick) { + global $iusers, $uusers; + $usr = ircd_getuser($nick, FALSE); + if ($usr === NULL) return; + if (is_a($usr, 'IUser')) ircd_userchannels($usr, array('CMD' => 'PART')); + if (is_a($usr, 'UUser')) unset($uusers[$usr->uname]); + unset($iusers[strtoupper($usr->iname)]); +} + +function ircd_getchannel($chn) { + global $ichannels; + $chn = strtoupper($chn); + if (!array_key_exists($chn, $ichannels)) return NULL; + return $ichannels[$chn]; +} +function ircd_getuser($nick, $create = TRUE) { + global $iusers; + $snick = strtoupper($nick); + if (array_key_exists($snick, $iusers)) return $iusers[$snick]; + if (!$create) return NULL; + $usr = new IUser(); + $usr->iname = $nick; + $iusers[$snick] = $usr; + return $usr; +} +function ircd_part($usr, $chn) { + $ch = ircd_getchannel($chn); + if ($ch === NULL || $usr === NULL) return; + unset($usr->channels[$ch->name]); + if (is_a($usr, 'IUser')) udpmsg_send(array('CMD' => 'PART', 'CHN' => $ch->uname, 'USR' => $usr->iname)); +} +function ircd_userchannels($usr, $msg) { + $msg['USR'] = $usr->iname; + foreach ($usr->channels as $ch) { + $msg['CHN'] = $ch->uname; + udpmsg_send($msg); + } +}