diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index a2c5ffd3..0c0bac49 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -351,20 +351,7 @@ class SFTP extends SSH2 { $this->window_size_server_to_client[self::CHANNEL] = $this->window_size; - $packet = Strings::packSSH2( - 'CsN3', - SSH2MessageType::CHANNEL_OPEN, - 'session', - self::CHANNEL, - $this->window_size, - 0x4000 - ); - - $this->send_binary_packet($packet); - - $this->channel_status[self::CHANNEL] = SSH2MessageType::CHANNEL_OPEN; - - $response = $this->get_channel_packet(self::CHANNEL, true); + $response = $this->openChannel(self::CHANNEL, true); if ($response === true && $this->isTimeout()) { return false; } diff --git a/phpseclib/Net/SSH2.php b/phpseclib/Net/SSH2.php index 30ede297..09d8c300 100644 --- a/phpseclib/Net/SSH2.php +++ b/phpseclib/Net/SSH2.php @@ -922,6 +922,21 @@ class SSH2 */ private bool $smartMFA = true; + /** + * How many channels are currently opened + * + * @var int + */ + private $channelCount = 0; + + /** + * Does the server support multiple channels? If not then error out + * when multiple channels are attempted to be opened + * + * @var bool + */ + private $errorOnMultipleChannels; + /** * Default Constructor. * @@ -1123,6 +1138,18 @@ class SSH2 throw new UnableToConnectException("Cannot connect to SSH $matches[3] servers"); } + // Ubuntu's OpenSSH from 5.8 to 6.9 didn't work with multiple channels. see + // https://bugs.launchpad.net/ubuntu/+source/openssh/+bug/1334916 for more info. + // https://lists.ubuntu.com/archives/oneiric-changes/2011-July/005772.html discusses + // when consolekit was incorporated. + // https://marc.info/?l=openssh-unix-dev&m=163409903417589&w=2 discusses some of the + // issues with how Ubuntu incorporated consolekit + $pattern = '#^SSH-2\.0-OpenSSH_([\d.]+)[^ ]* Ubuntu-.*$#'; + $match = preg_match($pattern, $this->server_identifier, $matches); + $match = $match && version_compare('5.8', $matches[1], '<='); + $match = $match && version_compare('6.9', $matches[1], '>='); + $this->errorOnMultipleChannels = $match; + if (!$this->send_id_string_first) { fwrite($this->fsock, $this->identifier . "\r\n"); } @@ -2432,28 +2459,7 @@ class SSH2 throw new RuntimeException('If you want to run multiple exec()\'s you will need to disable (and re-enable if appropriate) a PTY for each one.'); } - // RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to - // be adjusted". 0x7FFFFFFF is, at 2GB, the max size. technically, it should probably be decremented, but, - // honestly, if you're transferring more than 2GB, you probably shouldn't be using phpseclib, anyway. - // see http://tools.ietf.org/html/rfc4254#section-5.2 for more info - $this->window_size_server_to_client[self::CHANNEL_EXEC] = $this->window_size; - // 0x8000 is the maximum max packet size, per http://tools.ietf.org/html/rfc4253#section-6.1, although since PuTTy - // uses 0x4000, that's what will be used here, as well. - $packet_size = 0x4000; - - $packet = Strings::packSSH2( - 'CsN3', - MessageType::CHANNEL_OPEN, - 'session', - self::CHANNEL_EXEC, - $this->window_size_server_to_client[self::CHANNEL_EXEC], - $packet_size - ); - $this->send_binary_packet($packet); - - $this->channel_status[self::CHANNEL_EXEC] = MessageType::CHANNEL_OPEN; - - $this->get_channel_packet(self::CHANNEL_EXEC); + $this->openChannel(self::CHANNEL_EXEC); if ($this->request_pty === true) { $terminal_modes = pack('C', TerminalMode::TTY_OP_END); @@ -2533,6 +2539,42 @@ class SSH2 } } + /** + * Opens a channel + */ + protected function openChannel(string $channel, bool $skip_extended = false): bool + { + $this->channelCount++; + + if ($this->channelCount > 1 && $this->errorOnMultipleChannels) { + throw new \RuntimeException("Ubuntu's OpenSSH from 5.8 to 6.9 doesn't work with multiple channels"); + } + + // RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to + // be adjusted". 0x7FFFFFFF is, at 2GB, the max size. technically, it should probably be decremented, but, + // honestly, if you're transferring more than 2GB, you probably shouldn't be using phpseclib, anyway. + // see http://tools.ietf.org/html/rfc4254#section-5.2 for more info + $this->window_size_server_to_client[$channel] = $this->window_size; + // 0x8000 is the maximum max packet size, per http://tools.ietf.org/html/rfc4253#section-6.1, although since PuTTy + // uses 0x4000, that's what will be used here, as well. + $packet_size = 0x4000; + + $packet = Strings::packSSH2( + 'CsN3', + SSH2MessageType::CHANNEL_OPEN, + 'session', + $channel, + $this->window_size_server_to_client[$channel], + $packet_size + ); + + $this->send_binary_packet($packet); + + $this->channel_status[$channel] = SSH2MessageType::CHANNEL_OPEN; + + return $this->get_channel_packet($channel, $skip_extended); + } + /** * Creates an interactive shell * @@ -2556,23 +2598,7 @@ class SSH2 throw new InsufficientSetupException('Operation disallowed prior to login()'); } - $this->window_size_server_to_client[self::CHANNEL_SHELL] = $this->window_size; - $packet_size = 0x4000; - - $packet = Strings::packSSH2( - 'CsN3', - MessageType::CHANNEL_OPEN, - 'session', - self::CHANNEL_SHELL, - $this->window_size_server_to_client[self::CHANNEL_SHELL], - $packet_size - ); - - $this->send_binary_packet($packet); - - $this->channel_status[self::CHANNEL_SHELL] = MessageType::CHANNEL_OPEN; - - $this->get_channel_packet(self::CHANNEL_SHELL); + $this->openChannel(self::CHANNEL_SHELL); $terminal_modes = pack('C', TerminalMode::TTY_OP_END); $packet = Strings::packSSH2( @@ -2801,22 +2827,7 @@ class SSH2 */ public function startSubsystem(string $subsystem): bool { - $this->window_size_server_to_client[self::CHANNEL_SUBSYSTEM] = $this->window_size; - - $packet = Strings::packSSH2( - 'CsN3', - MessageType::CHANNEL_OPEN, - 'session', - self::CHANNEL_SUBSYSTEM, - $this->window_size, - 0x4000 - ); - - $this->send_binary_packet($packet); - - $this->channel_status[self::CHANNEL_SUBSYSTEM] = MessageType::CHANNEL_OPEN; - - $this->get_channel_packet(self::CHANNEL_SUBSYSTEM); + $this->openChannel(self::CHANNEL_SUBSYSTEM); $packet = Strings::packSSH2( 'CNsCs', @@ -2977,23 +2988,8 @@ class SSH2 return false; } - $this->window_size_server_to_client[self::CHANNEL_KEEP_ALIVE] = $this->window_size; - $packet_size = 0x4000; - $packet = Strings::packSSH2( - 'CsN3', - MessageType::CHANNEL_OPEN, - 'session', - self::CHANNEL_KEEP_ALIVE, - $this->window_size_server_to_client[self::CHANNEL_KEEP_ALIVE], - $packet_size - ); - try { - $this->send_binary_packet($packet); - - $this->channel_status[self::CHANNEL_KEEP_ALIVE] = MessageType::CHANNEL_OPEN; - - $response = $this->get_channel_packet(self::CHANNEL_KEEP_ALIVE); + $this->openChannel(self::CHANNEL_KEEP_ALIVE); } catch (\RuntimeException $e) { return $this->reconnect(); }