ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/Net-IRC-Server/Net/IRC/Server.pm
Revision: 1.5
Committed: Fri Jan 14 17:01:54 2005 UTC (21 years, 8 months ago) by elmex
Branch: MAIN
CVS Tags: HEAD
Changes since 1.4: +142 -20 lines
Log Message:
documentations, minor improvements

File Contents

# User Rev Content
1 elmex 1.1 =head1 NET::IRC::Server
2    
3     NET::IRCServer - a module to handle the IRC Protocol and implements a lightweight IRC Server (without server-to-server support)
4    
5     =head1 SYNOPSIS
6    
7     use NET::IRC::Server;
8    
9     $server = NET::IRC::Server->new (srv_prefix => "mein.irc.server.funtzt.net");
10    
11     $s->set_send_cb (sub { ... });
12    
13     $s->parse_irc_stream (...)
14    
15     =cut
16    
17     package Net::IRC::Server;
18     use Data::Dumper;
19     use strict;
20     use warnings;
21    
22     =head1 METHODS
23    
24     =over 4
25    
26     =cut
27    
28     =item new (srv_prefix => $server_prefix)
29    
30     srv_prefix - the prefix the server will use for irc commands from itself
31    
32     =cut
33     sub new {
34     my $class = shift;
35     my $self = { @_ };
36     bless $self, $class;
37     return $self;
38     }
39    
40 elmex 1.5 =item set_send_cb ($sub, [$command])
41 elmex 1.1
42     The code-ref in the C<$sub> will be called everythime the server wants to send data to a client.
43 elmex 1.5
44     If the C<$command> argument is given, the callback will only be called for irc-commands that match
45     C<$command>. If the callback returns a true value when it's matched to a C<$command> the alternative callback
46     (given without the C<$command>) won't be called.
47    
48 elmex 1.1 The callback will be called with following arguments:
49    
50 elmex 1.5 $sub->($client, $data, $prefix, $command, $trailing, @params)
51 elmex 1.1
52 elmex 1.5 $client - the client datastructure you give C<parse_irc_stream ()>
53     $data - the raw data to send out, for example on a socket
54     $prefix - irc message prefix (may be undefined)
55     $command - irc command
56     $trailing - the trailing irc parameter
57     @params - the other params or the irc message
58 elmex 1.1
59     =cut
60     sub set_send_cb {
61 elmex 1.5 my ($self, $cb, $cmd) = @_;
62    
63     if (defined $cmd) {
64     $self->{send_cb_cmd}->{uc $cmd} = $cb;
65    
66     } else {
67     $self->{send_cb} = $cb;
68     }
69 elmex 1.1 }
70    
71 elmex 1.5 =item set_cmd_cb ($cmd, $cb)
72    
73     Sets the command callback for the irc-command C<$cmd>.
74     The callback is executed B<before> the command is evaluated
75     by the server. If you want to prevent evaluation by the server
76     later, you have to return a true value. Otherwise the server
77     will evaluate the command and sends responses.
78    
79     There is are following special events/commands you can register
80     a callback for, set C<$cmd> to:
81    
82     '*' - to receive all commands
83     '!' - client successfully registered (after he send
84     PASS, NICK and USER and all commands were successful,
85     NOTE: the returnvalue of this callback will be ignored)
86    
87     Arguments of the callback are like:
88    
89     $cb->($client, $ircmsg);
90    
91     $client - the client datastructure for this command
92     $ircmsg - the irc-message hash containing:
93     {
94     prefix => <irc-prefix>,
95     command => <irc-command>,
96     params => <irc-params including trailing>,
97     trailing => <the trailing parameter, also included in params>
98     }
99    
100     =cut
101 elmex 1.1 sub set_cmd_cb {
102     my ($self, $cmd, $cb) = @_;
103     $self->{cmd_cbs}->{uc $cmd} = $cb;
104     }
105    
106 elmex 1.4 sub set_post_cmd_cb {
107     my ($self, $cmd, $cb) = @_;
108     $self->{post_cmd_cbs}->{uc $cmd} = $cb;
109     }
110    
111 elmex 1.2 =item send_srv_msg ($client, @msg)
112    
113     Sends a irc-message to C<$client> with the server-prefix.
114     @msg should look like: (<command>, <trailing>, <param-1>, ..., <param-n>)
115    
116     =cut
117     sub send_srv_msg {
118     my ($self, $client, @msg) = @_;
119     $self->send_msg ($client, $self->{srv_prefix}, @msg);
120     }
121    
122     =item send_msg ($client, @msg)
123    
124     Sends a irc-message to C<$client>.
125     @msg should look like: (<prefix>, <command>, <trailing>, <param-1>, ..., <param-n>)
126    
127     =cut
128 elmex 1.1 sub send_msg {
129     my ($self, $client, @msg) = @_;
130 elmex 1.5 my $r;
131    
132     $r = $self->{send_cb_cmd}->{uc $msg[1]}->($client, $self->mk_msg (@msg), @msg)
133     if defined $self->{send_cb_cmd}->{uc $msg[1]};
134    
135     $self->{send_cb}->($client, $self->mk_msg (@msg), @msg)
136     if defined $self->{send_cb} and not $r;
137 elmex 1.1 }
138    
139 elmex 1.2
140     =item send_nameslist ($client, $channel)
141    
142     This sends the names-list of C<$channel> to the C<$client>.
143    
144     =cut
145     sub send_nameslist {
146     my ($self, $client, $channel) = @_;
147    
148     # TODO: Modes for users! Look here too:
149     my @names = map { $_->{nickname} } values %{$self->{channels}->{lc $channel}};
150    
151     # TODO: Look for channel modes!
152    
153     my $i = 0;
154     my @part;
155    
156     while (@names) {
157     $i++;
158     push @part, shift @names;
159    
160     if ($i % 10 == 0) {
161 elmex 1.5 $self->send_srv_msg ($client, "353", join (' ', @part), $client->{nickname}, "=", $channel);
162 elmex 1.2 @part = ();
163     }
164     }
165    
166     if (@part) {
167     $self->send_srv_msg ($client, "353", join (' ', @part), $client->{nickname}, "=", $channel);
168     }
169    
170     $self->send_srv_msg ($client, "366", "End of NAMES list", $client->{nickname}, $channel);
171     }
172    
173 elmex 1.1 sub mk_msg {
174     my ($self, $prefix, $command, $trail, @params) = @_;
175     my $msg = "";
176    
177     $msg .= defined $prefix ? ":$prefix " : "";
178     $msg .= "$command";
179    
180     map { $msg .= " $_" } @params;
181    
182     $msg .= defined $trail ? " :$trail" : "";
183     $msg .= "\r\n";
184    
185     return $msg;
186     }
187    
188     sub mk_clpref {
189     my ($self, $client) = @_;
190     return $client->{nickname}."!".$client->{username}.'@'.$client->{hostname};
191     }
192    
193 elmex 1.5 =item feed_irc_data ($client, $data)
194    
195     Feeds raw IRC C<$data> for processing. When enough data for a command was
196     feeded the command is handled and the callbacks set with C<set_cmd_cb> and C<set_post_cmd_cb>
197     will be called. When the server wants to send a response he calls the callback set with C<set_send_cb>.
198    
199     The C<$client> data structure has to contain the C<hostname>-key, so the server can generate the irc
200     message prefix for that client in his replys.
201    
202     In the callbacks the C<$client> data structure will be filled with followin keys:
203    
204     $client = {
205     registered => <a flag (may be undef) whether the client finished the
206     registration process and has a valid nickname and username>,
207     nickname => <the nickname the client currently has>,
208     username => <the username the client send when he logged in>,
209     password => <the password the client send when logged in>,
210     hostname => <this is what you have to provide>,
211     channels => { <channel-name> => 1, <channel2-name> => 1, ..., <channeln-name> }
212     (a hash of channel names the client is currently joined )
213     };
214    
215     =cut
216     sub feed_irc_data {
217 elmex 1.1 my ($self, $client, $data) = @_;
218    
219     $self->{buffer} .= $data;
220    
221     while ($self->{buffer} =~ s/^([^\r\n]*)\r?\n//) {
222     push @{$self->{msgs}}, $1;
223     }
224    
225     map { $self->handle_irc_msg ($client, $self->parse_irc_msg ($_)) } @{$self->{msgs}};
226    
227     $self->{msgs} = [];
228     }
229    
230     sub handle_irc_msg {
231     my ($self, $cl, $msg) = @_;
232    
233     return if not defined $msg;
234    
235 elmex 1.2 my $c = uc $msg->{command};
236 elmex 1.1
237     my $r;
238 elmex 1.5 $r ||= $self->{cmd_cbs}->{$c}->($cl, $msg)
239     if defined $self->{cmd_cbs}->{$c};
240     $r ||= $self->{cmd_cbs}->{"*"}->($cl, $msg)
241     if defined $self->{cmd_cbs}->{"*"};
242 elmex 1.1
243     if ($r) {
244     warn "notihgn to do skip srv code\n";
245     return
246     }
247    
248     if ($c eq "USER") {
249     $cl->{username} = $msg->{params}->[0];
250     $cl->{realname} = $msg->{params}->[3];
251    
252     } elsif ($c eq "NICK") {
253     my $n = $msg->{params}->[0];
254     $self->change_nick ($n, $cl);
255     }
256    
257     if (defined $cl->{username}
258     and defined $cl->{nickname}
259     and not $cl->{registered})
260     {
261     $self->send_msg ($cl, $self->{srv_prefix}, "001",
262     "Welcome to NET::IRCServer! "
263     . $self->mk_clpref ($cl), $cl->{nickname});
264     $cl->{registered} = 1;
265 elmex 1.5
266     $self->{cmd_cbs}->{"!"}->($cl, $msg)
267     if defined $self->{cmd_cbs}->{"!"};
268 elmex 1.1 }
269    
270     if ($c eq "PASS") {
271     $cl->{password} = $msg->{params}->[0];
272    
273     } elsif ($c eq "JOIN") {
274     my @chnls = split /,/, $msg->{params}->[0];
275     my @keys;
276     @keys = split /,/, $msg->{params}->[1] if defined $msg->{params}->[1];
277    
278     $self->join_channel ($cl, $_, pop @keys) for @chnls;
279    
280     } elsif ($c eq "PART") {
281     my @chnls = split /,/, $msg->{params}->[0];
282     $self->part_channel ($cl, $_, $msg->{params}->[1]) for @chnls;
283    
284     } elsif ($c eq "NOTICE" or $c eq "PRIVMSG") {
285     $self->generic_msg ($cl, $msg->{params}->[0], $c, $msg->{params}->[1]);
286 elmex 1.2
287     } elsif ($c eq "QUIT") {
288     $self->quit ($cl, $msg->{params}->[0]);
289    
290     } elsif ($c eq "NAMES") {
291    
292     if (defined $msg->{params}->[1]) {
293     $self->send_srv_msg ($cl,"402", "No such server", $msg->{params}->[1]);
294    
295     } elsif (defined $msg->{params}->[0]) {
296     my @chans = split /,/, $msg->{params}->[0];
297     $self->send_nameslist ($cl, $_) for @chans;
298    
299     } else {
300     $self->send_nameslist ($cl, $_) for keys %{$self->{channels}};
301     }
302    
303     } elsif ($c eq "PING") {
304    
305     if (defined $msg->{params}->[1]) {
306     $self->send_srv_msg ($cl,"402", "No such server", $msg->{params}->[1]);
307     } else {
308     $self->send_srv_msg ($cl, "PONG", $msg->{params}->[0]);
309     }
310    
311     } else {
312     $self->send_srv_msg ($cl, "421", "Unknown command", $c)
313     if $c ne "USER" and $c ne "NICK"; # handled above
314 elmex 1.1 }
315    
316 elmex 1.4 $self->{post_cmd_cbs}->{$c}->($cl, $msg) if defined $self->{post_cmd_cbs}->{$c};
317     $self->{post_cmd_cbs}->{"*"}->($cl, $msg) if defined $self->{post_cmd_cbs}->{"*"};
318 elmex 1.1 }
319    
320 elmex 1.2 =item quit ($client, $quitmsg)
321    
322     It will remove the C<$client> from all lists in the server, and broadcasts the
323     C<$quitmsg> to all users on the channels the C<$client> was.
324    
325     =cut
326     sub quit {
327     my ($self, $client, $quitmsg) = @_;
328    
329     $self->channel_brdcst (
330     $self->mk_clpref ($client),
331     $_,
332     "QUIT",
333     $quitmsg) for keys %{$client->{channels}};
334    
335     $self->remove_channel ($client, $_) for keys %{$client->{channels}};
336     }
337    
338 elmex 1.1 =item part_channel ($client, $channel, $reason)
339    
340 elmex 1.3 It will remove the C<$client> from the C<$channel> and will broadcast a PART to all members of that channel.
341 elmex 1.1 The part C<$reason> may be undefined.
342    
343     =cut
344     sub part_channel {
345     my ($self, $client, $chan, $reas) = @_;
346    
347     $self->channel_brdcst ($self->mk_clpref ($client), $chan, "PART", $reas, $chan);
348     $self->remove_channel ($client, $chan);
349     }
350    
351     =item join_channel ($client, $channel, $key)
352    
353 elmex 1.3 It will add the C<$client> to the C<$channel> and will broadcast a JOIN to all members of that channel.
354    
355 elmex 1.1 =cut
356     sub join_channel {
357     my ($self, $client, $chan, $key) = @_;
358    
359     if ($chan eq "0") {
360     # part all channels of $client
361     $self->part_channel ($client, $_) for keys %{$client->{channels}};
362     $self->remove_channel ($client, $_) for keys %{$client->{channels}};
363    
364     } else {
365     $self->add_channel ($client, $chan);
366     $self->channel_brdcst ($self->mk_clpref ($client), $chan, "JOIN", $chan);
367 elmex 1.2 $self->send_nameslist ($client, $chan);
368 elmex 1.1 }
369     }
370    
371 elmex 1.4 =item get_user_list ($channel)
372    
373     Get's the userlist of the $channel in the form:
374     { nickname => client-structure, ... }
375    
376     =cut
377     sub get_user_list {
378     my ($self, $channel) = @_;
379     return $self->{channels}->{lc $channel};
380     }
381    
382 elmex 1.5 =item register_nick ($client, $nick)
383    
384     Registers the nickname C<$nick> in the server and sets it's
385     client data structure to C<$client>.
386    
387     =cut
388     sub register_nick {
389     my ($self, $client, $nick) = @_;
390     $self->{reg_nicks}->{lc $nick} = $client;
391     }
392    
393     =item get_nick ($nick)
394    
395     Returns the client datastructure for nickname C<$nick> or
396     undef if there is no such nick;
397    
398     =cut
399     sub get_nick {
400     my ($self, $nick) = @_;
401     $self->{reg_nicks}->{lc $nick};
402     }
403    
404     =item unregister_nick ($nick)
405    
406     Unregisters the nickname C<$nick> in the server.
407    
408     =cut
409     sub unregister_nick {
410     my ($self, $client, $nick) = @_;
411     delete $self->{reg_nicks}->{lc $client->{nickname}};
412     }
413    
414 elmex 1.1 sub add_channel {
415     my ($self, $client, $channel) = @_;
416     $self->{channels}->{lc $channel}->{lc $client->{nickname}} = $client;
417     $client->{channels}->{lc $channel} = 1;
418     }
419    
420     sub remove_channel {
421     my ($self, $client, $channel) = @_;
422     delete $self->{channels}->{lc $channel}->{lc $client->{nickname}};
423     delete $client->{channels}->{lc $channel};
424     }
425    
426 elmex 1.5 =item channel_exists ($channel)
427    
428     Return true if there are clients who are joined to that channel.
429    
430     =cut
431     sub channel_exists {
432     my ($self, $channel) = @_;
433    
434     return undef
435     if not defined $self->{channels}->{lc $channel};
436     return
437     (scalar (keys %{$self->{channels}->{lc $channel}}) != 0);
438     }
439    
440 elmex 1.1 sub channel_brdcst {
441     my ($self, $prefix, $channel, $command, @rmsg) = @_;
442     $self->channel_brdcst_exclude (undef, $prefix, $channel, $command, @rmsg);
443     }
444    
445     sub channel_brdcst_exclude {
446     my ($self, $excl_cl, $prefix, $channel, $command, @rmsg) = @_;
447    
448     for (values %{$self->{channels}->{lc $channel}}) {
449 elmex 1.2
450 elmex 1.1 if (not (defined $excl_cl) or $excl_cl != $_) {
451 elmex 1.2
452 elmex 1.1 $self->send_msg ($_, $prefix, uc $command, @rmsg);
453     }
454     }
455     }
456    
457    
458     sub generic_msg {
459     my ($self, $client, $target, $msgcmd, $msg) = @_;
460    
461     my $pref = $self->mk_clpref ($client);
462    
463     if (exists $self->{channels}->{lc $target}) {
464     $self->channel_brdcst_exclude ($client, $pref, $target, uc ($msgcmd), $msg, $target);
465     } else {
466 elmex 1.5 $self->send_msg ($self->get_nick ($target), $pref, uc ($msgcmd), $msg, $target);
467 elmex 1.1 }
468     }
469    
470    
471     sub change_nick {
472     my ($self, $nick, $client) = @_;
473    
474     # check wether the nick to change to is already in use
475 elmex 1.5 if ($self->get_nick ($nick)) {
476 elmex 1.1 $self->send_msg ($client, $self->{srv_prefix}, 433, "Nickname is already in use", $nick);
477     return;
478     }
479    
480     my %ppltotell; # hash-list of people to send a nick-update
481    
482     if (defined $client->{nickname}) {
483    
484     # update channels the client is on
485     for my $c (keys %{$client->{channels}}) {
486    
487     # search the people to tell this nick changed
488     for (values %{$self->{channels}->{lc $c}}) {
489     $ppltotell{$_->{nickname}} = $_;
490     }
491    
492     # change the nickname in the channel lists
493     delete $self->{channels}->{lc $c}->{$client->{nickname}};
494 elmex 1.5 $self->{channels}->{lc $c}->{lc $nick} = $client;
495 elmex 1.1 }
496     }
497    
498     if ($client->{registered}) { # only tell if this client is registered
499    
500     # send us and others that the nick changed
501     $ppltotell{$client->{nickname}} = $client;
502     my $oldprfx = $self->mk_clpref ($client);
503     $self->send_msg ($_, $oldprfx, "NICK", undef, $nick) for values %ppltotell;
504     }
505    
506     # now update the client data
507 elmex 1.5 $self->unregister_nick ($client->{nickname});
508    
509 elmex 1.1 $client->{nickname} = $nick;
510 elmex 1.5 $self->register_nick ($client, $client->{nickname});
511 elmex 1.1
512     return 1;
513     }
514    
515     sub parse_irc_msg {
516     my ($self, $msg) = @_;
517    
518 elmex 1.2 my $cmd;
519     my $pref;
520     my $t;
521     my @a;
522    
523     my $p = $msg =~ s/^(:([^ ]+)[ ])?([A-Za-z]+|\d{3})//;
524     $pref = $2;
525     $cmd = $3;
526 elmex 1.1
527 elmex 1.2 my $i = 0;
528    
529     while ($msg =~ s/^[ ]([^ :\r\n\0][^ \r\n\0]*)//) {
530 elmex 1.1
531 elmex 1.2 push @a, $1 if defined $1;
532     if (++$i > 13) { last; }
533     }
534    
535     if ($i == 14) {
536    
537     if ($msg =~ s/^[ ]:?([^\r\n\0]*)//) {
538     $t = $1 if $1 ne "";
539     }
540    
541     } else {
542    
543     if ($msg =~ s/^[ ]:([^\r\n\0]*)//) {
544     $t = $1 if $1 ne "";
545     }
546     }
547    
548     push @a, $t if defined $t;
549 elmex 1.1
550     my $m = { prefix => $pref, command => $cmd, params => \@a, trailing => $t };
551     return $p ? $m : undef;
552     }
553    
554     =back
555    
556     =cut
557    
558     1;