diff ircrelay/ircrelay.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/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);
+		}
+	}
+}