File Coverage

blib/lib/Net/FTPSSL.pm
Criterion Covered Total %
statement 135 2206 6.1
branch 6 1120 0.5
condition 0 593 0.0
subroutine 38 147 25.8
pod 48 53 90.5
total 227 4119 5.5


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