Skip to content

Instantly share code, notes, and snippets.

@benphelps
Created June 30, 2011 08:50
Show Gist options
  • Save benphelps/1055876 to your computer and use it in GitHub Desktop.
Save benphelps/1055876 to your computer and use it in GitHub Desktop.
<?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