Subject: | Override PASV IP address, and override HELP lookup |
I am submitting a patch to Net::FTPSSL to correct two issues by adding
three new options: Commands, SiteCommands, and OverridePASV.
A particular vendor that I have to deal with runs an FTPS server that
identifies itself as "Connect:Enterprise UNIX". Connect:Enterprise is a
product of Sterling Commerce (www.sterlingcommerce.com). IBM just
acquired Sterling Commerce from AT&T on August 27th.
There appears to be an issue with Connect:Enterprise when the HELP
command is issued. The first "214" line of HELP is sent encrypted, but
the following list of recognized commands is sent in cleartext for some
reason, which breaks the command channel. I do not understand why the
list of commands is being sent in cleartext; this behavior does not
occur with vsftpd. Net::FTPSSL displays the first "214" line, then
displays "555 Unexpected EOF on command channel socket!". A Wireshark
capture confirms that the entire conversation is encrypted except for
the list of commands (which is quite readable).
To get around the issue, I have added two new options to the new()
constructor: Commands, and SiteCommands. Both the Commands and
SiteCommands options expect a reference to an array. By passing a list
of recognized commands to new(), the HELP cache in the code is
pre-populated, so the HELP command never actually runs. With this
option, I am finally able to upload a file to a Connect:Enterprise FTPS
server. This could also come in handy for those sites where HELP
reports that a certain command is usable, but trying to use that command
breaks the connection; if you can override the list of acceptable
commands from the start, you can prevent the command from being used.
I have been dealing with two separate vendors running
Connect:Enterprise. Both FTPS sites have the same HELP command issue.
I am sure that others who have to deal with this software would
appreciate these new options.
Another option I have added to the new() constructor is the OverridePASV
option, which is either true or false. This comes in handy when the
FTPS server has not been properly configured to report back the correct
IP address to the PASV command.
The other vendor using Connect:Enterprise is reporting the internal,
non-routable IP address of their FTPS server (10.x.x.x) in the PASV
command output, instead of their external IP address. This seems to be
an issue with vendors who do not know how to properly set up their FTPS
server. I called them and explained that there is an option in their
server software to change the IP address reported by the PASV command,
but they claim that option does not exist in Connect:Enterprise. I do
not believe that, but in any case they're not going to fix their server.
The data channel can't route to the invalid IP address, so the
connection times out.
The OverridePASV option will cause Net::FTPSSL to ignore the IP
address portion of the PASV command output, and it will continue to use
the original address of the connection. This may not work in situations
where round-robin DNS is being used by the vendor to load-balance FTPS
connections, but vendors who are doing load balancing are usually smart
enough to configure their FTPS servers properly in the first place.
FileZilla will automatically override the IP address returned by the
PASV command if it determines that the IP address is unroutable. That
could be another method to use instead of having an OverridePASV option,
but I went with a very simple patch as proof-of-concept. It shouldn't
be too hard to add automatic detection of unroutable IP addresses if you
wanted to do that instead.
FileZilla does not try to run the HELP command (it tries to run FEAT
instead, which fails), so perhaps that's why FileZilla has been working
as well.
I also can't make a TLS connection to these two FTPS sites using
Net::FTPSSL, although FileZilla can make TLS connections to these sites.
That's a separate issue, though, so I'll file that under a separate
ticket. I have been able to get around this so far by forcing an SSL
connection instead.
FYI, I am running Red Hat Enterprise Linux 5.5, with Perl 5.8.8, and
Net::FTPSSL 0.15.
Thanks,
Chris Thompson
Subject: | debugfile.txt |
Net-FTPSSL Version: 0.15
Server (port): ftps.amajorbank.com (20021)
SKT <<< 220 Welcome to A Major Bank Data Distribution Services. Time = 10:32:37
SKT >>> AUTH SSL
SKT <<< 234 AUTH TLS-P/SSL OK.
>>> USER myusername
<<< 331 Password required for myusername.
>>> PASS *******
<<< 230 Connect:Enterprise UNIX login ok, access restrictions apply.
>>> TYPE A
<<< 200 Type set to A.
>>> PBSZ 0
<<< 200 PBSZ 0 OK.
>>> PROT P
<<< 200 PROT P OK, data channel will be secured.
>>> PASV
<<< 227 Entering Passive Mode (11,22,33,44,82,8)
>>> HELP
<<< 214- The following commands are recognized (* =>'s unimplemented).
<<+ 555 Unexpected EOF on command channel socket!
<<+ 502 Unknown command ALLO.
>>> STOR myfile.txt
<<+ 555 Can't read command channel socket:
>>> QUIT
<<+ 555 Can't write command on socket: Broken pipe
<<+ 555 Unexpected EOF on command channel socket!
Subject: | FTPSSL.015.patch |
--- FTPSSL.pm 2010-04-27 11:02:25.000000000 -0500
+++ FTPSSL.pm.new 2010-09-16 15:39:19.000000000 -0500
@@ -69,6 +69,8 @@
my $arg = (ref ($_[0]) eq "HASH") ? $_[0] : {@_};
my %ssl_args; # Only referenced via $advanced.
+ my @supported_cmds; # Only referenced via $commands.
+ my @supported_site_cmds; # Only referenced via $site_commands.
my $encrypt_mode = $arg->{Encryption} || EXP_CRYPT;
my $port = $arg->{Port} || (($encrypt_mode eq IMP_CRYPT) ? 990 : 21);
@@ -88,6 +90,12 @@
my $advanced = (ref ($arg->{SSL_Advanced}) eq "HASH")
? $arg->{SSL_Advanced} : \%ssl_args;
+ my $commands = (ref ($arg->{Commands}) eq "ARRAY")
+ ? $arg->{Commands} : \@supported_cmds;
+ my $site_commands = (ref ($arg->{SiteCommands}) eq "ARRAY")
+ ? $arg->{SiteCommands} : \@supported_site_cmds;
+ my $override_pasv = $arg->{OverridePASV} || 0;
+
# Determine where to write the Debug info to ...
if ( $use_logfile ) {
my $open_mode = ( $debug == 2 ) ? ">>" : ">";
@@ -207,6 +215,11 @@
${*$obj}{data_prot} = $data_prot;
${*$obj}{Croak} = $die;
${*$obj}{FixPutTs} = ${*$obj}{FixGetTs} = $pres_ts;
+ ${*$obj}{Commands} = $commands;
+ ${*$obj}{SiteCommands} = $site_commands;
+ ${*$obj}{Host} = $host;
+ ${*$obj}{OverridePASV} = $override_pasv;
+
${*$obj}{ftpssl_filehandle} = $FTPS_ERROR if ( $debug == 2 );
$FTPS_ERROR = undef;
@@ -273,7 +286,14 @@
my @address = split( /,/, $3 );
- my $host = join( '.', @address[ 0 .. 3 ] );
+ my $host;
+ if (${*$self}{OverridePASV}) {
+ $host = ${*$self}{Host};
+ _print_LOG ( $self, "--- Overriding PASV mode IP address with: $host\n" );
+ }
+ else {
+ $host = join( '.', @address[ 0 .. 3 ] );
+ }
my $port = $address[4] * 256 + $address[5];
my $socket;
@@ -1365,6 +1385,46 @@
return ( (defined $hlp) ? $hlp : \%help );
}
+ my @commands = @{${*$self}{Commands}};
+ my @site_commands = @{${*$self}{SiteCommands}};
+ my $helpmsg;
+
+ if ($all_cmds && @commands > 0) {
+ _print_LOG ( $self, "--- Overriding HELP command with the following commands: \n" );
+ $helpmsg = "214-The following commands are recognized.\n";
+ foreach (@commands) {
+ $help{$_} = 1 if ($_ !~ m/[*]$/);
+ $helpmsg .= " $_\n";
+ _print_LOG ( $self, "--- Command: " . $_ . "\n" );
+ }
+ $helpmsg .= "214 Help OK.\n";
+ }
+ elsif ($site_cmd && @site_commands > 0) {
+ _print_LOG ( $self, "--- Overriding SITE HELP command with the following commands: \n" );
+ $helpmsg = "214-The following commands are recognized.\n";
+ foreach (@site_commands) {
+ $help{$_} = 1 if ($_ !~ m/[*]$/);
+ $helpmsg .= " $_\n";
+ _print_LOG ( $self, "--- Site Command: " . $_ . "\n" );
+ }
+ $helpmsg .= "214 Help OK.\n";
+ }
+
+ # If we don't find anything, it's a problem. So don't cache if so ...
+ if (scalar (keys %help) > 0) {
+ if ($all_cmds) {
+ # Add the assumed OPTS command required if FEAT is supported!
+ # Even though not all servers support OPTS as is required with FEAT.
+ $help{OPTS} = 1 if ($help{FEAT}); # RFC 2389
+
+ ${*$self}{help_cmds_found} = \%help;
+ ${*$self}{help_cmds_msg} = $helpmsg;
+ } else {
+ ${*$self}{"help_${cmd}_found"} = \%help;
+ ${*$self}{"help_${cmd}_msg"} = $helpmsg;
+ }
+ }
+ else {
$self->command ("HELP", @_);
# Now lets see if we need to parse the result to get a hash of the
@@ -1411,6 +1471,7 @@
} else {
${*$self}{"help_${cmd}_msg"} = $self->last_message ();
}
+ }
return (\%help);
}