1
0
mirror of https://github.com/danog/phpseclib.git synced 2025-01-22 04:51:19 +01:00

SFTP: use Strings::packSSH2() / Strings::unpackSSH2()

This commit is contained in:
terrafrost 2019-04-03 22:09:15 -05:00
parent 0001b81950
commit cee3f3cd4a
2 changed files with 63 additions and 207 deletions

View File

@ -166,6 +166,9 @@ abstract class Strings
$result.= $element ? "\1" : "\0";
break;
case 'N':
if (is_float($element)) {
$element = (int) $element;
}
if (!is_int($element)) {
throw new \InvalidArgumentException('An integer was expected.');
}

View File

@ -420,10 +420,9 @@ class SFTP extends SSH2
$this->window_size_server_to_client[self::CHANNEL] = $this->window_size;
$packet = pack(
'CNa*N3',
$packet = Strings::packSSH2(
'CsN3',
NET_SSH2_MSG_CHANNEL_OPEN,
strlen('session'),
'session',
self::CHANNEL,
$this->window_size,
@ -439,14 +438,12 @@ class SFTP extends SSH2
return false;
}
$packet = pack(
'CNNa*CNa*',
$packet = Strings::packSSH2(
'CNsbs',
NET_SSH2_MSG_CHANNEL_REQUEST,
$this->server_channels[self::CHANNEL],
strlen('subsystem'),
'subsystem',
1,
strlen('sftp'),
true,
'sftp'
);
$this->send_binary_packet($packet);
@ -461,14 +458,12 @@ class SFTP extends SSH2
"exec sftp-server";
// we don't do $this->exec($command, false) because exec() operates on a different channel and plus the SSH_MSG_CHANNEL_OPEN that exec() does
// is redundant
$packet = pack(
'CNNa*CNa*',
$packet = Strings::packSSH2(
'CNsCs',
NET_SSH2_MSG_CHANNEL_REQUEST,
$this->server_channels[self::CHANNEL],
strlen('exec'),
'exec',
1,
strlen($command),
$command
);
$this->send_binary_packet($packet);
@ -493,28 +488,9 @@ class SFTP extends SSH2
. 'Got packet type: ' . $this->packet_type);
}
if (strlen($response) < 4) {
return false;
}
extract(unpack('Nversion', Strings::shift($response, 4)));
/** @var integer $version */
$this->version = $version;
list($this->version) = Strings::unpackSSH2('N', $response);
while (!empty($response)) {
if (strlen($response) < 4) {
return false;
}
extract(unpack('Nlength', Strings::shift($response, 4)));
/** @var integer $length */
$key = Strings::shift($response, $length);
if (strlen($response) < 4) {
return false;
}
extract(unpack('Nlength', Strings::shift($response, 4)));
/** @var integer $length */
$value = Strings::shift($response, $length);
list($key, $value) = Strings::unpackSSH2('ss', $response);
$this->extensions[$key] = $value;
}
@ -643,20 +619,14 @@ class SFTP extends SSH2
private function logError($response, $status = -1)
{
if ($status == -1) {
if (strlen($response) < 4) {
return;
}
extract(unpack('Nstatus', Strings::shift($response, 4)));
/** @var integer $status */
list($status) = Strings::unpackSSH2('N', $response);
}
$error = $this->status_codes[$status];
list($error) = $this->status_codes[$status];
if ($this->version > 2 || strlen($response) < 4) {
extract(unpack('Nlength', Strings::shift($response, 4)));
/** @var integer $length */
$this->sftp_errors[] = $error . ': ' . Strings::shift($response, $length);
if ($this->version > 2) {
list($message) = Strings::unpackSSH2('s', $response);
$this->sftp_errors[] = "$error: $message";
} else {
$this->sftp_errors[] = $error;
}
@ -685,7 +655,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, pack('Na*', strlen($path), $path))) {
if (!$this->send_sftp_packet(NET_SFTP_REALPATH, Strings::packSSH2('s', $path))) {
return false;
}
@ -695,14 +665,8 @@ class SFTP extends SSH2
// although SSH_FXP_NAME is implemented differently in SFTPv3 than it is in SFTPv4+, the following
// should work on all SFTP versions since the only part of the SSH_FXP_NAME packet the following looks
// at is the first part and that part is defined the same in SFTP versions 3 through 6.
Strings::shift($response, 4); // skip over the count - it should be 1, anyway
if (strlen($response) < 4) {
return false;
}
extract(unpack('Nlength', Strings::shift($response, 4)));
/** @var integer $length */
return Strings::shift($response, $length);
list(, $filename) = Strings::unpackSSH2('Ns', $response);
return $filename;
case NET_SFTP_STATUS:
$this->logError($response);
return false;
@ -770,7 +734,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, pack('Na*', strlen($dir), $dir))) {
if (!$this->send_sftp_packet(NET_SFTP_OPENDIR, Strings::packSSH2('s', $dir))) {
return false;
}
@ -910,7 +874,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, pack('Na*', strlen($dir), $dir))) {
if (!$this->send_sftp_packet(NET_SFTP_OPENDIR, Strings::packSSH2('s', $dir))) {
return false;
}
@ -938,34 +902,16 @@ 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, pack('Na*', strlen($handle), $handle))) {
if (!$this->send_sftp_packet(NET_SFTP_READDIR, Strings::packSSH2('s', $handle))) {
return false;
}
$response = $this->get_sftp_packet();
switch ($this->packet_type) {
case NET_SFTP_NAME:
if (strlen($response) < 4) {
return false;
}
extract(unpack('Ncount', Strings::shift($response, 4)));
/** @var integer $count */
list($count) = Strings::unpackSSH2('N', $response);
for ($i = 0; $i < $count; $i++) {
if (strlen($response) < 4) {
return false;
}
extract(unpack('Nlength', Strings::shift($response, 4)));
/** @var integer $length */
$shortname = Strings::shift($response, $length);
if (strlen($response) < 4) {
return false;
}
extract(unpack('Nlength', Strings::shift($response, 4)));
/** @var integer $length */
$longname = Strings::shift($response, $length);
list($shortname, $longname) = Strings::unpackSSH2('ss', $response);
$attributes = $this->parseAttributes($response);
if (!isset($attributes['type'])) {
$fileType = $this->parseLongname($longname);
@ -990,12 +936,7 @@ class SFTP extends SSH2
}
break;
case NET_SFTP_STATUS:
if (strlen($response) < 4) {
return false;
}
extract(unpack('Nstatus', Strings::shift($response, 4)));
/** @var integer $status */
list($status) = Strings::unpackSSH2('N', $response);
if ($status != NET_SFTP_STATUS_EOF) {
$this->logError($response, $status);
return false;
@ -1369,7 +1310,7 @@ class SFTP extends SSH2
private function stat_helper($filename, $type)
{
// SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
$packet = pack('Na*', strlen($filename), $filename);
$packet = Strings::packSSH2('s', $filename);
if (!$this->send_sftp_packet($type, $packet)) {
return false;
}
@ -1434,7 +1375,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 = pack('Na*Na*', strlen($filename), $filename, $flags, $attr);
$packet = Strings::packSSH2('sN', $filename, $flags) . $attr;
if (!$this->send_sftp_packet(NET_SFTP_OPEN, $packet)) {
return false;
}
@ -1576,7 +1517,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, pack('Na*a*', strlen($filename), $filename, $attr))) {
if (!$this->send_sftp_packet(NET_SFTP_SETSTAT, Strings::packSSH2('s', $filename) . $attr)) {
return false;
}
@ -1593,12 +1534,7 @@ class SFTP extends SSH2
. 'Got packet type: ' . $this->packet_type);
}
if (strlen($response) < 4) {
return false;
}
extract(unpack('Nstatus', Strings::shift($response, 4)));
/** @var integer $status */
list($status) = Strings::unpackSSH2('N', $response);
if ($status != NET_SFTP_STATUS_OK) {
$this->logError($response, $status);
return false;
@ -1648,7 +1584,7 @@ class SFTP extends SSH2
return false;
}
} else {
if (!$this->send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($temp), $temp, $attr))) {
if (!$this->send_sftp_packet(NET_SFTP_SETSTAT, Strings::packSSH2('s', $temp) . $attr)) {
return false;
}
@ -1663,7 +1599,7 @@ class SFTP extends SSH2
}
}
if (!$this->send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($path), $path, $attr))) {
if (!$this->send_sftp_packet(NET_SFTP_SETSTAT, Strings::packSSH2('s', $path) . $attr)) {
return false;
}
@ -1695,7 +1631,7 @@ class SFTP extends SSH2
$link = $this->realpath($link);
if (!$this->send_sftp_packet(NET_SFTP_READLINK, pack('Na*', strlen($link), $link))) {
if (!$this->send_sftp_packet(NET_SFTP_READLINK, Strings::packSSH2('s', $link))) {
return false;
}
@ -1711,23 +1647,15 @@ class SFTP extends SSH2
. 'Got packet type: ' . $this->packet_type);
}
if (strlen($response) < 4) {
return false;
}
extract(unpack('Ncount', Strings::shift($response, 4)));
/** @var integer $count */
list($count) = Strings::unpackSSH2('N', $response);
// the file isn't a symlink
if (!$count) {
return false;
}
if (strlen($response) < 4) {
return false;
}
extract(unpack('Nlength', Strings::shift($response, 4)));
/** @var integer $length */
list($filename) = Strings::unpackSSH2('s', $response);
return Strings::shift($response, $length);
return $filename;
}
/**
@ -1750,7 +1678,7 @@ class SFTP extends SSH2
//$target = $this->realpath($target);
$link = $this->realpath($link);
$packet = pack('Na*Na*', strlen($target), $target, strlen($link), $link);
$packet = Strings::packSSH2('ss', $target, $link);
if (!$this->send_sftp_packet(NET_SFTP_SYMLINK, $packet)) {
return false;
}
@ -1761,12 +1689,7 @@ class SFTP extends SSH2
. 'Got packet type: ' . $this->packet_type);
}
if (strlen($response) < 4) {
return false;
}
extract(unpack('Nstatus', Strings::shift($response, 4)));
/** @var integer $status */
list($status) = Strings::unpackSSH2('N', $response);
if ($status != NET_SFTP_STATUS_OK) {
$this->logError($response, $status);
return false;
@ -1822,7 +1745,7 @@ class SFTP extends SSH2
*/
private function mkdir_helper($dir, $attr)
{
if (!$this->send_sftp_packet(NET_SFTP_MKDIR, pack('Na*a*', strlen($dir), $dir, $attr))) {
if (!$this->send_sftp_packet(NET_SFTP_MKDIR, Strings::packSSH2('s', $dir) . $attr)) {
return false;
}
@ -1832,12 +1755,7 @@ class SFTP extends SSH2
. 'Got packet type: ' . $this->packet_type);
}
if (strlen($response) < 4) {
return false;
}
extract(unpack('Nstatus', Strings::shift($response, 4)));
/** @var integer $status */
list($status) = Strings::unpackSSH2('N', $response);
if ($status != NET_SFTP_STATUS_OK) {
$this->logError($response, $status);
return false;
@ -1865,7 +1783,7 @@ class SFTP extends SSH2
return false;
}
if (!$this->send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($dir), $dir))) {
if (!$this->send_sftp_packet(NET_SFTP_RMDIR, Strings::packSSH2('s', $dir))) {
return false;
}
@ -1875,12 +1793,7 @@ class SFTP extends SSH2
. 'Got packet type: ' . $this->packet_type);
}
if (strlen($response) < 4) {
return false;
}
extract(unpack('Nstatus', Strings::shift($response, 4)));
/** @var integer $status */
list($status) = Strings::unpackSSH2('N', $response);
if ($status != NET_SFTP_STATUS_OK) {
// presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED?
$this->logError($response, $status);
@ -1973,7 +1886,7 @@ class SFTP extends SSH2
$flags|= NET_SFTP_OPEN_TRUNCATE;
}
$packet = pack('Na*N2', strlen($remote_file), $remote_file, $flags, 0);
$packet = Strings::packSSH2('sNN', $remote_file, $flags, 0);
if (!$this->send_sftp_packet(NET_SFTP_OPEN, $packet)) {
return false;
}
@ -2115,12 +2028,7 @@ class SFTP extends SSH2
. 'Got packet type: ' . $this->packet_type);
}
if (strlen($response) < 4) {
return false;
}
extract(unpack('Nstatus', Strings::shift($response, 4)));
/** @var integer $status */
list($status) = Strings::unpackSSH2('N', $response);
if ($status != NET_SFTP_STATUS_OK) {
$this->logError($response, $status);
break;
@ -2152,12 +2060,7 @@ class SFTP extends SSH2
. 'Got packet type: ' . $this->packet_type);
}
if (strlen($response) < 4) {
return false;
}
extract(unpack('Nstatus', Strings::shift($response, 4)));
/** @var integer $status */
list($status) = Strings::unpackSSH2('N', $response);
if ($status != NET_SFTP_STATUS_OK) {
$this->logError($response, $status);
return false;
@ -2240,7 +2143,7 @@ class SFTP extends SSH2
$packet_size = $length > 0 ? min($this->max_sftp_packet, $length - $read) : $this->max_sftp_packet;
$packet = pack('Na*N3', strlen($handle), $handle, $tempoffset / 4294967296, $tempoffset, $packet_size);
$packet = Strings::packSSH2('sN3', $handle, $tempoffset / 4294967296, $tempoffset, $packet_size);
if (!$this->send_sftp_packet(NET_SFTP_READ, $packet, $i)) {
if ($fclose_check) {
fclose($fp);
@ -2361,12 +2264,7 @@ class SFTP extends SSH2
}
// if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
if (strlen($response) < 4) {
return false;
}
extract(unpack('Nstatus', Strings::shift($response, 4)));
/** @var integer $status */
list($status) = Strings::unpackSSH2('N', $response);
if ($status != NET_SFTP_STATUS_OK) {
$this->logError($response, $status);
if (!$recursive) {
@ -2419,7 +2317,7 @@ class SFTP extends SSH2
return false;
}
} else {
if (!$this->send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($temp), $temp))) {
if (!$this->send_sftp_packet(NET_SFTP_REMOVE, Strings::packSSH2('s', $temp))) {
return false;
}
$this->remove_from_stat_cache($temp);
@ -2435,7 +2333,7 @@ class SFTP extends SSH2
}
}
if (!$this->send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($path), $path))) {
if (!$this->send_sftp_packet(NET_SFTP_RMDIR, Strings::packSSH2('s', $path))) {
return false;
}
$this->remove_from_stat_cache($path);
@ -2532,9 +2430,7 @@ class SFTP extends SSH2
*/
public function is_readable($path)
{
$path = $this->realpath($path);
$packet = pack('Na*N2', strlen($path), $path, NET_SFTP_OPEN_READ, 0);
$packet = Strings::packSSH2('sNN', $this->realpath($path), NET_SFTP_OPEN_READ, 0);
if (!$this->send_sftp_packet(NET_SFTP_OPEN, $packet)) {
return false;
}
@ -2560,9 +2456,7 @@ class SFTP extends SSH2
*/
public function is_writable($path)
{
$path = $this->realpath($path);
$packet = pack('Na*N2', strlen($path), $path, NET_SFTP_OPEN_WRITE, 0);
$packet = Strings::packSSH2('sNN', $this->realpath($path), NET_SFTP_OPEN_WRITE, 0);
if (!$this->send_sftp_packet(NET_SFTP_OPEN, $packet)) {
return false;
}
@ -2781,7 +2675,7 @@ class SFTP extends SSH2
}
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
$packet = pack('Na*Na*', strlen($oldname), $oldname, strlen($newname), $newname);
$packet = Strings::packSSH2('ss', $oldname, $newname);
if (!$this->send_sftp_packet(NET_SFTP_RENAME, $packet)) {
return false;
}
@ -2793,12 +2687,7 @@ class SFTP extends SSH2
}
// if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
if (strlen($response) < 4) {
return false;
}
extract(unpack('Nstatus', Strings::shift($response, 4)));
/** @var integer $status */
list($status) = Strings::unpackSSH2('N', $response);
if ($status != NET_SFTP_STATUS_OK) {
$this->logError($response, $status);
return false;
@ -2825,12 +2714,7 @@ class SFTP extends SSH2
private function parseAttributes(&$response)
{
$attr = [];
if (strlen($response) < 4) {
//user_error('Malformed file attributes');
return [];
}
extract(unpack('Nflags', Strings::shift($response, 4)));
/** @var integer $flags */
list($flags) = Strings::unpackSSH2('N', $response);
// SFTPv4+ have a type field (a byte) that follows the above flag field
foreach ($this->attributes as $key => $value) {
@ -2842,21 +2726,15 @@ class SFTP extends SSH2
// IEEE 754 binary64 "double precision" on such platforms and
// as such can represent integers of at least 2^50 without loss
// of precision. Interpreted in filesize, 2^50 bytes = 1024 TiB.
$attr['size'] = hexdec(Hex::encode(Strings::shift($response, 8)));
list($upper, $size) = Strings::unpackSSH2('NN', $response);
$attr['size'] = $upper ? 4294967296 * $upper : 0;
$attr['size']+= $size < 0 ? ($size & 0x7FFFFFFF) + 0x80000000 : $size;
break;
case NET_SFTP_ATTR_UIDGID: // 0x00000002 (SFTPv3 only)
if (strlen($response) < 8) {
//user_error('Malformed file attributes');
return $attr;
}
$attr+= unpack('Nuid/Ngid', Strings::shift($response, 8));
list($attr['uid'], $attr['gid']) = Strings::unpackSSH2('NN', $response);
break;
case NET_SFTP_ATTR_PERMISSIONS: // 0x00000004
if (strlen($response) < 4) {
//user_error('Malformed file attributes');
return $attr;
}
$attr+= unpack('Npermissions', Strings::shift($response, 4));
list($attr['permissions']) = Strings::unpackSSH2('N', $response);
// mode == permissions; permissions was the original array key and is retained for bc purposes.
// mode was added because that's the more industry standard terminology
$attr+= ['mode' => $attr['permissions']];
@ -2866,38 +2744,13 @@ class SFTP extends SSH2
}
break;
case NET_SFTP_ATTR_ACCESSTIME: // 0x00000008
if (strlen($response) < 8) {
//user_error('Malformed file attributes');
return $attr;
}
$attr+= unpack('Natime/Nmtime', Strings::shift($response, 8));
list($attr['atime'], $attr['mtime']) = Strings::unpackSSH2('NN', $response);
break;
case NET_SFTP_ATTR_EXTENDED: // 0x80000000
if (strlen($response) < 4) {
//user_error('Malformed file attributes');
return $attr;
}
extract(unpack('Ncount', Strings::shift($response, 4)));
/** @var integer $count */
list($count) = Strings::unpack('N', $response);
for ($i = 0; $i < $count; $i++) {
if (strlen($response) < 4) {
//user_error('Malformed file attributes');
return $attr;
}
extract(unpack('Nlength', Strings::shift($response, 4)));
/** @var integer $length */
$key = Strings::shift($response, $length);
if (strlen($response) < 4) {
//user_error('Malformed file attributes');
return $attr;
}
extract(unpack('Nlength', Strings::shift($response, 4)));
/** @var integer $length */
$attr[$key] = Strings::shift($response, $length);
list($key, $value) = Strings::unpackSSH2('ss', $response);
$attr[$key] = $value;
}
}
}