diff unrealircdlink/unrealircdlink.php @ 0:dd81c38b513a

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