File Coverage

blib/lib/POE/Component/Server/FTP.pm
Criterion Covered Total %
statement 21 114 18.4
branch 0 30 0.0
condition 0 15 0.0
subroutine 7 24 29.1
pod 0 12 0.0
total 28 195 14.3


line stmt bran cond sub pod time code
1             package POE::Component::Server::FTP;
2              
3             ###########################################################################
4             ### POE::Component::Server::FTP
5             ### L.M.Orchard (deus_x@pobox.com)
6             ### David Davis (xantus@cpan.org)
7             ###
8             ### TODO:
9             ### - Should the Limiting depend on the ip connected via PORT/PASV or
10             ### the control connection ip
11             ### - Change virus checking to postprocessing
12             ###
13             ### Copyright (c) 2001 Leslie Michael Orchard. All Rights Reserved.
14             ### This module is free software; you can redistribute it and/or
15             ### modify it under the same terms as Perl itself.
16             ###
17             ### Changes Copyright (c) 2003-2004 David Davis and Teknikill Software
18             ###########################################################################
19              
20 1     1   195270 use strict;
  1         3  
  1         40  
21 1     1   6 use warnings;
  1         2  
  1         62  
22              
23             our @ISA = qw(Exporter);
24             our $VERSION = '0.08';
25              
26 1     1   6 use Socket;
  1         6  
  1         844  
27 1     1   8 use Carp;
  1         1  
  1         66  
28 1         8 use POE qw(Session Wheel::ReadWrite Filter::Line
29             Driver::SysRW Wheel::SocketFactory
30 1     1   5 Wheel::Run Filter::Reference);
  1         2  
31 1     1   60318 use POE::Component::Server::FTP::ControlSession;
  1         6  
  1         43  
32 1     1   11 use POE::Component::Server::FTP::ControlFilter;
  1         3  
  1         2056  
