Created
June 30, 2011 08:50
-
-
Save benphelps/1055876 to your computer and use it in GitHub Desktop.
This file contains hidden or 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 | |
/** | |
* PHP IRC Server - PHelPsIRCd | |
* Why? Why not! | |
* @author Ben Phelps | |
* @version 2 | |
* @copyright BenPhelps.me, 15 June, 2011 | |
* @package PHelPsIRCd | |
**/ | |
define ("LINE_END", "\n"); | |
/** | |
* IRC Message Headers | |
*/ | |
define ("CTCP", chr(0x01)); | |
define ("RPL_LUSERCLIENT", "251 "); | |
define ("RPL_LUSEROP", "252 "); | |
define ("RPL_LUSERCHANNELS", "254 "); | |
define ("RPL_LUSERME", "255 "); | |
define ("RPL_ISON", "303 "); | |
define ("RPL_LISTSTART", "321 "); | |
define ("RPL_LIST", "322 "); | |
define ("RPL_LISTEND", "323 "); | |
define ("RPL_ENDOFWHO", "315 "); | |
define ("RPL_WHOREPLY", "352 "); | |
define ("RPL_NAMREPLY", "353 "); | |
define ("RPL_ENDOFNAMES", "366 "); | |
define ("RPL_MOTD", "372 "); | |
define ("RPL_MOTDSTART", "375 "); | |
define ("RPL_ENDOFMOTD", "376 "); | |
define ("ERR_NOSUCHNICK", "401 "); | |
define ("ERR_NOSUCHCHANNEL", "403 "); | |
define ("ERR_NICKNAMEINUSE", "433 "); | |
define ("START_DATE", time()); | |
class Server | |
{ | |
public $lookup_host = true; | |
private $clients = array(); | |
private $sockets = array(); | |
private $channels = array(); | |
private $nicks = array(); | |
private $auth = array(); | |
private $total_connections = 0; | |
private $connections = 0; | |
private $host_cache = array(); | |
function __construct($settings) | |
{ | |
$this->sc(); // setup terminal | |
$this->lh(); // send snazzy header | |
$this->settings = $settings; | |
if ($this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) { | |
$this->lt("Created socket"); | |
} | |
else { | |
$this->lt("Could not create socket!", 'red'); | |
} | |
if (@socket_bind($this->socket, $settings['bind'], $settings['port'])) { | |
$this->lt("Bound socket to {$settings['bind']}:{$settings['port']}"); | |
} | |
else { | |
$this->lt("Could not bind socket! ( " . socket_strerror(socket_last_error()) . " )", 'red'); | |
die(); | |
} | |
if (@socket_listen($this->socket)) { | |
$this->lt("Listening for connections"); | |
} | |
else { | |
$this->lt("Can not listen on socket! ( " . socket_strerror(socket_last_error()) . " )", 'red'); | |
die(); | |
} | |
socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, 1); | |
socket_set_nonblock($this->socket); | |
} | |
public function run() | |
{ | |
while (true) { | |
$this->listen(); | |
$this->read(); | |
usleep(10); | |
} | |
} | |
public function listen() | |
{ | |
$socket_blip = @socket_accept($this->socket); | |
if ($socket_blip !== false) { | |
$this->add_client($socket_blip); | |
} | |
else { | |
return false; | |
} | |
} | |
private function read() | |
{ | |
foreach ($this->clients as $id => $client_socket) { | |
$client_socket = $this->clients[$id]['socket']; | |
$read = $this->socket_normal_read($client_socket, 25600); | |
if (!empty($read)) { | |
$this->lt("< [$id] " . trim($read)); | |
$this->current = $id; | |
$this->parse_read($read); | |
} | |
elseif ($read === false) { | |
$this->remove_client($id); | |
} | |
} | |
} | |
private function write($line, $type = "server", $socket = false, $socketID = false) | |
{ | |
$id = $this->current; | |
$client = $this->clients[$this->current]; | |
$server = $this->settings["host"]; | |
if ($type == "server") { | |
$pre_host = $server; | |
$buffer = ":". $pre_host . " " . $line; | |
socket_write($client["socket"], $buffer . LINE_END); | |
} | |
elseif($type == "host") { | |
$pre_host = $client["full_host"]; | |
$buffer = ":". $pre_host . " " . $line; | |
socket_write($client["socket"], $buffer. LINE_END); | |
} | |
elseif ($type == "client") { | |
$pre_host = $client["full_host"]; | |
$buffer = ":". $pre_host . " " . $line; | |
socket_write($socket, $buffer. LINE_END); | |
} | |
$this->lt("> [$id] " . trim($buffer)); | |
} | |
private function parse_read($line) | |
{ | |
$client = $this->current; | |
$line = trim($line); | |
$commands = explode(" ", $line, 2); | |
$command = strtoupper($commands[0]); | |
$arguments = $commands[1]; | |
switch ($command) { | |
case 'NICK': | |
$args = $this->args($arguments, 1); | |
$this->cmd_nick($args); | |
break; | |
case 'USER': | |
$args = $this->args($arguments, 4); | |
$this->cmd_user($args); | |
break; | |
case 'JOIN': | |
$args = $this->args($arguments); | |
$this->cmd_join($args); | |
$this->cmd_names($args); | |
break; | |
case 'PART': | |
$args = $this->args($arguments, 2); | |
$this->cmd_part($args); | |
break; | |
case 'WHO': | |
$args = $this->args($arguments, 1); | |
$this->cmd_who($args); | |
break; | |
case 'NAMES': | |
$args = $this->args($arguments, 1); | |
$this->cmd_names($args); | |
break; | |
case 'PRIVMSG': | |
$args = $this->args($arguments, 2); | |
$this->cmd_privmsg($args); | |
break; | |
case 'DROP': | |
//$this->drop_all(); | |
break; | |
case 'PING': | |
$args = $this->args($arguments, 1); | |
$this->cmd_ping($args); | |
break; | |
case 'QUIT': | |
$args = $this->args($arguments, 0); | |
$this->cmd_quit($args); | |
break; | |
case 'ISON': | |
$args = $this->args($arguments, 2); | |
$this->cmd_ison($args); | |
break; | |
case 'LIST': | |
$args = $this->args($arguments, 1); | |
$this->cmd_list(); | |
break; | |
case 'DBUG': | |
print_r($this->clients); | |
print_r($this->channels); | |
print_r($this->nicks); | |
break; | |
default: | |
break; | |
} | |
} | |
private function args($args, $count = null) | |
{ | |
return explode(" ", $args, $count); | |
} | |
private function cmd_list($args) | |
{ | |
$current_nick = $this->clients[$this->current]['nick']; | |
$buffer = RPL_LISTSTART . "{$current_nick} Channel :Users Name"; | |
$this->write($buffer); | |
foreach ($this->channels as $channel => $users) { | |
$on = count($users); | |
$buffer = RPL_LIST . "{$current_nick} {$channel} {$on} :[+nt]"; | |
$this->write($buffer); | |
} | |
$buffer = RPL_LISTEND . "{$current_nick} :End of /LIST"; | |
$this->write($buffer); | |
} | |
private function cmd_ping($args) | |
{ | |
$buffer = "PONG {$this->settings['host']} :" . $args[0]; | |
$this->write($buffer); | |
} | |
private function cmd_nick() | |
{ | |
echo "In Nick\n"; | |
$client = $this->current; | |
$nick = $args[0]; | |
if ($args[0] != $this->nicks[$this->clients[$client]['nick']]) { | |
if ( !isset($this->nicks[$args[0]]) && !isset($this->channel[$args[0]]) ) { | |
if (isset($this->clients[$client]['nick'])) { | |
unset($this->nicks[$this->clients[$client]['nick']]); | |
} | |
$this->nicks[$args[0]] = $client; | |
$this->clients[$client]['nick'] = $args[0]; | |
if (isset($this->clients[$client]['name'])) { | |
$this->clients[$client]['full_host'] = $args[0] . "!" . $this->clients[$client]['name'] . "@" . $this->clients[$client]['host']; | |
if ($this->clients[$client]['motd'] != true) { | |
$this->broadcast_motd($args[0]); | |
$this->clients[$client]['motd'] = true; | |
} | |
} | |
} | |
else { | |
$buffer = ERR_NICKNAMEINUSE . " * " . $args[0] . " :Nickname is already in use."; | |
$this->write($buffer); | |
} | |
} | |
} | |
private function cmd_user($args) | |
{ | |
echo "In User\n"; | |
$client = $this->current; | |
$this->clients[$client]['name'] = $args[0]; | |
$this->clients[$client]['real_name'] = ltrim($args[3], ':'); | |
if(isset($this->clients[$client]['nick'])) { | |
$this->clients[$client]['full_host'] = $this->clients[$client]['nick'] . "!" . $args[0] . "@" . $this->clients[$client]['host']; | |
if ($this->clients[$client]['motd'] != true) { | |
$this->broadcast_motd($this->clients[$client]['nick']); | |
$this->clients[$client]['motd'] = true; | |
} | |
} | |
} | |
private function cmd_join($args) | |
{ | |
$client = $this->current; | |
$channels = $args; | |
foreach ($channels as $channel) { | |
if (!isset($this->clients[$client]['channels'][$channel])) { | |
if (isset($this->channels[$channel])) { | |
$this->channels[$channel][] = $this->current; | |
} | |
else { | |
$this->channels[$channel] = array(); | |
$this->channels[$channel][] = $this->current; | |
} | |
$this->clients[$client]['channels'][$channel] = time(); | |
$buffer = " JOIN :" . $args[0]; | |
$this->write($buffer, "host"); | |
$this->broadcast_join($channel); | |
} | |
} | |
} | |
private function cmd_part($args) | |
{ | |
$client = $this->current; | |
$channel = $args[0]; | |
unset($this->clients[$client]['channels'][$channel]); | |
$this->channels[$channel] = $this->array_remove_value($this->channels[$channel], $this->current); | |
$reason = ltrim($args[1], ':'); | |
$buffer = " PART {$channel} :{$reason}"; | |
$this->write($buffer, "host"); | |
$this->broadcast_part($channel, $reason); | |
} | |
function array_remove_value () | |
{ | |
$args = func_get_args(); | |
return array_diff($args[0],array_slice($args,1)); | |
} | |
private function cmd_quit($args) | |
{ | |
$client = $this->current; | |
$message = $args[0]; | |
foreach ($this->clients[$client]['channels'] as $channel => $null) { | |
foreach ($this->channels[$channel] as $user) { | |
if ($user != $this->current) { | |
$socket = $this->clients[$user]['socket']; | |
$buffer = "QUIT :{$message}"; | |
$this->write($buffer, "client", $socket); | |
} | |
} | |
} | |
} | |
private function broadcast_motd($nick) | |
{ | |
$buffer = "001 {$nick} :Welcome to the {$this->settings['hostn']} IRC Network, {$nick}"; | |
$this->write($buffer); | |
$buffer = "002 {$nick} :Your host is {$this->settings['host']}, running version 0.1 PHPIRCd"; | |
$this->write($buffer); | |
$date = @date("D M j Y \\a\\t G:i:s e", START_DATE); | |
$buffer = "003 {$nick} :This server was created {$date}"; | |
$this->write($buffer); | |
$buffer = RPL_MOTDSTART . "{$nick} :- {$this->settings['host']} message of the day - "; | |
$this->write($buffer); | |
$motd_lines = "----------------------------------------- | |
_____ _ _ _____ _____ _____ _____ | |
| __ \| | | | __ \_ _| __ \ / ____| | |
| |__) | |__| | |__) || | | |__) | | | |
| ___/| __ | ___/ | | | _ /| | | |
| | | | | | | _| |_| | \ \| |____ | |
|_| |_| |_|_| |_____|_| \_\\_____| | |
http://benphelps.me/ | |
Fuck you Sean! | |
-----------------------------------------"; | |
$motd_lines = explode("\n", $motd_lines); | |
foreach ($motd_lines as $line) { | |
$buffer = RPL_MOTD . "{$nick} :- " . $line; | |
$this->write($buffer); | |
} | |
$buffer = RPL_ENDOFMOTD . ":End of /MOTD command."; | |
$this->write($buffer); | |
$buffer = RPL_LUSERCLIENT . "{$nick} :There are {$this->connections} users and 0 invisible on 1 servers"; | |
$this->write($buffer); | |
$buffer = RPL_LUSEROP . "{$nick} 0 :operator(s) online"; | |
$this->write($buffer); | |
$channels = count($this->channels); | |
$buffer = RPL_LUSERCHANNELS . "{$nick} {$channels} :channels formed"; | |
$this->write($buffer); | |
$buffer = RPL_LUSERME . "{$nick} :I have {$this->connections} clients and 1 servers"; | |
$this->write($buffer); | |
} | |
private function broadcast_join($channel) | |
{ | |
foreach ($this->channels[$channel] as $user) { | |
if ($user != $this->current) { | |
$socket = $this->clients[$user]['socket']; | |
$buffer = "JOIN :{$channel}"; | |
$this->write($buffer, "client", $socket); | |
} | |
} | |
} | |
private function broadcast_part($channel, $reason) | |
{ | |
foreach ($this->channels[$channel] as $user) { | |
if ($user != $this->current) { | |
$socket = $this->clients[$user]['socket']; | |
$buffer = " PART {$channel} :{$reason}"; | |
$this->write($buffer, "client", $socket); | |
} | |
} | |
} | |
// <channel> <user> <host> <server> <nick> <H|G>[*][@|+] :<hopcount> <real name> | |
private function cmd_who($args) | |
{ | |
$channel = $args[0]; | |
$current_nick = $this->clients[$this->current]['nick']; | |
if (!empty($channel)) { | |
if (isset($this->channels[$channel])) { | |
foreach ($this->channels[$channel] as $user) { | |
$host = $this->clients[$user]['full_host']; | |
$nick = $this->clients[$user]['nick']; | |
$name = $this->clients[$user]['name']; | |
$real = $this->clients[$user]['real_name']; | |
$buffer = RPL_WHOREPLY . "{$current_nick} {$channel} {$name} {$host} {$this->settings['host']} {$nick} H@ :0 {$real}"; | |
$this->write($buffer); | |
} | |
$buffer = RPL_ENDOFWHO . "{$current_nick} {$channel} :End of /WHO list."; | |
$this->write($buffer); | |
} | |
else { | |
$buffer = "{$channel} :No such channel."; | |
$this->write($buffer); | |
} | |
} | |
else { | |
// client list here shit... | |
} | |
} | |
private function cmd_names($args) | |
{ | |
$channel = $args[0]; | |
$current_nick = $this->clients[$this->current]['nick']; | |
if (isset($this->channels[$channel])) { | |
foreach ($this->channels[$channel] as $user) { | |
$nick = $this->clients[$user]['nick']; | |
$name_list .= "{$nick} "; | |
} | |
$buffer = RPL_NAMREPLY . "{$current_nick} = {$channel} :" . trim($name_list); | |
$this->write($buffer); | |
$buffer = RPL_ENDOFNAMES . "{$current_nick} {$channel} :End of /NAMES list."; | |
$this->write($buffer); | |
} | |
else { | |
$buffer = "{$channel} :No such channel."; | |
$this->write($buffer); | |
} | |
} | |
private function cmd_ison($args) | |
{ | |
$current_nick = $this->clients[$this->current]['nick']; | |
$server = $args[0]; | |
$names = explode(" ", $args[1]); | |
foreach ($names as $name) { | |
if (isset($this->nicks[$name])) { | |
$on_list .= "{$name} "; | |
} | |
} | |
$buffer = RPL_ISON . "{$current_nick} :" . trim($on_list); | |
$this->write($buffer); | |
} | |
private function cmd_privmsg($args) | |
{ | |
$target = $args[0]; | |
$message = ltrim($args[1], ":"); | |
if (isset($this->channels[$target])) { | |
foreach ($this->channels[$target] as $user) { | |
if ($user != $this->current) { | |
$nick = $this->clients[$user]['nick']; | |
$socket = $this->clients[$user]['socket']; | |
$buffer = "PRIVMSG {$target} :" . $message; | |
$this->write($buffer, "client", $socket); | |
} | |
} | |
} | |
elseif ($this->nicks[$target]) { | |
$socket = $this->clients[$this->nicks[$target]]['socket']; | |
$buffer = "PRIVMSG {$target} :" . $message; | |
$this->write($buffer, "client", $socket); | |
} | |
else { | |
$buffer = "{$channel} :No such channel."; | |
$this->write($buffer); | |
} | |
} | |
private function add_client($socket) | |
{ | |
socket_getpeername($socket, $address, $port); | |
$this->connections++; | |
$this->total_connections++; | |
$this->current = $this->total_connections; | |
$this->clients[$this->total_connections]['socket'] = $socket; | |
$this->clients[$this->total_connections]['address'] = $address; | |
$this->clients[$this->total_connections]['modes'] = "+i"; // to be polite | |
$this->lt("Connection from {$address}:{$port} ({$this->total_connections})"); | |
$this->lt("Connection count: {$this->connections}"); | |
if ($this->lookup_host) { | |
$this->write("NOTICE Auth :*** Looking up your hostname..."); | |
if (isset($this->host_cache[$address])) { | |
$hostname = $this->host_cache[$address]; | |
$this->write("NOTICE Auth :*** Found your hostname ({$hostname}) -- cached"); | |
} | |
else { | |
$hostname = gethostbyaddr($address); | |
$this->host_cache[$address] = $hostname; | |
$this->write("NOTICE Auth :*** Found your hostname ({$hostname})"); | |
} | |
$this->clients[$this->total_connections]['host'] = $hostname; | |
} | |
$this->clients[$this->total_connections]['port'] = $port; | |
return $this->total_connections; | |
} | |
private function remove_client($id) | |
{ | |
if (!@socket_shutdown($this->clients[$id]['socket'], 2)) { | |
$this->lt("Client connection lost! ({$id})"); | |
} | |
else { | |
$this->lt("Client disconnected. ({$id})"); | |
} | |
@socket_close($this->clients[$id]['socket']); | |
unset($this->nicks[$this->clients[$id]['nick']]); | |
unset($this->clients[$id]); | |
unset($this->sockets[$id]); | |
$this->connections--; | |
var_dump($this->clients); | |
$this->lt("Connection count: {$this->connections}"); | |
} | |
private function drop_all() | |
{ | |
foreach ($this->clients as $id => $client) { | |
$this->remove_client($id); | |
} | |
sleep(1);die(); | |
} | |
function socket_normal_read ($socket, $length) { | |
static $sockets = array (); | |
static $queues = array (); | |
static $sock_num = 0; | |
for ($i = 0; isset ($sockets[$i]) && $socket != $sockets[$i]; $i++); | |
if (!isset ($sockets[$i])) { | |
$sockets [$sock_num] = $socket; | |
$queues [$sock_num++] = ""; | |
} | |
$recv = socket_read ($socket, $length, PHP_BINARY_READ); | |
if ($recv === "") { | |
if (strpos ($queues[$i], LINE_END) === false) | |
return false; | |
} | |
else if ($recv !== false) { | |
$queues[$i] .= $recv; | |
} | |
$pos = strpos ($queues[$i], LINE_END); | |
if ($pos === false) | |
return ""; | |
$ret = substr ($queues[$i], 0, $pos); | |
$queues[$i] = substr ($queues[$i], $pos+1); | |
return $ret; | |
} | |
//////////////////////////////////// | |
///// Terminal Functions ///// | |
//////////////////////////////////// | |
public function sc() // setup colors | |
{ | |
$this->fcs['black'] = '0;30'; $this->fcs['dark_gray'] = '1;30'; | |
$this->fcs['blue'] = '0;34'; $this->fcs['light_blue'] = '1;34'; | |
$this->fcs['green'] = '0;32'; $this->fcs['light_green'] = '1;32'; | |
$this->fcs['cyan'] = '0;36'; $this->fcs['light_cyan'] = '1;36'; | |
$this->fcs['red'] = '0;31'; $this->fcs['light_red'] = '1;31'; | |
$this->fcs['purple'] = '0;35'; $this->fcs['light_purple'] = '1;35'; | |
$this->fcs['brown'] = '0;33'; $this->fcs['yellow'] = '1;33'; | |
$this->fcs['white'] = '1;37'; $this->fcs['light_gray'] = '0;37'; | |
$this->bcs['black'] = '40'; $this->bcs['red'] = '41'; | |
$this->bcs['green'] = '42'; $this->bcs['yellow'] = '43'; | |
$this->bcs['blue'] = '44'; $this->bcs['magenta'] = '45'; | |
$this->bcs['cyan'] = '46'; $this->bcs['light_gray'] = '47'; | |
} | |
public function ct($s, $fc = null, $bc = null) // color terminal | |
{ | |
$cs = ""; | |
if (isset($this->fcs[$fc])) { | |
$cs .= "\033[" . $this->fcs[$fc] . "m"; | |
} | |
if (isset($this->bcs[$bc])) { | |
$cs .= "\033[" . $this->bcs[$bc] . "m"; | |
} | |
$cs .= $s . "\033[0m"; | |
return $cs; | |
} | |
function lt($text, $color = null, $date = true ) // log text | |
{ | |
if ($date){ | |
echo $this->ct("[".@date("H:i:s")."]", 'light_green' ) . " "; | |
} | |
echo $this->ct($text, $color) . "\r\n"; | |
} | |
private function lh() // log header | |
{ | |
$this->lt(" _____ _ _ _____ _____ _____ _____ ", null, false); | |
$this->lt("| __ \| | | | __ \_ _| __ \ / ____|", null, false); | |
$this->lt("| |__) | |__| | |__) || | | |__) | | ", null, false); | |
$this->lt("| ___/| __ | ___/ | | | _ /| | ", null, false); | |
$this->lt("| | | | | | | _| |_| | \ \| |____ ", null, false); | |
$this->lt("|_| |_| |_|_| |_____|_| \_\\_____|", null, false); | |
$this->lt(" http://benphelps.me/ ", null, false); | |
$this->lt("", null, false); | |
} | |
} | |
$settings = array( | |
'netn' => 'PHelPsIRC', | |
'host' => 'irc.suddencraft.com', | |
'port' => 6667, | |
'bind' => '0.0.0.0' | |
); | |
$server = new Server($settings); | |
$server->run(); | |
?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment