File Coverage

blib/lib/POE/Component/Server/NNTP.pm
Criterion Covered Total %
statement 171 217 78.8
branch 46 80 57.5
condition 14 27 51.8
subroutine 25 32 78.1
pod 7 9 77.7
total 263 365 72.0


line stmt bran cond sub pod time code
1             package POE::Component::Server::NNTP;
2              
3 9     9   31187 use strict;
  9         21  
  9         384  
4 9     9   51 use warnings;
  9         21  
  9         432  
5 9     9   1103 use POE qw(Component::Client::NNTP Wheel::SocketFactory Wheel::ReadWrite Filter::Line);
  9         66772  
  9         90  
6 9     9   284726 use base qw(POE::Component::Pluggable);
  9         22  
  9         854  
7 9     9   58 use POE::Component::Pluggable::Constants qw(:ALL);
  9         20  
  9         1312  
8 9     9   57 use Socket;
  9         16  
  9         7915  
9 9     9   61 use vars qw($VERSION);
  9         22  
  9         27434  
10              
11             $VERSION = '1.04';
12              
13             sub spawn {
14 8     8 1 23509 my $package = shift;
15 8         46 my %opts = @_;
16 8         84 $opts{lc $_} = delete $opts{$_} for keys %opts;
17 8         26 my $options = delete $opts{options};
18 8 100 66     69 $opts{posting} = 1 unless defined $opts{posting} and !$opts{posting};
19 8 100 66     74 $opts{handle_connects} = 1 unless defined $opts{handle_connects} and !$opts{handle_connects};
20 8 100 66     75 $opts{extra_cmds} = [ ] unless defined $opts{extra_cmds} and ref $opts{extra_cmds} eq 'ARRAY';
21 8         24 $_ = lc $_ for @{ $opts{extra_cmds} };
  8         30  
22 8         34 my $self = bless \%opts, $package;
23 8         98 $self->_pluggable_init( prefix => 'nntpd_', types => [ 'NNTPD', 'NNTPC' ] );
24 8 50       416 $self->{session_id} = POE::Session->create(
25             object_states => [
26             $self => { shutdown => '_shutdown',
27             send_event => '__send_event',
28             send_to_client => '_send_to_client',
29             },
30             $self => [ qw(_start register unregister _accept_client _accept_failed _conn_input _conn_error _conn_flushed _conn_alarm _send_to_client __send_event) ],
31             ],
32             heap => $self,
33             ( ref($options) eq 'HASH' ? ( options => $options ) : () ),
34             )->ID();
35 8         1090 return $self;
36             }
37              
38             sub session_id {
39 8     8 1 33 return $_[0]->{session_id};
40             }
41              
42             sub _conn_exists {
43 414     414   624 my ($self,$wheel_id) = @_;
44 414 50 33     2280 return 0 unless $wheel_id and defined $self->{clients}->{ $wheel_id };
45 414         1103 return 1;
46             }
47              
48             sub _valid_cmd {
49 7     7   15 my $self = shift;
50 7   50     41 my $cmd = shift || return;
51 7         27 $cmd = lc $cmd;
52 7 100       14 return 0 unless grep { $_ eq $cmd } @{ $self->{cmds} }, @{ $self->{extra_cmds} };
  113         247  
  7         18  
  7         23  
53 6         38 return 1;
54             }
55              
56             sub shutdown {
57 0     0 1 0 my $self = shift;
58 0         0 $poe_kernel->post( $self->{session_id}, 'shutdown' );
59             }
60              
61             sub _start {
62 8     8   2552 my ($kernel,$self,$sender) = @_[KERNEL,OBJECT,SENDER];
63 8         41 $self->{session_id} = $_[SESSION]->ID();
64 8 100       70 if ( $self->{alias} ) {
65 7         50 $kernel->alias_set( $self->{alias} );
66             }
67             else {
68 1         6 $kernel->refcount_increment( $self->{session_id} => __PACKAGE__ );
69             }
70 8 50       318 if ( $kernel != $sender ) {
71 8         28 my $sender_id = $sender->ID;
72 8         88 $self->{events}->{'nntpd_all'}->{$sender_id} = $sender_id;
73 8         29 $self->{sessions}->{$sender_id}->{'ref'} = $sender_id;
74 8         23 $self->{sessions}->{$sender_id}->{'refcnt'}++;
75 8         44 $kernel->refcount_increment($sender_id, __PACKAGE__);
76 8         263 $kernel->post( $sender, 'nntpd_registered', $self );
77 8         686 $kernel->detach_myself();
78             }
79              
80 8         998 $self->{filter} = POE::Filter::Line->new();
81              
82 8         419 $self->{cmds} = [ qw(authinfo article body head stat group help ihave last list newgroups newnews next post quit slave) ];
83              
84 8 100       109 $self->{listener} = POE::Wheel::SocketFactory->new(
    50          
85             ( defined $self->{address} ? ( BindAddress => $self->{address} ) : () ),
86             ( defined $self->{port} ? ( BindPort => $self->{port} ) : ( BindPort => 119 ) ),
87             SuccessEvent => '_accept_client',
88             FailureEvent => '_accept_failed',
89             SocketDomain => AF_INET, # Sets the socket() domain
90             SocketType => SOCK_STREAM, # Sets the socket() type
91             SocketProtocol => 'tcp', # Sets the socket() protocol
92             Reuse => 'on', # Lets the port be reused
93             );
94 8         4556 return;
95             }
96              
97             sub _accept_client {
98 7     7   17557 my ($kernel,$self,$socket,$peeraddr,$peerport) = @_[KERNEL,OBJECT,ARG0..ARG2];
99 7         135 my $sockaddr = inet_ntoa( ( unpack_sockaddr_in ( getsockname $socket ) )[1] );
100 7         53 my $sockport = ( unpack_sockaddr_in ( getsockname $socket ) )[0];
101 7         36 $peeraddr = inet_ntoa( $peeraddr );
102              
103 7         119 my $wheel = POE::Wheel::ReadWrite->new(
104             Handle => $socket,
105             Filter => $self->{filter},
106             InputEvent => '_conn_input',
107             ErrorEvent => '_conn_error',
108             FlushedEvent => '_conn_flushed',
109             );
110              
111 7 50       2459 return unless $wheel;
112            
113 7         122 my $id = $wheel->ID();
114 7         90 $self->{clients}->{ $id } =
115             {
116             wheel => $wheel,
117             peeraddr => $peeraddr,
118             peerport => $peerport,
119             sockaddr => $sockaddr,
120             sockport => $sockport,
121             };
122 7         39 $self->_send_event( 'nntpd_connection', $id, $peeraddr, $peerport, $sockaddr, $sockport );
123              
124 7   100     73 $self->{clients}->{ $id }->{alarm} = $kernel->delay_set( '_conn_alarm', $self->{time_out} || 300, $id );
125 7         507 return;
126             }
127              
128              
129             sub _accept_failed {
130 0     0   0 my ($kernel,$self,$operation,$errnum,$errstr,$wheel_id) = @_[KERNEL,OBJECT,ARG0..ARG3];
131 0         0 warn "Wheel $wheel_id generated $operation error $errnum: $errstr\n";
132 0         0 delete $self->{listener};
133 0         0 $self->_send_event( 'nntpd_listener_failed', $operation, $errnum, $errstr );
134 0         0 return;
135             }
136              
137             sub _conn_input {
138 381     381   55301 my ($kernel,$self,$input,$id) = @_[KERNEL,OBJECT,ARG0,ARG1];
139 381 50       1413 return unless $self->_conn_exists( $id );
140 381   50     2835 $kernel->delay_adjust( $self->{clients}->{ $id }->{alarm}, $self->{time_out} || 300 );
141 381 100       49414 if ( $self->{clients}->{ $id }->{post_buffer} ) {
142 374 100       1142 if ( $input eq '.' ) {
143 2         9 my $buffer = delete $self->{clients}->{ $id }->{post_buffer};
144 2         7 my $code = $self->{clients}->{ $id }->{post_code};
145 2         34 $self->_send_event( 'nntpd_posting', $id, $code, $buffer );
146 2         8 return;
147             }
148 372         944 $input =~ s/^\.\.$/./;
149 372         453 push @{ $self->{clients}->{ $id }->{post_buffer} }, $input;
  372         1176  
150 372         1537 return;
151             }
152 7         23 $input =~ s/^\s+//g;
153 7         23 $input =~ s/\s+$//g;
154 7         30 my @args = split /\s+/, $input;
155 7         15 my $cmd = shift @args;
156 7 50       32 return unless $cmd;
157 7 100       32 unless ( $self->_valid_cmd( $cmd ) ) {
158 1         5 $self->send_to_client( $id, "500 command '$cmd' not recognized" );
159 1         9 return;
160             }
161 6         15 $cmd = lc $cmd;
162 6 100       51 if ( $cmd eq 'quit' ) {
163 3         11 $self->{clients}->{ $id }->{quit} = 1;
164 3         12 $self->send_to_client( $id, '205 closing connection - goodbye!' );
165 3         24 return;
166             }
167 3         17 $self->_send_event( 'nntpd_cmd_' . $cmd, $id, @args );
168 3         12 return;
169             }
170              
171             sub _conn_error {
172 0     0   0 my ($self,$errstr,$id) = @_[OBJECT,ARG2,ARG3];
173 0 0       0 return unless $self->_conn_exists( $id );
174 0         0 delete $self->{clients}->{ $id };
175 0         0 $self->_send_event( 'nntpd_disconnected', $id );
176 0         0 return;
177             }
178              
179             sub _conn_flushed {
180 16     16   19986 my ($self,$id) = @_[OBJECT,ARG0];
181 16 50       48 return unless $self->_conn_exists( $id );
182 16 100       90 return unless $self->{clients}->{ $id }->{quit};
183 3         25 delete $self->{clients}->{ $id };
184 3         671 $self->_send_event( 'nntpd_disconnected', $id );
185 3         12 return;
186             }
187              
188             sub _conn_alarm {
189 1     1   10008422 my ($kernel,$self,$id) = @_[KERNEL,OBJECT,ARG0];
190 1 50       7 return unless $self->_conn_exists( $id );
191 1         15 delete $self->{clients}->{ $id };
192 1         438 $self->_send_event( 'nntpd_disconnected', $id );
193 1         4 return;
194             }
195              
196             sub _shutdown {
197 8     8   22811 my ($kernel,$self) = @_[KERNEL,OBJECT];
198 8         68 delete $self->{listener};
199 8         1691 delete $self->{clients};
200 8         1090 $kernel->alarm_remove_all();
201 8         525 $kernel->alias_remove( $_ ) for $kernel->alias_list();
202 8 100       592 $kernel->refcount_decrement( $self->{session_id} => __PACKAGE__ ) unless $self->{alias};
203 8         163 $self->_pluggable_destroy();
204 8         545 $self->_unregister_sessions();
205 8         348 undef;
206             }
207              
208             sub register {
209 0     0 1 0 my ($kernel, $self, $session, $sender, @events) =
210             @_[KERNEL, OBJECT, SESSION, SENDER, ARG0 .. $#_];
211              
212 0 0       0 unless (@events) {
213 0         0 warn "register: Not enough arguments";
214 0         0 return;
215             }
216              
217 0         0 my $sender_id = $sender->ID();
218              
219 0         0 foreach (@events) {
220 0 0       0 $_ = "nntpd_" . $_ unless /^_/;
221 0         0 $self->{events}->{$_}->{$sender_id} = $sender_id;
222 0         0 $self->{sessions}->{$sender_id}->{'ref'} = $sender_id;
223 0 0 0     0 unless ($self->{sessions}->{$sender_id}->{refcnt}++ or $session == $sender) {
224 0         0 $kernel->refcount_increment($sender_id, __PACKAGE__);
225             }
226             }
227              
228 0         0 $kernel->post( $sender, 'nntpd_registered', $self );
229 0         0 return;
230             }
231              
232             sub unregister {
233 0     0 1 0 my ($kernel, $self, $session, $sender, @events) =
234             @_[KERNEL, OBJECT, SESSION, SENDER, ARG0 .. $#_];
235              
236 0 0       0 unless (@events) {
237 0         0 warn "unregister: Not enough arguments";
238 0         0 return;
239             }
240              
241 0         0 $self->_unregister($session,$sender,@events);
242 0         0 undef;
243             }
244              
245             sub _unregister {
246 0     0   0 my ($self,$session,$sender) = splice @_,0,3;
247 0         0 my $sender_id = $sender->ID();
248              
249 0         0 foreach (@_) {
250 0 0       0 $_ = "nntpd_" . $_ unless /^_/;
251 0         0 my $blah = delete $self->{events}->{$_}->{$sender_id};
252 0 0       0 unless ( $blah ) {
253 0         0 warn "$sender_id hasn't registered for '$_' events\n";
254 0         0 next;
255             }
256 0 0       0 if (--$self->{sessions}->{$sender_id}->{refcnt} <= 0) {
257 0         0 delete $self->{sessions}->{$sender_id};
258 0 0       0 unless ($session == $sender) {
259 0         0 $poe_kernel->refcount_decrement($sender_id, __PACKAGE__);
260             }
261             }
262             }
263 0         0 undef;
264             }
265              
266             sub _unregister_sessions {
267 8     8   17 my $self = shift;
268 8         35 my $nntpd_id = $self->session_id();
269 8         25 foreach my $session_id ( keys %{ $self->{sessions} } ) {
  8         36  
270 8 50       47 if (--$self->{sessions}->{$session_id}->{refcnt} <= 0) {
271 8         35 delete $self->{sessions}->{$session_id};
272 8 50       83 $poe_kernel->refcount_decrement($session_id, __PACKAGE__)
273             unless ( $session_id eq $nntpd_id );
274             }
275             }
276             }
277              
278             sub __send_event {
279 8     8   25238 my( $self, $event, @args ) = @_[ OBJECT, ARG0, ARG1 .. $#_ ];
280 8         161 $self->_send_event( $event, @args );
281 8         29 return;
282             }
283              
284             sub _pluggable_event {
285 8     8   26508 my $self = shift;
286 8         48 $poe_kernel->post( $self->{session_id}, '__send_event', @_ );
287             }
288              
289             sub send_event {
290 0     0 1 0 my $self = shift;
291 0         0 $poe_kernel->post( $self->{session_id}, '__send_event', @_ );
292             }
293              
294             sub _send_event {
295 24     24   45 my $self = shift;
296 24         65 my ($event, @args) = @_;
297 24         119 my $kernel = $POE::Kernel::poe_kernel;
298 24         97 my $session = $kernel->get_active_session()->ID();
299 24         154 my %sessions;
300              
301             my @extra_args;
302              
303 24 50       288 return 1 if $self->_pluggable_process( 'NNTPD', $event, \( @args ), \@extra_args ) == PLUGIN_EAT_ALL;
304              
305 24 50       3889 push @args, @extra_args if scalar @extra_args;
306              
307 24         45 $sessions{$_} = $_ for (values %{$self->{events}->{'nntpd_all'}}, values %{$self->{events}->{$event}});
  24         99  
  24         154  
308              
309 24         153 $kernel->post( $_ => $event => @args ) for values %sessions;
310 24         2440 undef;
311             }
312              
313             sub send_to_client {
314 16     16 1 4365 my $self = shift;
315 16         723 $poe_kernel->call( $self->{session_id}, '_send_to_client', @_ );
316             }
317              
318             sub _send_to_client {
319 16     16   755 my ($kernel,$self,$id,$output) = @_[KERNEL,OBJECT,ARG0..ARG1];
320 16 50       49 return unless $self->_conn_exists( $id );
321 16 50       41 return unless $output;
322              
323 16 50       121 return 1 if $self->_pluggable_process( 'NNTPC', 'response', $id, \$output ) == PLUGIN_EAT_ALL;
324              
325 16         5527 $self->{clients}->{ $id }->{wheel}->put($output);
326 16         1712 return 1;
327             }
328              
329             sub NNTPD_connection {
330 7     7 0 639 my ($self,$nntpd) = splice @_, 0, 2;
331 7         13 my $id = ${ $_[0] };
  7         19  
332 7 100       70 return 1 unless $self->{handle_connects};
333 5 100       57 if ( $self->{posting} ) {
334 4         15 $self->send_to_client( $id, '200 server ready - posting allowed' );
335             }
336             else {
337 1         3 $self->send_to_client( $id, '201 server ready - no posting allowed' );
338             }
339 5         49 return 1;
340             }
341              
342             sub NNTPC_response {
343 16     16 0 533 my ($self,$nntpd) = splice @_, 0, 2;
344 16         35 my $id = $_[0];
345 16         28 my $text = ${ $_[1] };
  16         33  
346 16         86 my ($code) = $text =~ /^\s*(\d{3,3})\s*/;
347 16 100 66     188 return 1 unless $code && ( $code eq '340' || $code eq '335' );
      33        
348 2         7 $self->{clients}->{ $id }->{post_code} = $code;
349 2         9 $self->{clients}->{ $id }->{post_buffer} = [ ];
350 2         6 return 1;
351             }
352              
353             1;
354             __END__