Subject: | Issues with PTY support in SSH2 |
Currently, the use_pty support for SSH2 is disabled. I tried enabling it
but found that programs that read from STDIN often hang attempting to
read after consuming all input. I've put some work into fixing that.
My investigation indicates that the issue is due to the way that PTYs
are handled on the server side. The child process on the server side
never sees an EOF condition. When there is no PTY, the SSH server closes
the STDIN file descriptor, which causes EOF to return. Since STDIN and
STDOUT share a file descriptor when a PTY is allocated, the file handle
cannot be closed and the read blocks.
I believe that appending an 0x04 or two is actually the right thing to
do, based on _Advanced UNIX Programming_, section 4.2 (page 204):
=================================
The user can end a line in one of two ways. Most often, a newline
character is the terminator. The return key is pressed, but the device
driver translates the return (octal 15) to a newline (octal 12).
Alternatively, the user can generate an end-offile (EOF) character by
pressing Ctrl-d. In this case the line is made available to read as is,
without a newline terminator. One special case is important: If the user
generates an EOF at the start of the line, read will return with a zero
count, since the line that the EOF terminated is empty. This looks like
an end-of-file, and that’s why Ctrl-d can be thought of as an
end-of-file "character"
=================================
My testing indicates this is a workable solution. In addition, although
it *should* be the default behavior, I have set the "terminal modes"
string in the pty_req channel request to set VEOF (the character to be
treated as EOF) to 0x04, and provided a method to configure this value.
The changes I propose are in the attached diff. The documentation, added
to the POD in Perl.pm, is as follows:
=================================
* terminal_mode_string
Specify the POSIX terminal mode string to send when use_pty is set. By
default the only mode set is the VEOF character to 0x04 (opcode 5, value
0x00000004). See RFC 4254 section 8 for complete details on this value.
* no_append_veof
(SSH-2 only) Set this to 1 if you specified use_pty and do not want
Ctrl-D (0x04) appended twice to the end of your input string. On most
systems, these bytes cause the terminal driver to return "EOF" when
standard input is read. Without them, many programs that read from
standard input will hang after consuming all the data on STDIN.
No other modifications are made to input data. If your data contains
0x04 bytes, you may need to escape them.
Set this to 0 if you have raw terminal data to specify on standard
input, and you have terminated it correctly.
=================================
Subject: | net-ssh-perl.patch |
diff -rc ../Net-SSH-Perl-1.34-orig/lib/Net/SSH/Perl/SSH2.pm ./lib/Net/SSH/Perl/SSH2.pm
*** ../Net-SSH-Perl-1.34-orig/lib/Net/SSH/Perl/SSH2.pm 2009-01-25 17:50:38.000000000 -0800
--- ./lib/Net/SSH/Perl/SSH2.pm 2011-01-05 18:18:52.162760000 -0800
***************
*** 135,144 ****
$channel->register_handler(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, sub {
my($channel, $packet) = @_;
- $channel->{ssh}->debug("Sending command: $cmd");
## Experimental pty support:
! if (0 and $ssh->{config}->get('use_pty')) {
$ssh->debug("Requesting pty.");
my $packet = $channel->request_start('pty-req', 0);
--- 135,143 ----
$channel->register_handler(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, sub {
my($channel, $packet) = @_;
## Experimental pty support:
! if ($ssh->{config}->get('use_pty')) {
$ssh->debug("Requesting pty.");
my $packet = $channel->request_start('pty-req', 0);
***************
*** 159,174 ****
if (!$foundsize) {
$packet->put_int32(0) for 1..4;
}
! $packet->put_str("");
! $packet->send;
}
- $channel->{ssh}->debug("Sending command: $cmd");
my $r_packet = $channel->request_start("exec", 0);
$r_packet->put_str($cmd);
$r_packet->send;
if (defined $stdin) {
$channel->send_data($stdin);
$channel->drain_outgoing;
--- 158,195 ----
if (!$foundsize) {
$packet->put_int32(0) for 1..4;
}
!
! # Array used to build Pseudo-tty terminal modes; fat commas separate opcodes from values for clarity.
!
! my $terminal_mode_string;
! if(!defined($ssh->{config}->get('terminal_mode_string'))) {
! my @terminal_modes = (
! 5 => 0,0,0,4, # VEOF => 0x04 (^d)
! 0 # string must end with a 0 opcode
! );
! for my $char (@terminal_modes) {
! $terminal_mode_string .= chr($char);
! }
! }
! else {
! $terminal_mode_string = $ssh->{config}->get('terminal_mode_string');
! }
! $packet->put_str($terminal_mode_string);
! $packet->send;
}
my $r_packet = $channel->request_start("exec", 0);
$r_packet->put_str($cmd);
$r_packet->send;
if (defined $stdin) {
+ if($ssh->{config}->get('use_pty') && !$ssh->{config}->get('no_append_veof')) {
+ my $append_string = $ssh->{config}->get('stdin_append');
+ if(!defined($append_string)) {
+ $append_string = chr(4) . chr(4);
+ }
+ $stdin .= $append_string;
+ }
$channel->send_data($stdin);
$channel->drain_outgoing;
diff -rc ../Net-SSH-Perl-1.34-orig/lib/Net/SSH/Perl.pm ./lib/Net/SSH/Perl.pm
*** ../Net-SSH-Perl-1.34-orig/lib/Net/SSH/Perl.pm 2009-02-01 17:18:27.000000000 -0800
--- ./lib/Net/SSH/Perl.pm 2011-01-05 18:18:31.806201000 -0800
***************
*** 9,19 ****
use Net::SSH::Perl::Constants qw( :protocol :compat :hosts );
use Net::SSH::Perl::Cipher;
use Net::SSH::Perl::Util qw( :hosts _read_yes_or_no );
use vars qw( $VERSION $CONFIG $HOSTNAME );
$CONFIG = {};
! use Socket;
use IO::Socket;
use Fcntl;
use Symbol;
--- 9,20 ----
use Net::SSH::Perl::Constants qw( :protocol :compat :hosts );
use Net::SSH::Perl::Cipher;
use Net::SSH::Perl::Util qw( :hosts _read_yes_or_no );
+ use Data::Dumper;
use vars qw( $VERSION $CONFIG $HOSTNAME );
$CONFIG = {};
! use Socket qw(IPPROTO_TCP TCP_NODELAY);
use IO::Socket;
use Fcntl;
use Symbol;
***************
*** 232,237 ****
--- 233,239 ----
for(; $p != $end; $p += $delta) {
socket($sock, AF_INET, SOCK_STREAM, getprotobyname('tcp') || 0) ||
croak "Net::SSH: Can't create socket: $!";
+ setsockopt($sock, IPPROTO_TCP, TCP_NODELAY, 1);
last if not $p or bind($sock, sockaddr_in($p,$addr));
if ($! =~ /Address already in use/i) {
close($sock) or warn qq{Could not close socket: $!\n};
***************
*** 346,351 ****
--- 348,355 ----
sub check_host_key {
my $ssh = shift;
my($key, $host, $u_hostfile, $s_hostfile) = @_;
+ my $strict_host_key_checking = $ssh->{config}->get('strict_host_key_checking');
+ $strict_host_key_checking ||= 'no';
$host ||= $ssh->{host};
$u_hostfile ||= $ssh->{config}->get('user_known_hosts');
$s_hostfile ||= $ssh->{config}->get('global_known_hosts');
***************
*** 359,379 ****
$ssh->debug("Host '$host' is known and matches the host key.");
}
elsif ($status == HOST_NEW) {
! if ($ssh->{config}->get('interactive')) {
my $prompt =
qq(The authenticity of host '$host' can't be established.
Key fingerprint is @{[ $key->fingerprint ]}.
Are you sure you want to continue connecting (yes/no)?);
unless (_read_yes_or_no($prompt, "yes")) {
croak "Aborted by user!";
! }
! }
! $ssh->debug("Permanently added '$host' to the list of known hosts.");
! _add_host_to_hostfile($host, $u_hostfile, $key);
! }
else {
! croak "Host key for '$host' has changed!";
! }
}
1;
--- 363,385 ----
$ssh->debug("Host '$host' is known and matches the host key.");
}
elsif ($status == HOST_NEW) {
! if ($strict_host_key_checking =~ /(ask|yes)/) {
! if (!$ssh->{config}->get('interactive')) {
! croak "Host key verification failed.";
! }
my $prompt =
qq(The authenticity of host '$host' can't be established.
Key fingerprint is @{[ $key->fingerprint ]}.
Are you sure you want to continue connecting (yes/no)?);
unless (_read_yes_or_no($prompt, "yes")) {
croak "Aborted by user!";
! }
! }
! $ssh->debug("Permanently added '$host' to the list of known hosts.");
! _add_host_to_hostfile($host, $u_hostfile, $key); }
else {
! croak "Host key for '$host' has changed!";
! }
}
1;
***************
*** 587,592 ****
--- 593,619 ----
The default is 1 if you're starting up a shell, and 0 otherwise.
+ =item * terminal_mode_string
+
+ Specify the POSIX terminal mode string to send when use_pty is
+ set. By default the only mode set is the VEOF character to 0x04
+ (opcode 5, value 0x00000004). See RFC 4254 section 8 for complete
+ details on this value.
+
+ =item * no_append_veof
+
+ (SSH-2 only) Set this to 1 if you specified use_pty and do not want
+ Ctrl-D (0x04) appended twice to the end of your input string. On most
+ systems, these bytes cause the terminal driver to return "EOF" when
+ standard input is read. Without them, many programs that read from
+ standard input will hang after consuming all the data on STDIN.
+
+ No other modifications are made to input data. If your data contains
+ 0x04 bytes, you may need to escape them.
+
+ Set this to 0 if you have raw terminal data to specify on standard
+ input, and you have terminated it correctly.
+
=item * options
Used to specify additional options to the configuration settings;