File Coverage

blib/lib/Net/FTPSSL.pm
Criterion Covered Total %
statement 135 2362 5.7
branch 6 1202 0.5
condition 0 661 0.0
subroutine 38 155 24.5
pod 52 57 91.2
total 231 4437 5.2


line stmt bran cond sub pod time code
1             # File : Net::FTPSSL
2             # Author : cleach
3             # Created : 01 March 2005
4             # Version : 0.41
5             # Revision: -Id: FTPSSL.pm,v 1.24 2005/10/23 14:37:12 cleach Exp -
6              
7             package Net::FTPSSL;
8              
9 16     16   1151286 use strict;
  16         165  
  16         543  
10 16     16   77 use warnings;
  16         31  
  16         450  
11              
12             # Enforce a minimum version of this module or Net::FTPSSL hangs!
13             # v1.08 works, v1.18 added ccc() support.
14             # Don't use v1.79 to v1.85 due to misleading warnings.
15 16     16   11682 use IO::Socket::SSL 1.26;
  16         1268711  
  16         122  
16              
17 16     16   3151 use vars qw( $VERSION @EXPORT $ERRSTR );
  16         70  
  16         1107  
18 16     16   115 use base ( 'Exporter', 'IO::Socket::SSL' );
  16         33  
  16         1810  
19              
20             # Only supports IPv4 (to also get IPv6 must use IO::Socket::IP instead. v0.20)
21 16     16   103 use IO::Socket::INET;
  16         32  
  16         188  
22              
23 16     16   15896 use Net::SSLeay::Handle;
  16         32304  
  16         727  
24 16     16   116 use File::Basename;
  16         29  
  16         920  
25 16     16   485 use File::Copy;
  16         3710  
  16         628  
26 16     16   7444 use Time::Local;
  16         32819  
  16         917  
27 16     16   6613 use Sys::Hostname;
  16         15750  
  16         828  
28 16     16   110 use Carp qw( carp croak );
  16         68  
  16         693  
29 16     16   96 use Errno qw/ EINTR /;
  16         29  
  16         931  
30              
31             @EXPORT = qw( IMP_CRYPT EXP_CRYPT CLR_CRYPT
32             DATA_PROT_CLEAR DATA_PROT_PRIVATE
33             DATA_PROT_SAFE DATA_PROT_CONFIDENTIAL
34             CMD_INFO CMD_OK CMD_MORE CMD_REJECT
35             CMD_ERROR CMD_PROTECT CMD_PENDING );
36             $ERRSTR = "No Errors Detected Yet.";
37              
38             # Command Channel Protection Levels
39 16     16   90 use constant IMP_CRYPT => "I";
  16         31  
  16         953  
40 16     16   98 use constant EXP_CRYPT => "E"; # Default
  16         30  
  16         933  
41 16     16   98 use constant CLR_CRYPT => "C";
  16         31  
  16         820  
42              
43             # Data Channel Protection Levels
44 16     16   135 use constant DATA_PROT_CLEAR => "C"; # Least secure!
  16         43  
  16         971  
45 16     16   100 use constant DATA_PROT_SAFE => "S";
  16         28  
  16         727  
46 16     16   89 use constant DATA_PROT_CONFIDENTIAL => "E";
  16         212  
  16         764  
47 16     16   94 use constant DATA_PROT_PRIVATE => "P"; # Default & most secure!
  16         27  
  16         692  
48              
49             # Valid FTP Result codes
50 16     16   87 use constant CMD_INFO => 1;
  16         25  
  16         801  
51 16     16   96 use constant CMD_OK => 2;
  16         46  
  16         1038  
52 16     16   126 use constant CMD_MORE => 3;
  16         41  
  16         691  
53 16     16   81 use constant CMD_REJECT => 4;
  16         30  
  16         697  
54 16     16   96 use constant CMD_ERROR => 5;
  16         48  
  16         844  
55 16     16   92 use constant CMD_PROTECT => 6;
  16         27  
  16         684  
56 16     16   87 use constant CMD_PENDING => 0;
  16         26  
  16         753  
57              
58             # -------- Above Exported ---- Below don't bother to export --------
59              
60             # File transfer modes (the mixed modes have no code)
61 16     16   100 use constant MODE_BINARY => "I";
  16         29  
  16         736  
62 16     16   86 use constant MODE_ASCII => "A"; # Default
  16         28  
  16         715  
63              
64             # The Data Connection Setup Commands ...
65             # Passive Options ... (All pasive modes are currently supported)
66 16     16   87 use constant FTPS_PASV => 1; # Default mode ...
  16         27  
  16         684  
67 16     16   86 use constant FTPS_EPSV_1 => 2; # EPSV 1 - Internet Protocol Version 4
  16         38  
  16         677  
68 16     16   86 use constant FTPS_EPSV_2 => 3; # EPSV 2 - Internet Protocol Version 6
  16         26  
  16         707  
69             # Active Options ... (No active modes are currently supported)
70 16     16   120 use constant FTPS_PORT => 4;
  16         46  
  16         833  
71 16     16   94 use constant FTPS_EPRT_1 => 5; # EPRT 1 - Internet Protocol Version 4
  16         27  
  16         683  
72 16     16   83 use constant FTPS_EPRT_2 => 6; # EPRT 2 - Internet Protocol Version 6
  16         29  
  16         679  
73              
74             # Misc constants
75 16     16   85 use constant TRACE_MOD => 10; # How many iterations between ".". Must be >= 5.
  16         40  
  16         5638  
76              
77             # Primarily used while the call to new() is still in scope!
78             my $FTPS_ERROR;
79              
80             # Used to handle trapping all warnings accross class instances
81             my %warn_list;
82              
83             # Tells if possible to use IPv6 in connections.
84             my $ipv6;
85             my $IOCLASS;
86             my $family_key; # Domain or Family
87             my $debug_log_msg; # Used if Debug is turned on
88              
89              
90             BEGIN {
91 16     16   74 $VERSION = "0.41"; # The version of this module!
92              
93 16         46 my $type = "IO::Socket::SSL";
94 16         43 $ipv6 = 0; # Assume IPv4 only ...
95 16         49 $IOCLASS = "IO::Socket::INET"; # Assume IPv4 only ...
96 16         48 $family_key = "Domain"; # Traditional ...
97 16         28 my $msg;
98              
99 16         26 my $ioOrig = $IOCLASS;
100              
101             # Can we use IPv6 vs IPv4? Let IO::Socket::SSL make the decision for us!
102             # The logic gets real messy otherwise.
103 16 50       398 if ( ! $type->can ("can_ipv6") ) {
    50          
104 0         0 $msg = "No IPv6 support available. You must 1st upgrade $type to support it!";
105              
106             } elsif ( $type->can_ipv6 () ) {
107 16         86 $ipv6 = 1; # Yes! IPv6 can be suporteed!
108 16         49 $IOCLASS = $type->can_ipv6 (); # Get which IPv6 module SSL uses.
109 16 50       115 $family_key = "Family" if ( $IOCLASS eq "IO::Socket::IP" );
110 16         214 my $ver = $IOCLASS->VERSION;
111 16         102 $msg = "IPv6 support available via $IOCLASS ($ver) Key: ($family_key)";
112              
113             } else {
114 0         0 $msg = "No IPv6 support available. Missing required modules!";
115             }
116              
117             # Now let's provide the logfile header information ...
118             # Done here so only have to generate this information one time!
119 16         258 my $pv = sprintf ("%s [%vd]", $], $^V); # The version of perl!
120              
121             # Required info when opening a CPAN ticket against this module ...
122 16         84 $debug_log_msg = "\n"
123             . "Net-FTPSSL Version: $VERSION\n";
124              
125             # Print out versions of critical modules we depend on ...
126 16         96 foreach ( "IO-Socket-SSL", "Net-SSLeay",
127             "IO-Socket-INET", "IO-Socket-INET6",
128             "IO-Socket-IP", "IO",
129             "Socket" ) {
130 112         191 my $mod = $_;
131 112         320 $mod =~ s/-/::/g;
132 112         913 my $ver = $mod->VERSION;
133 112 100       298 if ( defined $ver ) {
134 96         232 $debug_log_msg .= "$_ Version: $ver\n";
135             } else {
136 16         43 $debug_log_msg .= "$_ might not be installed.\n";
137             }
138             }
139              
140 16         95 $debug_log_msg .= "\n${msg}\n\n"
141             . "Perl: $pv, OS: $^O\n\n";
142              
143             # Comment out/remove when ready to implement iIPv6 ...
144 16         36 $IOCLASS = $ioOrig; $family_key = "Domain";
  16         28  
