File Coverage

blib/lib/POE/Component/Server/NNTP.pm
Criterion Covered Total %
statement 168 214 78.5
branch 46 80 57.5
condition 14 27 51.8
subroutine 24 31 77.4
pod 7 9 77.7
total 259 361 71.7


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