SFTP: reopen channel on channel closure

This commit is contained in:
terrafrost 2021-05-09 01:07:09 -05:00
parent bd0e217793
commit 05828a8759
2 changed files with 85 additions and 107 deletions

View File

@ -284,6 +284,16 @@ class SFTP extends SSH2
*/
private $preserveTime = false;
/**
* Was the last packet due to the channels being closed or not?
*
* @see self::get()
* @see self::get_sftp_packet()
* @var bool
* @access private
*/
private $channel_close = false;
/**
* Default Constructor.
*
@ -442,6 +452,18 @@ class SFTP extends SSH2
return false;
}
return $this->init_sftp_connection();
}
/**
* (Re)initializes the SFTP channel
*
* @throws \UnexpectedValueException on receipt of unexpected packets
* @return bool
* @access private
*/
private function init_sftp_connection()
{
$this->window_size_server_to_client[self::CHANNEL] = $this->window_size;
$packet = Strings::packSSH2(
@ -457,10 +479,7 @@ class SFTP extends SSH2
$this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_OPEN;
$response = $this->get_channel_packet(self::CHANNEL, true);
if ($response === false) {
return false;
}
$this->get_channel_packet(self::CHANNEL, true);
$packet = Strings::packSSH2(
'CNsbs',
@ -501,10 +520,7 @@ class SFTP extends SSH2
}
$this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_DATA;
if (!$this->send_sftp_packet(NET_SFTP_INIT, "\0\0\0\3")) {
return false;
}
$this->send_sftp_packet(NET_SFTP_INIT, "\0\0\0\3");
$response = $this->get_sftp_packet();
if ($this->packet_type != NET_SFTP_VERSION) {
@ -577,7 +593,7 @@ class SFTP extends SSH2
*
* @access public
*/
function disableStatCache()
public function disableStatCache()
{
$this->use_stat_cache = false;
}
@ -679,9 +695,7 @@ class SFTP extends SSH2
if ($this->pwd === false) {
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.9
if (!$this->send_sftp_packet(NET_SFTP_REALPATH, Strings::packSSH2('s', $path))) {
return false;
}
$this->send_sftp_packet(NET_SFTP_REALPATH, Strings::packSSH2('s', $path));
$response = $this->get_sftp_packet();
switch ($this->packet_type) {
@ -758,9 +772,7 @@ class SFTP extends SSH2
// the file's uid / gid match the currently logged in user's uid / gid but how there's no easy
// way to get those with SFTP
if (!$this->send_sftp_packet(NET_SFTP_OPENDIR, Strings::packSSH2('s', $dir))) {
return false;
}
$this->send_sftp_packet(NET_SFTP_OPENDIR, Strings::packSSH2('s', $dir));
// see \phpseclib3\Net\SFTP::nlist() for a more thorough explanation of the following
$response = $this->get_sftp_packet();
@ -899,9 +911,7 @@ class SFTP extends SSH2
}
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.2
if (!$this->send_sftp_packet(NET_SFTP_OPENDIR, Strings::packSSH2('s', $dir))) {
return false;
}
$this->send_sftp_packet(NET_SFTP_OPENDIR, Strings::packSSH2('s', $dir));
$response = $this->get_sftp_packet();
switch ($this->packet_type) {
@ -927,9 +937,7 @@ class SFTP extends SSH2
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.2
// why multiple SSH_FXP_READDIR packets would be sent when the response to a single one can span arbitrarily many
// SSH_MSG_CHANNEL_DATA messages is not known to me.
if (!$this->send_sftp_packet(NET_SFTP_READDIR, Strings::packSSH2('s', $handle))) {
return false;
}
$this->send_sftp_packet(NET_SFTP_READDIR, Strings::packSSH2('s', $handle));
$response = $this->get_sftp_packet();
switch ($this->packet_type) {
@ -1319,9 +1327,7 @@ class SFTP extends SSH2
{
// SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
$packet = Strings::packSSH2('s', $filename);
if (!$this->send_sftp_packet($type, $packet)) {
return false;
}
$this->send_sftp_packet($type, $packet);
$response = $this->get_sftp_packet();
switch ($this->packet_type) {
@ -1384,9 +1390,7 @@ class SFTP extends SSH2
$flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE | NET_SFTP_OPEN_EXCL;
$attr = pack('N3', NET_SFTP_ATTR_ACCESSTIME, $time, $atime);
$packet = Strings::packSSH2('sN', $filename, $flags) . $attr;
if (!$this->send_sftp_packet(NET_SFTP_OPEN, $packet)) {
return false;
}
$this->send_sftp_packet(NET_SFTP_OPEN, $packet);
$response = $this->get_sftp_packet();
switch ($this->packet_type) {
@ -1475,9 +1479,7 @@ class SFTP extends SSH2
// tell us if the file actually exists.
// incidentally, SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
$packet = pack('Na*', strlen($filename), $filename);
if (!$this->send_sftp_packet(NET_SFTP_STAT, $packet)) {
return false;
}
$this->send_sftp_packet(NET_SFTP_STAT, $packet);
$response = $this->get_sftp_packet();
switch ($this->packet_type) {
@ -1525,9 +1527,7 @@ class SFTP extends SSH2
// SFTPv4+ has an additional byte field - type - that would need to be sent, as well. setting it to
// SSH_FILEXFER_TYPE_UNKNOWN might work. if not, we'd have to do an SSH_FXP_STAT before doing an SSH_FXP_SETSTAT.
if (!$this->send_sftp_packet(NET_SFTP_SETSTAT, Strings::packSSH2('s', $filename) . $attr)) {
return false;
}
$this->send_sftp_packet(NET_SFTP_SETSTAT, Strings::packSSH2('s', $filename) . $attr);
/*
"Because some systems must use separate system calls to set various attributes, it is possible that a failure
@ -1592,9 +1592,7 @@ class SFTP extends SSH2
return false;
}
} else {
if (!$this->send_sftp_packet(NET_SFTP_SETSTAT, Strings::packSSH2('s', $temp) . $attr)) {
return false;
}
$this->send_sftp_packet(NET_SFTP_SETSTAT, Strings::packSSH2('s', $temp) . $attr);
$i++;
@ -1607,9 +1605,7 @@ class SFTP extends SSH2
}
}
if (!$this->send_sftp_packet(NET_SFTP_SETSTAT, Strings::packSSH2('s', $path) . $attr)) {
return false;
}
$this->send_sftp_packet(NET_SFTP_SETSTAT, Strings::packSSH2('s', $path) . $attr);
$i++;
@ -1639,9 +1635,7 @@ class SFTP extends SSH2
$link = $this->realpath($link);
if (!$this->send_sftp_packet(NET_SFTP_READLINK, Strings::packSSH2('s', $link))) {
return false;
}
$this->send_sftp_packet(NET_SFTP_READLINK, Strings::packSSH2('s', $link));
$response = $this->get_sftp_packet();
switch ($this->packet_type) {
@ -1687,9 +1681,7 @@ class SFTP extends SSH2
$link = $this->realpath($link);
$packet = Strings::packSSH2('ss', $target, $link);
if (!$this->send_sftp_packet(NET_SFTP_SYMLINK, $packet)) {
return false;
}
$this->send_sftp_packet(NET_SFTP_SYMLINK, $packet);
$response = $this->get_sftp_packet();
if ($this->packet_type != NET_SFTP_STATUS) {
@ -1751,9 +1743,7 @@ class SFTP extends SSH2
private function mkdir_helper($dir, $mode)
{
// send SSH_FXP_MKDIR without any attributes (that's what the \0\0\0\0 is doing)
if (!$this->send_sftp_packet(NET_SFTP_MKDIR, Strings::packSSH2('s', $dir) . "\0\0\0\0")) {
return false;
}
$this->send_sftp_packet(NET_SFTP_MKDIR, Strings::packSSH2('s', $dir) . "\0\0\0\0");
$response = $this->get_sftp_packet();
if ($this->packet_type != NET_SFTP_STATUS) {
@ -1793,9 +1783,7 @@ class SFTP extends SSH2
return false;
}
if (!$this->send_sftp_packet(NET_SFTP_RMDIR, Strings::packSSH2('s', $dir))) {
return false;
}
$this->send_sftp_packet(NET_SFTP_RMDIR, Strings::packSSH2('s', $dir));
$response = $this->get_sftp_packet();
if ($this->packet_type != NET_SFTP_STATUS) {
@ -1898,9 +1886,7 @@ class SFTP extends SSH2
$this->remove_from_stat_cache($remote_file);
$packet = Strings::packSSH2('sNN', $remote_file, $flags, 0);
if (!$this->send_sftp_packet(NET_SFTP_OPEN, $packet)) {
return false;
}
$this->send_sftp_packet(NET_SFTP_OPEN, $packet);
$response = $this->get_sftp_packet();
switch ($this->packet_type) {
@ -1982,11 +1968,13 @@ class SFTP extends SSH2
$subtemp = $offset + $sent;
$packet = pack('Na*N3a*', strlen($handle), $handle, $subtemp / 4294967296, $subtemp, strlen($temp), $temp);
if (!$this->send_sftp_packet(NET_SFTP_WRITE, $packet, $j)) {
try {
$this->send_sftp_packet(NET_SFTP_WRITE, $packet, $j);
} catch (\Exception $e) {
if ($mode & self::SOURCE_LOCAL_FILE) {
fclose($fp);
}
return false;
throw $e;
}
$sent+= strlen($temp);
if (is_callable($progressCallback)) {
@ -2066,9 +2054,7 @@ class SFTP extends SSH2
*/
private function close_handle($handle)
{
if (!$this->send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
return false;
}
$this->send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle));
// "The client MUST release all resources associated with the handle regardless of the status."
// -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.3
@ -2117,9 +2103,7 @@ class SFTP extends SSH2
}
$packet = pack('Na*N2', strlen($remote_file), $remote_file, NET_SFTP_OPEN_READ, 0);
if (!$this->send_sftp_packet(NET_SFTP_OPEN, $packet)) {
return false;
}
$this->send_sftp_packet(NET_SFTP_OPEN, $packet);
$response = $this->get_sftp_packet();
switch ($this->packet_type) {
@ -2163,11 +2147,13 @@ class SFTP extends SSH2
$packet_size = $length > 0 ? min($this->max_sftp_packet, $length - $read) : $this->max_sftp_packet;
$packet = Strings::packSSH2('sN3', $handle, $tempoffset / 4294967296, $tempoffset, $packet_size);
if (!$this->send_sftp_packet(NET_SFTP_READ, $packet, $i)) {
try {
$this->send_sftp_packet(NET_SFTP_READ, $packet, $i);
} catch (\Exception $e) {
if ($fclose_check) {
fclose($fp);
}
return false;
throw $e;
}
$packet = null;
$read+= $packet_size;
@ -2216,8 +2202,12 @@ class SFTP extends SSH2
if ($fclose_check) {
fclose($fp);
}
throw new \UnexpectedValueException('Expected NET_SFTP_DATA or NET_SFTP_STATUS. '
. 'Got packet type: ' . $this->packet_type);
if ($this->channel_close) {
$this->init_sftp_connection();
} else {
throw new \UnexpectedValueException('Expected NET_SFTP_DATA or NET_SFTP_STATUS. '
. 'Got packet type: ' . $this->packet_type);
}
}
$response = null;
}
@ -2282,9 +2272,7 @@ class SFTP extends SSH2
}
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
if (!$this->send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($path), $path))) {
return false;
}
$this->send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($path), $path));
$response = $this->get_sftp_packet();
if ($this->packet_type != NET_SFTP_STATUS) {
@ -2347,9 +2335,7 @@ class SFTP extends SSH2
return false;
}
} else {
if (!$this->send_sftp_packet(NET_SFTP_REMOVE, Strings::packSSH2('s', $temp))) {
return false;
}
$this->send_sftp_packet(NET_SFTP_REMOVE, Strings::packSSH2('s', $temp));
$this->remove_from_stat_cache($temp);
$i++;
@ -2363,9 +2349,7 @@ class SFTP extends SSH2
}
}
if (!$this->send_sftp_packet(NET_SFTP_RMDIR, Strings::packSSH2('s', $path))) {
return false;
}
$this->send_sftp_packet(NET_SFTP_RMDIR, Strings::packSSH2('s', $path));
$this->remove_from_stat_cache($path);
$i++;
@ -2461,9 +2445,7 @@ class SFTP extends SSH2
public function is_readable($path)
{
$packet = Strings::packSSH2('sNN', $this->realpath($path), NET_SFTP_OPEN_READ, 0);
if (!$this->send_sftp_packet(NET_SFTP_OPEN, $packet)) {
return false;
}
$this->send_sftp_packet(NET_SFTP_OPEN, $packet);
$response = $this->get_sftp_packet();
switch ($this->packet_type) {
@ -2487,9 +2469,7 @@ class SFTP extends SSH2
public function is_writable($path)
{
$packet = Strings::packSSH2('sNN', $this->realpath($path), NET_SFTP_OPEN_WRITE, 0);
if (!$this->send_sftp_packet(NET_SFTP_OPEN, $packet)) {
return false;
}
$this->send_sftp_packet(NET_SFTP_OPEN, $packet);
$response = $this->get_sftp_packet();
switch ($this->packet_type) {
@ -2706,9 +2686,7 @@ class SFTP extends SSH2
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
$packet = Strings::packSSH2('ss', $oldname, $newname);
if (!$this->send_sftp_packet(NET_SFTP_RENAME, $packet)) {
return false;
}
$this->send_sftp_packet(NET_SFTP_RENAME, $packet);
$response = $this->get_sftp_packet();
if ($this->packet_type != NET_SFTP_STATUS) {
@ -2942,6 +2920,8 @@ class SFTP extends SSH2
*/
private function get_sftp_packet($request_id = null)
{
$this->channel_close = false;
if (isset($request_id) && isset($this->requestBuffer[$request_id])) {
$this->packet_type = $this->requestBuffer[$request_id]['packet_type'];
$temp = $this->requestBuffer[$request_id]['packet'];
@ -2966,7 +2946,7 @@ class SFTP extends SSH2
$this->packet_buffer.= $temp;
}
if (strlen($this->packet_buffer) < 4) {
return false;
throw new \RuntimeException('Packet is too small');
}
extract(unpack('Nlength', Strings::shift($this->packet_buffer, 4)));
/** @var integer $length */

View File

@ -2657,10 +2657,7 @@ class SSH2
$this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_OPEN;
$response = $this->get_channel_packet(self::CHANNEL_EXEC);
if ($response === false) {
return false;
}
$this->get_channel_packet(self::CHANNEL_EXEC);
if ($this->request_pty === true) {
$terminal_modes = pack('C', NET_SSH2_TTY_OP_END);
@ -2719,8 +2716,7 @@ class SSH2
$this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_REQUEST;
$response = $this->get_channel_packet(self::CHANNEL_EXEC);
if ($response === false) {
if (!$this->get_channel_packet(self::CHANNEL_EXEC)) {
return false;
}
@ -2783,10 +2779,7 @@ class SSH2
$this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_OPEN;
$response = $this->get_channel_packet(self::CHANNEL_SHELL);
if ($response === false) {
return false;
}
$this->get_channel_packet(self::CHANNEL_SHELL);
$terminal_modes = pack('C', NET_SSH2_TTY_OP_END);
$packet = Strings::packSSH2(
@ -2884,8 +2877,7 @@ class SSH2
$this->send_binary_packet($packet);
$response = $this->get_channel_packet($request_channel);
if ($response === false) {
if (!$this->get_channel_packet($request_channel)) {
return false;
}
@ -2937,9 +2929,9 @@ class SSH2
return Strings::shift($this->interactiveBuffer, $pos + strlen($match));
}
$response = $this->get_channel_packet($channel);
if (is_bool($response)) {
if ($response === true) {
$this->in_request_pty_exec = false;
return $response ? Strings::shift($this->interactiveBuffer, strlen($this->interactiveBuffer)) : false;
return Strings::shift($this->interactiveBuffer, strlen($this->interactiveBuffer));
}
$this->interactiveBuffer.= $response;
@ -2999,10 +2991,7 @@ class SSH2
$this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_OPEN;
$response = $this->get_channel_packet(self::CHANNEL_SUBSYSTEM);
if ($response === false) {
return false;
}
$this->get_channel_packet(self::CHANNEL_SUBSYSTEM);
$packet = Strings::packSSH2(
'CNsCs',
@ -3016,8 +3005,7 @@ class SSH2
$this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_REQUEST;
$response = $this->get_channel_packet(self::CHANNEL_SUBSYSTEM);
if ($response === false) {
if (!$this->get_channel_packet(self::CHANNEL_SUBSYSTEM)) {
return false;
}
@ -3335,7 +3323,8 @@ class SSH2
}
if (strlen($raw) < 5) {
return false;
$this->bitmap = 0;
throw new \RuntimeException('Plaintext is too short');
}
extract(unpack('Npacket_length/Cpadding_length', Strings::shift($raw, 5)));
/**
@ -3686,7 +3675,16 @@ class SSH2
/**
* Gets channel data
*
* Returns the data as a string if it's available and false if not.
* Returns the data as a string. bool(true) is returned if:
*
* - the server closes the channel
* - if the connection times out
* - if the channel status is CHANNEL_OPEN and the response was CHANNEL_OPEN_CONFIRMATION
* - if the channel status is CHANNEL_REQUEST and the response was CHANNEL_SUCCESS
*
* bool(false) is returned if:
*
* - if the channel status is CHANNEL_REQUEST and the response was CHANNEL_FAILURE
*
* @param int $client_channel
* @param bool $skip_extended