Skip to content

Instantly share code, notes, and snippets.

@scottwalters
Created March 7, 2016 04:03
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save scottwalters/9c3c693fa93d2dcb3567 to your computer and use it in GitHub Desktop.
Save scottwalters/9c3c693fa93d2dcb3567 to your computer and use it in GitHub Desktop.
#!/usr/bin/perl
=for comment
MUD<->IRC proxy
Presents MUD as an IRC server
Todo
----
v/ authentication and login -- user should be prompted for password, or password
should be PRIVMSG'd to the 'game' psuedo-user. once the nick and password
are both specified, send login info.
v/ MOTD/banners
v/ from client: ACTION performs commands, PRIVMSG does a 'say'
o. meaningful error messages -- no such user, connection to server closed,
etc. 421 -- unknown command -- that's it.
o. need users in current room (like 'look') and on the game (like 'who')
http://www.valinor.sorcery.net/docs/rfc1459/ -- nice HTML-izied, cross referenced
guide to the IRC protocol.
=cut
package WeeIRC;
use strict;
use warnings;
no warnings 'uninitialized';
use Socket;
use POSIX qw(:errno_h);
use Sys::Hostname;
# use Data::Alias;
# use Error ':try';
use IO::Handle;
use Event;
use Coro::Event;
use Coro;
use Coro::Handle;
use Coro::Socket;
our $VERSION = '0.2';
my $EOF = "\r\n";
my $serverport = 6667;
#################################################
## DEBUGGING
# $SIG{__DIE__} = $SIG{INT} = sub {
# # when someone does kill -INT <our pid> from the command line, dump our stack and exit
# STDERR->print(shift, map { (caller($_))[0] ? sprintf("%s at line %d\n", (caller($_))[1,2]) : ''; } 0..30);
# STDERR->print(join "\n", @_);
# exit 1;
# };
open my $debug, '>>', '/dev/null' or die $!;
# open my $debug, '>>', 'wee-irc-debug.log' or die $!;
# my $debug = \*STDOUT; # doesn't register as a filehandle
sub debug {
$debug->print(@_);
$debug->flush;
}
local $SIG{PIPE} = sub {
# debug("sig pipe, $!, " . join " ", @_);
# debug("\n");
};
my $verboseLvl = 5;
#################################################
## START
# global state
# my $host = 'weehours.net';
my $host = 'localhost';
my $weehours = inet_ntoa scalar gethostbyname $host;
my @players;
my @sessions;
local $SIG{TERM} = sub {
foreach my $session (@sessions) {
close $session->client;
close $session->server;
}
exit;
};
#
# accept connections
#
async {
my $listen;
# local $SIG{__DIE__};
eval { $listen = Coro::Socket->new(
LocalPort => $serverport,
Type => SOCK_STREAM,
Listen => SOMAXCONN,
) } or die $@;
# $listen->autoflush(1);
while(1) {
#
# accept and proxy connections
#
debug("accept: 1\n");
my $client = $listen->accept();
debug("accept: 2\n");
$client or next;
$client->autoflush(1);
# trace( "SERVER: accepted a connection from " . inet_ntoa($client->peeraddr()), 7 );
trace( "SERVER: accepted a connection from unknown", 7 );
#
# chat with the game
#
#
# persistant shared state
#
my $session; $session = do {
package WeeIRC::Session;
use Socket;
use Attribute::Property;
sub new :New;
sub nick :Property;
sub channel :Property;
sub current_command :Property;
sub last_command :Property;
sub client :Property;
sub server :Property;
sub send_server { $_[0]->server or return; eval { $_[0]->server->print($_[1], "\n"); }; eval { WeeIRC::debug("SERVER OUT: $_[1]\n"); 1; }; };
sub send_client { $_[0]->client or return; eval { $_[0]->client->print($_[1], "\r\n"); }; WeeIRC::debug("CLIENT OUT: $_[1]\n") or die $!; };
sub connect {
$_[0]->server = Coro::Socket->new(
PeerAddr => $weehours,
PeerPort => 2000,
Type => SOCK_STREAM,
# Blocking => 1,
Autoflush => 1,
);
};
__PACKAGE__;
}->new(client => $client);
push @sessions, $session;
# XXX testing
# $session->nick = 'test';
# die unless $session->nick eq 'test';
# $session->nick = undef;
# start a new execution context for the IRC client -- that context will later spawn
# an execution context for the MUD if all goes well
async { irc_accept($session); };
}
};
#
# Coro::Event runloop
#
# Coro::Event is insane and wants us to have at least one event... or something
async {
my $timer = Coro::Event->timer(interval => 300, );
1 while $timer->next;
};
cede for 1..10;
Event::loop();
exit;
#################################################
## IRC CLIENT HANDLER
sub irc_accept {
# debug("debug: in irc_accept ", __LINE__, "\n");
my $session = shift() or return;
# debug("debug: in irc_accept ", __LINE__, "\n");
my $client = $session->client or die; # return;
# debug("debug: in irc_accept ", __LINE__, "\n");
my $parse_command = sub {
#
# read a line of input and digest it
#
# debug("client: 1\n"); $debug->flush;
my $input;
POLL:
$input = $client->readline;
defined $input or goto client_done;
# $client->fh->eof and return 0; # eof is only valid after the first read attempt # XXX not detecting closed connection -- busy spins
# debug("client: 2\n");
debug("CLIENT IN (@{[ $session->nick ]}): ", $input, "\n"); $debug->flush;
$input or goto POLL;
chomp $input;
$input or goto POLL;
# clients might prefix commands with ":$nick ". remove that.
$input =~ s{^:\S+ }{};
my @arglist;
while($input) {
if(substr($input, 0, 1) eq ':') {
substr $input, 0, 1, undef; # remove the ':'
push @arglist, $input;
last;
}
(my $first, my $rest) = $input =~ /^(\S+) ?(.*)/ or last;
push @arglist, $first;
$input = $rest;
}
my $command = uc shift @arglist;
return $command, @arglist if wantarray;
1;
};
# debug("debug: in irc_accept ", __LINE__, "\n");
$session->send_client("NOTICE AUTH :*** WeeHours IRC Proxy $VERSION");
$session->send_client("NOTICE AUTH :*** Ready to get your user and nick info from you and then connect to the game");
# debug("debug: in irc_accept ", __LINE__, "\n");
#
# this is the per-connection client-facing run-loop
#
while(1) {
#
# TCP next
#
(my $command, my @arglist) = $parse_command->() or goto client_done;
$arglist[-1] =~ s/[\x00-\x1f]//g if @arglist; # no nasty control codes, such as in PRIVMSG ACTIONs
#
### handle commands:
#
$session->current_command = $command;
# dispatch commands to handlers
({
AUTH => \&com_AUTH,
NICK => \&com_NICK,
USER => \&com_USER,
PRIVMSG => \&com_PRIVMSG,
NOTICE => \&com_NOTICE,
WHO => \&com_WHO,
NAMES => \&com_NAMES,
LIST => \&com_LIST,
PING => \&com_PING,
PONG => \&com_PONG,
QUIT => sub { goto client_done; },
JOIN => \&com_JOIN,
CHANNEL => \&com_JOIN,
MOTD => \&com_MOTD,
KICK => \&com_KICK,
MODE => \&com_MODE,
DEBUG => \&com_DEBUG,
USERHOST => \&com_USERHOST,
# PART => 0, # XXX should quit us off of the MUD
# RAW => \&com_RAW,
# INFO VERSION STATS -- commmands to server likely to be sent automatically by an IRC client
# INVITE KICK
}->{$command} || sub {
# unknown command
$session->send_server(lc($command) . ($arglist[-1] ? ' ' : '' ) . $arglist[-1]) or goto client_done;
})->( $session, @arglist );
# $session->send_client(":$host 421 @{[ $session->nick ]} $command :Unknown command ``$command''");
# debug("CLIENT: sent unknown command: $command\n");
# XXX -- if we get a 'What ?' from the server, maybe do a 421 on $session->last_command ?
$session->last_command = $command;
}
client_done:
debug("client_done: error was: $!\n");
eval { $session->send_server("quit"); $session->server->close(); };
eval { $session->client->close(); };
}
#################################################
# commands from the client
sub com_USER {
my $session = shift;
my @quad = @_;
# XXX - none of this data is stored, but I don't think it's needed.
# until this happens, the server won't really talk to the client
# 462 ERR_ALREADYREGISTRED
# $session->send_client(":$host 462 :Eh? You've already sent a USER command");
}
sub com_NICK {
my $session = shift;
my $nick = shift;
# XXX should attempt to figure out if the user even exists (on the MUD side)
# if($session->nick)
if($session->channel) {
# now, nick isn't sent until after channel join, so they can change it before then
$session->send_client(":$host 436 $nick :Eh? Right or wrong, login information has already been sent to the game. /quit and relog to change name.");
return;
}
my $oldnick = $session->nick;
$session->nick = $nick;
unless($oldnick) {
$session->send_client(":$host 001 $nick :WeeHours Multi-User Dungeon IRC Gateway");
$session->send_client(":$host 002 $nick :Your host is slowass.net[slowass.net/$serverport], running something Scott wrote in Perl $VERSION... hang on, connecting to the game...");
$session->connect;
async { mud_accept($session); };
com_MOTD($session);
$session->send_client(":$host PRIVMSG $nick :Connected! To login to the game, /join #mud, specifying $nick\'s password as the channel key.");
}
}
sub com_JOIN {
my $session = shift;
my $channel = shift; # XXX should check that it starts with a '#'
my $key = shift;
my $nick = $session->nick;
$channel eq '#mud' or do {
$session->send_client(":mud 403 $nick :No such channel"); # ERR_NOSUCHCHANNEL
return;
};
$key or do {
# 475 ERR_BADCHANNELKE
$session->send_client(":$host 475 $nick :Specify the password for character '$nick' as the channel key");
return;
};
$session->channel = $channel;
$session->send_server( $nick ); # username
$session->send_server( $key ); # password
$session->send_client(":$nick JOIN :$channel");
com_NAMES($session, $channel);
}
sub com_NAMES {
my $session = shift;
# everyone in the channel
# XXX -- currently, map.c sends a list of everyone on the game
$session->send_server("REQ:gui|cmd:19");
}
sub com_WHO {
com_NAMES(@_); # XXX -- /who is really something else but doing this for now
}
sub com_PRIVMSG {
my $session = shift;
my $target = shift;
my $message = shift;
if(! $session->channel) {
# some kind of "no permission" error message XXX
return;
}
debug("PRIVMSG: target: $target message: $message\n");
# debug(map("$_ ", map ord, split //, $message), "\n");
# :woggle!~somebody@c-24-63-116-152.hsd1.comcast.net PRIVMSG #perlhelp :It's just a standard tree traversal.
# XXX need an IMP command for getting the names off of the 'who' list.
# XXX this should do a 'tell' if the target is a name on the 'who' list.
# XXX should recognize special targets, including 'shout'
if($message =~ m/^ACTION ?/) {
# ($message) = $message =~ m/^\001(.*)\001$/ or debug("no pesky 001's to remove?\n"); # XXX
debug("PRIVMSG: it's an action: message is: $message\n");
# PRIVMSG #mud :ACTION smiles
$message =~ s/^ACTION ?//;
# this approach uses action verbs/feelings
# $message =~ s/^(\w+)s /$1 /; # /me smiles -> smile
# $message =~ s/^(\w+) at /$1 / unless $message =~ m/^look/; # smile at joe -> smile joe
# $session->send_server($message);
# this approach just uses emote
# $session->send_server("/me $message");
# this approach just passes commands in-tact to the game
$message =~ s/^(\w+)s$/$1/; # /me smiles -> smile
$message =~ s/^(\w+)s /$1 /; # /me smiles -> smile
$message =~ s/^(\w+) at /$1 / unless $message =~ m/^look/; # smile at joe -> smile joe
$session->send_server($message);
return;
} elsif(lc($target) eq lc($session->nick)) {
# apparently one IRC client likes to message itself gibberish constantly, and the MUD
# drops the gibberish, and it actually wants the gibberish
my $nick = $session->nick;
my $command = $session->current_command;
$session->send_client("$nick!$nick\@$host $command $nick :$message");
} elsif($target eq $session->channel or $target eq '#mud' or $target eq 'mud') {
$session->send_server("say $message");
} else {
$target = lc $target;
$session->send_server("tell $target $message");
}
return 1;
}
sub com_NOTICE {
com_PRIVMSG(@_);
}
sub com_MODE {
my $session = shift;
my $nick = shift;
my $mode = shift;
if($nick eq $session->nick) {
$mode ||= '+';
$session->send_client("$host 221 $nick $mode");
} elsif($nick eq $session->channel) {
# mode starts with + instead of : for some reason
$mode ||= '+sptn';
$session->send_client("$host 324 @{[ $session->channel ]} $mode");
}
# XXX -- replies are RPL_CHANNELMODEIS (324), RPL_UMODEIS (221)
}
sub com_LIST {
my $session = shift;
# lists all of the channels
# XXX -- just return a hard-code of #mud
}
sub com_PING {
my $session = shift;
my @args = (@_, '');
$session->send_client(":$host PONG :$args[0]");
}
sub com_PONG {
my $session = shift;
# XXX right now, we don't care we got a PONG, as we never send PINGs
}
sub com_MOTD {
my $session = shift;
my $nick = $session->nick;
# XXX -- should just do 'news' on the game
# XXX -- or just make this the title screen of the game
open my $motd, '<', 'motd.txt' or do { debug("no motd.txt file: $!\n"); return; };
$session->send_client(":$host 375 $nick :- Message of the Day");
while(my $line = <$motd>) {
chomp $line;
$session->send_client(":$host 372 $nick :- $line");
}
$session->send_client(":$host 376 $nick :- End of Message of the Day");
# 375 RPL_MOTDSTART
# ":- Message of the day - "
# 372 RPL_MOTD
# ":- "
# 376 RPL_ENDOFMOTD
# ":End of /MOTD command"
#
## - When responding to the MOTD message and the MOTD file
# is found, the file is displayed line by line, with
# each line no longer than 80 characters, using
# RPL_MOTD format replies. These should be surrounded
# by a RPL_MOTDSTART (before the RPL_MOTDs) and an
# RPL_ENDOFMOTD (after).
}
sub com_KICK {
my $session = shift;
my $chan = shift;
my $target = shift;
$target = $chan if ! $target;
$session->send_server("kick $target");
}
sub com_TOPIC {
# 331 RPL_NOTOPIC
# " :No topic is set"
# 332 RPL_TOPIC
# " :"
#
# - When sending a TOPIC message to determine the
# channel topic, one of two replies is sent. If
# the topic is set, RPL_TOPIC is sent back else
# RPL_NOTOPIC.
}
sub com_DEBUG {
my $session = shift;
my $nick = $session->nick;
foreach my $user (@sessions) {
$session->send_client("mud!mud\@$host PRIVMSG $nick :debug: @{[ $user->nick ]}");
}
}
# 302 RPL_USERHOST
# ":[<reply>{<space><reply>}]"
#
# - Reply format used by USERHOST to list replies to
# the query list. The reply string is composed as
# follows:
#
# <reply> ::= <nick>['*'] '=' <'+'|'-'><hostname>
#
# The '*' indicates whether the client has registered
# as an Operator. The '-' or '+' characters represent
# whether the client has set an AWAY message or not
# respectively.
sub com_USERHOST {
my $session = shift;
my @args = @_;
my $nick = $session->nick;
my $out = '';
for my $arg (@args) {
$out .= ' ' if $out;
$out .= "$arg=-127.0.0.1";
}
$session->send_client("$host 302 :$out");
1;
}
#################################################
## MUD HANDLER
sub mud_accept {
debug("debug: in mud_accept\n");
my $session = shift or return;
my $server = $session->server;
$server or do {
die "mud_accept: no server!";
return;
};
my $parse_command = sub {
my $packet;
my $meat;
parse_command_again:
# debug("server: 1\n"); $debug->flush;
$packet = $server->readline;
defined $packet or goto server_done;
# $server->read($packet, 4096);
# debug("server: 2\n");
# debug("SERVER IN: ", $packet); $debug->flush();
chomp $packet;
$packet =~ s/[\000-\036]//sg;
$packet =~ s/^> //; # get rid of the command prompt
if($packet =~ m/^REQ:/) {
# debug("server: ", __LINE__, "\n");
# XXX -- this program parses IMP-over-TCP requests, but doesn't (yet) do 'make conneciton' -- FYI
my @fragments = split /\|/, $packet;
$meat = {};
while(@fragments) {
my $frag = shift @fragments;
my $x = index $frag, ':';
if($x == -1) {
debug("debug: fragment '$frag' is not a key:value pair\n");
next;
}
my $key = substr $frag, 0, $x;
my $value = substr $frag, $x+1;
debug("debug: fragment '$frag' has key '$key' and value '$value'\n");
if($key eq 'DATA') {
$value .= '|' . join '|', @fragments if @fragments;
@fragments = ();
}
$meat->{$key} = $value;
}
} elsif($packet =~ m/^You say in \w+: (.*)/) {
# filter these out -- they're just spammy
goto parse_command_again;
# } elsif($packet =~ m/^(\w+) (.*)/ and grep $1 eq $_, @players) {
# # experimental -- XXX
# # sucks too badly -- messes up who list, doesn't trigger on monsters... maybe after IMP is running
# $meat = { REQ => 'gui', cmd => 7, from => "$1!$1\@$host", to => '#mud', DATA => "ACTION $2" };
} elsif($packet =~ m/^(\w+) says in \w+: (.*)/) {
# debug("server: ", __LINE__, "\n");
# these alternate cases fake having text running over IMP as it should be -- stop gap
# cmd 7 is "push text"
$meat = { REQ => 'gui', cmd => 7, from => "$1!$1\@$host", to => '#mud', DATA => $2 };
} elsif($packet =~ m/^(\w+) tells you in \w+: (.*)/) {
# debug("server: ", __LINE__, "\n");
$meat = { REQ => 'gui', cmd => 7, from => "$1!$1\@$host", to => $session->nick, DATA => $2 };
} else {
# debug("server: ", __LINE__, "\n");
# everything else
$meat = { REQ => 'gui', cmd => 7, from => 'mud', to => '#mud', DATA => $packet };
}
goto parse_command_again if $meat->{REQ} ne 'gui';
return $meat;
};
my $dispatch = {
# hot
7 => sub { MUD_push_text(@_) },
19 => sub { MUD_names_list(@_) },
# not in this version of the client interface
2 => sub { MUD_set_screen_size(@_) }, # client just ignores and then does a set_screen_size back
3 => sub { MUD_query_screen_size(@_) }, # hardcoded
5 => sub { MUD_scroll_screen(@_) },
11 => sub { MUD_send_screenful(@_) }, # from server, it means the server is done sending the screenful
12 => sub { MUD_redraw_column(@_) },
14 => sub { MUD_redraw_row(@_) },
20 => sub { MUD_walkto(@_) },
21 => sub { MUD_walkpos(@_) },
22 => sub { MUD_pick(@_) },
23 => sub { MUD_drop(@_) },
24 => sub { MUD_join(@_) },
25 => sub { MUD_part(@_) },
26 => sub { MUD_create(@_) }, # game is telling the client to go into user create mode
};
#
# server run loop
#
while(1) {
my $meat = $parse_command->();
if(! exists $dispatch->{$meat->{cmd}}) {
debug("server: unknown command ``$meat->{cmd}'' from MUD\n");
next;
}
$dispatch->{$meat->{cmd}}->($session, $meat);
}
server_done:
debug("MUD closed connection: $!\n");
eval { local $SIG{__DIE__}; $session->server->close(); };
}
#################################################
# commands from the MUD
sub MUD_push_text {
my $session = shift;
my $meat = shift;
# my $privmsg = '';
# $privmsg = ":$meat->{from} " if exists $meat->{from};
# $privmsg .= 'PRIVMSG ';
# $privmsg .= $meat->{to} . ' '; # might be user name or channel name
# $privmsg .= ":$meat->{DATA}";
# $session->send_client($privmsg);
if(! $session->nick ) {
# messages should come from weehours.net as a sort of server message -- xchat likes this.
$meat->{from} = $host;
$meat->{to} = '*';
} elsif(! $session->channel) {
# if we aren't in a channel, make sure the message is addressed directly to the client.
# even if addressed directly to the nick, it'll open a new tab in xchat if it's apparently from someone
$meat->{from} = $host;
$meat->{to} = $session->nick;
}
my $from = $meat->{from} ? ":$meat->{from} " : '';
$session->send_client("${from}PRIVMSG $meat->{to} :$meat->{DATA}");
}
sub MUD_names_list {
my $session = shift;
my $meat = shift;
my $nick = $session->nick;
my @names = split / /, $meat->{DATA};
@players = @names;
# if(grep { uc($session->last_command) eq $_ } 'NAMES', 'JOIN', 'MODE') {
while(@names) {
my $names = join ' ', splice @names, 0, 20, ();
$session->send_client(":$host 353 $nick \@ #mud :$names");
}
$session->send_client(":$host 366 $nick #mud :End of /NAMES list");
# } else {
# # presumably WHO
# # XXX client doesn't like this output but I can't tell why not! ugh
# for my $name (@names) {
# # :irc.kagmir.ca 352 scrottie0 #perlhelp ~duff dragon.cbi.tamucc.edu irc.igs.ca PerlJam H :2 Jonathan Scott Duff
# $session->send_client(":$host 352 $nick #mud $name $host $host $name H :$name");
# }
# $session->send_client(":$host 315 $nick * :End of /WHO list");
# }
# /NAMES response:
# XXX -- sending same response to NAMES and WHO for the time being, though WHO is like 'who' on MUD
# :irc.kagmir.ca 353 scrottie1 @ #perlhelp :scrottie1 infinity2 scrottie sitexec NoGods S-Pulse imMute Docmarten h3h di2co czthwrk__ codesnik Riedel @Juerd m0nark Garion beeyatch kore felixhcat memelo @cfedde Delivos @xmath LabEendje webmind +mauke k0re khaladan xd R|SC xdeth Sertys CobyJones planet77 fluxion_ dufuz DeAdWoRld wolverian zeroXten
# :irc.kagmir.ca 353 scrottie1 @ #perlhelp :@Somni StinkfooT uri Phazorx PerlJam czth mbean drw xtor Averell @flang qw forrest_ motel6 vlad_ Micha98 +cardioid irc____ inferno__ digip1mp +knowpaced klimt @Yaakov` jeffz gib jt401 Setzer CapoeiraB nobnobnob Speed-- thegeek indigoid _farmer_ Daveman fjoms eclypse Ahnberg ravnx Skinjob Perl_warn @perl-d
# :irc.kagmir.ca 353 scrottie1 @ #perlhelp :CapoeiraA mortum dngor grew^M
# :irc.kagmir.ca 366 scrottie1 #perlhelp :End of /NAMES list.^M
# 353 RPL_NAMREPLY
# 366 RPL_ENDOFNAMES
}
# /TOPIC response:
# :irc.kagmir.ca 332 scrottie1 #perlhelp :This is where the help is. For Perl. Right here. In this channel. The Help. Seriously.^M
# :irc.kagmir.ca 333 scrottie1 #perlhelp gripe!gripe@calamitycaucus.com 1123554824^M
sub MUD_walkto { }
sub MUD_walkpos { }
sub MUD_set_screen_size { }
sub MUD_query_screen_size { }
sub MUD_scroll_screen { }
sub MUD_redraw_column { }
sub MUD_redraw_row { }
sub MUD_send_screenful { }
sub MUD_pick { }
sub MUD_drop { }
sub MUD_create { }
#################################################
# logging and tracing
sub trace {
my $session; $session = shift if ref $_[0];
my $msg = shift;
my $lvl = shift() || 0;
my $timestamp = localtime();
return unless $lvl >= $verboseLvl;
if($session) {
# XXX 421 is the "unknown command" error, I think, but I don't know what would work better
$session->send_client(":$host 421 @{[ $session->nick ]} COMMANDHERE :trace daignostic message``$msg''");
} else {
debug("[$timestamp] ($lvl) $msg\n");
}
}
__END__
Upon connecting to an IRC server, a client is sent the MOTD (if
present) as well as the current user/server count (as per the LUSER
command). The server is also required to give an unambiguous message
to the client which states its name and version as well as any other
introductory messages which may be deemed appropriate.
After dealing with this, the server must then send out the new user's
nickname and other information as supplied by itself (USER command)
and as the server could discover (from DNS/authentication servers).
The server must send this information out with NICK first followed by
USER.
The current IRC protocol has 3 types of labels: the nickname, the
channel name and the server name.
IRC messages are always lines of characters terminated with a CR-LF
(Carriage Return - Line Feed) pair, and these messages shall not
exceed 512 characters in length, counting all characters including
the trailing CR-LF. Thus, there are 510 characters maximum allowed
for the command and its parameters. There is no provision for
continuation message lines. See section 7 for more details about
current implementations.
Most of the messages sent to the server generate a reply of some
sort. The most common reply is the numeric reply, used for both
errors and normal replies. The numeric reply must be sent as one
message consisting of the sender prefix, the three digit numeric, and
the target of the reply. A numeric reply is not allowed to originate
from a client; any such messages received by a server are silently
dropped.
The PASS command is used to set a 'connection password'. The
password can and must be set before any attempt to register the
connection is made. Currently this requires that clients send a PASS
command before sending the NICK/USER combination and servers *must*
send a PASS command before any SERVER command. The password supplied
must match the one contained in the C/N lines (for servers) or I
lines (for clients). It is possible to send multiple PASS commands
before registering but only the last one sent is used for
verification and it may not be changed once registered. Numeric
Replies:
ERR_NEEDMOREPARAMS ERR_ALREADYREGISTRED
Example:
PASS secretpasswordhere
The USER message is used at the beginning of connection to specify
the username, hostname, servername and realname of s new user. It is
also used in communication between servers to indicate new user
arriving on IRC, since only after both USER and NICK have been
received from a client does a user become registered.
Between servers USER must to be prefixed with client's NICKname.
Once a user has joined a channel, they receive notice about all
commands their server receives which affect the channel. This
includes MODE, KICK, PART, QUIT and of course PRIVMSG/NOTICE. The
JOIN command needs to be broadcast to all servers so that each server
knows where to find the users who are on the channel. This allows
optimal delivery of PRIVMSG/NOTICE messages to the channel.
If a JOIN is successful, the user is then sent the channel's topic
(using RPL_TOPIC) and the list of users who are on the channel (using
RPL_NAMREPLY), which must include the user joining.
Command: NAMES
Parameters: [{,}]
By using the NAMES command, a user can list all nicknames that are
visible to them on any channel that they can see. Channel names
which they can see are those which aren't private (+p) or secret (+s)
or those which they are actually on. The parameter
specifies which channel(s) to return information about if valid.
There is no error reply for bad channel names.
If no parameter is given, a list of all channels and their
occupants is returned. At the end of this list, a list of users who
are visible but either not on any channel or not on a visible channel
are listed as being on `channel' "*".
Numerics:
RPL_NAMREPLY RPL_ENDOFNAMES
Command: LIST
Parameters: [{,} []]
The list message is used to list channels and their topics. If the
parameter is used, only the status of that channel
is displayed. Private channels are listed (without their
topics) as channel "Prv" unless the client generating the query is
actually on that channel. Likewise, secret channels are not listed
at all unless the client is a member of the channel in question.
Numeric Replies:
ERR_NOSUCHSERVER RPL_LISTSTART
RPL_LIST RPL_LISTEND
Examples:
LIST ; List all channels.
Numeric Replies:
ERR_NOSUCHSERVER RPL_LISTSTART
RPL_LIST RPL_LISTEND
Useful message codes:
401 ERR_NOSUCHNICK
" :No such nick/channel"
- Used to indicate the nickname parameter supplied to a
command is currently unused.
402 ERR_NOSUCHSERVER
" :No such server"
- Used to indicate the server name given currently
doesn't exist.
403 ERR_NOSUCHCHANNEL
" :No such channel"
- Used to indicate the given channel name is invalid.
404 ERR_CANNOTSENDTOCHAN
" :Cannot send to channel"
- Sent to a user who is either (a) not on a channel
which is mode +n or (b) not a chanop (or mode +v) on
a channel which has mode +m set and is trying to send
a PRIVMSG message to that channel.
451 ERR_NOTREGISTERED
":You have not registered"
- Returned by the server to indicate that the client
must be registered before the server will allow it
to be parsed in detail.
461 ERR_NEEDMOREPARAMS
" :Not enough parameters"
- Returned by the server by numerous commands to
indicate to the client that it didn't supply enough
parameters.
431 ERR_NONICKNAMEGIVEN
":No nickname given"
- Returned when a nickname parameter expected for a
command and isn't found.
432 ERR_ERRONEUSNICKNAME
" :Erroneus nickname"
433 ERR_NICKNAMEINUSE
" :Nickname is already in use"
- Returned when a NICK message is processed that results
in an attempt to change to a currently existing
nickname.
351 RPL_VERSION
". :"
- Reply by the server showing its version details.
The is the version of the software being
used (including any patchlevel revisions) and the
is used to indicate if the server is
running in "debug mode".
The "comments" field may contain any comments about
the version or further version details.
353 RPL_NAMREPLY
" :[[@|+] [[@|+] [...]]]"
366 RPL_ENDOFNAMES
" :End of /NAMES list"
- To reply to a NAMES message, a reply pair consisting
of RPL_NAMREPLY and RPL_ENDOFNAMES is sent by the
server back to the client. If there is no channel
found as in the query, then only RPL_ENDOFNAMES is
returned. The exception to this is when a NAMES
message is sent with no parameters and all visible
channels and contents are sent back in a series of
RPL_NAMEREPLY messages with a RPL_ENDOFNAMES to mark
the end.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment