Subject: | fix for SSH key-exchange version string reading in Net::SSH::Perl |
Dear Sirs,
I sent this fix to Dave Rolsky on 2004-10-26. It was ignored. Our customers still write to us about this problem: it exists in all Net::SSH::Perl version. It is real.
My original email follows.
----------
I have a fix for a problem that Net::SSH::Perl has on the Cygwin platform.
The problem can be observed when using Net::SSH::Perl against our own SSH server, WinSSHD, which sends KEXINIT immediately after the version string.
In the latest version I obtained through CPAN (1.25), Net::SSH::Perl would hang on connecting - it would not notice that KEXINIT was already received and would wait indefinitely for it to "arrive".
The problem is that the _exchange_identification method in Perl.pm reads the version string with:
my $remote_id = <$sock>;
which is incompatible with later use of sysread on the socket - see perlfunc:
sysread FILEHANDLE,SCALAR,LENGTH
Attempts to read LENGTH bytes of data into variable SCALAR from
the specified FILEHANDLE, using the system call read(2). It
bypasses buffered IO, so mixing this with other kinds of reads,
"print", "write", "seek", "tell", or "eof" can cause confusion
because the perlio or stdio layers usually buffers data.
Additionally, the simplistic <$sock> ignores the fact that in the SSH2 protocol the server may send additional lines before the actual version string, which MUST be handled by the client according to the spec (draft-ietf-secsh-transport-18.txt).
I fixed this by replacing <$sock> in sub _exchange_identification with
my $remote_id = $ssh->_read_version;
and by implementing the _read_version and _read_version_line methods in Perl.pm (see ReadVersionMethods.pl, attached).
I hope this fix can be included in the next release, possibly after it has been additionally checked and augmented as necessary. I am fairly new to Perl so my code may not be ideal, but it's better than the existing call to "<$sock>", which fails to work.
I will appreciate your comments!
Thank you and best regards,
denis
# These two methods need to be added to Perl.pm in Net::SSH.
#
# The following line of code in Perl.pm:
#
# sub _exchange_identification {
# my $ssh = shift;
# my $sock = $ssh->{session}{sock};
# my $remote_id = <$sock>; # <<<<-----
#
# then needs to be replaced with:
#
# my $remote_id = $ssh->_read_version;
#
sub _read_version_line {
my $ssh = shift;
my $sock = $ssh->{session}{sock};
my $line;
while (1) {
my $s = IO::Select->new($sock);
my @ready = $s->can_read;
my $buf;
my $len = sysread($sock, $buf, 1);
croak "Connection closed by remote host." if $len == 0;
if (!defined($len)) {
next if $! == EAGAIN || $! == EWOULDBLOCK;
croak "Read from socket failed: $!";
}
$line .= $buf;
croak "Version line too long: $line" if (substr($line, 0, 4) eq "SSH-" && length($line) > 255);
croak "Pre-version line too long: $line" if (length($line) > 4*1024);
return $line if ($buf eq "\n");
}
}
sub _read_version {
my $ssh = shift;
my $line;
do { $line = $ssh->_read_version_line; } while (substr($line, 0, 4) ne "SSH-");
$ssh->debug("Remote version string: $line");
return $line;
}