Skip to content

Instantly share code, notes, and snippets.

@navarr
Created June 29, 2010 22:14
Show Gist options
  • Save navarr/457900 to your computer and use it in GitHub Desktop.
Save navarr/457900 to your computer and use it in GitHub Desktop.
<?php
class IRCServer
{
protected $config;
protected $debug_mode;
protected $hooks;
protected $user_funcs;
protected $master_socket;
public $max_clients = 10;
public $channels;
public $clients;
public function __construct($bind_ip,$port,$addr = "irc.localhost")
{
set_time_limit(0);
$this->hooks = array();
$this->user_funcs = array();
$this->config["ip"] = $bind_ip;
$this->config["port"] = $port;
$this->config["addr"] = $addr;
$this->master_socket = socket_create(AF_INET, SOCK_STREAM, 0);
socket_bind($this->master_socket,$this->config["ip"],$this->config["port"]) or die("Issue Binding");
socket_getsockname($this->master_socket,$bind_ip,$port);
socket_listen($this->master_socket);
IRCServer::debug("Listenting for connections on {$bind_ip}:{$port} as {$addr}");
}
public function hook($command,$function)
{
$command = strtoupper($command);
if(!isset($this->hooks[$command])) { $this->hooks[$command] = array(); }
$k = array_search($function,$this->hooks[$command]);
if($k === FALSE)
{
$this->hooks[$command][] = $function;
}
}
public function hook_all($fuction)
{
$this->hook("__GLOBAL",$function);
}
public function unhook($command = NULL,$function)
{
$command = strtoupper($command);
if($command !== NULL)
{
$k = array_search($function,$this->hooks[$command]);
if($k !== FALSE)
{
unset($this->hooks[$command][$k]);
}
} else {
$k = array_search($this->user_funcs,$function);
if($k !== FALSE)
{
unset($this->user_funcs[$k]);
}
}
}
public function loop_once()
{
// Setup Clients Listen Socket For Reading
$read[0] = $this->master_socket;
for($i = 0; $i < $this->max_clients; $i++)
{
if(isset($this->clients[$i]))
{
$read[$i + 1] = $this->clients[$i]->socket;
}
}
// Set up a blocking call to socket_select
if(socket_select($read,$write = NULL, $except = NULL, $tv_sec = 5) < 1)
{
// IRCServer::debug("Problem blocking socket_select?");
return true;
}
// Handle new Connections
if(in_array($this->master_socket, $read))
{
for($i = 0; $i < $this->max_clients; $i++)
{
if(empty($this->clients[$i]))
{
$temp_sock = $this->master_socket;
$this->clients[$i] = new IRCServerClient($this->master_socket,$i);
IRCServer::socket_write_smart($this->clients[$i]->socket,":{$this->config["addr"]} NOTICE AUTH :*** Looking up your hostname...");
$this->clients[$i]->lookup_hostname();
IRCServer::socket_write_smart($this->clients[$i]->socket,":{$this->config["addr"]} NOTICE AUTH :*** Found your hostname");
$this->trigger_hooks("__CONNECT",$this->clients[$i],"");
break;
}
elseif($i == ($this->max_clients-1))
{
IRCServer::debug("Too many clients... :( ");
}
}
}
// Handle Input
for($i = 0; $i < $this->max_clients; $i++) // for each client
{
if(isset($this->clients[$i]))
{
if(in_array($this->clients[$i]->socket, $read))
{
$input = socket_read($this->clients[$i]->socket, 1024);
if($input == null)
{
$this->disconnect($i);
}
else
{
// Get Any Previous Incompleted Lines
$input = $this->clients[$i]->buffer.$input;
// Unset the Buffer
$this->clients[$i]->buffer = "";
// Trash \r's. We don't like their kind.
str_replace("\r","",$input);
// Get each individual command, since they're \n terminated.
$lines = explode("\n",$input);
// Last line wasn't terminated
if(substr(-1,$input) != "\n")
{
$this->clients[$i]->buffer = $lines[count($lines)-1]; // Store last line in buffer.
unset($lines[count($lines)-1]); // Unset last line.
}
foreach($lines as $input) // Look through the commands.
{
$input = trim($input); // Clean it up :3
if($input) // If the Input isn't blank after cleanup
{
IRCServer::debug("{$i}@{$this->clients[$i]->ip} --> {$input}");
$this->trigger_hooks("__GLOBAL",$this->clients[$i],$input);
if(substr($input,0,1) == ":" && $this->clients[$i]->type == IRCServerClient::SERVER)
{
// Server to Server Stuff
}
else
{
if(substr($input,0,1) == ":") { list(,$input) = explode(" ",$input,2); }
list($command,) = explode(" ",$input,2);
$command = strtoupper($command);
$this->trigger_hooks($command,$this->clients[$i],$input);
}
}
}
}
}
}
}
return true;
}
public function disconnect($client_index,$message = "")
{
$i = $client_index;
IRCServer::debug("Client {$i} from {$this->clients[$i]->ip} Disconnecting");
$this->trigger_hooks("__DISCONNECT",$this->clients[$i],$message);
$this->clients[$i]->destroy();
unset($this->clients[$i]);
}
public function trigger_hooks($command,&$client,$input)
{
if(isset($this->hooks[$command]))
{
foreach($this->hooks[$command] as $function)
{
IRCServer::debug("Triggering Hook '{$function}' for Command '{$command}'");
$continue = call_user_func($function,$this,$client,$input);
if($continue === FALSE) { break; }
}
}
}
public function infinite_loop()
{
$test = true;
do
{
$test = $this->loop_once();
}
while($test);
}
public static function debug($text)
{
echo("{$text}\r\n");
}
public static function socket_write_smart(&$sock,$string,$lf = TRUE)
{
IRCServer::debug("<-- {$string}");
if($lf) { $string = "{$string}\n"; }
socket_write($sock,$string,strlen($string));
}
public static function tokenize($string,$max_expected = 15)
{
$array1 = explode(":",$string,2);
if(isset($array1[1])) { $max_expected--; }
$array2 = explode(" ",trim($array1[0]),$max_expected);
unset($array1[0]);
$toks = array_merge($array2,$array1);
return $toks;
}
function &__get($name)
{
return $this->{$name};
}
}
class IRCServerClient
{
protected $socket;
protected $ip;
protected $hostname;
public $nick = " ";
public $buffer = "";
public $type = self::CLIENT;
public $server_clients_index;
const CLIENT = 1;
const SERVER = 2;
public function __construct(&$socket,$i)
{
$this->server_clients_index = $i;
$this->socket = socket_accept($socket) or die("Failed to Accept");
IRCServer::debug("New Client Connected");
socket_getpeername($this->socket,$ip);
$this->ip = $ip;
}
public function lookup_hostname()
{
$this->hostname = gethostbyaddr($this->ip);
}
public function destroy()
{
socket_close($this->socket);
}
function &__get($name)
{
return $this->{$name};
}
function __isset($name)
{
return isset($this->{$name});
}
}
,-.----. ___
\ / \ ,--.'|_
; : \ ,---. ,--, | | :,'
| | .\ : ' ,'\ ,'_ /| : : ' :
. : |: | / / | .--. | | :.;__,' / ,---.
| | \ :. ; ,. :,'_ /| : . || | | / \
| : . /' | |: :| ' | | . .:__,'| : / / |
; | | \' | .; :| | ' | | | ' : |__ . ' / |
| | ;\ \ : |: | : ; ; | | | '.'|' ; /|
: ' | \.'\ \ / ' : `--' \ ; : ;' | / |
: : :-' `----' : , .-./ | , / | : |
| |.' `--`----' ---`-' \ \ /
`---' ,----,. `----'
,' ,' |
,' .' | ,----..
,----.' .' / / \
| | .' / . :
: : |--, . / ;. \
: | ;.' \. ; / ` ;
| | |; | ; \ ; |
`----'.'\ ;| : | ; | '
__ \ . |. | ' ' ' :
/ /\/ / :' ; \; / |
/ ,,/ ',- . \ \ ', /
\ ''\ ; ; : /
\ \ .' \ \ .'
`--`-,-' `---`
cls & "C:\program files (x86)\php\php.exe" server.php
pause
<?php
require_once("IRCServer.class.php");
$irc = new IRCServer(null,6667,"irc.route50.net");
IRCProtocol::setup($irc);
$irc->hook("__CONNECT",array("IRCProtocol","connect_prepare"));
$irc->hook("__DISCONNECT",array("IRCProtocol","disconnect"));
$irc->hook("NICK",array("IRCProtocol","command_nick"));
$irc->hook("USER",array("IRCProtocol","command_user"));
$irc->hook("JOIN",array("IRCProtocol","command_join"));
$irc->hook("PRIVMSG",array("IRCProtocol","command_privmsg"));
$irc->hook("PART",array("IRCProtocol","command_part"));
$irc->hook("PING",array("IRCProtocol","command_ping"));
$irc->hook("QUIT",array("IRCProtocol","command_quit"));
$irc->infinite_loop();
IRCServer::debug("Loop Ended.");
class IRCProtocol
{
static function setup(&$irc)
{
$irc->channels = array();
}
static function connect_prepare(&$irc,&$client,$input)
{
$client->did["nick"] = FALSE;
$client->did["user"] = FALSE;
$client->did["usernick"] = FALSE;
$client->in_channels = array();
}
static function disconnect(&$irc,&$client,$message)
{
foreach($client->in_channels as $channel)
{
$channel = strtolower($channel);
$c = count($irc->channels[$channel]);
$k = array_search($client->server_clients_index,$irc->channels[$channel]);
if($k !== FALSE)
{
IRCServer::debug("Discovered Disconnecting {$client->server_clients_index} User is {$k}+1/{$c} in Channels");
unset($irc->channels[$channel][$k]);
if(count($irc->channels[$channel]))
{
foreach($irc->channels[$channel] as $cid)
{
IRCServer::socket_write_smart($irc->clients[$cid]->socket,":{$client->nick}!{$client->username}@{$client->hostname} QUIT :{$message}");
}
} else {
IRCServer::debug("Deleting Channel Information, Was the only user in the channel.");
unset($irc->channels[$channel]);
}
}
}
unset($irc->nickname_array[strtolower($client->nick)]);
}
static function command_join(&$irc,&$client,$input)
{
$toks = IRCServer::tokenize($input);
$channels = explode(",",$toks[1]);
foreach($channels as $channel)
{
$channel_l = strtolower($channel);
if(substr($toks[1],0,1) != "#")
{
IRCServer::socket_write_smart($client->socket,":{$irc->config["addr"]} 403 {$client->nick} {$channel} :No such channel");
return;
} else {
if(!isset($irc->channels[$channel_l])) {
$irc->channels[$channel_l] = array();
}
$nicklist = array();
foreach($irc->channels[$channel_l] as $cid)
{
$nicklist[] = $irc->clients[$cid]->nick;
IRCServer::socket_write_smart($irc->clients[$cid]->socket,":{$client->nick}!{$client->username}@{$client->hostname} JOIN :{$channel}");
}
$irc->channels[$channel_l][] = $client->server_clients_index;
$nicklist[] = $client->nick;
$nicklist = implode(" ",$nicklist);
$client->in_channels[] = $channel_l;
IRCServer::socket_write_smart($client->socket,":{$client->nick}!{$client->username}@{$client->hostname} JOIN :{$channel}");
IRCServer::socket_write_smart($client->socket,":{$irc->config["addr"]} 353 {$client->nick} = {$channel} :{$nicklist}");
IRCServer::socket_write_smart($client->socket,":{$irc->config["addr"]} 366 {$client->nick} {$channel} :End of /NAMES list.");
}
}
}
static function command_privmsg(&$irc,&$client,$input)
{
// PRIVMSG <targets> :Message
$toks = IRCServer::tokenize($input,3);
$targets = explode(",",$toks[1]);
if(!isset($toks[2]))
{
IRCServer::socket_write_smart($client->socket,":{$irc->config["addr"]} 412 {$client->nick} :No text to send");
return;
}
$message = $toks[2];
foreach($targets as $target)
{
$target = strtolower($target);
if(isset($irc->channels[$target]))
{
if(array_search($target,$client->in_channels) !== FALSE)
{
foreach($irc->channels[$target] as $cid)
{
if($cid != $client->server_clients_index)
{
IRCServer::socket_write_smart($irc->clients[$cid]->socket,":{$client->nick}!{$client->username}@{$client->hostname} PRIVMSG {$target} :{$message}");
}
}
} else {
IRCServer::socket_write_smart($client->socket,":{$irc->config["addr"]} 404 {$client->nick} {$target} :No external channel messages ({$target})");
}
} elseif(isset($irc->nickname_array[$target])) {
$cid = $irc->nickname_array[$target];
IRCServer::socket_write_smart($irc->clients[$cid]->socket,":{$client->nick}!{$client->username}@{$client->hostname} PRIVMSG {$target} :{$message}");
} else {
IRCServer::socket_write_smart($client->socket,":{$irc->config["addr"]} 401 {$client->nick} {$target} :No such nick/channel");
}
}
if(empty($targets))
{
IRCServer::socket_write_smart($client->socket,":{$irc->config["addr"]} 401 {$client->nick} :No recipient given (PRIVMSG)");
}
}
static function command_part(&$irc,&$client,$input)
{
// PART #channel,#channel Message
$toks = IRCServer::tokenize($input);
$channels = explode(",",$toks[1]);
if(isset($toks[2])) { $message = ":{$toks[2]}"; } else { $message = ""; }
foreach($channels as $channel)
{
$channel_l = strtolower($channel);
if(isset($irc->channels[$channel_l]))
{
$channel_l = strtolower($channel);
if(array_search($channel_l,$client->in_channels) !== FALSE)
{
foreach($irc->channels[$channel_l] as $cid)
{
IRCServer::socket_write_smart($irc->clients[$cid]->socket,":{$client->nick}!{$client->username}@{$client->hostname} PART {$channel_l} {$message}");
}
$k = array_search($client->server_clients_index,$irc->channels[$channel_l]);
if($k !== FALSE)
{ unset($irc->channels[$channel_l][$k]); }
$k = array_search($channel_l,$client->in_channels);
if($k !== FALSE)
{ unset($client->in_channels[$k]); }
if(count($irc->channels[$channel_l]) < 1)
{
unset($irc->channels[$channel_l]);
}
} else {
IRCServer::socket_write_smart($client->socket,":{$irc->config["addr"]} 442 {$client->nick} {$channel} :You're not on that channel.");
}
} else {
IRCServer::socket_write_smart($client->socket,":{$irc->config["addr"]} 403 {$client->nick} {$channel} :No such channel");
}
}
}
static function command_ping(&$irc,&$client,$input)
{
$toks = IRCServer::tokenize($input);
if(!$toks[1])
{
IRCServer::socket_write_smart($client->socket,":{$irc->config["addr"]} 409 :No origin specified");
} else {
IRCServer::socket_write_smart($client->socket,":{$irc->config["addr"]} PONG {$irc->config["addr"]} :{$toks[1]}");
}
}
static function command_quit(&$irc,&$client,$input)
{
$toks = IRCServer::tokenize($input);
$message = "Quit: {$toks[1]}";
$message = trim($message);
IRCServer::socket_write_smart($client->socket,"ERROR :Closing Link: {$client->nick}[{$client->ip}] ({$message})");
$irc->disconnect($client->server_clients_index,$message);
}
static function command_nick(&$irc,&$client,$input)
{
// $input should look like "NICK Nickname"
$toks = IRCServer::tokenize($input);
$oldnick = $client->nick;
$nick = $toks[1];
if($nick == $oldnick) { return; } // Disregard
if(!$nick)
{
IRCServer::socket_write_smart($client->socket,":{$irc->config["addr"]} 431 {$client->nick} :No nickname given");
return;
} // Seriously, what a joke. Who would send a blank NICK request?
if(!isset($irc->nickname_array)) { $irc->nickname_array = array(); }
if(!isset($irc->nickname_array[strtolower($nick)]))
{
unset($irc->nickname_array[strtolower($oldnick)]);
$irc->nickname_array[strtolower($nick)] = $client->server_clients_index;
$client->nick = $nick;
$client->did["nick"] = true;
} else {
IRCServer::socket_write_smart($client->socket,":{$irc->config["addr"]} 433 {$client->nick} {$nick} :Nickname is already in use.");
return;
}
if($client->did["nick"] && $client->did["user"] && !$client->did["usernick"])
{
IRCProtocol::command_after_nickuser($irc,$client);
}
elseif($client->did["usernick"]) // Nickname change
{
foreach($client->in_channels as $channel)
{
$channel = strtolower($channel);
$c = count($irc->channels[$channel]);
$k = array_search($client->server_clients_index,$irc->channels[$channel]);
foreach($irc->channels[$channel] as $cid)
{
if($cid != $client->server_clients_index)
{
IRCServer::socket_write_smart($irc->clients[$cid]->socket,":{$oldnick}!{$client->username}@{$client->hostname} NICK :{$nick}");
}
}
}
IRCServer::socket_write_smart($client->socket,":{$oldnick}!{$client->username}@{$client->hostname} NICK :{$nick}");
}
}
static function command_user(&$irc,&$client,$input)
{
// $input should look like USER user hostname servername :Real Name
$toks = IRCServer::tokenize($input);
if(!$toks[1] || !$toks[2] || !$toks[3] || !$toks[4])
{
IRCServer::socket_write_smart($client->socket,":{$irc->config["addr"]} 461 {$client->nick} USER :Not enough parameters");
return;
}
if($client->did["usernick"])
{
IRCServer::socket_write_smart($client->socket,":{$irc->config["addr"]} 462 {$client->nick} :You may not reregister");
return;
}
$client->did["user"] = true;
$client->username = $toks[1];
$client->data["hostname"] = $toks[2];
$client->data["servername"] = $toks[3]; // lol, what?
$client->realname = $toks[4];
if($client->did["nick"] && $client->did["user"] && !$client->did["usernick"])
{
IRCProtocol::command_after_nickuser($irc,$client);
}
}
static function command_after_nickuser(&$irc,&$client)
{
$client->did["usernick"] = true;
IRCServer::socket_write_smart($client->socket,":{$irc->config["addr"]} 001 {$client->nick} :Welcome to the Route50 IRC Network {$client->nick}!{$client->username}@{$client->ip}");
IRCServer::socket_write_smart($client->socket,":{$irc->config["addr"]} 002 {$client->nick} :Your host is {$irc->config["addr"]}, running version PHPIRCd0.0.1");
IRCServer::socket_write_smart($client->socket,":{$irc->config["addr"]} 003 {$client->nick} :This server was created Tue Jun 29 2010 at 12:00:00 CDT");
// IRCServer::socket_write_smart($client->socket,":{$irc->config["addr"]} 004 ");
// IRCServer::socket_write_smart($client->socket,":{$irc->config["addr"]} 005 ");
// :server 251 nick :There are x users and x invisible on x servers
// :server 252 nick x :operator(s) online
// :server 253 nick x :unknown connection(s)
// :server 254 nick x :channels formed
// :server 255 nick :I have x clients and x servers
IRCServer::socket_write_smart($client->socket,":{$irc->config["addr"]} 265 {$client->nick} :Current Local Users: x Max: x");
// :server 266 nick :Current Global Users: x Max: x
if(file_exists("motd.txt"))
{
IRCServer::socket_write_smart($client->socket,":{$irc->config["addr"]} 375 {$client->nick} :- {$irc->config["addr"]} Message of the Day -");
$motd = file_get_contents("motd.txt");
$lines = explode("\n",$motd);
foreach($lines as $line)
{
$line = str_replace("\r","",$line);
IRCServer::socket_write_smart($client->socket,":{$irc->config["addr"]} 372 {$client->nick} :- {$line}");
}
IRCServer::socket_write_smart($client->socket,":{$irc->config["addr"]} 376 {$client->nick} :End of /MOTD command.");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment