File Coverage

blib/lib/Net/FTPSSL.pm
Criterion Covered Total %
statement 135 2366 5.7
branch 6 1202 0.5
condition 0 661 0.0
subroutine 38 157 24.2
pod 54 59 91.5
total 233 4445 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.42
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   1114539 use strict;
  16         145  
  16         397  
10 16     16   64 use warnings;
  16         21  
  16         392  
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   10440 use IO::Socket::SSL 1.26;
  16         1219672  
  16         110  
16              
17 16     16   2891 use vars qw( $VERSION @EXPORT $ERRSTR );
  16         33  
  16         1013  
18 16     16   80 use base ( 'Exporter', 'IO::Socket::SSL' );
  16         29  
  16         1595  
19              
20             # Only supports IPv4 (to also get IPv6 must use IO::Socket::IP instead. v0.20)
21 16     16   89 use IO::Socket::INET;
  16         26  
  16         172  
22              
23 16     16   14014 use Net::SSLeay::Handle;
  16         28985  
  16         617  
24 16     16   101 use File::Basename;
  16         28  
  16         789  
25 16     16   511 use File::Copy;
  16         3962  
  16         579  
26 16     16   6683 use Time::Local;
  16         30379  
  16         773  
27 16     16   5902 use Sys::Hostname;
  16         14164  
  16         790  
28 16     16   95 use Carp qw( carp croak );
  16         29  
  16         576  
29 16     16   91 use Errno qw/ EINTR /;
  16         26  
  16         802  
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   78 use constant IMP_CRYPT => "I";
  16         23  
  16         834  
40 16     16   83 use constant EXP_CRYPT => "E"; # Default
  16         24  
  16         820  
41 16     16   94 use constant CLR_CRYPT => "C";
  16         25  
  16         756  
42              
43             # Data Channel Protection Levels
44 16     16   103 use constant DATA_PROT_CLEAR => "C"; # Least secure!
  16         38  
  16         821  
45 16     16   87 use constant DATA_PROT_SAFE => "S";
  16         25  
  16         613  
46 16     16   75 use constant DATA_PROT_CONFIDENTIAL => "E";
  16         295  
  16         695  
47 16     16   80 use constant DATA_PROT_PRIVATE => "P"; # Default & most secure!
  16         23  
  16         670  
48              
49             # Valid FTP Result codes
50 16     16   76 use constant CMD_INFO => 1;
  16         26  
  16         651  
51 16     16   84 use constant CMD_OK => 2;
  16         39  
  16         931  
52 16     16   97 use constant CMD_MORE => 3;
  16         32  
  16         641  
53 16     16   72 use constant CMD_REJECT => 4;
  16         23  
  16         630  
54 16     16   108 use constant CMD_ERROR => 5;
  16         37  
  16         715  
55 16     16   80 use constant CMD_PROTECT => 6;
  16         24  
  16         635  
56 16     16   74 use constant CMD_PENDING => 0;
  16         30  
  16         618  
57              
58             # -------- Above Exported ---- Below don't bother to export --------
59              
60             # File transfer modes (the mixed modes have no code)
61 16     16   91 use constant MODE_BINARY => "I";
  16         25  
  16         608  
62 16     16   92 use constant MODE_ASCII => "A"; # Default
  16         26  
  16         642  
63              
64             # The Data Connection Setup Commands ...
65             # Passive Options ... (All pasive modes are currently supported)
66 16     16   75 use constant FTPS_PASV => 1; # Default mode ...
  16         43  
  16         615  
67 16     16   73 use constant FTPS_EPSV_1 => 2; # EPSV 1 - Internet Protocol Version 4
  16         36  
  16         622  
68 16     16   96 use constant FTPS_EPSV_2 => 3; # EPSV 2 - Internet Protocol Version 6
  16         26  
  16         603  
69             # Active Options ... (No active modes are currently supported)
70 16     16   77 use constant FTPS_PORT => 4;
  16         36  
  16         723  
71 16     16   79 use constant FTPS_EPRT_1 => 5; # EPRT 1 - Internet Protocol Version 4
  16         24  
  16         628  
72 16     16   75 use constant FTPS_EPRT_2 => 6; # EPRT 2 - Internet Protocol Version 6
  16         24  
  16         582  
73              
74             # Misc constants
75 16     16   87 use constant TRACE_MOD => 10; # How many iterations between ".". Must be >= 5.
  16         35  
  16         5238  
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   62 $VERSION = "0.42"; # The version of this module!
92              
93 16         39 my $type = "IO::Socket::SSL";
94 16         52 $ipv6 = 0; # Assume IPv4 only ...
95 16         42 $IOCLASS = "IO::Socket::INET"; # Assume IPv4 only ...
96 16         35 $family_key = "Domain"; # Traditional ...
97 16         28 my $msg;
98              
99 16         22 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       317 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         104 $ipv6 = 1; # Yes! IPv6 can be suporteed!
108 16         42 $IOCLASS = $type->can_ipv6 (); # Get which IPv6 module SSL uses.
109 16 50       105 $family_key = "Family" if ( $IOCLASS eq "IO::Socket::IP" );
110 16         173 my $ver = $IOCLASS->VERSION;
111 16         79 $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         189 my $pv = sprintf ("%s [%vd]", $], $^V); # The version of perl!
120              
121             # Required info when opening a CPAN ticket against this module ...
122 16         75 $debug_log_msg = "\n"
123             . "Net-FTPSSL Version: $VERSION\n";
124              
125             # Print out versions of critical modules we depend on ...
126 16         54 foreach ( "IO-Socket-SSL", "Net-SSLeay",
127             "IO-Socket-INET", "IO-Socket-INET6",
128             "IO-Socket-IP", "IO",
129             "Socket" ) {
130 112         166 my $mod = $_;
131 112         256 $mod =~ s/-/::/g;
132 112         914 my $ver = $mod->VERSION;
133 112 100       268 if ( defined $ver ) {
134 96         196 $debug_log_msg .= "$_ Version: $ver\n";
135             } else {
136 16         47 $debug_log_msg .= "$_ might not be installed.\n";
137             }
138             }
139              
140 16         84 $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         30 $IOCLASS = $ioOrig; $family_key = "Domain";
  16         21  
145 16         351962 $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 MLSD!
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 ls {
3020 0     0 1   my $self = shift;
3021 0           return ( $self->nlst (@_) );
3022             }
3023              
3024             sub dir {
3025 0     0 1   my $self = shift;
3026 0           return ( $self->list (@_) );
3027             }
3028              
3029             sub is_file {
3030 0     0 1   my $self = shift;
3031 0           my $file = shift;
3032              
3033 0           my $isFile = 0; # Assume not a regular file ...
3034              
3035             # Now let's disable Croak so we can't die during this test ...
3036 0           my $die = $self->set_croak (0);
3037              
3038             # Not implemented on many FTPS servers ...
3039             # But it's the most reliable way if it is ...
3040 0 0         if ( $self->supported ("MLST") ) {
3041             # Must use "OPTS MLST TYPE" if the type feature is currently disabled.
3042 0           my $data = $self->parse_mlsx ( $self->mlst ($file), 1 );
3043              
3044             # We now know something was found, but we don't yet know what it is!
3045 0 0         if ( $data ) {
3046 0 0         if ( exists $data->{type} ) {
3047 0           my $t = $data->{type};
3048 0 0         $isFile = ( $t eq "file" ) ? 1 : 0;
3049 0           $self->set_croak ( $die ); # Restore the croak settings!
3050 0           return ( $isFile );
3051             }
3052 0           warn ("Turn on TYPE feature with OPTS before using this function!\n");
3053             }
3054             }
3055              
3056 0           my $size = $self->size ( $file, 1 );
3057              
3058 0           $self->set_croak ( $die ); # Restore the croak settings!
3059              
3060 0 0 0       if ( defined $size && $size >= 0 ) {
3061 0           return ( 1 ); # It's a plain file! We successfully got it's size!
3062             }
3063              
3064 0           return ( 0 ); # It's not a plain file or it doesn't exist!
3065             }
3066              
3067             sub is_dir {
3068 0     0 1   my $self = shift;
3069 0           my $dir = shift;
3070              
3071 0           my $isDir = 0; # Assume not a directory ...
3072              
3073             # The current direcory!
3074 0           my $curDir = $self->pwd ();
3075              
3076             # Now let's disable Croak so we can't die during this test ...
3077 0           my $die = $self->set_croak (0);
3078              
3079             # Not implemented on many FTPS servers ...
3080             # But it's the most reliable way if it is ...
3081 0 0         if ( $self->supported ("MLST") ) {
3082             # Must use "OPTS MLST TYPE" if the type feature is currently disabled.
3083 0           my $data = $self->parse_mlsx ( $self->mlst ($dir), 1 );
3084              
3085             # We now know something was found, but we don't yet know what it is!
3086 0 0         if ( $data ) {
3087 0 0         if ( exists $data->{type} ) {
3088 0           my $t = $data->{type};
3089 0 0 0       $isDir = ( $t eq "dir" || $t eq "cdir" || $t eq "pdir" ) ? 1 : 0;
3090 0           $self->set_croak ( $die ); # Restore the croak settings!
3091 0           return ( $isDir );
3092             }
3093 0           warn ("Turn on TYPE feature with OPTS before using this function!\n");
3094             }
3095             }
3096              
3097             # Check if it's a directory we have access to ...
3098 0 0         if ( $self->cwd ( $dir ) ) {
3099 0           $self->cwd ( $curDir );
3100 0           $isDir = 1;
3101              
3102             } else {
3103             # At this point if it's really a directory, we don't have access to it.
3104             # And parsing error messages really isn't an option.
3105              
3106             # So what if we now assume it it might be a directory if "is_file()"
3107             # returns false and we can see that the file does exists via "nlst()"?
3108              
3109             # I don't really like that no-access test, too many chances for false
3110             # positives, so I'm open to better ideas! I'll leave this code disabled
3111             # until I can mull this over some more.
3112              
3113             # Currently disabled ...
3114 0           if ( 1 != 1 ) {
3115             # If it isn't a regular file, then it might yet still be a directory!
3116             unless ( $self->is_file ( $dir ) ) {
3117             # Now check if we can see a file of this name ...
3118             my @lst = $self->nlst (dirname ($dir), basename ($dir));
3119             if ( scalar (@lst) ) {
3120             # It may or may not be a directory ...
3121             $self->_print_DBG ("--- Found match: ", $lst[0], "\n");
3122             $isDir = 1;
3123             }
3124             }
3125             }
3126             }
3127              
3128 0           $self->set_croak ( $die ); # Restore the croak settings!
3129              
3130 0           return ( $isDir );
3131             }
3132              
3133             sub copy_cc_to_dc {
3134 0     0 1   my $self = shift;
3135 0 0         my $args = (ref ($_[0]) eq "ARRAY") ? $_[0] : \@_;
3136              
3137 0           my %dcValues;
3138 0 0         if ( exists ${*$self}{_FTPSSL_arguments}->{myContext} ) {
  0            
3139 0           %dcValues = %{${*$self}{_FTPSSL_arguments}->{myContext}};
  0            
  0            
3140             }
3141              
3142 0           my $cnt = 0;
3143 0           foreach ( @{$args} ) {
  0            
3144 0           my $val;
3145 0 0         if ( exists ${*$self}{_SSL_arguments}->{$_} ) {
  0 0          
    0          
3146 0           $val = ${*$self}{_SSL_arguments}->{$_};
  0            
3147              
3148 0           } elsif ( exists ${*$self}{_FTPSSL_arguments}->{start_SSL_opts}->{$_} ) {
3149 0           $val = ${*$self}{_FTPSSL_arguments}->{start_SSL_opts}->{$_};
  0            
3150              
3151 0           } elsif ( exists ${*$self}{$_} ) {
3152 0           $val = ${*$self}{$_};
  0            
3153              
3154             } else {
3155 0           $self->_print_DBG ("No such Key defined for the CC: ", $_, "\n");
3156 0           next;
3157             }
3158              
3159 0           $dcValues{$_} = $val;
3160 0           ++$cnt;
3161             }
3162              
3163             # Update with the new Data Channel options ...
3164 0 0         if ( $cnt > 0 ) {
3165 0           ${*$self}{_FTPSSL_arguments}->{myContext} = \%dcValues;
  0            
3166             }
3167              
3168 0 0         if ( ${*$self}{_FTPSSL_arguments}->{debug} ) {
  0            
3169 0           $self->_debug_print_hash ( "DC Hash", "options", "cc2dc($cnt)", \%dcValues, "#" );
3170             }
3171              
3172 0           return ( $cnt );
3173             }
3174              
3175             sub set_dc_from_hash {
3176 0     0 1   my $self = shift;
3177 0 0         my $args = (ref ($_[0]) eq "HASH") ? $_[0] : {@_};
3178              
3179 0           my %dcValues;
3180 0 0         if ( exists ${*$self}{_FTPSSL_arguments}->{myContext} ) {
  0            
3181 0           %dcValues = %{${*$self}{_FTPSSL_arguments}->{myContext}};
  0            
  0            
3182             }
3183              
3184 0           my $cnt = 0;
3185 0           foreach my $key ( keys %{$args} ) {
  0            
3186 0           my $val = $args->{$key};
3187              
3188 0 0         if ( defined $val ) {
    0          
3189             # Add the requested value to the DC hash ...
3190 0           $dcValues{$key} = $val;
3191 0           ++$cnt;
3192              
3193             } elsif ( exists $dcValues{$key} ) {
3194             # Delete the requested value from the DC hash ...
3195 0           delete $dcValues{$key};
3196 0           ++$cnt;
3197             }
3198             }
3199              
3200             # Update with the new Data Channel options ...
3201 0 0         if ( $cnt > 0 ) {
3202 0           ${*$self}{_FTPSSL_arguments}->{myContext} = \%dcValues;
  0            
3203             }
3204              
3205 0 0         if ( ${*$self}{_FTPSSL_arguments}->{debug} ) {
  0            
3206 0           $self->_debug_print_hash ( "DC Hash", "options", "setdc($cnt)", \%dcValues, "%" );
3207             }
3208              
3209 0           return ( $cnt );
3210             }
3211              
3212             #-----------------------------------------------------------------------
3213             # Checks what commands are available on the remote server
3214             # If a "*" follows a command, it's unimplemented!
3215             # The caller is free to modify the returned hash refrence.
3216             # It's just a copy of what's been cached, not the original!
3217             #-----------------------------------------------------------------------
3218             # The returned hash may contain both active & disabled FTP commands.
3219             # If disabled, the command's value will be 0. Otherwise it will
3220             # contain a non-zero value. So testing using "exists" is BAD form now.
3221             #-----------------------------------------------------------------------
3222             # Please remember that when OverrideHELP=>1 is used, it will always
3223             # return an empty hash!!!
3224             #-----------------------------------------------------------------------
3225              
3226             sub _help {
3227             # Only shift off self, bug otherwise!
3228 0     0     my $self = shift;
3229 0   0       my $cmd = uc ($_[0] || ""); # Converts undef to "". (Do not do a shift!)
3230              
3231             # Check if requesting a list of all commands or details on specific commands.
3232 0           my $all_cmds = ($cmd eq "");
3233 0           my $site_cmd = ($cmd eq "SITE");
3234              
3235 0           my %help;
3236 0           my $arg = ${*$self}{_FTPSSL_arguments};
  0            
3237              
3238             # Only possible if _help() is called before 1st call to supported()!
3239 0 0 0       unless ( $all_cmds || exists $arg->{help_cmds_msg} ) {
3240 0           $self->_help ();
3241             }
3242              
3243             # Use FEAT instead of HELP to populate the supported hash!
3244             # Assuming the HELP command itself is broken! "via OverrideHELP=>-1"
3245 0 0 0       if ( exists $arg->{removeHELP} && $arg->{removeHELP} == 1 ) {
3246 0           my $ft = $self->_feat ();
3247 0 0         $ft->{FEAT} = 2 if (scalar (keys %{$ft}) > 0);
  0            
3248 0           foreach ( keys %{$ft} ) { $ft->{$_} = 2; } # So always TRUE
  0            
  0            
3249              
3250 0           $arg->{help_cmds_found} = $ft;
3251 0           $arg->{help_cmds_msg} = $self->last_message ();
3252              
3253 0           $self->_site_help ( $arg->{help_cmds_found} );
3254 0           $arg->{removeHELP} = 2; # So won't execute again ...
3255             }
3256              
3257             # Now see if we've cached any results previously ...
3258 0           my $key;
3259 0 0 0       if ($all_cmds && exists $arg->{help_cmds_msg}) {
    0          
    0          
3260 0           $arg->{last_ftp_msg} = $arg->{help_cmds_msg};
3261 0           $key = "help_cmds_found";
3262 0 0         %help = %{$arg->{$key}} if ( exists $arg->{$key} );
  0            
3263 0           return ( \%help );
3264              
3265             } elsif (exists $arg->{"help_${cmd}_msg"}) {
3266 0           $arg->{last_ftp_msg} = $arg->{"help_${cmd}_msg"};
3267 0           $key = "help_${cmd}_found";
3268 0 0         %help = %{$arg->{$key}} if ( exists $arg->{$key} );
  0            
3269 0           return ( \%help );
3270              
3271             } elsif ( exists $arg->{help_cmds_no_syntax_available} ) {
3272 0 0 0       if ( exists $arg->{help_cmds_found}->{$cmd} || $arg->{OverrideHELP} ) {
3273 0           $arg->{last_ftp_msg} = "503 Syntax for ${cmd} is not available.";
3274             } else {
3275 0           $arg->{last_ftp_msg} = "501 Unknown command ${cmd}.";
3276             }
3277             # $self->_print_DBG ( "<<+ ", $self->last_message (), "\n" );
3278 0           return ( \%help ); # The empty hash ...
3279             }
3280              
3281             # From here on out, we will get at least one server hit ...
3282              
3283 0           my $sts;
3284 0 0         if ($all_cmds) {
3285 0           $sts = $self->command ("HELP")->response ();
3286 0           $arg->{help_cmds_msg} = $self->last_message ();
3287 0 0         $arg->{help_cmds_no_syntax_available} = 1 if ( $sts != CMD_OK );
3288             } else {
3289 0           $sts = $self->command ("HELP", @_)->response ();
3290 0           $arg->{"help_${cmd}_msg"} = $self->last_message ();
3291             }
3292              
3293             # If failure, return empty hash ...
3294 0 0         return (\%help) if ( $sts != CMD_OK );
3295              
3296             # Check if "HELP" & "HELP CMD" return the same thing ...
3297 0 0 0       if ( (! $all_cmds) && $arg->{help_cmds_msg} eq $self->last_message () ) {
3298 0           $arg->{help_cmds_no_syntax_available} = 1;
3299 0           delete $arg->{"help_${cmd}_msg"}; # Delete this wrong message ...
3300 0           return ( $self->_help ($cmd) ); # Recursive to get the right error msg!
3301             }
3302              
3303             # HELP ...
3304 0 0         if ( $all_cmds ) {
    0          
3305 0           %help = %{$self->_help_parse (0)};
  0            
3306              
3307             # If we don't find anything for HELP, it's a problem.
3308             # So don't cache if false ...
3309 0 0         if (scalar (keys %help) > 0) {
3310 0 0         if ($help{FEAT}) {
3311             # Now put any new features into the help response as well ...
3312 0           my $feat = $self->_feat ();
3313 0           foreach (keys %{$feat}) {
  0            
3314 0 0         $help{$_} = 2 unless (exists $help{$_});
3315             }
3316             }
3317              
3318 0           my %siteHelp;
3319             my $msg;
3320 0 0         if ($help{SITE}) {
3321             # See if this returns a usage statement or a list of SITE commands!
3322 0           %siteHelp = %{$self->_help ("SITE")};
  0            
3323 0 0         $msg = $self->message () if ( $self->last_status_code() == CMD_OK );
3324             }
3325              
3326             # Do only if no SITE details yet ...
3327 0 0         if (scalar (keys %siteHelp) == 0) {
3328 0           $self->_site_help (\%help, $msg);
3329             }
3330              
3331 0           my %lclHelp = %help;
3332 0           $arg->{help_cmds_found} = \%lclHelp;
3333             }
3334              
3335             # HELP SITE ...
3336             } elsif ( $site_cmd ) {
3337 0           %help = %{$self->_help_parse (1)};
  0            
3338              
3339             # If we find something, it means it's returning the list of SITE commands.
3340             # Some servers do this rather than returning a syntax statement.
3341 0 0         if (scalar (keys %help) > 0) {
3342 0           my %siteHelp = %help;
3343 0           $arg->{help_SITE_found} = \%siteHelp;
3344             }
3345              
3346             # HELP some_other_command ...
3347             } else {
3348             # Nothing really to do here ...
3349             }
3350              
3351 0           return (\%help);
3352             }
3353              
3354             #---------------------------------------------------------------------------
3355             # Try to get a list of SITE commands supported.
3356             #---------------------------------------------------------------------------
3357             sub _site_help
3358             {
3359 0     0     my $self = shift;
3360 0           my $help = shift; # Parent help hash
3361 0           my $msg = shift; # Optional override message.
3362              
3363 0           my $arg = ${*$self}{_FTPSSL_arguments};
  0            
3364              
3365             # Not calling site() in case Croak is turned on.
3366             # It's not a fatal error if this call fails ...
3367             # my $ok = $self->site ("HELP");
3368 0           my $ok = ($self->command("SITE", "HELP")->response() == CMD_OK);
3369              
3370 0           $arg->{help_SITE_msg} = $self->last_message ();
3371              
3372 0 0         if ( $ok ) {
3373 0           my $siteHelp = $self->_help_parse (1);
3374              
3375 0 0         if (scalar (keys %{$siteHelp}) > 0) {
  0            
3376 0 0         if ( defined $help ) {
3377 0 0         $help->{SITE} = -1 unless ( exists $help->{SITE} );
3378             }
3379 0 0         $siteHelp->{HELP} = -1 unless ( exists $siteHelp->{HELP} );
3380 0           $arg->{help_SITE_found} = $siteHelp;
3381              
3382             # Only do optional override of the cached message on success!
3383 0 0         $arg->{help_SITE_msg} = $msg if ( $msg );
3384             }
3385             }
3386              
3387 0           return;
3388             }
3389              
3390             #---------------------------------------------------------------------------
3391             # Handles the parsing of the "HELP", "HELP SITE" & "SITE HELP" commands ...
3392             # Not all servers return a list of commands for the 2nd two items.
3393             #---------------------------------------------------------------------------
3394             sub _help_parse {
3395 0     0     my $self = shift;
3396 0           my $site_cmd = shift; # Only 0 for HELP.
3397              
3398             # This value is used to distinguish which call set the hash entry.
3399             # No logic is based on it. Just done to ease debugging later on!
3400 0 0         my $flag = ($site_cmd) ? -2 : 1;
3401              
3402 0           my $helpmsg = $self->last_message ();
3403 0           my @lines = split (/\n/, $helpmsg);
3404              
3405 0           my %help;
3406              
3407 0           foreach my $line (@lines) {
3408             # Strip off the code & separator or leading blanks if multi line.
3409 0           $line =~ s/((^[0-9]+[\s-]?)|(^\s*))//;
3410 0           my $lead = $1;
3411              
3412 0 0         next if ($line eq "");
3413              
3414             # Skip over the start/end part of the response ...
3415             # Doesn't work for all servers!
3416             # next if ( defined $lead && $lead =~ m/^\d+[\s-]?$/ );
3417              
3418             # Make sure no space between command & the * that marks it unsupported!
3419             # May be more than one hit per line ...
3420 0           $line =~ s/(\S)\s+[*]($|\s|,)/$1*$2/g;
3421              
3422 0           my @lst = split (/[\s,.]+/, $line); # Break into individual commands
3423              
3424 0 0 0       if ( $site_cmd && $lst[0] eq "SITE" && $lst[1] =~ m/^[A-Z]+$/ ) {
    0 0        
3425 0           $help{$lst[1]} = 1; # Each line: "SITE CMD mixed-case-usage"
3426             }
3427             # Now only process if nothing is in lower case (ie: its a comment)
3428             # All commands must be in upper case, some special chars not allowed.
3429             # Commands ending in "*" are currently turned off.
3430             elsif ( $line !~ m/[a-z()]/ ) {
3431 0           foreach (@lst) {
3432             # $help{$_} = 1 if ($_ !~ m/[*]$/);
3433 0 0         if ($_ !~ m/^(.+)[*]$/) {
    0          
3434 0           $help{$_} = $flag; # Record enabled for all options ...
3435             } elsif ( $site_cmd == 0 ) {
3436 0           $help{$1} = 0; # Record command is disabled for HELP.
3437             }
3438             }
3439             }
3440             }
3441              
3442 0           return (\%help);
3443             }
3444              
3445             #-----------------------------------------------------------------------
3446             # Returns a hash of features supported by this server ...
3447             # It's always uses the cache after the 1st call ... this list never changes!
3448             # Making this a static list!
3449             # This is the version used internally by _help & supported!
3450             #-----------------------------------------------------------------------
3451              
3452             sub _feat {
3453 0     0     my $self = shift;
3454              
3455 0           my $arg = ${*$self}{_FTPSSL_arguments};
  0            
3456              
3457             # Check to see if we've cached the result previously ...
3458             # Must use slightly different naming convenion than used
3459             # in _help() to avoid conflicts. [set in feat()]
3460 0 0         if (exists $arg->{help_FEAT_msg2}) {
3461 0           $arg->{last_ftp_msg} = $arg->{help_FEAT_msg2};
3462 0           my %hlp = %{$arg->{help_FEAT_found2}};
  0            
3463 0           return ( \%hlp );
3464             }
3465              
3466 0           my $res = $self->feat (1); # Undocumented opt to disable Croak if on!
3467              
3468 0           return ($res); # Feel free to modify it if you wish! Won't harm anything!
3469             }
3470              
3471              
3472             #-----------------------------------------------------------------------
3473             # Returns a hash of features supported by this server ...
3474             # It's conditionally cached based on the results of the 1st call to FEAT!
3475             # So on some servers this list will be static, while on others dynamic!
3476             #-----------------------------------------------------------------------
3477             # The FEAT command returns one line per command, with optional behaviors.
3478             # If the command ends in "*", the command isn't supported by FEAT!
3479             # And if not supported it won't show up in the hash!
3480             # Format: CMD [behavior]
3481             #-----------------------------------------------------------------------
3482             # If one or more commands have behaviors, then it's possible for the
3483             # results of the FEAT command to change based on calls to
3484             # "OPTS CMD behavior"
3485             # So if even one command has a behavior, there will be a server hit
3486             # to see if the FEAT results changed. It will also add OPTS to the hash!
3487             # Otherwise the results are cached!
3488             #-----------------------------------------------------------------------
3489             # Note: {help_FEAT_found2} & {help_FEAT_msg2} are used here since it's
3490             # possible that {help_FEAT_found} & {help_FEAT_msg} can be auto
3491             # generated via "HELP FEAT" [during a call to _help("FEAT").]
3492             # These special vars are only used in feat() & _feat().
3493             #-----------------------------------------------------------------------
3494             sub feat {
3495 0     0 1   my $self = shift;
3496 0           my $disable_croak = shift; # Undocumented option in POD on purpose!
3497             # Only used when called from _feat()!
3498              
3499 0           my $arg = ${*$self}{_FTPSSL_arguments};
  0            
3500              
3501 0           my %res;
3502              
3503             # Conditionally use the cache if the server will always return a static list!
3504             # It will be static if the OPTS command isn't supported!
3505 0 0 0       if ( exists $arg->{help_FEAT_found2} &&
3506             ! exists $arg->{help_FEAT_found2}->{OPTS} ) {
3507 0           $arg->{last_ftp_msg} = $arg->{help_FEAT_msg2};
3508 0           %res = %{$arg->{help_FEAT_found2}};
  0            
3509 0           return ( \%res );
3510             }
3511              
3512             # Check if a request has been made to not honor HELP if in the FEAT list.
3513 0           my $remove = "";
3514 0 0         if ( $arg->{removeHELP} ) {
    0          
3515 0           $remove = "-1";
3516             } elsif ( exists $arg->{OverrideHELP} ) {
3517 0           $remove = $arg->{OverrideHELP};
3518 0 0         if ( $remove == 0 ) {
3519 0           my @lst = keys %{$arg->{help_cmds_found}};
  0            
3520 0 0         $remove = "[array]" if ($#lst != -1);
3521             }
3522             }
3523 0           my $mask = "HELP * OverrideHELP=>${remove} says to remove this command.";
3524              
3525             # Check if a request has been made to not honor HELP if in the FEAT list.
3526 0 0         if ( $remove ne "" ) {
3527 0           $arg->{_hide_value_in_response_} = "HELP";
3528 0           $arg->{_mask_value_in_response_} = $mask;
3529             }
3530              
3531 0           my $status = $self->command ("FEAT")->response ();
3532              
3533 0 0         if ( $remove ne "" ) {
3534 0           $arg->{last_ftp_msg} =~ s/HELP/<${mask}>/i;
3535 0           delete $arg->{_hide_value_in_response_};
3536 0           delete $arg->{_mask_value_in_response_};
3537             }
3538              
3539 0 0         if ( $status == CMD_OK ) {
3540 0           my @lines = split (/\n/, $self->last_message ());
3541 0           my %behave;
3542              
3543 0           my $may_change = 0; # Assume always returns an unchanging list ...
3544 0           foreach my $line (@lines) {
3545             # Strip off the code & separator or leading blanks if multi line.
3546 0           $line =~ s/((^[0-9]+[\s-]?)|(^\s*))//;
3547 0           my $lead = $1;
3548              
3549             # Skip over the start/end part of the response ...
3550 0 0 0       next if ( defined $lead && $lead =~ m/^\d+[\s-]?$/ );
3551              
3552 0 0         next if ( $line eq "" ); # Skip over all blank lines
3553              
3554 0           my @part = split (/\s+/, $line);
3555              
3556             # Command ends in "*" or the next part is "*" ???
3557             # Used to conditionally remove the HELP cmd if necessary ...
3558             # Otherwise not sure if this test ever really happens ...
3559 0 0 0       next if ($part[0] =~ m/[*]$/ || (defined $part[1] && $part[1] eq "*"));
      0        
3560              
3561             # The value is the rest of the command ...
3562 0 0         if ( $#part == 0 ) {
3563 0           $res{$part[0]} = ""; # No behavior defined.
3564             } else {
3565             # Save the behavior!
3566 0           $behave{$part[0]} = $res{$part[0]} = (split (/\s+/, $line, 2))[1];
3567 0           $may_change = 1;
3568             }
3569             }
3570              
3571 0 0         if ( $may_change ) {
3572             # Added per RFC 2389: It says OPTS is an assumed command if FEAT is
3573             # supported. But some servers fail to implement OPTS if there are
3574             # no features it can modify. So adding OPTS to the hash only if at
3575             # least one FEAT command has a behavior string defined!
3576             # If no behaviors are defined it will assume the OPTS command isn't
3577             # supported after all!
3578 0 0         my $msg = (exists $res{OPTS}) ? "Updating OPTS Command!"
3579             : "Auto-adding OPTS Command!";
3580 0           $self->_print_DBG ("<<+ ", CMD_INFO, "11 ", $msg, "\n");
3581              
3582             # Adding hash reference to list all valid OPTS commands ...
3583 0           $res{OPTS} = \%behave;
3584             }
3585             }
3586              
3587             # Only cache the results if the 1st time called! This cache is only used by
3588             # this method if OPTS is not supported! But its always used by _feat()!
3589 0 0         unless ( exists $arg->{help_FEAT_msg2} ) {
3590 0           my %res2 = %res;
3591 0           $arg->{help_FEAT_found2} = \%res2;
3592 0           $arg->{help_FEAT_msg2} = $self->last_message ();
3593             }
3594              
3595 0 0 0       unless ( $status == CMD_OK || $disable_croak ) {
3596 0           $self->_croak_or_return ();
3597             }
3598              
3599 0           return (\%res); # The caller is free to modify the hash if they wish!
3600             }
3601              
3602             #-----------------------------------------------------------------------
3603             # Enable/Disable the Croak logic!
3604             # Returns the previous Croak setting!
3605             #-----------------------------------------------------------------------
3606              
3607             sub set_croak {
3608 0     0 1   my $self = shift;
3609 0           my $turn_on = shift;
3610              
3611 0   0       my $res = ${*$self}{_FTPSSL_arguments}->{Croak} || 0;
3612              
3613 0 0         if ( defined $turn_on ) {
3614 0 0         if ( $turn_on ) {
    0          
3615 0           ${*$self}{_FTPSSL_arguments}->{Croak} = 1;
  0            
3616 0           } elsif ( exists ( ${*$self}{_FTPSSL_arguments}->{Croak} ) ) {
3617 0           delete ( ${*$self}{_FTPSSL_arguments}->{Croak} );
  0            
3618             }
3619             }
3620              
3621 0           return ( $res );
3622             }
3623              
3624             #-----------------------------------------------------------------------
3625             # Boolean check for croak!
3626             # Uses the current message as the croak message on error!
3627             #-----------------------------------------------------------------------
3628              
3629             sub _test_croak {
3630 0     0     my $self = shift;
3631 0           my $true = shift;
3632              
3633 0 0         unless ( $true ) {
3634 0           $ERRSTR = ${*$self}{_FTPSSL_arguments}->{last_ftp_msg};
  0            
3635 0 0         if ( ${*$self}{_FTPSSL_arguments}->{Croak} ) {
  0            
3636 0           my $c = (caller(1))[3];
3637 0 0 0       if ( defined $c && $c ne "Net::FTPSSL::login" ) {
3638 0           $self->_abort ();
3639 0           $self->quit ();
3640 0           ${*$self}{_FTPSSL_arguments}->{last_ftp_msg} = $ERRSTR;
  0            
3641             }
3642              
3643 0           croak ( $ERRSTR . "\n" );
3644             }
3645             }
3646              
3647 0           return ( $true );
3648             }
3649              
3650             #-----------------------------------------------------------------------
3651             # Error handling - Decides if to Croak or return undef ...
3652             # Has 2 modes, a regular member func & when not a member func ...
3653             #-----------------------------------------------------------------------
3654              
3655             sub _croak_or_return {
3656 0     0     my $self = shift;
3657              
3658             # The error code to use if we update the last message!
3659             # Or if we print it to FTPS_ERROR & we don't croak!
3660 0           my $err = CMD_ERROR . CMD_ERROR . CMD_ERROR;
3661              
3662 0 0         unless (defined $self) {
3663             # Called this way only by new() before $self is created ...
3664 0           my $should_we_die = shift;
3665 0           my $should_we_print = shift;
3666 0   0       $ERRSTR = shift || "Unknown Error";
3667              
3668 0 0         _print_LOG ( undef, "<<+ $err ", $ERRSTR, "\n" ) if ( $should_we_print );
3669 0 0         croak ( $ERRSTR . "\n" ) if ( $should_we_die );
3670              
3671             } else {
3672             # Called this way as a memeber func by everyone else ...
3673 0           my $replace_mode = shift; # 1 - append, 0 - replace,
3674             # undef - leave last_message() unchanged
3675 0           my $msg = shift;
3676 0   0       $ERRSTR = $msg || ${*$self}{_FTPSSL_arguments}->{last_ftp_msg};
3677              
3678             # Do 1st so updated if caller trapped the Croak!
3679 0 0 0       if ( defined $replace_mode && uc ($msg || "") ne "" ) {
      0        
3680 0 0 0       if ($replace_mode && uc (${*$self}{_FTPSSL_arguments}->{last_ftp_msg} || "") ne "" ) {
      0        
3681 0           ${*$self}{_FTPSSL_arguments}->{last_ftp_msg} .= "\n" . $err . " " . $msg;
  0            
3682             } else {
3683 0           ${*$self}{_FTPSSL_arguments}->{last_ftp_msg} = $err . " " . $msg;
  0            
3684             }
3685             }
3686              
3687 0 0         if ( ${*$self}{_FTPSSL_arguments}->{Croak} ) {
  0            
3688 0   0       my $c = (caller(1))[3] || "";
3689              
3690             # Trying to prevent infinite recursion ...
3691             # Also reseting the PIPE Signal in case catastrophic failure detected!
3692 0 0 0       if ( ref($self) eq __PACKAGE__ &&
      0        
      0        
      0        
3693 0           (! exists ${*$self}{_FTPSSL_arguments}->{_command_failed_}) &&
3694 0           (! exists ${*$self}{_FTPSSL_arguments}->{recursion}) &&
3695             $c ne "Net::FTPSSL::command" &&
3696             $c ne "Net::FTPSSL::response" ) {
3697 0           ${*$self}{_FTPSSL_arguments}->{recursion} = "TRUE";
  0            
3698 0           my $tmp = ${*$self}{_FTPSSL_arguments}->{last_ftp_msg};
  0            
3699 0           local $SIG{PIPE} = "IGNORE"; # Limits scope to just current block!
3700 0           $self->_abort ();
3701 0           $self->quit ();
3702 0           ${*$self}{_FTPSSL_arguments}->{last_ftp_msg} = $tmp;
  0            
3703             }
3704              
3705             # Only do if writing the message to the error log file ...
3706 0 0 0       if ( defined $replace_mode && uc ($msg || "") ne "" &&
      0        
      0        
3707 0           ${*$self}{_FTPSSL_arguments}->{debug} == 2 ) {
3708 0           _print_LOG ( $self, "<<+ $err ", $msg, "\n" );
3709             }
3710              
3711 0           croak ( $ERRSTR . "\n" );
3712             }
3713              
3714             # Handles both cases of writing to STDERR or the error log file ...
3715 0 0 0       if ( defined $replace_mode && uc ($msg || "") ne "" && ${*$self}{_FTPSSL_arguments}->{debug} ) {
  0   0        
      0        
3716 0           _print_LOG ( $self, "<<+ $err " . $msg . "\n" );
3717             }
3718             }
3719              
3720 0           return ( undef );
3721             }
3722              
3723             #-----------------------------------------------------------------------
3724             # Messages handler
3725             # ----------------------------------------------------------------------
3726             # Called by both Net::FTPSSL and IO::Socket::INET classes.
3727             #-----------------------------------------------------------------------
3728              
3729             sub command {
3730 0     0 0   my $self = shift; # Remaining arg(s) accessed directly.
3731              
3732 0           my @args;
3733             my $data;
3734              
3735             # Remove any previous failure ...
3736 0           delete ( ${*$self}{_FTPSSL_arguments}->{_command_failed_} );
  0            
3737              
3738             # remove undef values from the list.
3739             # Maybe I have to find out why those undef were passed.
3740 0           @args = grep ( defined($_), @_ );
3741              
3742             $data = join( " ",
3743 0           map { /\n/
3744 0 0         ? do { my $n = $_; $n =~ tr/\n/ /; $n }
  0            
  0            
  0            
3745             : $_;
3746             } @args
3747             );
3748              
3749             # Log the command being executed ...
3750 0 0         if ( ${*$self}{_FTPSSL_arguments}->{debug} ) {
  0            
3751 0 0         my $prefix = ( ref($self) eq __PACKAGE__ ) ? ">>> " : "SKT >>> ";
3752 0 0         if ( $data =~ m/^PASS\s/ ) {
    0          
3753 0           _print_LOG ( $self, $prefix, "PASS *******\n" ); # Don't echo passwords
3754             } elsif ( $data =~ m/^USER\s/ ) {
3755 0           _print_LOG ( $self, $prefix, "USER +++++++\n" ); # Don't echo user names
3756             } else {
3757 0           _print_LOG ( $self, $prefix, $data, "\n" ); # Echo everything else
3758             }
3759             }
3760              
3761 0           $data .= "\015\012";
3762              
3763 0           my $len = length $data;
3764 0           my $written = syswrite( $self, $data, $len );
3765 0 0         unless ( defined $written ) {
3766 0           ${*$self}{_FTPSSL_arguments}->{_command_failed_} = "ERROR";
  0            
3767 0           my $err_msg = "Can't write command on socket: $!";
3768 0           carp "$err_msg"; # This prints a warning.
3769             # Not called as an object member in case $self not a Net::FTPSSL obj.
3770 0           _my_close ($self); # Old way $self->close();
3771 0           _croak_or_return ($self, 0, $err_msg);
3772 0           return $self; # Included here due to non-standard _croak_or_return() usage.
3773             }
3774              
3775 0           return $self; # So can directly call response()!
3776             }
3777              
3778             # -----------------------------------------------------------------------------
3779             # Some responses take multiple lines to finish. ("211-" [more] vs "211 " [done])
3780             # Some responses have CR's embeded in them. (ie: no code in the next line)
3781             # Sometimes the data channel response comes with the open data connection msg.
3782             # (Especially if the data channel is not encrypted or the file is small.)
3783             # So be careful, you will be blocked if you read past the last row of the
3784             # current response or return the wrong code if you get into the next response!
3785             # (And will probably hang the next time response() is called.)
3786             # So far the only thing I haven't seen is a call to sysread() returning a
3787             # partial line response! (Drat, that just happened! See 0.20 Change notes.)
3788             # -----------------------------------------------------------------------------
3789             # Called by both Net::FTPSSL and IO::Socket::INET classes.
3790             # Hence using func($self, ...) instead of $self->func(...)
3791             # -----------------------------------------------------------------------------
3792             # Returns a single digit response code! (The CMD_* constants!)
3793             # -----------------------------------------------------------------------------
3794             sub response {
3795 0     0 0   my $self = shift;
3796 0   0       my $ccc_mess = shift || 0; # Only set by the CCC command! Hangs if not used.
3797              
3798             # The buffer size to use during the sysread() call on the command channel.
3799 0           my $buffer_size = 4096;
3800              
3801             # Uncomment to experiment with variable buffer sizes.
3802             # Very usefull in debugging _response_details () & simulating server issues.
3803             # Supports any value >= 1.
3804             # $buffer_size = 10;
3805              
3806             # The warning to use when printing past the end of the current response!
3807             # Used in place of $prefix in certain conditions.
3808 0           my $warn = "Warning: Attempted to read past end of response! ";
3809              
3810             # Only continue if the command() call worked!
3811             # Otherwise on failure this method will hang!
3812             # We already printed out the failure message in command() if not croaking!
3813 0 0         return (CMD_ERROR) if ( exists ${*$self}{_FTPSSL_arguments}->{_command_failed_} );
  0            
3814              
3815 0           ${*$self}{_FTPSSL_arguments}->{last_ftp_msg} = ""; # Clear out the message
  0            
3816 0 0         my $prefix = ( ref($self) eq __PACKAGE__ ) ? "<<< " : "SKT <<< ";
3817              
3818 0           my $timeout = ${*$self}{_FTPSSL_arguments}->{Timeout};
  0            
3819              
3820 0 0 0       my $sep = ( ${*$self}{_FTPSSL_arguments}->{debug} && ${*$self}{_FTPSSL_arguments}->{debug_extra} ) ? "===============" : undef;
3821              
3822             # Starting a new message ...
3823 0           ${*$self}{_FTPSSL_arguments}->{last_ftp_msg} = "";
  0            
3824 0           my $data = "";
3825 0           my ($done, $complete) = (0, 1);
3826              
3827             # Check if we need to process anything read in past the previous command.
3828             # Hopefully under normal conditions we'll find nothing to process.
3829 0 0         if ( exists ${*$self}{_FTPSSL_arguments}->{next_ftp_msg} ) {
  0            
3830 0 0         _print_LOG ( $self, "Info: Response found from previous read ...\n") if ( ${*$self}{_FTPSSL_arguments}->{debug} );
  0            
3831 0           $data = ${*$self}{_FTPSSL_arguments}->{next_ftp_msg};
  0            
3832 0           delete ( ${*$self}{_FTPSSL_arguments}->{next_ftp_msg} );
  0            
3833 0           ($done, $complete) = _response_details ($self, $prefix, \$data, 0, $ccc_mess);
3834 0 0 0       if ( $done && $complete ) {
3835 0           _print_edited_response ( $self, $prefix, ${*$self}{_FTPSSL_arguments}->{last_ftp_msg}, $sep, 0 );
  0            
3836 0           _print_edited_response ( $self, $warn, ${*$self}{_FTPSSL_arguments}->{next_ftp_msg}, $sep, 2 );
  0            
3837 0           return last_status_code ( $self );
3838             }
3839              
3840             # Should never happen, but using very short timeout on continued commands.
3841 0           $timeout = 2;
3842             }
3843              
3844             # Check if there is data still pending on the command channel ...
3845 0           my $rin = "";
3846 0           vec ($rin, fileno($self), 1) = 1;
3847 0           my $res = select ( $rin, undef, undef, $timeout );
3848 0 0         if ( $res > 0 ) {
    0          
3849             # Now lets read the response from the command channel itself.
3850 0           my $cnt = 0;
3851 0           while ( sysread( $self, $data, $buffer_size ) ) {
3852 0           ($done, $complete) = _response_details ($self, $prefix, \$data, $done, $ccc_mess);
3853 0           ++$cnt;
3854 0 0 0       last if ($done && $complete);
3855             }
3856              
3857             # Check for errors ...
3858 0 0 0       if ( $done && $complete ) {
    0 0        
3859             # A no-op to protect against random setting of "$!" on no real error!
3860 0           my $nothing = "";
3861              
3862             } elsif ( $cnt == 0 || $! ne "" ) {
3863 0 0         if ($cnt > 0) {
3864             # Will put brackes arround the error reponse!
3865 0           _print_edited_response ( $self, $prefix, ${*$self}{_FTPSSL_arguments}->{last_ftp_msg}, $sep, 1 );
  0            
3866 0           _print_edited_response ( $self, $warn, ${*$self}{_FTPSSL_arguments}->{next_ftp_msg}, $sep, 2 );
  0            
3867             }
3868 0           _croak_or_return ($self, 0, "Unexpected EOF on Command Channel [$cnt] ($done, $complete) ($!)");
3869 0           return (CMD_ERROR);
3870             }
3871              
3872 0           } elsif ( ${*$self}{_FTPSSL_arguments}->{last_ftp_msg} ne "" ) {
3873             # A Timeout here is OK, it meant the previous command was complete.
3874 0           my $nothing = "";
3875              
3876             } else {
3877             # Will put brackes arround the error reponse!
3878 0           _print_edited_response ( $self, $prefix, ${*$self}{_FTPSSL_arguments}->{last_ftp_msg}, $sep, 1 );
  0            
3879 0           _print_edited_response ( $self, $warn, ${*$self}{_FTPSSL_arguments}->{next_ftp_msg}, $sep, 2 );
  0            
3880 0           _croak_or_return ($self, 0, "Timed out waiting for a response! [$res] ($!)");
3881 0           return (CMD_ERROR);
3882             }
3883              
3884             # Now print out the final patched together responses ...
3885 0           _print_edited_response ( $self, $prefix, ${*$self}{_FTPSSL_arguments}->{last_ftp_msg}, $sep, 0 );
  0            
3886 0           _print_edited_response ( $self, $warn, ${*$self}{_FTPSSL_arguments}->{next_ftp_msg}, $sep, 2 );
  0            
3887              
3888             # Returns the 1st digit of the 3 digit status code!
3889 0           return last_status_code ( $self );
3890             }
3891              
3892             #-----------------------------------------------------------------------
3893             # Mask sensitive information before it's written to the log file.
3894             # Separated out since done in multiple places.
3895             #-----------------------------------------------------------------------
3896             sub _print_edited_response {
3897 0     0     my $self = shift;
3898 0           my $prefix = shift; # "<<< " vs "SKT <<< ".
3899 0           my $msg = shift; # The response to print. (may be undef)
3900 0           my $sep = shift; # An optional separator string.
3901 0           my $bracket = shift; # 0 or 1 or 2.
3902              
3903             # Tells which separator to use to break up lines in $msg!
3904 0 0         my $breakStr = ($bracket == 2) ? "\015\012" : "\n";
3905              
3906             # A safety check to simplify when calling with undefined {next_ftp_msg}.
3907 0 0         unless (defined $msg) {
3908 0           return;
3909             }
3910              
3911 0 0         if ( ${*$self}{_FTPSSL_arguments}->{debug} ) {
  0            
3912             # Do we need to hide a value in the logged response ???
3913 0 0         if ( exists ${*$self}{_FTPSSL_arguments}->{_hide_value_in_response_} ) {
  0            
3914 0           my $val = _mask_regex_chars ($self, ${*$self}{_FTPSSL_arguments}->{_hide_value_in_response_});
  0            
3915 0   0       my $mask = ${*$self}{_FTPSSL_arguments}->{_mask_value_in_response_} || "????";
3916 0           $msg =~ s/\s${val}($|[\s.!,])/ <$mask>${1}/g;
3917             }
3918              
3919 0 0         if ($bracket) {
3920 0           $msg = $prefix . "[" . join ("]\n${prefix}[", split ($breakStr, $msg)) . "]";
3921             } else {
3922 0           $msg = $prefix . join ("\n$prefix", split ($breakStr, $msg));
3923             }
3924              
3925 0 0 0       if ( defined $sep && $sep !~ m/^\s*$/ ) {
3926 0           $msg = "Start: " . $sep . "\n" . $msg . "\nEnd::: " . $sep;
3927             }
3928 0           _print_LOG ( $self, $msg, "\n");
3929             }
3930              
3931 0           return;
3932             }
3933              
3934             #-----------------------------------------------------------------------
3935             # Broken out from response() in order to simplify the logic.
3936             # The previous version was getting way too convoluted to support.
3937             # Any bugs in this function easily causes things to hang or insert
3938             # random into the returned messages!
3939             #-----------------------------------------------------------------------
3940             # If you need to turn on the logging for this method use "Debug => 99"
3941             # in the constructor!
3942             #-----------------------------------------------------------------------
3943             # What a line should look like
3944             # - --- Continuation line(s) [repeateable]
3945             # --- Response completed line
3946             # Anything else means it's a Continuation line with embedded 's.
3947             # I think its safe to say the response completed line dosn't have
3948             # any extra 's embeded in it. Otherwise it's kind of difficult
3949             # to know when to stop reading from the socket & risk hangs.
3950             #-----------------------------------------------------------------------
3951             # But what I actually saw in many cases: (list not complete)
3952             # 2
3953             # 13-First Line
3954             # 213
3955             # -Second Line
3956             # 213-
3957             # Third Line
3958             # 213-Fourth
3959             # Line
3960             # Turns out sysread() isn't generous. It returns as little as possible
3961             # sometimes. Even when there is plenty of space left in the buffer.
3962             # Hence the strange behaviour above. But once all the pieces are put
3963             # together properly, you see what you expected in the 1st place.
3964             #-----------------------------------------------------------------------
3965             # Returns if it thinks the current response is done & complete or not.
3966             # end_respnose - (passed as "$status" next time called)
3967             # 0 - Response isn't complete yet.
3968             # 1 - Response was done, but may or may not be truncated in .
3969             # response_complete - Tells if the final line is complete or truncated.
3970             # 0 - Line was truncated!
3971             # 1 - Last line was complete!
3972             # Both must be true to stop reading from the socket.
3973             # If we've read past the response into the next one, we don't stop
3974             # reading until the overflow response is complete as well. Otherwise
3975             # the Timeout logic might not work properly later on.
3976             #-----------------------------------------------------------------------
3977             # The data buffer. I've seen the following:
3978             # 1) A line begining with: \012 (The \015 ended the pevious buffer)
3979             # 2) A line ending with: \015 (The \012 started the next buffer)
3980             # 3) Lines not ending with: \015\012
3981             # 4) A line only containing: \015\012
3982             # 5) A line only containing: \012
3983             # 6) Lines ending with: \015\012
3984             # If you see the 1st three items, you know there is more to read
3985             # from the socket. If you see the last 3 items, it's possible
3986             # that the next read from the socket will hang if you've already
3987             # seen the response complete message. So be careful here!
3988             #-----------------------------------------------------------------------
3989             sub _response_details {
3990 0     0     my $self = shift;
3991 0           my $prefix = shift; # "<<< " vs "SKT <<< ".
3992 0           my $data_ref = shift; # The data buffer to parse ...
3993 0           my $status = shift; # 0 or 1 (the returned status from previous call)
3994              
3995 0           my $ccc_kludge = shift; # Tells us if we are dealing with a corrupted CC
3996             # due to the aftermath of a CCC command!
3997             # 1st hit terminates the command in this case!
3998              
3999             # The return values ...
4000 0           my ($end_response, $response_complete) = (0, 0);
4001              
4002             # A more restrictive option for turning on logging is needed in this method.
4003             # Otherwise too much info is written to the logs and it is very confusing.
4004             # (Debug => 99 turns this extra logging on!)
4005             # So only use this special option if we need to debug this one method!
4006 0   0       my $debug = ${*$self}{_FTPSSL_arguments}->{debug} && ${*$self}{_FTPSSL_arguments}->{debug_extra};
4007              
4008             # Assuming that if the line doesn't end in a , the response is truncated
4009             # and we'll need the next sysread() to continue with the response.
4010             # Split drops trailing , so need this flag to detect this.
4011 0 0         my $end_with_cr = (substr (${$data_ref}, -2) eq "\015\012") ? 1 : 0;
  0            
4012              
4013 0 0         if ( $debug ) {
4014 0 0         my $type = ( exists ${*$self}{_FTPSSL_arguments}->{next_ftp_msg} ) ? "Overflow" : "Current";
  0            
4015 0 0         my $k = $ccc_kludge ? ", Kludge: $ccc_kludge" : "";
4016 0           _print_LOG ($self, "In _response_details ($type, Status: $status, len = ", length (${$data_ref}), ", End: ${end_with_cr}${k})\n");
  0            
4017             }
4018              
4019 0           my ($ref, $splt);
4020 0 0         if ( exists ${*$self}{_FTPSSL_arguments}->{next_ftp_msg} ) {
  0            
4021 0           $ref = \${*$self}{_FTPSSL_arguments}->{next_ftp_msg};
  0            
4022 0           $splt = "\015\012";
4023             } else {
4024 0           $ref = \${*$self}{_FTPSSL_arguments}->{last_ftp_msg};
  0            
4025 0           $splt = "\n";
4026             }
4027              
4028             # Sysread() does split the \015 & \012 to seperate lines, so test for it!
4029             # And fix the problem as well if it's found!
4030 0           my $index = 0;
4031 0 0         if ( substr (${$data_ref}, 0, 1) eq "\012" ) {
  0            
4032             # It hangs if I strip off from $data_ref, so handle later! (via $index)
4033 0 0         if ( substr (${$ref}, -1) eq "\015" ) {
  0            
4034 0           substr (${$ref}, -1) = $splt; # Replace with proper terminator.
  0            
4035 0           $index = 1;
4036 0 0         _print_LOG ($self, "Fixed 015/012 split!\n") if ( $debug );
4037 0 0         if ( ${$data_ref} eq "\012" ) {
  0            
4038 0   0       return ($status || $ccc_kludge, 1); # Only thing on the line.
4039             }
4040             }
4041             }
4042              
4043             # Check if the last line from the previous call was trucated ...
4044 0           my $trunc = "";
4045 0 0 0       if ( ${$ref} ne "" && substr (${$ref}, -length($splt)) ne $splt ) {
  0            
  0            
4046 0           $trunc = (split ($splt, ${$ref}))[-1];
  0            
4047             }
4048              
4049 0           my @term;
4050             my @data;
4051 0 0         if ( $end_with_cr ) {
4052             # Protects from split throwing away trailing empty lines ...
4053 0           @data = split( "\015\012", substr ( ${$data_ref}, $index ) . "|" );
  0            
4054 0           pop (@data);
4055             } else {
4056             # Last line was truncated ...
4057 0           @data = split( "\015\012", substr ( ${$data_ref}, $index ) );
  0            
4058             }
4059              
4060             # Tag which lines are complete! (Only the last one can be truncated)
4061 0           foreach (0..$#data) {
4062 0           $term[$_] = 1;
4063             }
4064 0           $term[-1] = $end_with_cr;
4065              
4066             # Current command or rolled over to the next command ???
4067 0           my (@lines, @next, @line_term, @next_term);
4068 0 0         if ( exists ${*$self}{_FTPSSL_arguments}->{next_ftp_msg} ) {
  0            
4069 0           @next = @data;
4070 0           @next_term = @term;
4071 0           @data = @term = @lines; # All are now empty.
4072             } else {
4073 0           @lines = @data;
4074 0           @line_term = @term;
4075 0           @data = @term = @next; # All are now empty.
4076             }
4077              
4078             # ------------------------------------------------------------------------
4079             # Now lets process the response messages we've read in. See the comments
4080             # above response() on why this code is such a mess.
4081             # But it's much cleaner than it used to be.
4082             # ------------------------------------------------------------------------
4083 0           my ( $code, $sep, $desc, $done ) = ( CMD_ERROR, "-", "", 0 );
4084 0           my ( $line, $term );
4085              
4086 0           foreach ( 0..$#lines ) {
4087 0           $line = $lines[$_];
4088 0           $term = $line_term[$_];
4089              
4090             # If the previous line was the end of the response ...
4091             # There can be no in that line!
4092             # So if true, it means we've read past the end of the response!
4093 0 0         if ( $done ) {
4094 0           push (@next, $line);
4095 0           push (@next_term, $term);
4096 0           next;
4097             }
4098              
4099             # Always represents the start of a new line ...
4100 0           my $test = $trunc . $line;
4101 0           $trunc = ""; # No longer possible for previous line to be truncated.
4102              
4103             # Check if this line marks the response complete! (If sep is a space)
4104 0 0         if ( $test =~ m/^(\d{3})([-\s])(.*)$/s ) {
4105 0           ($code, $sep, $desc) = ($1, $2, $3);
4106 0 0         $done = ($sep eq " ") ? $term : 0;
4107              
4108             # Update the return status ...
4109 0 0         $end_response = ($sep eq " ") ? 1: 0;
4110 0           $response_complete = $term;
4111             }
4112              
4113             # The CCC command messes up the Command Channel for a while!
4114             # So we need this work arround to immediately stop processing
4115             # to avoid breaking the command channel or hanging things.
4116 0 0 0       if ( $ccc_kludge && $term && ! $done ) {
      0        
4117 0 0         _print_LOG ( $self, "Kludge: 1st CCC work around detected ...\n") if ( $debug );
4118 0           $end_response = $response_complete = $done = 1;
4119             }
4120              
4121             # Save the unedited message ...
4122 0           ${*$self}{_FTPSSL_arguments}->{last_ftp_msg} .= $line;
  0            
4123              
4124             # Write to the log file if requested ...
4125             # But due to random splits, it risks not masking properly!
4126 0 0         _print_edited_response ( $self, $prefix, $line, undef, 1 ) if ( $debug );
4127              
4128             # Finish the current line ...
4129 0 0 0       if ($sep eq "-" && $term) {
4130 0           ${*$self}{_FTPSSL_arguments}->{last_ftp_msg} .= "\n"; # Restore the internal .
  0            
4131             }
4132             }
4133              
4134             # ------------------------------------------------------------------------
4135             # Process the response to the next command ... (read in with this one)
4136             # Shouldn't happen, but it sometimes does ...
4137             # ------------------------------------------------------------------------
4138 0           my $warn = "Warning: Attempting to read past end of response! ";
4139 0           my $next_kludge = 0;
4140 0           $done = 0;
4141 0           foreach ( 0..$#next ) {
4142 0           $next_kludge = 1;
4143 0           $line = $next[$_];
4144 0           $term = $next_term[$_];
4145              
4146             # We've read past the end of the current response into the next one ...
4147 0 0         _print_edited_response ( $self, $warn, $line, undef, 2 ) if ( $debug );
4148              
4149 0 0         if ( ! exists ${*$self}{_FTPSSL_arguments}->{next_ftp_msg} ) {
  0 0          
4150 0           ${*$self}{_FTPSSL_arguments}->{next_ftp_msg} = $line;
  0            
4151             } elsif ( $trunc ne "" ) {
4152 0           ${*$self}{_FTPSSL_arguments}->{next_ftp_msg} .= $line;
  0            
4153             } else {
4154 0           ${*$self}{_FTPSSL_arguments}->{next_ftp_msg} .= "\015\012" . $line;
  0            
4155             }
4156              
4157             # Always represents the start of a new line ...
4158 0           my $test = $trunc . $line;
4159 0           $trunc = ""; # No longer possible for previous line to be truncated.
4160              
4161             # Check if this line marks the response complete! (If sep is a space)
4162 0 0         if ( $test =~ m/^(\d{3})([-\s])(.*)$/s ) {
4163 0           ($code, $sep, $desc) = ($1, $2, $3);
4164 0 0         $done = ($sep eq " ") ? $term : 0;
4165              
4166             # Update the return status ...
4167 0 0         $end_response = ($sep eq " ") ? 1: 0;
4168 0           $response_complete = $term;
4169             }
4170             }
4171              
4172 0 0 0       if ( $end_with_cr && exists ${*$self}{_FTPSSL_arguments}->{next_ftp_msg} ) {
  0            
4173 0           ${*$self}{_FTPSSL_arguments}->{next_ftp_msg} .= "\015\012";
  0            
4174             }
4175              
4176             # Complete the Kludge! (Only needed if entered the @next loop!)
4177 0 0 0       if ( $ccc_kludge && $next_kludge && ! ($end_response && $response_complete) ) {
      0        
      0        
4178 0 0         _print_LOG ( $self, "Kludge: 2nd CCC work around detected ...\n") if ( $debug );
4179 0           $end_response = $response_complete = 1;
4180             }
4181              
4182 0           return ($end_response, $response_complete);
4183             }
4184              
4185             #-----------------------------------------------------------------------
4186              
4187             sub last_message {
4188 0     0 1   my $self = shift;
4189 0           return ${*$self}{_FTPSSL_arguments}->{last_ftp_msg};
  0            
4190             }
4191              
4192             #-----------------------------------------------------------------------
4193             # This method sets up a trap so that warnings can be written to my logs.
4194             # Always call like: $ftps->trapWarn().
4195             #-----------------------------------------------------------------------
4196             sub trapWarn {
4197 0     0 1   my $self = shift;
4198 0   0       my $force = shift || 0; # Only used by some of the t/*.t test cases!
4199             # Do not use the $force parameter otherwise!
4200             # You've been warned!
4201              
4202 0           my $res = 0; # Warnings are not yet trapped ...
4203              
4204             # Only trap warnings if a debug log is turned on to write to ...
4205 0 0 0       if ( defined $self && ${*$self}{_FTPSSL_arguments}->{debug} &&
  0   0        
      0        
4206             ($force || exists ${*$self}{_FTPSSL_arguments}->{ftpssl_filehandle}) ) {
4207 0           my $tmp = $SIG{__WARN__};
4208              
4209             # Must do as an inline function call so things will go to
4210             # the proper log file.
4211 0     0     my $func_ref = sub { $self->_print_LOG ("WARNING: ", $_[0]); };
  0            
4212              
4213 0           $warn_list{$self} = $func_ref;
4214              
4215             # This test prevents a recursive trap ...
4216 0 0         if (! exists $warn_list{OTHER}) {
4217 0           $warn_list{OTHER} = $tmp;
4218 0           $SIG{__WARN__} = __PACKAGE__ . "::_handleWarn";
4219             }
4220              
4221 0           $res = 1; # The warnings are trapped now ...
4222             }
4223              
4224 0           return ($res); # Whether trapped or not!
4225             }
4226              
4227             # Warning, this method cannot be called as a member function.
4228             # So it will never reference $self! It's also not documented in the POD!
4229             # See trapWarn() instead!
4230             sub _handleWarn {
4231 0     0     my $warn = shift; # The warning being processed ...
4232              
4233             # Print warning to each of the registered log files.
4234             # Will always be a reference to the function to call!
4235 0           my $func_ref;
4236 0           foreach ( keys %warn_list ) {
4237 0 0         next if ($_ eq "OTHER");
4238 0           $func_ref = $warn_list{$_};
4239 0           $func_ref->( $warn ); # Prints to an open Net::FTPSSL log file ...
4240             }
4241              
4242             # Was there any parent we replaced to chain the warning to?
4243 0 0 0       if (exists $warn_list{OTHER} && defined $warn_list{OTHER}) {
4244 0           $func_ref = $warn_list{OTHER};
4245 0 0 0       if (ref ($func_ref) eq "CODE") {
    0          
    0          
4246 0           $func_ref->( $warn );
4247             } elsif ( $func_ref eq "" || $func_ref eq "DEFAULT" ) {
4248 0           print STDERR "$warn\n";
4249             } elsif ( $func_ref ne "IGNORE" ) {
4250 0           &{\&{$func_ref}}($warn); # Will throw exception if doesn't exist!
  0            
  0            
4251             }
4252             }
4253             }
4254              
4255             # Called automatically when an instance of Net::FTPSSL goes out of scope!
4256             # Only called if new() was successfull! Used so we could remove all this
4257             # termination logic from quit()!
4258             sub DESTROY {
4259 0     0     my $self = shift;
4260              
4261 0 0         if ( ${*$self}{_FTPSSL_arguments}->{debug} ) {
  0            
4262             # Disable optional trapping of the warnings written to the log file
4263             # now that we're going out of scope!
4264 0 0         if ( exists $warn_list{$self} ) {
4265 0           delete ($warn_list{$self});
4266             }
4267              
4268             # Now let's close the log file itself ...
4269 0           $self->_close_LOG ();
4270              
4271             # Comment out this Debug Statement when no longer needed!
4272             # print STDERR "Good Bye FTPSSL instance! (", ref($self), ") [$self]\n";
4273             }
4274             }
4275              
4276             # Called automatically when this module is removed from memory.
4277             # NOTE: Due to how Perl's garbage collector works, in many cases END may be
4278             # called before DESTROY is called! Not what you'd expect!
4279             sub END {
4280             # Restore to original setting when the module gets unloaded from memory!
4281             # If this entry wasn't created, then we never redirected any warnings!
4282 16 50   16   58122 if ( exists $warn_list{OTHER} ) {
4283 0           $SIG{__WARN__} = $warn_list{OTHER};
4284 0           delete ( $warn_list{OTHER} );
4285             # print STDERR "Good Bye FTPSSL! (", $SIG{__WARN__}, ")\n";
4286             }
4287             }
4288              
4289             #-----------------------------------------------------------------------
4290             # Not in POD on purpose. It's an internal work arround for a debug issue.
4291             # Replace all chars known to cause issues with RegExp by putting
4292             # a "\" in front of it to remove the chars special meaning.
4293             # (less messy than putting it into square brackets ...)
4294             #-----------------------------------------------------------------------
4295             sub _mask_regex_chars {
4296 0     0     my $self = shift;
4297 0           my $mask = shift;
4298              
4299 0           $mask =~ s/([([?+*\\^$).])/\\$1/g;
4300              
4301 0           return ($mask);
4302             }
4303              
4304             #-----------------------------------------------------------------------
4305             # Added to make backwards compatible with Net::FTP
4306             #-----------------------------------------------------------------------
4307             sub message {
4308 0     0 1   my $self = shift;
4309 0           return ${*$self}{_FTPSSL_arguments}->{last_ftp_msg};
  0            
4310             }
4311              
4312             sub last_status_code {
4313 0     0 1   my $self = shift;
4314              
4315 0           my $code = CMD_ERROR;
4316 0 0         if ( defined ${*$self}{_FTPSSL_arguments}->{last_ftp_msg} ) {
  0            
4317 0           $code = substr (${*$self}{_FTPSSL_arguments}->{last_ftp_msg}, 0, 1);
  0            
4318             }
4319              
4320 0           return ($code);
4321             }
4322              
4323             sub _change_status_code {
4324 0     0     my $self = shift;
4325 0           my $code = shift; # Should be a single digit. Strange behaviour otherwise!
4326              
4327 0 0         if ( defined ${*$self}{_FTPSSL_arguments}->{last_ftp_msg} ) {
  0            
4328 0           substr (${*$self}{_FTPSSL_arguments}->{last_ftp_msg}, 0, 1) = $code;
  0            
4329             }
4330              
4331 0           return;
4332             }
4333              
4334             sub restart {
4335 0     0 1   my $self = shift;
4336 0           my $offset = shift;
4337 0           ${*$self}{_FTPSSL_arguments}->{net_ftpssl_rest_offset} = $offset;
  0            
4338 0           return (1);
4339             }
4340              
4341             #-----------------------------------------------------------------------
4342             # Implements data channel call back functionality ...
4343             #-----------------------------------------------------------------------
4344             sub set_callback {
4345 0     0 1   my $self = shift;
4346 0           my $func_ref = shift; # The callback function to call.
4347 0           my $end_func_ref = shift; # The end callback function to call.
4348 0           my $cb_work_area_ref = shift; # Optional ref to the callback work area!
4349              
4350 0 0 0       if ( defined $func_ref && defined $end_func_ref ) {
4351 0           ${*$self}{_FTPSSL_arguments}->{callback_func} = $func_ref;
  0            
4352 0           ${*$self}{_FTPSSL_arguments}->{callback_end_func} = $end_func_ref;
  0            
4353 0           ${*$self}{_FTPSSL_arguments}->{callback_data} = $cb_work_area_ref;
  0            
4354             } else {
4355 0           delete ( ${*$self}{_FTPSSL_arguments}->{callback_func} );
  0            
4356 0           delete ( ${*$self}{_FTPSSL_arguments}->{callback_end_func} );
  0            
4357 0           delete ( ${*$self}{_FTPSSL_arguments}->{callback_data} );
  0            
4358             }
4359              
4360 0           return;
4361             }
4362              
4363             sub _end_callback {
4364 0     0     my $self = shift;
4365 0           my $offset = shift; # Always >= 1. Index to original function called.
4366 0           my $total = shift;
4367              
4368 0           my $res;
4369 0           my $len = 0;
4370              
4371             # Is there an end callback function to use ?
4372 0 0         if ( defined ${*$self}{_FTPSSL_arguments}->{callback_end_func} ) {
  0            
4373 0           $res = &{${*$self}{_FTPSSL_arguments}->{callback_end_func}} ( (caller($offset))[3], $total,
  0            
4374 0           ${*$self}{_FTPSSL_arguments}->{callback_data} );
  0            
4375              
4376             # Now check the results for terminating the call back.
4377 0 0         if (defined $res) {
4378 0 0         if ($res eq "") {
4379 0           $res = undef; # Make it easier to work with.
4380             } else {
4381 0           $len = length ($res);
4382 0           $total += $len;
4383             }
4384             }
4385             }
4386              
4387 0           return ($res, $len, $total);
4388             }
4389              
4390             sub _call_callback {
4391 0     0     my $self = shift;
4392 0           my $offset = shift; # Always >= 1. Index to original function called.
4393 0           my $data_ref = shift;
4394 0           my $data_len_ref = shift;
4395 0           my $total_len = shift;
4396              
4397 0           my $cb_flag = 0;
4398              
4399             # Is there is a callback function to use ?
4400 0 0         if ( defined ${*$self}{_FTPSSL_arguments}->{callback_func} ) {
  0            
4401              
4402             # Allowed to modify contents of $data_ref & $data_len_ref ...
4403 0           &{${*$self}{_FTPSSL_arguments}->{callback_func}} ( (caller($offset))[3],
  0            
4404             $data_ref, $data_len_ref, $total_len,
4405 0           ${*$self}{_FTPSSL_arguments}->{callback_data} );
  0            
4406 0           $cb_flag = 1;
4407             }
4408              
4409             # Calculate the new total length to use for next time ...
4410 0 0         $total_len += (defined $data_len_ref ? ${$data_len_ref} : 0);
  0            
4411              
4412 0 0         if ( wantarray ) {
4413 0           return ($total_len, $cb_flag);
4414             }
4415 0           return ($total_len);
4416             }
4417              
4418             sub _fmt_num {
4419 0     0     my $self = shift;
4420 0           my $num = shift;
4421              
4422             # Change: 1234567890 --> 1,234,567,890
4423 0           while ( $num =~ s/(\d)(\d{3}(\D|$))/$1,$2/ ) { }
4424              
4425 0           return ( $num );
4426             }
4427              
4428             #-----------------------------------------------------------------------
4429             # To assist in debugging the flags being used by this module ...
4430             #-----------------------------------------------------------------------
4431              
4432             sub _debug_print_hash
4433             {
4434 0     0     my $self = shift;
4435 0           my $host = shift;
4436 0           my $port = shift;
4437 0           my $mode = shift;
4438 0   0       my $obj = shift || $self; # So can log most GLOB object types ...
4439 0           my $sep = shift; # The optional separator char to print out.
4440              
4441 0           _print_LOG ( $self, "\nObject ", ref($obj), " Details ..." );
4442 0 0         _print_LOG ( $self, " ($host:$port - $mode)" ) if (defined $host);
4443 0           _print_LOG ( $self, "\n" );
4444              
4445             # Fix to support non-GLOB object types ...
4446 0           my @lst;
4447 0           my $hash = 0;
4448 0 0         if ( ref ($obj) eq "HASH" ) {
4449 0           @lst = sort keys %{$obj};
  0            
4450 0           $hash = 1;
4451             } else {
4452             # It's a GLOB reference ...
4453 0           @lst = sort keys %{*$obj};
  0            
4454             }
4455              
4456             # The separators to use ...
4457 0           my @seps = ( "==>", "===>",
4458             "---->", "++++>", "====>",
4459             "----->", "+++++>", "=====>",
4460             "------>", "++++++>", "======>" );
4461              
4462             # To help detect infinite recursive loops ...
4463 0           my %loop;
4464             my %empty;
4465              
4466 0           foreach (@lst) {
4467 0 0         unless ( defined $host ) {
4468 0 0         next unless ( m/^(io_|_SSL|SSL)/ );
4469             }
4470 0 0         my $val = ($hash) ? $obj->{$_} : ${*$obj}{$_};
  0            
4471              
4472 0           %loop = %empty; # Empty out the hash again ...
4473 0           _print_hash_tree ( $self, " ", 0, $_, $val, \@seps, \%loop );
4474             }
4475              
4476 0 0 0       if (defined $sep && $sep !~ m/^\s*$/) {
4477 0           _print_LOG ( $self, $sep x 60, "\n");
4478             } else {
4479 0           _print_LOG ( $self, "\n" );
4480             }
4481              
4482 0           return;
4483             }
4484              
4485             # Recursive so can handle unlimited depth of hash trees ...
4486             sub _print_hash_tree
4487             {
4488 0     0     my $self = shift;
4489 0           my $indent = shift;
4490 0           my $lvl = shift; # Index to the $sep_ref array reference.
4491 0           my $lbl = shift;
4492 0           my $val = shift;
4493 0           my $sep_ref = shift; # An array reference.
4494 0           my $loop_ref = shift; # A hash ref to detect infinit recursion with.
4495              
4496 0 0         my $prefix = ($lvl == 0) ? "" : "-- ";
4497 0 0         my $sep = (defined $sep_ref->[$lvl]) ? $sep_ref->[$lvl] : ".....>";
4498              
4499             # Make sure it always has a value ...
4500 0 0         $val = "(undef)" unless (defined $val);
4501              
4502             # Fix indentation in case "\n" appears in the value ...
4503 0 0         $val = join ("\n${indent} ", split (/\n/, $val)) unless (ref($val));
4504              
4505             # Fix in case it's a scalar reference ...
4506 0 0         $val .= " [" . ${$val} . "]" if ($val =~ m/SCALAR\(0/);
  0            
4507              
4508 0           my $msg = "${indent}${prefix}${lbl} ${sep} ${val}";
4509              
4510             # How deep to indent for the next level ... (add 4 spaces)
4511 0           $indent .= " ";
4512              
4513 0 0         if ( $val =~ m/ARRAY\(0/ ) {
    0          
4514 0           my $lst = join (", ", @{$val});
  0            
4515 0           _print_LOG ( $self, $msg, "\n" );
4516 0           _print_LOG ( $self, "${indent}[", $lst, "]\n" );
4517              
4518             } elsif ( $val =~ m/HASH\((0x[\da-zA-Z]+)\)/ ) {
4519 0           my $key = $1; # The Hash address ...
4520 0           my %start = %{$loop_ref};
  0            
4521              
4522 0           _print_LOG ( $self, $msg );
4523 0 0         if ( exists $loop_ref->{$key} ) {
4524 0           _print_LOG ($self, " ... Infinite Hash Loop Detected!\n");
4525             } else {
4526 0           $start{$key} = $loop_ref->{$key} = $val;
4527 0           _print_LOG ( $self, "\n" );
4528 0           foreach (sort keys %{$val}) {
  0            
4529 0           %{$loop_ref} = %start;
  0            
4530 0           _print_hash_tree ( $self, $indent, $lvl + 1, $_, $val->{$_},
4531             $sep_ref, $loop_ref );
4532             }
4533             }
4534              
4535             # Else not an ARRAY or HASH ...
4536             } else {
4537 0           _print_LOG ( $self, $msg, "\n" );
4538             }
4539             }
4540              
4541             #-----------------------------------------------------------------------
4542             # Provided so each class instance gets its own log file to write to.
4543             #-----------------------------------------------------------------------
4544             # Always writes to the log when called ...
4545             sub _print_LOG
4546             {
4547 0     0     my $self = shift;
4548 0           my $msg = shift;
4549              
4550 0           my $FILE;
4551              
4552             # Determine where to write the log message to ...
4553 0 0 0       if ( defined $self && exists ${*$self}{_FTPSSL_arguments}->{ftpssl_filehandle} ) {
  0 0          
4554 0           $FILE = ${*$self}{_FTPSSL_arguments}->{ftpssl_filehandle}; # A custom log file ...
  0            
4555             } elsif ( defined $FTPS_ERROR ) {
4556 0           $FILE = $FTPS_ERROR; # Write to file when called during new() ...
4557             } else {
4558 0           $FILE = \*STDERR; # Write to screen anyone ?
4559             }
4560              
4561 0           while ( defined $msg ) {
4562 0           print $FILE $msg; # Write to the log file ...
4563 0           $msg = shift;
4564             }
4565             }
4566              
4567             # Only write to the log if debug is turned on ...
4568             # So we don't have to test everywhere ...
4569             # Done this way so can be called in new() on a socket as well.
4570             sub _print_DBG
4571             {
4572 0     0     my $self = shift;
4573 0 0 0       if ( defined $self && ${*$self}{_FTPSSL_arguments}->{debug} ) {
  0            
4574 0           _print_LOG ( $self, @_ ); # Only if debug is turned on ...
4575             }
4576             }
4577              
4578             sub get_log_filehandle
4579             {
4580 0     0 1   my $self = shift;
4581              
4582 0           my $FILE;
4583 0 0 0       if ( defined $self && exists ${*$self}{_FTPSSL_arguments}->{ftpssl_filehandle} ) {
  0            
4584 0           $FILE = ${*$self}{_FTPSSL_arguments}->{ftpssl_filehandle};
  0            
4585             }
4586              
4587 0           return ($FILE);
4588             }
4589              
4590             sub _close_LOG
4591             {
4592 0     0     my $self = shift;
4593              
4594 0 0 0       if ( defined $self && exists ${*$self}{_FTPSSL_arguments}->{ftpssl_filehandle} ) {
  0            
4595 0           my $FILE = ${*$self}{_FTPSSL_arguments}->{ftpssl_filehandle};
  0            
4596 0 0         close ($FILE) if ( ${*$self}{_FTPSSL_arguments}->{debug} == 2 );
  0            
4597 0           delete ( ${*$self}{_FTPSSL_arguments}->{ftpssl_filehandle} );
  0            
4598 0           ${*$self}{_FTPSSL_arguments}->{debug} = 1; # Back to using STDERR again ...
  0            
4599             }
4600             }
4601              
4602             # A helper method to tell if it can be counted as a GLOB ...
4603             sub _isa_glob
4604             {
4605 0     0     my $self = shift;
4606 0           my $fh = shift;
4607              
4608 0           my $res = 0; # Assume not a file handle/GLOB ...
4609              
4610 0 0         if ( defined $fh ) {
4611 0           my $tmp = ref ( $fh );
4612 0 0         if ( $tmp ) {
4613 0 0 0       $res = 1 if ( $tmp eq "GLOB" || $fh->isa ("IO::Handle") );
4614             }
4615             }
4616              
4617 0           return ( $res );
4618             }
4619              
4620             #-----------------------------------------------------------------------
4621             # If the Domain/Family is passed as a string, this function will convert
4622             # it into the needed numerical value. [Only called by new().]
4623             sub _validate_domain {
4624 0     0     my $type = shift; # It's a string, not an Net::FTPSSL object!
4625 0           my $family = shift; # The tag used for this value.
4626 0           my $domain = shift; # Should never be undef when called.
4627 0           my $debug = shift;
4628 0           my $die = shift;
4629              
4630 0           my $ret;
4631              
4632 0 0         if ( $domain =~ m/^\d+$/ ) {
    0          
4633 0           $ret = $domain; # Already a numeric value, so just return it ...
4634              
4635             # Valid domains are inherited functions named after the value!
4636             } elsif ( $domain =~ m/^AF_/i ) {
4637 0 0         if ( $type->can ( uc ($domain) ) ) {
4638 0           my $func = $type . "::" . uc ($domain) . "()";
4639 0           $ret = eval $func; # Call the function to convert it to an integer!
4640             }
4641             }
4642              
4643 0 0         unless ( defined $ret ) {
4644 0           _croak_or_return ( undef, $die, $debug,
4645             "Unknown value \"${domain}\" for option ${family}." );
4646             }
4647              
4648             # Return the domain/family as a numeric value.
4649             # Can be undef if invalid & Croak is turned off.
4650 0           return ( $ret );
4651             }
4652              
4653              
4654             #-----------------------------------------------------------------------
4655              
4656             1;
4657              
4658             __END__