changeset 0:dd81c38b513a

Initial commit
author Ivo Smits <Ivo@UCIS.nl>
date Mon, 28 Feb 2011 00:49:07 +0100
parents
children 7f01316130e8
files client/client.php client/debugclient.php hub/config.php hub/hub.php ircd/config.php ircd/ircd.php ircrelay/config.php ircrelay/ircrelay.php ircrelay/ircrelay_admincmd.php multiclientrelay/config.php multiclientrelay/multiclientrelay.php multiclientrelay/multiclientrelay2.php simpleircd/config.php simpleircd/simpleircd.php singlerelay/config.php singlerelay/singlerelay.php unrealircdlink/config.php unrealircdlink/unrealircdlink.php
diffstat 18 files changed, 3533 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /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 @@
+<?php
+/* Copyright 2010 Ivo Smits <Ivo@UCIS.nl>. All rights reserved.
+   Redistribution and use in source and binary forms, with or without modification, are
+   permitted provided that the following conditions are met:
+
+   1. Redistributions of source code must retain the above copyright notice, this list of
+      conditions and the following disclaimer.
+
+   2. Redistributions in binary form must reproduce the above copyright notice, this list
+      of conditions and the following disclaimer in the documentation and/or other materials
+      provided with the distribution.
+
+   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR
+   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+   ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+   The views and conclusions contained in the software and documentation are those of the
+   authors and should not be interpreted as representing official policies, either expressed
+   or implied, of Ivo Smits.*/
+
+$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);
+}
+
--- /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 @@
+<?php
+/* Copyright 2010 Ivo Smits <Ivo@UCIS.nl>. All rights reserved.
+   Redistribution and use in source and binary forms, with or without modification, are
+   permitted provided that the following conditions are met:
+
+   1. Redistributions of source code must retain the above copyright notice, this list of
+      conditions and the following disclaimer.
+
+   2. Redistributions in binary form must reproduce the above copyright notice, this list
+      of conditions and the following disclaimer in the documentation and/or other materials
+      provided with the distribution.
+
+   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR
+   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+   ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+   The views and conclusions contained in the software and documentation are those of the
+   authors and should not be interpreted as representing official policies, either expressed
+   or implied, of Ivo Smits.*/
+
+$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);
+}
--- /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 @@
+<?php
+$config = array(
+	'tcplisten' => array(
+		array(),
+	), 'tcpconnect' => array(
+	), 'udppeer' => array(
+	),
+	'logsize' => 1024,
+);
+
+if (!defined('CONFIGFILE')) define('CONFIGFILE', __FILE__);
+if (!defined('APP_LOADED')) include './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 @@
+<?php
+/* Copyright 2010 Ivo Smits <Ivo@UCIS.nl>. All rights reserved.
+   Redistribution and use in source and binary forms, with or without modification, are
+   permitted provided that the following conditions are met:
+
+   1. Redistributions of source code must retain the above copyright notice, this list of
+      conditions and the following disclaimer.
+
+   2. Redistributions in binary form must reproduce the above copyright notice, this list
+      of conditions and the following disclaimer in the documentation and/or other materials
+      provided with the distribution.
+
+   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR
+   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+   ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+   The views and conclusions contained in the software and documentation are those of the
+   authors and should not be interpreted as representing official policies, either expressed
+   or implied, of Ivo Smits.*/
+
+if (defined('APP_LOADED')) return;
+define('APP_LOADED', TRUE);
+if (!defined('CONFIGFILE')) define('CONFIGFILE', './config.php');
+
+print("UCIS UDPMSG3 Hub (c) 2010 Ivo Smits <Ivo@UCIS.nl>\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();
+}
--- /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 @@
+<?php
+$config = array(
+	'udpmsg' => 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';
--- /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 @@
+<?php
+/* Copyright 2010 Ivo Smits <Ivo@UCIS.nl>. All rights reserved.
+   Redistribution and use in source and binary forms, with or without modification, are
+   permitted provided that the following conditions are met:
+
+   1. Redistributions of source code must retain the above copyright notice, this list of
+      conditions and the following disclaimer.
+
+   2. Redistributions in binary form must reproduce the above copyright notice, this list
+      of conditions and the following disclaimer in the documentation and/or other materials
+      provided with the distribution.
+
+   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR
+   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+   ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+   The views and conclusions contained in the software and documentation are those of the
+   authors and should not be interpreted as representing official policies, either expressed
+   or implied, of Ivo Smits.*/
+
+if (defined('app_loaded')) return;
+define('app_loaded', TRUE);
+if (!isset($config)) include './config.php';
+
+print("UCIS UDPMSG3 IRC server (c) 2010 Ivo Smits <Ivo@UCIS.nl>\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);
+	}
+}
--- /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 @@
+<?php
+$config = array(
+	'udpmsg' => 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';
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ircrelay/ircrelay.php	Mon Feb 28 00:49:07 2011 +0100
@@ -0,0 +1,457 @@
+#!/usr/bin/php
+<?php
+/* Copyright 2010 Ivo Smits <Ivo@UCIS.nl>. All rights reserved.
+   Redistribution and use in source and binary forms, with or without modification, are
+   permitted provided that the following conditions are met:
+
+   1. Redistributions of source code must retain the above copyright notice, this list of
+      conditions and the following disclaimer.
+
+   2. Redistributions in binary form must reproduce the above copyright notice, this list
+      of conditions and the following disclaimer in the documentation and/or other materials
+      provided with the distribution.
+
+   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR
+   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+   ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+   The views and conclusions contained in the software and documentation are those of the
+   authors and should not be interpreted as representing official policies, either expressed
+   or implied, of Ivo Smits.*/
+
+print("UCIS IRC Relay bot (c) 2010 Ivo Smits <Ivo@UCIS.nl>\n");
+print("More information: http://wiki.ucis.nl/IRCRelay\n");
+print("\n");
+
+if (defined('app_loaded')) return;
+define('app_loaded', TRUE);
+if (!isset($config)) include './config.php';
+
+class Network {
+	public $key = NULL;
+	public $name = NULL;
+	public $socket = NULL;
+	public $server = NULL;
+	public $port = 6667;
+	public $nick = NULL;
+	public $realnick = NULL;
+	public $connected = FALSE;
+	public $connecting = FALSE;
+	public $timeout = 0;
+	public $channels = array();
+	public $addressfamily = AF_INET;
+	public $active = FALSE;
+	public $ident = 'none';
+	public $realname = 'Not set - http://wiki.qontrol.nl/IRCRelay';
+	public $debug = FALSE;
+}
+class NetworkChannel {
+	public $network;
+	public $channel;
+	public $name;
+	public $display;
+}
+class Channel {
+	public $name = NULL;
+	public $udpmsg = FALSE;
+	public $links = array();
+	public function __construct($name) { $this->name = $name; }
+}
+
+$networks = array();
+$channels = array();
+$udpmsg = NULL;
+$udpmsg_config = NULL;
+$udpmsg_connected = FALSE;
+$udpmsg_timer = 0;
+$udpmsg_buffer = '';
+$udpmsg_channels = array();
+$status = NULL;
+$adminpass = NULL;
+
+srand();
+
+prepare();
+configure();
+init();
+readloop();
+
+function getchannel($name) {
+	global $channels;
+	if (array_key_exists($name, $channels)) return $channels[$name];
+	$ch = new Channel($name);
+	$channels[$name] = $ch;
+	return $ch;
+}
+function configure() {
+	global $networks, $config;
+	global $channels;
+	global $adminpass;
+	global $udpmsg, $udpmsg_config, $udpmsg_channels;
+
+	print("Parsing configuration...\n");
+	//require 'config.php';
+
+	if (isset($config['password'])) {
+		$adminpass = $config['password'];
+		require_once './ircrelay_admincmd.php';
+	}
+	if (isset($config['udpmsg'])) {
+		$udpmsg_config = $config['udpmsg'];
+		$udpmsg = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
+		socket_set_nonblock($udpmsg);
+		@socket_connect($udpmsg, $udpmsg_config['host'], $udpmsg_config['port']);
+		foreach ($udpmsg_config['channels'] as $chk => $chn) {
+			$ch = getchannel($chn);
+			$ch->udpmsg = $chk;
+			$udpmsg_channels[$chk] = $ch;
+		}
+		unset($udpmsg_config['channels']);
+	}
+	foreach ($config['networks'] as $key => $nc) {
+		$net = new Network();
+		$net->key = $key;
+		$net->server = $nc['server'];
+		$net->nick = $nc['nick'];
+		$net->name = isset($nc['name']) ? $nc['name'] : $key;
+		if (isset($nc['port'])) $net->port = $nc['port'];
+		if (isset($nc['ident'])) $net->ident = $nc['ident'];
+		if (isset($nc['realname'])) $net->realname = $nc['realname'];
+		if (isset($nc['ipv6']) && $nc['ipv6']) $net->addressfamily = AF_INET6;
+		if (isset($nc['debug']) && $nc['debug']) $net->debug = TRUE;
+		foreach ($nc['channels'] as $chn => $cf) {
+			$ch = getchannel($cf['link']);
+			$nch = new NetworkChannel();
+			$nch->name = $chn;
+			$nch->network = $net;
+			$nch->channel = $ch;
+			$nch->display = isset($cf['display']) ? $cf['display'] : $net->key;
+			$ch->links[] = $nch;
+			$net->channels[$chn] = $nch;
+		}
+		$networks[$key] = $net;
+		$net->active = TRUE;
+		print('NET '.$net->key.' create'."\n");
+	}
+}
+
+function prepare() {
+	global $channels, $status;
+	print("Starting IRC Relay...\n");
+	$ch = new Channel('status');
+	$channels[$ch->name] = $ch;
+	$status = $ch;
+}
+
+function init() {
+	global $networks;
+	print("Initializing...\n");
+}
+
+function readloop() {
+	global $networks, $udpmsg, $listener, $udpmsg_connected, $udpmsg_config, $udpmsg_timer;
+
+	$ltime = 0;
+
+	print("Running\n");
+	while (TRUE) {
+		$selread = array();
+		$selwrite = array();
+		$selerror = array();
+		$selcount = 0;
+
+		$ntime = time();
+		$tdiff = $ntime - $ltime;
+		$ltime = $ntime;
+
+		if ($udpmsg === NULL && $udpmsg_config['host'] !== NULL) {
+			$udpmsg_timer += $tdiff;
+			if ($udpmsg_timer >= 30) {
+				fprintf(STDERR, "UDPMSG: Reconnecting...\n");
+				$udpmsg = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
+				socket_set_nonblock($udpmsg);
+				@socket_connect($udpmsg, $udpmsg_config['host'], $udpmsg_config['port']);
+				$udpmsg_timer = 0;
+			}
+		}
+
+		if ($udpmsg !== NULL) {
+			$selread[] = $udpmsg;
+			$selerror[] = $udpmsg;
+			if (!$udpmsg_connected) $selwrite[] = $udpmsg;
+		}
+
+		foreach ($networks as $net) {
+			if ($net->connected) {
+				$selread[] = $net->socket;
+				$selerror[] = $net->socket;
+				$selcount++;
+			} else if ($net->connecting) {
+				$selwrite[] = $net->socket;
+				$selerror[] = $net->socket;
+				$selcount++;
+			} else if ($net->active) {
+				$net->timeout -= $tdiff;
+				if ($net->timeout <= 0) {
+					$net->connecting = TRUE;
+					print('NET '.$net->key.' connect '.$net->server.':'.$net->port."\n");
+					$net->socket = socket_create($net->addressfamily, SOCK_STREAM, SOL_TCP);
+					socket_set_nonblock($net->socket);
+					socket_connect($net->socket, $net->server, $net->port);
+					$selwrite[] = $net->socket;
+					$selerror[] = $net->socket;
+				}
+			}
+		}
+
+		if (!$selcount) {
+			sleep(1);
+			continue;
+		}
+
+		socket_select(&$selread, &$selwrite, &$selerror, 10);
+
+		if (in_array($udpmsg, $selerror)) {
+			fprintf(STDERR, "UDPMSG: Error: select error.\n");
+			socket_close($udpmsg);
+			$udpmsg = NULL;
+			$udpmsg_connected = FALSE;
+		} else {
+			if (in_array($udpmsg, $selwrite) && !$udpmsg_connected) {
+				fprintf(STDERR, "UDPMSG: Connected.\n");
+				$udpmsg_buffer = '';
+				$udpmsg_connected = TRUE;
+			}
+			if (in_array($udpmsg, $selread)) {
+				if (!udpmsg_read($udpmsg)) {
+					fprintf(STDERR, "UDPMSG: Error: closing connection.\n");
+					socket_close($udpmsg);
+					$udpmsg = NULL;
+					$udpmsg_connected = FALSE;
+				}
+			}
+		}
+
+		foreach ($networks as $net) {
+			if (in_array($net->socket, $selerror)) {
+				print('NET '.$net->key.' socket error'."\n");
+				network_reset($net);
+			} else if ($net->connected && in_array($net->socket, $selread)) {
+				if (!network_read($net)) {
+					print('NET '.$net->key.' read error'."\n");
+					network_reset($net);
+				}
+			} else if ($net->connecting && in_array($net->socket, $selwrite)) {
+				$net->connected = TRUE;
+				$net->connecting = FALSE;
+				print('NET '.$net->key.' identify '.$net->nick."\n");
+				network_send($net, 'USER '.$net->ident.' host server :'.$net->realname);
+				network_send($net, 'NICK '.$net->nick);
+			}
+		}
+	}
+}
+
+function udpmsg_read() {
+	global $channels, $clients, $udpmsg, $udpmsg_buffer, $udpmsg_channels, $udpmsg_config;
+	$msg = socket_read($udpmsg, 1024);
+	if (!strlen($msg)) {
+		fprintf(STDERR, "UDPMSG: End of file\n");
+		return FALSE;
+	}
+	$udpmsg_buffer .= $msg;
+	while (strlen($udpmsg_buffer) > 2) {
+		$len = ord($udpmsg_buffer[0]) * 256 + ord($udpmsg_buffer[1]);
+		if ($len <= 0 || $len > 1024) {
+			fprintf(STDERR, "UDPMSG: Error: protocol error\n");
+			return FALSE;
+		}
+		if (strlen($udpmsg_buffer) < 2 + $len) break;
+		$msg = substr($udpmsg_buffer, 2, $len);
+		$udpmsg_buffer = substr($udpmsg_buffer, 2 + $len);
+		$parts = explode("\0", $msg);
+		$ret = array();
+		for ($i = 0; $i < count($parts)-1; $i += 2) $ret[$parts[$i]] = $parts[$i+1];
+
+		if (!isset($ret['CHN']) || !isset($ret['CMD']) || !isset($ret['USR'])) continue;
+		if (!array_key_exists($ret['CHN'], $udpmsg_channels)) continue;
+		$ch = $udpmsg_channels[$ret['CHN']];
+		if ($ret['CMD'] == 'MSG') {
+			if (!isset($ret['MSG'])) continue;
+			$from = preg_replace('/[^a-zA-Z0-9\-_]/', '', $ret['USR']);
+			if ($udpmsg_config['nicknetwork'] && isset($ret['NET'])) $from = preg_replace('/[^a-zA-Z0-9\-_]/', '', $ret['NET']).$udpmsg_config['nickseparator'].$from;
+			$from = $udpmsg_config['nickprefix'].$from;
+			$message = preg_replace('/[\x00\x10-\x13]/', '', $ret['MSG']);
+			$prefix = '['.$from.'] ';
+			if (ord($message[0]) == 1) { //CTCP
+				if (substr($message, 1, 6) != 'ACTION') continue;
+				$message = chr(1).'ACTION '.$prefix.substr($message, 8, -1).chr(1);
+			} else {
+				$message = $prefix.$message;
+			}
+			channel_send($ch, $message, $udpmsg);
+		}
+	}
+	return TRUE;
+}
+
+function udpmsg_send($arr) {
+	global $udpmsg, $udpmsg_connected;
+	if (!$udpmsg || !$udpmsg_connected) return;
+	$tmp = array();
+	foreach ($arr as $key => $value) { $tmp[] = $key; $tmp[] = $value; }
+	$tmp[] = '';
+	$msg = implode("\0", $tmp);
+	$len = strlen($msg);
+	if ($len > 1024) {
+		fprintf(STDERR, "UDPMSG: Error: message too long!\n");
+		return;
+	}
+	$lens = chr(floor($len / 256)).chr($len % 256);
+	socket_write($udpmsg, $lens.$msg);
+}
+
+function network_reset($net) {
+	print('NET '.$net->key.' reset'."\n");
+	socket_close($net->socket);
+	$net->connected = FALSE;
+	$net->connecting = FALSE;
+	$net->readbuffer = '';
+	$net->timeout = 60;
+}
+
+function network_read($net) {
+	$newdata = socket_read($net->socket, 1024);
+	if ($newdata === FALSE || !strlen($newdata)) return FALSE;
+	$net->readbuffer .= $newdata;
+	$offset = 0;
+	$len = strlen($net->readbuffer);
+	while ($offset < $len) {
+		$posa = strpos($net->readbuffer, "\n", $offset);
+		$posb = strpos($net->readbuffer, "\r", $offset);
+		if ($posa !== FALSE && $posb !== FALSE) {
+			$pos = min($posa, $posb);
+		} else if ($posa !== FALSE) {
+			$pos = $posa;
+		} else if ($posb !== FALSE) {
+			$pos = $posb;
+		} else {
+			break;
+		}
+		$line = substr($net->readbuffer, $offset, $pos - $offset);
+		if (strlen($line)) {
+			if (!network_process($net, $line)) return FALSE;
+		}
+		$offset = $pos + 1;
+	}
+	if ($offset == $len) {
+		$net->readbuffer = '';
+	} else if ($offset != 0) {
+		$net->readbuffer = substr($net->readbuffer, $offset);
+	}
+	return TRUE;
+}
+
+function network_process($net, $line) {
+	global $adminpass, $udpmsg, $udpmsg_connected;
+	if ($net->debug) print('NET '.$net->key.' recv '.$line."\n");
+	$partsa = explode(' :', $line, 2);
+	$parts = explode(' ', $partsa[0]);
+	if ($parts[0][0] == ':') {
+		$sender = substr(array_shift(&$parts), 1);
+	} else {
+		$sender = NULL;
+	}
+	$command = array_shift(&$parts);
+	if (count($partsa) > 1) array_push(&$parts, $partsa[1]);
+	$partsa = explode('!', $sender);
+	$sendernick = $partsa[0];
+	switch (strtoupper($command)) {
+		case '001': //Welcome
+			$net->realnick = $parts[0];
+			print('NET '.$net->key.' welcome '.$net->realnick."\n");
+			foreach ($net->channels as $ch) network_send($net, "JOIN :".$ch->name);
+			break;
+		case '433': //Nickname in use
+			print('NET '.$net->key.' nickname_in_use'."\n");
+			network_send($net, "NICK ".$net->nick.rand(100,999));
+			break;
+		case 'NICK': //Nickname change
+			if (strcasecmp($sendernick, $net->realnick)) {
+				$net->realnick = $parts[0];
+				print('NET '.$net->key.' nickname '.$net->realnick."\n");
+			}
+			break;
+		case 'PING':
+			network_send($net, 'PONG :'.$parts[0]);
+			break;
+		case 'PRIVMSG':
+		case 'NOTICE':
+			if ($parts[0][0] == '#') {
+				$to = $parts[0];
+			} else {
+				$to = $sendernick;
+				if (!is_null($adminpass) && $parts[1][0] == '.' && substr($parts[1], 1, strlen($adminpass)) === $adminpass) {
+					$ret = admincmd(array_slice(explode(' ', $parts[1]), 1));
+					network_send($net, 'PRIVMSG '.$to.' :'.$ret);
+					break;
+				}
+			}
+			if (!array_key_exists($to, $net->channels)) {
+				print('NET '.$net->key.' privmsg '.$sendernick.' @ '.$to.' - '.$parts[1]."\n");
+				break;
+			}
+			$ch = $net->channels[$to];
+			$prefix = '['.$sendernick.(is_null($ch->display) ? '' : ' @ '.$ch->display).'] ';
+			if (ord($parts[1][0]) == 1) { //CTCP
+				if (substr($parts[1], 1, 6) != 'ACTION') break;
+				$msg = chr(1).'ACTION '.$prefix.substr($parts[1], 8, -1).chr(1);
+			} else {
+				$msg = $prefix.$parts[1];
+			}
+			channel_send($ch->channel, $msg, $ch, $command == 'NOTICE');
+			if ($udpmsg && $udpmsg_connected && $ch->channel->udpmsg) {
+				$msg = array('CMD' => 'MSG', 'CHN' => $ch->channel->udpmsg, 'MSG' => $parts[1], 'USR' => $sendernick, 'DUMMY' => rand(10000, 99999));
+				if (!is_null($ch->display)) $msg['NET'] = $ch->display;
+				udpmsg_send($msg);
+			}
+			break;
+		case '372': //Motd
+		case '376': //End of moth
+		case '002': //Your host
+		case '003': //This server
+		case '004': //Version
+		case '005': //Supported
+		case '422': //Motd missing
+		case '251': case '254': case '255': case '265': case '266': case '396': case '353': case '366':
+		case '252': case '375':
+		case 'MODE':
+		case 'JOIN':
+		case 'PART':
+		case 'QUIT':
+			break;
+		default:
+			print('NET '.$net->key.' unknown '.$line."\n");
+	}
+	return TRUE;
+}
+
+function network_send($net, $line) {
+	if ($net->debug) print('NET '.$net->key.' send '.$line."\n");
+	$line .= "\r\n";
+	socket_send($net->socket, $line, strlen($line), 0);
+}
+
+function channel_send($ch, $message, $source = NULL, $notice = FALSE) {
+	foreach ($ch->links as $link) {
+		if ($link->network->connected && $link !== $source) {
+			network_send($link->network, ($notice ? 'NOTICE ' : 'PRIVMSG ').$link->name.' :'.$message);
+		}
+	}
+}
--- /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 @@
+<?php
+function admincmd($parts) {
+	global $channels, $networks;
+	switch (strtolower($parts[0])) {
+		case 'die': exit(0);
+		case 'c': case 'chn': case 'chan': case 'channel':
+			switch (strtolower($parts[1])) {
+				case 'join': case 'part': case 'rejoin': case 'msg': case 'links':
+					if (!isset($channels[$parts[2]])) return 'ERR Channel does not exist';
+					return admincmd_channel($parts[1], $channels[$parts[2]], array_slice($parts, 3));
+				case 'list': return 'OK '.implode('; ', array_keys($channels));
+				case 'help':
+					return 'OK list; links [ch]; join [ch]; part [ch]; rejoin [ch]; msg [ch] [msg]; help';
+			}
+			break;
+		case 'n': case 'net': case 'network':
+			switch (strtolower($parts[1])) {
+				case 'disconnect': case 'raw': case 'join': case 'part': case 'rejoin': case 'msg': case 'delete': case 'info': case 'status': case 'channels': case 'set': case 'active': case 'ipv6': case 'link': case 'unlink': case 'linkinfo':
+					if (!isset($networks[$parts[2]])) return 'ERR Network does not exist';
+					return admincmd_network($parts[1], $networks[$parts[2]], array_slice($parts, 3));
+				case 'list': return 'OK '.implode('; ', array_keys($networks));
+				case 'help':
+					return 'OK list; disconnect [net]; raw [net] [text]; join [net] [ch]; part [net] [ch]; rejoin [net] [ch]; msg [net] [ch] [msg]; help; delete; info; status; channels; set [name] [server] [port] [nick]; active [enable]; ipv6 [enable]; link [name] [channel] [display]; unlink [name]; linkinfo [name]';
+			}
+			break;
+		case 'help':
+			return 'OK die; channel help; network help';
+	}
+	return 'ERR Unknown command';
+}
+function admincmd_channel($cmd, $chn, $parts) {
+	switch (strtolower($cmd)) {
+		case 'links':
+			$arr = array();
+			foreach ($chn->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';
+}
--- /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 @@
+<?php
+$config = array(
+	'udpmsg' => 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';
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/multiclientrelay/multiclientrelay.php	Mon Feb 28 00:49:07 2011 +0100
@@ -0,0 +1,465 @@
+<?php
+/* Copyright 2010 Ivo Smits <Ivo@UCIS.nl>. All rights reserved.
+   Redistribution and use in source and binary forms, with or without modification, are
+   permitted provided that the following conditions are met:
+
+   1. Redistributions of source code must retain the above copyright notice, this list of
+      conditions and the following disclaimer.
+
+   2. Redistributions in binary form must reproduce the above copyright notice, this list
+      of conditions and the following disclaimer in the documentation and/or other materials
+      provided with the distribution.
+
+   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR
+   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+   ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+   The views and conclusions contained in the software and documentation are those of the
+   authors and should not be interpreted as representing official policies, either expressed
+   or implied, of Ivo Smits.*/
+
+if (defined('app_loaded')) return;
+define('app_loaded', TRUE);
+
+print("UCIS UDPMSG3 Multi client IRC relay (c) 2010 Ivo Smits <Ivo@UCIS.nl>\n");
+print("More information: http://wiki.ucis.nl/UDPMSG3\n");
+print("\n");
+
+$irc_socket = NULL;
+$irc_nick = NULL;
+$irc_channels = array();
+$irc_buffer = '';
+$irc_connected = FALSE;
+$irc_users = array();
+$irc_clients = array();
+
+$udpmsg_channels = array();
+$udpmsg_buffer = '';
+
+class Channel {
+	public $uname = NULL;
+	public $iname = NULL;
+}
+class User {
+	public $iname = NULL;
+	public $channels = array();
+}
+class Client {
+	public $uname = NULL;
+	public $iname = NULL;
+	public $channels = array();
+	public $socket = NULL;
+	public $buffer = '';
+	public $connected = FALSE;
+	public $identified = FALSE;
+	public $joinchannels = array();
+}
+
+srand();
+
+print("Loading configuration...\n");
+if (!isset($config)) require './config.php';
+$udpmsg_socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
+socket_connect($udpmsg_socket, $config['udpmsg']['host'], $config['udpmsg']['port']);
+
+$irc_socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
+socket_connect($irc_socket, $config['irc']['host'], $config['irc']['port']);
+irc_send('USER '.$config['irc']['ident'].' host server :'.$config['irc']['realname']);
+$irc_nick = $config['irc']['nick'];
+irc_send('NICK '.$irc_nick);
+foreach ($config['channels'] as $iname => $uname) {
+	$ch = new Channel();
+	$ch->iname = $iname;
+	$ch->uname = $uname;
+	$irc_channels[strtoupper($ch->iname)] = $ch;
+	$udpmsg_channels[$ch->uname] = $ch;
+}
+
+print("Starting IRC Relay...\n");
+print("Running\n");
+$atime = 0;
+while (TRUE) {
+	$selread = array($udpmsg_socket, $irc_socket);
+	$selwrite = array();
+	$selerror = array($udpmsg_socket, $irc_socket);
+	foreach ($irc_clients as $client) {
+		if ($client->connected) {
+			$selread[] = $client->socket;
+			$selerror[] = $client->socket;
+		} else {
+			$selwrite[] = $client->socket;
+			$selerror[] = $client->socket;
+		}
+	}
+	socket_select(&$selread, &$selwrite, &$selerror, 120);
+	if (in_array($udpmsg_socket, $selerror)) die("UDPMSG: Error: select error.\n");
+	if (in_array($irc_socket, $selerror)) die("IRC: Error: select error.\n");
+	if (in_array($udpmsg_socket, $selread) && !udpmsg_read()) die("UDPMSG: Error: closing connection.\n");
+	if (in_array($irc_socket, $selread) && !irc_read()) die("IRC: read error.\n");
+	foreach ($irc_clients as $client) {
+		if (in_array($client->socket, $selerror)) {
+			irc_client_close($client);
+		} else if ($client->connected && in_array($client->socket, $selread)) {
+			if (!ircc_read($client)) {
+				print("IRCC: read error.\n");
+				irc_client_close($client);
+			}
+		} else if (!$client->connected && in_array($client->socket, $selwrite)) {
+			$client->connected = TRUE;
+			ircc_send($client, 'USER '.$client->iname.' host server :'.$client->iname);
+			ircc_send($client, 'NICK '.$client->iname);
+		}
+	}
+	$ctime = time();
+	if ($atime < $ctime) {
+		foreach ($irc_users as $usr) if (is_a($usr, 'User')) irc_userchannels($usr, array('CMD' => 'ALIVE'));
+		$atime = $ctime + 1800;
+	}
+}
+
+function udpmsg_read() {
+	global $udpmsg_socket, $udpmsg_buffer;
+	$msg = socket_read($udpmsg_socket, 1024);
+	if (!strlen($msg)) {
+		fprintf(STDERR, "UDPMSG: End of file\n");
+		return FALSE;
+	}
+	$udpmsg_buffer .= $msg;
+	while (strlen($udpmsg_buffer) > 2) {
+		$len = ord($udpmsg_buffer[0]) * 256 + ord($udpmsg_buffer[1]);
+		if ($len <= 0 || $len > 1024) {
+			fprintf(STDERR, "UDPMSG: Error: protocol error\n");
+			return FALSE;
+		}
+		if (strlen($udpmsg_buffer) < 2 + $len) break;
+		$msg = substr($udpmsg_buffer, 2, $len);
+		$udpmsg_buffer = substr($udpmsg_buffer, 2 + $len);
+		$parts = explode("\0", $msg);
+		$ret = array();
+		for ($i = 0; $i < count($parts)-1; $i += 2) $ret[$parts[$i]] = $parts[$i+1];
+		udpmsg_process($ret);
+	}
+	return TRUE;
+}
+
+function udpmsg_send($arr) {
+	global $udpmsg_socket, $config;
+	$tmp = array();
+	$arr['DUMMY'] = rand(0, 999999);
+	$arr['NET'] = $config['udpmsg']['netname'];
+	foreach ($arr as $key => $value) { $tmp[] = $key; $tmp[] = $value; }
+	$tmp[] = '';
+	$msg = implode("\0", $tmp);
+	$len = strlen($msg);
+	if ($len > 1024) {
+		fprintf(STDERR, "UDPMSG: Error: message too long!\n");
+		return;
+	}
+	$lens = chr(floor($len / 256)).chr($len % 256);
+	socket_write($udpmsg_socket, $lens.$msg);
+}
+
+function ircc_send($client, $line) {
+	print('ICW: '.$line."\n");
+	$line .= "\r\n";
+	socket_send($client->socket, $line, strlen($line), 0);
+}
+function ircc_read($client) {
+	$newdata = socket_read($client->socket, 1024);
+	if ($newdata === FALSE || !strlen($newdata)) return FALSE;
+	$irc_buffer = $client->buffer . $newdata;
+	$offset = 0;
+	$len = strlen($irc_buffer);
+	while ($offset < $len) {
+		$posa = strpos($irc_buffer, "\n", $offset);
+		$posb = strpos($irc_buffer, "\r", $offset);
+		if ($posa !== FALSE && $posb !== FALSE) $pos = min($posa, $posb);
+		else if ($posa !== FALSE) $pos = $posa;
+		else if ($posb !== FALSE) $pos = $posb;
+		else break;
+		$line = substr($irc_buffer, $offset, $pos - $offset);
+		if (strlen($line) && !ircc_process($client, $line)) return FALSE;
+		$offset = $pos + 1;
+	}
+	$client->buffer = ($offset == $len) ? '' : substr($irc_buffer, $offset);
+	return TRUE;
+}
+function ircc_process($client, $line) {
+	global $irc_channels, $irc_users;
+	print('ICR: '.$line."\n");
+	$partsa = explode(' :', $line, 2);
+	$parts = explode(' ', $partsa[0]);
+	$sender = ($parts[0][0] == ':') ? substr(array_shift(&$parts), 1) : NULL;
+	$command = array_shift(&$parts);
+	if (count($partsa) > 1) array_push(&$parts, $partsa[1]);
+	$partsa = explode('!', $sender);
+	$sendernick = $partsa[0];
+	switch (strtoupper($command)) {
+		case '001': //Welcome
+			$client->iname = $parts[0];
+			$client->identified = TRUE;
+			$irc_users[strtoupper($client->iname)] = $client;
+			foreach ($client->joinchannels as $ch) ircc_send($client, "JOIN :".$ch->iname);
+			break;
+		case '433': //Nickname in use
+			ircc_send($client, "NICK ".$client->iname.rand(100,999));
+			break;
+		case 'PING':
+			ircc_send($client, 'PONG :'.$parts[0]);
+			break;
+		case 'NICK': //Nickname change
+		case 'JOIN':
+		case '353': //:server 353 me = channel :nickname nickname nickname
+		case 'PART':
+		case 'KICK':
+		case 'QUIT':
+		case 'PRIVMSG':
+		case 'NOTICE':
+			//These messages are also received and processed by the master relay bot, so we ignore them here
+			break;
+	}
+	return TRUE;
+}
+function ircc_channel_send($client, $ch, $msg) {
+	ircc_send($client, 'PRIVMSG '.$ch->iname.' :'.$msg);
+}
+
+function irc_read() {
+	global $irc_socket, $irc_buffer;
+	$newdata = socket_read($irc_socket, 1024);
+	if ($newdata === FALSE || !strlen($newdata)) return FALSE;
+	$irc_buffer .= $newdata;
+	$offset = 0;
+	$len = strlen($irc_buffer);
+	while ($offset < $len) {
+		$posa = strpos($irc_buffer, "\n", $offset);
+		$posb = strpos($irc_buffer, "\r", $offset);
+		if ($posa !== FALSE && $posb !== FALSE) $pos = min($posa, $posb);
+		else if ($posa !== FALSE) $pos = $posa;
+		else if ($posb !== FALSE) $pos = $posb;
+		else break;
+		$line = substr($irc_buffer, $offset, $pos - $offset);
+		if (strlen($line) && !irc_process($line)) return FALSE;
+		$offset = $pos + 1;
+	}
+	$irc_buffer = ($offset == $len) ? '' : substr($irc_buffer, $offset);
+	return TRUE;
+}
+
+function irc_send($line) {
+	global $irc_socket;
+	print('IW: '.$line."\n");
+	$line .= "\r\n";
+	socket_send($irc_socket, $line, strlen($line), 0);
+}
+
+function get_irc_client($name, $ch) {
+	if (!isset($irc_clients[$name])) {
+		if (count($irc_clients) < $config['irc']['clientconnections']) {
+			$client = new Client();
+			$client->uname = $client->iname = $name;
+			$client->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
+			socket_connect($client->socket, $config['irc']['host'], $config['irc']['port']);
+			$client->joinchannels[$ch->iname] = $ch;
+			$irc_clients[$name] = $client;
+		}
+		return NULL;
+	}
+	$client = $irc_clients[$name];
+	if (!$client->connected || !$client->identified) return NULL;
+	if (!isset($client->channels[$ch->iname])) {
+		$client->joinchannels[$ch->iname] = $ch;
+		ircc_send($client, 'JOIN :'.$ch->iname);
+		return NULL;
+	}
+	return $client;
+}
+function irc_client_part($name, $ch) {
+	if (!isset($irc_clients[$name])) return FALSE;
+	$client = $irc_clients[$name];
+	unset($client->channels[$ch->iname]);
+	unset($client->joinchannels[$ch->iname]);
+	if (!count($client->channels) && !count($client->joinchannels)) {
+		ircc_send($client, 'QUIT :No remaining channels.');
+		irc_client_close($client);
+	} else {
+		ircc_send($client, 'PART :'.$ch->iname);
+	}
+}
+function irc_client_close($client) {
+	global $irc_clients, $irc_users;
+	unset($irc_clients[$client->uname]);
+	if ($client->identified) unset($irc_users[strtoupper($client->iname)]);
+	socket_close($client->socket);
+}
+
+function udpmsg_process($ret) {
+	global $udpmsg_channels, $irc_clients, $config;
+	if (!isset($ret['CHN']) || !isset($ret['CMD']) || !isset($ret['USR'])) return;
+	if (!array_key_exists($ret['CHN'], $udpmsg_channels)) return;
+	$ch = $udpmsg_channels[$ret['CHN']];
+	$net = isset($ret['NET']) ? preg_replace('/[\x00\x10-\x13]/', '', $ret['NET']) : NULL;
+	$usr = preg_replace('/[\x00\x10-\x13]/', '', $ret['USR']);
+	switch ($ret['CMD']) {
+		case 'MSG':
+			if (!isset($ret['MSG'])) break;
+			$msg = preg_replace('/[\x00\x10-\x13]/', '', $ret['MSG']);
+			$client = NULL;
+			$client = get_irc_client($usr, $ch);
+			if ($client === NULL) {
+				$pfx = '['.$usr.($net?' @ '.$net:'').'] ';
+				if (ord($msg[0]) == 1) {
+					if (substr($message, 1, 6) != 'ACTION') break;
+					$msg = chr(1).'ACTION '.$pfx.substr($msg, 8, -1).chr(1);
+				} else {
+					$msg = $pfx.$msg;
+				}
+				irc_channel_send($ch, $msg);
+			} else {
+				ircc_channel_send($client, $ch, $msg);
+			}
+			break;
+		case 'JOIN':
+			if (get_irc_client($usr, $ch) === NULL) irc_channel_send($ch, '* '.$usr.' has joined'.($net?' on network '.$net:''));
+			break;
+		case 'PART':
+			if (!irc_client_part($usr, $ch)) irc_channel_send($ch, '* '.$usr.' has left'.($net?' on network '.$net:''));
+			break;
+		case 'NICK':
+			if (!isset($ret['NEWNICK'])) break;
+			$newnick = preg_replace('/[\x00\x10-\x13]/', '', $ret['NEWNICK']);
+			if (irc_client_part($usr, $ch)) {
+				if (get_irc_client($usr, $ch) === NULL) irc_channel_send($ch, '* '.$usr.' has joined'.($net?' on network '.$net:''));
+			} else {
+				irc_channel_send($ch, '* '.$usr.' has changed their nickname to '.$newnick.' '.($net?' on network '.$net:''));
+			}
+			break;
+	}
+}
+
+function irc_process($line) {
+	global $irc_nick, $irc_connected, $irc_channels, $config, $irc_users;
+	print('IR: '.$line."\n");
+	$partsa = explode(' :', $line, 2);
+	$parts = explode(' ', $partsa[0]);
+	$sender = ($parts[0][0] == ':') ? substr(array_shift(&$parts), 1) : NULL;
+	$command = array_shift(&$parts);
+	if (count($partsa) > 1) array_push(&$parts, $partsa[1]);
+	$partsa = explode('!', $sender);
+	$sendernick = $partsa[0];
+	switch (strtoupper($command)) {
+		case '001': //Welcome
+			$irc_nick = $parts[0];
+			$irc_connected = TRUE;
+			foreach ($irc_channels as $ch) irc_send("JOIN :".$ch->iname);
+			break;
+		case '433': //Nickname in use
+			irc_send("NICK ".$config['irc']['nick'].rand(100,999));
+			break;
+		case 'PING':
+			irc_send('PONG :'.$parts[0]);
+			break;
+		case 'NICK': //Nickname change
+			if (strcasecmp($sendernick, $irc_nick)) {
+				$irc_nick = $parts[0];
+			} else {
+				$usr = irc_getuser($sendernick, FALSE);
+				if ($usr === NULL) break;
+				irc_userchannels($usr, array('CMD' => 'NICK', 'NEWNICK' => $parts[0]));
+				unset($irc_users[strtoupper($usr->iname)]);
+				$usr->iname = $parts[0];
+				$irc_users[strtoupper($usr->iname)] = $usr;
+			}
+			break;
+		case 'JOIN':
+			if (!strcasecmp($sendernick, $irc_nick)) break;
+			$chs = explode(',', $parts[0]);
+			$usr = NULL;
+			foreach ($chs as $chn) {
+				$ch = irc_getchannel($chn);
+				if ($ch === NULL) continue;
+				if ($usr === NULL) $usr = irc_getuser($sendernick, TRUE);
+				$usr->channels[$ch->iname] = $ch;
+				if (!is_a($usr, 'Client')) udpmsg_send(array('CMD' => 'JOIN', 'CHN' => $ch->uname, 'USR' => $usr->iname));
+			}
+			break;
+		case '353': //:server 353 me = channel :nickname nickname nickname
+			$ch = irc_getchannel($parts[2]);
+			if ($ch === NULL) break;
+			$partsa = explode(' ', $parts[3]);
+			foreach ($partsa as $un) {
+				if (!strlen($un)) continue;
+				$usr = irc_getuser(ltrim($un, '~&@%+'), TRUE);
+				$usr->channels[$ch->iname] = $ch;
+				if (!is_a($usr, 'Client')) udpmsg_send(array('CMD' => 'JOIN', 'CHN' => $ch->uname, 'USR' => $usr->iname));
+			}
+			break;
+		case 'PART':
+			$usr = irc_getuser($sendernick, FALSE);
+			if ($usr === NULL) break;
+			$chs = explode(',', $parts[0]);
+			foreach ($chs as $chn) irc_part($usr, $chn);
+			break;
+		case 'KICK':
+			$usr = irc_getuser($parts[1], FALSE);
+			if (is_a($usr, 'Client')) break;
+			irc_part($usr, $parts[0]);
+			break;
+		case 'QUIT':
+			$usr = irc_getuser($sendernick, FALSE);
+			if ($usr === NULL) break;
+			if (is_a($usr, 'Client')) break;
+			irc_userchannels($usr, array('CMD' => 'PART'));
+			unset($irc_users[strtoupper($usr->iname)]);
+			break;
+		case 'PRIVMSG':
+		case 'NOTICE':
+			$ch = irc_getchannel($parts[0]);
+			if (is_a(irc_getuser($sendernick, FALSE), 'Client')) break;
+			if ($ch === NULL) break;
+			udpmsg_send(array('CMD' => 'MSG', 'CHN' => $ch->uname, 'MSG' => $parts[1], 'USR' => $sendernick));
+			break;
+	}
+	return TRUE;
+}
+
+function irc_getchannel($chn) {
+	global $irc_channels;
+	$chn = strtoupper($chn);
+	if (!array_key_exists($chn, $irc_channels)) return NULL;
+	return $irc_channels[$chn];
+}
+function irc_getuser($nick, $create = TRUE) {
+	global $irc_users;
+	$snick = strtoupper($nick);
+	if (array_key_exists($snick, $irc_users)) return $irc_users[$snick];
+	if (!$create) return NULL;
+	$usr = new User();
+	$usr->iname = $nick;
+	$irc_users[$snick] = $usr;
+	return $usr;
+}
+function irc_part($usr, $chn) {
+	$ch = irc_getchannel($chn);
+	if ($ch === NULL || $usr === NULL) return;
+	unset($usr->channels[$ch->name]);
+	if (!is_a($usr, 'Client')) udpmsg_send(array('CMD' => 'PART', 'CHN' => $ch->uname, 'USR' => $usr->iname));
+}
+function irc_userchannels($usr, $msg) {
+	$msg['USR'] = $usr->iname;
+	foreach ($usr->channels as $ch) {
+		$msg['CHN'] = $ch->uname;
+		udpmsg_send($msg);
+	}
+}
+function irc_channel_send($ch, $msg) {
+	global $irc_connected;
+	if (!$irc_connected) return;
+	irc_send('PRIVMSG '.$ch->iname.' :'.$msg);
+}
--- /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 @@
+<?php
+/* Copyright 2010 Ivo Smits <Ivo@UCIS.nl>. All rights reserved.
+   Redistribution and use in source and binary forms, with or without modification, are
+   permitted provided that the following conditions are met:
+
+   1. Redistributions of source code must retain the above copyright notice, this list of
+      conditions and the following disclaimer.
+
+   2. Redistributions in binary form must reproduce the above copyright notice, this list
+      of conditions and the following disclaimer in the documentation and/or other materials
+      provided with the distribution.
+
+   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR
+   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+   ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+   The views and conclusions contained in the software and documentation are those of the
+   authors and should not be interpreted as representing official policies, either expressed
+   or implied, of Ivo Smits.*/
+
+if (defined('APP_LOADED')) return;
+define('APP_LOADED', TRUE);
+if (!defined('CONFIGFILE')) define('CONFIGFILE', './config.php');
+
+print("UCIS UDPMSG3 Multi client IRC relay (c) 2010 Ivo Smits <Ivo@UCIS.nl>\n");
+print("More information: http://wiki.ucis.nl/UDPMSG3\n");
+print("\n");
+
+$irc_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'];
+	}
+}
--- /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 @@
+<?php
+$config = array(
+	'udpmsg' => array(
+		'host' => '1.3.3.8',
+		'port' => 15387,
+		'nickprefix' => 'E\\',
+		'nickseparator' => '',
+		'nicknetwork' => FALSE,
+		'netname' => 'sIRCd',
+		'chprefix' => 'chat/',
+	),
+	'channels' => array('#anonet'),
+);
+
--- /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
+<?php
+/* Copyright 2010 Ivo Smits <Ivo@UCIS.nl>. All rights reserved.
+   Redistribution and use in source and binary forms, with or without modification, are
+   permitted provided that the following conditions are met:
+
+   1. Redistributions of source code must retain the above copyright notice, this list of
+      conditions and the following disclaimer.
+
+   2. Redistributions in binary form must reproduce the above copyright notice, this list
+      of conditions and the following disclaimer in the documentation and/or other materials
+      provided with the distribution.
+
+   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR
+   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+   ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+   The views and conclusions contained in the software and documentation are those of the
+   authors and should not be interpreted as representing official policies, either expressed
+   or implied, of Ivo Smits.*/
+
+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");
+}
--- /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 @@
+<?php
+$config = array(
+	'udpmsg' => 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';
--- /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 @@
+<?php
+/* Copyright 2010 Ivo Smits <Ivo@UCIS.nl>. All rights reserved.
+   Redistribution and use in source and binary forms, with or without modification, are
+   permitted provided that the following conditions are met:
+
+   1. Redistributions of source code must retain the above copyright notice, this list of
+      conditions and the following disclaimer.
+
+   2. Redistributions in binary form must reproduce the above copyright notice, this list
+      of conditions and the following disclaimer in the documentation and/or other materials
+      provided with the distribution.
+
+   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR
+   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+   ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+   The views and conclusions contained in the software and documentation are those of the
+   authors and should not be interpreted as representing official policies, either expressed
+   or implied, of Ivo Smits.*/
+
+if (defined('app_loaded')) return;
+define('app_loaded', TRUE);
+
+print("UCIS UDPMSG3 Single server IRC relay (c) 2010 Ivo Smits <Ivo@UCIS.nl>\n");
+print("More information: http://wiki.ucis.nl/UDPMSG3\n");
+print("\n");
+
+$irc_socket = NULL;
+$irc_nick = NULL;
+$irc_channels = array();
+$irc_buffer = '';
+$irc_connected = FALSE;
+$irc_users = array();
+
+$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);
+}
--- /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 @@
+<?php
+$config = array(
+	'ircd' => 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';
--- /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 @@
+<?php
+/* Copyright 2010 Ivo Smits <Ivo@UCIS.nl>. All rights reserved.
+   Redistribution and use in source and binary forms, with or without modification, are
+   permitted provided that the following conditions are met:
+
+   1. Redistributions of source code must retain the above copyright notice, this list of
+      conditions and the following disclaimer.
+
+   2. Redistributions in binary form must reproduce the above copyright notice, this list
+      of conditions and the following disclaimer in the documentation and/or other materials
+      provided with the distribution.
+
+   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR
+   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+   ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+   The views and conclusions contained in the software and documentation are those of the
+   authors and should not be interpreted as representing official policies, either expressed
+   or implied, of Ivo Smits.*/
+
+if (defined('APP_LOADED')) return;
+define('APP_LOADED', TRUE);
+if (!defined('CONFIGFILE')) define('CONFIGFILE', './config.php');
+
+print("UCIS IRC Relay bot (c) 2010 Ivo Smits <Ivo@UCIS.nl>\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);
+	}
+}