Mercurial > hg > udpmsg3
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); + } +}