diff --git a/phpseclib/Net/SSH2.php b/phpseclib/Net/SSH2.php index 21b0cda9..f7a099a8 100644 --- a/phpseclib/Net/SSH2.php +++ b/phpseclib/Net/SSH2.php @@ -715,6 +715,14 @@ class SSH2 */ protected $curTimeout; + /** + * Keep Alive Interval + * + * @see self::setKeepAlive() + * @access private + */ + var $keepAlive; + /** * Real-time log file pointer * @@ -2537,6 +2545,19 @@ class SSH2 $this->timeout = $this->curTimeout = $timeout; } + /** + * Set Keep Alive + * + * Sends an SSH2_MSG_IGNORE message every x seconds, if x is a positive non-zero number. + * + * @param mixed $timeout + * @access public + */ + function setKeepAlive($interval) + { + $this->keepAlive = $interval; + } + /** * Get the output from stdError * @@ -3167,6 +3188,54 @@ class SSH2 */ private function get_binary_packet($skip_channel_filter = false) { + if ($skip_channel_filter) { + $read = [$this->fsock]; + $write = $except = null; + + if ($this->curTimeout <= 0) { + if ($this->keepAlive <= 0) { + stream_select($read, $write, $except, null); + } else { + if (!stream_select($read, $write, $except, $this->keepAlive)) { + $this->send_binary_packet(pack('CN', NET_SSH2_MSG_IGNORE, 0)); + return $this->get_binary_packet(true); + } + } + } else { + if ($this->curTimeout < 0) { + $this->is_timeout = true; + return true; + } + + $read = [$this->fsock]; + $write = $except = null; + + $start = microtime(true); + + if ($this->keepAlive > 0 && $this->keepAlive < $this->curTimeout) { + if (!stream_select($read, $write, $except, $this->keepAlive)) { + $this->send_binary_packet(pack('CN', NET_SSH2_MSG_IGNORE, 0)); + $elapsed = microtime(true) - $start; + $this->curTimeout-= $elapsed; + return $this->get_binary_packet(true); + } + $elapsed = microtime(true) - $start; + $this->curTimeout-= $elapsed; + } + + $sec = floor($this->curTimeout); + $usec = 1000000 * ($this->curTimeout - $sec); + + // on windows this returns a "Warning: Invalid CRT parameters detected" error + if (!stream_select($read, $write, $except, $sec, $usec)) { + $this->is_timeout = true; + return true; + } + $elapsed = microtime(true) - $start; + $this->curTimeout-= $elapsed; + } + } + if (!is_resource($this->fsock) || feof($this->fsock)) { $this->bitmap = 0; throw new ConnectionClosedException('Connection closed prematurely'); @@ -3423,9 +3492,19 @@ class SSH2 // only called when we've already logged in if (($this->bitmap & self::MASK_CONNECTED) && $this->isAuthenticated()) { switch (ord($payload[0])) { + case NET_SSH2_MSG_CHANNEL_REQUEST: + if (strlen($payload) == 31) { + extract(unpack('cpacket_type/Nchannel/Nlength', $payload)); + if (substr($payload, 9, $length) == 'keepalive@openssh.com' && isset($this->server_channels[$channel])) { + if (ord(substr($payload, 9 + $length))) { // want reply + $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_SUCCESS, $this->server_channels[$channel])); + } + $payload = $this->get_binary_packet($skip_channel_filter); + } + } + break; case NET_SSH2_MSG_CHANNEL_DATA: case NET_SSH2_MSG_CHANNEL_EXTENDED_DATA: - case NET_SSH2_MSG_CHANNEL_REQUEST: case NET_SSH2_MSG_CHANNEL_CLOSE: case NET_SSH2_MSG_CHANNEL_EOF: if (!$skip_channel_filter && !empty($this->server_channels)) { @@ -3609,35 +3688,13 @@ class SSH2 $response = $this->binary_packet_buffer; $this->binary_packet_buffer = false; } else { - $read = [$this->fsock]; - $write = $except = null; - - if (!$this->curTimeout) { - stream_select($read, $write, $except, null); - } else { - if ($this->curTimeout < 0) { - $this->is_timeout = true; - return true; - } - - $read = [$this->fsock]; - $write = $except = null; - - $start = microtime(true); - $sec = floor($this->curTimeout); - $usec = 1000000 * ($this->curTimeout - $sec); - if (!stream_select($read, $write, $except, $sec, $usec)) { - $this->is_timeout = true; - if ($client_channel == self::CHANNEL_EXEC && !$this->request_pty) { - $this->close_channel($client_channel); - } - return true; - } - $elapsed = microtime(true) - $start; - $this->curTimeout-= $elapsed; - } - $response = $this->get_binary_packet(true); + if ($response === true && $this->is_timeout) { + if ($client_channel == self::CHANNEL_EXEC && !$this->request_pty) { + $this->close_channel($client_channel); + } + return true; + } if ($response === false) { $this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST); throw new ConnectionClosedException('Connection closed by server');