Skip to content

Commit

Permalink
Merge branch '3.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
terrafrost committed Jan 16, 2025
2 parents c876c25 + 087fe31 commit 41003ab
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 22 deletions.
67 changes: 45 additions & 22 deletions phpseclib/Net/SSH2.php
Original file line number Diff line number Diff line change
Expand Up @@ -2674,8 +2674,8 @@ public function getOpenChannelCount()
*/
protected function open_channel(int $channel, bool $skip_extended = false): bool
{
if (isset($this->channel_status[$channel]) && $this->channel_status[$channel] != MessageType::CHANNEL_CLOSE) {
throw new RuntimeException('Please close the channel (' . $channel . ') before trying to open it again');
if (isset($this->channel_status[$channel])) {
throw new \RuntimeException('Please close the channel (' . $channel . ') before trying to open it again');
}

$this->channelCount++;
Expand Down Expand Up @@ -3029,6 +3029,27 @@ public function reset(?int $channel = null): void
}
}

/**
* Send EOF on a channel
*
* Sends an EOF to the stream; this is typically used to close standard
* input, while keeping output and error alive.
*
* @param int|null $channel Channel id returned by self::getInteractiveChannelId()
* @return void
*/
public function sendEOF(?int $channel = null): void
{
if ($channel === null) {
$channel = $this->get_interactive_channel();
}

$excludeStatuses = [MessageType::CHANNEL_EOF, MessageType::CHANNEL_CLOSE];
if (isset($this->channel_status[$channel]) && !in_array($this->channel_status[$channel], $excludeStatuses)) {
$this->send_binary_packet(pack('CN', MessageType::CHANNEL_EOF, $this->server_channels[$channel]));
}
}

