File Coverage

blib/lib/POE/Component/IRC/Plugin/MultiProxy.pm
Criterion Covered Total %
statement 47 130 36.1
branch 0 30 0.0
condition 0 5 0.0
subroutine 17 26 65.3
pod 1 3 33.3
total 65 194 33.5


line stmt bran cond sub pod time code
1             package POE::Component::IRC::Plugin::MultiProxy;
2             BEGIN {
3 1     1   1503 $POE::Component::IRC::Plugin::MultiProxy::AUTHORITY = 'cpan:HINRIK';
4             }
5             BEGIN {
6 1     1   17 $POE::Component::IRC::Plugin::MultiProxy::VERSION = '0.01';
7             }
8              
9 1     1   9 use strict;
  1         2  
  1         34  
10 1     1   7 use warnings FATAL => 'all';
  1         1  
  1         39  
11 1     1   6 use Carp qw(croak);
  1         2  
  1         81  
12 1     1   7 use Digest::MD5 qw(md5_hex);
  1         2  
  1         72  
13 1     1   6 use POE;
  1         2  
  1         10  
14 1     1   405 use POE::Component::IRC::Plugin::MultiProxy::Away;
  1         3  
  1         39  
15 1     1   7 use POE::Component::IRC::Plugin::MultiProxy::ClientManager;
  1         1  
  1         34  
16 1     1   5 use POE::Component::IRC::Plugin::MultiProxy::Recall;
  1         2  
  1         23  
17 1     1   14 use POE::Component::IRC::Plugin::MultiProxy::State;
  1         2  
  1         23  
18 1     1   6 use POE::Filter::IRCD;
  1         2  
  1         32  
19 1     1   12 use POE::Filter::Line;
  1         2  
  1         20  
20 1     1   5 use POE::Filter::Stackable;
  1         1  
  1         22  
21 1     1   1138 use POE::Wheel::ReadWrite;
  1         13077  
  1         41  
22 1     1   1379 use POE::Wheel::SocketFactory;
  1         7771  
  1         52  
23 1     1   13 use Socket qw(inet_ntoa);
  1         3  
  1         1637  
