From bf88ba4382f2a0e1328083dc4892fd71d5445df8 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Fri, 25 Jun 2021 11:29:03 -0500 Subject: [PATCH 01/22] incremental commit --- phpseclib/Net/SFTP.php | 174 ++++++++++++++++++++++++++++++++++------- 1 file changed, 145 insertions(+), 29 deletions(-) diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index 74a29739..9aed5b2e 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -1,4 +1,5 @@ attributes = array( 0x00000001 => 'NET_SFTP_ATTR_SIZE', - 0x00000002 => 'NET_SFTP_ATTR_UIDGID', // defined in SFTPv3, removed in SFTPv4+ + 0x00000002 => 'NET_SFTP_ATTR_UIDGID', // defined in SFTPv3, removed in SFTPv4+ 0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS', 0x00000008 => 'NET_SFTP_ATTR_ACCESSTIME', + 0x00000010 => 'NET_SFTP_ATTR_CREATETIME', // SFTPv4+ + 0x00000020 => 'NET_SFTP_ATTR_MODIFYTIME', + 0x00000040 => 'NET_SFTP_ATTR_ACL', + 0x00000080 => 'NET_SFTP_ATTR_OWNERGROUP', + 0x00000100 => 'NET_SFTP_ATTR_SUBSECOND_TIMES', + 0x00000200 => 'NET_SFTP_ATTR_BITS', // SFTPv5+ + 0x00000400 => 'NET_SFTP_ATTR_ALLOCATION_SIZE', // SFTPv6+ + 0x00000800 => 'NET_SFTP_ATTR_TEXT_HINT', + 0x00001000 => 'NET_SFTP_ATTR_MIME_TYPE', + 0x00002000 => 'NET_SFTP_ATTR_LINK_COUNT', + 0x00008000 => 'NET_SFTP_ATTR_CTIME', // 0x80000000 will yield a floating point on 32-bit systems and converting floating points to integers // yields inconsistent behavior depending on how php is compiled. so we left shift -1 (which, in // two's compliment, consists of all 1 bits) by 31. on 64-bit systems this'll yield 0xFFFFFFFF80000000. @@ -586,6 +598,8 @@ class Net_SFTP extends Net_SSH2 return false; } + $this->use_request_id = true; + if (strlen($response) < 4) { return false; } @@ -605,22 +619,6 @@ class Net_SFTP extends Net_SSH2 $this->extensions[$key] = $value; } - /* - SFTPv4+ defines a 'newline' extension. SFTPv3 seems to have unofficial support for it via 'newline@vandyke.com', - however, I'm not sure what 'newline@vandyke.com' is supposed to do (the fact that it's unofficial means that it's - not in the official SFTPv3 specs) and 'newline@vandyke.com' / 'newline' are likely not drop-in substitutes for - one another due to the fact that 'newline' comes with a SSH_FXF_TEXT bitmask whereas it seems unlikely that - 'newline@vandyke.com' would. - */ - /* - if (isset($this->extensions['newline@vandyke.com'])) { - $this->extensions['newline'] = $this->extensions['newline@vandyke.com']; - unset($this->extensions['newline@vandyke.com']); - } - */ - - $this->use_request_id = true; - /* A Note on SFTPv4/5/6 support: states the following: @@ -644,12 +642,54 @@ class Net_SFTP extends Net_SSH2 in draft-ietf-secsh-filexfer-13 would be quite impossible. As such, what Net_SFTP would do is close the channel and reopen it with a new and updated SSH_FXP_INIT packet. */ - switch ($this->version) { - case 2: - case 3: - break; - default: - return false; + if (isset($this->extensions['versions'])) { + $versions = explode(',', $this->extensions['versions']); + foreach ([6, 5, 4] as $ver) { + if (in_array($ver, $versions)) { + if ($ver === $this->version) { + break; + } + $this->version = (int) $ver; + $packet = pack('Na*Na*', strlen('version-select'), 'version-select', strlen($ver), $ver); + if (!$this->_send_sftp_packet(NET_SFTP_EXTENDED, $packet)) { + return false; + } + $response = $this->_get_sftp_packet(); + if ($this->packet_type != NET_SFTP_STATUS) { + user_error('Expected SSH_FXP_STATUS'); + return false; + } + + if (strlen($response) < 4) { + return false; + } + extract(unpack('Nstatus', $this->_string_shift($response, 4))); + if ($status != NET_SFTP_STATUS_OK) { + $this->_logError($response, $status); + return false; + } + + break; + } + } + } + + /* + SFTPv4+ defines a 'newline' extension. SFTPv3 seems to have unofficial support for it via 'newline@vandyke.com', + however, I'm not sure what 'newline@vandyke.com' is supposed to do (the fact that it's unofficial means that it's + not in the official SFTPv3 specs) and 'newline@vandyke.com' / 'newline' are likely not drop-in substitutes for + one another due to the fact that 'newline' comes with a SSH_FXF_TEXT bitmask whereas it seems unlikely that + 'newline@vandyke.com' would. + */ + /* + if (isset($this->extensions['newline@vandyke.com'])) { + $this->extensions['newline'] = $this->extensions['newline@vandyke.com']; + unset($this->extensions['newline@vandyke.com']); + } + */ + + if ($this->version < 2 || $this->version > 6) { + return false; } $this->pwd = $this->_realpath('.'); @@ -1560,6 +1600,7 @@ class Net_SFTP extends Net_SSH2 */ function chown($filename, $uid, $recursive = false) { +//zzzzzzzzz // quoting from , // "if the owner or group is specified as -1, then that ID is not changed" $attr = pack('N3', NET_SFTP_ATTR_UIDGID, $uid, -1); @@ -1988,7 +2029,6 @@ class Net_SFTP extends Net_SSH2 * * If $data is a resource then it'll be used as a resource instead. * - * * Setting $mode to NET_SFTP_CALLBACK will use $data as callback function, which gets only one parameter -- number * of bytes to return, and returns a string if there is some data or null if there is no more data * @@ -2024,6 +2064,27 @@ class Net_SFTP extends Net_SSH2 */ function put($remote_file, $data, $mode = NET_SFTP_STRING, $start = -1, $local_start = -1, $progressCallback = null) { + /* + SFTPv4 introduces the following new mode: + - SSH_FXP_TEXT (renamed to SSH_FXF_TEXT_MODE in v5) + + SFTPv5 introduced the following new modes: + - SSH_FXF_ACCESS_READ_LOCK (renamed to SSH_FXF_BLOCK_READ in v6) + - SSH_FXF_ACCESS_WRITE_LOCK (renamed to SSH_FXF_BLOCK_WRITE in v6) + - SSH_FXF_ACCESS_DELETE_LOCK (renamed to SSH_FXF_BLOCK_DELETE in v6) + + SFTPv6 introduced the following new modes: + - SSH_FXF_BLOCK_ADVISORY + - SSH_FXF_NOFOLLOW + - SSH_FXF_DELETE_ON_CLOSE + - SSH_FXF_ACCESS_AUDIT_ALARM_INFO + - SSH_FXF_ACCESS_BACKUP + - SSH_FXF_BACKUP_STREAM + - ACCESS_OVERRIDE_OWNER + + none of these are currently supported + */ + if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { return false; } @@ -2903,6 +2964,31 @@ class Net_SFTP extends Net_SSH2 return true; } + /** + * Parse Time + * + * See '7.7. Times' of draft-ietf-secsh-filexfer-13 for more info. + * + * @param string $key + * @param int $flags + * @param string $response + * @return array + * @access private + */ + function _parseTime($key, $flags, &$response) + { + if (strlen($response) < 8) { + user_error('Malformed file attributes'); + return array(); + } + $attr = array(); + $attr[$key] = hexdec(bin2hex($this->_string_shift($response, 4))); + if ($flags & NET_SFTP_ATTR_SUBSECOND_TIMES) { + $attr+= extract(unpack('N' . $key . '_nseconds', $this->_string_shift($response, 4))); + } + return $attr; + } + /** * Parse Attributes * @@ -2923,7 +3009,7 @@ class Net_SFTP extends Net_SSH2 // SFTPv4+ have a type field (a byte) that follows the above flag field foreach ($this->attributes as $key => $value) { switch ($flags & $key) { - case NET_SFTP_ATTR_SIZE: // 0x00000001 + case NET_SFTP_ATTR_SIZE: // 0x00000001 // The size attribute is defined as an unsigned 64-bit integer. // The following will use floats on 32-bit platforms, if necessary. // As can be seen in the BigInteger class, floats are generally @@ -2932,14 +3018,14 @@ class Net_SFTP extends Net_SSH2 // of precision. Interpreted in filesize, 2^50 bytes = 1024 TiB. $attr['size'] = hexdec(bin2hex($this->_string_shift($response, 8))); break; - case NET_SFTP_ATTR_UIDGID: // 0x00000002 (SFTPv3 only) + case NET_SFTP_ATTR_UIDGID: // 0x00000002 (SFTPv3 only) if (strlen($response) < 8) { user_error('Malformed file attributes'); return $attr; } $attr+= unpack('Nuid/Ngid', $this->_string_shift($response, 8)); break; - case NET_SFTP_ATTR_PERMISSIONS: // 0x00000004 + case NET_SFTP_ATTR_PERMISSIONS: // 0x00000004 if (strlen($response) < 4) { user_error('Malformed file attributes'); return $attr; @@ -2953,14 +3039,40 @@ class Net_SFTP extends Net_SSH2 $attr+= array('type' => $fileType); } break; - case NET_SFTP_ATTR_ACCESSTIME: // 0x00000008 + case NET_SFTP_ATTR_ACCESSTIME: // 0x00000008 + if ($this->version >= 4) { + $attr+= $this->_parseTime('atime', $flags, $response); + break; + } if (strlen($response) < 8) { user_error('Malformed file attributes'); return $attr; } $attr+= unpack('Natime/Nmtime', $this->_string_shift($response, 8)); break; - case NET_SFTP_ATTR_EXTENDED: // 0x80000000 + case NET_SFTP_ATTR_CREATETIME: // 0x00000010 (SFTPv4+) + $attr+= $this->_parseTime('createtime', $flags, $response); + break; + case NET_SFTP_ATTR_MODIFYTIME: // 0x00000020 + $attr+= $this->_parseTime('mtime', $flags, $response); + break; + case NET_SFTP_ATTR_ACL: // 0x00000040 + // see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-04#section-5.7 + // currently unsupported + break; + case NET_SFTP_ATTR_OWNERGROUP: // 0x00000080 + break; + case NET_SFTP_ATTR_SUBSECOND_TIMES: // 0x00000100 + break; + case NET_SFTP_ATTR_BITS: // 0x00000200 (SFTPv5+) + case NET_SFTP_ATTR_ALLOCATION_SIZE: // 0x00000400 (SFTPv6+) + case NET_SFTP_ATTR_TEXT_HINT: // 0x00000800 + case NET_SFTP_ATTR_MIME_TYPE: // 0x00001000 + case NET_SFTP_ATTR_LINK_COUNT: // 0x00002000 + case NET_SFTP_ATTR_UNTRANSLATED_NAME:// 0x00004000 + case NET_SFTP_ATTR_CTIME: // 0x00008000 + break; + case NET_SFTP_ATTR_EXTENDED: // 0x80000000 if (strlen($response) < 4) { user_error('Malformed file attributes'); return $attr; @@ -3298,6 +3410,10 @@ class Net_SFTP extends Net_SSH2 */ function getSupportedVersions() { + if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { + return false; + } + $temp = array('version' => $this->version); if (isset($this->extensions['versions'])) { $temp['extensions'] = $this->extensions['versions']; From 4cb6bcb75edc7c6ad92159245e1672a4388810e7 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Sat, 31 Jul 2021 13:00:52 -0500 Subject: [PATCH 02/22] add setPreferredVersion() and other changes --- phpseclib/Net/SFTP.php | 147 +++++++++++++++++++++++++++++++---------- 1 file changed, 113 insertions(+), 34 deletions(-) diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index 9aed5b2e..0f1039c3 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -321,6 +321,22 @@ class Net_SFTP extends Net_SSH2 */ var $channel_close = false; + /** + * Has the SFTP channel been partially negotiated? + * + * @var bool + * @access private + */ + var $partial_init = false; + + /** + * Has the SFTP channel been fully negotiated? + * + * @var bool + * @access private + */ + var $full_init = false; + /** * Default Constructor. * @@ -426,6 +442,7 @@ class Net_SFTP extends Net_SSH2 0x00000800 => 'NET_SFTP_ATTR_TEXT_HINT', 0x00001000 => 'NET_SFTP_ATTR_MIME_TYPE', 0x00002000 => 'NET_SFTP_ATTR_LINK_COUNT', + 0x00004000 => 'NET_SFTP_ATTR_UNTRANSLATED_NAME', 0x00008000 => 'NET_SFTP_ATTR_CTIME', // 0x80000000 will yield a floating point on 32-bit systems and converting floating points to integers // yields inconsistent behavior depending on how php is compiled. so we left shift -1 (which, in @@ -490,32 +507,31 @@ class Net_SFTP extends Net_SSH2 } /** - * Login + * Check a few things before SFTP functions are called * - * @param string $username * @return bool * @access public */ - function login($username) + function _precheck() { - $args = func_get_args(); - $callback = version_compare(PHP_VERSION, '5.3.0') < 0 ? - array(&$this, 'parent::login') : - 'parent::login'; - if (!call_user_func_array($callback, $args)) { + if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { return false; } - return $this->_init_sftp_connection(); + if (!$this->full_init) { + return $this->_init_sftp_connection(); + } + + return true; } /** - * (Re)initializes the SFTP channel + * Partiailly initialize an SFTP connection * * @return bool - * @access private + * @access public */ - function _init_sftp_connection() + function _partial_init_sftp_connection() { $this->window_size_server_to_client[NET_SFTP_CHANNEL] = $this->window_size; @@ -619,6 +635,23 @@ class Net_SFTP extends Net_SSH2 $this->extensions[$key] = $value; } + $this->partial_init = true; + + return true; + } + + /** + * (Re)initializes the SFTP channel + * + * @return bool + * @access private + */ + function _init_sftp_connection() + { + if (!$this->partial_init && !$this->_partial_init_sftp_connection()) { + return false; + } + /* A Note on SFTPv4/5/6 support: states the following: @@ -642,9 +675,14 @@ class Net_SFTP extends Net_SSH2 in draft-ietf-secsh-filexfer-13 would be quite impossible. As such, what Net_SFTP would do is close the channel and reopen it with a new and updated SSH_FXP_INIT packet. */ - if (isset($this->extensions['versions'])) { + if (isset($this->extensions['versions']) && (!$this->preferredVersion || $this->preferredVersion != $this->version)) { $versions = explode(',', $this->extensions['versions']); - foreach ([6, 5, 4] as $ver) { + $supported = [6, 5, 4]; + if ($this->preferredVersion) { + $supported = array_diff($supported, [$this->preferredVersion]); + array_unshift($supported, $this->preferredVersion); + } + foreach ($supported as $ver) { if (in_array($ver, $versions)) { if ($ver === $this->version) { break; @@ -880,7 +918,7 @@ class Net_SFTP extends Net_SSH2 */ function chdir($dir) { - if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { + if (!$this->_precheck()) { return false; } @@ -1037,7 +1075,7 @@ class Net_SFTP extends Net_SSH2 */ function _list($dir, $raw = true) { - if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { + if (!$this->_precheck()) { return false; } @@ -1092,13 +1130,17 @@ class Net_SFTP extends Net_SSH2 } extract(unpack('Nlength', $this->_string_shift($response, 4))); $shortname = $this->_string_shift($response, $length); - if (strlen($response) < 4) { - return false; + // SFTPv4 "removed the long filename from the names structure-- it can now be + // built from information available in the attrs structure." + if ($this->version == 3) { + if (strlen($response) < 4) { + return false; + } + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $longname = $this->_string_shift($response, $length); } - extract(unpack('Nlength', $this->_string_shift($response, 4))); - $longname = $this->_string_shift($response, $length); $attributes = $this->_parseAttributes($response); - if (!isset($attributes['type'])) { + if (!isset($attributes['type']) && $this->version == 3) { $fileType = $this->_parseLongname($longname); if ($fileType) { $attributes['type'] = $fileType; @@ -1258,7 +1300,7 @@ class Net_SFTP extends Net_SSH2 */ function size($filename) { - if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { + if (!$this->_precheck()) { return false; } @@ -1378,7 +1420,7 @@ class Net_SFTP extends Net_SSH2 */ function stat($filename) { - if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { + if (!$this->_precheck()) { return false; } @@ -1435,7 +1477,7 @@ class Net_SFTP extends Net_SSH2 */ function lstat($filename) { - if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { + if (!$this->_precheck()) { return false; } @@ -1549,7 +1591,7 @@ class Net_SFTP extends Net_SSH2 */ function touch($filename, $time = null, $atime = null) { - if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { + if (!$this->_precheck()) { return false; } @@ -1600,6 +1642,8 @@ class Net_SFTP extends Net_SSH2 */ function chown($filename, $uid, $recursive = false) { +// from v3 to v4 they made this a string +//"Made file attribute owner and group strings so they can actually be used on disparate systems." //zzzzzzzzz // quoting from , // "if the owner or group is specified as -1, then that ID is not changed" @@ -1621,6 +1665,9 @@ class Net_SFTP extends Net_SSH2 */ function chgrp($filename, $gid, $recursive = false) { +// from v3 to v4 they made this a string +//"Made file attribute owner and group strings so they can actually be used on disparate systems." +//zzzzzzzzz $attr = pack('N3', NET_SFTP_ATTR_UIDGID, -1, $gid); return $this->_setstat($filename, $attr, $recursive); @@ -1688,7 +1735,7 @@ class Net_SFTP extends Net_SSH2 */ function _setstat($filename, $attr, $recursive) { - if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { + if (!$this->_precheck()) { return false; } @@ -1818,7 +1865,7 @@ class Net_SFTP extends Net_SSH2 */ function readlink($link) { - if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { + if (!$this->_precheck()) { return false; } @@ -1868,7 +1915,14 @@ class Net_SFTP extends Net_SSH2 */ function symlink($target, $link) { - if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { +/* +SFTP v6 changes (from v5) + o Changed the SYMLINK packet to be LINK and give it the ability to + create hard links. Also change it's packet number because many + implementation implemented SYMLINK with the arguments reversed. + Hopefully the new argument names make it clear which way is which. +*/ + if (!$this->_precheck()) { return false; } @@ -1909,7 +1963,7 @@ class Net_SFTP extends Net_SSH2 */ function mkdir($dir, $mode = -1, $recursive = false) { - if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { + if (!$this->_precheck()) { return false; } @@ -1978,7 +2032,7 @@ class Net_SFTP extends Net_SSH2 */ function rmdir($dir) { - if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { + if (!$this->_precheck()) { return false; } @@ -2085,7 +2139,7 @@ class Net_SFTP extends Net_SSH2 none of these are currently supported */ - if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { + if (!$this->_precheck()) { return false; } @@ -2327,7 +2381,7 @@ class Net_SFTP extends Net_SSH2 */ function get($remote_file, $local_file = false, $offset = 0, $length = -1, $progressCallback = null) { - if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { + if (!$this->_precheck()) { return false; } @@ -2438,6 +2492,7 @@ class Net_SFTP extends Net_SSH2 } // maybe the file was successfully transferred, maybe it wasn't if ($this->channel_close) { + $this->partial_init = false; $this->_init_sftp_connection(); return false; } else { @@ -2487,7 +2542,7 @@ class Net_SFTP extends Net_SSH2 */ function delete($path, $recursive = true) { - if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { + if (!$this->_precheck()) { return false; } @@ -2923,7 +2978,12 @@ class Net_SFTP extends Net_SSH2 */ function rename($oldname, $newname) { - if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { +// in SFTP v5+ +// Add support for better control of the rename operation. +// so we'll just need to add \0\0\0\0 after +// by default if the destination file already exists it fails +// but in v5+ you can pass a SSH_FXF_RENAME_OVERWRITE flag + if (!$this->_precheck()) { return false; } @@ -3414,6 +3474,10 @@ class Net_SFTP extends Net_SSH2 return false; } + if (!$this->partial_init) { + $this->_partial_init_sftp_connection(); + } + $temp = array('version' => $this->version); if (isset($this->extensions['versions'])) { $temp['extensions'] = $this->extensions['versions']; @@ -3421,6 +3485,21 @@ class Net_SFTP extends Net_SSH2 return $temp; } + /** + * Set preferred version + * + * If you're preferred version isn't supported then the highest supported + * version of SFTP will be utilized. Set to null or false or int(0) to + * unset the preferred version + * + * @param int $version + * @access public + */ + function setPreferredVersion($version) + { + $this->preferredVersion = $version; + } + /** * Disconnect * From 092bad12c542ca48feb21030aa3634a2cc193a5a Mon Sep 17 00:00:00 2001 From: terrafrost Date: Sat, 31 Jul 2021 15:38:21 -0500 Subject: [PATCH 03/22] add attribute reading for sftpv4 --- phpseclib/Net/SFTP.php | 77 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 66 insertions(+), 11 deletions(-) diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index 0f1039c3..90f783b2 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -382,9 +382,6 @@ class Net_SFTP extends Net_SSH2 101=> 'NET_SFTP_STATUS', 102=> 'NET_SFTP_HANDLE', - /* the format of SSH_FXP_NAME changed between SFTPv3 and SFTPv4+: - SFTPv4+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.4 - pre-SFTPv4 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-7 */ 103=> 'NET_SFTP_DATA', 104=> 'NET_SFTP_NAME', 105=> 'NET_SFTP_ATTRS', @@ -430,12 +427,12 @@ class Net_SFTP extends Net_SSH2 $this->attributes = array( 0x00000001 => 'NET_SFTP_ATTR_SIZE', 0x00000002 => 'NET_SFTP_ATTR_UIDGID', // defined in SFTPv3, removed in SFTPv4+ + 0x00000080 => 'NET_SFTP_ATTR_OWNERGROUP', // defined in SFTPv4+ 0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS', 0x00000008 => 'NET_SFTP_ATTR_ACCESSTIME', 0x00000010 => 'NET_SFTP_ATTR_CREATETIME', // SFTPv4+ 0x00000020 => 'NET_SFTP_ATTR_MODIFYTIME', 0x00000040 => 'NET_SFTP_ATTR_ACL', - 0x00000080 => 'NET_SFTP_ATTR_OWNERGROUP', 0x00000100 => 'NET_SFTP_ATTR_SUBSECOND_TIMES', 0x00000200 => 'NET_SFTP_ATTR_BITS', // SFTPv5+ 0x00000400 => 'NET_SFTP_ATTR_ALLOCATION_SIZE', // SFTPv6+ @@ -1130,9 +1127,10 @@ class Net_SFTP extends Net_SSH2 } extract(unpack('Nlength', $this->_string_shift($response, 4))); $shortname = $this->_string_shift($response, $length); +echo "================= $shortname\n"; // SFTPv4 "removed the long filename from the names structure-- it can now be // built from information available in the attrs structure." - if ($this->version == 3) { + if ($this->version < 4) { if (strlen($response) < 4) { return false; } @@ -1140,7 +1138,8 @@ class Net_SFTP extends Net_SSH2 $longname = $this->_string_shift($response, $length); } $attributes = $this->_parseAttributes($response); - if (!isset($attributes['type']) && $this->version == 3) { +var_dump($attributes);// exit; + if (!isset($attributes['type']) && $this->version < 4) { $fileType = $this->_parseLongname($longname); if ($fileType) { $attributes['type'] = $fileType; @@ -3042,7 +3041,7 @@ SFTP v6 changes (from v5) return array(); } $attr = array(); - $attr[$key] = hexdec(bin2hex($this->_string_shift($response, 4))); + $attr[$key] = hexdec(bin2hex($this->_string_shift($response, 8))); if ($flags & NET_SFTP_ATTR_SUBSECOND_TIMES) { $attr+= extract(unpack('N' . $key . '_nseconds', $this->_string_shift($response, 4))); } @@ -3060,16 +3059,27 @@ SFTP v6 changes (from v5) */ function _parseAttributes(&$response) { + if ($this->version >= 4) { + $length = 5; + $format = 'Nflags/Ctype'; + } else { + $length = 4; + $format = 'Nflags'; + } + $attr = array(); - if (strlen($response) < 4) { + if (strlen($response) < $length) { user_error('Malformed file attributes'); return array(); } - extract(unpack('Nflags', $this->_string_shift($response, 4))); - // SFTPv4+ have a type field (a byte) that follows the above flag field + extract(unpack($format, $this->_string_shift($response, $length))); + if (isset($type)) { + $attr['type'] = $type; + } foreach ($this->attributes as $key => $value) { switch ($flags & $key) { case NET_SFTP_ATTR_SIZE: // 0x00000001 +echo "size\n"; // The size attribute is defined as an unsigned 64-bit integer. // The following will use floats on 32-bit platforms, if necessary. // As can be seen in the BigInteger class, floats are generally @@ -3078,7 +3088,8 @@ SFTP v6 changes (from v5) // of precision. Interpreted in filesize, 2^50 bytes = 1024 TiB. $attr['size'] = hexdec(bin2hex($this->_string_shift($response, 8))); break; - case NET_SFTP_ATTR_UIDGID: // 0x00000002 (SFTPv3 only) + case NET_SFTP_ATTR_UIDGID: // 0x00000002 (SFTPv3 or earlier) +echo "uidgid\n"; if (strlen($response) < 8) { user_error('Malformed file attributes'); return $attr; @@ -3086,6 +3097,7 @@ SFTP v6 changes (from v5) $attr+= unpack('Nuid/Ngid', $this->_string_shift($response, 8)); break; case NET_SFTP_ATTR_PERMISSIONS: // 0x00000004 +echo "perms\n"; if (strlen($response) < 4) { user_error('Malformed file attributes'); return $attr; @@ -3100,6 +3112,7 @@ SFTP v6 changes (from v5) } break; case NET_SFTP_ATTR_ACCESSTIME: // 0x00000008 +echo "accesstime\n"; if ($this->version >= 4) { $attr+= $this->_parseTime('atime', $flags, $response); break; @@ -3111,16 +3124,58 @@ SFTP v6 changes (from v5) $attr+= unpack('Natime/Nmtime', $this->_string_shift($response, 8)); break; case NET_SFTP_ATTR_CREATETIME: // 0x00000010 (SFTPv4+) +echo "createtime\n"; $attr+= $this->_parseTime('createtime', $flags, $response); break; case NET_SFTP_ATTR_MODIFYTIME: // 0x00000020 +echo "modifytime\n"; $attr+= $this->_parseTime('mtime', $flags, $response); break; case NET_SFTP_ATTR_ACL: // 0x00000040 +echo "acl\n"; // see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-04#section-5.7 // currently unsupported + if (strlen($response) < 4) { + user_error('Malformed file attributes'); + return $attr; + } + extract(unpack('Ncount', $this->_string_shift($response, 4))); + for ($i = 0; $i < $count; $i++) { + if (strlen($response) < 16) { + user_error('Malformed file attributes'); + return $attr; + } + extract(unpack('Ntype/Nflag/Nmask/Nlength', $this->_string_shift($response, 16))); + if (strlen($response) < $length) { + user_error('Malformed file attributes'); + return $attr; + } + $this->_string_shift($response, $length); // who + } break; case NET_SFTP_ATTR_OWNERGROUP: // 0x00000080 +echo "ownergroup\n"; + if (strlen($response) < 4) { + user_error('Malformed file attributes'); + return $attr; + } + extract(unpack('Nlength', $this->_string_shift($response, 4))); + if (strlen($response) < $length) { + user_error('Malformed file attributes'); + return $attr; + } + $attr['owner'] = $this->_string_shift($response, $length); + + if (strlen($response) < 4) { + user_error('Malformed file attributes'); + return $attr; + } + extract(unpack('Nlength', $this->_string_shift($response, 4))); + if (strlen($response) < $length) { + user_error('Malformed file attributes'); + return $attr; + } + $attr['group'] = $this->_string_shift($response, $length); break; case NET_SFTP_ATTR_SUBSECOND_TIMES: // 0x00000100 break; From f56158e73f719625b7fbc4537dbc8f56e660ee59 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Sun, 1 Aug 2021 23:16:31 -0500 Subject: [PATCH 04/22] add attribute reading for sftpv5 --- phpseclib/Net/SFTP.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index 90f783b2..a0a60457 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -3079,7 +3079,6 @@ SFTP v6 changes (from v5) foreach ($this->attributes as $key => $value) { switch ($flags & $key) { case NET_SFTP_ATTR_SIZE: // 0x00000001 -echo "size\n"; // The size attribute is defined as an unsigned 64-bit integer. // The following will use floats on 32-bit platforms, if necessary. // As can be seen in the BigInteger class, floats are generally @@ -3089,7 +3088,6 @@ echo "size\n"; $attr['size'] = hexdec(bin2hex($this->_string_shift($response, 8))); break; case NET_SFTP_ATTR_UIDGID: // 0x00000002 (SFTPv3 or earlier) -echo "uidgid\n"; if (strlen($response) < 8) { user_error('Malformed file attributes'); return $attr; @@ -3097,7 +3095,6 @@ echo "uidgid\n"; $attr+= unpack('Nuid/Ngid', $this->_string_shift($response, 8)); break; case NET_SFTP_ATTR_PERMISSIONS: // 0x00000004 -echo "perms\n"; if (strlen($response) < 4) { user_error('Malformed file attributes'); return $attr; @@ -3112,7 +3109,6 @@ echo "perms\n"; } break; case NET_SFTP_ATTR_ACCESSTIME: // 0x00000008 -echo "accesstime\n"; if ($this->version >= 4) { $attr+= $this->_parseTime('atime', $flags, $response); break; @@ -3124,15 +3120,12 @@ echo "accesstime\n"; $attr+= unpack('Natime/Nmtime', $this->_string_shift($response, 8)); break; case NET_SFTP_ATTR_CREATETIME: // 0x00000010 (SFTPv4+) -echo "createtime\n"; $attr+= $this->_parseTime('createtime', $flags, $response); break; case NET_SFTP_ATTR_MODIFYTIME: // 0x00000020 -echo "modifytime\n"; $attr+= $this->_parseTime('mtime', $flags, $response); break; case NET_SFTP_ATTR_ACL: // 0x00000040 -echo "acl\n"; // see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-04#section-5.7 // currently unsupported if (strlen($response) < 4) { @@ -3154,7 +3147,6 @@ echo "acl\n"; } break; case NET_SFTP_ATTR_OWNERGROUP: // 0x00000080 -echo "ownergroup\n"; if (strlen($response) < 4) { user_error('Malformed file attributes'); return $attr; @@ -3180,6 +3172,14 @@ echo "ownergroup\n"; case NET_SFTP_ATTR_SUBSECOND_TIMES: // 0x00000100 break; case NET_SFTP_ATTR_BITS: // 0x00000200 (SFTPv5+) + // see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-05#section-5.8 + // currently unsupported + if (strlen($response) < 4) { + user_error('Malformed file attributes'); + return $attr; + } + extract(unpack('Nbits', $this->_string_shift($response, 4))); + break; case NET_SFTP_ATTR_ALLOCATION_SIZE: // 0x00000400 (SFTPv6+) case NET_SFTP_ATTR_TEXT_HINT: // 0x00000800 case NET_SFTP_ATTR_MIME_TYPE: // 0x00001000 From 1f1e2794c746e81b8b1fb8c715e0f0f17b4d4623 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Thu, 26 Aug 2021 09:23:33 -0500 Subject: [PATCH 05/22] add attribute reading for sftpv6 --- phpseclib/Net/SFTP.php | 58 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index a0a60457..87f082e9 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -1127,7 +1127,6 @@ class Net_SFTP extends Net_SSH2 } extract(unpack('Nlength', $this->_string_shift($response, 4))); $shortname = $this->_string_shift($response, $length); -echo "================= $shortname\n"; // SFTPv4 "removed the long filename from the names structure-- it can now be // built from information available in the attrs structure." if ($this->version < 4) { @@ -1138,7 +1137,6 @@ echo "================= $shortname\n"; $longname = $this->_string_shift($response, $length); } $attributes = $this->_parseAttributes($response); -var_dump($attributes);// exit; if (!isset($attributes['type']) && $this->version < 4) { $fileType = $this->_parseLongname($longname); if ($fileType) { @@ -3126,6 +3124,7 @@ SFTP v6 changes (from v5) $attr+= $this->_parseTime('mtime', $flags, $response); break; case NET_SFTP_ATTR_ACL: // 0x00000040 + // access control list // see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-04#section-5.7 // currently unsupported if (strlen($response) < 4) { @@ -3174,18 +3173,65 @@ SFTP v6 changes (from v5) case NET_SFTP_ATTR_BITS: // 0x00000200 (SFTPv5+) // see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-05#section-5.8 // currently unsupported + // tells if you file is: + // readonly, system, hidden, case inensitive, archive, encrypted, compressed, sparse + // append only, immutable, sync + if (strlen($response) < 8) { + user_error('Malformed file attributes'); + return $attr; + } + extract(unpack('Nattrib-bits/Nattrib-bits-valid', $this->_string_shift($response, 8))); + break; + case NET_SFTP_ATTR_ALLOCATION_SIZE: // 0x00000400 (SFTPv6+) + // see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.4 + // represents the number of bytes htat the file consumes on the disk. will + // usually be larger than the 'size' field + $attr['allocation-size'] = hexdec(bin2hex($this->_string_shift($response, 8))); + break; + case NET_SFTP_ATTR_TEXT_HINT: // 0x00000800 + // https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.10 + // currently unsupported + // tells if file is "known text", "guessed text", "known binary", "guessed binary" + extract(unpack('Ctext-hint', $this->_string_shift($response))); + break; + case NET_SFTP_ATTR_MIME_TYPE: // 0x00001000 + // see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.11 if (strlen($response) < 4) { user_error('Malformed file attributes'); return $attr; } - extract(unpack('Nbits', $this->_string_shift($response, 4))); + extract(unpack('Nlength', $this->_string_shift($response, 4))); + if (strlen($response) < $length) { + user_error('Malformed file attributes'); + return $attr; + } + $attr['mime-type'] = $this->_string_shift($response, $length); break; - case NET_SFTP_ATTR_ALLOCATION_SIZE: // 0x00000400 (SFTPv6+) - case NET_SFTP_ATTR_TEXT_HINT: // 0x00000800 - case NET_SFTP_ATTR_MIME_TYPE: // 0x00001000 case NET_SFTP_ATTR_LINK_COUNT: // 0x00002000 + // see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.12 + if (strlen($response) < 4) { + user_error('Malformed file attributes'); + return $attr; + } + $attr+= unpack('Nlink-count', $this->_string_shift($response, 4)); + break; case NET_SFTP_ATTR_UNTRANSLATED_NAME:// 0x00004000 + // see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.13 + if (strlen($response) < 4) { + user_error('Malformed file attributes'); + return $attr; + } + extract(unpack('Nlength', $this->_string_shift($response, 4))); + if (strlen($response) < $length) { + user_error('Malformed file attributes'); + return $attr; + } + $attr['untranslated-name'] = $this->_string_shift($response, $length); + break; case NET_SFTP_ATTR_CTIME: // 0x00008000 + // 'ctime' contains the last time the file attributes were changed. The + // exact meaning of this field depends on the server. + $attr+= $this->_parseTime('ctime', $flags, $response); break; case NET_SFTP_ATTR_EXTENDED: // 0x80000000 if (strlen($response) < 4) { From dbfc76225714d28d41f832cf424ff6696d14e344 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Thu, 26 Aug 2021 18:26:57 -0500 Subject: [PATCH 06/22] update chown and chgrp to wor with sftpv4+ --- phpseclib/Net/SFTP.php | 53 ++++++++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index 87f082e9..5cf4a598 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -1629,22 +1629,47 @@ class Net_SFTP extends Net_SSH2 /** * Changes file or directory owner * + * $uid should be an int for SFTPv3 and a string for SFTPv4+. Ideally the string + * would be of the form "user@dns_domain" but it does not need to be. + * `$sftp->getSupportedVersions()['version']` will return the specific version + * that's being used. + * * Returns true on success or false on error. * * @param string $filename - * @param int $uid + * @param int|string $uid * @param bool $recursive * @return bool * @access public */ function chown($filename, $uid, $recursive = false) { -// from v3 to v4 they made this a string -//"Made file attribute owner and group strings so they can actually be used on disparate systems." -//zzzzzzzzz - // quoting from , - // "if the owner or group is specified as -1, then that ID is not changed" - $attr = pack('N3', NET_SFTP_ATTR_UIDGID, $uid, -1); + /* + quoting , + + "To avoid a representation that is tied to a particular underlying + implementation at the client or server, the use of UTF-8 strings has + been chosen. The string should be of the form "user@dns_domain". + This will allow for a client and server that do not use the same + local representation the ability to translate to a common syntax that + can be interpreted by both. In the case where there is no + translation available to the client or server, the attribute value + must be constructed without the "@"." + + phpseclib _could_ auto append the dns_domain to $uid BUT what if it shouldn't + have one? phpseclib would have no way of knowing so rather than guess phpseclib + will just use whatever value the user provided + */ + + $attr = $this->version >= 4 ? + // quoting , + // "if the owner or group is specified as -1, then that ID is not changed" + pack('N3', NET_SFTP_ATTR_UIDGID, $uid, -1) : + // quoting , + // "If either the owner or group field is zero length, the field should be + // considered absent, and no change should be made to that specific field + // during a modification operation" + pack('NNa*Na*', NET_SFTP_ATTR_OWNERGROUP, strlen($uid), $uid, 0, ''); return $this->_setstat($filename, $attr, $recursive); } @@ -1652,20 +1677,24 @@ class Net_SFTP extends Net_SSH2 /** * Changes file or directory group * + * $gid should be an int for SFTPv3 and a string for SFTPv4+. Ideally the string + * would be of the form "user@dns_domain" but it does not need to be. + * `$sftp->getSupportedVersions()['version']` will return the specific version + * that's being used. + * * Returns true on success or false on error. * * @param string $filename - * @param int $gid + * @param int|string $gid * @param bool $recursive * @return bool * @access public */ function chgrp($filename, $gid, $recursive = false) { -// from v3 to v4 they made this a string -//"Made file attribute owner and group strings so they can actually be used on disparate systems." -//zzzzzzzzz - $attr = pack('N3', NET_SFTP_ATTR_UIDGID, -1, $gid); + $attr = $this->version >= 4 ? + pack('N3', NET_SFTP_ATTR_UIDGID, $gid, -1) : + pack('NNa*Na*', NET_SFTP_ATTR_OWNERGROUP, 0, '', strlen($gid), $gid); return $this->_setstat($filename, $attr, $recursive); } From 69dba39bc92599d2e30046abdfa31862ff474a10 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Thu, 26 Aug 2021 20:18:27 -0500 Subject: [PATCH 07/22] update chown / chgrp if stmt --- phpseclib/Net/SFTP.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index 5cf4a598..1d8b9d7d 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -1661,7 +1661,7 @@ class Net_SFTP extends Net_SSH2 will just use whatever value the user provided */ - $attr = $this->version >= 4 ? + $attr = $this->version < 4 ? // quoting , // "if the owner or group is specified as -1, then that ID is not changed" pack('N3', NET_SFTP_ATTR_UIDGID, $uid, -1) : @@ -1692,7 +1692,7 @@ class Net_SFTP extends Net_SSH2 */ function chgrp($filename, $gid, $recursive = false) { - $attr = $this->version >= 4 ? + $attr = $this->version < 4 ? pack('N3', NET_SFTP_ATTR_UIDGID, $gid, -1) : pack('NNa*Na*', NET_SFTP_ATTR_OWNERGROUP, 0, '', strlen($gid), $gid); From a25d3a75d36454469f1e094a1ae07a60dc7b2b9a Mon Sep 17 00:00:00 2001 From: terrafrost Date: Fri, 27 Aug 2021 12:40:38 -0500 Subject: [PATCH 08/22] fix pre-php-5.4 syntax error --- phpseclib/Net/SFTP.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index 1d8b9d7d..7a341338 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -674,7 +674,7 @@ class Net_SFTP extends Net_SSH2 */ if (isset($this->extensions['versions']) && (!$this->preferredVersion || $this->preferredVersion != $this->version)) { $versions = explode(',', $this->extensions['versions']); - $supported = [6, 5, 4]; + $supported = array(6, 5, 4); if ($this->preferredVersion) { $supported = array_diff($supported, [$this->preferredVersion]); array_unshift($supported, $this->preferredVersion); From c9e49443707d2f4817d86c140d5904666fceaa27 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Fri, 27 Aug 2021 21:57:38 -0500 Subject: [PATCH 09/22] pwd didn't work if called first --- phpseclib/Net/SFTP.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index 7a341338..a532f2cd 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -792,6 +792,10 @@ class Net_SFTP extends Net_SSH2 */ function pwd() { + if (!$this->_precheck()) { + return false; + } + return $this->pwd; } From 19e7737eadd16a14446d50df6aee14fcece05fd9 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Sun, 29 Aug 2021 13:00:21 -0500 Subject: [PATCH 10/22] fix _precheck --- phpseclib/Net/SFTP.php | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index a532f2cd..18d741b6 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -329,14 +329,6 @@ class Net_SFTP extends Net_SSH2 */ var $partial_init = false; - /** - * Has the SFTP channel been fully negotiated? - * - * @var bool - * @access private - */ - var $full_init = false; - /** * Default Constructor. * @@ -515,7 +507,7 @@ class Net_SFTP extends Net_SSH2 return false; } - if (!$this->full_init) { + if ($this->pwd === false) { return $this->_init_sftp_connection(); } From 4102bbacccd393e0620cb16c27b6efdd9ef216b1 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Sun, 29 Aug 2021 11:41:32 -0500 Subject: [PATCH 11/22] add precheck in a few more places --- phpseclib/Net/SFTP.php | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index 18d741b6..a5baad68 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -829,6 +829,10 @@ class Net_SFTP extends Net_SSH2 */ function realpath($path) { + if (!$this->_precheck()) { + return false; + } + return $this->_realpath($path); } @@ -1293,10 +1297,6 @@ class Net_SFTP extends Net_SSH2 */ function size($filename) { - if (!$this->_precheck()) { - return false; - } - $result = $this->stat($filename); if ($result === false) { return false; @@ -2693,6 +2693,10 @@ SFTP v6 changes (from v5) function file_exists($path) { if ($this->use_stat_cache) { + if (!$this->_precheck()) { + return false; + } + $path = $this->_realpath($path); $result = $this->_query_stat_cache($path); @@ -2763,6 +2767,10 @@ SFTP v6 changes (from v5) */ function is_readable($path) { + if (!$this->_precheck()) { + return false; + } + $path = $this->_realpath($path); $packet = pack('Na*N2', strlen($path), $path, NET_SFTP_OPEN_READ, 0); @@ -2791,6 +2799,10 @@ SFTP v6 changes (from v5) */ function is_writable($path) { + if (!$this->_precheck()) { + return false; + } + $path = $this->_realpath($path); $packet = pack('Na*N2', strlen($path), $path, NET_SFTP_OPEN_WRITE, 0); @@ -2971,6 +2983,10 @@ SFTP v6 changes (from v5) */ function _get_xstat_cache_prop($path, $prop, $type) { + if (!$this->_precheck()) { + return false; + } + if ($this->use_stat_cache) { $path = $this->_realpath($path); @@ -3000,6 +3016,10 @@ SFTP v6 changes (from v5) */ function rename($oldname, $newname) { + if (!$this->_precheck()) { + return false; + } + // in SFTP v5+ // Add support for better control of the rename operation. // so we'll just need to add \0\0\0\0 after From 42b4ab065be290e090a600b47da9188970a6b5b3 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Sun, 29 Aug 2021 13:04:32 -0500 Subject: [PATCH 12/22] typo --- phpseclib/Net/SFTP.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index a5baad68..9a95042f 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -515,7 +515,7 @@ class Net_SFTP extends Net_SSH2 } /** - * Partiailly initialize an SFTP connection + * Partially initialize an SFTP connection * * @return bool * @access public From 41b5f7cfe98f014936530e13df75aacea5e1361b Mon Sep 17 00:00:00 2001 From: terrafrost Date: Sun, 29 Aug 2021 13:11:26 -0500 Subject: [PATCH 13/22] tweak version management stuff --- phpseclib/Net/SFTP.php | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index 9a95042f..4afe1967 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -196,6 +196,15 @@ class Net_SFTP extends Net_SSH2 */ var $version; + /** + * Default Server SFTP version + * + * @var int + * @see self::_initChannel() + * @access private + */ + var $defaultVersion; + /** * Current working directory * @@ -609,7 +618,7 @@ class Net_SFTP extends Net_SSH2 return false; } extract(unpack('Nversion', $this->_string_shift($response, 4))); - $this->version = $version; + $this->defaultVersion = $version; while (!empty($response)) { if (strlen($response) < 4) { return false; @@ -664,11 +673,12 @@ class Net_SFTP extends Net_SSH2 in draft-ietf-secsh-filexfer-13 would be quite impossible. As such, what Net_SFTP would do is close the channel and reopen it with a new and updated SSH_FXP_INIT packet. */ + $this->version = $this->defaultVersion; if (isset($this->extensions['versions']) && (!$this->preferredVersion || $this->preferredVersion != $this->version)) { $versions = explode(',', $this->extensions['versions']); $supported = array(6, 5, 4); if ($this->preferredVersion) { - $supported = array_diff($supported, [$this->preferredVersion]); + $supported = array_diff($supported, array($this->preferredVersion)); array_unshift($supported, $this->preferredVersion); } foreach ($supported as $ver) { @@ -3624,13 +3634,28 @@ SFTP v6 changes (from v5) $this->_partial_init_sftp_connection(); } - $temp = array('version' => $this->version); + $temp = array('version' => $this->defaultVersion); if (isset($this->extensions['versions'])) { $temp['extensions'] = $this->extensions['versions']; } return $temp; } + /** + * Get supported SFTP versions + * + * @return array + * @access public + */ + function getNegotiatedVersion() + { + if (!$this->_precheck()) { + return false; + } + + return $this->version; + } + /** * Set preferred version * From ced7dab1d5bb8ec86a35230e4a0879c97706a23e Mon Sep 17 00:00:00 2001 From: terrafrost Date: Wed, 8 Sep 2021 09:34:16 -0500 Subject: [PATCH 14/22] update symlink --- phpseclib/Net/SFTP.php | 42 ++++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index 4afe1967..9d6143c9 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -380,6 +380,7 @@ class Net_SFTP extends Net_SSH2 18 => 'NET_SFTP_RENAME', 19 => 'NET_SFTP_READLINK', 20 => 'NET_SFTP_SYMLINK', + 21 => 'NET_SFTP_LINK', 101=> 'NET_SFTP_STATUS', 102=> 'NET_SFTP_HANDLE', @@ -1947,13 +1948,6 @@ class Net_SFTP extends Net_SSH2 */ function symlink($target, $link) { -/* -SFTP v6 changes (from v5) - o Changed the SYMLINK packet to be LINK and give it the ability to - create hard links. Also change it's packet number because many - implementation implemented SYMLINK with the arguments reversed. - Hopefully the new argument names make it clear which way is which. -*/ if (!$this->_precheck()) { return false; } @@ -1961,8 +1955,37 @@ SFTP v6 changes (from v5) //$target = $this->_realpath($target); $link = $this->_realpath($link); - $packet = pack('Na*Na*', strlen($target), $target, strlen($link), $link); - if (!$this->_send_sftp_packet(NET_SFTP_SYMLINK, $packet)) { + /* quoting https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-09#section-12.1 : + + Changed the SYMLINK packet to be LINK and give it the ability to + create hard links. Also change it's packet number because many + implementation implemented SYMLINK with the arguments reversed. + Hopefully the new argument names make it clear which way is which. + */ + if ($this->version == 6) { + $type = NET_SFTP_LINK; + $packet = pack('Na*Na*C', strlen($link), $link, strlen($target), $target, 1); + } else { + $type = NET_SFTP_SYMLINK; + /* quoting http://bxr.su/OpenBSD/usr.bin/ssh/PROTOCOL#347 : + + 3.1. sftp: Reversal of arguments to SSH_FXP_SYMLINK + + When OpenSSH's sftp-server was implemented, the order of the arguments + to the SSH_FXP_SYMLINK method was inadvertently reversed. Unfortunately, + the reversal was not noticed until the server was widely deployed. Since + fixing this to follow the specification would cause incompatibility, the + current order was retained. For correct operation, clients should send + SSH_FXP_SYMLINK as follows: + + uint32 id + string targetpath + string linkpath */ + $packet = substr($this->server_identifier, 0, 13) == 'SSH-2.0-OpenSSH' ? + pack('Na*Na*', strlen($target), $target, strlen($link), $link) : + pack('Na*Na*', strlen($link), $link, strlen($target), $target); + } + if (!$this->_send_sftp_packet($type, $packet)) { return false; } @@ -3506,7 +3529,6 @@ SFTP v6 changes (from v5) $tempLength = $length; $tempLength-= strlen($this->packet_buffer); - // 256 * 1024 is what SFTP_MAX_MSG_LENGTH is set to in OpenSSH's sftp-common.h if (!$this->use_request_id && $tempLength > 256 * 1024) { user_error('Invalid SFTP packet size'); From 6303fbac9e776d20899fcfc14e01566e809cc3f7 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Thu, 9 Sep 2021 05:39:20 -0500 Subject: [PATCH 15/22] fix OpenSSH detection --- phpseclib/Net/SFTP.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index 9d6143c9..fd83f8ac 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -1981,7 +1981,7 @@ class Net_SFTP extends Net_SSH2 uint32 id string targetpath string linkpath */ - $packet = substr($this->server_identifier, 0, 13) == 'SSH-2.0-OpenSSH' ? + $packet = substr($this->server_identifier, 0, 15) == 'SSH-2.0-OpenSSH' ? pack('Na*Na*', strlen($target), $target, strlen($link), $link) : pack('Na*Na*', strlen($link), $link, strlen($target), $target); } From 5fcba4627f1c80aadcca25fc95317d8c0efa02b9 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Sat, 11 Sep 2021 21:24:43 -0500 Subject: [PATCH 16/22] update rename --- phpseclib/Net/SFTP.php | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index fd83f8ac..2c916180 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -3040,7 +3040,9 @@ class Net_SFTP extends Net_SSH2 } /** - * Renames a file or a directory on the SFTP server + * Renames a file or a directory on the SFTP server. + * + * If the file already exists this will return false * * @param string $oldname * @param string $newname @@ -3053,11 +3055,6 @@ class Net_SFTP extends Net_SSH2 return false; } -// in SFTP v5+ -// Add support for better control of the rename operation. -// so we'll just need to add \0\0\0\0 after -// by default if the destination file already exists it fails -// but in v5+ you can pass a SSH_FXF_RENAME_OVERWRITE flag if (!$this->_precheck()) { return false; } @@ -3070,6 +3067,18 @@ class Net_SFTP extends Net_SSH2 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3 $packet = pack('Na*Na*', strlen($oldname), $oldname, strlen($newname), $newname); + if ($this->version >= 5) { + /* quoting https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-05#section-6.5 , + + 'flags' is 0 or a combination of: + + SSH_FXP_RENAME_OVERWRITE 0x00000001 + SSH_FXP_RENAME_ATOMIC 0x00000002 + SSH_FXP_RENAME_NATIVE 0x00000004 + + (none of these are currently supported) */ + $packet.= "\0\0\0\0"; + } if (!$this->_send_sftp_packet(NET_SFTP_RENAME, $packet)) { return false; } From 9ae2b0030d460b39a48d10ded52aa704c6485b51 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Mon, 13 Sep 2021 09:08:48 -0500 Subject: [PATCH 17/22] SFTP: update get(), put() and touch() --- phpseclib/Net/SFTP.php | 95 ++++++++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 41 deletions(-) diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index 2c916180..f414d3e8 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -358,9 +358,6 @@ class Net_SFTP extends Net_SSH2 $this->packet_types = array( 1 => 'NET_SFTP_INIT', 2 => 'NET_SFTP_VERSION', - /* the format of SSH_FXP_OPEN changed between SFTPv4 and SFTPv5+: - SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.1 - pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3 */ 3 => 'NET_SFTP_OPEN', 4 => 'NET_SFTP_CLOSE', 5 => 'NET_SFTP_READ', @@ -374,9 +371,6 @@ class Net_SFTP extends Net_SSH2 15 => 'NET_SFTP_RMDIR', 16 => 'NET_SFTP_REALPATH', 17 => 'NET_SFTP_STAT', - /* the format of SSH_FXP_RENAME changed between SFTPv4 and SFTPv5+: - SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3 - pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.5 */ 18 => 'NET_SFTP_RENAME', 19 => 'NET_SFTP_READLINK', 20 => 'NET_SFTP_SYMLINK', @@ -449,16 +443,38 @@ class Net_SFTP extends Net_SSH2 // that's not a problem, however, and 'anded' and a 32-bit number, as all the leading 1 bits are ignored. (-1 << 31) & 0xFFFFFFFF => 'NET_SFTP_ATTR_EXTENDED' ); - // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3 - // the flag definitions change somewhat in SFTPv5+. if SFTPv5+ support is added to this library, maybe name - // the array for that $this->open5_flags and similarly alter the constant names. $this->open_flags = array( 0x00000001 => 'NET_SFTP_OPEN_READ', 0x00000002 => 'NET_SFTP_OPEN_WRITE', 0x00000004 => 'NET_SFTP_OPEN_APPEND', 0x00000008 => 'NET_SFTP_OPEN_CREATE', 0x00000010 => 'NET_SFTP_OPEN_TRUNCATE', - 0x00000020 => 'NET_SFTP_OPEN_EXCL' + 0x00000020 => 'NET_SFTP_OPEN_EXCL', + 0x00000040 => 'NET_SFTP_OPEN_TEXT' // defined in SFTPv4 + ); + // SFTPv5+ changed the flags up: + // https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-8.1.1.3 + $this->open_flags5 = array( + // when SSH_FXF_ACCESS_DISPOSITION is a 3 bit field that controls how the file is opened + 0x00000000 => 'NET_SFTP_OPEN_CREATE_NEW', + 0x00000001 => 'NET_SFTP_OPEN_CREATE_TRUNCATE', + 0x00000002 => 'NET_SFTP_OPEN_OPEN_EXISTING', + 0x00000003 => 'NET_SFTP_OPEN_OPEN_OR_CREATE', + 0x00000004 => 'NET_SFTP_OPEN_TRUNCATE_EXISTING', + // the rest of the flags are not supported + 0x00000008 => 'NET_SFTP_OPEN_APPEND_DATA', // "the offset field of SS_FXP_WRITE requests is ignored" + 0x00000010 => 'NET_SFTP_OPEN_APPEND_DATA_ATOMIC', + 0x00000020 => 'NET_SFTP_OPEN_TEXT_MODE', + 0x00000040 => 'NET_SFTP_OPEN_BLOCK_READ', + 0x00000080 => 'NET_SFTP_OPEN_BLOCK_WRITE', + 0x00000100 => 'NET_SFTP_OPEN_BLOCK_DELETE', + 0x00000200 => 'NET_SFTP_OPEN_BLOCK_ADVISORY', + 0x00000400 => 'NET_SFTP_OPEN_NOFOLLOW', + 0x00000800 => 'NET_SFTP_OPEN_DELETE_ON_CLOSE', + 0x00001000 => 'NET_SFTP_OPEN_ACCESS_AUDIT_ALARM_INFO', + 0x00002000 => 'NET_SFTP_OPEN_ACCESS_BACKUP', + 0x00004000 => 'NET_SFTP_OPEN_BACKUP_STREAM', + 0x00008000 => 'NET_SFTP_OPEN_OVERRIDE_OWNER', ); // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2 // see Net_SFTP::_parseLongname() for an explanation @@ -480,6 +496,7 @@ class Net_SFTP extends Net_SSH2 $this->status_codes, $this->attributes, $this->open_flags, + $this->open_flags5, $this->file_types ); @@ -1611,9 +1628,12 @@ class Net_SFTP extends Net_SSH2 $atime = $time; } - $flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE | NET_SFTP_OPEN_EXCL; - $attr = pack('N3', NET_SFTP_ATTR_ACCESSTIME, $time, $atime); - $packet = pack('Na*Na*', strlen($filename), $filename, $flags, $attr); + $packet = pack('Na*', strlen($filename), $filename); + $packet.= $this->version >= 5 ? + pack('N2', 0, NET_SFTP_OPEN_OPEN_EXISTING) : + pack('N', NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE | NET_SFTP_OPEN_EXCL); + $packet.= pack('N3', NET_SFTP_ATTR_ACCESSTIME, $time, $atime); + if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) { return false; } @@ -2173,27 +2193,6 @@ class Net_SFTP extends Net_SSH2 */ function put($remote_file, $data, $mode = NET_SFTP_STRING, $start = -1, $local_start = -1, $progressCallback = null) { - /* - SFTPv4 introduces the following new mode: - - SSH_FXP_TEXT (renamed to SSH_FXF_TEXT_MODE in v5) - - SFTPv5 introduced the following new modes: - - SSH_FXF_ACCESS_READ_LOCK (renamed to SSH_FXF_BLOCK_READ in v6) - - SSH_FXF_ACCESS_WRITE_LOCK (renamed to SSH_FXF_BLOCK_WRITE in v6) - - SSH_FXF_ACCESS_DELETE_LOCK (renamed to SSH_FXF_BLOCK_DELETE in v6) - - SFTPv6 introduced the following new modes: - - SSH_FXF_BLOCK_ADVISORY - - SSH_FXF_NOFOLLOW - - SSH_FXF_DELETE_ON_CLOSE - - SSH_FXF_ACCESS_AUDIT_ALARM_INFO - - SSH_FXF_ACCESS_BACKUP - - SSH_FXF_BACKUP_STREAM - - ACCESS_OVERRIDE_OWNER - - none of these are currently supported - */ - if (!$this->_precheck()) { return false; } @@ -2205,10 +2204,14 @@ class Net_SFTP extends Net_SSH2 $this->_remove_from_stat_cache($remote_file); - $flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE; - // according to the SFTP specs, NET_SFTP_OPEN_APPEND should "force all writes to append data at the end of the file." - // in practice, it doesn't seem to do that. - //$flags|= ($mode & NET_SFTP_RESUME) ? NET_SFTP_OPEN_APPEND : NET_SFTP_OPEN_TRUNCATE; + if ($this->version >= 5) { + $flags = NET_SFTP_OPEN_OPEN_OR_CREATE; + } else { + $flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE; + // according to the SFTP specs, NET_SFTP_OPEN_APPEND should "force all writes to append data at the end of the file." + // in practice, it doesn't seem to do that. + //$flags|= ($mode & NET_SFTP_RESUME) ? NET_SFTP_OPEN_APPEND : NET_SFTP_OPEN_TRUNCATE; + } if ($start >= 0) { $offset = $start; @@ -2218,10 +2221,17 @@ class Net_SFTP extends Net_SSH2 $offset = $size !== false ? $size : 0; } else { $offset = 0; - $flags|= NET_SFTP_OPEN_TRUNCATE; + if ($this->version >= 5) { + $flags = NET_SFTP_OPEN_CREATE_TRUNCATE; + } else { + $flags|= NET_SFTP_OPEN_TRUNCATE; + } } - $packet = pack('Na*N2', strlen($remote_file), $remote_file, $flags, 0); + $packet = pack('Na*', strlen($remote_file), $remote_file); + $packet.= $this->version >= 5 ? + pack('N3', 0, $flags, 0) : + pack('N2', $flags, 0); if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) { return false; } @@ -2445,7 +2455,10 @@ class Net_SFTP extends Net_SSH2 return false; } - $packet = pack('Na*N2', strlen($remote_file), $remote_file, NET_SFTP_OPEN_READ, 0); + $packet = pack('Na*', strlen($remote_file), $remote_file); + $packet.= $this->version >= 5 ? + pack('N3', 0, NET_SFTP_OPEN_OPEN_EXISTING, 0) : + pack('N2', NET_SFTP_OPEN_READ, 0); if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) { return false; } From bb17b35d3e58890b66c113ca50f514263b479621 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Tue, 14 Sep 2021 09:05:41 -0500 Subject: [PATCH 18/22] preferentially use SFTPv3, since that's waaaay more tested than any other version --- phpseclib/Net/SFTP.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index f414d3e8..15449f5b 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -205,6 +205,15 @@ class Net_SFTP extends Net_SSH2 */ var $defaultVersion; + /** + * Preferred SFTP version + * + * @var int + * @see self::_initChannel() + * @access private + */ + var $preferredVersion = 3; + /** * Current working directory * From 4769de45f06d43ab381819c23f8afa98b8ab65d5 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Tue, 14 Sep 2021 09:11:53 -0500 Subject: [PATCH 19/22] comment is no longer valid --- phpseclib/Net/SFTP.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index 15449f5b..77a8e509 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -1,6 +1,4 @@ Date: Tue, 14 Sep 2021 20:33:11 -0500 Subject: [PATCH 20/22] attr wasn't defined --- phpseclib/Net/SFTP.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index 77a8e509..7c3f37b5 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -1635,11 +1635,13 @@ class Net_SFTP extends Net_SSH2 $atime = $time; } + $attr = pack('N3', NET_SFTP_ATTR_ACCESSTIME, $time, $atime); + $packet = pack('Na*', strlen($filename), $filename); $packet.= $this->version >= 5 ? pack('N2', 0, NET_SFTP_OPEN_OPEN_EXISTING) : pack('N', NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE | NET_SFTP_OPEN_EXCL); - $packet.= pack('N3', NET_SFTP_ATTR_ACCESSTIME, $time, $atime); + $packet.= $attr; if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) { return false; From d48bdde5c666256baa39b4128bdc83db3e212789 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Tue, 14 Sep 2021 20:34:30 -0500 Subject: [PATCH 21/22] update comment --- phpseclib/Net/SFTP.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index 7c3f37b5..2a89cca1 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -4,9 +4,7 @@ * * PHP versions 4 and 5 * - * Currently only supports SFTPv2 and v3, which, according to wikipedia.org, "is the most widely used version, - * implemented by the popular OpenSSH SFTP server". If you want SFTPv4/5/6 support, provide me with access - * to an SFTPv4/5/6 server. + * Supports SFTPv2/3/4/5/6. Defaults to v3. * * The API for this library is modeled after the API from PHP's {@link http://php.net/book.ftp FTP extension}. * From f6ae13a0922db3b5be70eeb0894fa242f068f131 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Wed, 15 Sep 2021 08:33:31 -0500 Subject: [PATCH 22/22] fixes for setting atime and mtime --- phpseclib/Net/SFTP.php | 55 +++++++++++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index 2a89cca1..79265bde 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -1,4 +1,5 @@ 'NET_SFTP_WRITE', 7 => 'NET_SFTP_LSTAT', 9 => 'NET_SFTP_SETSTAT', + 10 => 'NET_SFTP_FSETSTAT', 11 => 'NET_SFTP_OPENDIR', 12 => 'NET_SFTP_READDIR', 13 => 'NET_SFTP_REMOVE', @@ -1633,7 +1635,15 @@ class Net_SFTP extends Net_SSH2 $atime = $time; } - $attr = pack('N3', NET_SFTP_ATTR_ACCESSTIME, $time, $atime); + if ($this->version < 4) { + $attr = pack('N3', NET_SFTP_ATTR_ACCESSTIME, $atime, $time); + } else { + $attr = pack('N5', + NET_SFTP_ATTR_ACCESSTIME | NET_SFTP_ATTR_MODIFYTIME, + $atime / 4294967296, $atime, + $time / 4294967296, $time + ); + } $packet = pack('Na*', strlen($filename), $filename); $packet.= $this->version >= 5 ? @@ -1813,9 +1823,10 @@ class Net_SFTP extends Net_SSH2 return $result; } - // 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, pack('Na*a*', strlen($filename), $filename, $attr))) { + $packet = $this->version >= 4 ? + pack('Na*a*Ca*', strlen($filename), $filename, substr($attr, 0, 4), NET_SFTP_TYPE_UNKNOWN, substr($attr, 4)) : + pack('Na*a*', strlen($filename), $filename, $attr); + if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, $packet)) { return false; } @@ -1885,7 +1896,10 @@ class Net_SFTP extends Net_SSH2 return false; } } else { - if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($temp), $temp, $attr))) { + $packet = $this->version >= 4 ? + pack('Na*Ca*', strlen($temp), $temp, NET_SFTP_TYPE_UNKNOWN, $attr) : + pack('Na*a*', strlen($temp), $temp, $attr); + if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, $packet)) { return false; } @@ -1900,7 +1914,10 @@ class Net_SFTP extends Net_SSH2 } } - if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($path), $path, $attr))) { + $packet = $this->version >= 4 ? + pack('Na*Ca*', strlen($temp), $temp, NET_SFTP_TYPE_UNKNOWN, $attr) : + pack('Na*a*', strlen($temp), $temp, $attr); + if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, $packet)) { return false; } @@ -2347,6 +2364,8 @@ class Net_SFTP extends Net_SSH2 } } + $result = $this->_close_handle($handle); + if (!$this->_read_put_responses($i)) { if ($mode & NET_SFTP_LOCAL_FILE) { fclose($fp); @@ -2356,17 +2375,29 @@ class Net_SFTP extends Net_SSH2 } if ($mode & NET_SFTP_LOCAL_FILE) { - if ($this->preserveTime) { - $stat = fstat($fp); - $this->touch($remote_file, $stat['mtime'], $stat['atime']); - } - if (isset($fp) && is_resource($fp)) { fclose($fp); } + + if ($this->preserveTime) { + $stat = stat($data); + if ($this->version < 4) { + $attr = pack('N3', NET_SFTP_ATTR_ACCESSTIME, $stat['atime'], $stat['mtime']); + } else { + $attr = pack('N5', + NET_SFTP_ATTR_ACCESSTIME | NET_SFTP_ATTR_MODIFYTIME, + $stat['atime'] / 4294967296, $stat['atime'], + $stat['mtime'] / 4294967296, $stat['mtime'] + ); + } + + if (!$this->_setstat($remote_file, $attr, false)) { + user_error('Error setting file time'); + } + } } - return $this->_close_handle($handle); + return $result; } /**