diff ircd/ircd.php @ 0:dd81c38b513a

Initial commit
author Ivo Smits <Ivo@UCIS.nl>
date Mon, 28 Feb 2011 00:49:07 +0100
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/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);
+	}
+}