diff --git a/phpseclib/Net/SSH2.php b/phpseclib/Net/SSH2.php index 42f1f4e4..0dd3a2ee 100644 --- a/phpseclib/Net/SSH2.php +++ b/phpseclib/Net/SSH2.php @@ -2752,9 +2752,9 @@ class SSH2 return false; } - if ($this->isPTYOpen()) { - 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.'); - } + //if ($this->isPTYOpen()) { + // 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.'); + //} $this->openChannel(self::CHANNEL_EXEC); @@ -2836,6 +2836,16 @@ class SSH2 } } + /** + * How many channels are currently open? + * + * @return int + */ + public function getOpenChannelCount() + { + return $this->channelCount; + } + /** * Opens a channel * @@ -2845,6 +2855,10 @@ class SSH2 */ protected function openChannel($channel, $skip_extended = false) { + if (isset($this->channel_status[$channel]) && $this->channel_status[$channel] != NET_SSH2_MSG_CHANNEL_CLOSE) { + throw new \RuntimeException('Please close the channel (' . $channel . ') before trying to open it again'); + } + $this->channelCount++; if ($this->channelCount > 1 && $this->errorOnMultipleChannels) { @@ -2892,10 +2906,6 @@ class SSH2 */ public function openShell() { - if ($this->isShellOpen()) { - return false; - } - if (!$this->isAuthenticated()) { throw new InsufficientSetupException('Operation disallowed prior to login()'); } @@ -3060,7 +3070,7 @@ class SSH2 $channel = $this->get_interactive_channel(); } - if (!$this->isInteractiveChannelOpen($channel)) { + if (!$this->isInteractiveChannelOpen($channel) && empty($this->channel_buffers[$channel])) { if ($channel != self::CHANNEL_SHELL) { throw new InsufficientSetupException('Data is not available on channel'); } elseif (!$this->openShell()) { @@ -4111,6 +4121,8 @@ class SSH2 } $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_CLOSE; + $this->channelCount--; + if ($client_channel == $channel) { return true; } @@ -4442,6 +4454,7 @@ class SSH2 } $this->channel_status[$client_channel] = NET_SSH2_MSG_CHANNEL_CLOSE; + $this->channelCount--; $this->curTimeout = 5; @@ -4913,6 +4926,14 @@ class SSH2 ]; } + /** + * Force multiple channels (even if phpseclib has decided to disable them) + */ + public function forceMultipleChannels() + { + $this->errorOnMultipleChannels = false; + } + /** * Allows you to set the terminal * diff --git a/tests/Functional/Net/SSH2Test.php b/tests/Functional/Net/SSH2Test.php index 83d35fc3..55df076f 100644 --- a/tests/Functional/Net/SSH2Test.php +++ b/tests/Functional/Net/SSH2Test.php @@ -416,7 +416,7 @@ class SSH2Test extends PhpseclibFunctionalTestCase public function testMultipleExecPty() { $this->expectException(\RuntimeException::class); - $this->expectExceptionMessage('If you want to run multiple exec()\'s you will need to disable (and re-enable if appropriate) a PTY for each one.'); + $this->expectExceptionMessage('Please close the channel (1) before trying to open it again'); $ssh = $this->getSSH2Login(); diff --git a/tests/Unit/Net/SSH2UnitTest.php b/tests/Unit/Net/SSH2UnitTest.php index c6b8fa15..42497c06 100644 --- a/tests/Unit/Net/SSH2UnitTest.php +++ b/tests/Unit/Net/SSH2UnitTest.php @@ -202,12 +202,11 @@ class SSH2UnitTest extends PhpseclibTestCase { $ssh = $this->getMockBuilder(SSH2::class) ->disableOriginalConstructor() - ->setMethods(['__destruct', 'isShellOpen']) + ->setMethods(['__destruct']) ->getMock(); - $ssh->expects($this->once()) - ->method('isShellOpen') - ->willReturn(true); + $this->expectException(InsufficientSetupException::class); + $this->expectExceptionMessage('Operation disallowed prior to login()'); $this->assertFalse($ssh->openShell()); }