summaryrefslogtreecommitdiffstats
path: root/Net/IRC/DCC.pm
diff options
context:
space:
mode:
authorLibravatarWilliam Heimbigner <william.heimbigner@gmail.com>2014-04-23 18:36:38 +0400
committerLibravatarWilliam Heimbigner <william.heimbigner@gmail.com>2014-04-23 18:36:38 +0400
commit87cf6352810c00952a79e58a1d418a28be01b33c (patch)
tree350ee3c59f8c1e69b642801b0561e2ad34bb44c9 /Net/IRC/DCC.pm
parent534ca688d9c9c6ecd6584e53dca5cf0b3d996632 (diff)
Added Net-IRC to the repo
Diffstat (limited to 'Net/IRC/DCC.pm')
-rw-r--r--Net/IRC/DCC.pm808
1 files changed, 808 insertions, 0 deletions
diff --git a/Net/IRC/DCC.pm b/Net/IRC/DCC.pm
new file mode 100644
index 0000000..eccbba3
--- /dev/null
+++ b/Net/IRC/DCC.pm
@@ -0,0 +1,808 @@
+#####################################################################
+# #
+# Net::IRC -- Object-oriented Perl interface to an IRC server #
+# #
+# DCC.pm: An object for Direct Client-to-Client connections. #
+# #
+# Copyright (c) 1997 Greg Bacon & Dennis Taylor. #
+# All rights reserved. #
+# #
+# This module is free software; you can redistribute or #
+# modify it under the terms of Perl's Artistic License. #
+# #
+#####################################################################
+# $Id: DCC.pm,v 1.1.1.1 2002/11/14 17:32:15 jmuhlich Exp $
+
+package Net::IRC::DCC;
+
+use strict;
+
+
+
+# --- #perl was here! ---
+#
+# The comments scattered throughout this module are excerpts from a
+# log saved from one particularly surreal night on #perl. Ahh, the
+# trials of being young, single, and drunk...
+#
+# ---------------------
+# \merlyn has offered the shower to a randon guy he met in a bar.
+# fimmtiu: Shower?
+# \petey raises an eyebrow at \merlyn
+# \merlyn: but he seems like a nice trucker guy...
+# archon: you offered to shower with a random guy?
+
+
+# Methods that can be shared between the various DCC classes.
+package Net::IRC::DCC::Connection;
+
+use Carp;
+use Socket; # need inet_ntoa...
+use strict;
+
+sub fixaddr {
+ my ($address) = @_;
+
+ chomp $address; # just in case, sigh.
+ if ($address =~ /^\d+$/) {
+ return inet_ntoa(pack "N", $address);
+ } elsif ($address =~ /^[12]?\d{1,2}\.[12]?\d{1,2}\.[12]?\d{1,2}\.[12]?\d{1,2}$/) {
+ return $address;
+ } elsif ($address =~ tr/a-zA-Z//) { # Whee! Obfuscation!
+ return inet_ntoa(((gethostbyname($address))[4])[0]);
+ } else {
+ return;
+ }
+}
+
+sub bytes_in {
+ return shift->{_bin};
+}
+
+sub bytes_out {
+ return shift->{_bout};
+}
+
+sub nick {
+ return shift->{_nick};
+}
+
+sub socket {
+ return shift->{_socket};
+}
+
+sub time {
+ return time - shift->{_time};
+}
+
+sub debug {
+ return shift->{_debug};
+}
+
+# Changes here 1998-04-01 by MJD
+# Optional third argument `$block'.
+# If true, don't break the input into lines... just process it in blocks.
+sub _getline {
+ my ($self, $sock, $block) = @_;
+ my ($input, $line);
+ my $frag = $self->{_frag};
+
+ if (defined $sock->recv($input, 10240)) {
+ $frag .= $input;
+ if (length($frag) > 0) {
+
+ warn "Got ". length($frag) ." bytes from $sock\n"
+ if $self->{_debug};
+
+ if ($block) { # Block mode (GET)
+ return $input;
+
+ } else { # Line mode (CHAT)
+ # We're returning \n's 'cause DCC's need 'em
+ my @lines = split /\012/, $frag, -1;
+ $lines[-1] .= "\012";
+ $self->{_frag} = ($frag !~ /\012$/) ? pop @lines : '';
+ return (@lines);
+ }
+ }
+ else {
+ # um, if we can read, i say we should read more than 0
+ # besides, recv isn't returning undef on closed
+ # sockets. getting rid of this connection...
+
+ warn "recv() received 0 bytes in _getline, closing connection.\n"
+ if $self->{_debug};
+
+ $self->{_parent}->handler(Net::IRC::Event->new('dcc_close',
+ $self->{_nick},
+ $self->{_socket},
+ $self->{_type}));
+ $self->{_parent}->parent->removefh($sock);
+ $self->{_socket}->close;
+ $self->{_fh}->close if $self->{_fh};
+ return;
+ }
+ } else {
+ # Error, lets scrap this connection
+
+ warn "recv() returned undef, socket error in _getline()\n"
+ if $self->{_debug};
+
+ $self->{_parent}->handler(Net::IRC::Event->new('dcc_close',
+ $self->{_nick},
+ $self->{_socket},
+ $self->{_type}));
+ $self->{_parent}->parent->removefh($sock);
+ $self->{_socket}->close;
+ $self->{_fh}->close if $self->{_fh};
+ return;
+ }
+}
+
+sub DESTROY {
+ my $self = shift;
+
+ # Only do the Disconnection Dance of Death if the socket is still
+ # live. Duplicate dcc_close events would be a Bad Thing.
+
+ if ($self->{_socket}->opened) {
+ $self->{_parent}->handler(Net::IRC::Event->new('dcc_close',
+ $self->{_nick},
+ $self->{_socket},
+ $self->{_type}));
+ $self->{_socket}->close;
+ close $self->{_fh} if $self->{_fh};
+ $self->{_parent}->{_parent}->parent->removeconn($self);
+ }
+
+}
+
+sub peer {
+ return ( $_[0]->{_nick}, "DCC " . $_[0]->{_type} );
+}
+
+# -- #perl was here! --
+# orev: hehe...
+# Silmaril: to, not with.
+# archon: heheh
+# tmtowtdi: \merlyn will be hacked to death by a psycho
+# archon: yeah, but with is much more amusing
+
+
+# Connection handling GETs
+package Net::IRC::DCC::GET;
+
+use IO::Socket;
+use Carp;
+use strict;
+
+@Net::IRC::DCC::GET::ISA = qw(Net::IRC::DCC::Connection);
+
+sub new {
+
+ my ($class, $container, $nick, $address,
+ $port, $size, $filename, $handle, $offset) = @_;
+ my ($sock, $fh);
+
+ # get the address into a dotted quad
+ $address = &Net::IRC::DCC::Connection::fixaddr($address);
+ return if $port < 1024 or not defined $address or $size < 1;
+
+ $fh = defined $handle ? $handle : IO::File->new(">$filename");
+
+ unless(defined $fh) {
+ carp "Can't open $filename for writing: $!";
+ $sock = new IO::Socket::INET( Proto => "tcp",
+ PeerAddr => "$address:$port" ) and
+ $sock->close();
+ return;
+ }
+
+ binmode $fh; # I love this next line. :-)
+ ref $fh eq 'GLOB' ? select((select($fh), $|++)[0]) : $fh->autoflush(1);
+
+ $sock = new IO::Socket::INET( Proto => "tcp",
+ PeerAddr => "$address:$port" );
+
+ if (defined $sock) {
+ $container->handler(Net::IRC::Event->new('dcc_open',
+ $nick,
+ $sock,
+ 'get',
+ 'get', $sock));
+
+ } else {
+ carp "Can't connect to $address: $!";
+ close $fh;
+ return;
+ }
+
+ $sock->autoflush(1);
+
+ my $self = {
+ _bin => defined $offset ? $offset : 0, # bytes recieved so far
+ _bout => 0, # Bytes we've sent
+ _connected => 1,
+ _debug => $container->debug,
+ _fh => $fh, # FileHandle we will be writing to.
+ _filename => $filename,
+ _frag => '',
+ _nick => $nick, # Nick of person on other end
+ _parent => $container,
+ _size => $size, # Expected size of file
+ _socket => $sock, # Socket we're reading from
+ _time => time,
+ _type => 'GET',
+ };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+# -- #perl was here! --
+# \merlyn: we were both ogling a bartender named arley
+# \merlyn: I mean carle
+# \merlyn: carly
+# Silmaril: man merlyn
+# Silmaril: you should have offered HER the shower.
+# \petey: all three of them?
+
+sub parse {
+ my ($self) = shift;
+
+ my $line = $self->_getline($_[0], 'BLOCKS');
+
+ next unless defined $line;
+ unless(print {$self->{_fh}} $line) {
+ carp ("Error writing to " . $self->{_filename} . ": $!");
+ close $self->{_fh};
+ $self->{_parent}->parent->removeconn($self);
+ $self->{_parent}->handler(Net::IRC::Event->new('dcc_close',
+ $self->{_nick},
+ $self->{_socket},
+ $self->{_type}));
+ $self->{_socket}->close;
+ return;
+ }
+
+ $self->{_bin} += length($line);
+
+
+ # confirm the packet we've just recieved
+ unless ( $self->{_socket}->send( pack("N", $self->{_bin}) ) ) {
+ carp "Error writing to DCC GET socket: $!";
+ close $self->{_fh};
+ $self->{_parent}->parent->removeconn($self);
+ $self->{_parent}->handler(Net::IRC::Event->new('dcc_close',
+ $self->{_nick},
+ $self->{_socket},
+ $self->{_type}));
+ $self->{_socket}->close;
+ return;
+ }
+
+ $self->{_bout} += 4;
+
+ # The file is done.
+ # If we close the socket, the select loop gets screwy because
+ # it won't remove its reference to the socket.
+ if ( $self->{_size} and $self->{_size} <= $self->{_bin} ) {
+ close $self->{_fh};
+ $self->{_parent}->parent->removeconn($self);
+ $self->{_parent}->handler(Net::IRC::Event->new('dcc_close',
+ $self->{_nick},
+ $self->{_socket},
+ $self->{_type}));
+ $self->{_socket}->close;
+ return;
+ }
+
+ $self->{_parent}->handler(Net::IRC::Event->new('dcc_update',
+ $self->{_nick},
+ $self,
+ $self->{_type},
+ $self ));
+}
+
+sub filename {
+ return shift->{_filename};
+}
+
+sub size {
+ return shift->{_size};
+}
+
+sub close {
+ my ($self, $sock) = @_;
+ $self->{_fh}->close;
+ $self->{_parent}->parent->removeconn($self);
+ $self->{_parent}->handler(Net::IRC::Event->new('dcc_close',
+ $self->{_nick},
+ $self->{_socket},
+ $self->{_type}));
+ $self->{_socket}->close;
+ return;
+}
+
+# -- #perl was here! --
+# \merlyn: I can't type... she created a numbner of very good drinks
+# \merlyn: She's still at work
+# \petey resists mentioning that there's "No manual entry
+# for merlyn."
+# Silmaril: Haven't you ever seen swingers?
+# \merlyn: she's off tomorrow... will meet me at the bar at 9:30
+# Silmaril: AWWWWwwww yeeeaAAHH.
+# archon: waka chica waka chica
+
+
+# Connection handling SENDs
+package Net::IRC::DCC::SEND;
+@Net::IRC::DCC::SEND::ISA = qw(Net::IRC::DCC::Connection);
+
+use IO::File;
+use IO::Socket;
+use Carp;
+use strict;
+
+sub new {
+
+ my ($class, $container, $nick, $filename, $blocksize) = @_;
+ my ($size, $port, $fh, $sock, $select);
+
+ $blocksize ||= 1024;
+
+ # Shell-safe DCC filename stuff. Trying to prank-proof this
+ # module is rather difficult.
+ $filename =~ tr/a-zA-Z.+0-9=&()[]%\-\\\/:,/_/c;
+ $fh = new IO::File $filename;
+
+ unless (defined $fh) {
+ carp "Couldn't open $filename for reading: $!";
+ return;
+ }
+
+ binmode $fh;
+ $fh->seek(0, SEEK_END);
+ $size = $fh->tell;
+ $fh->seek(0, SEEK_SET);
+
+ $sock = new IO::Socket::INET( Proto => "tcp",
+ Listen => 1);
+
+ unless (defined $sock) {
+ carp "Couldn't open DCC SEND socket: $!";
+ $fh->close;
+ return;
+ }
+
+ $container->ctcp('DCC SEND', $nick, $filename,
+ unpack("N",inet_aton($container->hostname())),
+ $sock->sockport(), $size);
+
+ $sock->autoflush(1);
+
+ my $self = {
+ _bin => 0, # Bytes we've recieved thus far
+ _blocksize => $blocksize,
+ _bout => 0, # Bytes we've sent
+ _debug => $container->debug,
+ _fh => $fh, # FileHandle we will be reading from.
+ _filename => $filename,
+ _frag => '',
+ _nick => $nick,
+ _parent => $container,
+ _size => $size, # Size of file
+ _socket => $sock, # Socket we're writing to
+ _time => 0, # This gets set by Accept->parse()
+ _type => 'SEND',
+ };
+
+ bless $self, $class;
+
+ $sock = Net::IRC::DCC::Accept->new($sock, $self);
+
+ unless (defined $sock) {
+ carp "Error in accept: $!";
+ $fh->close;
+ return;
+ }
+
+ return $self;
+}
+
+# -- #perl was here! --
+# fimmtiu: So a total stranger is using your shower?
+# \merlyn: yes... a total stranger is using my hotel shower
+# Stupid coulda sworn \merlyn was married...
+# \petey: and you have a date.
+# fimmtiu: merlyn isn't married.
+# \petey: not a bad combo......
+# \merlyn: perhaps a adate
+# \merlyn: not maerried
+# \merlyn: not even sober. --)
+
+sub parse {
+ my ($self, $sock) = @_;
+ my $size = ($self->_getline($sock, 1))[0];
+ my $buf;
+
+ # i don't know how useful this is, but let's stay consistent
+ $self->{_bin} += 4;
+
+ unless (defined $size) {
+ # Dang! The other end unexpectedly canceled.
+ carp (($self->peer)[1] . " connection to " .
+ ($self->peer)[0] . " lost");
+ $self->{_fh}->close;
+ $self->{_parent}->parent->removefh($sock);
+ $self->{_parent}->handler(Net::IRC::Event->new('dcc_close',
+ $self->{_nick},
+ $self->{_socket},
+ $self->{_type}));
+ $self->{_socket}->close;
+ return;
+ }
+
+ $size = unpack("N", $size);
+
+ if ($size >= $self->{_size}) {
+
+ if ($self->{_debug}) {
+ warn "Other end acknowledged entire file ($size >= ",
+ $self->{_size}, ")";
+ }
+ # they've acknowledged the whole file, we outtie
+ $self->{_fh}->close;
+ $self->{_parent}->parent->removeconn($self);
+ $self->{_parent}->handler(Net::IRC::Event->new('dcc_close',
+ $self->{_nick},
+ $self->{_socket},
+ $self->{_type}));
+ $self->{_socket}->close;
+ return;
+ }
+
+ # we're still waiting for acknowledgement,
+ # better not send any more
+ return if $size < $self->{_bout};
+
+ unless (defined $self->{_fh}->read($buf,$self->{_blocksize})) {
+
+ if ($self->{_debug}) {
+ warn "Failed to read from source file in DCC SEND!";
+ }
+ $self->{_fh}->close;
+ $self->{_parent}->parent->removeconn($self);
+ $self->{_parent}->handler(Net::IRC::Event->new('dcc_close',
+ $self->{_nick},
+ $self->{_socket},
+ $self->{_type}));
+ $self->{_socket}->close;
+ return;
+ }
+
+ unless($self->{_socket}->send($buf)) {
+
+ if ($self->{_debug}) {
+ warn "send() failed horribly in DCC SEND"
+ }
+ $self->{_fh}->close;
+ $self->{_parent}->parent->removeconn($self);
+ $self->{_parent}->handler(Net::IRC::Event->new('dcc_close',
+ $self->{_nick},
+ $self->{_socket},
+ $self->{_type}));
+ $self->{_socket}->close;
+ return;
+ }
+
+ $self->{_bout} += length($buf);
+
+ $self->{_parent}->handler(Net::IRC::Event->new('dcc_update',
+ $self->{_nick},
+ $self,
+ $self->{_type},
+ $self ));
+
+ return 1;
+}
+
+# -- #perl was here! --
+# fimmtiu: Man, merlyn, you must be drunk to type like that. :)
+# \merlyn: too many longislands.
+# \merlyn: she made them strong
+# archon: it's a plot
+# \merlyn: not even a good amoun tof coke
+# archon: she's in league with the guy in your shower
+# archon: she gets you drunk and he takes your wallet!
+
+
+# handles CHAT connections
+package Net::IRC::DCC::CHAT;
+@Net::IRC::DCC::CHAT::ISA = qw(Net::IRC::DCC::Connection);
+
+use IO::Socket;
+use Carp;
+use strict;
+
+sub new {
+
+ my ($class, $container, $type, $nick, $address, $port) = @_;
+ my ($sock, $self);
+
+ if ($type) {
+ # we're initiating
+
+ $sock = new IO::Socket::INET( Proto => "tcp",
+ Listen => 1);
+
+ unless (defined $sock) {
+ carp "Couldn't open DCC CHAT socket: $!";
+ return;
+ }
+
+ $sock->autoflush(1);
+ $container->ctcp('DCC CHAT', $nick, 'chat',
+ unpack("N",inet_aton($container->hostname)),
+ $sock->sockport());
+
+ $self = {
+ _bin => 0, # Bytes we've recieved thus far
+ _bout => 0, # Bytes we've sent
+ _connected => 1,
+ _debug => $container->debug,
+ _frag => '',
+ _nick => $nick, # Nick of the client on the other end
+ _parent => $container,
+ _socket => $sock, # Socket we're reading from
+ _time => 0, # This gets set by Accept->parse()
+ _type => 'CHAT',
+ };
+
+ bless $self, $class;
+
+ $sock = Net::IRC::DCC::Accept->new($sock, $self);
+
+ unless (defined $sock) {
+ carp "Error in DCC CHAT connect: $!";
+ return;
+ }
+
+ } else { # we're connecting
+
+ $address = &Net::IRC::DCC::Connection::fixaddr($address);
+ return if $port < 1024 or not defined $address;
+
+ $sock = new IO::Socket::INET( Proto => "tcp",
+ PeerAddr => "$address:$port");
+
+ if (defined $sock) {
+ $container->handler(Net::IRC::Event->new('dcc_open',
+ $nick,
+ $sock,
+ 'chat',
+ 'chat', $sock));
+ } else {
+ carp "Error in DCC CHAT connect: $!";
+ return;
+ }
+
+ $sock->autoflush(1);
+
+ $self = {
+ _bin => 0, # Bytes we've recieved thus far
+ _bout => 0, # Bytes we've sent
+ _connected => 1,
+ _nick => $nick, # Nick of the client on the other end
+ _parent => $container,
+ _socket => $sock, # Socket we're reading from
+ _time => time,
+ _type => 'CHAT',
+ };
+
+ bless $self, $class;
+
+ $self->{_parent}->parent->addfh($self->socket,
+ $self->can('parse'), 'r', $self);
+ }
+
+ return $self;
+}
+
+# -- #perl was here! --
+# \merlyn: tahtd be coole
+# KTurner bought the camel today, so somebody can afford one
+# more drink... ;)
+# tmtowtdi: I've heard of things like this...
+# \merlyn: as an experience. that is.
+# archon: i can think of cooler things (;
+# \merlyn: I don't realiy have that mch in my wallet.
+
+sub parse {
+ my ($self, $sock) = @_;
+
+ foreach my $line ($self->_getline($sock)) {
+ return unless defined $line;
+
+ $self->{_bin} += length($line);
+
+ return undef if $line eq "\012";
+ $self->{_bout} += length($line);
+
+ $self->{_parent}->handler(Net::IRC::Event->new('chat',
+ $self->{_nick},
+ $self->{_socket},
+ 'chat',
+ $line));
+
+ $self->{_parent}->handler(Net::IRC::Event->new('dcc_update',
+ $self->{_nick},
+ $self,
+ $self->{_type},
+ $self ));
+ }
+}
+
+# Sends a message to a channel or person.
+# Takes 2 args: the target of the message (channel or nick)
+# the text of the message to send
+sub privmsg {
+ my ($self) = shift;
+
+ unless (@_) {
+ croak 'Not enough arguments to privmsg()';
+ }
+
+ # Don't send a CR over DCC CHAT -- it's not wanted.
+ $self->socket->send(join('', @_) . "\012");
+}
+
+
+# -- #perl was here! --
+# \merlyn: this girl carly at the bar is aBABE
+# archon: are you sure? you don't sound like you're in a condition to
+# judge such things (;
+# *** Stupid has set the topic on channel #perl to \merlyn is shit-faced
+# with a trucker in the shower.
+# tmtowtdi: uh, yeah...
+# \merlyn: good topic
+
+
+# Sockets waiting for accept() use this to shoehorn into the select loop.
+package Net::IRC::DCC::Accept;
+
+@Net::IRC::DCC::Accept::ISA = qw(Net::IRC::DCC::Connection);
+use Carp;
+use Socket; # we use a lot of Socket functions in parse()
+use strict;
+
+
+sub new {
+ my ($class, $sock, $parent) = @_;
+ my ($self);
+
+ $self = { _debug => $parent->debug,
+ _nonblock => 1,
+ _socket => $sock,
+ _parent => $parent,
+ _type => 'accept',
+ };
+
+ bless $self, $class;
+
+ # Tkil's gonna love this one. :-) But what the hell... it's safe to
+ # assume that the only thing initiating DCCs will be Connections, right?
+ # Boy, we're not built for extensibility, I guess. Someday, I'll clean
+ # all of the things like this up.
+ $self->{_parent}->{_parent}->parent->addconn($self);
+ return $self;
+}
+
+sub parse {
+ my ($self) = shift;
+ my ($sock);
+
+ $sock = $self->{_socket}->accept;
+ $self->{_parent}->{_socket} = $sock;
+ $self->{_parent}->{_time} = time;
+
+ if ($self->{_parent}->{_type} eq 'SEND') {
+ # ok, to get the ball rolling, we send them the first packet.
+ my $buf;
+ unless (defined $self->{_parent}->{_fh}->
+ read($buf, $self->{_parent}->{_blocksize})) {
+ return;
+ }
+ unless (defined $sock->send($buf)) {
+ $sock->close;
+ $self->{_parent}->{_fh}->close;
+ $self->{_parent}->{_parent}->parent->removefh($sock);
+ $self->{_parent}->handler(Net::IRC::Event->new('dcc_close',
+ $self->{_nick},
+ $self->{_socket},
+ $self->{_type}));
+ $self->{_socket}->close;
+ return;
+ }
+ }
+
+ $self->{_parent}->{_parent}->parent->addconn($self->{_parent});
+ $self->{_parent}->{_parent}->parent->removeconn($self);
+
+ $self->{_parent}->{_parent}->handler(Net::IRC::Event->
+ new('dcc_open',
+ $self->{_parent}->{_nick},
+ $self->{_parent}->{_socket},
+ $self->{_parent}->{_type},
+ $self->{_parent}->{_type},
+ $self->{_parent}->{_socket})
+ );
+}
+
+
+
+1;
+
+
+__END__
+
+=head1 NAME
+
+Net::IRC::DCC - Object-oriented interface to a single DCC connection
+
+=head1 SYNOPSIS
+
+Hard hat area: This section under construction.
+
+=head1 DESCRIPTION
+
+This documentation is a subset of the main Net::IRC documentation. If
+you haven't already, please "perldoc Net::IRC" before continuing.
+
+Net::IRC::DCC defines a few subclasses that handle DCC CHAT, GET, and SEND
+requests for inter-client communication. DCC objects are created by
+C<Connection-E<gt>new_{chat,get,send}()> in much the same way that
+C<IRC-E<gt>newconn()> creates a new connection object.
+
+=head1 METHOD DESCRIPTIONS
+
+This section is under construction, but hopefully will be finally written up
+by the next release. Please see the C<irctest> script and the source for
+details about this module.
+
+=head1 AUTHORS
+
+Conceived and initially developed by Greg Bacon E<lt>gbacon@adtran.comE<gt> and
+Dennis Taylor E<lt>dennis@funkplanet.comE<gt>.
+
+Ideas and large amounts of code donated by Nat "King" Torkington E<lt>gnat@frii.comE<gt>.
+
+Currently being hacked on, hacked up, and worked over by the members of the
+Net::IRC developers mailing list. For details, see
+http://www.execpc.com/~corbeau/irc/list.html .
+
+=head1 URL
+
+Up-to-date source and information about the Net::IRC project can be found at
+http://netirc.betterbox.net/ .
+
+=head1 SEE ALSO
+
+=over
+
+=item *
+
+perl(1).
+
+=item *
+
+RFC 1459: The Internet Relay Chat Protocol
+
+=item *
+
+http://www.irchelp.org/, home of fine IRC resources.
+
+=back
+
+=cut