/**
* Is timeout?
*
Expand Down Expand Up @@ -3539,7 +3560,7 @@ private function handleDisconnect($payload)
Strings::shift($payload, 1);
[$reason_code, $message] = Strings::unpackSSH2('Ns', $payload);
$this->errors[] = 'SSH_MSG_DISCONNECT: ' . self::$disconnect_reasons[$reason_code] . "\r\n$message";
$this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST);
$this->disconnect_helper(DisconnectReason::CONNECTION_LOST);
throw new ConnectionClosedException('Connection closed by server');
}

Expand Down Expand Up @@ -3570,7 +3591,7 @@ private function filter(string $payload): string
// this is here for server key re-exchanges after the initial key exchange
if ($this->session_id !== false) {
if (!$this->key_exchange($payload)) {
$this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
$this->disconnect_helper(DisconnectReason::KEY_EXCHANGE_FAILED);
throw new ConnectionClosedException('Key exchange failed');
}
$payload = $this->get_binary_packet();
Expand Down Expand Up @@ -3756,7 +3777,8 @@ public function isPTYEnabled(): bool
protected function get_channel_packet(int $client_channel, bool $skip_extended = false)
{
if (!empty($this->channel_buffers[$client_channel])) {
switch ($this->channel_status[$client_channel]) {
// in phpseclib 4.0 this should be changed to $this->channel_status[$client_channel] ?? null
switch (isset($this->channel_status[$client_channel]) ?? null) {
case MessageType::CHANNEL_REQUEST:
foreach ($this->channel_buffers[$client_channel] as $i => $packet) {
switch (ord($packet[0])) {
Expand Down Expand Up @@ -3824,7 +3846,7 @@ protected function get_channel_packet(int $client_channel, bool $skip_extended =

continue 2;
case MessageType::CHANNEL_REQUEST:
if ($this->channel_status[$channel] == MessageType::CHANNEL_CLOSE) {
if (!isset($this->channel_status[$channel])) {
continue 2;
}
[$value] = Strings::unpackSSH2('s', $response);
Expand All @@ -3842,10 +3864,14 @@ protected function get_channel_packet(int $client_channel, bool $skip_extended =
$this->errors[count($this->errors) - 1] .= "\r\n$error_message";
}

$this->send_binary_packet(pack('CN', MessageType::CHANNEL_EOF, $this->server_channels[$client_channel]));
$this->send_binary_packet(pack('CN', MessageType::CHANNEL_CLOSE, $this->server_channels[$channel]));
if (isset($this->channel_status[$channel]) && $this->channel_status[$channel] != MessageType::CHANNEL_CLOSE) {
if ($this->channel_status[$channel] != MessageType::CHANNEL_EOF) {
$this->send_binary_packet(pack('CN', MessageType::CHANNEL_EOF, $this->server_channels[$channel]));
}
$this->send_binary_packet(pack('CN', MessageType::CHANNEL_CLOSE, $this->server_channels[$channel]));

$this->channel_status[$channel] = MessageType::CHANNEL_EOF;
$this->channel_status[$channel] = MessageType::CHANNEL_CLOSE;
}

continue 3;
case 'exit-status':
Expand Down Expand Up @@ -3946,11 +3972,11 @@ protected function get_channel_packet(int $client_channel, bool $skip_extended =

$this->close_channel_bitmap($channel);

if ($this->channel_status[$channel] != MessageType::CHANNEL_EOF) {
if ($this->channel_status[$channel] != MessageType::CHANNEL_CLOSE) {
$this->send_binary_packet(pack('CN', MessageType::CHANNEL_CLOSE, $this->server_channels[$channel]));
}

$this->channel_status[$channel] = MessageType::CHANNEL_CLOSE;
unset($this->channel_status[$channel]);
$this->channelCount--;

if ($client_channel == $channel) {
Expand All @@ -3976,7 +4002,7 @@ protected function get_channel_packet(int $client_channel, bool $skip_extended =
protected function send_binary_packet(string $data, ?string $logged = null): void
{
if (!is_resource($this->fsock) || feof($this->fsock)) {
$this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST);
$this->disconnect_helper(DisconnectReason::CONNECTION_LOST);
throw new ConnectionClosedException('Connection closed prematurely');
}

Expand Down Expand Up @@ -4113,7 +4139,7 @@ protected function send_binary_packet(string $data, ?string $logged = null): voi
$this->last_packet = microtime(true);

if (strlen($packet) != $sent) {
$this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
$this->disconnect_helper(DisconnectReason::BY_APPLICATION);
$message = $sent === false ?
'Unable to write ' . strlen($packet) . ' bytes' :
"Only $sent of " . strlen($packet) . " bytes were sent";
Expand Down Expand Up @@ -4298,27 +4324,24 @@ protected function send_channel_packet(int $client_channel, string $data): void
* and for SFTP channels are presumably closed when the client disconnects. This functions is intended
* for SCP more than anything.
*/
private function close_channel(int $client_channel, bool $want_reply = false): void
private function close_channel(int $client_channel): void
{
// see http://tools.ietf.org/html/rfc4254#section-5.3

$this->send_binary_packet(pack('CN', MessageType::CHANNEL_EOF, $this->server_channels[$client_channel]));

if (!$want_reply) {
$this->send_binary_packet(pack('CN', MessageType::CHANNEL_CLOSE, $this->server_channels[$client_channel]));
if ($this->channel_status[$client_channel] != MessageType::CHANNEL_EOF) {
$this->send_binary_packet(pack('CN', MessageType::CHANNEL_EOF, $this->server_channels[$client_channel]));
}
$this->send_binary_packet(pack('CN', MessageType::CHANNEL_CLOSE, $this->server_channels[$client_channel]));

$this->channel_status[$client_channel] = MessageType::CHANNEL_CLOSE;

$this->channelCount--;

$this->curTimeout = 5;

while (!is_bool($this->get_channel_packet($client_channel))) {
}

if ($want_reply) {
$this->send_binary_packet(pack('CN', MessageType::CHANNEL_CLOSE, $this->server_channels[$client_channel]));
}
unset($this->channel_status[$client_channel]);

$this->close_channel_bitmap($client_channel);
}
Expand Down
13 changes: 13 additions & 0 deletions tests/Functional/Net/SSH2Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,19 @@ public static function getCryptoAlgorithms()
return $tests;
}

/**
* @group github2062
*/
public function testSendEOF()
{
$ssh = $this->getSSH2Login();

$ssh->write("ls -latr; exit\n");
$ssh->read();
$ssh->sendEOF();
$ssh->exec('ls -latr');
}

/**
* @dataProvider getCryptoAlgorithms
* @param string $type
Expand Down

0 comments on commit 41003ab

Please sign in to comment.