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

# Content
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 =item set_send_cb ($sub, [$command])
41
42 The code-ref in the C<$sub> will be called everythime the server wants to send data to a client.
43
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 The callback will be called with following arguments:
49
50 $sub->($client, $data, $prefix, $command, $trailing, @params)
51
52 $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
59 =cut
60 sub set_send_cb {
61 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 }
70
71 =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 sub set_cmd_cb {
102 my ($self, $cmd, $cb) = @_;
103 $self->{cmd_cbs}->{uc $cmd} = $cb;
104 }
105
106 sub set_post_cmd_cb {
107 my ($self, $cmd, $cb) = @_;
108 $self->{post_cmd_cbs}->{uc $cmd} = $cb;
109 }
110
111 =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 sub send_msg {
129 my ($self, $client, @msg) = @_;
130 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 }
138
139
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 $self->send_srv_msg ($client, "353", join (' ', @part), $client->{nickname}, "=", $channel);
162 @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 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 =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 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 my $c = uc $msg->{command};
236
237 my $r;
238 $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
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
266 $self->{cmd_cbs}->{"!"}->($cl, $msg)
267 if defined $self->{cmd_cbs}->{"!"};
268 }
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
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 }
315
316 $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 }
319
320 =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 =item part_channel ($client, $channel, $reason)
339
340 It will remove the C<$client> from the C<$channel> and will broadcast a PART to all members of that channel.
341 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 It will add the C<$client> to the C<$channel> and will broadcast a JOIN to all members of that channel.
354
355 =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 $self->send_nameslist ($client, $chan);
368 }
369 }
370
371 =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 =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 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 =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 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
450 if (not (defined $excl_cl) or $excl_cl != $_) {
451
452 $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 $self->send_msg ($self->get_nick ($target), $pref, uc ($msgcmd), $msg, $target);
467 }
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 if ($self->get_nick ($nick)) {
476 $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 $self->{channels}->{lc $c}->{lc $nick} = $client;
495 }
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 $self->unregister_nick ($client->{nickname});
508
509 $client->{nickname} = $nick;
510 $self->register_nick ($client, $client->{nickname});
511
512 return 1;
513 }
514
515 sub parse_irc_msg {
516 my ($self, $msg) = @_;
517
518 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
527 my $i = 0;
528
529 while ($msg =~ s/^[ ]([^ :\r\n\0][^ \r\n\0]*)//) {
530
531 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
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;