24              
25             my $CRYPT_SALT = 'erxpnUyerCerugbaNgfhW';
26              
27             sub new {
28 0     0 1   my ($package, %params) = @_;
29 0           my $self = bless \%params, $package;
30              
31 0 0         if (!defined $self->{Password}) {
32 0           croak __PACKAGE__.' requires a Password argument';
33             }
34 0 0         if (!defined $self->{Listen_port}) {
35 0           croak __PACKAGE__.' requires a Listen_port argument';
36             }
37 0           return $self;
38             }
39              
40             sub PCI_register {
41 0     0 0   my ($self, $irc, %args) = @_;
42 0           $self->{net2irc}{$args{network}} = $irc;
43 0           $self->{irc2net}{$irc} = $args{network};
44              
45 0           $self->{plugins}{ $args{network} } = [
46             [MultiProxyState => POE::Component::IRC::Plugin::MultiProxy::State->new()],
47             [MultiProxyAway => POE::Component::IRC::Plugin::MultiProxy::Away->new(
48             Message => $self->{Away_msg}
49             )],
50             [MultiProxyRecall => POE::Component::IRC::Plugin::MultiProxy::Recall->new(
51             Mode => $self->{Recall_mode},
52             )],
53             [MultiProxyClientManager => POE::Component::IRC::Plugin::MultiProxy::ClientManager->new()],
54             ];
55              
56 0           for my $plugin (@{ $self->{plugins}{ $args{network} } }) {
  0            
57 0           my ($name, $object) = @$plugin;
58 0           $irc->plugin_add($name, $object);
59             }
60              
61 0 0         if (!$self->{registered}) {
62 0           POE::Session->create(
63             object_states => [
64             $self => [qw(
65             _start
66             _client_error
67             _client_input
68             _listener_accept
69             _listener_failed
70             _shutdown
71             )],
72             ],
73             );
74             }
75              
76 0           $self->{registered}++;
77              
78 0           return 1;
79             }
80              
81             sub PCI_unregister {
82 0     0 0   my ($self, $irc) = @_;
83 0           my $network = delete $self->{irc2net}{$irc};
84              
85 0           $self->{registered}--;
86 0 0         $poe_kernel->call($self->{session_id}, '_shutdown') if !$self->{registered};
87              
88 0           for my $plugin (@{ $self->{plugins}{$network} }) {
  0            
89 0           $irc->plugin_del($plugin->[1]);
90             }
91 0           delete $self->{net2irc}{$network};
92              
93 0           return 1;
94             }
95              
96             sub _start {
97 0     0     my ($self) = $_[OBJECT];
98              
99 0           $self->{session_id} = $_[SESSION]->ID;
100 0 0         $self->{filter} = POE::Filter::Stackable->new(
101             Filters => [
102             POE::Filter::Line->new(),
103             POE::Filter::IRCD->new(),
104             ],
105             ) if !defined $self->{filter};
106              
107 0           $self->{listener} = POE::Wheel::SocketFactory->new(
108             BindAddress => $self->{Listen_host},
109             BindPort => $self->{Listen_port},
110             SuccessEvent => '_listener_accept',
111             FailureEvent => '_listener_failed',
112             Reuse => 'yes',
113             );
114              
115 0 0 0       if (defined $self->{SSL_key} && defined $self->{SSL_cert}) {
116 0           require POE::Component::SSLify;
117 0           POE::Component::SSLify->import(qw(Server_SSLify SSLify_Options));
118              
119 0           eval { SSLify_Options($self->{SSL_key}, $self->{SSL_cert}) };
  0            
120 0           chomp $@;
121 0 0         die "Unable to load SSL key ($self->{SSL_key}) or certificate ($self->{SSL_cert}): $@\n" if $@;
122              
123 0           eval { $self->{listener} = Server_SSLify($self->{listener}) };
  0            
124 0           chomp $@;
125 0 0         die "Unable to SSLify the listener: $@\n" if $@;
126             }
127              
128 0           return;
129             }
130              
131             sub _shutdown {
132 0     0     my ($self) = $_[OBJECT];
133 0           delete $self->{$_} for qw(wheels listener session_id);
134 0           return;
135             }
136              
137             sub _client_error {
138 0     0     my ($self, $id) = @_[OBJECT, ARG3];
139 0           delete $self->{wheels}{$id};
140 0           return;
141             }
142              
143             sub _client_input {
144 0     0     my ($self, $input, $id) = @_[OBJECT, ARG0, ARG1];
145 0           my $info = $self->{wheels}{$id};
146              
147 0 0         if ($input->{command} =~ /(PASS)/) {
    0          
148 0           $info->{lc $1} = $input->{params}[0];
149             }
150             elsif ($input->{command} =~ /(NICK|USER)/) {
151 0           $info->{lc $1} = $input->{params}[0];
152 0           $info->{registered}++;
153             }
154              
155 0 0         if ($info->{registered} == 2) {
156 0 0         AUTH: {
157 0           last AUTH if !defined $info->{pass};
158 0 0         $info->{pass} = md5_hex($info->{pass}, $CRYPT_SALT) if length $self->{Password} == 32;
159 0 0         last AUTH unless $info->{pass} eq $self->{Password};
160 0 0         last AUTH unless my $irc = $self->{net2irc}{ $info->{nick} };
161              
162 0           $info->{wheel}->put("$info->{nick} NICK :".$irc->nick_name);
163 0           my $clients = $self->{plugins}{ $info->{nick} }[-1][1];
164 0           $clients->add_client($info->{socket});
165 0           $irc->send_event(irc_proxy_authed => $id);
166 0           delete $self->{wheels}{$id};
167 0           return;
168             }
169              
170             # wrong password or nick (network), dump the client
171 0   0       $info->{wheel}->put('ERROR :Closing Link: * [' . ( $info->{user} || 'unknown' ) . '@' . $info->{ip} . '] (Unauthorised connection)' );
172 0           delete $self->{wheels}{$id};
173             }
174              
175 0           return;
176             }
177              
178             sub _listener_failed {
179 0     0     my ($self, $error) = @_[OBJECT, ARG2];
180 0           warn "Failed to spawn listener: $error; aborted\n";
181 0           $poe_kernel->call($self->{session_id}, '_shutdown');
182 0           return;
183             }
184              
185             sub _listener_accept {
186 0     0     my ($self, $socket, $peer_addr) = @_[OBJECT, ARG0, ARG1];
187              
188 0           my $wheel = POE::Wheel::ReadWrite->new(
189             Handle => $socket,
190             InputFilter => $self->{filter},
191             OutputFilter => POE::Filter::Line->new(),
192             InputEvent => '_client_input',
193             ErrorEvent => '_client_error',
194             );
195              
196 0           my $id = $wheel->ID();
197 0           $self->{wheels}{$id}{wheel} = $wheel;
198 0           $self->{wheels}{$id}{ip} = inet_ntoa($peer_addr);
199 0           $self->{wheels}{$id}{registered} = 0;
200 0           $self->{wheels}{$id}{socket} = $socket;
201              
202 0           return;
203             }
204              
205             1;
206              
207             =encoding utf8
208              
209             =head1 NAME
210              
211             POE::Component::IRC::Plugin::MultiProxy - A multi-server IRC proxy
212              
213             =head1 SYNOPSIS
214              
215             use POE::Component::IRC::Plugin::MultiProxy;
216              
217             my $proxy = POE::Component::IRC::Plugin::MultiProxy->new(
218             Listen_port = 12345,
219             Password = 'foobar',
220             );
221              
222             $irc->plugin_add(
223             MultiProxy => $proxy,
224             network => 'freenode',
225             );
226              
227             =head1 METHODS
228              
229             =head2 C
230              
231             Creates a new MultiProxy plugin object. Takes the following arguments:
232              
233             B<'Password'> (required), the password you will use when connecting to the
234             proxy.
235              
236             B<'Listen_port'> (required), the port you want the proxy to listen on.
237              
238             B<'Listen_host'> (optional), the host you want the proxy to listen on.
239             Defaults to '0.0.0.0'.
240              
241             B<'Away_msg'> (optional), the away message you want to use when no clients
242             are connected.
243              
244             B<'SSL_key'>, the name of a file containing an SSL key for the listener to
245             use, if you want to enable SSL.
246              
247             B<'SSL_cert'>, the name of a file containing an SSL certificate for the
248             listener to use, if you want to enable SSL.
249              
250             B<'Recall_mode'>, how you want messages to be recalled. Available modes are:
251              
252             =over 4
253              
254             =item B<'missed'> (the default): MultiProxy will only recall the channel
255             messages you missed since the last time you detached from MultiProxy.
256              
257             =item B<'none'>: MultiProxy will not recall any channel messages.
258              
259             =item B<'all'>: MultiProxy will recall all channel messages.
260              
261             =back
262              
263             B: MultiProxy will always recall I that you missed while
264             you were away, regardless of this option.
265              
266             =head1 TODO
267              
268             Look into using L as
269             an intermediary for multiple clients.
270              
271             Keep recall messages away from prying eyes, instead of in F.
272              
273             Add proper tests.
274              
275             =head1 AUTHOR
276              
277             Hinrik Ern SigurEsson, hinrik.sig@gmail.com
278              
279             =head1 LICENSE AND COPYRIGHT
280              
281             Copyright 2008-2010 Hinrik Ern SigurEsson
282              
283             This program is free software, you can redistribute it and/or modify
284             it under the same terms as Perl itself.
285              
286             =head1 SEE ALSO
287              
288             Other useful IRC bouncers:
289              
290             =over
291              
292             =item L
293              
294             =item L
295              
296             =item L
297              
298             =item L
299              
300             =item L
301              
302             =item L
303              
304             =item L
305              
306             =back
307              
308             =cut