33              
34             sub spawn {
35 0     0 0   my $package = shift;
36 0 0         croak "$package requires an even number of parameters" if @_ % 2;
37 0           my %params = @_;
38 0           my $alias = $params{'Alias'};
39 0 0 0       $alias = 'ftpd' unless defined($alias) and length($alias);
40 0           $params{'Alias'} = $alias;
41 0   0       $params{'ListenPort'} = $params{'ListenPort'} || 21;
42 0   0       $params{'TimeOut'} = $params{'TimeOut'} || 0;
43 0   0       $params{'DownloadLimit'} = $params{'DownloadLimit'} || 0;
44 0   0       $params{'UploadLimit'} = $params{'UploadLimit'} || 0;
45 0           $params{'LimitScheme'} = $params{'LimitSceme'};
46 0   0       $params{'LimitScheme'} = $params{'LimitScheme'} || 'none';
47              
48 0           POE::Session->create(
49             #options => {trace=>1},
50             args => [ \%params ],
51             package_states => [
52             'POE::Component::Server::FTP' => {
53             _start => '_start',
54             _stop => '_stop',
55             _write_log => '_write_log',
56             register => 'register',
57             unregister => 'unregister',
58             notify => 'notify',
59             accept => 'accept',
60             accept_error => 'accept_error',
61             signals => 'signals',
62             _bw_limit => '_bw_limit',
63             _dcon_cleanup => '_dcon_cleanup',
64             virus_check_error => 'virus_check_error',
65             virus_check_done => 'virus_check_done',
66             virus_check_stdout => 'virus_check_stdout',
67             virus_check_stderr => 'virus_check_stderr',
68             }
69             ],
70             );
71              
72 0           return 1;
73             }
74              
75             sub _start {
76 0     0     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
77 0           %{$heap->{params}} = %{ $_[ARG0] };
  0            
  0            
78              
79 0           $heap->{_main_pid} = $$;
80              
81 0 0         $session->option( @{$heap->{params}{'SessionOptions'}} ) if $heap->{params}{'SessionOptions'};
  0            
82 0           $kernel->alias_set($heap->{params}{'Alias'});
83              
84             # watch for SIGINT
85 0           $kernel->sig('INT', 'signals');
86              
87             # create a socket factory
88 0           $heap->{wheel} = POE::Wheel::SocketFactory->new(
89             BindPort => $heap->{params}{ListenPort}, # on this port
90             Reuse => 'yes', # and allow immediate port reuse
91             SuccessEvent => 'accept', # generating this event on connection
92             FailureEvent => 'accept_error' # generating this event on error
93             );
94              
95 0           $kernel->call($session->ID => _write_log => { v => 2, msg => "Listening to port $heap->{params}{ListenPort} on all interfaces." });
96             }
97              
98             sub _stop {
99 0     0     my ($kernel, $session) = @_[KERNEL, SESSION];
100 0           $kernel->call($session->ID => _write_log => { v => 2, msg => "Server stopped." });
101             }
102              
103             sub register {
104 0     0 0   my($kernel, $heap, $sender) = @_[KERNEL, HEAP, SENDER];
105 0           $kernel->refcount_increment($sender->ID, __PACKAGE__);
106 0           $heap->{listeners}->{$sender->ID} = 1;
107 0           $kernel->post($sender->ID => ftpd_registered => $_[SESSION]->ID);
108             }
109              
110             sub unregister {
111 0     0 0   my($kernel, $heap, $sender) = @_[KERNEL, HEAP, SENDER];
112 0           $kernel->refcount_decrement($sender->ID, __PACKAGE__);
113 0           delete $heap->{listeners}->{$sender->ID};
114             }
115              
116             sub notify {
117 0     0 0   my($kernel, $heap, $sender, $name, $data) = @_[KERNEL, HEAP, SENDER, ARG0, ARG1];
118 0 0         $data->{con_session} = $sender unless(exists($data->{con_session}));
119 0           my $ret = 0;
120 0           foreach (keys %{$heap->{listeners}}) {
  0            
121 0           my $tmp = $kernel->call($_ => $name => $data);
122 0 0         if (defined($tmp)) {
123 0           $ret += $tmp;
124             }
125             # print STDERR "ret is $ret for $name\n";
126             }
127 0 0         return ($ret > 0) ? 1 : 0;
128             }
129              
130             # Accept a new connection
131              
132             sub accept {
133 0     0 0   my ($kernel, $heap, $session, $accept_handle, $peer_addr, $peer_port) = @_[KERNEL, HEAP, SESSION, ARG0, ARG1, ARG2];
134              
135 0           $peer_addr = inet_ntoa($peer_addr);
136 0           my ($port, $ip) = (sockaddr_in(getsockname($accept_handle)));
137 0           $ip = inet_ntoa($ip);
138 0 0         my $report_ip = (defined $heap->{params}{FirewallIP}) ? $heap->{params}{FirewallIP} : $ip;
139              
140 0           $kernel->call($session->ID => _write_log => { v => 2, msg => "Server received connection on $report_ip ($ip:$port) from $peer_addr : $peer_port" });
141              
142 0           my $opt = { %{$heap->{params}} };
  0            
143 0           $opt->{Handle} = $accept_handle;
144 0           $opt->{ListenIP} = $report_ip;
145 0           $opt->{PeerAddr} = $peer_addr;
146 0           $opt->{PeerPort} = $peer_port;
147              
148 0           $opt->{LocalIP} = $ip;
149 0           $opt->{LocalPort} = $port;
150 0           $opt->{ReportIP} = $report_ip;
151              
152 0 0         unless ($kernel->call($session->ID, notify => ftpd_accept => {
153             session => $session,
154             handle => $accept_handle,
155             report_ip => $report_ip,
156             local_ip => $ip,
157             local_port => $port,
158             peer_addr => $peer_addr,
159             peer_port => $peer_port,
160             })) {
161 0           close($accept_handle);
162 0           return 0;
163             }
164              
165 0           POE::Component::Server::FTP::ControlSession->new($opt);
166             }
167              
168             sub _bw_limit {
169 0     0     my ($kernel, $heap, $session, $sender, $type, $ip, $bps) = @_[KERNEL, HEAP, SESSION, SENDER, ARG0, ARG1, ARG2];
170 0           $heap->{$type}{$ip}{$sender->ID} = $bps;
171 0           my $num = scalar(keys %{$heap->{$type}{$ip}});
  0            
172 0 0         my $newlimit = ((($type eq 'dl') ? $heap->{params}{'DownloadLimit'} : $heap->{params}{'UploadLimit'}) / $num);
173 0 0         return ($bps > $newlimit) ? 1 : 0;
174             }
175              
176             sub _dcon_cleanup {
177 0     0     my ($kernel, $heap, $session, $type, $ip, $sid) = @_[KERNEL, HEAP, SESSION, ARG0, ARG1, ARG2];
178 0           $kernel->call($session->ID => _write_log => { v => 4, msg => "cleaing up $type limiter for $ip (session $sid)" });
179 0           delete $heap->{$type}{$ip}{$sid};
180             }
181              
182             # Handle an error in connection acceptance
183              
184             sub accept_error {
185 0     0 0   my ($kernel, $session, $operation, $errnum, $errstr) = @_[KERNEL, SESSION, ARG0, ARG1, ARG2];
186 0           $kernel->call($session->ID => write_log => { v => 1, msg => "Server encountered $operation error $errnum: $errstr" });
187 0           $kernel->call($session->ID, notify => accept_error => { session => $session, operation => $operation, error_num => $errnum, err_str => $errstr });
188             }
189              
190             # Handle incoming signals (INT)
191              
192             sub signals {
193 0     0 0   my ($kernel, $session, $signal_name) = @_[KERNEL, SESSION, ARG0];
194              
195 0           $kernel->call($session->ID => _write_log => { v => 1, msg => "Server caught SIG$signal_name" });
196              
197             # to stop ctrl-c / INT
198 0 0         if ($signal_name eq 'INT') {
199             #$_[KERNEL]->sig_handled();
200             }
201              
202 0           return 0;
203             }
204              
205             sub _write_log {
206 0     0     my ($kernel, $session, $heap, $sender, $o) = @_[KERNEL, SESSION, HEAP, SENDER, ARG0];
207 0 0         if ($o->{v} <= $heap->{params}{'LogLevel'}) {
208             # my $datetime = localtime();
209 0 0         my $sender = (defined $o->{sid}) ? $o->{sid} : $sender->ID;
210 0 0         my $type = (defined $o->{type}) ? $o->{type} : 'M';
211             # print "[$datetime][$type$sender] $o->{msg}\n";
212 0           $kernel->call($session->ID, notify => ftpd_write_log => {
213             sender => $sender,
214             type => $type,
215             msg => $o->{msg},
216             data => $o,
217             });
218             }
219             }
220              
221             # TODO finish this, and change it to post processor
222              
223             sub virus_check {
224 0     0 0   my ($kernel, $session, $heap, $sender, $o) = @_[KERNEL, SESSION, HEAP, SENDER, ARG0];
225              
226 0 0         if (exists($heap->{viruscheck_wheel})) {
227             # try again later, 1 at a time!
228 0           $kernel->delay_set(virus_check => 15 => splice(@_,ARG0));
229 0           return;
230             }
231              
232 0   0       my $params = $heap->{params}{'VirusCheckerParams'} || [];
233              
234 0           $heap->{viruscheck_wheel} = POE::Wheel::Run->new(
235             Program => $heap->{params}{'VirusCheckerCmd'},
236             ProgramArgs => $params, # Parameters for $program.
237             ErrorEvent => 'virus_check_error', # Event to emit on errors.
238             CloseEvent => 'virus_check_done', # Child closed all output.
239             StdoutEvent => 'virus_check_stdout', # Event to emit with child stdout information.
240             StderrEvent => 'virus_check_stderr', # Event to emit with child stderr information.
241             StdoutFilter => POE::Filter::Line->new(), # Child output as lines.
242             StderrFilter => POE::Filter::Line->new(), # Child errors are lines.
243             );
244              
245             }
246              
247             sub virus_check_error {
248 0     0 0   print "error: $_[ARG0]";
249             }
250              
251             sub virus_check_done {
252 0     0 0   print "done: $_[ARG0]";
253             }
254              
255             sub virus_check_stdout {
256 0     0 0   print "stdout: $_[ARG0]";
257             }
258              
259             sub virus_check_stderr {
260 0     0 0   print "stderr: $_[ARG0]";
261             }
262              
263             1;
264             __END__