ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/Net-IRC-Server/Net/IRC/Server.pm
Revision: 1.4
Committed: Fri Jan 14 14:41:43 2005 UTC (21 years, 8 months ago) by elmex
Branch: MAIN
Changes since 1.3: +18 -0 lines
Log Message:
*** empty log message ***

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     =item set_send_cb ($sub)
41    
42     The code-ref in the C<$sub> will be called everythime the server wants to send data to a client.
43     The callback will be called with following arguments:
44    
45     $sub->($client, $data)
46    
47     $client - the client datastructure you give C<parse_irc_stream ()>
48     $data - the raw data to send out, for example on a socket
49    
50     =cut
51     sub set_send_cb {
52     my ($self, $cb) = @_;
53     $self->{send_cb} = $cb;
54     }
55    
56     sub set_cmd_cb {
57     my ($self, $cmd, $cb) = @_;
58     $self->{cmd_cbs}->{uc $cmd} = $cb;
59     }
60    
61 elmex 1.4 sub set_post_cmd_cb {
62     my ($self, $cmd, $cb) = @_;
63     $self->{post_cmd_cbs}->{uc $cmd} = $cb;
64     }
65    
66 elmex 1.2 =item send_srv_msg ($client, @msg)
67    
68     Sends a irc-message to C<$client> with the server-prefix.
69     @msg should look like: (<command>, <trailing>, <param-1>, ..., <param-n>)
70    
71     =cut
72     sub send_srv_msg {
73     my ($self, $client, @msg) = @_;
74     $self->send_msg ($client, $self->{srv_prefix}, @msg);
75     }
76    
77     =item send_msg ($client, @msg)
78    
79     Sends a irc-message to C<$client>.
80     @msg should look like: (<prefix>, <command>, <trailing>, <param-1>, ..., <param-n>)
81    
82     =cut
83 elmex 1.1 sub send_msg {
84     my ($self, $client, @msg) = @_;
85     $self->{send_cb}->($client, $self->mk_msg (@msg));
86     }
87    
88 elmex 1.2
89     =item send_nameslist ($client, $channel)
90    
91     This sends the names-list of C<$channel> to the C<$client>.
92    
93     =cut
94     sub send_nameslist {
95     my ($self, $client, $channel) = @_;
96    
97     # TODO: Modes for users! Look here too:
98     my @names = map { $_->{nickname} } values %{$self->{channels}->{lc $channel}};
99    
100     # TODO: Look for channel modes!
101    
102     my $i = 0;
103     my @part;
104    
105     while (@names) {
106     $i++;
107     push @part, shift @names;
108    
109     if ($i % 10 == 0) {
110     $self->send_srv_msg ($client, "353", join (' ', @part), "=", $channel);
111     @part = ();
112     }
113     }
114    
115     if (@part) {
116     $self->send_srv_msg ($client, "353", join (' ', @part), $client->{nickname}, "=", $channel);
117     }
118    
119     $self->send_srv_msg ($client, "366", "End of NAMES list", $client->{nickname}, $channel);
120     }
121    
122 elmex 1.1 sub mk_msg {
123     my ($self, $prefix, $command, $trail, @params) = @_;
124     my $msg = "";
125    
126     $msg .= defined $prefix ? ":$prefix " : "";
127     $msg .= "$command";
128    
129     map { $msg .= " $_" } @params;
130    
131     $msg .= defined $trail ? " :$trail" : "";
132     $msg .= "\r\n";
133    
134     return $msg;
135     }
136    
137     sub mk_clpref {
138     my ($self, $client) = @_;
139     return $client->{nickname}."!".$client->{username}.'@'.$client->{hostname};
140     }
141    
142     sub parse_irc_stream {
143     my ($self, $client, $data) = @_;
144    
145     $self->{buffer} .= $data;
146    
147     while ($self->{buffer} =~ s/^([^\r\n]*)\r?\n//) {
148     push @{$self->{msgs}}, $1;
149     }
150    
151     map { $self->handle_irc_msg ($client, $self->parse_irc_msg ($_)) } @{$self->{msgs}};
152    
153     $self->{msgs} = [];
154     }
155    
156     sub handle_irc_msg {
157     my ($self, $cl, $msg) = @_;
158    
159     return if not defined $msg;
160    
161 elmex 1.2 my $c = uc $msg->{command};
162 elmex 1.1
163     my $r;
164 elmex 1.2 $r ||= $self->{cmd_cbs}->{$c}->($cl, $msg) if defined $self->{cmd_cbs}->{$c};
165 elmex 1.1 $r ||= $self->{cmd_cbs}->{"*"}->($cl, $msg) if defined $self->{cmd_cbs}->{"*"};
166    
167     if ($r) {
168     warn "notihgn to do skip srv code\n";
169     return
170     }
171    
172     if ($c eq "USER") {
173     $cl->{username} = $msg->{params}->[0];
174     $cl->{realname} = $msg->{params}->[3];
175    
176     } elsif ($c eq "NICK") {
177     my $n = $msg->{params}->[0];
178     $self->change_nick ($n, $cl);
179     }
180    
181     if (defined $cl->{username}
182     and defined $cl->{nickname}
183     and not $cl->{registered})
184     {
185     $self->send_msg ($cl, $self->{srv_prefix}, "001",
186     "Welcome to NET::IRCServer! "
187     . $self->mk_clpref ($cl), $cl->{nickname});
188     $cl->{registered} = 1;
189     }
190    
191     if ($c eq "PASS") {
192     $cl->{password} = $msg->{params}->[0];
193    
194     } elsif ($c eq "JOIN") {
195     my @chnls = split /,/, $msg->{params}->[0];
196     my @keys;
197     @keys = split /,/, $msg->{params}->[1] if defined $msg->{params}->[1];
198    
199     $self->join_channel ($cl, $_, pop @keys) for @chnls;
200    
201     } elsif ($c eq "PART") {
202     my @chnls = split /,/, $msg->{params}->[0];
203     $self->part_channel ($cl, $_, $msg->{params}->[1]) for @chnls;
204    
205     } elsif ($c eq "NOTICE" or $c eq "PRIVMSG") {
206     $self->generic_msg ($cl, $msg->{params}->[0], $c, $msg->{params}->[1]);
207 elmex 1.2
208     } elsif ($c eq "QUIT") {
209     $self->quit ($cl, $msg->{params}->[0]);
210    
211     } elsif ($c eq "NAMES") {
212    
213     if (defined $msg->{params}->[1]) {
214     $self->send_srv_msg ($cl,"402", "No such server", $msg->{params}->[1]);
215    
216     } elsif (defined $msg->{params}->[0]) {
217     my @chans = split /,/, $msg->{params}->[0];
218     $self->send_nameslist ($cl, $_) for @chans;
219    
220     } else {
221     $self->send_nameslist ($cl, $_) for keys %{$self->{channels}};
222     }
223    
224     } elsif ($c eq "PING") {
225    
226     if (defined $msg->{params}->[1]) {
227     $self->send_srv_msg ($cl,"402", "No such server", $msg->{params}->[1]);
228     } else {
229     $self->send_srv_msg ($cl, "PONG", $msg->{params}->[0]);
230     }
231    
232     } else {
233     $self->send_srv_msg ($cl, "421", "Unknown command", $c)
234     if $c ne "USER" and $c ne "NICK"; # handled above
235 elmex 1.1 }
236    
237 elmex 1.4 $self->{post_cmd_cbs}->{$c}->($cl, $msg) if defined $self->{post_cmd_cbs}->{$c};
238     $self->{post_cmd_cbs}->{"*"}->($cl, $msg) if defined $self->{post_cmd_cbs}->{"*"};
239 elmex 1.1 }
240    
241 elmex 1.2 =item quit ($client, $quitmsg)
242    
243     It will remove the C<$client> from all lists in the server, and broadcasts the
244     C<$quitmsg> to all users on the channels the C<$client> was.
245    
246     =cut
247     sub quit {
248     my ($self, $client, $quitmsg) = @_;
249    
250     $self->channel_brdcst (
251     $self->mk_clpref ($client),
252     $_,
253     "QUIT",
254     $quitmsg) for keys %{$client->{channels}};
255    
256     $self->remove_channel ($client, $_) for keys %{$client->{channels}};
257     }
258    
259 elmex 1.1 =item part_channel ($client, $channel, $reason)
260    
261 elmex 1.3 It will remove the C<$client> from the C<$channel> and will broadcast a PART to all members of that channel.
262 elmex 1.1 The part C<$reason> may be undefined.
263    
264     =cut
265     sub part_channel {
266     my ($self, $client, $chan, $reas) = @_;
267    
268     $self->channel_brdcst ($self->mk_clpref ($client), $chan, "PART", $reas, $chan);
269     $self->remove_channel ($client, $chan);
270     }
271    
272     =item join_channel ($client, $channel, $key)
273    
274 elmex 1.3 It will add the C<$client> to the C<$channel> and will broadcast a JOIN to all members of that channel.
275    
276 elmex 1.1 =cut
277     sub join_channel {
278     my ($self, $client, $chan, $key) = @_;
279    
280     if ($chan eq "0") {
281     # part all channels of $client
282     $self->part_channel ($client, $_) for keys %{$client->{channels}};
283     $self->remove_channel ($client, $_) for keys %{$client->{channels}};
284    
285     } else {
286     $self->add_channel ($client, $chan);
287     $self->channel_brdcst ($self->mk_clpref ($client), $chan, "JOIN", $chan);
288 elmex 1.2 $self->send_nameslist ($client, $chan);
289 elmex 1.1 }
290     }
291    
292 elmex 1.4 =item get_user_list ($channel)
293    
294     Get's the userlist of the $channel in the form:
295     { nickname => client-structure, ... }
296    
297     =cut
298     sub get_user_list {
299     my ($self, $channel) = @_;
300     return $self->{channels}->{lc $channel};
301     }
302    
303 elmex 1.1 sub add_channel {
304     my ($self, $client, $channel) = @_;
305     $self->{channels}->{lc $channel}->{lc $client->{nickname}} = $client;
306     $client->{channels}->{lc $channel} = 1;
307     }
308    
309     sub remove_channel {
310     my ($self, $client, $channel) = @_;
311     delete $self->{channels}->{lc $channel}->{lc $client->{nickname}};
312     delete $client->{channels}->{lc $channel};
313     }
314    
315     sub channel_brdcst {
316     my ($self, $prefix, $channel, $command, @rmsg) = @_;
317     $self->channel_brdcst_exclude (undef, $prefix, $channel, $command, @rmsg);
318     }
319    
320     sub channel_brdcst_exclude {
321     my ($self, $excl_cl, $prefix, $channel, $command, @rmsg) = @_;
322    
323     for (values %{$self->{channels}->{lc $channel}}) {
324 elmex 1.2
325 elmex 1.1 if (not (defined $excl_cl) or $excl_cl != $_) {
326 elmex 1.2
327 elmex 1.1 $self->send_msg ($_, $prefix, uc $command, @rmsg);
328     }
329     }
330     }
331    
332    
333     sub generic_msg {
334     my ($self, $client, $target, $msgcmd, $msg) = @_;
335    
336     my $pref = $self->mk_clpref ($client);
337    
338     if (exists $self->{channels}->{lc $target}) {
339     $self->channel_brdcst_exclude ($client, $pref, $target, uc ($msgcmd), $msg, $target);
340     } else {
341     $self->send_msg ($self->{reg_nicks}->{lc $target}, $pref, uc ($msgcmd), $msg, $target);
342     }
343     }
344    
345     sub check_nick_exists {
346     my ($self, $nick) = @_;
347     return 0;
348     }
349    
350     sub change_nick {
351     my ($self, $nick, $client) = @_;
352    
353     # check wether the nick to change to is already in use
354     if (exists $self->{reg_nicks}->{lc $nick}) {
355     $self->send_msg ($client, $self->{srv_prefix}, 433, "Nickname is already in use", $nick);
356     return;
357     }
358    
359     my %ppltotell; # hash-list of people to send a nick-update
360    
361     if (defined $client->{nickname}) {
362    
363     # update channels the client is on
364     for my $c (keys %{$client->{channels}}) {
365    
366     # search the people to tell this nick changed
367     for (values %{$self->{channels}->{lc $c}}) {
368     $ppltotell{$_->{nickname}} = $_;
369     }
370    
371     # change the nickname in the channel lists
372     delete $self->{channels}->{lc $c}->{$client->{nickname}};
373     $self->{channels}->{lc $c}->{$nick} = $client;
374     }
375     }
376    
377     if ($client->{registered}) { # only tell if this client is registered
378    
379     # send us and others that the nick changed
380     $ppltotell{$client->{nickname}} = $client;
381     my $oldprfx = $self->mk_clpref ($client);
382     $self->send_msg ($_, $oldprfx, "NICK", undef, $nick) for values %ppltotell;
383     }
384    
385     # now update the client data
386     delete $self->{reg_nicks}->{lc $client->{nickname}};
387     $client->{nickname} = $nick;
388     $self->{reg_nicks}->{$nick} = $client;
389    
390     return 1;
391     }
392    
393     sub parse_irc_msg {
394     my ($self, $msg) = @_;
395    
396 elmex 1.2 my $cmd;
397     my $pref;
398     my $t;
399     my @a;
400    
401     my $p = $msg =~ s/^(:([^ ]+)[ ])?([A-Za-z]+|\d{3})//;
402     $pref = $2;
403     $cmd = $3;
404 elmex 1.1
405 elmex 1.2 my $i = 0;
406    
407     while ($msg =~ s/^[ ]([^ :\r\n\0][^ \r\n\0]*)//) {
408 elmex 1.1
409 elmex 1.2 push @a, $1 if defined $1;
410     if (++$i > 13) { last; }
411     }
412    
413     if ($i == 14) {
414    
415     if ($msg =~ s/^[ ]:?([^\r\n\0]*)//) {
416     $t = $1 if $1 ne "";
417     }
418    
419     } else {
420    
421     if ($msg =~ s/^[ ]:([^\r\n\0]*)//) {
422     $t = $1 if $1 ne "";
423     }
424     }
425    
426     push @a, $t if defined $t;
427 elmex 1.1
428     my $m = { prefix => $pref, command => $cmd, params => \@a, trailing => $t };
429     return $p ? $m : undef;
430     }
431    
432     =back
433    
434     =cut
435    
436     1;