145 16         358689 $debug_log_msg .= "***** IPv6 not yet supported in Net::FTPSSL! *****\n\n";
146             }
147              
148             # ============================================================================
149              
150             sub new {
151 0     0 1   my $self = shift;
152 0   0       my $type = ref($self) || $self;
153 0           my $host = shift;
154 0 0         my $arg = (ref ($_[0]) eq "HASH") ? $_[0] : {@_};
155              
156              
157             # Make sure to reset in case previous call to new generated an error!
158 0           $ERRSTR = "No Errors Detected Yet.";
159              
160             # Used as a quick way to detect if the caller passed us SSL_* arguments ...
161 0           my $found_ssl_args = 0;
162              
163             # The hash to pass to start_ssl() ...
164 0           my %ssl_args;
165              
166             # Depreciated in v0.18 (in 2011) and fatal in v0.26 (2015)
167             # Will remove this test sometime in 2017/2018!
168 0 0         if (ref ($arg->{SSL_Advanced}) eq "HASH") {
169 0           %ssl_args = %{$arg->{SSL_Advanced}};
  0            
170 0           $ERRSTR = "SSL_Advanced is no longer supported! Using a separate hash is no longer required for adding SSL options!";
171 0           croak ("\n" . $ERRSTR . "\n");
172             }
173              
174             # May depreciate in the near future in favor of the "grep" loop below!
175             # Debating the merrits of having two ways to do this.
176 0 0         if (ref ($arg->{SSL_Client_Certificate}) eq "HASH") {
177             # The main purpose of this option was to allow users to specify
178             # client certificates when their FTPS server requires them.
179             # This hash applies to both the command & data channels.
180             # Tags specified here overrided normal options if any tags
181             # conflict.
182             # See IO::Socket::SSL for supported options.
183 0           %ssl_args = %{$arg->{SSL_Client_Certificate}};
  0            
184 0           $found_ssl_args = 1;
185             }
186              
187             # See IO::Socket::SSL for supported options.
188             # Provides a way to directly pass needed SSL_* arguments to this module.
189             # There is only one Net::FTPSSL option that starts with SSL_, so skipping it!
190 0           for (grep { m{^SSL_} } keys %{$arg}) {
  0            
  0            
191 0 0         next if ( $_ eq "SSL_Client_Certificate" ); # The FTPSSL opt to skip!
192 0           $ssl_args{$_} = $arg->{$_};
193 0           $found_ssl_args = 1;
194             }
195              
196             # Only add if not using certificates & the caller didn't provide a value ...
197 0 0 0       unless ( $ssl_args{SSL_use_cert} || $ssl_args{SSL_verify_mode} ) {
198             # Stops the Man-In-The-Middle (MITM) security warning from start_ssl()
199             # when it calls configure_SSL() in IO::Socket::SSL.
200             # To plug that MITM security hole requires the use of certificates,
201             # so all that's being done here is supressing the warning. The MITM
202             # security hole is still open!
203             # That warning is now a fatal error in newer versions of IO::Socket::SSL.
204             # warn "WARNING: Your connection is vunerable to the MITM attacks\n";
205 0           $ssl_args{SSL_verify_mode} = Net::SSLeay::VERIFY_NONE();
206             }
207              
208             # --------------------------------------------------------------------------
209             # Will hold all the control options to this class
210             # Similar in use as _SSL_arguments ...
211 0           my %ftpssl_args;
212              
213             # Now onto processing the regular hash of arguments provided ...
214 0   0       my $encrypt_mode = $arg->{Encryption} || EXP_CRYPT;
215 0   0       my $port = $arg->{Port} || (($encrypt_mode eq IMP_CRYPT) ? 990 : 21);
216 0   0       my $debug = $arg->{Debug} || 0;
217 0   0       my $trace = $arg->{Trace} || 0;
218 0   0       my $timeout = $ssl_args{Timeout} || $arg->{Timeout} || 120;
219 0   0       my $buf_size = $arg->{Buffer} || 10240;
220             my $data_prot = ($encrypt_mode eq CLR_CRYPT) ? DATA_PROT_CLEAR
221 0 0 0       : ($arg->{DataProtLevel} || DATA_PROT_PRIVATE);
222 0   0       my $die = $arg->{Croak} || $arg->{Die};
223 0   0       my $pres_ts = $arg->{PreserveTimestamp} || 0;
224 0   0       my $use__ssl = $arg->{useSSL} || 0; # Being depreciated.
225              
226 0           my ($use_logfile, $use_glob) = (0, 0);
227 0 0 0       if ( $debug && defined $arg->{DebugLogFile} ) {
228             $use_logfile = (ref ($arg->{DebugLogFile}) eq "" &&
229 0   0       $arg->{DebugLogFile} ne "");
230 0           $use_glob = _isa_glob (undef, $arg->{DebugLogFile});
231             }
232 0   0       my $localaddr = $ssl_args{LocalAddr} || $arg->{LocalAddr};
233 0   0       my $pret = $arg->{Pret} || 0;
234 0   0       my $domain = $arg->{Domain} || $arg->{Family};
235 0   0       my $xWait = $arg->{xWait} || 0;
236              
237 0   0       my $reuseSession = $arg->{ReuseSession} || 0;
238              
239             # Default to true unless you request to disable it or no encryption used ...
240 0 0 0       my $enableCtx = ($arg->{DisableContext} || $encrypt_mode eq CLR_CRYPT) ? 0 : 1;
241              
242             # Used to work arround some FTPS servers behaving badly!
243 0           my $pasvHost = $arg->{OverridePASV};
244 0           my $fixHelp = $arg->{OverrideHELP};
245              
246             # --------------------------------------------------------------------------
247             # if ( $debug && ! exists $arg->{DebugLogFile} ) {
248             # # So will write any debug comments to STDERR ...
249             # $IO::Socket::SSL::DEBUG = 3;
250             # }
251              
252             # A special case used for further debugging the response!
253             # This special value is undocumented in the POD on purpose!
254 0 0         my $debug_extra = ($debug == 99) ? 1 : 0;
255              
256             # Special case for eliminating listing help text during login!
257 0 0         my $no_login_help = ($debug == 90) ? 1 : 0;
258              
259 0           my $f_exists = 0;
260              
261             # Determine where to write the Debug info to ...
262 0 0         if ( $use_logfile ) {
    0          
263 0 0         my $open_mode = ( $debug == 2 ) ? ">>" : ">";
264 0           my $f = $arg->{DebugLogFile};
265 0 0 0       unlink ( $f ) if ( -f $f && $open_mode ne ">>" );
266 0           $f_exists = (-f $f);
267              
268             # Always calls die on failure to open the requested log file ...
269 0 0         open ( $FTPS_ERROR, "$open_mode $f" ) or
270             _croak_or_return (undef, 1, 0,
271             "Can't create debug logfile: $f ($!)");
272              
273 0           $FTPS_ERROR->autoflush (1);
274              
275 0           $debug = 2; # Save the file handle & later close it ...
276              
277             } elsif ( $use_glob ) {
278 0           $FTPS_ERROR = $arg->{DebugLogFile};
279 0           $debug = 3; # Save the file handle, but never close it ...
280             }
281              
282 0 0 0       if ( $use_logfile || $use_glob ) {
    0          
283 0 0         unless ( $f_exists ) {
284 0           print $FTPS_ERROR $debug_log_msg;
285             } else {
286 0           print $FTPS_ERROR "\n\n";
287             }
288              
289             } elsif ( $debug ) {
290 0           $debug = 1; # No file handle to save ...
291              
292             # open ( $FTPS_ERROR, ">&STDERR" ) or
293             # _croak_or_return (undef, 1, 0,
294             # "Can't attach the debug logfile to STDERR. ($!)");
295             # $FTPS_ERROR->autoflush (1);
296              
297 0           print STDERR $debug_log_msg;
298             }
299              
300 0 0         if ( $debug ) {
301 0           _print_LOG (undef, "Server (port): $host ($port)\n\n");
302 0           _print_LOG (undef, "Keys: (", join ("), (", keys %${arg}), ")\n");
303 0           _print_LOG (undef, "Values: (", join ("), (", values %${arg}), ")\n\n");
304             }
305              
306             # Determines if we die if we will also need to write to the error log file ...
307 0 0         my $dbg_flg = $die ? ( $debug >= 2 ? 1 : 0 ) : $debug;
    0          
308              
309 0 0         return _croak_or_return (undef, $die, $dbg_flg, "Host undefined") unless $host;
310              
311 0 0 0       return _croak_or_return (undef, $die, $dbg_flg,
      0        
312             "Encryption mode unknown! ($encrypt_mode)")
313             if ( $encrypt_mode ne IMP_CRYPT && $encrypt_mode ne EXP_CRYPT &&
314             $encrypt_mode ne CLR_CRYPT );
315              
316 0 0 0       return _croak_or_return (undef, $die, $dbg_flg,
      0        
      0        
317             "Data Channel mode unknown! ($data_prot)")
318             if ( $data_prot ne DATA_PROT_CLEAR &&
319             $data_prot ne DATA_PROT_SAFE &&
320             $data_prot ne DATA_PROT_CONFIDENTIAL &&
321             $data_prot ne DATA_PROT_PRIVATE );
322              
323 0 0 0       if ( $ipv6 && defined $domain ) {
324 0 0         my $fmly = (exists $arg->{Domain}) ? "Domain" : "Family";
325 0           $domain = _validate_domain ( $type, $fmly, $domain, $dbg_flg, $die );
326 0 0         return ( undef ) unless (defined $domain);
327             }
328              
329             # We start with a clear connection, because I don't know if the
330             # connection will be implicit or explicit or remain clear after all.
331 0           my $socket;
332              
333 0 0         if (exists $arg->{ProxyArgs}) {
334             # Establishing a Proxy Connection ...
335 0           my %proxyArgs = %{$arg->{ProxyArgs}};
  0            
336              
337 0           $proxyArgs{'remote-host'} = $host;
338 0           $proxyArgs{'remote-port'} = $port;
339              
340 0           eval {
341 0           require Net::HTTPTunnel; # So not everyone has to install this module ...
342              
343 0           $socket = Net::HTTPTunnel->new ( %proxyArgs );
344             };
345 0 0         if ($@) {
346 0           return _croak_or_return (undef, $die, $dbg_flg, "Missing Perl Module Error:\n" . $@);
347             }
348 0 0         unless ( defined $socket ) {
349 0   0       my $pmsg = ($proxyArgs{'proxy-host'} || "undef") . ":" . ($proxyArgs{'proxy-port'} || "undef");
      0        
350 0           return _croak_or_return (undef, $die, $dbg_flg,
351             "Can't open HTTPTunnel proxy connection! ($pmsg) to ($host:$port)");
352             }
353 0           $ftpssl_args{myProxyArgs} = \%proxyArgs;
354              
355             } else {
356             # Establishing a Direct Connection ...
357 0           my %socketArgs = ( PeerAddr => $host,
358             PeerPort => $port,
359             Proto => 'tcp',
360             Timeout => $timeout
361             );
362 0 0         $socketArgs{LocalAddr} = $localaddr if (defined $localaddr);
363 0 0 0       $socketArgs{$family_key} = $domain if ($ipv6 && defined $domain);
364              
365 0 0         $socket = $IOCLASS->new ( %socketArgs )
366             # $socket = IO::Socket::INET->new ( %socketArgs )
367             or
368             return _croak_or_return (undef, $die, $dbg_flg,
369             "Can't open tcp connection! ($host:$port)");
370 0           $ftpssl_args{mySocketOpts} = \%socketArgs;
371             }
372              
373 0           _my_autoflush ( $socket );
374              
375             # Save so we can log socket activity if needed ...
376 0           $ftpssl_args{debug} = $debug;
377 0           $ftpssl_args{debug_extra} = $debug_extra;
378 0           $ftpssl_args{Croak} = $die;
379 0           $ftpssl_args{Timeout} = $timeout;
380              
381             # Bug Id: 120341 says this will be removed from socket by start_SSL() call.
382 0           ${*$socket}{_FTPSSL_arguments} = \%ftpssl_args;
  0            
383              
384 0           my $obj;
385              
386 0 0         if ( $encrypt_mode eq CLR_CRYPT ) {
387             # Catch the banner from the connection request ...
388 0 0         return _croak_or_return ($socket) unless ( response($socket) == CMD_OK );
389              
390             # Leave the command channel clear for regular FTP.
391 0           $obj = $socket;
392 0           bless ( $obj, $type );
393 0           ${*$obj}{_SSL_opened} = 0; # To get rid of SSL warning on quit ...
  0            
394              
395             } else {
396             # Determine the options to use in start_SSL() ...
397             # ------------------------------------------------------------------------
398             # Reset SSL_version & Timeout in %ssl_args so these options can be
399             # applied to the SSL_Client_Certificate functionality.
400             # ------------------------------------------------------------------------
401 0           my $mode;
402             my $use_ssl_flag;
403 0 0         if (defined $ssl_args{SSL_version}) {
    0          
404 0           $mode = $ssl_args{SSL_version}; # Mode was overridden.
405 0           $use_ssl_flag = ( $mode =~ m/^SSLv/i ); # Bug ID 115296
406             } elsif ( $use__ssl ) {
407 0           $mode = $ssl_args{SSL_version} = "SSLv23"; # SSL per override
408 0           $use_ssl_flag = 1;
409 0           warn ("Option useSSL has been depreciated. Use option SSL_version instead.\n");
410             } else {
411 0           $mode = $ssl_args{SSL_version} = "TLSv12"; # TLS v1.2 per defaults
412 0           $use_ssl_flag = 0;
413             }
414 0 0         $ssl_args{Timeout} = $timeout unless (exists $ssl_args{Timeout});
415              
416             # ------------------------------------------------------------------------
417             # The options for Reusing the Session ...
418             # ------------------------------------------------------------------------
419 0 0         if ( $reuseSession ) {
420 0           $ssl_args{SSL_session_cache} = IO::Socket::SSL::Session_Cache->new (4 + $reuseSession);
421 0           $ssl_args{SSL_session_key} = "Net-FTPSSL-${VERSION}-$$:${port}";
422             }
423              
424             # _debug_print_hash (undef, "Socket call", "initialization", "?", $socket);
425             # _debug_print_hash (undef, "Before start_SSL() call", "initialization", "?", \%ssl_args);
426             # ------------------------------------------------------------------------
427              
428             # Can we use SNI?
429 0 0 0       if ( $type->can ("can_client_sni") && $type->can_client_sni () ) {
430 0 0         $ssl_args{SSL_hostname} = $host if (! exists $ssl_args{SSL_hostname});
431             }
432              
433 0 0         if ( $encrypt_mode eq EXP_CRYPT ) {
434             # Catch the banner from the connection request ...
435 0 0         return _croak_or_return ($socket) unless (response ($socket) == CMD_OK);
436              
437             # In explicit mode FTPSSL sends an AUTH TLS/SSL command, catch the msgs
438 0 0         command( $socket, "AUTH", ($use_ssl_flag ? "SSL" : "TLS") );
439 0 0         return _croak_or_return ($socket) unless (response ($socket) == CMD_OK);
440             }
441              
442             # ------------------------------------------------------------------------
443             # Now transform the clear connection into a SSL one on our end.
444             # Messy since newer IO::Socket::SSL modules remove {_FTPSSL_arguments}!
445             # Bug Id: 120341.
446             # ------------------------------------------------------------------------
447 0           $obj = $type->start_SSL( $socket, %ssl_args );
448 0 0         unless ( $obj ) {
449 0 0         unless ( exists ${*$socket}{_FTPSSL_arguments} ) {
  0            
450 0           ${*$socket}{_FTPSSL_arguments} = \%ftpssl_args;
  0            
451 0 0         _print_LOG (undef, "Restoring _FTPSSL_arguments to \$socket.\n") if ( $debug );
452             }
453 0           return _croak_or_return ( $socket, undef,
454             "$mode: " . IO::Socket::SSL::errstr () );
455             }
456              
457 0 0         unless ( exists ${*$obj}{_FTPSSL_arguments} ) {
  0            
458 0           ${*$obj}{_FTPSSL_arguments} = \%ftpssl_args;
  0            
459 0 0         $obj->_print_LOG ("Restoring _FTPSSL_arguments to \$obj.\n") if ( $debug );
460             }
461             # ------------------------------------------------------------------------
462              
463 0 0         if ( $encrypt_mode eq IMP_CRYPT ) {
464             # Catch the banner from the implicit connection request ...
465 0 0         return $obj->_croak_or_return () unless ( $obj->response() == CMD_OK );
466             }
467              
468 0           $ftpssl_args{start_SSL_opts} = \%ssl_args;
469             }
470              
471              
472             # --------------------------------------
473             # Check if overriding "_help()" ...
474             # --------------------------------------
475 0 0         if ( defined $fixHelp ) {
476 0           my %helpHash;
477              
478 0           $ftpssl_args{OverrideHELP} = 0; # So we know OverrideHELP was used ...
479 0 0         if ( ref ($fixHelp) eq "ARRAY" ) {
    0          
    0          
480 0           foreach (@{$fixHelp}) {
  0            
481 0           my $k = uc ($_);
482 0 0         $helpHash{$k} = 1 if ( $k ne "HELP" );
483             }
484             } elsif ( $fixHelp == -1 ) {
485 0           $ftpssl_args{removeHELP} = 1; # Uses FEAT to list commands supported!
486             } elsif ( $fixHelp ) {
487 0           $ftpssl_args{OverrideHELP} = 1; # All FTP commands supported ...
488             }
489              
490             # Set the "cache" tags used by "_help()" so that it can still be called!
491 0           $ftpssl_args{help_cmds_found} = \%helpHash;
492 0           $ftpssl_args{help_cmds_msg} = "214 HELP Command Overridden by request.";
493              
494             # Causes direct calls to _help($cmd) to skip the server hit. (HELP $cmd)
495 0           $ftpssl_args{help_cmds_no_syntax_available} = 1;
496              
497             # When you get here, OverrideHELP is either "0" or "1"!
498             }
499             # --------------------------------------
500             # End overriding "_help()" ...
501             # --------------------------------------
502              
503             # These options control the behaviour of the Net::FTPSSL class ...
504 0           $ftpssl_args{Host} = $host;
505 0           $ftpssl_args{Crypt} = $encrypt_mode;
506 0           $ftpssl_args{debug} = $debug;
507 0           $ftpssl_args{debug_extra} = $debug_extra;
508 0           $ftpssl_args{debug_no_help} = $no_login_help;
509 0           $ftpssl_args{trace} = $trace;
510 0           $ftpssl_args{buf_size} = $buf_size;
511 0           $ftpssl_args{type} = MODE_ASCII;
512 0           $ftpssl_args{data_prot} = $data_prot;
513 0           $ftpssl_args{Croak} = $die;
514 0           $ftpssl_args{FixPutTs} = $ftpssl_args{FixGetTs} = $pres_ts;
515 0 0         $ftpssl_args{OverridePASV} = $pasvHost if (defined $pasvHost);
516 0           $ftpssl_args{dcsc_mode} = FTPS_PASV;
517 0           $ftpssl_args{Pret} = $pret;
518 0           $ftpssl_args{Timeout} = $timeout;
519 0 0         $ftpssl_args{xWait} = $xWait if ( $xWait );
520              
521 0 0         $ftpssl_args{ftpssl_filehandle} = $FTPS_ERROR if ( $debug >= 2 );
522 0           $FTPS_ERROR = undef;
523              
524             # Must be last for certificates to work correctly ...
525 0 0 0       if ( $reuseSession || $enableCtx ||
      0        
526             ref ($arg->{SSL_Client_Certificate}) eq "HASH" ) {
527             # Reuse the command channel context ...
528 0           my %ssl_reuse = ( SSL_reuse_ctx => ${*$obj}{_SSL_ctx} );
  0            
529              
530             # Added to fix CPAN Bug Id: 101388 ...
531 0           my $key = "SSL_ca_file";
532 0 0         if ( exists ${*$obj}{_SSL_arguments}->{$key} ) {
  0            
533 0           $ssl_reuse{$key} = ${*$obj}{_SSL_arguments}->{$key};
  0            
534             }
535 0           $key = "SSL_verifycn_name";
536 0 0         if ( exists ${*$obj}{_SSL_arguments}->{$key} ) {
  0            
537 0           $ssl_reuse{$key} = ${*$obj}{_SSL_arguments}->{$key};
  0            
538             }
539 0           $key = "SSL_verifycn_scheme";
540 0 0         if ( exists ${*$obj}{_SSL_arguments}->{$key} ) {
  0 0          
541 0           $ssl_reuse{$key} = ${*$obj}{_SSL_arguments}->{$key};
  0            
542             } elsif ( exists $ssl_args{$key} ) {
543 0           $ssl_reuse{$key} = $ssl_args{$key};
544             }
545              
546             # Fix for Bug Ids # 104407 & 76108. (Session Reuse!)
547 0           $key = "SSL_session_key";
548 0 0 0       if ( exists ${*$obj}{_SSL_arguments}->{$key} && ! exists $ssl_reuse{$key} ) {
  0            
549 0           $ssl_reuse{$key} = ${*$obj}{_SSL_arguments}->{$key};
  0            
550             # $obj->_print_LOG ("\n *** Adding: $key --> $ssl_reuse{$key} ***\n");
551             }
552              
553 0           $ftpssl_args{myContext} = \%ssl_reuse;
554             }
555              
556             # -------------------------------------------------------------------------
557             # Print out the details of the SSL object. It's TRUE only for debugging!
558             # -------------------------------------------------------------------------
559 0 0         if ( $debug ) {
560 0 0         if ( ref ($arg->{SSL_Client_Certificate}) eq "HASH" ) {
561             $obj->_debug_print_hash ( "SSL_Client_Certificate", "options",
562 0           $encrypt_mode, $arg->{SSL_Client_Certificate} );
563             }
564 0           $obj->_debug_print_hash ( "SSL", "arguments", $encrypt_mode, \%ssl_args );
565 0           $obj->_debug_print_hash ( $host, $port, $encrypt_mode, undef, "*" );
566             }
567              
568 0           return $obj;
569             }
570              
571             #-----------------------------------------------------------------------
572             # TODO: Adding ACCT (Account) support (response 332 [CMD_MORE] on password)
573              
574             sub login {
575 0     0 1   my ( $self, $user, $pass ) = @_;
576              
577 0           my $arg = ${*$self}{_FTPSSL_arguments};
  0            
578              
579 0 0 0       if ( defined $user && $user ne "" ) {
580 0           $arg->{_hide_value_in_response_} = $user;
581 0           $arg->{_mask_value_in_response_} = "++++++";
582             }
583              
584 0   0       my $logged_on = $self->_test_croak ( $self->_user ($user) &&
585             $self->_passwd ($pass) );
586              
587 0           delete ( $arg->{_hide_value_in_response_} );
588 0           delete ( $arg->{_mask_value_in_response_} );
589              
590 0 0         if ( $logged_on ) {
591             # Check if we want to supress the help logging ...
592 0           my $save = $arg->{debug};
593 0 0         if ( $arg->{debug_no_help} ) {
594 0           delete $arg->{debug};
595             }
596              
597             # So _help is always called early instead of later.
598 0           $self->supported ("HELP");
599              
600 0           $arg->{debug} = $save; # Re-enabled again!
601              
602             # Printing to the log for info purposes only.
603 0 0 0       if ( $arg->{debug} && $arg->{debug_extra} ) {
604 0           my %h = %{$self->_help ()};
  0            
605 0           foreach ( sort keys %h ) {
606 0           $h{$_} = sprintf ("%s[%s]", $_, $h{$_});
607             }
608 0           my $hlp = join ("), (", sort values %h);
609              
610 0 0         if ( $hlp eq "" ) {
611 0 0         my $msg = ( $arg->{OverrideHELP} ) ? "All" : "No";
612 0           $self->_print_LOG ("HELP: () --> $msg FTP Commands.\n");
613             } else {
614 0           $self->_print_LOG ("HELP: ($hlp)\n");
615             }
616             }
617              
618             # Check if these commands are not supported by this server after all!
619 0 0 0       if ( $arg->{FixPutTs} && ! $self->supported ("MFMT") ) {
620 0           $arg->{FixPutTs} = 0;
621             }
622 0 0 0       if ( $arg->{FixGetTs} && ! $self->supported ("MDTM") ) {
623 0           $arg->{FixGetTs} = 0;
624             }
625             }
626              
627 0           return ( $logged_on );
628             }
629              
630             #-----------------------------------------------------------------------
631              
632             sub quit {
633 0     0 1   my $self = shift;
634 0 0         $self->_quit() or return 0; # Don't do a croak here, since who tests?
635 0           _my_close ($self); # Old way $self->close();
636 0           return 1;
637             }
638              
639             #-----------------------------------------------------------------------
640              
641             sub force_epsv {
642 0     0 1   my $self = shift;
643 0   0       my $epsv_mode = shift || "1";
644              
645 0 0 0       unless ($epsv_mode eq "1" || $epsv_mode eq "2") {
646 0           return $self->croak_or_return (0, "Invalid IP Protocol Flag ($epsv_mode)");
647             }
648              
649             # Don't resend the command to the FTPS server if it was sent before!
650 0 0 0       if ( ${*$self}{_FTPSSL_arguments}->{dcsc_mode} != FTPS_EPSV_1 &&
  0            
651 0           ${*$self}{_FTPSSL_arguments}->{dcsc_mode} != FTPS_EPSV_2 ) {
652 0 0         unless ($self->command ("EPSV", "ALL")->response () == CMD_OK) {
653 0           return $self->_croak_or_return ();
654             }
655             }
656              
657             # Now that only EPSV is supported, remember which one was requested ...
658             # You can no longer swap back to PASV, PORT or EPRT.
659 0 0         ${*$self}{_FTPSSL_arguments}->{dcsc_mode} = ($epsv_mode eq "1") ? FTPS_EPSV_1 : FTPS_EPSV_2;
  0            
660              
661             # Now check out if the requested EPSV mode was actually supported ...
662 0 0         unless ($self->command ("EPSV", $epsv_mode)->response () == CMD_OK) {
663 0           return $self->_croak_or_return ();
664             }
665              
666             # So the server will release the returned port!
667 0           $self->_abort();
668              
669 0           return (1); # Success!
670             }
671              
672             sub _pasv {
673 0     0     my $self = shift;
674             # Leaving the other arguments on the stack (for use by PRET if called)
675              
676 0           my ($host, $port) = ("", "");
677              
678 0 0         if ( ${*$self}{_FTPSSL_arguments}->{Pret} ) {
  0            
679 0 0         unless ( $self->command ("PRET", @_)->response () == CMD_OK ) {
680 0           $self->_croak_or_return ();
681 0           return ($host, $port);
682             }
683             }
684              
685 0 0         unless ( $self->command ("PASV")->response () == CMD_OK ) {
686 0 0         if ( ${*$self}{_FTPSSL_arguments}->{Pret} ) {
  0 0          
687             # Prevents infinite recursion on failure if PRET is already set ...
688 0           $self->_croak_or_return ();
689              
690             } elsif ( $self->last_message () =~ m/(^|\s)PRET($|[\s.!?])/i ) {
691             # Turns PRET on for all future calls to _pasv()!
692             # Stays on even if it still doesn't work with PRET!
693 0           ${*$self}{_FTPSSL_arguments}->{Pret} = 1;
  0            
694 0           $self->_print_DBG ("<<+ Auto-adding PRET option!\n");
695 0           ($host, $port) = $self->_pasv (@_);
696              
697             } else {
698 0           $self->_croak_or_return ();
699             }
700              
701 0           return ($host, $port);
702             }
703              
704             # [227] [Entering Passive Mode] ([h1,h2,h3,h4,p1,p2]).
705 0           my $msg = $self->last_message ();
706 0 0         unless ($msg =~ m/(\d+)\s(.*)\(((\d+,?)+)\)\.?/) {
707 0           $self->_croak_or_return (0, "Can't parse the PASV response.");
708 0           return ($host, $port);
709             }
710              
711 0           my @address = split( /,/, $3 );
712              
713 0           $host = join( '.', @address[ 0 .. 3 ] );
714 0           $port = $address[4] * 256 + $address[5];
715              
716 0 0         if ( ${*$self}{_FTPSSL_arguments}->{OverridePASV} ) {
  0            
717 0           my $ip = $host;
718 0           $host = ${*$self}{_FTPSSL_arguments}->{OverridePASV};
  0            
719 0           $self->_print_DBG ( "--- Overriding PASV IP Address $ip with $host\n" );
720             }
721              
722 0           return ($host, $port);
723             }
724              
725             sub _epsv {
726 0     0     my $self = shift;
727 0           my $ipver = shift;
728              
729 0 0         $self->command ("EPSV", ($ipver == FTPS_EPSV_1) ? "1" : "2");
730 0 0         unless ( $self->response () == CMD_OK ) {
731 0           $self->_croak_or_return ();
732 0           return ("", "");
733             }
734              
735             # [227] [Entering Extended Passive Mode] (||||).
736 0           my $msg = $self->last_message ();
737 0 0         unless ($msg =~ m/[(](.)(.)(.)(\d+)(.)[)]/) {
738 0           $self->_croak_or_return (0, "Can't parse the EPSV response.");
739 0           return ("", "");
740             }
741              
742 0           my ($s1, $s2, $s3, $port, $s4) = ($1, $2, $3, $4, $5);
743              
744             # By definition, EPSV must use the same host for the DC as the CC.
745 0           return (${*$self}{_FTPSSL_arguments}->{Host}, $port);
  0            
746             }
747              
748             sub prep_data_channel {
749 0     0 0   my $self = shift;
750             # Leaving other arguments on the stack (for use by PRET if called via PASV)
751              
752             # Should only do this for encrypted Command Channels.
753 0 0         if ( ${*$self}{_FTPSSL_arguments}->{Crypt} ne CLR_CRYPT ) {
  0            
754 0           $self->_pbsz();
755 0 0         unless ($self->_prot()) { return $self->_croak_or_return (); }
  0            
756             }
757              
758             # Determine what host/port pairs to use for the data channel ...
759 0           my $mode = ${*$self}{_FTPSSL_arguments}->{dcsc_mode};
  0            
760 0           my ($host, $port);
761 0 0 0       if ( $mode == FTPS_PASV ) {
    0          
762 0           ($host, $port) = $self->_pasv (@_);
763             } elsif ( $mode == FTPS_EPSV_1 || $mode == FTPS_EPSV_2 ) {
764 0           ($host, $port) = $self->_epsv ($mode);
765             } else {
766 0 0 0       my $err = ($mode == FTPS_PORT ||
767             $mode == FTPS_EPRT_1 || $mode == FTPS_EPRT_2)
768             ? "Active FTP mode ($mode)"
769             : "Unknown FTP mode ($mode)";
770 0           return $self->_croak_or_return (0, "Currently doesn't support $err when requesting the data channel port to use!");
771             }
772              
773 0           $self->_print_DBG ("--- Host ($host) Port ($port)\n");
774              
775             # Already decided not to call croak earlier if this happens.
776 0 0 0       return (0) if ($host eq "" || $port eq "");
777              
778             # Returns if the data channel was established or not ...
779 0           return ( $self->_open_data_channel ($host, $port) );
780             }
781              
782             sub _open_data_channel {
783 0     0     my $self = shift;
784 0           my $host = shift;
785 0           my $port = shift;
786              
787             # Warning: also called by t/06-login.t func check_for_pasv_issue(),
788             # so verify still works there if any significant changes are made here.
789              
790             # We don't care about any context features here, only in _get_data_channel().
791             # You can't apply these features until after the command using the data
792             # channel has been sent to the FTPS server and the FTPS server responds
793             # to the socket you are creating below!
794              
795             # Makes it easier to refrence all those pesky values over & over again.
796 0           my $ftps_ref = ${*$self}{_FTPSSL_arguments};
  0            
797              
798 0           my $msg = "";
799 0           my %proxyArgs;
800 0 0         if (exists $ftps_ref->{myProxyArgs} ) {
801 0           %proxyArgs = %{$ftps_ref->{myProxyArgs}};
  0            
802 0   0       $msg = ($proxyArgs{'proxy-host'} || "undef") . ":" . ($proxyArgs{'proxy-port'} || "undef");
      0        
803              
804             # Update the host & port to connect to through the proxy server ...
805 0           $proxyArgs{'remote-host'} = $host;
806 0           $proxyArgs{'remote-port'} = $port;
807             }
808              
809 0           my $socket;
810              
811 0 0 0       if ( $ftps_ref->{data_prot} eq DATA_PROT_PRIVATE ) {
    0          
    0          
812 0 0         if (exists $ftps_ref->{myProxyArgs} ) {
813             # Set the proxy parameters for all future data connections ...
814             Net::SSLeay::set_proxy ( $proxyArgs{'proxy-host'}, $proxyArgs{'proxy-port'},
815 0           $proxyArgs{'proxy-user'}, $proxyArgs{'proxy-pass'} );
816 0           $msg = " (via proxy $msg)";
817             }
818              
819             # carp "MSG=($msg)\n" . "proxyhost=($Net::SSLeay::proxyhost--$Net::SSLeay::proxyport)\n" . "auth=($Net::SSLeay::proxyauth--$Net::SSLeay::CRLF)\n";
820              
821 0 0         $socket = Net::SSLeay::Handle->make_socket( $host, $port )
822             or return $self->_croak_or_return (0,
823             "Can't open private data connection to $host:$port $msg");
824              
825             } elsif ( $ftps_ref->{data_prot} eq DATA_PROT_CLEAR && exists $ftps_ref->{myProxyArgs} ) {
826 0 0         $socket = Net::HTTPTunnel->new ( %proxyArgs ) or
827             return $self->_croak_or_return (0,
828             "Can't open HTTP Proxy data connection tunnel from $msg to $host:$port");
829              
830             } elsif ( $ftps_ref->{data_prot} eq DATA_PROT_CLEAR ) {
831 0           my %socketArgs = %{$ftps_ref->{mySocketOpts}};
  0            
832 0           $socketArgs{PeerAddr} = $host;
833 0           $socketArgs{PeerPort} = $port;
834              
835 0 0         $socket = $IOCLASS->new ( %socketArgs ) or
836             # $socket = IO::Socket::INET->new( %socketArgs ) or
837             return $self->_croak_or_return (0,
838             "Can't open clear data connection to $host:$port");
839              
840             } else {
841             # TODO: Fix so DATA_PROT_SAFE & DATA_PROT_CONFIDENTIAL work.
842 0           return $self->_croak_or_return (0, "Currently doesn't support mode $ftps_ref->{data_prot} for data channels to $host:$port");
843             }
844              
845 0           $ftps_ref->{data_ch} = \*$socket; # Must call _get_data_channel() before using.
846 0           $ftps_ref->{data_host} = $host; # Save the IP Address used ...
847              
848 0           return 1; # Data Channel was established!
849             }
850              
851             sub _get_data_channel {
852 0     0     my $self = shift;
853              
854             # Makes it easier to refrence all those pesky values over & over again.
855 0           my $ftps_ref = ${*$self}{_FTPSSL_arguments};
  0            
856              
857             # $self->_debug_print_hash ("host", "port", $ftps_ref->{data_prot}, $ftps_ref->{data_ch});
858              
859 0           my $io;
860 0 0 0       if ( $ftps_ref->{data_prot} eq DATA_PROT_PRIVATE && exists ($ftps_ref->{myContext}) ) {
    0          
    0          
861 0           my %ssl_opts = %{$ftps_ref->{myContext}};
  0            
862 0           my $mode = ${*$self}{_SSL_arguments}->{SSL_version};
  0            
863              
864             # Can we use SNI?
865 0 0 0       if ( $self->can ("can_client_sni") && $self->can_client_sni () ) {
866 0           $ssl_opts{SSL_hostname} = $ftps_ref->{data_host};
867             }
868              
869 0 0         $io = IO::Socket::SSL->start_SSL ( $ftps_ref->{data_ch}, \%ssl_opts )
870             or return $self->_croak_or_return ( 0,
871             "$mode: " . IO::Socket::SSL::errstr () );
872              
873             } elsif ( $ftps_ref->{data_prot} eq DATA_PROT_PRIVATE ) {
874 0           $io = IO::Handle->new ();
875 0           tie ( *$io, "Net::SSLeay::Handle", $ftps_ref->{data_ch} );
876              
877             } elsif ( $ftps_ref->{data_prot} eq DATA_PROT_CLEAR ) {
878 0           $io = $ftps_ref->{data_ch};
879              
880             } else {
881             # TODO: Fix so DATA_PROT_SAFE & DATA_PROT_CONFIDENTIAL work.
882 0           return $self->_croak_or_return (0, "Currently doesn't support mode $ftps_ref->{data_prot} for data channels.");
883             }
884              
885 0           _my_autoflush ( $io );
886              
887             # $self->_debug_print_hash ("host", "port", $ftps_ref->{data_prot}, $io, "=");
888              
889 0           return ( $io );
890             }
891              
892             # Note: This doesn't reference $self on purpose! (so not a bug!)
893             # See Bug Id 82094
894             sub _my_autoflush {
895 0     0     my $skt = shift;
896              
897 0 0         if ( $skt->can ('autoflush') ) {
898 0           $skt->autoflush (1);
899             } else {
900             # So turn it on manually instead ...
901 0           my $oldFh = select $skt;
902 0           $| = 1;
903 0           select $oldFh;
904             }
905              
906 0           return;
907             }
908              
909             # Note: This doesn't reference $self on purpose! (so not a bug!)
910             # See Bug Id 82094
911             sub _my_close {
912 0     0     my $io = shift;
913              
914 0 0         if ( $io->can ('close') ) {
915 0           $io->close ();
916             } else {
917 0           close ($io);
918             }
919              
920 0           return;
921             }
922              
923             # The Shell wild cards are "*" & "?" only.
924             # So want to convert a shell pattern into its equivalent RegExp.
925             # Which means disabling all RegExp chars with special meaning and
926             # converting shell wild cards into its RegExp wild equivalent.
927             # Handles them all even if they are not legal in a file's name.
928             sub _convert_shell_pattern_to_regexp
929             {
930 0     0     my $self = shift;
931 0           my $pattern = shift;
932 0           my $disable_star = shift;
933              
934 0 0         if ( $pattern ) {
935             # There are 8 problem chars with special meaning in a RegExp ...
936             # Chars: . + ^ | $ \ * ?
937             # But want to drop "*" & "?" since they are shell wild cards as well.
938 0           $pattern =~ s/([.+^|\$\\])/\\$1/g; # Ex: '.' to '\.'
939              
940             # As do these 3 types of brackets: (), {}, []
941 0           $pattern =~ s/([(){}[\]])/\\$1/g;
942              
943             # Now convert the "?" into it's equivalent RegExp value ...
944 0           $pattern =~ s/[?]/./g; # All '?' to '.'.
945              
946             # Now convert the "*" into it's equivalent RegExp value ...
947 0 0         unless ( $disable_star ) {
948 0           $pattern =~ s/[*]/.*/g; # All '*' to '.*'.
949             }
950             }
951              
952 0           return ( $pattern );
953             }
954              
955              
956             sub nlst {
957 0     0 1   my $self = shift;
958 0           my $pattern = $_[1];
959              
960 0 0         if ( $pattern ) {
961 0           $pattern = $self->_convert_shell_pattern_to_regexp ( $pattern, 1 );
962              
963 0 0         if ( $pattern =~ m/[*]/ ) {
964             # Don't allow path separators in the string ...
965             # Can't do this with regular expressions ...
966 0           $pattern = join ( "[^\\\\/]*", split (/\*/, $pattern . "XXX") );
967 0           $pattern =~ s/XXX$//;
968             }
969 0           $pattern = '(^|[\\\\/])' . $pattern . '$';
970             }
971              
972 0           return ( $self->_common_list ("NLST", $pattern, @_) );
973             }
974              
975             sub list {
976 0     0 1   my $self = shift;
977 0           my $pattern = $_[1];
978              
979 0 0         if ( $pattern ) {
980 0           $pattern = $self->_convert_shell_pattern_to_regexp ( $pattern, 1 );
981              
982 0           $pattern =~ s/[*]/\\S*/g; # No spaces in file's name is allowed!
983 0           $pattern = '\s+(' . $pattern . ')($|\s+->\s+)'; # -> is symbolic link!
984             }
985              
986 0           return ( $self->_common_list ("LIST", $pattern, @_) );
987             }
988              
989             # Get List details ...
990             sub mlsd {
991 0     0 1   my $self = shift;
992 0           my $pattern = $_[1];
993              
994 0 0         if ( $pattern ) {
995 0           $pattern = $self->_convert_shell_pattern_to_regexp ( $pattern, 0 );
996 0           $pattern = '; ' . $pattern . '$';
997             }
998              
999 0           return ( $self->_common_list ("MLSD", $pattern, @_) );
1000             }
1001              
1002             # Get file details ...
1003             sub mlst {
1004 0     0 1   my $self = shift;
1005 0           my $file = shift;
1006              
1007 0           my $info;
1008 0 0         if ( $self->command ( "MLST", $file, @_ )->response () == CMD_OK ) {
1009 0           my @lines = split ("\n", $self->last_message ());
1010 0           $info = $lines[1];
1011 0           $info =~ s/^\s+//;
1012             }
1013              
1014 0           return ( $self->_test_croak ($info) );
1015             }
1016              
1017              
1018             sub parse_mlsx {
1019 0     0 1   my $self = shift;
1020 0           my $features = shift; # Fmt: tag=val;tag=val;tag=val;...; file
1021 0   0       my $lowercase = shift || 0;
1022              
1023 0           my $empty; # The return value on error ...
1024 0 0         return ( $empty ) unless ( $features );
1025              
1026 0           my ($feat_lst, $file) = split (/; /, $features, 2);
1027 0 0 0       return ( $empty ) unless ( defined $feat_lst && defined $file && $file );
      0        
1028              
1029 0 0         $feat_lst = lc ($feat_lst) if ( $lowercase );
1030              
1031             # Now that we know it parses, lets grab the data.
1032 0           my %data;
1033 0           $data{";file;"} = $file;
1034 0           foreach ( split (/;/, $feat_lst) ) {
1035 0 0         return ( $empty ) unless ( $_ =~ m/=/ ); # tag=val format?
1036              
1037 0           my ($tag, $val) = split (/=/, $_, 2);
1038 0 0         return ( $empty ) unless ( $tag ); # Missing tag?
1039              
1040 0 0         $data{$tag} = (defined $val) ? $val : "";
1041             }
1042              
1043 0           return ( \%data ); # The parse worked!
1044             }
1045              
1046              
1047             # Returns an empty array on failure ...
1048             sub _common_list {
1049 0     0     my $self = shift;
1050             # ----- The Calculated Arguments -------------------------
1051 0           my $lst_cmd = shift; # LIST, NLST, or MLSD.
1052 0   0       my $pattern = shift || ""; # The corrected pattern as a perl regular expression.
1053             # ----- The Forwarded Arguments --------------------------
1054 0   0       my $path = shift || undef; # Causes "" to be treated as "."!
1055 0   0       my $orig_ptrn = shift || undef; # Only wild cards are * and ? (same as ls cmd)
1056 0   0       my $ftype = shift || 0; # Only used for MLST!
1057              
1058 0           my $dati = "";
1059              
1060             # Open the data channel before issuing the appropriate list command ...
1061 0 0         unless ( $self->prep_data_channel( $lst_cmd ) ) {
1062 0           return (); # Already decided not to call croak if you get here!
1063             }
1064              
1065             # Run the requested list type command ...
1066 0 0         unless ( $self->command ( $lst_cmd, $path )->response () == CMD_INFO ) {
1067 0           $self->_croak_or_return ();
1068 0           return ();
1069             }
1070              
1071 0           my ( $tmp, $io, $size );
1072              
1073 0           $size = ${*$self}{_FTPSSL_arguments}->{buf_size};
  0            
1074              
1075 0           $io = $self->_get_data_channel ();
1076 0 0         unless ( defined $io ) {
1077 0           return (); # Already decided not to call croak if you get here!
1078             }
1079              
1080 0           while ( my $len = sysread $io, $tmp, $size ) {
1081 0 0         unless ( defined $len ) {
1082 0 0         next if $! == EINTR;
1083 0           my $type = lc ($lst_cmd) . "()";
1084 0           $self->_croak_or_return (0, "System read error on read while $type: $!");
1085 0           _my_close ($io); # Old way $io->close();
1086 0           return ();
1087             }
1088 0           $dati .= $tmp;
1089             }
1090              
1091 0           _my_close ($io); # Old way $io->close();
1092              
1093             # To catch the expected "226 Closing data connection."
1094 0 0         if ( $self->response() != CMD_OK ) {
1095 0           $self->_croak_or_return ();
1096 0           return ();
1097             }
1098              
1099             # Convert to use local separators ...
1100             # Required for callback functionality ...
1101 0           $dati =~ s/\015\012/\n/g;
1102              
1103             # Remove that pesky total that isn't returned from all FTPS servers.
1104             # This way we are consistant for everyone!
1105             # Another reason to strip it out is that it's the total block size,
1106             # not the total number of files. Which gets confusing.
1107             # Works no matter where the total is in the string ...
1108 0 0         if ( $lst_cmd eq "LIST" ) {
1109 0 0         $dati =~ s/^\n//s if ( $dati =~ s/^\s*total\s+\d+\s*$//mi );
1110 0           $dati =~ s/\n\n/\n/s; # In case total not 1st line ...
1111             }
1112              
1113             # What if we asked to use patterns to limit the listing returned ?
1114 0 0         if ( $pattern ) {
1115 0           $self->_print_DBG ( "MAKE PATTERN: <- $orig_ptrn => $pattern ->\n" );
1116              
1117             # Now only keep those files that match the pattern.
1118 0           $dati = $self->_apply_list_pattern ($dati, $pattern);
1119             }
1120              
1121 0   0       my $mlsd_flg =( $lst_cmd eq "MLSD" && $ftype != 0 );
1122 0 0         $dati = $self->_apply_ftype_filter ($dati, $ftype) if ($mlsd_flg);
1123              
1124 0           my $len = length ($dati);
1125 0           my $cblvl = 2; # Offset to the calling function.
1126 0           my $total = 0;
1127              
1128 0 0         if ( $len > 0 ) {
1129 0           my $cb;
1130 0           ($total, $cb) = $self->_call_callback ($cblvl, \$dati, \$len, 0);
1131 0 0         if ( $cb ) {
1132 0           $dati = $self->_apply_list_pattern ($dati, $pattern);
1133 0 0         $dati = $self->_apply_ftype_filter ($dati, $ftype) if ($mlsd_flg);
1134             }
1135             }
1136              
1137             # Process trailing call back info if present.
1138 0           my $trail;
1139 0           ($trail, $len, $total) = $self->_end_callback ($cblvl, $total);
1140 0           $trail = $self->_apply_list_pattern ($trail, $pattern);
1141 0 0         $trail = $self->_apply_ftype_filter ($$trail, $ftype) if ($mlsd_flg);
1142 0 0         if ( $trail ) {
1143 0           $dati .= $trail;
1144             }
1145              
1146 0 0         return $dati ? split( /\n/, $dati ) : ();
1147             }
1148              
1149             # Filter the results based on the given pattern ...
1150             sub _apply_list_pattern {
1151 0     0     my $self = shift;
1152 0           my $data = shift;
1153 0           my $pattern = shift;
1154              
1155 0 0 0       if ( $data && $pattern ) {
1156 0           $self->_print_DBG ( " USE PATTERN: $pattern\n" );
1157 0           my @res;
1158 0           foreach ( split ( /\n/, $data ) ) {
1159 0 0         push (@res, $_) if ( $_ =~ m/$pattern/i );
1160             }
1161 0           $data = join ("\n", @res);
1162 0 0         $data .= "\n" if ( $data );
1163             }
1164              
1165 0           return ($data);
1166             }
1167              
1168             # Filter the results based on the file type ... (MLSD only)
1169             sub _apply_ftype_filter {
1170 0     0     my $self = shift;
1171 0           my $data = shift;
1172 0           my $ftype = shift;
1173              
1174 0 0 0       if ( $data && $ftype ) {
1175 0           my @types = qw / ALL REGULAR_DIRECTORY REGULAR_FILE SPECIAL_FILE SPECIAL_DIRECTORY /;
1176 0           $self->_print_DBG ( " FILE TYPE FILTER: $types[$ftype]\n" );
1177 0           my $type_active = 0; # Assume the "type" attribute wasn't returned!
1178 0           my @res;
1179              
1180             # For each row that has a type attribute returned ...
1181 0           foreach ( split ( /\n/, $data ) ) {
1182 0 0         if ( $_ =~ m/(^|;)type=([^;]*);/i ) {
1183 0           my $t = lc ( $2 );
1184 0           $type_active = 1; # The "type" attribute was returned!
1185              
1186 0   0       my $isSpecialDir = ( $t eq "cdir" || $t eq "pdir" );
1187 0           my $isRegDir = ( $t eq "dir" );
1188 0   0       my $isDir = ( $isRegDir || $isSpecialDir );
1189 0           my $isFile = ( $t eq "file" );
1190              
1191 0 0 0       if ( $ftype == 1 && $isRegDir ) {
    0 0        
    0 0        
    0 0        
      0        
1192 0           push (@res, $_); # It's a regular directory ...
1193             } elsif ( $ftype == 2 && $isFile ) {
1194 0           push (@res, $_); # It's a regular file ...
1195             } elsif ( $ftype == 3 && (! $isDir) && (! $isFile) ) {
1196 0           push (@res, $_); # It's a special file ...
1197             } elsif ( $ftype == 4 && $isSpecialDir ) {
1198 0           push (@res, $_); # It's a special directory ...
1199             }
1200             }
1201             }
1202              
1203 0 0         unless ( $type_active ) {
1204 0           warn ("Turn on TYPE feature with OPTS before filtering on file type!\n");
1205             }
1206              
1207 0           $data = join ("\n", @res);
1208 0 0         $data .= "\n" if ( $data );
1209             }
1210              
1211 0           return ($data);
1212             }
1213              
1214             sub _get_local_file_size {
1215 0     0     my $self = shift;
1216 0           my $file_name = shift;
1217              
1218             # Return the trivial cases ...
1219 0 0         return (0) unless ( -f $file_name);
1220 0 0         return (-s $file_name) if ( ${*$self}{_FTPSSL_arguments}->{type} eq MODE_BINARY );
  0            
1221              
1222             # If we get here, we know we are transfering the file in ASCII mode ...
1223 0           my $fd;
1224 0 0         unless ( open( $fd, "< $file_name" ) ) {
1225 0           return $self->_croak_or_return(0,
1226             "Can't open file in ASCII mode! ($file_name) $!");
1227             }
1228              
1229 0           my ($len, $offset) = (0, 0);
1230 0           my $data;
1231 0   0       my $size = ${*$self}{_FTPSSL_arguments}->{buf_size} || 2048;
1232              
1233 0           while ( $len = sysread ( $fd, $data, $size ) ) {
1234             # print STDERR "Line: ($len, $data)\n";
1235 0           $data =~ s/\n/\015\012/g;
1236 0           $len = length ($data);
1237 0           $offset += $len;
1238             }
1239              
1240 0 0         unless ( defined $len ) {
1241 0 0         unless ( $! == EINTR ) {
1242 0           return $self->_croak_or_return (0,
1243             "System read error on calculating OFFSET: $!");
1244             }
1245             }
1246              
1247 0           close ($fd);
1248              
1249 0           return ($offset);
1250             }
1251              
1252             sub _get_local_file_truncate {
1253 0     0     my $self = shift;
1254 0           my $file_name = shift;
1255 0           my $offset = shift; # Value > 0.
1256              
1257 0           my $max_offset = $self->_get_local_file_size ( $file_name );
1258 0 0         return (undef) unless ( defined $offset );
1259              
1260 0 0         if ( $offset > $max_offset ) {
1261 0           return $self->_croak_or_return (0,
1262             "OFFSET ($offset) is larger than the local file ($max_offset)");
1263             }
1264              
1265             # Exactly the size of the file ...
1266 0 0         return ( $offset ) if ( $offset == $max_offset );
1267              
1268             # It's smaller & non-zero, so now we must truncate the local file ...
1269 0           my $fd;
1270 0 0         unless ( open( $fd, "+< $file_name" ) ) {
1271 0           return $self->_croak_or_return(0,
1272             "Can't open file in read/write mode! ($file_name): $!");
1273             }
1274              
1275 0           my $pos = 0;
1276 0 0         if ( ${*$self}{_FTPSSL_arguments}->{type} eq MODE_BINARY ) {
  0            
1277 0 0         unless ( binmode $fd ) {
1278 0           return $self->_croak_or_return(0, "Can't set binary mode to local file!");
1279             }
1280 0           $pos = $offset;
1281              
1282             } else {
1283             # ASCII Mode ...
1284             # For some OS, $off & $pos are always the same,
1285             # while for other OS they differ once the 1st
1286             # was hit!
1287 0           my ($len, $off) = (0, 0);
1288 0           my $data;
1289 0   0       my $size = ${*$self}{_FTPSSL_arguments}->{buf_size} || 2048;
1290              
1291 0 0         $size = $offset if ( $size > $offset );
1292              
1293 0           while ( $len = sysread ( $fd, $data, $size ) ) {
1294             # print STDERR "Line: ($len, $data)\n";
1295 0           my $cr_only = ($data eq "\n");
1296 0           $data =~ s/\n/\015\012/g;
1297 0           $off += length ($data);
1298 0           my $diff = $offset - $off;
1299              
1300             # The offset was between the \015 & \012
1301             # (Bogus for a lot of OS, so must fix offset one char smaller.)
1302 0 0 0       if ( $diff == -1 && $cr_only ) {
1303 0           my $old = $offset--;
1304 0           $self->_print_DBG ("<<+ 222 HOT FIX ==> Offset ($old ==> $offset) ",
1305             "Since can't truncate between \\015 & \\012 ",
1306             "in ASCII mode!\n");
1307             # Use the last $pos value, no need to recalculate it ...
1308 0           last;
1309             }
1310              
1311             # Found the requested offset ...
1312 0 0         if ( $diff == 0 ) {
1313 0           $pos = sysseek ( $fd, 0, 1 ); # Current position in the file
1314 0           last;
1315             }
1316              
1317             # Still more data to read ...
1318 0 0         if ( $diff > 0 ) {
1319 0           $pos = sysseek ( $fd, 0, 1 ); # Current position in the file
1320 0 0         $size = $diff if ( $size > $diff );
1321              
1322             # Read past my offset value ... So re-read the last line again
1323             # with a smaller buffer size!
1324             } else {
1325 0           $pos = sysseek ( $fd, $pos, 0 ); # The previous position in the file
1326 0           $off -= length ($data);
1327 0           $size += $diff; # Diff is negative here ...
1328             }
1329              
1330 0 0         last unless ($pos);
1331             } # End while ...
1332              
1333 0 0         unless ( defined $len ) {
1334 0 0         unless ( $! == EINTR ) {
1335 0           return $self->_croak_or_return (0,
1336             "System read error on calculating OFFSET: $!");
1337             }
1338             }
1339             } # End else ASCII ...
1340              
1341 0 0         unless ($pos) {
1342 0           return $self->_croak_or_return (0,
1343             "System seek error before Truncation: $!");
1344             }
1345              
1346 0 0         unless ( truncate ( $fd, $pos ) ) {
1347 0           return $self->_croak_or_return (0, "Truncate File Error: $!");
1348             }
1349              
1350 0           close ( $fd );
1351              
1352 0           return ( $offset );
1353             }
1354              
1355             sub get {
1356 0     0 1   my $self = shift;
1357 0           my $file_rem = shift;
1358 0           my $file_loc = shift;
1359 0   0       my $offset = shift || ${*$self}{_FTPSSL_arguments}->{net_ftpssl_rest_offset} || 0;
1360              
1361             # Clear out this messy restart() cluge for next time ...
1362 0           delete ( ${*$self}{_FTPSSL_arguments}->{net_ftpssl_rest_offset} );
  0            
1363              
1364 0 0         if ( $offset < -1 ) {
1365 0           return $self->_croak_or_return(0, "Invalid file offset ($offset)!");
1366             }
1367              
1368 0           my ( $size, $localfd );
1369 0           my $close_file = 0;
1370              
1371 0 0         unless ($file_loc) {
1372 0           $file_loc = basename($file_rem);
1373             }
1374              
1375 0   0       $size = ${*$self}{_FTPSSL_arguments}->{buf_size} || 2048;
1376              
1377 0 0         if ( $self->_isa_glob ($file_loc) ) {
1378 0 0         if ( $offset == -1 ) {
1379 0           return $self->_croak_or_return(0,
1380             "Invalid file offset ($offset) for a file handle!");
1381             }
1382 0           $localfd = \*$file_loc;
1383              
1384             } else {
1385             # Calculate the file offset to send to the FTPS server via REST ...
1386 0 0         if ($offset == -1) {
    0          
1387 0           $offset = $self->_get_local_file_size ($file_loc);
1388 0 0         return (undef) unless (defined $offset);
1389             } elsif ($offset) {
1390 0           $offset = $self->_get_local_file_truncate ($file_loc, $offset);
1391 0 0         return (undef) unless (defined $offset);
1392             }
1393              
1394             # Now we can open the file we need to write to ...
1395 0 0         my $mode = ($offset) ? ">>" : ">";
1396 0 0         unless ( open( $localfd, "$mode $file_loc" ) ) {
1397 0           return $self->_croak_or_return(0,
1398             "Can't create/open local file! ($mode $file_loc)");
1399             }
1400 0           $close_file = 1;
1401             }
1402              
1403 0           my $fix_cr_issue = 1;
1404 0 0         if ( ${*$self}{_FTPSSL_arguments}->{type} eq MODE_BINARY ) {
  0            
1405 0 0         unless ( binmode $localfd ) {
1406 0 0         if ( $close_file ) {
1407 0           close ($localfd);
1408 0 0         unlink ($file_loc) unless ($offset);
1409             }
1410 0           return $self->_croak_or_return(0, "Can't set binary mode to local file!");
1411             }
1412 0           $fix_cr_issue = 0;
1413             }
1414              
1415 0 0         unless ( $self->prep_data_channel( "RETR", $file_rem ) ) {
1416 0 0         if ( $close_file ) {
1417 0           close ($localfd);
1418 0 0         unlink ($file_loc) unless ($offset);
1419             }
1420 0           return undef; # Already decided not to call croak if you get here!
1421             }
1422              
1423             # "(caller(1))[3]" returns undef if not called by another Net::FTPSSL method!
1424 0           my $c = (caller(1))[3];
1425 0 0 0       my $cb_idx = ( defined $c && $c eq "Net::FTPSSL::xget" ) ? 2 : 1;
1426 0 0         my $func = ( $cb_idx == 1 ) ? "get" : "xget";
1427              
1428              
1429             # Check if the "get" failed ...
1430 0 0         my $rest = ($offset) ? $self->_rest ($offset) : 1;
1431 0 0 0       unless ( $rest && $self->_retr($file_rem) ) {
1432 0 0         if ($close_file) {
1433 0           close ($localfd);
1434 0 0         unlink ($file_loc) unless ($offset);
1435             }
1436              
1437 0 0 0       if ( $offset && $rest ) {
1438 0           my $msg = $self->last_message ();
1439 0           $self->_rest (0); # Must clear out on failure!
1440 0           ${*$self}{_FTPSSL_arguments}->{last_ftp_msg} = $msg; # Restore original error message!
  0            
1441             }
1442              
1443 0           return $self->_croak_or_return ();
1444             }
1445              
1446 0           my ( $data, $written, $io );
1447              
1448 0           $io = $self->_get_data_channel ();
1449 0 0         unless ( defined $io ) {
1450 0 0         if ( $close_file ) {
1451 0           close ($localfd);
1452 0 0         unlink ($file_loc) unless ($offset);
1453             }
1454 0           return undef; # Already decided not to call croak if you get here!
1455             }
1456              
1457 0           my $trace_flag = ${*$self}{_FTPSSL_arguments}->{trace};
  0            
1458 0 0         print STDERR "$func() trace ." if ($trace_flag);
1459 0           my $cnt = 0;
1460 0           my $prev = "";
1461 0           my $total = 0;
1462 0           my $len;
1463              
1464 0           while ( ( $len = sysread $io, $data, $size ) ) {
1465 0 0         unless ( defined $len ) {
1466 0 0         next if $! == EINTR;
1467 0 0         close ($localfd) if ( $close_file );
1468 0           return $self->_croak_or_return (0, "System read error on $func(): $!");
1469             }
1470              
1471 0 0         if ( $fix_cr_issue ) {
1472             # What if the line only contained \015 ? (^M)
1473 0 0         if ( $data eq "\015" ) {
1474 0           $prev .= "\015";
1475 0           next;
1476             }
1477              
1478             # What if this line was truncated? (Ends with \015 instead of \015\012)
1479             # Can't test with reg expr since m/(\015)$/s & m/(\015\012)$/s same!
1480             # Don't care if it was truncated anywhere else!
1481 0           my $last_char = substr ($data, -1);
1482 0 0         if ( $last_char eq "\015" ) {
    0          
1483 0           $data =~ s/^(.+).$/$prev$1/s;
1484 0           $prev = $last_char;
1485             }
1486              
1487             # What if the previous line was truncated? But not this one.
1488             elsif ( $prev ne "" ) {
1489 0           $data = $prev . $data;
1490 0           $prev = "";
1491             }
1492              
1493 0           $data =~ s/\015\012/\n/g;
1494 0           $len = length ($data);
1495             }
1496              
1497 0 0 0       print STDERR "." if ($trace_flag && ($cnt % TRACE_MOD) == 0);
1498 0           ++$cnt;
1499              
1500 0           $total = $self->_call_callback ($cb_idx, \$data, \$len, $total);
1501              
1502 0 0         if ( $len > 0 ) {
1503 0           $written = syswrite $localfd, $data, $len;
1504 0 0         return $self->_croak_or_return (0, "System write error on $func(): $!")
1505             unless (defined $written);
1506             }
1507             }
1508              
1509             # Potentially write a last ASCII char to the file ...
1510 0 0         if ($prev ne "") {
1511 0           $len = length ($prev);
1512 0           $total = $self->_call_callback ($cb_idx, \$prev, \$len, $total);
1513 0 0         if ( $len > 0 ) {
1514 0           $written = syswrite $localfd, $prev, $len;
1515 0 0         return $self->_croak_or_return (0, "System write error on $func(prev): $!")
1516             unless (defined $written);
1517             }
1518             }
1519              
1520             # Process trailing "callback" info if returned.
1521 0           my $trail;
1522 0           ($trail, $len, $total) = $self->_end_callback ($cb_idx, $total);
1523 0 0         if ( $trail ) {
1524 0           $written = syswrite $localfd, $trail, $len;
1525 0 0         return $self->_croak_or_return (0, "System write error on $func(trail): $!")
1526             unless (defined $written);
1527             }
1528              
1529 0 0         print STDERR ". done! (" . $self->_fmt_num ($total) . " byte(s))\n" if ($trace_flag);
1530              
1531 0           _my_close ($io); # Old way $io->close();
1532              
1533             # To catch the expected "226 Closing data connection."
1534 0 0         if ( $self->response() != CMD_OK ) {
1535 0 0         close ($localfd) if ( $close_file );
1536 0           return $self->_croak_or_return ();
1537             }
1538              
1539 0 0         if ( $close_file ) {
1540 0           close ($localfd);
1541 0 0         if ( ${*$self}{_FTPSSL_arguments}->{FixGetTs} ) {
  0            
1542 0           my $tm = $self->_mdtm ( $file_rem );
1543 0 0         utime ( $tm, $tm, $file_loc ) if ( $tm );
1544             }
1545             }
1546              
1547 0           return 1;
1548             }
1549              
1550              
1551             sub put { # Regular put (STOR command)
1552 0     0 1   my $self = shift;
1553 0           my ($resp, $msg1, $msg2, $requested_file_name, $tm) = $self->_common_put (@_);
1554              
1555 0 0 0       if ( $resp && ${*$self}{_FTPSSL_arguments}->{FixPutTs} && defined $tm ) {
  0   0        
1556 0           $self->_mfmt ($tm, $requested_file_name);
1557             }
1558              
1559 0           return ( $resp );
1560             }
1561              
1562             sub append { # Append put (APPE command)
1563 0     0 1   my $self = shift;
1564 0           my ($resp, $msg1, $msg2, $requested_file_name, $tm) = $self->_common_put (@_);
1565              
1566 0 0 0       if ( $resp && ${*$self}{_FTPSSL_arguments}->{FixPutTs} && defined $tm ) {
  0   0        
1567 0           $self->_mfmt ($tm, $requested_file_name);
1568             }
1569              
1570 0           return ( $resp );
1571             }
1572              
1573             sub uput { # Unique put (STOU command)
1574 0     0 1   my $self = shift;
1575 0           my ($resp, $msg1, $msg2, $requested_file_name, $tm) = $self->_common_put (@_);
1576              
1577             # Now lets get the real name of the file uploaded!
1578 0 0         if ( $resp ) {
1579             # The file name may appear in either message returned. (The 150 or 226 msg)
1580             # So lets check both messages merged together!
1581 0           my $msg = $msg1 . "\n" . $msg2;
1582              
1583             # -------------------------------------------------------
1584             # *** Assumes no spaces are in the new file's name! ***
1585             # -------------------------------------------------------
1586 0 0         if ( $msg =~ m/(FILE|name):\s*([^\s)]+)($|[\s)])/im ) {
    0          
    0          
1587 0           $requested_file_name = $2; # We found an actual name to use ...
1588              
1589             } elsif ( $msg =~ m/Transfer starting for\s+([^\s]+)($|\s)/im ) {
1590 0           $requested_file_name = $1; # We found an actual name to use ...
1591 0           $requested_file_name =~ s/[.]$//; # Remove optional trailing ".".
1592              
1593 0           } elsif ( ${*$self}{_FTPSSL_arguments}->{uput} == 1 ) {
1594             # The alternate STOU command format was used where the remote
1595             # ftps server won't allow us to recomend any hints!
1596             # So we don't know what the remote server used for a filename
1597             # if it didn't appear in either of the message formats!
1598 0           $requested_file_name = "?";
1599              
1600             } else {
1601 0           $tm = undef; # Disable's PreserveTimestamp if using default guess ...
1602             }
1603              
1604             # TODO: Figure out other uput variants to check for besides the ones above.
1605              
1606             # Until then, if we can't find the file name used in the messages,
1607             # we'll just have to assume that the default file name was used if
1608             # we were not explicitly told it wasn't being used!
1609              
1610 0 0         if ( $requested_file_name ne "?" ) {
1611             # Now lets update the timestamp for that file on the server ...
1612             # It's allowed to fail since we are not 100% sure of the remote name used!
1613 0 0 0       if ( ${*$self}{_FTPSSL_arguments}->{FixPutTs} && defined $tm ) {
  0            
1614 0           $self->_mfmt ($tm, $requested_file_name);
1615             }
1616              
1617             # Fix done in v0.25
1618             # Some servers returned the full path to the file. But that sometimes
1619             # causes issues. So always strip off the path information.
1620 0           $requested_file_name = basename ($requested_file_name);
1621             }
1622              
1623 0           return ( $requested_file_name );
1624             }
1625              
1626 0           return ( undef ); # Fatal error & Croak is turned off.
1627             }
1628              
1629              
1630             sub uput2 { # Unique put (STOU command)
1631 0     0 1   my $self = shift;
1632 0           my $file_loc = $_[0];
1633              
1634 0           my %before;
1635 0           foreach ( $self->nlst () ) { $before{$_} = 1; }
  0            
1636 0 0         return (undef) if ($self->last_status_code () != CMD_OK);
1637              
1638 0           my $found_file;
1639             {
1640             # Temporarily disable timestamps ...
1641 0           local ${*$self}{_FTPSSL_arguments}->{FixPutTs} = 0;
  0            
  0            
1642 0           $found_file = $self->put (@_);
1643             }
1644              
1645 0 0         if ( defined $found_file ) {
1646 0           my @after;
1647 0           foreach ( $self->nlst () ) {
1648 0 0         push ( @after, $_ ) unless ( $before{$_} );
1649             }
1650              
1651             # Did we find only one possible answer?
1652 0           my $cnt = @after;
1653 0 0         if ( $cnt == 1 ) {
1654 0           $found_file = $after[0]; # Yes!
1655             } else {
1656 0           $found_file = $self->_croak_or_return ("?", "Can't determine what the file was called. Found '${cnt}' candidates!");
1657             }
1658              
1659             # Do we update the timestamp on the uploaded file ?
1660 0 0 0       if ( $cnt == 1 &&
      0        
1661 0           ${*$self}{_FTPSSL_arguments}->{FixPutTs} &&
1662             ! $self->_isa_glob ($file_loc) ) {
1663 0           my $tm = (stat ($file_loc))[9]; # Get's the local file's timestamp!
1664 0           $self->_mfmt ($tm, $found_file);
1665             }
1666             }
1667              
1668 0           return ( $found_file );
1669             }
1670              
1671              
1672             # Makes sure the scratch file name generated appears in the same directory as
1673             # the real file unless you provide a prefix with a directory as part of it.
1674             sub _get_scratch_file {
1675 0     0     my $self = shift;
1676 0           my $prefix = shift; # May include a path
1677 0           my $body = shift;
1678 0           my $postfix = shift;
1679 0           my $file = shift; # The final file name to use (may include a path)
1680              
1681             # So we don't override "", which is OK for these 2 parts.
1682 0 0         $prefix = "_tmp." unless ( defined $prefix );
1683 0 0         $postfix = ".tmp" unless ( defined $postfix );
1684              
1685             # Determine if we need to parse by OS or FTP path rules ... (get vs put)
1686             # And get default body to use if none was supplied or it's ""!
1687 0           my $c = (caller(1))[3];
1688 0           my $os;
1689 0 0 0       if ( defined $c &&
      0        
1690             ( $c eq "Net::FTPSSL::xput" || $c eq "Net::FTPSSL::xtransfer" ) ) {
1691 0           $os = fileparse_set_fstype ("FTP"); # Follow Unix instead of OS rules.
1692             # Client Name + process PID ... Unique on remote server ...
1693 0   0       $body = $body || (hostname () . ".$$");
1694             } else {
1695 0           $os = fileparse_set_fstype (); # Follow local OS rules.
1696             # reverse(Client Name) + process PID ... Unique on local server ...
1697 0   0       $body = $body || (reverse (hostname ()) . ".$$");
1698             }
1699              
1700             # Makes sure the scratch file and the final file will appear in the same
1701             # directory unless the user overrides the directory as part of the prefix!
1702 0           my ($base, $dir, $type) = fileparse ($file);
1703 0 0         if ( $base ne $file ) {
1704             # The file is not in the current direcory ...
1705 0           my ($pbase, $pdir, $ptype) = fileparse ($prefix);
1706 0 0         if ( $pbase eq $prefix ) {
1707             # Prefix has no path, so put it in the file's directory!
1708 0           $prefix = $dir . $prefix;
1709             }
1710             }
1711              
1712             # Return to the previously remembered OS rules again! (Avoids side affects!)
1713 0           fileparse_set_fstype ($os);
1714              
1715 0           my $scratch_name = $prefix . $body . $postfix;
1716              
1717 0 0         if ( $scratch_name eq $file ) {
1718 0           return $self->_croak_or_return (0, "The scratch name and final name are the same! ($file) It's required that they must be different!" );
1719             }
1720              
1721 0           return ( $scratch_name );
1722             }
1723              
1724             sub xput { # A variant of the regular put (STOR command)
1725 0     0 1   my $self = shift;
1726 0           my $file_loc = shift;
1727 0           my $file_rem = shift;
1728              
1729             # See _get_scratch_file() for the default values if undef!
1730 0           my ($prefix, $postfix, $body) = (shift, shift, shift);
1731              
1732 0 0         unless ($file_rem) {
1733 0 0         if ( $self->_isa_glob ($file_loc) ) {
1734 0           return $self->_croak_or_return (0, "When you pass a stream, you must specify the remote filename.");
1735             }
1736              
1737 0           $file_rem = basename ($file_loc);
1738             }
1739              
1740 0           my $scratch_name = $self->_get_scratch_file ($prefix, $body, $postfix,
1741             $file_rem);
1742 0 0         return undef unless ($scratch_name);
1743              
1744 0 0         unless ( $self->all_supported ( "STOR", "DELE", "RNFR", "RNTO" ) ) {
1745 0           return $self->_croak_or_return (0, "Function xput is not supported by this server.");
1746             }
1747              
1748             # Now lets send the file. Make sure we can't die during this process ...
1749 0           my $die = ${*$self}{_FTPSSL_arguments}->{Croak};
  0            
1750 0           ${*$self}{_FTPSSL_arguments}->{Croak} = 0;
  0            
1751              
1752 0           my ($resp, $msg1, $msg2, $requested_file_name, $tm) =
1753             $self->_common_put ($file_loc, $scratch_name);
1754              
1755 0 0         if ( $resp ) {
1756 0           $self->_xWait (); # Some servers require a wait before you may move on!
1757              
1758             # Delete any file sitting on the server with the final name we want to use
1759             # to avoid file permission issues. Usually the file won't exist so the
1760             # delete will fail ...
1761 0           $self->delete ( $file_rem );
1762              
1763             # Now lets make it visible to the file recognizer ...
1764 0           $resp = $self->rename ( $requested_file_name, $file_rem );
1765              
1766             # Now lets update the timestamp for the file on the server ...
1767             # It's not an error if the file recognizer grabs it before the
1768             # timestamp is reset ...
1769 0 0 0       if ( $resp && ${*$self}{_FTPSSL_arguments}->{FixPutTs} && defined $tm ) {
  0   0        
1770 0           $self->_mfmt ($tm, $file_rem);
1771             }
1772             }
1773              
1774             # Delete the scratch file on error, but don't return this as the error msg.
1775             # We want the actual error encounterd from the put or rename commands!
1776 0 0         unless ($resp) {
1777 0           $msg1 = ${*$self}{_FTPSSL_arguments}->{last_ftp_msg};
  0            
1778 0           $self->delete ( $scratch_name );
1779 0           ${*$self}{_FTPSSL_arguments}->{last_ftp_msg} = $msg1;
  0            
1780             }
1781              
1782             # Now allow us to die again if we must ...
1783 0           ${*$self}{_FTPSSL_arguments}->{Croak} = $die;
  0            
1784              
1785 0           return ( $self->_test_croak ( $resp ) );
1786             }
1787              
1788             sub xget { # A variant of the regular get (RETR command)
1789 0     0 1   my $self = shift;
1790 0           my $file_rem = shift;
1791 0           my $file_loc = shift;
1792              
1793             # See _get_scratch_file() for the default values if undef!
1794 0           my ($prefix, $postfix, $body) = (shift, shift, shift);
1795              
1796 0 0         unless ( $file_loc ) {
1797 0           $file_loc = basename ($file_rem);
1798             }
1799              
1800 0 0         if ( $self->_isa_glob ($file_loc) ) {
1801 0           return $self->_croak_or_return (0, "xget doesn't support file_loc being an open file handle.");
1802             }
1803              
1804 0           my $scratch_name = $self->_get_scratch_file ( $prefix, $body, $postfix,
1805             $file_loc );
1806 0 0         return undef unless ($scratch_name);
1807              
1808 0 0         if (defined ${*$self}{_FTPSSL_arguments}->{net_ftpssl_rest_offset}) {
  0            
1809 0           return $self->_croak_or_return (0, "Can't call restart() before xget()!");
1810             }
1811              
1812             # In this case, we can die if we must, no required post work here ...
1813 0           my $resp = $self->get ( $file_rem, $scratch_name, undef );
1814              
1815             # Make it visisble to the local file recognizer on success ...
1816 0 0         if ( $resp ) {
1817 0           $self->_print_DBG ( "<<+ renamed $scratch_name to $file_loc\n" );
1818 0           unlink ( $file_loc ); # To avoid potential permission issues ...
1819 0 0         move ( $scratch_name, $file_loc ) or
1820             return $self->_croak_or_return (0, "Can't rename the local scratch file!");
1821             }
1822              
1823 0           return ( $self->_test_croak ( $resp ) );
1824             }
1825              
1826             # Doesn't do the CF/LF transformation.
1827             # It lets the source & dest servers do it if it's necessary!
1828             # Please note that $self & $dest_ftp will write to different log files!
1829             sub transfer {
1830 0     0 1   my $self = shift;
1831 0           my $dest_ftp = shift; # A Net::FTPSSL object.
1832 0   0       my $remote_file = shift || "";
1833 0   0       my $dest_file = shift || $remote_file;
1834 0   0       my $offset = shift || ${*$self}{_FTPSSL_arguments}->{net_ftpssl_rest_offset} || 0;
1835              
1836             # Verify we are dealing with a Net::FTPSSL object ...
1837 0 0 0       if ( ref($dest_ftp) eq "" || ref($dest_ftp) ne __PACKAGE__ ) {
1838 0           return $self->_croak_or_return(0, "The destination server must be a valid Net::FTPSSL object! (" . ref($dest_ftp) . ")");
1839             }
1840              
1841 0           my $sArg = ${*$self}{_FTPSSL_arguments};
  0            
1842 0           my $dArg = ${*$dest_ftp}{_FTPSSL_arguments};
  0            
1843              
1844             # Clear out this messy restart() cluge for next time ...
1845 0           delete ( $sArg->{net_ftpssl_rest_offset} );
1846              
1847             # Don't care if this value was set or not. Just remove it!
1848             # We just use any offset from ${*$self} instead ...
1849 0           delete ( $dArg->{net_ftpssl_rest_offset} );
1850              
1851 0   0       my ($stmp, $dtmp) = ($sArg->{Croak} || 0, $dArg->{Croak} || 0);
      0        
1852 0 0         if ( $stmp != $dtmp ) {
1853 0           my $msg = "Both connections must use the same Croak Settings for the transfer!";
1854 0           $msg .= " (${stmp} vs ${dtmp})";
1855 0           $dest_ftp->_print_DBG ("<<+ 555 $msg\n");
1856 0           return $self->_croak_or_return (0, $msg);
1857             }
1858              
1859 0           ($stmp, $dtmp) = ($sArg->{type}, $dArg->{type});
1860 0 0         if ( $stmp ne $dtmp ) {
1861 0           my $msg = "Both connections must use ASCII or BIN for the transfer!";
1862 0           $msg .= " (${stmp} vs ${dtmp})";
1863 0           $dest_ftp->_print_DBG ("<<+ 555 $msg\n");
1864 0           return $self->_croak_or_return(0, $msg);
1865             }
1866              
1867 0   0       my $size = $sArg->{buf_size} || 2048;
1868              
1869             # Validate the remaining arguments ...
1870 0 0 0       if ( ref($remote_file) || $remote_file eq "" ) {
1871 0           return $self->_croak_or_return(0, "The remote file must be a file name!");
1872             }
1873 0 0 0       if ( ref($dest_file) || $dest_file eq "" ) {
1874 0           return $self->_croak_or_return(0, "The destination file must be a file name!");
1875             }
1876 0 0         if ( $offset < -1 ) {
1877 0           return $self->_croak_or_return(0, "Invalid file offset ($offset)!");
1878             }
1879              
1880             # "(caller(1))[3]" returns undef if not called by another Net::FTPSSL method!
1881 0           my $c = (caller(1))[3];
1882 0 0 0       my $cb_idx = ( defined $c && $c eq "Net::FTPSSL::xtransfer" ) ? 2 : 1;
1883 0 0         my $func = ( $cb_idx == 1 ) ? "transfer" : "xtransfer";
1884 0 0         my $func2 = ( $cb_idx == 1 ) ? "Transfer" : "xTransfer";
1885              
1886 0           $self->_print_DBG ( "+++ Starting $func2 Between Servers +++\n");
1887 0           $dest_ftp->_print_DBG ( "--- Starting $func2 Between Servers ---\n");
1888              
1889             # Calculate the file offset to send to the FTPS source server via REST ...
1890 0 0         if ($offset == -1) {
1891 0           $offset = $dest_ftp->size ($dest_file);
1892 0 0         return (undef) unless (defined $offset);
1893             }
1894              
1895             # -------------------------------------------------
1896             # Set up the transfer destination server ... (put)
1897             # -------------------------------------------------
1898 0 0         return (undef) unless ( $dest_ftp->prep_data_channel ("STOR", $dest_file) );
1899 0 0         my $restart = ($offset) ? $dest_ftp->_rest ($offset) : 1;
1900 0           my $response = $dest_ftp->_stor ($dest_file);
1901 0 0 0       unless ($restart && $response) {
1902 0 0 0       $dest_ftp->_rest (0) if ($restart && $offset);
1903 0           return ($dest_ftp->_croak_or_return (), undef, undef, $dest_file, undef);
1904             }
1905             # my $put_msg = $dest_ftp->last_message ();
1906 0           my $dio = $dest_ftp->_get_data_channel ();
1907 0 0         return (undef) unless (defined $dio);
1908              
1909             # -------------------------------------------------
1910             # Set up the transfer source server ... (get)
1911             # -------------------------------------------------
1912 0 0         unless ( $self->prep_data_channel( "RETR", $remote_file ) ) {
1913 0           _my_close ($dio);
1914 0           $dest_ftp->response ();
1915 0           return (undef); # Already decided not to call croak if you get here!
1916             }
1917 0 0         my $rest = ($offset) ? $self->_rest ($offset) : 1;
1918 0 0 0       unless ( $rest && $self->_retr ($remote_file) ) {
1919 0 0 0       if ( $offset && $rest ) {
1920 0           my $msg = $self->last_message ();
1921 0           $self->_rest (0); # Must clear out on failure!
1922 0           $sArg->{last_ftp_msg} = $msg; # Restore original error message!
1923             }
1924 0           _my_close ($dio);
1925 0           $dest_ftp->response ();
1926 0           return ($self->_croak_or_return ());
1927             }
1928              
1929 0           my $sio = $self->_get_data_channel ();
1930 0 0         unless (defined $sio) {
1931 0           _my_close ($dio);
1932 0           $dest_ftp->response ();
1933             return (undef)
1934 0           }
1935              
1936 0           my $trace_flag = $sArg->{trace};
1937 0 0         print STDERR "$func() trace ." if ($trace_flag);
1938              
1939 0           my ($cnt, $total, $len) = (0, 0, 0);
1940 0           my $data;
1941             my $written;
1942              
1943             # So simple without CR/LF transformations ...
1944 0           while ( $len = sysread ($sio, $data, $size) ) {
1945 0 0         unless ( defined $len ) {
1946 0 0         next if ( $! == EINTR );
1947 0           _my_close ($dio);
1948 0           $dest_ftp->response ();
1949 0           return $self->_croak_or_return (0, "System read error on $func(): $!");
1950             }
1951              
1952 0 0 0       print STDERR "." if ($trace_flag && ($cnt % TRACE_MOD) == 0);
1953 0           ++$cnt;
1954              
1955 0           $total = $self->_call_callback ($cb_idx, \$data, \$len, $total);
1956              
1957             # Write to the destination server ...
1958 0 0         if ($len > 0) {
1959 0           $written = syswrite ($dio, $data, $len);
1960 0 0         unless (defined $written) {
1961 0           _my_close ($sio);
1962 0           $self->response ();
1963 0           return ($dest_ftp->_croak_or_return (0, "System write error on $func(): $!"));
1964             }
1965             }
1966             } # End while reading from the source server ...
1967              
1968              
1969             # Process trailing "callback" info if returned.
1970 0           my $trail;
1971 0           ($trail, $len, $total) = $self->_end_callback ($cb_idx, $total);
1972              
1973             # Write to the destination server ...
1974 0 0 0       if ($trail && $len > 0) {
1975 0           $written = syswrite ($dio, $trail, $len);
1976 0 0         unless (defined $written) {
1977 0           _my_close ($sio);
1978 0           $self->response ();
1979 0           return ($dest_ftp->_croak_or_return (0, "System write error on $func(): $!"));
1980             }
1981             }
1982              
1983 0 0         print STDERR ". done!", $self->_fmt_num ($total) . " byte(s)\n" if ($trace_flag);
1984              
1985             # Lets finish off both connections ...
1986 0           _my_close ($sio);
1987 0           _my_close ($dio);
1988 0           my $resp1 = $self->response ();
1989 0           my $resp2 = $dest_ftp->response ();
1990              
1991 0 0 0       if ($resp1 != CMD_OK || $resp2 != CMD_OK) {
1992 0           return ($self->_croak_or_return ());
1993             }
1994              
1995             # Preserve the timestamp on the transfered file ...
1996 0 0 0       if ($cb_idx == 1 && $sArg->{FixGetTs} && $dArg->{FixPutTs}) {
      0        
1997 0           my $tm = $self->_mdtm ($remote_file);
1998 0           $dest_ftp->_mfmt ($tm, $dest_file);
1999             }
2000              
2001 0           $self->_print_DBG ( "+++ $func2 Between Servers Completed +++\n");
2002 0           $dest_ftp->_print_DBG ( "--- $func2 Between Servers Completed ---\n");
2003              
2004 0           return (1);
2005             }
2006              
2007             sub xtransfer {
2008 0     0 1   my $self = shift;
2009 0           my $dest_ftp = shift; # A Net::FTPSSL object.
2010 0   0       my $remote_file = shift || "";
2011 0   0       my $dest_file = shift || $remote_file;
2012              
2013             # See _get_scratch_file() for default valuies if undef!
2014 0           my ($prefix, $postfix, $body) = (shift, shift, shift);
2015              
2016 0 0 0       if ( ref($dest_ftp) eq "" || ref($dest_ftp) ne __PACKAGE__ ) {
2017 0           return $self->_croak_or_return(0, "The destination server must be a valid Net::FTPSSL object! (" . ref($dest_ftp) . ")");
2018             }
2019              
2020 0           my $sArg = ${*$self}{_FTPSSL_arguments};
  0            
2021 0           my $dArg = ${*$dest_ftp}{_FTPSSL_arguments};
  0            
2022              
2023 0 0         if (defined $sArg->{net_ftpssl_rest_offset}) {
2024 0           return $self->_croak_or_return (0, "Can't call restart() before xtransfer()!");
2025             }
2026 0 0         if (defined $dArg->{net_ftpssl_rest_offset}) {
2027 0           return $dest_ftp->_croak_or_return (0, "Can't call restart() before xtransfer()!");
2028             }
2029              
2030 0 0         if ( $self->_isa_glob ($remote_file) ) {
2031 0           return $self->_croak_or_return (0, "xtransfer doesn't support REMOTE_FILE being an open file handle.");
2032             }
2033 0 0         if ( $self->_isa_glob ($dest_file) ) {
2034 0           return $self->_croak_or_return (0, "xtransfer doesn't support DEST_FILE being an open file handle.");
2035             }
2036              
2037             # Check if allowed on the destination server ...
2038 0 0         unless ( $dest_ftp->all_supported ( "STOR", "DELE", "RNFR", "RNTO" ) ) {
2039 0           return $dest_ftp->_croak_or_return (0, "Function xtransfer is not supported by the destination server.");
2040             }
2041              
2042 0           my $scratch_name = $self->_get_scratch_file ( $prefix, $body, $postfix,
2043             $dest_file );
2044 0 0         return undef unless ($scratch_name);
2045              
2046             # Save the current die settings for both servers ...
2047 0   0       my ($sdie, $ddie) = ($sArg->{Croak} || 0, $dArg->{Croak} || 0);
      0        
2048 0 0         if ( $sdie != $ddie ) {
2049 0           return $self->_croak_or_return (0, "xtransfer requires the Croak setting to be the same on both servers (${sdie} vs ${ddie})");
2050             }
2051              
2052             # Disable calling "die" on errors ... (save the current Croak setting again)
2053 0           ($sdie, $ddie) = ($sArg->{Croak}, $dArg->{Croak});
2054 0           (${*$self}{_FTPSSL_arguments}->{Croak}, ${*$dest_ftp}{_FTPSSL_arguments}->{Croak}) = (0, 0);
  0            
  0            
2055              
2056             # Now lets send the file, we can no longer die during this process ...
2057 0           my $resp = $self->transfer ($dest_ftp, $remote_file, $scratch_name, undef);
2058              
2059 0 0         if ( $resp ) {
2060 0           $dest_ftp->_xWait (); # Some servers require a wait before moving on!
2061              
2062             # Delete any file sitting on the server with the final name we want to use
2063             # to avoid file permission issues. Usually the file won't exist so the
2064             # delete will fail ...
2065 0           $dest_ftp->delete ( $dest_file );
2066              
2067             # Now lets make it visible to the file recognizer ...
2068 0           $resp = $dest_ftp->rename ( $scratch_name, $dest_file );
2069              
2070             # Now lets update the timestamp for the file on the dest server ...
2071             # It's not an error if the file recognizer grabs it before the
2072             # timestamp is reset ...
2073 0 0 0       if ($resp && $sArg->{FixGetTs} && $dArg->{FixPutTs}) {
      0        
2074 0           my $tm = $self->_mdtm ($remote_file);
2075 0           $dest_ftp->_mfmt ($tm, $dest_file);
2076             }
2077             }
2078              
2079             # Delete the scratch file on error, but don't return this as the error msg.
2080             # We want the actual error encounterd from the put or rename commands!
2081 0 0         unless ($resp) {
2082 0           my $msg1 = $dArg->{last_ftp_msg};
2083 0           $dest_ftp->delete ( $scratch_name );
2084 0           $dArg->{last_ftp_msg} = $msg1;
2085             }
2086              
2087             # Now allow us to die again if we must ...
2088 0           ($sArg->{Croak}, $dArg->{Croak}) = ($sdie, $ddie);
2089              
2090 0           return ( $self->_test_croak ( $resp ) );
2091             }
2092              
2093             sub _put_offset_fix {
2094 0     0     my $self = shift;
2095 0           my $offset = shift;
2096 0           my $len = shift;
2097 0           my $data = shift;
2098              
2099             # Determine if we can send any of this data to the server ...
2100 0 0         if ( $offset >= $len ) {
    0          
2101             # Can't send anything form the data buffer this time ...
2102 0           $offset -= $len; # Result is >= 0
2103 0           $len = 0;
2104 0           $data = "";
2105              
2106             } elsif ( $offset ) {
2107             # Sending a partial data buffer, stripping off leading chars ...
2108 0           my $p = "." x $offset;
2109 0           $data =~ s/^$p//s; # Use option "s" since $data has "\n" in it.
2110 0           $len -= $offset; # Result is >= 0
2111 0           $offset = 0;
2112             }
2113              
2114 0           return ($offset, $len, $data);
2115             }
2116              
2117             sub _common_put {
2118 0     0     my $self = shift;
2119 0           my $file_loc = shift;
2120 0           my $file_rem = shift;
2121 0   0       my $offset = shift || ${*$self}{_FTPSSL_arguments}->{net_ftpssl_rest_offset} || 0;
2122              
2123             # Clear out this messy restart() cluge for next time ...
2124 0           delete ( ${*$self}{_FTPSSL_arguments}->{net_ftpssl_rest_offset} );
  0            
2125              
2126 0 0 0       if ( $self->_isa_glob ($file_loc) && ! $file_rem ) {
2127 0           return $self->_croak_or_return (0, "When you pass a stream, you must specify the remote filename.");
2128             }
2129              
2130 0 0         unless ($file_rem) {
2131 0           $file_rem = basename ($file_loc);
2132             }
2133              
2134 0 0         if ( $offset < -1 ) {
2135 0           return $self->_croak_or_return(0, "Invalid file offset ($offset)!");
2136             }
2137              
2138             # Find out which of 4 "put" functions called me ...
2139 0   0       my $func = (caller(1))[3] || ":unknown";
2140 0           $func =~ m/:([^:]+)$/;
2141 0           $func = $1;
2142              
2143 0 0 0       if ( $offset && $func ne "put" && $func ne "append" ) {
      0        
2144 0           return $self->_croak_or_return(0, "Function $func() doesn't support RESTart.");
2145             }
2146              
2147 0 0         if ( $offset == -1 ) {
2148 0           $offset = $self->size ($file_rem);
2149 0 0         unless ( defined $offset ) {
2150 0           return (undef); # Already did croak test in size().
2151             }
2152             }
2153              
2154 0           my ( $size, $localfd );
2155 0           my $close_file = 0;
2156              
2157 0   0       $size = ${*$self}{_FTPSSL_arguments}->{buf_size} || 2048;
2158              
2159 0 0         if ( $self->_isa_glob ($file_loc) ) {
2160 0           $localfd = \*$file_loc;
2161              
2162             } else {
2163 0 0         unless ( open( $localfd, "< $file_loc" ) ) {
2164 0           return $self->_croak_or_return (0, "Can't open local file! ($file_loc)");
2165             }
2166 0           $close_file = 1;
2167             }
2168              
2169 0           my $fix_cr_issue = 1;
2170 0 0         if ( ${*$self}{_FTPSSL_arguments}->{type} eq MODE_BINARY ) {
  0            
2171 0 0         unless ( binmode $localfd ) {
2172 0           return $self->_croak_or_return(0, "Can't set binary mode to local file!");
2173             }
2174 0           $fix_cr_issue = 0;
2175             }
2176              
2177             # Set in case we require the use of the PRET command ...
2178 0           my $cmd = "";
2179 0 0         if ( $func eq "uput" ) {
    0          
    0          
    0          
2180 0           $cmd = "STOU";
2181             } elsif ( $func eq "xput" ) {
2182 0           $cmd = "STOR";
2183             } elsif ( $func eq "put" ) {
2184 0           $cmd = "STOR";
2185             } elsif ( $func eq "append" ) {
2186 0           $cmd = "APPE";
2187             }
2188              
2189 0 0         unless ( $self->prep_data_channel( $cmd, $file_rem ) ) {
2190 0 0         close ($localfd) if ($close_file);
2191 0           return undef; # Already decided not to call croak if you get here!
2192             }
2193              
2194             # If alloc_size is already set, I skip this part
2195 0 0         unless ( defined ${*$self}{_FTPSSL_arguments}->{alloc_size} ) {
  0            
2196 0 0 0       if ( $close_file && -f $file_loc ) {
2197 0           my $size = -s $file_loc;
2198 0           $self->alloc($size);
2199             }
2200             }
2201              
2202 0           delete ${*$self}{_FTPSSL_arguments}->{alloc_size};
  0            
2203              
2204             # Issue the correct "put" request ...
2205 0           my ($response, $restart) = (0, 1);
2206 0 0         if ( $func eq "uput" ) {
    0          
    0          
    0          
2207 0           $response = $self->_stou ($file_rem);
2208             } elsif ( $func eq "xput" ) {
2209 0           $response = $self->_stor ($file_rem);
2210             } elsif ( $func eq "put" ) {
2211 0 0         $restart = ($offset) ? $self->_rest ($offset) : 1;
2212 0           $response = $self->_stor ($file_rem);
2213             } elsif ( $func eq "append" ) {
2214             # Just uses OFFSET, doesn't send REST out.
2215 0           $response = $self->_appe ($file_rem);
2216             }
2217              
2218             # If the "put" request fails ...
2219 0 0 0       unless ($restart && $response) {
2220 0 0         close ($localfd) if ($close_file);
2221 0 0 0       if ( $restart && $offset && $func eq "get" ) {
      0        
2222 0           $self->_rest (0);
2223             }
2224 0           return ( $self->_croak_or_return (), undef, undef, $file_rem, undef );
2225             }
2226              
2227             # The "REST" command doesn't affect file streams ...
2228 0 0         $offset = 0 unless ($close_file);
2229              
2230 0           my $put_msg = $self->last_message ();
2231              
2232 0           my ( $data, $written, $io );
2233              
2234 0           $io = $self->_get_data_channel ();
2235 0 0         unless ( defined $io ) {
2236 0 0         close ($localfd) if ($close_file);
2237 0           return undef; # Already decided not to call croak if you get here!
2238             }
2239              
2240 0           my $trace_flag = ${*$self}{_FTPSSL_arguments}->{trace};
  0            
2241 0 0         print STDERR "$func() trace ." if ($trace_flag);
2242 0           my $cnt = 0;
2243 0           my $total = 0;
2244 0           my $len;
2245              
2246 0           while ( ( $len = sysread $localfd, $data, $size ) ) {
2247 0 0         unless ( defined $len ) {
2248 0 0         next if $! == EINTR;
2249 0           return $self->_croak_or_return (0, "System read error on $func(): $!");
2250             }
2251              
2252 0           $total = $self->_call_callback (2, \$data, \$len, $total);
2253              
2254 0 0         if ($fix_cr_issue) {
2255 0           $data =~ s/\n/\015\012/g;
2256 0           $len = length ($data);
2257             }
2258              
2259             # Determine if we can send any of this data to the server ...
2260 0 0         if ( $offset ) {
2261 0           ($offset, $len, $data) = $self->_put_offset_fix ( $offset, $len, $data );
2262             }
2263              
2264 0 0 0       print STDERR "." if ($trace_flag && ($cnt % TRACE_MOD) == 0);
2265 0           ++$cnt;
2266              
2267 0 0         if ( $len > 0 ) {
2268 0           $written = syswrite $io, $data, $len;
2269 0 0         return $self->_croak_or_return (0, "System write error on $func(): $!")
2270             unless (defined $written);
2271             }
2272             } # End while sysread() loop!
2273              
2274              
2275             # Process trailing call back info if present.
2276 0           my $trail;
2277 0           ($trail, $len, $total) = $self->_end_callback (2, $total);
2278 0 0         if ( $trail ) {
2279 0 0         if ($fix_cr_issue) {
2280 0           $trail =~ s/\n/\015\012/g;
2281 0           $len = length ($trail);
2282             }
2283              
2284             # Determine if we can send any of this data to the server ...
2285 0 0         if ( $offset ) {
2286 0           ($offset, $len, $data) = $self->_put_offset_fix ( $offset, $len, $data );
2287             }
2288              
2289 0 0         if ( $len > 0 ) {
2290 0           $written = syswrite $io, $trail, $len;
2291 0 0         return $self->_croak_or_return (0, "System write error on $func(): $!")
2292             unless (defined $written);
2293             }
2294             }
2295              
2296 0 0         print STDERR ". done! (" . $self->_fmt_num ($total) . " byte(s))\n" if ($trace_flag);
2297              
2298 0           my $tm;
2299 0 0         if ($close_file) {
2300 0           close ($localfd);
2301 0 0         if ( ${*$self}{_FTPSSL_arguments}->{FixPutTs} ) {
  0            
2302 0           $tm = (stat ($file_loc))[9]; # Get's the local file's timestamp!
2303             }
2304             }
2305              
2306 0           _my_close ($io); # Old way $io->close();
2307              
2308             # To catch the expected "226 Closing data connection."
2309 0 0         if ( $self->response() != CMD_OK ) {
2310 0           return $self->_croak_or_return ();
2311             }
2312              
2313 0           return ( 1, $put_msg, $self->last_message (), $file_rem, $tm );
2314             }
2315              
2316              
2317             # On some servers this command always fails! So no croak test!
2318             # It's also why supported gets called.
2319             # Just be aware of HELP issue (OverrideHELP option)
2320             sub alloc {
2321 0     0 0   my $self = shift;
2322 0           my $size = shift;
2323              
2324 0 0 0       if ( $self->supported ("ALLO") &&
2325             $self->_alloc($size) ) {
2326 0           ${*$self}{_FTPSSL_arguments}->{alloc_size} = $size;
  0            
2327             }
2328             else {
2329 0           return 0;
2330             }
2331              
2332 0           return 1;
2333             }
2334              
2335             sub delete {
2336 0     0 1   my $self = shift;
2337 0           return ($self->_test_croak ($self->command("DELE", @_)->response() == CMD_OK));
2338             }
2339              
2340             sub auth {
2341 0     0 0   my $self = shift;
2342 0           return ($self->_test_croak ($self->command("AUTH", "TLS")->response() == CMD_OK));
2343             }
2344              
2345             sub pwd {
2346 0     0 1   my $self = shift;
2347 0           my $path;
2348              
2349 0           $self->command("PWD")->response();
2350              
2351 0 0         if ( ${*$self}{_FTPSSL_arguments}->{last_ftp_msg} =~ /\"(.*)\".*/ )
  0            
2352             {
2353             # 257 "//" is current directory.
2354             # "Quote-doubling" convention - RFC 959, Appendix II
2355 0           ( $path = $1 ) =~ s/\"\"/\"/g;
2356 0           return $path;
2357             }
2358             else {
2359 0           return $self->_croak_or_return ();
2360             }
2361             }
2362              
2363             sub cwd {
2364 0     0 1   my $self = shift;
2365 0           return ( $self->_test_croak ($self->command("CWD", @_)->response() == CMD_OK) );
2366             }
2367              
2368             sub noop {
2369 0     0 1   my $self = shift;
2370 0           return ( $self->_test_croak ($self->command("NOOP")->response() == CMD_OK) );
2371             }
2372              
2373             sub rename {
2374 0     0 1   my $self = shift;
2375 0           my $old_name = shift;
2376 0           my $new_name = shift;
2377              
2378 0   0       return ( $self->_test_croak ( $self->_rnfr ($old_name) &&
2379             $self->_rnto ($new_name) ) );
2380             }
2381              
2382             sub cdup {
2383 0     0 1   my $self = shift;
2384 0           return ( $self->_test_croak ($self->command("CDUP")->response() == CMD_OK) );
2385             }
2386              
2387             # TODO: Make mkdir() working with recursion.
2388             sub mkdir {
2389 0     0 1   my $self = shift;
2390 0           my $dir = shift;
2391 0           $self->command("MKD", $dir);
2392 0           return ( $self->_test_croak ($self->response() == CMD_OK) );
2393             }
2394              
2395             # TODO: Make rmdir() working with recursion.
2396             sub rmdir {
2397 0     0 1   my $self = shift;
2398 0           my $dir = shift;
2399 0           $self->command("RMD", $dir);
2400 0           return ( $self->_test_croak ($self->response() == CMD_OK) );
2401             }
2402              
2403             sub site {
2404 0     0 1   my $self = shift;
2405              
2406 0           return ($self->_test_croak ($self->command("SITE", @_)->response() == CMD_OK));
2407             }
2408              
2409             # A true boolean func, should never call croak!
2410             sub supported {
2411 0     0 1   my $self = shift;
2412 0   0       my $cmd = uc (shift || "");
2413 0   0       my $sub_cmd = uc (shift || "");
2414              
2415 0           my $result = 0; # Assume invalid FTP command
2416 0           my $arg = ${*$self}{_FTPSSL_arguments};
  0            
2417              
2418             # It will cache the result so OK to call multiple times.
2419 0           my $help = $self->_help ();
2420              
2421             # Only finds exact matches, no abbreviations like some FTP servers allow.
2422 0 0 0       if ( exists $arg->{OverrideHELP} && $cmd eq "HELP" ) {
    0 0        
2423 0           $arg->{last_ftp_msg} = "503 Unsupported command $cmd.";
2424             } elsif ( $arg->{OverrideHELP} || $help->{$cmd} ) {
2425 0           $result = 1; # It is a valid FTP command
2426 0           $arg->{last_ftp_msg} = "214 The $cmd command is supported.";
2427             } else {
2428 0           $arg->{last_ftp_msg} = "502 Unknown command $cmd.";
2429             }
2430              
2431             # Are we validating a SITE sub-command?
2432 0 0 0       if ($result && $cmd eq "SITE" && $sub_cmd ne "") {
      0        
2433 0           my $help2 = $self->_help ($cmd);
2434 0 0         if ( $help2->{$sub_cmd} ) {
    0          
2435 0           $arg->{last_ftp_msg} = "214 The SITE sub-command $sub_cmd is supported.";
2436 0           } elsif ( scalar (keys %{$help2}) > 0 ) {
2437 0           $arg->{last_ftp_msg} = "502 Unknown SITE sub-command - $sub_cmd.";
2438 0           $result = 0; # It failed after all!
2439             } else {
2440 0           $arg->{last_ftp_msg} = "503 Can't validate SITE sub-commands - $sub_cmd.";
2441 0           $result = -1; # Maybe/mabye not supported!
2442             }
2443             }
2444              
2445             # Are we validating a FEAT sub-command?
2446             # Everything in the hash is a valid command. But it's value is frequently "".
2447             # So must use "exists $fest2->{$cmd}" for all tests!
2448 0 0 0       if ($result && $cmd eq "FEAT" && $sub_cmd ne "") {
      0        
2449 0           my $feat2 = $self->_feat ();
2450 0 0         if ( exists $feat2->{$sub_cmd} ) {
2451 0           $arg->{last_ftp_msg} = "214 The FEAT sub-command $sub_cmd is supported.";
2452 0 0 0       if ( exists $feat2->{OPTS} && exists $feat2->{OPTS}->{$sub_cmd} ) {
2453 0           $arg->{last_ftp_msg} .= " And its behaviour may be modified by OPTS.";
2454             }
2455             } else {
2456 0           $arg->{last_ftp_msg} = "502 Unknown FEAT sub-command - $sub_cmd.";
2457 0           $result = 0; # It failed after all!
2458             }
2459             }
2460              
2461             # Are we validating a OPTS sub-command?
2462             # It's a special case of FEAT!
2463 0 0 0       if ($result && $cmd eq "OPTS" && $sub_cmd ne "") {
      0        
2464 0           my $feat3 = $self->_feat ();
2465 0 0 0       if ( exists $feat3->{OPTS} && exists $feat3->{OPTS}->{$sub_cmd} ) {
    0          
2466 0           $arg->{last_ftp_msg} = "214 The FEAT sub-command $sub_cmd may be modified by the OPTS command.";
2467             } elsif ( exists $feat3->{sub_cmd} ) {
2468 0           $arg->{last_ftp_msg} = "504 The FEAT sub-command $sub_cmd may not be modified by the OPTS command.";
2469             } else {
2470 0           $arg->{last_ftp_msg} = "505 The FEAT sub-command $sub_cmd doesn't exist and so can't be modified by the OPTS command.";
2471             }
2472             }
2473              
2474 0           $self->_print_DBG ( "<<+ ", $self->last_message (), "\n" );
2475              
2476 0           return ($result);
2477             }
2478              
2479              
2480             # A true boolean func, should never call croak!
2481             sub all_supported {
2482 0     0 1   my $self = shift;
2483             # Leave the rest of the options on the @_ array ...
2484              
2485 0           my $cnt = 0;
2486 0           foreach ( @_ ) {
2487 0 0 0       next unless (defined $_ && $_ ne "");
2488 0           ++$cnt;
2489 0 0         next if ($self->supported ($_));
2490 0           return (0); # Something wasn't supported!
2491             }
2492              
2493 0 0         return ( ($cnt >= 1) ? 1 : 0 );
2494             }
2495              
2496              
2497             # Hacks the _help() cache, otherwise can't modify the supported logic!
2498             sub fix_supported {
2499 0     0 1   my $self = shift;
2500 0           my $mode = shift; # True Add/False Remove
2501             # Leave the rest of the options on the @_ array ...
2502              
2503             # Can't update supported() if using OverrideHELP => 1. (Everything supported)
2504 0 0         return (0) if ( ${*$self}{_FTPSSL_arguments}->{OverrideHELP} );
  0            
2505              
2506             # Holds the real cached help values ...
2507 0           my $help = ${*$self}{_FTPSSL_arguments}->{help_cmds_found};
  0            
2508 0 0         return (0) unless ( defined $help );
2509              
2510             # Flag to tell if you can add/remove the HELP command from supported!
2511             my $help_flag = ( exists ${*$self}{_FTPSSL_arguments}->{OverrideHELP} ||
2512 0   0       exists ${*$self}{_FTPSSL_arguments}->{removeHELP} );
2513              
2514 0           my $cnt = 0;
2515 0           foreach ( @_ ) {
2516 0           my $key = uc ($_);
2517              
2518 0 0 0       next if ( $key eq "HELP" && $help_flag );
2519              
2520 0 0 0       if ( $mode && ! exists $help->{$key} ) {
    0 0        
2521 0           $help->{$key} = 3; # Add the command as supported.
2522 0           ++$cnt;
2523              
2524             } elsif ( ! $mode && exists $help->{$key} ) {
2525 0           delete $help->{$key}; # Remove the command as supported.
2526 0           ++$cnt;
2527             }
2528             }
2529              
2530 0           return ( $cnt );
2531             }
2532              
2533              
2534             # The Clear Command Channel func is only valid after login.
2535             sub ccc {
2536 0     0 1   my $self = shift;
2537 0   0       my $prot = shift || ${*$self}{_FTPSSL_arguments}->{data_prot};
2538              
2539 0 0         if ( ${*$self}{_FTPSSL_arguments}->{Crypt} eq CLR_CRYPT ) {
  0            
2540 0           return $self->_croak_or_return (undef, "Command Channel already clear!");
2541             }
2542              
2543             # Set the data channel to the requested security level ...
2544             # This command is no longer supported after the CCC command executes.
2545 0 0 0       unless ($self->_pbsz() && $self->_prot ($prot)) {
2546 0           return $self->_croak_or_return ();
2547             }
2548              
2549             # Do before the CCC command so we know which command is available to clear
2550             # out the command channel with. All servers should support one or the other.
2551             # We also want commands that return just one line! [To make it less likely
2552             # that the hack will cause response() to hang or get out of sync when
2553             # unrecognizable junk is returned for the hack.]
2554 0 0         my $ccc_fix_cmd = $self->supported ("NOOP") ? "NOOP" : "PWD";
2555              
2556             # Request that just the commnad channel go clear ...
2557 0 0         unless ( $self->command ("CCC")->response () == CMD_OK ) {
2558 0           return $self->_croak_or_return ();
2559             }
2560 0           ${*$self}{_FTPSSL_arguments}->{Crypt} = CLR_CRYPT;
  0            
2561              
2562             # Save before stop_SSL() removes the bless.
2563 0           my $bless_type = ref ($self);
2564              
2565             # Give the command channel a chance to stabalize again.
2566 0           sleep (1);
2567              
2568             # -------------------------------------------------------------------------
2569             # Stop SSL, but leave the socket open!
2570             # Converts $self to IO::Socket::INET object instead of Net::FTPSSL
2571             # NOTE: SSL_no_shutdown => 1 doesn't work on some boxes, and when 0,
2572             # it hangs on others without the SSL_fast_shutdown => 1 option.
2573             # -------------------------------------------------------------------------
2574 0 0         unless ( $self->stop_SSL ( SSL_no_shutdown => 0, SSL_fast_shutdown => 1 ) ) {
2575 0           return $self->_croak_or_return (undef, "Command Channel downgrade failed!");
2576             }
2577              
2578             # Bless back to Net::FTPSSL from IO::Socket::INET ...
2579 0           bless ( $self, $bless_type );
2580 0           ${*$self}{_SSL_opened} = 0; # To get rid of warning on quit ...
  0            
2581              
2582             # Give the command channel a chance to stabalize again.
2583 0           sleep (1);
2584              
2585             # -------------------------------------------------------------------------
2586             # This is a hack, but it seems to resolve the command channel corruption
2587             # problem where the 1st command or two afer CCC may fail or look strange ...
2588             # I've even caught it a few times sending back 2 independant OK responses
2589             # to a single command!
2590             # ------------------------------------------------------------------------
2591 0           my $ok = CMD_ERROR;
2592 0           foreach ( 1..4 ) {
2593 0           $ok = $self->command ($ccc_fix_cmd)->response (1); # This "1" is a hack!
2594              
2595             # Do char compare since not always a number.
2596 0 0 0       last if ( defined $ok && $ok eq CMD_OK );
2597 0           $ok = CMD_ERROR;
2598             }
2599              
2600 0 0         if ( $ok == CMD_OK ) {
2601             # Complete the hack, now force a failure response!
2602             # And if the server was still confused ?
2603             # Keep asking for responses until we get our error!
2604 0           $self->command ("xxxxNOOP");
2605 0           while ( $self->response () == CMD_OK ) {
2606 0           my $tmp = CMD_ERROR; # A no-op command for loop body ...
2607             }
2608             }
2609             # -------------------------------------------------------------------------
2610             # End hack of CCC command recovery.
2611             # -------------------------------------------------------------------------
2612              
2613 0           return ( $self->_test_croak ( $ok == CMD_OK ) );
2614             }
2615              
2616              
2617             #-----------------------------------------------------------------------
2618             # Allow the user to send a random FTP command directly, BE CAREFUL !!
2619             # Since doing unsupported stuff, we can never call croak!
2620             # Also not all unsupported stuff will show up via supported().
2621             # So all we can do is try to prevent commands known to have side affects.
2622             #-----------------------------------------------------------------------
2623             sub quot {
2624 0     0 1   my $self = shift;
2625 0           my $cmd = shift;
2626              
2627             # Format the command for testing ...
2628 0 0         my $cmd2 = (defined $cmd) ? uc ($cmd) : "";
2629 0 0         $cmd2 = $1 if ( $cmd2 =~ m/^\s*(\S+)(\s|$)/ );
2630              
2631 0           my $msg = ""; # Assume all is OK ...
2632              
2633             # The following FTP commands are known to open a data channel
2634 0 0 0       if ( $cmd2 eq "STOR" || $cmd2 eq "RETR" ||
    0 0        
    0 0        
    0 0        
      0        
      0        
      0        
      0        
2635             $cmd2 eq "NLST" || $cmd2 eq "LIST" ||
2636             $cmd2 eq "STOU" || $cmd2 eq "APPE" ||
2637             $cmd2 eq "MLSD" ) {
2638 0           $msg = "x23 Data Connections are not supported via quot(). [$cmd2]";
2639              
2640             } elsif ( $cmd2 eq "CCC" ) {
2641 0           $msg = "x22 Why didn't you call CCC directly via it's interface?";
2642              
2643             } elsif ( $cmd2 eq "" ) {
2644 0           $msg = "x21 Where is the needed command?";
2645 0           $cmd = ""; # Making sure it isn't undefined.
2646              
2647             } elsif ( $cmd2 eq "HELP" &&
2648             ( exists ${*$self}{_FTPSSL_arguments}->{OverrideHELP} ||
2649             exists ${*$self}{_FTPSSL_arguments}->{removeHELP} ) ) {
2650 0           $msg = "x20 Why did you try to call HELP after you overrode all calls to it?";
2651              
2652             } else {
2653             # Strip off leading spaces, some servers choak on them!
2654 0           $cmd =~ s/^\s+//;
2655             }
2656              
2657 0 0         if ( $msg ne "" ) {
2658 0           my $cmd_str = join (" ", $cmd, @_);
2659 0           ${*$self}{_FTPSSL_arguments}->{last_ftp_msg} = $msg;
  0            
2660 0           $self->_change_status_code (CMD_REJECT);
2661 0           $self->_print_DBG ( ">>+ ", $cmd_str, "\n" );
2662 0           $self->_print_DBG ( "<<+ ", $self->last_message (), "\n" );
2663 0           return (CMD_REJECT);
2664             }
2665              
2666 0           return ( $self->command ($cmd, @_)->response () );
2667             }
2668              
2669             #-----------------------------------------------------------------------
2670             # Type setting function
2671             #-----------------------------------------------------------------------
2672              
2673             sub ascii {
2674 0     0 1   my $self = shift;
2675 0           ${*$self}{_FTPSSL_arguments}->{type} = MODE_ASCII;
  0            
2676 0           return $self->_test_croak ($self->_type(MODE_ASCII));
2677             }
2678              
2679             sub binary {
2680 0     0 1   my $self = shift;
2681 0           ${*$self}{_FTPSSL_arguments}->{type} = MODE_BINARY;
  0            
2682 0           return $self->_test_croak ($self->_type(MODE_BINARY));
2683             }
2684              
2685             # Server thinks it's ASCII & Client thinks it's BINARY
2686             sub mixedModeAI {
2687 0     0 1   my $self = shift;
2688 0           ${*$self}{_FTPSSL_arguments}->{type} = MODE_BINARY;
  0            
2689 0           return $self->_test_croak ($self->_type(MODE_ASCII));
2690             }
2691              
2692             # Server thinks it's BINARY & Client thinks it's ASCII
2693             sub mixedModeIA {
2694 0     0 1   my $self = shift;
2695 0           ${*$self}{_FTPSSL_arguments}->{type} = MODE_ASCII;
  0            
2696 0           return $self->_test_croak ($self->_type(MODE_BINARY));
2697             }
2698              
2699             #-----------------------------------------------------------------------
2700             # Internal functions
2701             #-----------------------------------------------------------------------
2702              
2703             sub _xWait {
2704 0     0     my $self = shift;
2705              
2706 0           my $slp = ${*$self}{_FTPSSL_arguments}->{xWait};
  0            
2707              
2708 0 0         if ( $slp ) {
2709 0           $self->_print_DBG ("---- ", "Sleeping ${slp} second(s)\n");
2710 0           sleep ( $slp );
2711             }
2712             }
2713              
2714             sub _user {
2715 0     0     my $self = shift;
2716 0           my $resp = $self->command ( "USER", @_ )->response ();
2717 0   0       return ( $resp == CMD_OK || $resp == CMD_MORE );
2718             }
2719              
2720             sub _passwd {
2721 0     0     my $self = shift;
2722 0           my $resp = $self->command ( "PASS", @_ )->response ();
2723 0   0       return ( $resp == CMD_OK || $resp == CMD_MORE );
2724             }
2725              
2726             sub _quit {
2727 0     0     my $self = shift;
2728 0           return ( $self->command ("QUIT")->response () == CMD_OK );
2729             }
2730              
2731             sub _prot {
2732 0     0     my $self = shift;
2733 0   0       my $opt = shift || ${*$self}{_FTPSSL_arguments}->{data_prot};
2734              
2735             # C, S, E or P.
2736 0           my $resp = ( $self->command ( "PROT", $opt )->response () == CMD_OK );
2737              
2738             # Check if someone changed the data channel protection mode ...
2739 0 0 0       if ($resp && $opt ne ${*$self}{_FTPSSL_arguments}->{data_prot}) {
  0            
2740 0           ${*$self}{_FTPSSL_arguments}->{data_prot} = $opt; # They did change it!
  0            
2741             }
2742              
2743 0           return ( $resp );
2744             }
2745              
2746             # Depreciated, only present to make backwards compatible with v0.05 & earlier.
2747             sub _protp {
2748 0     0     my $self = shift;
2749 0           return ($self->_prot (DATA_PROT_PRIVATE));
2750             }
2751              
2752             sub _pbsz {
2753 0     0     my $self = shift;
2754 0           return ( $self->command ( "PBSZ", "0" )->response () == CMD_OK );
2755             }
2756              
2757             sub _nlst {
2758 0     0     my $self = shift;
2759 0           return ( $self->command ( "NLST", @_ )->response () == CMD_INFO );
2760             }
2761              
2762             sub _list {
2763 0     0     my $self = shift;
2764 0           return ( $self->command ( "LIST", @_ )->response () == CMD_INFO );
2765             }
2766              
2767             sub _type {
2768 0     0     my $self = shift;
2769 0           return ( $self->command ( "TYPE", @_ )->response () == CMD_OK );
2770             }
2771              
2772             sub _rest {
2773 0     0     my $self = shift;
2774 0           return ( $self->command ( "REST", @_ )->response () == CMD_MORE );
2775             }
2776              
2777             sub _retr {
2778 0     0     my $self = shift;
2779 0           return ( $self->command ( "RETR", @_ )->response () == CMD_INFO );
2780             }
2781              
2782             sub _stor {
2783 0     0     my $self = shift;
2784 0           return ( $self->command ( "STOR", @_ )->response () == CMD_INFO );
2785             }
2786              
2787             sub _appe {
2788 0     0     my $self = shift;
2789 0           return ( $self->command ( "APPE", @_ )->response () == CMD_INFO );
2790             }
2791              
2792             sub _stou {
2793 0     0     my $self = shift;
2794              
2795             # Works for most non-windows FTPS servers ...
2796 0           ${*$self}{_FTPSSL_arguments}->{uput} = 0; # Conditionally uses scratch name.
  0            
2797 0           my $res = $self->command ( "STOU", @_ )->response ();
2798 0 0         return ( 1 ) if ( $res == CMD_INFO );
2799              
2800             # Some windows servers won't allow any arguments ...
2801             # They always use a scratch name! (But don't always return what it is.)
2802 0           my $msg = $self->last_message ();
2803 0 0 0       if ( $res == CMD_ERROR && $msg =~ m/Invalid number of parameters/ ) {
2804 0           ${*$self}{_FTPSSL_arguments}->{uput} = 1; # Will always use scratch name.
  0            
2805 0           $res = $self->command ( "STOU" )->response ();
2806             }
2807              
2808 0           return ( $res == CMD_INFO );
2809             }
2810              
2811             sub _abort {
2812 0     0     my $self = shift;
2813 0           return ( $self->command ("ABOR")->response () == CMD_OK );
2814             }
2815              
2816             sub _alloc {
2817 0     0     my $self = shift;
2818 0           return ( $self->command ( "ALLO", @_ )->response () == CMD_OK );
2819             }
2820              
2821             sub _rnfr {
2822 0     0     my $self = shift;
2823 0           return ( $self->command ( "RNFR", @_ )->response () == CMD_MORE );
2824             }
2825              
2826             sub _rnto {
2827 0     0     my $self = shift;
2828 0           return ( $self->command ( "RNTO", @_ )->response () == CMD_OK );
2829             }
2830              
2831             sub mfmt {
2832 0     0 1   my $self = shift;
2833 0           $self->command( "MFMT", @_ );
2834 0           return ( $self->_test_croak ($self->response () == CMD_OK) );
2835             }
2836              
2837             # Uses the PreserveTimestamp kludge!
2838             # If not preserving timestamps, assumes GMT!
2839             sub _mfmt {
2840 0     0     my $self = shift;
2841 0           my $timestamp = shift; # (stat ($loc_file))[9] - The local file's timestamp!
2842 0           my $remote_file = shift;
2843 0           my $local = shift; # True Local / False GMT / Undef use PreserveTimestamp
2844              
2845             # Asks if the FTPS server is using GMT or Local time for the returned timestamp.
2846 0           my $GMT_flag = 1; # Assume GMT ...
2847 0 0         if ( defined $local ) {
    0          
2848 0 0         $GMT_flag = $local ? 0 : 1; # Override PreserveTimestamp option ...
2849 0           } elsif ( ${*$self}{_FTPSSL_arguments}->{FixPutTs} < 0 ) {
2850 0           $GMT_flag = 0; # PreserveTimestamp said to use Local Time ...
2851             }
2852              
2853             # Convert it into YYYYMMDDHHMMSS format (GM Time) [ gmtime() vs localtime() ]
2854 0           my ($sec, $min, $hr, $day, $mon, $yr, $wday, $yday, $isdst);
2855              
2856             # Using perl's built-in functions here. (years offset of 1900.)
2857 0 0         if ( $GMT_flag ) {
2858             # Use GMT Time [ gmtime() vs timegm() ]
2859 0           ($sec, $min, $hr, $day, $mon, $yr, $wday, $yday, $isdst) =
2860             gmtime ( $timestamp );
2861             } else {
2862             # Use Local Time [ localtime() vs timelocal() ]
2863 0           ($sec, $min, $hr, $day, $mon, $yr, $wday, $yday, $isdst) =
2864             localtime ( $timestamp );
2865             }
2866              
2867 0           my $time = sprintf ("%04d%02d%02d%02d%02d%02d",
2868             $yr + 1900, $mon + 1, $day, $hr, $min, $sec);
2869              
2870             # Upload the file's new timestamp ...
2871 0           return ( $self->command ( "MFMT", $time, $remote_file )->response () == CMD_OK );
2872             }
2873              
2874              
2875             # Parses the remote FTPS server's response for the file's timestamp!
2876             # Now in a separate function due to server bug reported by Net::FTP!
2877             # Returns: undef or the timestamp in YYYYMMDDHHMMSS (len 14) format!
2878             sub _internal_mdtm_parse {
2879 0     0     my $self = shift;
2880              
2881             # Get the message returned by the FTPS server for the MDTM command ...
2882 0           my $msg = ${*$self}{_FTPSSL_arguments}->{last_ftp_msg};
  0            
2883              
2884 0           my $gmt_time_str;
2885              
2886             # Check for the expected timestamp format ...
2887 0 0         if ( $msg =~ m/(^|\D)(\d{14})($|\D)/ ) {
    0          
2888 0           $gmt_time_str = $2; # The timestamp on the remote server: YYYYMMDDHHMMSS.
2889              
2890             # According to Net::FTP, some idiotic FTP server bug used
2891             # ("19%d", tm.tm_year") instead of ("%d", tm.tm_year+1900)
2892             # to format the year. So converting it into the expected format!
2893             # This way this bug isn't propagated outside this function!
2894             # Fix doesn't work for dates before 1-1-1910, which should never happen!
2895             } elsif ( $msg =~ m/(^|\D)19(\d{3})(\d{10})($|\D)/ ) {
2896 0           my ( $yr, $rest ) = ( $2, $3 );
2897              
2898             # The corrected date ...
2899 0           $gmt_time_str = sprintf ("%04d%s", 1900 + $yr, $rest);
2900              
2901 0           $self->_print_DBG ("---- ", "Bad Year: 19${yr}${rest}! ",
2902             "Converting to ${gmt_time_str}\n");
2903             }
2904              
2905 0           return ( $gmt_time_str );
2906             }
2907              
2908             sub mdtm {
2909 0     0 1   my $self = shift;
2910              
2911 0           my $gmt_time_str;
2912              
2913 0 0         if ( $self->command( "MDTM", @_ )-> response () == CMD_OK ) {
2914 0           $gmt_time_str = $self->_internal_mdtm_parse ();
2915             }
2916              
2917 0           return ( $self->_test_croak ($gmt_time_str) ); # In GMT time ...
2918             }
2919              
2920             # Uses the PreserveTimestamp kludge!
2921             # If not preserving timestamps, assumes GMT!
2922             sub _mdtm {
2923 0     0     my $self = shift;
2924 0           my $remote_file = shift;
2925 0           my $local = shift; # True Local / False GMT / Undef use PreserveTimestamp
2926              
2927 0           my $timestamp; # The return value ...
2928              
2929             # Asks if the FTPS server is using GMT or Local time for the returned timestamp.
2930 0           my $GMT_flag = 1; # Assume GMT ...
2931 0 0         if ( defined $local ) {
    0          
2932 0 0         $GMT_flag = $local ? 0 : 1; # Override PreserveTimestamp option ...
2933 0           } elsif ( ${*$self}{_FTPSSL_arguments}->{FixGetTs} < 0 ) {
2934 0           $GMT_flag = 0; # PreserveTimestamp said to use Local Time ...
2935             }
2936              
2937             # Collect the timestamp from the FTPS server ...
2938 0 0         if ( $self->command ("MDTM", $remote_file)->response () == CMD_OK ) {
2939 0           my $time_str = $self->_internal_mdtm_parse ();
2940              
2941             # Now convert it into the internal format used by Perl ...
2942 0 0 0       if ( defined $time_str &&
2943             $time_str =~ m/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/ ) {
2944 0           my ($yr, $mon, $day, $hr, $min, $sec) = ($1, $2, $3, $4, $5, $6);
2945              
2946             # Using Time::Local functions here.
2947             # (Not a true inverse of the built-in funcs with regards to the year.)
2948 0 0         if ( $GMT_flag ) {
2949             # Use GMT Time [ timegm() vs gmtime() ]
2950 0           $timestamp = timegm ( $sec, $min, $hr, $day, $mon - 1, $yr );
2951             } else {
2952             # Use Local Time [ timelocal() vs localtime() ]
2953 0           $timestamp = timelocal ( $sec, $min, $hr, $day, $mon - 1, $yr );
2954             }
2955             }
2956             }
2957              
2958 0           return ( $timestamp );
2959             }
2960              
2961             sub size {
2962 0     0 1   my $self = shift;
2963 0           my $file = shift;
2964 0   0       my $skip_mlst = shift || 0; # Not in POD on purpose!
2965              
2966             # The expected option ...
2967 0 0         if ( $self->supported ("SIZE") ) {
2968 0 0 0       if ( $self->command ("SIZE", $file, @_)->response () == CMD_OK &&
2969             $self->message () =~ m/\d+\s+(\d+)($|\D)/ ) {
2970 0           return ( $1 ); # The size in bytes! May be zero!
2971             }
2972             }
2973              
2974             # Will only set to 1 if we know the file is really a directory!
2975 0           my $skip_stat = 0;
2976              
2977             # Not implemented on many FTPS servers ...
2978             # But is the most reliable way if it is ...
2979             # It returns the size for all file types, not just regular files!
2980 0 0 0       if ( $self->supported ("MLST") && ! $skip_mlst ) {
2981             # Must use "OPTS MLST SIZE" if the size feature is currently disabled.
2982 0           my $data = $self->parse_mlsx ( $self->mlst ($file), 1 );
2983 0 0         if ( $data ) {
2984 0 0         if ( exists $data->{size} ) {
    0          
2985 0           return ( $data->{size} ); # The size in bypes! May be zero!
2986              
2987             # Is it a directory? If so, we'd like to skip executing "STAT".
2988             } elsif ( exists $data->{type} ) {
2989 0           my $t = $data->{type};
2990 0 0 0       $skip_stat = 1 if ( $t eq "dir" || $t eq "cdir" || $t eq "pdir" );
      0        
2991             }
2992              
2993 0           warn ("Turn on SIZE feature with OPTS before using this function!\n");
2994             }
2995             }
2996              
2997             # Note: If $file is in fact a directory, STAT will return the directory's
2998             # contents! Which can be very slow if there are tons of files in the dir!
2999 0 0 0       if ( $self->supported ("STAT") && ! $skip_stat ) {
3000 0 0         if ( $self->command ("STAT", $file, @_)->response () == CMD_OK ) {
3001 0           my @msg = split ("\n", $self->message ());
3002 0           my $cnt = @msg;
3003 0           my $rFile = $self->_mask_regex_chars ( basename ($file) );
3004              
3005             # ... Size Filename
3006 0 0 0       if ( $cnt == 3 && $msg[1] =~ m/\s(\d+)\s+${rFile}/ ) {
3007 0           return ( $1 ); # The size in bytes! May be zero!
3008             }
3009             # ... Size Month Day HH:MM Filename
3010 0 0 0       if ( $cnt == 3 && $msg[1] =~ m/\s(\d+)\s+(\S+)\s+(\d+)\s+(\d+:\d+)\s+${rFile}/ ) {
3011 0           return ( $1 ); # The size in bytes! May be zero!
3012             }
3013             }
3014             }
3015              
3016 0           return ( $self->_test_croak (undef) ); # It's not a regular file!
3017             }
3018              
3019             sub is_file {
3020 0     0 1   my $self = shift;
3021 0           my $file = shift;
3022              
3023 0           my $isFile = 0; # Assume not a regular file ...
3024              
3025             # Now let's disable Croak so we can't die during this test ...
3026 0           my $die = $self->set_croak (0);
3027              
3028             # Not implemented on many FTPS servers ...
3029             # But it's the most reliable way if it is ...
3030 0 0         if ( $self->supported ("MLST") ) {
3031             # Must use "OPTS MLST TYPE" if the type feature is currently disabled.
3032 0           my $data = $self->parse_mlsx ( $self->mlst ($file), 1 );
3033              
3034             # We now know something was found, but we don't yet know what it is!
3035 0 0         if ( $data ) {
3036 0 0         if ( exists $data->{type} ) {
3037 0           my $t = $data->{type};
3038 0 0         $isFile = ( $t eq "file" ) ? 1 : 0;
3039 0           $self->set_croak ( $die ); # Restore the croak settings!
3040 0           return ( $isFile );
3041             }
3042 0           warn ("Turn on TYPE feature with OPTS before using this function!\n");
3043             }
3044             }
3045              
3046 0           my $size = $self->size ( $file, 1 );
3047              
3048 0           $self->set_croak ( $die ); # Restore the croak settings!
3049              
3050 0 0 0       if ( defined $size && $size >= 0 ) {
3051 0           return ( 1 ); # It's a plain file! We successfully got it's size!
3052             }
3053              
3054 0           return ( 0 ); # It's not a plain file or it doesn't exist!
3055             }
3056              
3057             sub is_dir {
3058 0     0 1   my $self = shift;
3059 0           my $dir = shift;
3060              
3061 0           my $isDir = 0; # Assume not a directory ...
3062              
3063             # The current direcory!
3064 0           my $curDir = $self->pwd ();
3065              
3066             # Now let's disable Croak so we can't die during this test ...
3067 0           my $die = $self->set_croak (0);
3068              
3069             # Not implemented on many FTPS servers ...
3070             # But it's the most reliable way if it is ...
3071 0 0         if ( $self->supported ("MLST") ) {
3072             # Must use "OPTS MLST TYPE" if the type feature is currently disabled.
3073 0           my $data = $self->parse_mlsx ( $self->mlst ($dir), 1 );
3074              
3075             # We now know something was found, but we don't yet know what it is!
3076 0 0         if ( $data ) {
3077 0 0         if ( exists $data->{type} ) {
3078 0           my $t = $data->{type};
3079 0 0 0       $isDir = ( $t eq "dir" || $t eq "cdir" || $t eq "pdir" ) ? 1 : 0;
3080 0           $self->set_croak ( $die ); # Restore the croak settings!
3081 0           return ( $isDir );
3082             }
3083 0           warn ("Turn on TYPE feature with OPTS before using this function!\n");
3084             }
3085             }
3086              
3087             # Check if it's a directory we have access to ...
3088 0 0         if ( $self->cwd ( $dir ) ) {
3089 0           $self->cwd ( $curDir );
3090 0           $isDir = 1;
3091              
3092             } else {
3093             # At this point if it's really a directory, we don't have access to it.
3094             # And parsing error messages really isn't an option.
3095              
3096             # So what if we now assume it it might be a directory if "is_file()"
3097             # returns false and we can see that the file does exists via "nlst()"?
3098              
3099             # I don't really like that no-access test, too many chances for false
3100             # positives, so I'm open to better ideas! I'll leave this code disabled
3101             # until I can mull this over some more.
3102              
3103             # Currently disabled ...
3104 0           if ( 1 != 1 ) {
3105             # If it isn't a regular file, then it might yet still be a directory!
3106             unless ( $self->is_file ( $dir ) ) {
3107             # Now check if we can see a file of this name ...
3108             my @lst = $self->nlst (dirname ($dir), basename ($dir));
3109             if ( scalar (@lst) ) {
3110             # It may or may not be a directory ...
3111             $self->_print_DBG ("--- Found match: ", $lst[0], "\n");
3112             $isDir = 1;
3113             }
3114             }
3115             }
3116             }
3117              
3118 0           $self->set_croak ( $die ); # Restore the croak settings!
3119              
3120 0           return ( $isDir );
3121             }
3122              
3123             sub copy_cc_to_dc {
3124 0     0 1   my $self = shift;
3125 0 0         my $args = (ref ($_[0]) eq "ARRAY") ? $_[0] : \@_;
3126              
3127 0           my %dcValues;
3128 0 0         if ( exists ${*$self}{_FTPSSL_arguments}->{myContext} ) {
  0            
3129 0           %dcValues = %{${*$self}{_FTPSSL_arguments}->{myContext}};
  0            
  0            
3130             }
3131              
3132 0           my $cnt = 0;
3133 0           foreach ( @{$args} ) {
  0            
3134 0           my $val;
3135 0 0         if ( exists ${*$self}{_SSL_arguments}->{$_} ) {
  0 0          
    0          
3136 0           $val = ${*$self}{_SSL_arguments}->{$_};
  0            
3137              
3138 0           } elsif ( exists ${*$self}{_FTPSSL_arguments}->{start_SSL_opts}->{$_} ) {
3139 0           $val = ${*$self}{_FTPSSL_arguments}->{start_SSL_opts}->{$_};
  0            
3140              
3141 0           } elsif ( exists ${*$self}{$_} ) {
3142 0           $val = ${*$self}{$_};
  0            
3143              
3144             } else {
3145 0           $self->_print_DBG ("No such Key defined for the CC: ", $_, "\n");
3146 0           next;
3147             }
3148              
3149 0           $dcValues{$_} = $val;
3150 0           ++$cnt;
3151             }
3152              
3153             # Update with the new Data Channel options ...
3154 0 0         if ( $cnt > 0 ) {
3155 0           ${*$self}{_FTPSSL_arguments}->{myContext} = \%dcValues;
  0            
3156             }
3157              
3158 0 0         if ( ${*$self}{_FTPSSL_arguments}->{debug} ) {
  0            
3159 0           $self->_debug_print_hash ( "DC Hash", "options", "cc2dc($cnt)", \%dcValues, "#" );
3160             }
3161              
3162 0           return ( $cnt );
3163             }
3164              
3165             sub set_dc_from_hash {
3166 0     0 1   my $self = shift;
3167 0 0         my $args = (ref ($_[0]) eq "HASH") ? $_[0] : {@_};
3168              
3169 0           my %dcValues;
3170 0 0         if ( exists ${*$self}{_FTPSSL_arguments}->{myContext} ) {
  0            
3171 0           %dcValues = %{${*$self}{_FTPSSL_arguments}->{myContext}};
  0            
  0            
3172             }
3173              
3174 0           my $cnt = 0;
3175 0           foreach my $key ( keys %{$args} ) {
  0            
3176 0           my $val = $args->{$key};
3177              
3178 0 0         if ( defined $val ) {
    0          
3179             # Add the requested value to the DC hash ...
3180 0           $dcValues{$key} = $val;
3181 0           ++$cnt;
3182              
3183             } elsif ( exists $dcValues{$key} ) {
3184             # Delete the requested value from the DC hash ...
3185 0           delete $dcValues{$key};
3186 0           ++$cnt;
3187             }
3188             }
3189              
3190             # Update with the new Data Channel options ...
3191 0 0         if ( $cnt > 0 ) {
3192 0           ${*$self}{_FTPSSL_arguments}->{myContext} = \%dcValues;
  0            
3193             }
3194              
3195 0 0         if ( ${*$self}{_FTPSSL_arguments}->{debug} ) {
  0            
3196 0           $self->_debug_print_hash ( "DC Hash", "options", "setdc($cnt)", \%dcValues, "%" );
3197             }
3198              
3199 0           return ( $cnt );
3200             }
3201              
3202             #-----------------------------------------------------------------------
3203             # Checks what commands are available on the remote server
3204             # If a "*" follows a command, it's unimplemented!
3205             # The caller is free to modify the returned hash refrence.
3206             # It's just a copy of what's been cached, not the original!
3207             #-----------------------------------------------------------------------
3208             # The returned hash may contain both active & disabled FTP commands.
3209             # If disabled, the command's value will be 0. Otherwise it will
3210             # contain a non-zero value. So testing using "exists" is BAD form now.
3211             #-----------------------------------------------------------------------
3212             # Please remember that when OverrideHELP=>1 is used, it will always
3213             # return an empty hash!!!
3214             #-----------------------------------------------------------------------
3215              
3216             sub _help {
3217             # Only shift off self, bug otherwise!
3218 0     0     my $self = shift;
3219 0   0       my $cmd = uc ($_[0] || ""); # Converts undef to "". (Do not do a shift!)
3220              
3221             # Check if requesting a list of all commands or details on specific commands.
3222 0           my $all_cmds = ($cmd eq "");
3223 0           my $site_cmd = ($cmd eq "SITE");
3224              
3225 0           my %help;
3226 0           my $arg = ${*$self}{_FTPSSL_arguments};
  0            
3227              
3228             # Only possible if _help() is called before 1st call to supported()!
3229 0 0 0       unless ( $all_cmds || exists $arg->{help_cmds_msg} ) {
3230 0           $self->_help ();
3231             }
3232              
3233             # Use FEAT instead of HELP to populate the supported hash!
3234             # Assuming the HELP command itself is broken! "via OverrideHELP=>-1"
3235 0 0 0       if ( exists $arg->{removeHELP} && $arg->{removeHELP} == 1 ) {
3236 0           my $ft = $self->_feat ();
3237 0 0         $ft->{FEAT} = 2 if (scalar (keys %{$ft}) > 0);
  0            
3238 0           foreach ( keys %{$ft} ) { $ft->{$_} = 2; } # So always TRUE
  0            
  0            
3239              
3240 0           $arg->{help_cmds_found} = $ft;
3241 0           $arg->{help_cmds_msg} = $self->last_message ();
3242              
3243 0           $self->_site_help ( $arg->{help_cmds_found} );
3244 0           $arg->{removeHELP} = 2; # So won't execute again ...
3245             }
3246              
3247             # Now see if we've cached any results previously ...
3248 0           my $key;
3249 0 0 0       if ($all_cmds && exists $arg->{help_cmds_msg}) {
    0          
    0          
3250 0           $arg->{last_ftp_msg} = $arg->{help_cmds_msg};
3251 0           $key = "help_cmds_found";
3252 0 0         %help = %{$arg->{$key}} if ( exists $arg->{$key} );
  0            
3253 0           return ( \%help );
3254              
3255             } elsif (exists $arg->{"help_${cmd}_msg"}) {
3256 0           $arg->{last_ftp_msg} = $arg->{"help_${cmd}_msg"};
3257 0           $key = "help_${cmd}_found";
3258 0 0         %help = %{$arg->{$key}} if ( exists $arg->{$key} );
  0            
3259 0           return ( \%help );
3260              
3261             } elsif ( exists $arg->{help_cmds_no_syntax_available} ) {
3262 0 0 0       if ( exists $arg->{help_cmds_found}->{$cmd} || $arg->{OverrideHELP} ) {
3263 0           $arg->{last_ftp_msg} = "503 Syntax for ${cmd} is not available.";
3264             } else {
3265 0           $arg->{last_ftp_msg} = "501 Unknown command ${cmd}.";
3266             }
3267             # $self->_print_DBG ( "<<+ ", $self->last_message (), "\n" );
3268 0           return ( \%help ); # The empty hash ...
3269             }
3270              
3271             # From here on out, we will get at least one server hit ...
3272              
3273 0           my $sts;
3274 0 0         if ($all_cmds) {
3275 0           $sts = $self->command ("HELP")->response ();
3276 0           $arg->{help_cmds_msg} = $self->last_message ();
3277 0 0         $arg->{help_cmds_no_syntax_available} = 1 if ( $sts != CMD_OK );
3278             } else {
3279 0           $sts = $self->command ("HELP", @_)->response ();
3280 0           $arg->{"help_${cmd}_msg"} = $self->last_message ();
3281             }
3282              
3283             # If failure, return empty hash ...
3284 0 0         return (\%help) if ( $sts != CMD_OK );
3285              
3286             # Check if "HELP" & "HELP CMD" return the same thing ...
3287 0 0 0       if ( (! $all_cmds) && $arg->{help_cmds_msg} eq $self->last_message () ) {
3288 0           $arg->{help_cmds_no_syntax_available} = 1;
3289 0           delete $arg->{"help_${cmd}_msg"}; # Delete this wrong message ...
3290 0           return ( $self->_help ($cmd) ); # Recursive to get the right error msg!
3291             }
3292              
3293             # HELP ...
3294 0 0         if ( $all_cmds ) {
    0          
3295 0           %help = %{$self->_help_parse (0)};
  0            
3296              
3297             # If we don't find anything for HELP, it's a problem.
3298             # So don't cache if false ...
3299 0 0         if (scalar (keys %help) > 0) {
3300 0 0         if ($help{FEAT}) {
3301             # Now put any new features into the help response as well ...
3302 0           my $feat = $self->_feat ();
3303 0           foreach (keys %{$feat}) {
  0            
3304 0 0         $help{$_} = 2 unless (exists $help{$_});
3305             }
3306             }
3307              
3308 0           my %siteHelp;
3309             my $msg;
3310 0 0         if ($help{SITE}) {
3311             # See if this returns a usage statement or a list of SITE commands!
3312 0           %siteHelp = %{$self->_help ("SITE")};
  0            
3313 0 0         $msg = $self->message () if ( $self->last_status_code() == CMD_OK );
3314             }
3315              
3316             # Do only if no SITE details yet ...
3317 0 0         if (scalar (keys %siteHelp) == 0) {
3318 0           $self->_site_help (\%help, $msg);
3319             }
3320              
3321 0           my %lclHelp = %help;
3322 0           $arg->{help_cmds_found} = \%lclHelp;
3323             }
3324              
3325             # HELP SITE ...
3326             } elsif ( $site_cmd ) {
3327 0           %help = %{$self->_help_parse (1)};
  0            
3328              
3329             # If we find something, it means it's returning the list of SITE commands.
3330             # Some servers do this rather than returning a syntax statement.
3331 0 0         if (scalar (keys %help) > 0) {
3332 0           my %siteHelp = %help;
3333 0           $arg->{help_SITE_found} = \%siteHelp;
3334             }
3335              
3336             # HELP some_other_command ...
3337             } else {
3338             # Nothing really to do here ...
3339             }
3340              
3341 0           return (\%help);
3342             }
3343              
3344             #---------------------------------------------------------------------------
3345             # Try to get a list of SITE commands supported.
3346             #---------------------------------------------------------------------------
3347             sub _site_help
3348             {
3349 0     0     my $self = shift;
3350 0           my $help = shift; # Parent help hash
3351 0           my $msg = shift; # Optional override message.
3352              
3353 0           my $arg = ${*$self}{_FTPSSL_arguments};
  0            
3354              
3355             # Not calling site() in case Croak is turned on.
3356             # It's not a fatal error if this call fails ...
3357             # my $ok = $self->site ("HELP");
3358 0           my $ok = ($self->command("SITE", "HELP")->response() == CMD_OK);
3359              
3360 0           $arg->{help_SITE_msg} = $self->last_message ();
3361              
3362 0 0         if ( $ok ) {
3363 0           my $siteHelp = $self->_help_parse (1);
3364              
3365 0 0         if (scalar (keys %{$siteHelp}) > 0) {
  0            
3366 0 0         if ( defined $help ) {
3367 0 0         $help->{SITE} = -1 unless ( exists $help->{SITE} );
3368             }
3369 0 0         $siteHelp->{HELP} = -1 unless ( exists $siteHelp->{HELP} );
3370 0           $arg->{help_SITE_found} = $siteHelp;
3371              
3372             # Only do optional override of the cached message on success!
3373 0 0         $arg->{help_SITE_msg} = $msg if ( $msg );
3374             }
3375             }
3376              
3377 0           return;
3378             }
3379              
3380             #---------------------------------------------------------------------------
3381             # Handles the parsing of the "HELP", "HELP SITE" & "SITE HELP" commands ...
3382             # Not all servers return a list of commands for the 2nd two items.
3383             #---------------------------------------------------------------------------
3384             sub _help_parse {
3385 0     0     my $self = shift;
3386 0           my $site_cmd = shift; # Only 0 for HELP.
3387              
3388             # This value is used to distinguish which call set the hash entry.
3389             # No logic is based on it. Just done to ease debugging later on!
3390 0 0         my $flag = ($site_cmd) ? -2 : 1;
3391              
3392 0           my $helpmsg = $self->last_message ();
3393 0           my @lines = split (/\n/, $helpmsg);
3394              
3395 0           my %help;
3396              
3397 0           foreach my $line (@lines) {
3398             # Strip off the code & separator or leading blanks if multi line.
3399 0           $line =~ s/((^[0-9]+[\s-]?)|(^\s*))//;
3400 0           my $lead = $1;
3401              
3402 0 0         next if ($line eq "");
3403              
3404             # Skip over the start/end part of the response ...
3405             # Doesn't work for all servers!
3406             # next if ( defined $lead && $lead =~ m/^\d+[\s-]?$/ );
3407              
3408             # Make sure no space between command & the * that marks it unsupported!
3409             # May be more than one hit per line ...
3410 0           $line =~ s/(\S)\s+[*]($|\s|,)/$1*$2/g;
3411              
3412 0           my @lst = split (/[\s,.]+/, $line); # Break into individual commands
3413              
3414 0 0 0       if ( $site_cmd && $lst[0] eq "SITE" && $lst[1] =~ m/^[A-Z]+$/ ) {
    0 0        
3415 0           $help{$lst[1]} = 1; # Each line: "SITE CMD mixed-case-usage"
3416             }
3417             # Now only process if nothing is in lower case (ie: its a comment)
3418             # All commands must be in upper case, some special chars not allowed.
3419             # Commands ending in "*" are currently turned off.
3420             elsif ( $line !~ m/[a-z()]/ ) {
3421 0           foreach (@lst) {
3422             # $help{$_} = 1 if ($_ !~ m/[*]$/);
3423 0 0         if ($_ !~ m/^(.+)[*]$/) {
    0          
3424 0           $help{$_} = $flag; # Record enabled for all options ...
3425             } elsif ( $site_cmd == 0 ) {
3426 0           $help{$1} = 0; # Record command is disabled for HELP.
3427             }
3428             }
3429             }
3430             }
3431              
3432 0           return (\%help);
3433             }
3434              
3435             #-----------------------------------------------------------------------
3436             # Returns a hash of features supported by this server ...
3437             # It's always uses the cache after the 1st call ... this list never changes!
3438             # Making this a static list!
3439             # This is the version used internally by _help & supported!
3440             #-----------------------------------------------------------------------
3441              
3442             sub _feat {
3443 0     0     my $self = shift;
3444              
3445 0           my $arg = ${*$self}{_FTPSSL_arguments};
  0            
3446              
3447             # Check to see if we've cached the result previously ...
3448             # Must use slightly different naming convenion than used
3449             # in _help() to avoid conflicts. [set in feat()]
3450 0 0         if (exists $arg->{help_FEAT_msg2}) {
3451 0           $arg->{last_ftp_msg} = $arg->{help_FEAT_msg2};
3452 0           my %hlp = %{$arg->{help_FEAT_found2}};
  0            
3453 0           return ( \%hlp );
3454             }
3455              
3456 0           my $res = $self->feat (1); # Undocumented opt to disable Croak if on!
3457              
3458 0           return ($res); # Feel free to modify it if you wish! Won't harm anything!
3459             }
3460              
3461              
3462             #-----------------------------------------------------------------------
3463             # Returns a hash of features supported by this server ...
3464             # It's conditionally cached based on the results of the 1st call to FEAT!
3465             # So on some servers this list will be static, while on others dynamic!
3466             #-----------------------------------------------------------------------
3467             # The FEAT command returns one line per command, with optional behaviors.
3468             # If the command ends in "*", the command isn't supported by FEAT!
3469             # And if not supported it won't show up in the hash!
3470             # Format: CMD [behavior]
3471             #-----------------------------------------------------------------------
3472             # If one or more commands have behaviors, then it's possible for the
3473             # results of the FEAT command to change based on calls to
3474             # "OPTS CMD behavior"
3475             # So if even one command has a behavior, there will be a server hit
3476             # to see if the FEAT results changed. It will also add OPTS to the hash!
3477             # Otherwise the results are cached!
3478             #-----------------------------------------------------------------------
3479             # Note: {help_FEAT_found2} & {help_FEAT_msg2} are used here since it's
3480             # possible that {help_FEAT_found} & {help_FEAT_msg} can be auto
3481             # generated via "HELP FEAT" [during a call to _help("FEAT").]
3482             # These special vars are only used in feat() & _feat().
3483             #-----------------------------------------------------------------------
3484             sub feat {
3485 0     0 1   my $self = shift;
3486 0           my $disable_croak = shift; # Undocumented option in POD on purpose!
3487             # Only used when called from _feat()!
3488              
3489 0           my $arg = ${*$self}{_FTPSSL_arguments};
  0            
3490              
3491 0           my %res;
3492              
3493             # Conditionally use the cache if the server will always return a static list!
3494             # It will be static if the OPTS command isn't supported!
3495 0 0 0       if ( exists $arg->{help_FEAT_found2} &&
3496             ! exists $arg->{help_FEAT_found2}->{OPTS} ) {
3497 0           $arg->{last_ftp_msg} = $arg->{help_FEAT_msg2};
3498 0           %res = %{$arg->{help_FEAT_found2}};
  0            
3499 0           return ( \%res );
3500             }
3501              
3502             # Check if a request has been made to not honor HELP if in the FEAT list.
3503 0           my $remove = "";
3504 0 0         if ( $arg->{removeHELP} ) {
    0          
3505 0           $remove = "-1";
3506             } elsif ( exists $arg->{OverrideHELP} ) {
3507 0           $remove = $arg->{OverrideHELP};
3508 0 0         if ( $remove == 0 ) {
3509 0           my @lst = keys %{$arg->{help_cmds_found}};
  0            
3510 0 0         $remove = "[array]" if ($#lst != -1);
3511             }
3512             }
3513 0           my $mask = "HELP * OverrideHELP=>${remove} says to remove this command.";
3514              
3515             # Check if a request has been made to not honor HELP if in the FEAT list.
3516 0 0         if ( $remove ne "" ) {
3517 0           $arg->{_hide_value_in_response_} = "HELP";
3518 0           $arg->{_mask_value_in_response_} = $mask;
3519             }
3520              
3521 0           my $status = $self->command ("FEAT")->response ();
3522              
3523 0 0         if ( $remove ne "" ) {
3524 0           $arg->{last_ftp_msg} =~ s/HELP/<${mask}>/i;
3525 0           delete $arg->{_hide_value_in_response_};
3526 0           delete $arg->{_mask_value_in_response_};
3527             }
3528              
3529 0 0         if ( $status == CMD_OK ) {
3530 0           my @lines = split (/\n/, $self->last_message ());
3531 0           my %behave;
3532              
3533 0           my $may_change = 0; # Assume always returns an unchanging list ...
3534 0           foreach my $line (@lines) {
3535             # Strip off the code & separator or leading blanks if multi line.
3536 0           $line =~ s/((^[0-9]+[\s-]?)|(^\s*))//;
3537 0           my $lead = $1;
3538              
3539             # Skip over the start/end part of the response ...
3540 0 0 0       next if ( defined $lead && $lead =~ m/^\d+[\s-]?$/ );
3541              
3542 0 0         next if ( $line eq "" ); # Skip over all blank lines
3543              
3544 0           my @part = split (/\s+/, $line);
3545              
3546             # Command ends in "*" or the next part is "*" ???
3547             # Used to conditionally remove the HELP cmd if necessary ...
3548             # Otherwise not sure if this test ever really happens ...
3549 0 0 0       next if ($part[0] =~ m/[*]$/ || (defined $part[1] && $part[1] eq "*"));
      0        
3550              
3551             # The value is the rest of the command ...
3552 0 0         if ( $#part == 0 ) {
3553 0           $res{$part[0]} = ""; # No behavior defined.
3554             } else {
3555             # Save the behavior!
3556 0           $behave{$part[0]} = $res{$part[0]} = (split (/\s+/, $line, 2))[1];
3557 0           $may_change = 1;
3558             }
3559             }
3560              
3561 0 0         if ( $may_change ) {
3562             # Added per RFC 2389: It says OPTS is an assumed command if FEAT is
3563             # supported. But some servers fail to implement OPTS if there are
3564             # no features it can modify. So adding OPTS to the hash only if at
3565             # least one FEAT command has a behavior string defined!
3566             # If no behaviors are defined it will assume the OPTS command isn't
3567             # supported after all!
3568 0 0         my $msg = (exists $res{OPTS}) ? "Updating OPTS Command!"
3569             : "Auto-adding OPTS Command!";
3570 0           $self->_print_DBG ("<<+ ", CMD_INFO, "11 ", $msg, "\n");
3571              
3572             # Adding hash reference to list all valid OPTS commands ...
3573 0           $res{OPTS} = \%behave;
3574             }
3575             }
3576              
3577             # Only cache the results if the 1st time called! This cache is only used by
3578             # this method if OPTS is not supported! But its always used by _feat()!
3579 0 0         unless ( exists $arg->{help_FEAT_msg2} ) {
3580 0           my %res2 = %res;
3581 0           $arg->{help_FEAT_found2} = \%res2;
3582 0           $arg->{help_FEAT_msg2} = $self->last_message ();
3583             }
3584              
3585 0 0 0       unless ( $status == CMD_OK || $disable_croak ) {
3586 0           $self->_croak_or_return ();
3587             }
3588              
3589 0           return (\%res); # The caller is free to modify the hash if they wish!
3590             }
3591              
3592             #-----------------------------------------------------------------------
3593             # Enable/Disable the Croak logic!
3594             # Returns the previous Croak setting!
3595             #-----------------------------------------------------------------------
3596              
3597             sub set_croak {
3598 0     0 1   my $self = shift;
3599 0           my $turn_on = shift;
3600              
3601 0   0       my $res = ${*$self}{_FTPSSL_arguments}->{Croak} || 0;
3602              
3603 0 0         if ( defined $turn_on ) {
3604 0 0         if ( $turn_on ) {
    0          
3605 0           ${*$self}{_FTPSSL_arguments}->{Croak} = 1;
  0            
3606 0           } elsif ( exists ( ${*$self}{_FTPSSL_arguments}->{Croak} ) ) {
3607 0           delete ( ${*$self}{_FTPSSL_arguments}->{Croak} );
  0            
3608             }
3609             }
3610              
3611 0           return ( $res );
3612             }
3613              
3614             #-----------------------------------------------------------------------
3615             # Boolean check for croak!
3616             # Uses the current message as the croak message on error!
3617             #-----------------------------------------------------------------------
3618              
3619             sub _test_croak {
3620 0     0     my $self = shift;
3621 0           my $true = shift;
3622              
3623 0 0         unless ( $true ) {
3624 0           $ERRSTR = ${*$self}{_FTPSSL_arguments}->{last_ftp_msg};
  0            
3625 0 0         if ( ${*$self}{_FTPSSL_arguments}->{Croak} ) {
  0            
3626 0           my $c = (caller(1))[3];
3627 0 0 0       if ( defined $c && $c ne "Net::FTPSSL::login" ) {
3628 0           $self->_abort ();
3629 0           $self->quit ();
3630 0           ${*$self}{_FTPSSL_arguments}->{last_ftp_msg} = $ERRSTR;
  0            
3631             }
3632              
3633 0           croak ( $ERRSTR . "\n" );
3634             }
3635             }
3636              
3637 0           return ( $true );
3638             }
3639              
3640             #-----------------------------------------------------------------------
3641             # Error handling - Decides if to Croak or return undef ...
3642             # Has 2 modes, a regular member func & when not a member func ...
3643             #-----------------------------------------------------------------------
3644              
3645             sub _croak_or_return {
3646 0     0     my $self = shift;
3647              
3648             # The error code to use if we update the last message!
3649             # Or if we print it to FTPS_ERROR & we don't croak!
3650 0           my $err = CMD_ERROR . CMD_ERROR . CMD_ERROR;
3651              
3652 0 0         unless (defined $self) {
3653             # Called this way only by new() before $self is created ...
3654 0           my $should_we_die = shift;
3655 0           my $should_we_print = shift;
3656 0   0       $ERRSTR = shift || "Unknown Error";
3657              
3658 0 0         _print_LOG ( undef, "<<+ $err ", $ERRSTR, "\n" ) if ( $should_we_print );
3659 0 0         croak ( $ERRSTR . "\n" ) if ( $should_we_die );
3660              
3661             } else {
3662             # Called this way as a memeber func by everyone else ...
3663 0           my $replace_mode = shift; # 1 - append, 0 - replace,
3664             # undef - leave last_message() unchanged
3665 0           my $msg = shift;
3666 0   0       $ERRSTR = $msg || ${*$self}{_FTPSSL_arguments}->{last_ftp_msg};
3667              
3668             # Do 1st so updated if caller trapped the Croak!
3669 0 0 0       if ( defined $replace_mode && uc ($msg || "") ne "" ) {
      0        
3670 0 0 0       if ($replace_mode && uc (${*$self}{_FTPSSL_arguments}->{last_ftp_msg} || "") ne "" ) {
      0        
3671 0           ${*$self}{_FTPSSL_arguments}->{last_ftp_msg} .= "\n" . $err . " " . $msg;
  0            
3672             } else {
3673 0           ${*$self}{_FTPSSL_arguments}->{last_ftp_msg} = $err . " " . $msg;
  0            
3674             }
3675             }
3676              
3677 0 0         if ( ${*$self}{_FTPSSL_arguments}->{Croak} ) {
  0            
3678 0   0       my $c = (caller(1))[3] || "";
3679              
3680             # Trying to prevent infinite recursion ...
3681             # Also reseting the PIPE Signal in case catastrophic failure detected!
3682 0 0 0       if ( ref($self) eq __PACKAGE__ &&
      0        
      0        
      0        
3683 0           (! exists ${*$self}{_FTPSSL_arguments}->{_command_failed_}) &&
3684 0           (! exists ${*$self}{_FTPSSL_arguments}->{recursion}) &&
3685             $c ne "Net::FTPSSL::command" &&
3686             $c ne "Net::FTPSSL::response" ) {
3687 0           ${*$self}{_FTPSSL_arguments}->{recursion} = "TRUE";
  0            
3688 0           my $tmp = ${*$self}{_FTPSSL_arguments}->{last_ftp_msg};
  0            
3689 0           local $SIG{PIPE} = "IGNORE"; # Limits scope to just current block!
3690 0           $self->_abort ();
3691 0           $self->quit ();
3692 0           ${*$self}{_FTPSSL_arguments}->{last_ftp_msg} = $tmp;
  0            
3693             }
3694              
3695             # Only do if writing the message to the error log file ...
3696 0 0 0       if ( defined $replace_mode && uc ($msg || "") ne "" &&
      0        
      0        
3697 0           ${*$self}{_FTPSSL_arguments}->{debug} == 2 ) {
3698 0           _print_LOG ( $self, "<<+ $err ", $msg, "\n" );
3699             }
3700              
3701 0           croak ( $ERRSTR . "\n" );
3702             }
3703              
3704             # Handles both cases of writing to STDERR or the error log file ...
3705 0 0 0       if ( defined $replace_mode && uc ($msg || "") ne "" && ${*$self}{_FTPSSL_arguments}->{debug} ) {
  0   0        
      0        
3706 0           _print_LOG ( $self, "<<+ $err " . $msg . "\n" );
3707             }
3708             }
3709              
3710 0           return ( undef );
3711             }
3712              
3713             #-----------------------------------------------------------------------
3714             # Messages handler
3715             # ----------------------------------------------------------------------
3716             # Called by both Net::FTPSSL and IO::Socket::INET classes.
3717             #-----------------------------------------------------------------------
3718              
3719             sub command {
3720 0     0 0   my $self = shift; # Remaining arg(s) accessed directly.
3721              
3722 0           my @args;
3723             my $data;
3724              
3725             # Remove any previous failure ...
3726 0           delete ( ${*$self}{_FTPSSL_arguments}->{_command_failed_} );
  0            
3727              
3728             # remove undef values from the list.
3729             # Maybe I have to find out why those undef were passed.
3730 0           @args = grep ( defined($_), @_ );
3731              
3732             $data = join( " ",
3733 0           map { /\n/
3734 0 0         ? do { my $n = $_; $n =~ tr/\n/ /; $n }
  0            
  0            
  0            
3735             : $_;
3736             } @args
3737             );
3738              
3739             # Log the command being executed ...
3740 0 0         if ( ${*$self}{_FTPSSL_arguments}->{debug} ) {
  0            
3741 0 0         my $prefix = ( ref($self) eq __PACKAGE__ ) ? ">>> " : "SKT >>> ";
3742 0 0         if ( $data =~ m/^PASS\s/ ) {
    0          
3743 0           _print_LOG ( $self, $prefix, "PASS *******\n" ); # Don't echo passwords
3744             } elsif ( $data =~ m/^USER\s/ ) {
3745 0           _print_LOG ( $self, $prefix, "USER +++++++\n" ); # Don't echo user names
3746             } else {
3747 0           _print_LOG ( $self, $prefix, $data, "\n" ); # Echo everything else
3748             }
3749             }
3750              
3751 0           $data .= "\015\012";
3752              
3753 0           my $len = length $data;
3754 0           my $written = syswrite( $self, $data, $len );
3755 0 0         unless ( defined $written ) {
3756 0           ${*$self}{_FTPSSL_arguments}->{_command_failed_} = "ERROR";
  0            
3757 0           my $err_msg = "Can't write command on socket: $!";
3758 0           carp "$err_msg"; # This prints a warning.
3759             # Not called as an object member in case $self not a Net::FTPSSL obj.
3760 0           _my_close ($self); # Old way $self->close();
3761 0           _croak_or_return ($self, 0, $err_msg);
3762 0           return $self; # Included here due to non-standard _croak_or_return() usage.
3763             }
3764              
3765 0           return $self; # So can directly call response()!
3766             }
3767              
3768             # -----------------------------------------------------------------------------
3769             # Some responses take multiple lines to finish. ("211-" [more] vs "211 " [done])
3770             # Some responses have CR's embeded in them. (ie: no code in the next line)
3771             # Sometimes the data channel response comes with the open data connection msg.
3772             # (Especially if the data channel is not encrypted or the file is small.)
3773             # So be careful, you will be blocked if you read past the last row of the
3774             # current response or return the wrong code if you get into the next response!
3775             # (And will probably hang the next time response() is called.)
3776             # So far the only thing I haven't seen is a call to sysread() returning a
3777             # partial line response! (Drat, that just happened! See 0.20 Change notes.)
3778             # -----------------------------------------------------------------------------
3779             # Called by both Net::FTPSSL and IO::Socket::INET classes.
3780             # Hence using func($self, ...) instead of $self->func(...)
3781             # -----------------------------------------------------------------------------
3782             # Returns a single digit response code! (The CMD_* constants!)
3783             # -----------------------------------------------------------------------------
3784             sub response {
3785 0     0 0   my $self = shift;
3786 0   0       my $ccc_mess = shift || 0; # Only set by the CCC command! Hangs if not used.
3787              
3788             # The buffer size to use during the sysread() call on the command channel.
3789 0           my $buffer_size = 4096;
3790              
3791             # Uncomment to experiment with variable buffer sizes.
3792             # Very usefull in debugging _response_details () & simulating server issues.
3793             # Supports any value >= 1.
3794             # $buffer_size = 10;
3795              
3796             # The warning to use when printing past the end of the current response!
3797             # Used in place of $prefix in certain conditions.
3798 0           my $warn = "Warning: Attempted to read past end of response! ";
3799              
3800             # Only continue if the command() call worked!
3801             # Otherwise on failure this method will hang!
3802             # We already printed out the failure message in command() if not croaking!
3803 0 0         return (CMD_ERROR) if ( exists ${*$self}{_FTPSSL_arguments}->{_command_failed_} );
  0            
3804              
3805 0           ${*$self}{_FTPSSL_arguments}->{last_ftp_msg} = ""; # Clear out the message
  0            
3806 0 0         my $prefix = ( ref($self) eq __PACKAGE__ ) ? "<<< " : "SKT <<< ";
3807              
3808 0           my $timeout = ${*$self}{_FTPSSL_arguments}->{Timeout};
  0            
3809              
3810 0 0 0       my $sep = ( ${*$self}{_FTPSSL_arguments}->{debug} && ${*$self}{_FTPSSL_arguments}->{debug_extra} ) ? "===============" : undef;
3811              
3812             # Starting a new message ...
3813 0           ${*$self}{_FTPSSL_arguments}->{last_ftp_msg} = "";
  0            
3814 0           my $data = "";
3815 0           my ($done, $complete) = (0, 1);
3816              
3817             # Check if we need to process anything read in past the previous command.
3818             # Hopefully under normal conditions we'll find nothing to process.
3819 0 0         if ( exists ${*$self}{_FTPSSL_arguments}->{next_ftp_msg} ) {
  0            
3820 0 0         _print_LOG ( $self, "Info: Response found from previous read ...\n") if ( ${*$self}{_FTPSSL_arguments}->{debug} );
  0            
3821 0           $data = ${*$self}{_FTPSSL_arguments}->{next_ftp_msg};
  0            
3822 0           delete ( ${*$self}{_FTPSSL_arguments}->{next_ftp_msg} );
  0            
3823 0           ($done, $complete) = _response_details ($self, $prefix, \$data, 0, $ccc_mess);
3824 0 0 0       if ( $done && $complete ) {
3825 0           _print_edited_response ( $self, $prefix, ${*$self}{_FTPSSL_arguments}->{last_ftp_msg}, $sep, 0 );
  0            
3826 0           _print_edited_response ( $self, $warn, ${*$self}{_FTPSSL_arguments}->{next_ftp_msg}, $sep, 2 );
  0            
3827 0           return last_status_code ( $self );
3828             }
3829              
3830             # Should never happen, but using very short timeout on continued commands.
3831 0           $timeout = 2;
3832             }
3833              
3834             # Check if there is data still pending on the command channel ...
3835 0           my $rin = "";
3836 0           vec ($rin, fileno($self), 1) = 1;
3837 0           my $res = select ( $rin, undef, undef, $timeout );
3838 0 0         if ( $res > 0 ) {
    0          
3839             # Now lets read the response from the command channel itself.
3840 0           my $cnt = 0;
3841 0           while ( sysread( $self, $data, $buffer_size ) ) {
3842 0           ($done, $complete) = _response_details ($self, $prefix, \$data, $done, $ccc_mess);
3843 0           ++$cnt;
3844 0 0 0       last if ($done && $complete);
3845             }
3846              
3847             # Check for errors ...
3848 0 0 0       if ( $done && $complete ) {
    0 0        
3849             # A no-op to protect against random setting of "$!" on no real error!
3850 0           my $nothing = "";
3851              
3852             } elsif ( $cnt == 0 || $! ne "" ) {
3853 0 0         if ($cnt > 0) {
3854             # Will put brackes arround the error reponse!
3855 0           _print_edited_response ( $self, $prefix, ${*$self}{_FTPSSL_arguments}->{last_ftp_msg}, $sep, 1 );
  0            
3856 0           _print_edited_response ( $self, $warn, ${*$self}{_FTPSSL_arguments}->{next_ftp_msg}, $sep, 2 );
  0            
3857             }
3858 0           _croak_or_return ($self, 0, "Unexpected EOF on Command Channel [$cnt] ($done, $complete) ($!)");
3859 0           return (CMD_ERROR);
3860             }
3861              
3862 0           } elsif ( ${*$self}{_FTPSSL_arguments}->{last_ftp_msg} ne "" ) {
3863             # A Timeout here is OK, it meant the previous command was complete.
3864 0           my $nothing = "";
3865              
3866             } else {
3867             # Will put brackes arround the error reponse!
3868 0           _print_edited_response ( $self, $prefix, ${*$self}{_FTPSSL_arguments}->{last_ftp_msg}, $sep, 1 );
  0            
3869 0           _print_edited_response ( $self, $warn, ${*$self}{_FTPSSL_arguments}->{next_ftp_msg}, $sep, 2 );
  0            
3870 0           _croak_or_return ($self, 0, "Timed out waiting for a response! [$res] ($!)");
3871 0           return (CMD_ERROR);
3872             }
3873              
3874             # Now print out the final patched together responses ...
3875 0           _print_edited_response ( $self, $prefix, ${*$self}{_FTPSSL_arguments}->{last_ftp_msg}, $sep, 0 );
  0            
3876 0           _print_edited_response ( $self, $warn, ${*$self}{_FTPSSL_arguments}->{next_ftp_msg}, $sep, 2 );
  0            
3877              
3878             # Returns the 1st digit of the 3 digit status code!
3879 0           return last_status_code ( $self );
3880             }
3881              
3882             #-----------------------------------------------------------------------
3883             # Mask sensitive information before it's written to the log file.
3884             # Separated out since done in multiple places.
3885             #-----------------------------------------------------------------------
3886             sub _print_edited_response {
3887 0     0     my $self = shift;
3888 0           my $prefix = shift; # "<<< " vs "SKT <<< ".
3889 0           my $msg = shift; # The response to print. (may be undef)
3890 0           my $sep = shift; # An optional separator string.
3891 0           my $bracket = shift; # 0 or 1 or 2.
3892              
3893             # Tells which separator to use to break up lines in $msg!
3894 0 0         my $breakStr = ($bracket == 2) ? "\015\012" : "\n";
3895              
3896             # A safety check to simplify when calling with undefined {next_ftp_msg}.
3897 0 0         unless (defined $msg) {
3898 0           return;
3899             }
3900              
3901 0 0         if ( ${*$self}{_FTPSSL_arguments}->{debug} ) {
  0            
3902             # Do we need to hide a value in the logged response ???
3903 0 0         if ( exists ${*$self}{_FTPSSL_arguments}->{_hide_value_in_response_} ) {
  0            
3904 0           my $val = _mask_regex_chars ($self, ${*$self}{_FTPSSL_arguments}->{_hide_value_in_response_});
  0            
3905 0   0       my $mask = ${*$self}{_FTPSSL_arguments}->{_mask_value_in_response_} || "????";
3906 0           $msg =~ s/\s${val}($|[\s.!,])/ <$mask>${1}/g;
3907             }
3908              
3909 0 0         if ($bracket) {
3910 0           $msg = $prefix . "[" . join ("]\n${prefix}[", split ($breakStr, $msg)) . "]";
3911             } else {
3912 0           $msg = $prefix . join ("\n$prefix", split ($breakStr, $msg));
3913             }
3914              
3915 0 0 0       if ( defined $sep && $sep !~ m/^\s*$/ ) {
3916 0           $msg = "Start: " . $sep . "\n" . $msg . "\nEnd::: " . $sep;
3917             }
3918 0           _print_LOG ( $self, $msg, "\n");
3919             }
3920              
3921 0           return;
3922             }
3923              
3924             #-----------------------------------------------------------------------
3925             # Broken out from response() in order to simplify the logic.
3926             # The previous version was getting way too convoluted to support.
3927             # Any bugs in this function easily causes things to hang or insert
3928             # random into the returned messages!
3929             #-----------------------------------------------------------------------
3930             # If you need to turn on the logging for this method use "Debug => 99"
3931             # in the constructor!
3932             #-----------------------------------------------------------------------
3933             # What a line should look like
3934             # - --- Continuation line(s) [repeateable]
3935             # --- Response completed line
3936             # Anything else means it's a Continuation line with embedded 's.
3937             # I think its safe to say the response completed line dosn't have
3938             # any extra 's embeded in it. Otherwise it's kind of difficult
3939             # to know when to stop reading from the socket & risk hangs.
3940             #-----------------------------------------------------------------------
3941             # But what I actually saw in many cases: (list not complete)
3942             # 2
3943             # 13-First Line
3944             # 213
3945             # -Second Line
3946             # 213-
3947             # Third Line
3948             # 213-Fourth
3949             # Line
3950             # Turns out sysread() isn't generous. It returns as little as possible
3951             # sometimes. Even when there is plenty of space left in the buffer.
3952             # Hence the strange behaviour above. But once all the pieces are put
3953             # together properly, you see what you expected in the 1st place.
3954             #-----------------------------------------------------------------------
3955             # Returns if it thinks the current response is done & complete or not.
3956             # end_respnose - (passed as "$status" next time called)
3957             # 0 - Response isn't complete yet.
3958             # 1 - Response was done, but may or may not be truncated in .
3959             # response_complete - Tells if the final line is complete or truncated.
3960             # 0 - Line was truncated!
3961             # 1 - Last line was complete!
3962             # Both must be true to stop reading from the socket.
3963             # If we've read past the response into the next one, we don't stop
3964             # reading until the overflow response is complete as well. Otherwise
3965             # the Timeout logic might not work properly later on.
3966             #-----------------------------------------------------------------------
3967             # The data buffer. I've seen the following:
3968             # 1) A line begining with: \012 (The \015 ended the pevious buffer)
3969             # 2) A line ending with: \015 (The \012 started the next buffer)
3970             # 3) Lines not ending with: \015\012
3971             # 4) A line only containing: \015\012
3972             # 5) A line only containing: \012
3973             # 6) Lines ending with: \015\012
3974             # If you see the 1st three items, you know there is more to read
3975             # from the socket. If you see the last 3 items, it's possible
3976             # that the next read from the socket will hang if you've already
3977             # seen the response complete message. So be careful here!
3978             #-----------------------------------------------------------------------
3979             sub _response_details {
3980 0     0     my $self = shift;
3981 0           my $prefix = shift; # "<<< " vs "SKT <<< ".
3982 0           my $data_ref = shift; # The data buffer to parse ...
3983 0           my $status = shift; # 0 or 1 (the returned status from previous call)
3984              
3985 0           my $ccc_kludge = shift; # Tells us if we are dealing with a corrupted CC
3986             # due to the aftermath of a CCC command!
3987             # 1st hit terminates the command in this case!
3988              
3989             # The return values ...
3990 0           my ($end_response, $response_complete) = (0, 0);
3991              
3992             # A more restrictive option for turning on logging is needed in this method.
3993             # Otherwise too much info is written to the logs and it is very confusing.
3994             # (Debug => 99 turns this extra logging on!)
3995             # So only use this special option if we need to debug this one method!
3996 0   0       my $debug = ${*$self}{_FTPSSL_arguments}->{debug} && ${*$self}{_FTPSSL_arguments}->{debug_extra};
3997              
3998             # Assuming that if the line doesn't end in a , the response is truncated
3999             # and we'll need the next sysread() to continue with the response.
4000             # Split drops trailing , so need this flag to detect this.
4001 0 0         my $end_with_cr = (substr (${$data_ref}, -2) eq "\015\012") ? 1 : 0;
  0            
4002              
4003 0 0         if ( $debug ) {
4004 0 0         my $type = ( exists ${*$self}{_FTPSSL_arguments}->{next_ftp_msg} ) ? "Overflow" : "Current";
  0            
4005 0 0         my $k = $ccc_kludge ? ", Kludge: $ccc_kludge" : "";
4006 0           _print_LOG ($self, "In _response_details ($type, Status: $status, len = ", length (${$data_ref}), ", End: ${end_with_cr}${k})\n");
  0            
4007             }
4008              
4009 0           my ($ref, $splt);
4010 0 0         if ( exists ${*$self}{_FTPSSL_arguments}->{next_ftp_msg} ) {
  0            
4011 0           $ref = \${*$self}{_FTPSSL_arguments}->{next_ftp_msg};
  0            
4012 0           $splt = "\015\012";
4013             } else {
4014 0           $ref = \${*$self}{_FTPSSL_arguments}->{last_ftp_msg};
  0            
4015 0           $splt = "\n";
4016             }
4017              
4018             # Sysread() does split the \015 & \012 to seperate lines, so test for it!
4019             # And fix the problem as well if it's found!
4020 0           my $index = 0;
4021 0 0         if ( substr (${$data_ref}, 0, 1) eq "\012" ) {
  0            
4022             # It hangs if I strip off from $data_ref, so handle later! (via $index)
4023 0 0         if ( substr (${$ref}, -1) eq "\015" ) {
  0            
4024 0           substr (${$ref}, -1) = $splt; # Replace with proper terminator.
  0            
4025 0           $index = 1;
4026 0 0         _print_LOG ($self, "Fixed 015/012 split!\n") if ( $debug );
4027 0 0         if ( ${$data_ref} eq "\012" ) {
  0            
4028 0   0       return ($status || $ccc_kludge, 1); # Only thing on the line.
4029             }
4030             }
4031             }
4032              
4033             # Check if the last line from the previous call was trucated ...
4034 0           my $trunc = "";
4035 0 0 0       if ( ${$ref} ne "" && substr (${$ref}, -length($splt)) ne $splt ) {
  0            
  0            
4036 0           $trunc = (split ($splt, ${$ref}))[-1];
  0            
4037             }
4038              
4039 0           my @term;
4040             my @data;
4041 0 0         if ( $end_with_cr ) {
4042             # Protects from split throwing away trailing empty lines ...
4043 0           @data = split( "\015\012", substr ( ${$data_ref}, $index ) . "|" );
  0            
4044 0           pop (@data);
4045             } else {
4046             # Last line was truncated ...
4047 0           @data = split( "\015\012", substr ( ${$data_ref}, $index ) );
  0            
4048             }
4049              
4050             # Tag which lines are complete! (Only the last one can be truncated)
4051 0           foreach (0..$#data) {
4052 0           $term[$_] = 1;
4053             }
4054 0           $term[-1] = $end_with_cr;
4055              
4056             # Current command or rolled over to the next command ???
4057 0           my (@lines, @next, @line_term, @next_term);
4058 0 0         if ( exists ${*$self}{_FTPSSL_arguments}->{next_ftp_msg} ) {
  0            
4059 0           @next = @data;
4060 0           @next_term = @term;
4061 0           @data = @term = @lines; # All are now empty.
4062             } else {
4063 0           @lines = @data;
4064 0           @line_term = @term;
4065 0           @data = @term = @next; # All are now empty.
4066             }
4067              
4068             # ------------------------------------------------------------------------
4069             # Now lets process the response messages we've read in. See the comments
4070             # above response() on why this code is such a mess.
4071             # But it's much cleaner than it used to be.
4072             # ------------------------------------------------------------------------
4073 0           my ( $code, $sep, $desc, $done ) = ( CMD_ERROR, "-", "", 0 );
4074 0           my ( $line, $term );
4075              
4076 0           foreach ( 0..$#lines ) {
4077 0           $line = $lines[$_];
4078 0           $term = $line_term[$_];
4079              
4080             # If the previous line was the end of the response ...
4081             # There can be no in that line!
4082             # So if true, it means we've read past the end of the response!
4083 0 0         if ( $done ) {
4084 0           push (@next, $line);
4085 0           push (@next_term, $term);
4086 0           next;
4087             }
4088              
4089             # Always represents the start of a new line ...
4090 0           my $test = $trunc . $line;
4091 0           $trunc = ""; # No longer possible for previous line to be truncated.
4092              
4093             # Check if this line marks the response complete! (If sep is a space)
4094 0 0         if ( $test =~ m/^(\d{3})([-\s])(.*)$/s ) {
4095 0           ($code, $sep, $desc) = ($1, $2, $3);
4096 0 0         $done = ($sep eq " ") ? $term : 0;
4097              
4098             # Update the return status ...
4099 0 0         $end_response = ($sep eq " ") ? 1: 0;
4100 0           $response_complete = $term;
4101             }
4102              
4103             # The CCC command messes up the Command Channel for a while!
4104             # So we need this work arround to immediately stop processing
4105             # to avoid breaking the command channel or hanging things.
4106 0 0 0       if ( $ccc_kludge && $term && ! $done ) {
      0        
4107 0 0         _print_LOG ( $self, "Kludge: 1st CCC work around detected ...\n") if ( $debug );
4108 0           $end_response = $response_complete = $done = 1;
4109             }
4110              
4111             # Save the unedited message ...
4112 0           ${*$self}{_FTPSSL_arguments}->{last_ftp_msg} .= $line;
  0            
4113              
4114             # Write to the log file if requested ...
4115             # But due to random splits, it risks not masking properly!
4116 0 0         _print_edited_response ( $self, $prefix, $line, undef, 1 ) if ( $debug );
4117              
4118             # Finish the current line ...
4119 0 0 0       if ($sep eq "-" && $term) {
4120 0           ${*$self}{_FTPSSL_arguments}->{last_ftp_msg} .= "\n"; # Restore the internal .
  0            
4121             }
4122             }
4123              
4124             # ------------------------------------------------------------------------
4125             # Process the response to the next command ... (read in with this one)
4126             # Shouldn't happen, but it sometimes does ...
4127             # ------------------------------------------------------------------------
4128 0           my $warn = "Warning: Attempting to read past end of response! ";
4129 0           my $next_kludge = 0;
4130 0           $done = 0;
4131 0           foreach ( 0..$#next ) {
4132 0           $next_kludge = 1;
4133 0           $line = $next[$_];
4134 0           $term = $next_term[$_];
4135              
4136             # We've read past the end of the current response into the next one ...
4137 0 0         _print_edited_response ( $self, $warn, $line, undef, 2 ) if ( $debug );
4138              
4139 0 0         if ( ! exists ${*$self}{_FTPSSL_arguments}->{next_ftp_msg} ) {
  0 0          
4140 0           ${*$self}{_FTPSSL_arguments}->{next_ftp_msg} = $line;
  0            
4141             } elsif ( $trunc ne "" ) {
4142 0           ${*$self}{_FTPSSL_arguments}->{next_ftp_msg} .= $line;
  0            
4143             } else {
4144 0           ${*$self}{_FTPSSL_arguments}->{next_ftp_msg} .= "\015\012" . $line;
  0            
4145             }
4146              
4147             # Always represents the start of a new line ...
4148 0           my $test = $trunc . $line;
4149 0           $trunc = ""; # No longer possible for previous line to be truncated.
4150              
4151             # Check if this line marks the response complete! (If sep is a space)
4152 0 0         if ( $test =~ m/^(\d{3})([-\s])(.*)$/s ) {
4153 0           ($code, $sep, $desc) = ($1, $2, $3);
4154 0 0         $done = ($sep eq " ") ? $term : 0;
4155              
4156             # Update the return status ...
4157 0 0         $end_response = ($sep eq " ") ? 1: 0;
4158 0           $response_complete = $term;
4159             }
4160             }
4161              
4162 0 0 0       if ( $end_with_cr && exists ${*$self}{_FTPSSL_arguments}->{next_ftp_msg} ) {
  0            
4163 0           ${*$self}{_FTPSSL_arguments}->{next_ftp_msg} .= "\015\012";
  0            
4164             }
4165              
4166             # Complete the Kludge! (Only needed if entered the @next loop!)
4167 0 0 0       if ( $ccc_kludge && $next_kludge && ! ($end_response && $response_complete) ) {
      0        
      0        
4168 0 0         _print_LOG ( $self, "Kludge: 2nd CCC work around detected ...\n") if ( $debug );
4169 0           $end_response = $response_complete = 1;
4170             }
4171              
4172 0           return ($end_response, $response_complete);
4173             }
4174              
4175             #-----------------------------------------------------------------------
4176              
4177             sub last_message {
4178 0     0 1   my $self = shift;
4179 0           return ${*$self}{_FTPSSL_arguments}->{last_ftp_msg};
  0            
4180             }
4181              
4182             #-----------------------------------------------------------------------
4183             # This method sets up a trap so that warnings can be written to my logs.
4184             # Always call like: $ftps->trapWarn().
4185             #-----------------------------------------------------------------------
4186             sub trapWarn {
4187 0     0 1   my $self = shift;
4188 0   0       my $force = shift || 0; # Only used by some of the t/*.t test cases!
4189             # Do not use the $force parameter otherwise!
4190             # You've been warned!
4191              
4192 0           my $res = 0; # Warnings are not yet trapped ...
4193              
4194             # Only trap warnings if a debug log is turned on to write to ...
4195 0 0 0       if ( defined $self && ${*$self}{_FTPSSL_arguments}->{debug} &&
  0   0        
      0        
4196             ($force || exists ${*$self}{_FTPSSL_arguments}->{ftpssl_filehandle}) ) {
4197 0           my $tmp = $SIG{__WARN__};
4198              
4199             # Must do as an inline function call so things will go to
4200             # the proper log file.
4201 0     0     my $func_ref = sub { $self->_print_LOG ("WARNING: ", $_[0]); };
  0            
4202              
4203 0           $warn_list{$self} = $func_ref;
4204              
4205             # This test prevents a recursive trap ...
4206 0 0         if (! exists $warn_list{OTHER}) {
4207 0           $warn_list{OTHER} = $tmp;
4208 0           $SIG{__WARN__} = __PACKAGE__ . "::_handleWarn";
4209             }
4210              
4211 0           $res = 1; # The warnings are trapped now ...
4212             }
4213              
4214 0           return ($res); # Whether trapped or not!
4215             }
4216              
4217             # Warning, this method cannot be called as a member function.
4218             # So it will never reference $self! It's also not documented in the POD!
4219             # See trapWarn() instead!
4220             sub _handleWarn {
4221 0     0     my $warn = shift; # The warning being processed ...
4222              
4223             # Print warning to each of the registered log files.
4224             # Will always be a reference to the function to call!
4225 0           my $func_ref;
4226 0           foreach ( keys %warn_list ) {
4227 0 0         next if ($_ eq "OTHER");
4228 0           $func_ref = $warn_list{$_};
4229 0           $func_ref->( $warn ); # Prints to an open Net::FTPSSL log file ...
4230             }
4231              
4232             # Was there any parent we replaced to chain the warning to?
4233 0 0 0       if (exists $warn_list{OTHER} && defined $warn_list{OTHER}) {
4234 0           $func_ref = $warn_list{OTHER};
4235 0 0 0       if (ref ($func_ref) eq "CODE") {
    0          
    0          
4236 0           $func_ref->( $warn );
4237             } elsif ( $func_ref eq "" || $func_ref eq "DEFAULT" ) {
4238 0           print STDERR "$warn\n";
4239             } elsif ( $func_ref ne "IGNORE" ) {
4240 0           &{\&{$func_ref}}($warn); # Will throw exception if doesn't exist!
  0            
  0            
4241             }
4242             }
4243             }
4244              
4245             # Called automatically when an instance of Net::FTPSSL goes out of scope!
4246             # Only called if new() was successfull! Used so we could remove all this
4247             # termination logic from quit()!
4248             sub DESTROY {
4249 0     0     my $self = shift;
4250              
4251 0 0         if ( ${*$self}{_FTPSSL_arguments}->{debug} ) {
  0            
4252             # Disable optional trapping of the warnings written to the log file
4253             # now that we're going out of scope!
4254 0 0         if ( exists $warn_list{$self} ) {
4255 0           delete ($warn_list{$self});
4256             }
4257              
4258             # Now let's close the log file itself ...
4259 0           $self->_close_LOG ();
4260              
4261             # Comment out this Debug Statement when no longer needed!
4262             # print STDERR "Good Bye FTPSSL instance! (", ref($self), ") [$self]\n";
4263             }
4264             }
4265              
4266             # Called automatically when this module is removed from memory.
4267             # NOTE: Due to how Perl's garbage collector works, in many cases END may be
4268             # called before DESTROY is called! Not what you'd expect!
4269             sub END {
4270             # Restore to original setting when the module gets unloaded from memory!
4271             # If this entry wasn't created, then we never redirected any warnings!
4272 16 50   16   36058 if ( exists $warn_list{OTHER} ) {
4273 0           $SIG{__WARN__} = $warn_list{OTHER};
4274 0           delete ( $warn_list{OTHER} );
4275             # print STDERR "Good Bye FTPSSL! (", $SIG{__WARN__}, ")\n";
4276             }
4277             }
4278              
4279             #-----------------------------------------------------------------------
4280             # Not in POD on purpose. It's an internal work arround for a debug issue.
4281             # Replace all chars known to cause issues with RegExp by putting
4282             # a "\" in front of it to remove the chars special meaning.
4283             # (less messy than putting it into square brackets ...)
4284             #-----------------------------------------------------------------------
4285             sub _mask_regex_chars {
4286 0     0     my $self = shift;
4287 0           my $mask = shift;
4288              
4289 0           $mask =~ s/([([?+*\\^$).])/\\$1/g;
4290              
4291 0           return ($mask);
4292             }
4293              
4294             #-----------------------------------------------------------------------
4295             # Added to make backwards compatible with Net::FTP
4296             #-----------------------------------------------------------------------
4297             sub message {
4298 0     0 1   my $self = shift;
4299 0           return ${*$self}{_FTPSSL_arguments}->{last_ftp_msg};
  0            
4300             }
4301              
4302             sub last_status_code {
4303 0     0 1   my $self = shift;
4304              
4305 0           my $code = CMD_ERROR;
4306 0 0         if ( defined ${*$self}{_FTPSSL_arguments}->{last_ftp_msg} ) {
  0            
4307 0           $code = substr (${*$self}{_FTPSSL_arguments}->{last_ftp_msg}, 0, 1);
  0            
4308             }
4309              
4310 0           return ($code);
4311             }
4312              
4313             sub _change_status_code {
4314 0     0     my $self = shift;
4315 0           my $code = shift; # Should be a single digit. Strange behaviour otherwise!
4316              
4317 0 0         if ( defined ${*$self}{_FTPSSL_arguments}->{last_ftp_msg} ) {
  0            
4318 0           substr (${*$self}{_FTPSSL_arguments}->{last_ftp_msg}, 0, 1) = $code;
  0            
4319             }
4320              
4321 0           return;
4322             }
4323              
4324             sub restart {
4325 0     0 1   my $self = shift;
4326 0           my $offset = shift;
4327 0           ${*$self}{_FTPSSL_arguments}->{net_ftpssl_rest_offset} = $offset;
  0            
4328 0           return (1);
4329             }
4330              
4331             #-----------------------------------------------------------------------
4332             # Implements data channel call back functionality ...
4333             #-----------------------------------------------------------------------
4334             sub set_callback {
4335 0     0 1   my $self = shift;
4336 0           my $func_ref = shift; # The callback function to call.
4337 0           my $end_func_ref = shift; # The end callback function to call.
4338 0           my $cb_work_area_ref = shift; # Optional ref to the callback work area!
4339              
4340 0 0 0       if ( defined $func_ref && defined $end_func_ref ) {
4341 0           ${*$self}{_FTPSSL_arguments}->{callback_func} = $func_ref;
  0            
4342 0           ${*$self}{_FTPSSL_arguments}->{callback_end_func} = $end_func_ref;
  0            
4343 0           ${*$self}{_FTPSSL_arguments}->{callback_data} = $cb_work_area_ref;
  0            
4344             } else {
4345 0           delete ( ${*$self}{_FTPSSL_arguments}->{callback_func} );
  0            
4346 0           delete ( ${*$self}{_FTPSSL_arguments}->{callback_end_func} );
  0            
4347 0           delete ( ${*$self}{_FTPSSL_arguments}->{callback_data} );
  0            
4348             }
4349              
4350 0           return;
4351             }
4352              
4353             sub _end_callback {
4354 0     0     my $self = shift;
4355 0           my $offset = shift; # Always >= 1. Index to original function called.
4356 0           my $total = shift;
4357              
4358 0           my $res;
4359 0           my $len = 0;
4360              
4361             # Is there an end callback function to use ?
4362 0 0         if ( defined ${*$self}{_FTPSSL_arguments}->{callback_end_func} ) {
  0            
4363 0           $res = &{${*$self}{_FTPSSL_arguments}->{callback_end_func}} ( (caller($offset))[3], $total,
  0            
4364 0           ${*$self}{_FTPSSL_arguments}->{callback_data} );
  0            
4365              
4366             # Now check the results for terminating the call back.
4367 0 0         if (defined $res) {
4368 0 0         if ($res eq "") {
4369 0           $res = undef; # Make it easier to work with.
4370             } else {
4371 0           $len = length ($res);
4372 0           $total += $len;
4373             }
4374             }
4375             }
4376              
4377 0           return ($res, $len, $total);
4378             }
4379              
4380             sub _call_callback {
4381 0     0     my $self = shift;
4382 0           my $offset = shift; # Always >= 1. Index to original function called.
4383 0           my $data_ref = shift;
4384 0           my $data_len_ref = shift;
4385 0           my $total_len = shift;
4386              
4387 0           my $cb_flag = 0;
4388              
4389             # Is there is a callback function to use ?
4390 0 0         if ( defined ${*$self}{_FTPSSL_arguments}->{callback_func} ) {
  0            
4391              
4392             # Allowed to modify contents of $data_ref & $data_len_ref ...
4393 0           &{${*$self}{_FTPSSL_arguments}->{callback_func}} ( (caller($offset))[3],
  0            
4394             $data_ref, $data_len_ref, $total_len,
4395 0           ${*$self}{_FTPSSL_arguments}->{callback_data} );
  0            
4396 0           $cb_flag = 1;
4397             }
4398              
4399             # Calculate the new total length to use for next time ...
4400 0 0         $total_len += (defined $data_len_ref ? ${$data_len_ref} : 0);
  0            
4401              
4402 0 0         if ( wantarray ) {
4403 0           return ($total_len, $cb_flag);
4404             }
4405 0           return ($total_len);
4406             }
4407              
4408             sub _fmt_num {
4409 0     0     my $self = shift;
4410 0           my $num = shift;
4411              
4412             # Change: 1234567890 --> 1,234,567,890
4413 0           while ( $num =~ s/(\d)(\d{3}(\D|$))/$1,$2/ ) { }
4414              
4415 0           return ( $num );
4416             }
4417              
4418             #-----------------------------------------------------------------------
4419             # To assist in debugging the flags being used by this module ...
4420             #-----------------------------------------------------------------------
4421              
4422             sub _debug_print_hash
4423             {
4424 0     0     my $self = shift;
4425 0           my $host = shift;
4426 0           my $port = shift;
4427 0           my $mode = shift;
4428 0   0       my $obj = shift || $self; # So can log most GLOB object types ...
4429 0           my $sep = shift; # The optional separator char to print out.
4430              
4431 0           _print_LOG ( $self, "\nObject ", ref($obj), " Details ..." );
4432 0 0         _print_LOG ( $self, " ($host:$port - $mode)" ) if (defined $host);
4433 0           _print_LOG ( $self, "\n" );
4434              
4435             # Fix to support non-GLOB object types ...
4436 0           my @lst;
4437 0           my $hash = 0;
4438 0 0         if ( ref ($obj) eq "HASH" ) {
4439 0           @lst = sort keys %{$obj};
  0            
4440 0           $hash = 1;
4441             } else {
4442             # It's a GLOB reference ...
4443 0           @lst = sort keys %{*$obj};
  0            
4444             }
4445              
4446             # The separators to use ...
4447 0           my @seps = ( "==>", "===>",
4448             "---->", "++++>", "====>",
4449             "----->", "+++++>", "=====>",
4450             "------>", "++++++>", "======>" );
4451              
4452             # To help detect infinite recursive loops ...
4453 0           my %loop;
4454             my %empty;
4455              
4456 0           foreach (@lst) {
4457 0 0         unless ( defined $host ) {
4458 0 0         next unless ( m/^(io_|_SSL|SSL)/ );
4459             }
4460 0 0         my $val = ($hash) ? $obj->{$_} : ${*$obj}{$_};
  0            
4461              
4462 0           %loop = %empty; # Empty out the hash again ...
4463 0           _print_hash_tree ( $self, " ", 0, $_, $val, \@seps, \%loop );
4464             }
4465              
4466 0 0 0       if (defined $sep && $sep !~ m/^\s*$/) {
4467 0           _print_LOG ( $self, $sep x 60, "\n");
4468             } else {
4469 0           _print_LOG ( $self, "\n" );
4470             }
4471              
4472 0           return;
4473             }
4474              
4475             # Recursive so can handle unlimited depth of hash trees ...
4476             sub _print_hash_tree
4477             {
4478 0     0     my $self = shift;
4479 0           my $indent = shift;
4480 0           my $lvl = shift; # Index to the $sep_ref array reference.
4481 0           my $lbl = shift;
4482 0           my $val = shift;
4483 0           my $sep_ref = shift; # An array reference.
4484 0           my $loop_ref = shift; # A hash ref to detect infinit recursion with.
4485              
4486 0 0         my $prefix = ($lvl == 0) ? "" : "-- ";
4487 0 0         my $sep = (defined $sep_ref->[$lvl]) ? $sep_ref->[$lvl] : ".....>";
4488              
4489             # Make sure it always has a value ...
4490 0 0         $val = "(undef)" unless (defined $val);
4491              
4492             # Fix indentation in case "\n" appears in the value ...
4493 0 0         $val = join ("\n${indent} ", split (/\n/, $val)) unless (ref($val));
4494              
4495             # Fix in case it's a scalar reference ...
4496 0 0         $val .= " [" . ${$val} . "]" if ($val =~ m/SCALAR\(0/);
  0            
4497              
4498 0           my $msg = "${indent}${prefix}${lbl} ${sep} ${val}";
4499              
4500             # How deep to indent for the next level ... (add 4 spaces)
4501 0           $indent .= " ";
4502              
4503 0 0         if ( $val =~ m/ARRAY\(0/ ) {
    0          
4504 0           my $lst = join (", ", @{$val});
  0            
4505 0           _print_LOG ( $self, $msg, "\n" );
4506 0           _print_LOG ( $self, "${indent}[", $lst, "]\n" );
4507              
4508             } elsif ( $val =~ m/HASH\((0x[\da-zA-Z]+)\)/ ) {
4509 0           my $key = $1; # The Hash address ...
4510 0           my %start = %{$loop_ref};
  0            
4511              
4512 0           _print_LOG ( $self, $msg );
4513 0 0         if ( exists $loop_ref->{$key} ) {
4514 0           _print_LOG ($self, " ... Infinite Hash Loop Detected!\n");
4515             } else {
4516 0           $start{$key} = $loop_ref->{$key} = $val;
4517 0           _print_LOG ( $self, "\n" );
4518 0           foreach (sort keys %{$val}) {
  0            
4519 0           %{$loop_ref} = %start;
  0            
4520 0           _print_hash_tree ( $self, $indent, $lvl + 1, $_, $val->{$_},
4521             $sep_ref, $loop_ref );
4522             }
4523             }
4524              
4525             # Else not an ARRAY or HASH ...
4526             } else {
4527 0           _print_LOG ( $self, $msg, "\n" );
4528             }
4529             }
4530              
4531             #-----------------------------------------------------------------------
4532             # Provided so each class instance gets its own log file to write to.
4533             #-----------------------------------------------------------------------
4534             # Always writes to the log when called ...
4535             sub _print_LOG
4536             {
4537 0     0     my $self = shift;
4538 0           my $msg = shift;
4539              
4540 0           my $FILE;
4541              
4542             # Determine where to write the log message to ...
4543 0 0 0       if ( defined $self && exists ${*$self}{_FTPSSL_arguments}->{ftpssl_filehandle} ) {
  0 0          
4544 0           $FILE = ${*$self}{_FTPSSL_arguments}->{ftpssl_filehandle}; # A custom log file ...
  0            
4545             } elsif ( defined $FTPS_ERROR ) {
4546 0           $FILE = $FTPS_ERROR; # Write to file when called during new() ...
4547             } else {
4548 0           $FILE = \*STDERR; # Write to screen anyone ?
4549             }
4550              
4551 0           while ( defined $msg ) {
4552 0           print $FILE $msg; # Write to the log file ...
4553 0           $msg = shift;
4554             }
4555             }
4556              
4557             # Only write to the log if debug is turned on ...
4558             # So we don't have to test everywhere ...
4559             # Done this way so can be called in new() on a socket as well.
4560             sub _print_DBG
4561             {
4562 0     0     my $self = shift;
4563 0 0 0       if ( defined $self && ${*$self}{_FTPSSL_arguments}->{debug} ) {
  0            
4564 0           _print_LOG ( $self, @_ ); # Only if debug is turned on ...
4565             }
4566             }
4567              
4568             sub get_log_filehandle
4569             {
4570 0     0 1   my $self = shift;
4571              
4572 0           my $FILE;
4573 0 0 0       if ( defined $self && exists ${*$self}{_FTPSSL_arguments}->{ftpssl_filehandle} ) {
  0            
4574 0           $FILE = ${*$self}{_FTPSSL_arguments}->{ftpssl_filehandle};
  0            
4575             }
4576              
4577 0           return ($FILE);
4578             }
4579              
4580             sub _close_LOG
4581             {
4582 0     0     my $self = shift;
4583              
4584 0 0 0       if ( defined $self && exists ${*$self}{_FTPSSL_arguments}->{ftpssl_filehandle} ) {
  0            
4585 0           my $FILE = ${*$self}{_FTPSSL_arguments}->{ftpssl_filehandle};
  0            
4586 0 0         close ($FILE) if ( ${*$self}{_FTPSSL_arguments}->{debug} == 2 );
  0            
4587 0           delete ( ${*$self}{_FTPSSL_arguments}->{ftpssl_filehandle} );
  0            
4588 0           ${*$self}{_FTPSSL_arguments}->{debug} = 1; # Back to using STDERR again ...
  0            
4589             }
4590             }
4591              
4592             # A helper method to tell if it can be counted as a GLOB ...
4593             sub _isa_glob
4594             {
4595 0     0     my $self = shift;
4596 0           my $fh = shift;
4597              
4598 0           my $res = 0; # Assume not a file handle/GLOB ...
4599              
4600 0 0         if ( defined $fh ) {
4601 0           my $tmp = ref ( $fh );
4602 0 0         if ( $tmp ) {
4603 0 0 0       $res = 1 if ( $tmp eq "GLOB" || $fh->isa ("IO::Handle") );
4604             }
4605             }
4606              
4607 0           return ( $res );
4608             }
4609              
4610             #-----------------------------------------------------------------------
4611             # If the Domain/Family is passed as a string, this function will convert
4612             # it into the needed numerical value. [Only called by new().]
4613             sub _validate_domain {
4614 0     0     my $type = shift; # It's a string, not an Net::FTPSSL object!
4615 0           my $family = shift; # The tag used for this value.
4616 0           my $domain = shift; # Should never be undef when called.
4617 0           my $debug = shift;
4618 0           my $die = shift;
4619              
4620 0           my $ret;
4621              
4622 0 0         if ( $domain =~ m/^\d+$/ ) {
    0          
4623 0           $ret = $domain; # Already a numeric value, so just return it ...
4624              
4625             # Valid domains are inherited functions named after the value!
4626             } elsif ( $domain =~ m/^AF_/i ) {
4627 0 0         if ( $type->can ( uc ($domain) ) ) {
4628 0           my $func = $type . "::" . uc ($domain) . "()";
4629 0           $ret = eval $func; # Call the function to convert it to an integer!
4630             }
4631             }
4632              
4633 0 0         unless ( defined $ret ) {
4634 0           _croak_or_return ( undef, $die, $debug,
4635             "Unknown value \"${domain}\" for option ${family}." );
4636             }
4637              
4638             # Return the domain/family as a numeric value.
4639             # Can be undef if invalid & Croak is turned off.
4640 0           return ( $ret );
4641             }
4642              
4643              
4644             #-----------------------------------------------------------------------
4645              
4646             1;
4647              
4648             __END__