Created
June 29, 2010 22:14
-
-
Save navarr/457900 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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}); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
,-.----. ___ | |
\ / \ ,--.'|_ | |
; : \ ,---. ,--, | | :,' | |
| | .\ : ' ,'\ ,'_ /| : : ' : | |
. : |: | / / | .--. | | :.;__,' / ,---. | |
| | \ :. ; ,. :,'_ /| : . || | | / \ | |
| : . /' | |: :| ' | | . .:__,'| : / / | | |
; | | \' | .; :| | ' | | | ' : |__ . ' / | | |
| | ;\ \ : |: | : ; ; | | | '.'|' ; /| | |
: ' | \.'\ \ / ' : `--' \ ; : ;' | / | | |
: : :-' `----' : , .-./ | , / | : | | |
| |.' `--`----' ---`-' \ \ / | |
`---' ,----,. `----' | |
,' ,' | | |
,' .' | ,----.. | |
,----.' .' / / \ | |
| | .' / . : | |
: : |--, . / ;. \ | |
: | ;.' \. ; / ` ; | |
| | |; | ; \ ; | | |
`----'.'\ ;| : | ; | ' | |
__ \ . |. | ' ' ' : | |
/ /\/ / :' ; \; / | | |
/ ,,/ ',- . \ \ ', / | |
\ ''\ ; ; : / | |
\ \ .' \ \ .' | |
`--`-,-' `---` | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
cls & "C:\program files (x86)\php\php.exe" server.php | |
pause |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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